fix(provisioning): stop redrawing the QR on every poll, add WiFi-fail retry screen

Two related fixes that together let the post-WiFi-setup window be quiet:

1. operation.h 204/404: skip the panel redraw entirely. The panel already
   holds the right thing — setup QR if no image has ever been painted
   (img_id == -1), or a real photo if img_id >= 0. Redrawing the QR every
   15s during the bootstrap claim window put the e-ink into a perpetual
   ~20s mid-refresh loop and risked ghosting. Tests updated to assert
   no redraw on either sub-case.

2. main.cpp WiFi-fail path: drop the epd_fill(RED) + 3s delay + AP
   re-redraw sequence (~43s of e-ink work that destroyed the QR mid-flow)
   and replace with a single repaint of a new "Connection Failed — try
   again" Step 1/2 screen with red accents. gen_screens.py grows a
   gen_ap_retry() variant that recolors yellow → red and swaps the
   header/QR labels; the result is shipped as ap_bg_retry.bin alongside
   ap_bg.bin in LittleFS. epd.h exposes epd_draw_ap_screen_retry().
This commit is contained in:
2026-05-08 23:43:59 -04:00
parent e7f0a11ad3
commit fb4c5ff5d3
9 changed files with 105 additions and 44 deletions
+14 -9
View File
@@ -263,16 +263,21 @@ void normal_operation_impl(const String& mac, HTTP& http, const String& url, Pre
Serial.println("[op] recovery aborted: /img.bin not in LittleFS");
}
}
} else if (code == 204) {
} else if (code == 204 || code == 404) {
// No image to serve. Don't touch the panel — whatever's already
// displayed is the right thing:
// • currentImgId == -1 → the setup QR is up (painted by
// enter_provisioning after WiFi save). The 15s bootstrap poll
// hits this branch every cycle until the user claims via
// /setup/{mac}; redrawing the QR each time would put the panel
// in a perpetual ~20s e-ink redraw loop and risk ghosting.
// • currentImgId >= 0 → a real photo is up (server hiccup, asset
// missing, image deleted). Don't paint the setup QR over the
// user's photo; leave the last-good image alone.
// displayInitialized stays false → epd_sleep() at the bottom is
// also skipped, since the display was already in sleep from the
// previous cycle.
http.end();
displayInitialized = true;
epd_init();
show_setup_qr(mac);
} else if (code == 404) {
http.end();
displayInitialized = true;
epd_init();
show_setup_qr(mac);
} else {
// Sync failed (5xx, timeout, malformed). Per FR38, the last-good image
// must persist; only the border indicates the error. epd_draw_image_with_border