Add ESP32 recovery app with WiFi setup portal

- New flash layout (partitions.csv): factory = recovery app, ota_0/ota_1 =
  clock firmware; esp32_usb uploads to ota_0 so it never overwrites it
- src/recovery: WiFi + OTA + web page only. Opens the password protected
  'FlipDot-Setup' AP with a captive portal when WiFi fails, lets you pick
  another WiFi (stored in NVS, used by both apps), start the clock or stay
- Recovery is started when the clock firmware can't connect to WiFi,
  crashes 3 times, after 3 quick power cycles, from a new Home Assistant
  button, or by the bootloader when no clock firmware works
- Shared NVS store and boot switching in src/esp32/system
- Clock firmware keeps working on the old flash layout (falls back to
  OTA-only safe mode)
- README: recovery app, one-time USB installation; tests: 65 total

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
mangavalkandClaude Opus 5.5 committed 2026-10-13 01:21:04 +02:00
1 parent ddbdf7e65f
commit 4749f9e596
25 files changed
+979 -29

No files matched your search

+75 -2
View File
@@ -36,8 +36,10 @@ Nano and drawn there.
platformio.ini one PlatformIO project, multiple environments
lib/BROSE9323/ shared flipdot driver (used by both targets)
src/nano/ Arduino Nano firmware
src/esp32/ ESP32 firmware
src/esp32/ ESP32 clock firmware
secrets.example.h template for WiFi / MQTT credentials
src/recovery/ ESP32 recovery app: WiFi setup page + OTA
partitions.csv ESP32 flash layout (recovery app + two clock firmware slots)
test/test_nano/ unit tests for the Nano firmware (run on the PC)
test/test_esp32/ unit tests for the ESP32 logic (run on the PC)
test/mocks/ fake Arduino, EEPROM and display driver for the Nano tests
@@ -61,7 +63,8 @@ this folder.
| `nano` | Arduino Nano (ATmega328P) | USB (`COM10`) |
| `nano_ota` | Arduino Nano (ATmega328P) | Over WiFi through the ESP32 (`10.1.1.198`). See [Flashing the Nano over WiFi](#flashing-the-nano-over-wifi). |
| `esp32` | ESP32 DevKit v1 | OTA over WiFi (`10.1.1.198`) |
| `esp32_usb` | ESP32 DevKit v1 | USB (`COM11`). Use it for the first flash or for recovery. |
| `esp32_usb` | ESP32 DevKit v1 | USB (`COM11`), into `ota_0`. Normally not needed. |
| `recovery` | ESP32 DevKit v1 | USB (`COM11`), one-time install of the [recovery app](#recovery-app) |
```sh
pio run # build nano + esp32
@@ -155,6 +158,69 @@ even if the uploader is interrupted.
> **Security:** anyone on your network can connect to port 2323 and flash the Nano, just
> like the ESP32's own OTA has no password. Keep the display on a trusted network.
## Recovery app
The ESP32 has a small second app, the **recovery app** ([src/recovery](src/recovery)). It
has its own place on the flash and is never changed by OTA updates. It contains only WiFi,
a setup web page and OTA, so the ESP32 can always be reached, even when the clock firmware
is broken. The display stays off while it runs, and the built-in LED double-blinks.
```
flash (4 MB): bootloader | nvs | otadata | factory: recovery app | ota_0: clock | ota_1: clock
```
**When it starts**
| Trigger | Back to the clock |
|---------|-------------------|
| The clock firmware can't connect to WiFi (30 s at boot, or 10 min later on) | As soon as the WiFi works again (after at least 1 minute) |
| The clock firmware crashed 3 times in a row | After 10 minutes |
| **FlipDot Display Recovery** button in Home Assistant | After 10 minutes |
| Switching the power on and off 3 times, each time within 10 s | After 10 minutes |
| There is no working clock firmware (the bootloader falls back to it) | Never by itself |
The setup page can also start the clock right away, or keep the recovery app running
("Stay in recovery").
**WiFi setup.** The recovery app keeps trying the stored WiFi. If that fails for 20 s, it
also opens its own WiFi: **FlipDot-Setup**, with the password `SETUP_AP_PASSWORD` from
`src/esp32/secrets.h`. Connect a phone to it; the setup page opens by itself (otherwise go
to <http://192.168.4.1>). Pick a network, enter the password and save. The new WiFi is
stored in the ESP32's NVS and used by both apps from then on (instead of `WIFI_SSID` from
`secrets.h`). The setup page is also reachable on your normal WiFi, at the ESP32's IP
address.
**Updating the clock firmware from the recovery app.** Upload as usual,
`pio run -e esp32 -t upload`. On the setup WiFi, add `--upload-port 192.168.4.1`. The
recovery app writes the firmware to `ota_0` and starts it.
**Updating the recovery app itself** needs USB (`pio run -e recovery -t upload`). It
should rarely be necessary.
### One-time installation (USB)
This changes the flash layout, so it can only be done over USB. It is the last USB flash
the ESP32 needs. Best done together with the wiring TODOs, while the display is open
anyway.
1. Connect the ESP32 over USB (`COM11`, see `platformio.ini`).
2. Erase the flash and install the recovery app:
```sh
pio run -e recovery -t erase
pio run -e recovery -t upload
```
The recovery app starts (LED double-blinks), connects to the WiFi from `secrets.h`, and
waits, because there is no clock firmware yet.
3. Install the clock firmware **over WiFi**. This also tests that the recovery app works:
```sh
pio run -e esp32 -t upload
```
4. Check Home Assistant: the **FlipDot Display Firmware** sensor should show
`2.1.0, testing, ...`, and after a minute `2.1.0, ok, 0 crashes, recovery ok`.
Erasing the flash also clears the NVS: WiFi settings made on the setup page are reset to
the ones in `secrets.h`.
## Wiring between ESP32 and Nano
The ESP32 talks to the Nano on **UART2** (`Serial2`, 115200 baud). The ESP32's own
@@ -194,6 +260,13 @@ The serial protocol is described in [src/nano/README.md](src/nano/README.md).
## TODO
- [ ] **Install the recovery app** (one-time USB flash, see
[One-time installation](#one-time-installation-usb)). Until then the ESP32 runs the old
flash layout: the clock firmware works, but without the recovery app; the Home
Assistant sensor shows "no recovery app".
- [ ] **Test the recovery app on the real hardware**: the Home Assistant button, the setup
WiFi with a wrong WiFi password, 3 quick power cycles, and an OTA upload into it.
- [ ] **Rewire the Nano link to UART2.** The firmware now uses `Serial2` (GPIO 16/17)
instead of `Serial` (GPIO 1/3). Move the ESP32 TX wire from GPIO 1 to **GPIO 17** before
you flash this firmware, or the display gets no data. See