77//
88// TABLE OF CONTENTS line range
99// ──────────────────────────────────────────── ──────────
10- // §1 HARDWARE CONFIGURATION 61– 130
11- // §2 OTA CONFIGURATION 131 – 149
12- // §3 FORWARD DECLARATIONS 150 – 229
13- // §4 WEIGHT ROUNDING 230 – 249
14- // §5 GLOBAL OBJECTS 250 – 263
15- // §6 CONFIGURATION VARIABLES 264 – 458
16- // §7 OLED DISPLAY 459 – 576
17- // §8 CLOUD PARSING 577 – 591
18- // §9 WIFI SETUP 592 – 864
19- // §10 LITTLEFS 865 – 909
20- // §11 FIREBASE AUTHENTICATION 910– 1103
21- // §12 FIRESTORE SCALE HEARTBEAT & SYNC 1104– 2034
22- // §13 WEBSOCKET 2035– 2061
23- // §14 5 — CLOUD WORKER TASK (non-blocking Firestore on core 0) 2062– 2121
24- // §15 5 — UNIFIED WS FRAME BUILDER 2122– 2165
25- // §16 WEIGHT FILTER HELPERS 2166– 2180
26- // §17 POST-SEND STATE RESET (shared by all send paths) 2181– 2199
27- // §18 SHARED WEIGHT PUSH HANDLER (used by /api/weight and /api/push-weight) 2200– 2272
28- // §19 WEB SERVER 2273– 2973
29- // §20 CLOUD COMMUNICATION 2974– 3156
30- // §21 WEIGH WORKFLOW (IDLE → SCANNING → STABLE_WAIT → SENDING) 3157– 3392
31- // §22 mDNS 3393– 3430
32- // §23 SCALE 3431– 3522
33- // §24 RFID 3523– 3852
34- // §25 OTA — Over-the-air firmware + filesystem update 3853– 4221
35- // §26 SETUP & LOOP 4222– 4623
10+ // §1 HARDWARE CONFIGURATION 61– 135
11+ // §2 OTA CONFIGURATION 136 – 154
12+ // §3 FORWARD DECLARATIONS 155 – 234
13+ // §4 WEIGHT ROUNDING 235 – 254
14+ // §5 GLOBAL OBJECTS 255 – 268
15+ // §6 CONFIGURATION VARIABLES 269 – 464
16+ // §7 OLED DISPLAY 465 – 582
17+ // §8 CLOUD PARSING 583 – 597
18+ // §9 WIFI SETUP 598 – 870
19+ // §10 LITTLEFS 871 – 915
20+ // §11 FIREBASE AUTHENTICATION 916– 1109
21+ // §12 FIRESTORE SCALE HEARTBEAT & SYNC 1110– 2040
22+ // §13 WEBSOCKET 2041– 2067
23+ // §14 5 — CLOUD WORKER TASK (non-blocking Firestore on core 0) 2068– 2138
24+ // §15 5 — UNIFIED WS FRAME BUILDER 2139– 2182
25+ // §16 WEIGHT FILTER HELPERS 2183– 2197
26+ // §17 POST-SEND STATE RESET (shared by all send paths) 2198– 2216
27+ // §18 SHARED WEIGHT PUSH HANDLER (used by /api/weight and /api/push-weight) 2217– 2289
28+ // §19 WEB SERVER 2290– 2990
29+ // §20 CLOUD COMMUNICATION 2991– 3173
30+ // §21 WEIGH WORKFLOW (IDLE → SCANNING → STABLE_WAIT → SENDING) 3174– 3424
31+ // §22 mDNS 3425– 3462
32+ // §23 SCALE 3463– 3554
33+ // §24 RFID 3555– 3884
34+ // §25 OTA — Over-the-air firmware + filesystem update 3885– 4253
35+ // §26 SETUP & LOOP 4254– 4655
3636//
3737// To regenerate this block: ./scripts/update_toc.sh
3838// ─── TOC END ───────────────────────────────────────────────
@@ -116,6 +116,11 @@ const uint32_t NEG_DRIFT_TARE_DELAY_MS = 1000; // 1 second below zero →
116116const uint32_t NEG_DRIFT_TARE_COOLDOWN_MS = 10000 ; // min. 10s between neg-drift tares
117117const uint32_t SPOOL_SCAN_DURATION_MS = 6000 ; // 1 full spool turn at SERVO_SEARCH_US
118118const uint32_t NO_MOTOR_UID_WAIT_MS = 2000 ; // passive UID wait when motor disabled
119+ // STABLE_WAIT timeout — if weight never stabilizes, use best candidate and proceed
120+ const uint32_t STABLE_WAIT_TIMEOUT_MS = 15000 ; // 15s max in STABLE_WAIT before forced SENDING
121+ // Cloud send retry — backoff delays between attempts (ms)
122+ const uint32_t SEND_RETRY_DELAYS_MS [] = {1000 , 2000 }; // wait 1s, then 2s between retries
123+ const int SEND_MAX_ATTEMPTS = 3 ;
119124
120125#if ENABLE_LEGACY_API_BRIDGE
121126const char * TIGERTAG_CLOUD_FN_URL = " https://us-central1-tigertag-connect.cloudfunctions.net/setSpoolWeightByRfid" ;
@@ -444,10 +449,11 @@ static float gCloudSendWeight = 0.0f;
444449// ────────────────────────────────────────────────────────────────────────────
445450
446451// Auto-push stability tracking
447- static float lastPushedWeight = NAN ;
448- static uint32_t stableSinceMs = 0 ;
449- static float stableCandidate = NAN ;
450- static uint32_t lastPushMs = 0 ;
452+ static float lastPushedWeight = NAN ;
453+ static uint32_t stableSinceMs = 0 ;
454+ static float stableCandidate = NAN ;
455+ static uint32_t lastPushMs = 0 ;
456+ static uint32_t wfStableWaitStartMs = 0 ; // timestamp when STABLE_WAIT began (for timeout)
451457
452458// Name lookup cache to avoid repeated HTTP fetches for the same brand/material IDs
453459
@@ -2085,9 +2091,20 @@ static void cloudWorkerTask(void* /*param*/) {
20852091 gContainerFetchPending = false ;
20862092 gContainerFetchDone = true ; // signal immediately, no waiting for twin
20872093 }
2088- // ── Cloud send ───────────────────────────────────────────── ──────────
2094+ // ── Cloud send — with retry (3 attempts: immediate, +1s, +2s) ──────────
20892095 if (gCloudSendPending ) {
2090- bool ok = pushWeightToCloud (gCloudSendWeight );
2096+ bool ok = false ;
2097+ for (int attempt = 0 ; attempt < SEND_MAX_ATTEMPTS && !ok; attempt++) {
2098+ if (attempt > 0 ) {
2099+ netLog (" SEND RETRY " + String (attempt) + " /" + String (SEND_MAX_ATTEMPTS - 1 )
2100+ + " wait=" + String (SEND_RETRY_DELAYS_MS [attempt - 1 ]) + " ms" );
2101+ vTaskDelay (pdMS_TO_TICKS (SEND_RETRY_DELAYS_MS [attempt - 1 ]));
2102+ }
2103+ ok = pushWeightToCloud (gCloudSendWeight );
2104+ if (!ok && attempt < SEND_MAX_ATTEMPTS - 1 ) {
2105+ netLog (" SEND attempt " + String (attempt + 1 ) + " FAIL — retrying" );
2106+ }
2107+ }
20912108 gCloudSendOk = ok;
20922109 gCloudSendPending = false ;
20932110 gCloudSendDone = true ;
@@ -3289,7 +3306,8 @@ void handleWeighWorkflow(float w) {
32893306 netLog (" WF SCANNING→IDLE (no UID)" );
32903307 } else {
32913308 wfPhase = WF_STABLE_WAIT ;
3292- stableCandidate = NAN ; stableSinceMs = 0 ;
3309+ stableCandidate = NAN ; stableSinceMs = 0 ;
3310+ wfStableWaitStartMs = now;
32933311 sendCountdown = (int )((STABLE_WINDOW_MS + 999 ) / 1000 );
32943312 netLog (" WF SCANNING→STABLE_WAIT uid=" + lastUID + " uid2=" + lastUID2
32953313 + " reason=" + (bothUidsReady ? " both" : " timeout" ));
@@ -3313,6 +3331,20 @@ void handleWeighWorkflow(float w) {
33133331 if (secsLeft < 0 ) secsLeft = 0 ;
33143332 if (secsLeft != sendCountdown) sendCountdown = secsLeft;
33153333
3334+ // ── Timeout fallback (Option A) ───────────────────────────────────────
3335+ // Surface instable ou vibrations : le poids ne se stabilise jamais.
3336+ // Après STABLE_WAIT_TIMEOUT_MS (15s), on prend la meilleure valeur
3337+ // disponible et on passe quand même en SENDING plutôt que de bloquer.
3338+ if (now - wfStableWaitStartMs >= STABLE_WAIT_TIMEOUT_MS ) {
3339+ float fallback = (!isnan (stableCandidate) && stableCandidate >= MIN_WEIGHT_TO_SEND_G )
3340+ ? stableCandidate : w;
3341+ stableCandidate = fallback;
3342+ wfPhase = WF_SENDING ;
3343+ netLog (" WF STABLE_WAIT→SENDING timeout=" + String (STABLE_WAIT_TIMEOUT_MS /1000 )
3344+ + " s fallback=" + String (fallback,1 ) + " g (unstable surface)" );
3345+ return ;
3346+ }
3347+
33163348 if (weightStable && (now - stableSinceMs >= STABLE_WINDOW_MS )) {
33173349 wfPhase = WF_SENDING ;
33183350 netLog (" WF STABLE_WAIT→SENDING stable=" + String (stableCandidate,1 )
0 commit comments