feat(operation): send X-Boot-Reason so power-cycle is a force-resync

Distinguish a cold-boot poll (UNDEFINED wakeup cause = power-on, hard
reset, plug-cycle) from a normal timer wake. Encoded as the
X-Boot-Reason request header; server uses it to deliberately bypass
the schedule and rotate. Matches how users actually use the device:
unplug-and-replug as a manual refresh.

Tests: two new native cases asserting the header is "cold" on
UNDEFINED wakeup and "timer" on TIMER wakeup. esp_sleep mock now
exposes a settable wakeup_cause global.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-08 12:18:32 -04:00
parent 988759f738
commit bbd5e84db0
3 changed files with 48 additions and 0 deletions
+9
View File
@@ -127,6 +127,15 @@ void normal_operation_impl(const String& mac, HTTP& http, const String& url, Pre
http.addHeader("X-Current-Image-Id", String(currentImgId));
}
// Tell the server how we got here. The server uses this to honor a
// power-cycle as a deliberate "force resync" — a poll that arrives with
// X-Boot-Reason: cold gets a fresh rotation even outside configured wake
// times, so unplugging and replugging the frame works as a manual refresh.
// Timer wakes (the normal case) keep their schedule-gated semantics.
const esp_sleep_wakeup_cause_t cause = esp_sleep_get_wakeup_cause();
http.addHeader("X-Boot-Reason",
cause == ESP_SLEEP_WAKEUP_TIMER ? "timer" : "cold");
const char* collectHeaders[] = { "X-Interval-Ms", "X-Image-Id", "X-Image-Sha256" };
http.collectHeaders(collectHeaders, 3);
int code = http.GET();