1212// §3 FORWARD DECLARATIONS 147– 226
1313// §4 WEIGHT ROUNDING 227– 246
1414// §5 GLOBAL OBJECTS 247– 260
15- // §6 CONFIGURATION VARIABLES 261– 454
16- // §7 OLED DISPLAY 455 – 572
17- // §8 CLOUD PARSING 573 – 587
18- // §9 WIFI SETUP 588 – 860
19- // §10 LITTLEFS 861 – 905
20- // §11 FIREBASE AUTHENTICATION 906– 1099
21- // §12 FIRESTORE SCALE HEARTBEAT & SYNC 1100– 2030
22- // §13 WEBSOCKET 2031– 2057
23- // §14 5 — CLOUD WORKER TASK (non-blocking Firestore on core 0) 2058– 2100
24- // §15 5 — UNIFIED WS FRAME BUILDER 2101– 2140
25- // §16 WEIGHT FILTER HELPERS 2141– 2155
26- // §17 POST-SEND STATE RESET (shared by all send paths) 2156– 2174
27- // §18 SHARED WEIGHT PUSH HANDLER (used by /api/weight and /api/push-weight) 2175– 2247
28- // §19 WEB SERVER 2248– 2948
29- // §20 CLOUD COMMUNICATION 2949– 3131
30- // §21 WEIGH WORKFLOW (IDLE → SCANNING → STABLE_WAIT → SENDING) 3132– 3367
31- // §22 mDNS 3368– 3405
32- // §23 SCALE 3406– 3497
33- // §24 RFID 3498– 3827
34- // §25 OTA — Over-the-air firmware + filesystem update 3828– 4196
35- // §26 SETUP & LOOP 4197– 4569
15+ // §6 CONFIGURATION VARIABLES 261– 455
16+ // §7 OLED DISPLAY 456 – 573
17+ // §8 CLOUD PARSING 574 – 588
18+ // §9 WIFI SETUP 589 – 861
19+ // §10 LITTLEFS 862 – 906
20+ // §11 FIREBASE AUTHENTICATION 907– 1100
21+ // §12 FIRESTORE SCALE HEARTBEAT & SYNC 1101– 2031
22+ // §13 WEBSOCKET 2032– 2058
23+ // §14 5 — CLOUD WORKER TASK (non-blocking Firestore on core 0) 2059– 2118
24+ // §15 5 — UNIFIED WS FRAME BUILDER 2119– 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– 2966
29+ // §20 CLOUD COMMUNICATION 2967– 3149
30+ // §21 WEIGH WORKFLOW (IDLE → SCANNING → STABLE_WAIT → SENDING) 3150– 3385
31+ // §22 mDNS 3386– 3423
32+ // §23 SCALE 3424– 3515
33+ // §24 RFID 3516– 3845
34+ // §25 OTA — Over-the-air firmware + filesystem update 3846– 4214
35+ // §26 SETUP & LOOP 4215– 4591
3636//
3737// To regenerate this block: ./scripts/update_toc.sh
3838// ─── TOC END ───────────────────────────────────────────────
@@ -422,11 +422,12 @@ static volatile bool gContainerFetchPending = false;
422422static volatile bool gContainerFetchDone = false ;
423423static volatile float gContainerFetchResult = 0 .0f ;
424424static String gContainerFetchUID = " " ;
425- // Twin tag prefetch — filled by cloudWorkerTask right after container fetch,
426- // consumed by pushWeightToCloud to avoid a second round-trip during SENDING .
425+ // Twin tag prefetch — kicked off by cloudWorkerTask, executed by twinWorkerTask
426+ // in parallel with the container fetch. Consumed by pushWeightToCloud .
427427static String gTwinFetchUID = " " ;
428428static String gTwinFetchResult = " " ;
429429static volatile bool gTwinFetchDone = false ;
430+ static volatile bool gTwinFetchPending = false ; // signals twinWorkerTask to start
430431// uid_b (2nd physical tag) container prefetch — used when motor scans both tags
431432// and uid_a happens to be the filament tag (no container_weight).
432433static String gContainerBFetchUID = " " ;
@@ -2063,29 +2064,23 @@ void onWsEvent(AsyncWebSocket *server, AsyncWebSocketClient *client,
20632064// Writes to volatile flags so the workflow state-machine on core 1 can poll
20642065// for completion each 250 ms tick without sleeping.
20652066
2067+ // cloudWorkerTask (core 0) — container fetch + cloud send
2068+ // Kicks off twinWorkerTask in parallel for the twin/containerB fetches.
20662069static void cloudWorkerTask (void * /* param*/ ) {
20672070 for (;;) {
20682071 // ── Container fetch ──────────────────────────────────────────────────
20692072 if (gContainerFetchPending ) {
2070- String uid_a = gContainerFetchUID ; // local copy (thread-safe read)
2071- float cw_a = readInventoryContainerWeight (uid_a);
2073+ String uid_a = gContainerFetchUID ; // local copy
2074+ // Launch twin fetch in parallel BEFORE the container HTTP call.
2075+ // twinWorkerTask starts immediately and runs concurrently on core 0.
2076+ gTwinFetchUID = uid_a;
2077+ gTwinFetchDone = false ;
2078+ gTwinFetchPending = true ; // twinWorkerTask picks this up now
2079+ // Container fetch — runs in parallel with twinWorkerTask's twin fetch.
2080+ float cw_a = readInventoryContainerWeight (uid_a);
20722081 gContainerFetchResult = cw_a;
20732082 gContainerFetchPending = false ;
2074- gContainerFetchDone = true ; // Signal immediately — SCANNING picks it up NOW,
2075- // before twin fetch occupies the next ~2 seconds.
2076- // Twin fetch continues after container is signaled.
2077- // pushWeightToCloud will consume gTwinFetchResult from cache.
2078- gTwinFetchUID = uid_a;
2079- gTwinFetchResult = " " ;
2080- readInventoryDocTwinTag (uid_a, gTwinFetchResult );
2081- gTwinFetchDone = true ;
2082- // If a 2nd physical tag was already detected, also prefetch its container.
2083- // This covers Case 2 where uid_a may be the filament tag (no container_weight).
2084- if (gContainerBFetchUID .length () > 0 ) {
2085- gContainerBFetchResult = readInventoryContainerWeight (gContainerBFetchUID );
2086- gContainerBFetchDone = true ;
2087- netLog (" CONTAINER_B prefetch=" + String (gContainerBFetchResult ,1 ) + " g uid=" + gContainerBFetchUID );
2088- }
2083+ gContainerFetchDone = true ; // signal immediately, no waiting for twin
20892084 }
20902085 // ── Cloud send ───────────────────────────────────────────────────────
20912086 if (gCloudSendPending ) {
@@ -2098,6 +2093,29 @@ static void cloudWorkerTask(void* /*param*/) {
20982093 }
20992094}
21002095
2096+ // twinWorkerTask (core 0) — twin fetch + uid_b container fetch, in parallel
2097+ // with cloudWorkerTask's container fetch. Both tasks run on core 0 and timeslice
2098+ // over socket waits, so Firestore round-trips overlap naturally.
2099+ static void twinWorkerTask (void * /* param*/ ) {
2100+ for (;;) {
2101+ if (gTwinFetchPending ) {
2102+ gTwinFetchPending = false ; // clear immediately (safe: written here only)
2103+ String uid_a = gTwinFetchUID ; // local copy
2104+ gTwinFetchResult = " " ;
2105+ readInventoryDocTwinTag (uid_a, gTwinFetchResult );
2106+ gTwinFetchDone = true ;
2107+ netLog (" TWIN prefetch=" + (gTwinFetchResult .length () > 0 ? gTwinFetchResult : " none" ) + " uid=" + uid_a);
2108+ // uid_b container fetch (Case 2: motor scanned both tags)
2109+ if (gContainerBFetchUID .length () > 0 ) {
2110+ gContainerBFetchResult = readInventoryContainerWeight (gContainerBFetchUID );
2111+ gContainerBFetchDone = true ;
2112+ netLog (" CONTAINER_B prefetch=" + String (gContainerBFetchResult ,1 ) + " g uid=" + gContainerBFetchUID );
2113+ }
2114+ }
2115+ vTaskDelay (pdMS_TO_TICKS (20 ));
2116+ }
2117+ }
2118+
21012119// ============================================================================
21022120// §15 — 5 — UNIFIED WS FRAME BUILDER
21032121// ============================================================================
@@ -2160,7 +2178,7 @@ void resetWeightFilters() {
21602178static void resetAfterSuccessfulSend (int shownWeight) {
21612179 currentOledState = OLED_STATE_IDLE ;
21622180 oledStateChangeMs = millis ();
2163- lastUID = " " ; lastUID2 = " " ; lastUIDLeft = " " ; lastUIDRight = " " ; lastUIDTwin = " " ; gTwinFetchDone = false ; gTwinFetchUID = " " ; gTwinFetchResult = " " ;
2181+ lastUID = " " ; lastUID2 = " " ; lastUIDLeft = " " ; lastUIDRight = " " ; lastUIDTwin = " " ; gTwinFetchDone = false ; gTwinFetchPending = false ; gTwinFetchUID = " " ; gTwinFetchResult = " " ;
21642182 lastUIDHex = " " ; lastUID2Hex = " " ;
21652183 firstUidDetectedMs = 0 ; firstUidPauseUntilMs = 0 ;
21662184 stableSinceMs = 0 ; stableCandidate = NAN ;
@@ -2816,7 +2834,7 @@ void setupWebServer() {
28162834 resetSlopeBuffer ();
28172835 stopServoSearch ();
28182836 lastUID = " " ; lastUID2 = " " ; lastUIDHex = " " ; lastUID2Hex = " " ;
2819- lastUIDLeft = " " ; lastUIDRight = " " ; lastUIDTwin = " " ; gTwinFetchDone = false ; gTwinFetchUID = " " ; gTwinFetchResult = " " ;
2837+ lastUIDLeft = " " ; lastUIDRight = " " ; lastUIDTwin = " " ; gTwinFetchDone = false ; gTwinFetchPending = false ; gTwinFetchUID = " " ; gTwinFetchResult = " " ;
28202838 firstUidDetectedMs = 0 ; firstUidPauseUntilMs = 0 ;
28212839 stableSinceMs = 0 ; stableCandidate = NAN ;
28222840 wfContainerWeight = 0 .0f ; wfContainerFetched = false ;
@@ -2888,7 +2906,7 @@ void setupWebServer() {
28882906 resetWeightFilters ();
28892907 autoTarePending = false ;
28902908 autoTareStableSinceMs = 0 ;
2891- lastUID = " " ; lastUID2 = " " ; lastUIDLeft = " " ; lastUIDRight = " " ; lastUIDTwin = " " ; gTwinFetchDone = false ; gTwinFetchUID = " " ; gTwinFetchResult = " " ;
2909+ lastUID = " " ; lastUID2 = " " ; lastUIDLeft = " " ; lastUIDRight = " " ; lastUIDTwin = " " ; gTwinFetchDone = false ; gTwinFetchPending = false ; gTwinFetchUID = " " ; gTwinFetchResult = " " ;
28922910 float offset = scale.get_offset ();
28932911 prefs.begin (" config" , false );
28942912 prefs.putFloat (" tareFactor" , offset);
@@ -3349,7 +3367,7 @@ void handleWeighWorkflow(float w) {
33493367 if (removingNow || w < MIN_WEIGHT_TO_SEND_G ) {
33503368 wfPhase = WF_IDLE ;
33513369 lastUID = " " ; lastUID2 = " " ; lastUIDHex = " " ; lastUID2Hex = " " ;
3352- lastUIDLeft = " " ; lastUIDRight = " " ; lastUIDTwin = " " ; gTwinFetchDone = false ; gTwinFetchUID = " " ; gTwinFetchResult = " " ;
3370+ lastUIDLeft = " " ; lastUIDRight = " " ; lastUIDTwin = " " ; gTwinFetchDone = false ; gTwinFetchPending = false ; gTwinFetchUID = " " ; gTwinFetchResult = " " ;
33533371 wfContainerFetched = false ; wfContainerWeight = 0 .0f ;
33543372 gLastContainer = 0 .0f ; // clear container so web UI hides filament row
33553373 stableCandidate = NAN ; stableSinceMs = 0 ;
@@ -4265,6 +4283,10 @@ void setup() {
42654283 // loop on core 1 is never blocked by a network round-trip.
42664284 xTaskCreatePinnedToCore (cloudWorkerTask, " cloudWorker" ,
42674285 8192 , nullptr , 1 , nullptr , 0 );
4286+ // Twin worker on core 0 — runs in parallel with cloudWorkerTask.
4287+ // Handles twin fetch + uid_b container fetch concurrently with container fetch.
4288+ xTaskCreatePinnedToCore (twinWorkerTask, " twinWorker" ,
4289+ 8192 , nullptr , 1 , nullptr , 0 );
42684290
42694291 // Show IP in large text so it's easy to read and type into a browser
42704292 display.clearDisplay ();
@@ -4507,7 +4529,7 @@ void loop() {
45074529 firstUidDetectedMs = 0 ;
45084530 firstUidPauseUntilMs = 0 ;
45094531 lastUID = " " ; lastUID2 = " " ; lastUIDHex = " " ; lastUID2Hex = " " ;
4510- lastUIDLeft = " " ; lastUIDRight = " " ; lastUIDTwin = " " ; gTwinFetchDone = false ; gTwinFetchUID = " " ; gTwinFetchResult = " " ;
4532+ lastUIDLeft = " " ; lastUIDRight = " " ; lastUIDTwin = " " ; gTwinFetchDone = false ; gTwinFetchPending = false ; gTwinFetchUID = " " ; gTwinFetchResult = " " ;
45114533 wfPhase = WF_IDLE ;
45124534 wfContainerWeight = 0 .0f ; wfContainerFetched = false ;
45134535 currentOledState = OLED_STATE_IDLE ;
@@ -4522,7 +4544,7 @@ void loop() {
45224544 Serial.printf (" [REMOVAL] Sent: %.2f, Now: %.2f\n " , gLastSentWeight , weight);
45234545 gLastSentWeight = NAN ; gLastCloudWeight = NAN ; gCloudWeightSetMs = 0 ;
45244546 lastUID = " " ; lastUID2 = " " ; lastUIDHex = " " ; lastUID2Hex = " " ;
4525- lastUIDLeft = " " ; lastUIDRight = " " ; lastUIDTwin = " " ; gTwinFetchDone = false ; gTwinFetchUID = " " ; gTwinFetchResult = " " ;
4547+ lastUIDLeft = " " ; lastUIDRight = " " ; lastUIDTwin = " " ; gTwinFetchDone = false ; gTwinFetchPending = false ; gTwinFetchUID = " " ; gTwinFetchResult = " " ;
45264548 firstUidDetectedMs = 0 ; firstUidPauseUntilMs = 0 ;
45274549 rfidLockedForCurrentLoad = true ; // Keep RFID locked during removal hold
45284550 servoLockedUntilAutotare = false ; // CRITICAL: unlock servo for next measurement
0 commit comments