Skip to content
This repository was archived by the owner on Jul 31, 2026. It is now read-only.

Commit b4e260c

Browse files
perf: parallel CONTAINER_GET + TWIN_GET via dedicated twinWorkerTask
Before: cloudWorkerTask fetched container THEN twin sequentially (~2.5s total) After: cloudWorkerTask fetches container (~500ms) while twinWorkerTask fetches twin (~2s) concurrently on the same core — FreeRTOS timeslices over socket waits so both HTTP round-trips overlap naturally. Changes: - Add gTwinFetchPending flag: cloudWorkerTask sets it before starting container, twinWorkerTask picks it up immediately - twinWorkerTask (new, pinned to core 0): fetches twin + uid_b container in seq - cloudWorkerTask: no longer does any twin/containerB work, just container + send - Add gTwinFetchPending=false to all WF reset locations - Add xTaskCreatePinnedToCore for twinWorkerTask in setup() Timeline with 5s scan: t=0 : SCANNING starts, uid detected, prefetch kicked off t=500ms: CONTAINER done → gContainerFetchDone=true (SCANNING picks it up) t=2s : TWIN done → gTwinFetchDone=true t=2.5s : CONTAINER_B done → gContainerBFetchDone=true t=5s : SCANNING→STABLE_WAIT, all prefetch results ready SENDING: cache hit for all — 0 extra HTTP calls Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent 55bb006 commit b4e260c

1 file changed

Lines changed: 68 additions & 46 deletions

File tree

‎src/main.cpp‎

Lines changed: 68 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -12,27 +12,27 @@
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;
422422
static volatile bool gContainerFetchDone = false;
423423
static volatile float gContainerFetchResult = 0.0f;
424424
static 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.
427427
static String gTwinFetchUID = "";
428428
static String gTwinFetchResult = "";
429429
static 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).
432433
static 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.
20662069
static 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() {
21602178
static 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

Comments
 (0)