Commit Graph

5 Commits

Author SHA1 Message Date
football2801 bbd5e84db0 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>
2026-05-08 12:18:32 -04:00
football2801 988759f738 feat(firmware): honor server X-Interval-Ms instead of capping at 60s
The dev-only cap that forced every-1-min polling regardless of the app's
schedule is removed. The device now sleeps for whatever X-Interval-Ms
the server hands back (driven by rotationIntervalMinutes / wakeTimes),
clamped to [30s, 25h] as a safety net against malformed values.

Renamed FETCH_INTERVAL_MS to FETCH_INTERVAL_MS_FALLBACK — it's now
*only* used when the header is absent (rare; rolling deploy / hand-
crafted response). Added SLEEP_CLAMP_MIN/MAX for the bounds.

Tests FW-09 and FW-10 flipped to lock the new behavior; added FW-10b
covering sub-MIN clamping (battery protection if server sends 1000ms).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-07 15:34:20 -04:00
football2801 27d01057e4 feat(operation): verify X-Image-Sha256 before painting the panel
Pairs with the server-side header. After streaming the response body to
LittleFS, hash the file with mbedtls/sha256 (hardware-accelerated on
ESP32-S3) and compare against the server's claim. On mismatch:

- Don't update NVS_KEY_IMG_ID, so the next poll reports the old id and
  the server sends 200 again with fresh bytes (natural retry, no extra
  HTTP round-trip in this cycle).
- Don't draw — panel keeps whatever was up before, no garbage on the
  e-ink.
- Raise NVS_KEY_ERR_BORDER so the next healthy 304 paints a clean
  recovery frame with the sync-fail border.

Verification is skipped when the header is absent, so the firmware
stays compatible with any server that hasn't deployed the matching
header yet. mbedtls compiles into a native-test no-op stub (returns
empty hex), so existing native tests don't need a SHA implementation.

Two new tests: FW-17a (mismatch path) and FW-17b (missing header
backward compat). Mock String now has equalsIgnoreCase so the new
comparison compiles in native-test.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-06 19:43:02 -04:00
football2801 cbdcad3154 fix: preserve last image and overlay yellow border on sync failure
Previously a 5xx / timeout / malformed response fired epd_fill(COLOR_YELLOW),
which writes the yellow nibble across the entire 800×480 framebuffer and
destroys the last good image — exactly what FR38 forbids ("Last image
persists ... yellow border signals state"). The device then got stuck on a
blank yellow screen because the next 304 didn't redraw.

Changes:

- New epd_draw_image_with_border streams the cached .bin row-by-row,
  overwrites border-region pixels in the row buffer, and pushes a single
  composited framebuffer (same pattern as the existing setup-QR overlay).
- normal_operation_impl else-branch now redraws the cached image with a
  yellow border, falling back to epd_fill only when no cache exists
  (first-boot error). Sets a new NVS_KEY_ERR_BORDER flag.
- 200 and 304 paths clear NVS_KEY_ERR_BORDER. The 304 branch now
  triggers a clean repaint when the err flag is set, so the device
  recovers from the stuck-yellow state on the next healthy poll
  without waiting for rotation to advance.
- LittleFS read mock now returns invalid File when the file doesn't
  exist (matches real LittleFS), so the no-cache fallback path is
  actually exercisable in tests.

Tests:

- Replaces the old test_fw06_error_fills_yellow (which locked in the
  buggy fill behavior) with FW-06a..e covering: error+cache draws
  border (no fill), error+no-cache falls back to fill, 304 after
  error repaints clean, steady-state 304 touches nothing (the
  regression the user flagged), 200 after error clears the flag.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-06 13:30:04 -04:00
football2801 87af8cb030 fix: harden firmware NVS persistence, WDT, and 304 epd_sleep
Three bugs fixed:
- NVS img_id now written before epd_init/draw; new draw_needed flag in NVS
  survives power-loss mid-refresh so next boot re-draws from LittleFS instead
  of showing stale content
- epd_sleep() now only called when display was initialized this cycle,
  preventing a 60 s wait_busy() timeout on every 304 poll
- esp_task_wdt_reset() added to wait_busy() loop so the ~20 s 6-color
  refresh no longer triggers the task watchdog

Also extracts normal_operation into operation.h template and adds
a native PlatformIO test suite (16 tests) covering the full response matrix.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-06 12:09:37 -04:00