1212// §3 FORWARD DECLARATIONS 147– 226
1313// §4 WEIGHT ROUNDING 227– 246
1414// §5 GLOBAL OBJECTS 247– 260
15- // §6 CONFIGURATION VARIABLES 261– 442
16- // §7 OLED DISPLAY 443 – 560
17- // §8 CLOUD PARSING 561 – 575
18- // §9 WIFI SETUP 576 – 848
19- // §10 LITTLEFS 849 – 893
20- // §11 FIREBASE AUTHENTICATION 894– 1087
21- // §12 FIRESTORE SCALE HEARTBEAT & SYNC 1088– 2039
22- // §13 WEBSOCKET 2040– 2066
23- // §14 5 — CLOUD WORKER TASK (non-blocking Firestore on core 0) 2067– 2095
24- // §15 5 — UNIFIED WS FRAME BUILDER 2096– 2134
25- // §16 WEIGHT FILTER HELPERS 2135– 2149
26- // §17 POST-SEND STATE RESET (shared by all send paths) 2150– 2168
27- // §18 SHARED WEIGHT PUSH HANDLER (used by /api/weight and /api/push-weight) 2169– 2241
28- // §19 WEB SERVER 2242– 2941
29- // §20 CLOUD COMMUNICATION 2942– 3124
30- // §21 WEIGH WORKFLOW (IDLE → SCANNING → STABLE_WAIT → SENDING) 3125– 3340
31- // §22 mDNS 3341– 3378
32- // §23 SCALE 3379– 3470
33- // §24 RFID 3471– 3800
34- // §25 OTA — Over-the-air firmware + filesystem update 3801– 4169
35- // §26 SETUP & LOOP 4170– 4542
15+ // §6 CONFIGURATION VARIABLES 261– 447
16+ // §7 OLED DISPLAY 448 – 565
17+ // §8 CLOUD PARSING 566 – 580
18+ // §9 WIFI SETUP 581 – 853
19+ // §10 LITTLEFS 854 – 898
20+ // §11 FIREBASE AUTHENTICATION 899– 1092
21+ // §12 FIRESTORE SCALE HEARTBEAT & SYNC 1093– 2057
22+ // §13 WEBSOCKET 2058– 2084
23+ // §14 5 — CLOUD WORKER TASK (non-blocking Firestore on core 0) 2085– 2119
24+ // §15 5 — UNIFIED WS FRAME BUILDER 2120– 2158
25+ // §16 WEIGHT FILTER HELPERS 2159– 2173
26+ // §17 POST-SEND STATE RESET (shared by all send paths) 2174– 2192
27+ // §18 SHARED WEIGHT PUSH HANDLER (used by /api/weight and /api/push-weight) 2193– 2265
28+ // §19 WEB SERVER 2266– 2965
29+ // §20 CLOUD COMMUNICATION 2966– 3148
30+ // §21 WEIGH WORKFLOW (IDLE → SCANNING → STABLE_WAIT → SENDING) 3149– 3364
31+ // §22 mDNS 3365– 3402
32+ // §23 SCALE 3403– 3494
33+ // §24 RFID 3495– 3824
34+ // §25 OTA — Over-the-air firmware + filesystem update 3825– 4193
35+ // §26 SETUP & LOOP 4194– 4566
3636//
3737// To regenerate this block: ./scripts/update_toc.sh
3838// ─── TOC END ───────────────────────────────────────────────
@@ -420,6 +420,11 @@ static volatile bool gContainerFetchPending = false;
420420static volatile bool gContainerFetchDone = false ;
421421static volatile float gContainerFetchResult = 0 .0f ;
422422static String gContainerFetchUID = " " ;
423+ // Twin tag prefetch — filled by cloudWorkerTask right after container fetch,
424+ // consumed by pushWeightToCloud to avoid a second round-trip during SENDING.
425+ static String gTwinFetchUID = " " ;
426+ static String gTwinFetchResult = " " ;
427+ static volatile bool gTwinFetchDone = false ;
423428
424429static volatile bool gCloudSendPending = false ;
425430static volatile bool gCloudSendDone = false ;
@@ -1584,7 +1589,14 @@ float readInventoryContainerWeight(const String& uid, float* outMeasureGr) {
15841589// Rounds to 1 decimal place
15851590float computeWeightAvailable (float raw_grams, const String& uid_a) {
15861591 float measure_gr = 0 .0f ;
1587- float container = readInventoryContainerWeight (uid_a, &measure_gr);
1592+ float container;
1593+ if (wfContainerFetched && wfContainerWeight > 0 .0f ) {
1594+ // Use the value prefetched during SCANNING — avoids a duplicate HTTP call
1595+ container = wfContainerWeight;
1596+ netLog (" CONTAINER cache hit=" + String (container,1 ) + " g uid=" + uid_a);
1597+ } else {
1598+ container = readInventoryContainerWeight (uid_a, &measure_gr);
1599+ }
15881600 gLastWeightCalcReliable = true ;
15891601
15901602 // Guardrail: if raw is clearly below container, this sample is likely invalid
@@ -1808,7 +1820,13 @@ bool updateScaleLastSpool(const String& uid_a, const String& uid_b = "", float w
18081820 // Case 1: Single tag (or same tag detected twice)
18091821 if (uidBHex.length () == 0 || uidBHex == uidAHex) {
18101822 String twin_a = " " ;
1811- readInventoryDocTwinTag (uidAHex, twin_a);
1823+ if (gTwinFetchDone && gTwinFetchUID == uidAHex) {
1824+ twin_a = gTwinFetchResult ;
1825+ gTwinFetchDone = false ; // consume
1826+ netLog (" TWIN cache hit=" + (twin_a.length () > 0 ? twin_a : " none" ) + " uid=" + uidAHex);
1827+ } else {
1828+ readInventoryDocTwinTag (uidAHex, twin_a);
1829+ }
18121830
18131831 // If uid_a has a twin_tag_uid, update both docs with same payload (§6.2)
18141832 if (twin_a.length () > 0 ) {
@@ -2079,6 +2097,12 @@ static void cloudWorkerTask(void* /*param*/) {
20792097 String uid = gContainerFetchUID ; // local copy (thread-safe read)
20802098 float cw = readInventoryContainerWeight (uid);
20812099 gContainerFetchResult = cw;
2100+ // Prefetch twin tag for the same UID right now — same HTTP round-trip budget,
2101+ // so pushWeightToCloud won't need a separate TWIN_GET during SENDING.
2102+ gTwinFetchUID = uid;
2103+ gTwinFetchResult = " " ;
2104+ readInventoryDocTwinTag (uid, gTwinFetchResult );
2105+ gTwinFetchDone = true ;
20822106 gContainerFetchPending = false ; // clear before Done for coherent state
20832107 gContainerFetchDone = true ;
20842108 }
@@ -2154,7 +2178,7 @@ void resetWeightFilters() {
21542178static void resetAfterSuccessfulSend (int shownWeight) {
21552179 currentOledState = OLED_STATE_IDLE ;
21562180 oledStateChangeMs = millis ();
2157- lastUID = " " ; lastUID2 = " " ; lastUIDLeft = " " ; lastUIDRight = " " ;
2181+ lastUID = " " ; lastUID2 = " " ; lastUIDLeft = " " ; lastUIDRight = " " ; gTwinFetchDone = false ; gTwinFetchUID = " " ; gTwinFetchResult = " " ;
21582182 lastUIDHex = " " ; lastUID2Hex = " " ;
21592183 firstUidDetectedMs = 0 ; firstUidPauseUntilMs = 0 ;
21602184 stableSinceMs = 0 ; stableCandidate = NAN ;
@@ -2809,7 +2833,7 @@ void setupWebServer() {
28092833 resetSlopeBuffer ();
28102834 stopServoSearch ();
28112835 lastUID = " " ; lastUID2 = " " ; lastUIDHex = " " ; lastUID2Hex = " " ;
2812- lastUIDLeft = " " ; lastUIDRight = " " ;
2836+ lastUIDLeft = " " ; lastUIDRight = " " ; gTwinFetchDone = false ; gTwinFetchUID = " " ; gTwinFetchResult = " " ;
28132837 firstUidDetectedMs = 0 ; firstUidPauseUntilMs = 0 ;
28142838 stableSinceMs = 0 ; stableCandidate = NAN ;
28152839 wfContainerWeight = 0 .0f ; wfContainerFetched = false ;
@@ -2881,7 +2905,7 @@ void setupWebServer() {
28812905 resetWeightFilters ();
28822906 autoTarePending = false ;
28832907 autoTareStableSinceMs = 0 ;
2884- lastUID = " " ; lastUID2 = " " ; lastUIDLeft = " " ; lastUIDRight = " " ;
2908+ lastUID = " " ; lastUID2 = " " ; lastUIDLeft = " " ; lastUIDRight = " " ; gTwinFetchDone = false ; gTwinFetchUID = " " ; gTwinFetchResult = " " ;
28852909 float offset = scale.get_offset ();
28862910 prefs.begin (" config" , false );
28872911 prefs.putFloat (" tareFactor" , offset);
@@ -3323,7 +3347,7 @@ void handleWeighWorkflow(float w) {
33233347 if (removingNow || w < MIN_WEIGHT_TO_SEND_G ) {
33243348 wfPhase = WF_IDLE ;
33253349 lastUID = " " ; lastUID2 = " " ; lastUIDHex = " " ; lastUID2Hex = " " ;
3326- lastUIDLeft = " " ; lastUIDRight = " " ;
3350+ lastUIDLeft = " " ; lastUIDRight = " " ; gTwinFetchDone = false ; gTwinFetchUID = " " ; gTwinFetchResult = " " ;
33273351 wfContainerFetched = false ; wfContainerWeight = 0 .0f ;
33283352 gLastContainer = 0 .0f ; // clear container so web UI hides filament row
33293353 stableCandidate = NAN ; stableSinceMs = 0 ;
@@ -4480,7 +4504,7 @@ void loop() {
44804504 firstUidDetectedMs = 0 ;
44814505 firstUidPauseUntilMs = 0 ;
44824506 lastUID = " " ; lastUID2 = " " ; lastUIDHex = " " ; lastUID2Hex = " " ;
4483- lastUIDLeft = " " ; lastUIDRight = " " ;
4507+ lastUIDLeft = " " ; lastUIDRight = " " ; gTwinFetchDone = false ; gTwinFetchUID = " " ; gTwinFetchResult = " " ;
44844508 wfPhase = WF_IDLE ;
44854509 wfContainerWeight = 0 .0f ; wfContainerFetched = false ;
44864510 currentOledState = OLED_STATE_IDLE ;
@@ -4495,7 +4519,7 @@ void loop() {
44954519 Serial.printf (" [REMOVAL] Sent: %.2f, Now: %.2f\n " , gLastSentWeight , weight);
44964520 gLastSentWeight = NAN ; gLastCloudWeight = NAN ; gCloudWeightSetMs = 0 ;
44974521 lastUID = " " ; lastUID2 = " " ; lastUIDHex = " " ; lastUID2Hex = " " ;
4498- lastUIDLeft = " " ; lastUIDRight = " " ;
4522+ lastUIDLeft = " " ; lastUIDRight = " " ; gTwinFetchDone = false ; gTwinFetchUID = " " ; gTwinFetchResult = " " ;
44994523 firstUidDetectedMs = 0 ; firstUidPauseUntilMs = 0 ;
45004524 rfidLockedForCurrentLoad = true ; // Keep RFID locked during removal hold
45014525 servoLockedUntilAutotare = false ; // CRITICAL: unlock servo for next measurement
0 commit comments