Fix shifted pictures: non-blocking Nano drawing, frame check, refresh settings

The Nano's periodic full redraw blocked for ~0.8 s. A frame that arrived
meanwhile overflowed its 64-byte serial buffer; lost bytes in a bitmap
shift the rest of the image (picture moved up), and diff frames never
repaired it.

- Nano: all drawing (frames, effects, the dot refresh) runs from update()
  in ~1 ms slices; the refresh pauses while a frame is received or shown
- New commands: C (reply with the image checksum) and I (refresh minutes,
  EEPROM)
- ESP32: NanoMonitor asks for the checksum after frames and sends the whole
  image again on a mismatch, missing answer or Nano restart; counts ERR
  replies. Periodic whole-image resync.
- Settings: "Dot refresh" and "Image resync" intervals (settings v5,
  v4 records are migrated)
- System tab: loop timing, free memory, frame check counts
- test_driver: the real driver against a simulated panel

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
mangavalkandClaude Opus 5.5 committed 2026-10-16 15:20:26 +02:00
1 parent 58a7cb03c2
commit 090c2bf8ae
45 files changed
+1265 -157

No files matched your search

+18 -11
View File
@@ -6,7 +6,8 @@ See README.md for the overview.
## Layout and build
- `platformio.ini` has the environments `nano`, `nano_ota`, `esp32` (OTA to 10.1.1.198),
`esp32_usb` (COM11), and the PC test environments `test_nano` and `test_esp32`. The
`esp32_usb` (COM11), and the PC test environments `test_nano`, `test_esp32` and
`test_driver`. The
sources are split with `build_src_filter` (`src/nano/`, `src/esp32/`).
- `lib/BROSE9323/` is the shared driver. Its behaviour depends on build flags set in
`platformio.ini`, not in the header:
@@ -27,7 +28,7 @@ See README.md for the overview.
The uploader only uses the Python standard library. Test it against a simulated
bootloader before changing it.
- After any change, build both targets (`pio run`) and run the tests
(`pio test -e test_nano -e test_esp32`).
(`pio test -e test_nano -e test_esp32 -e test_driver`).
## Constraints
@@ -36,7 +37,7 @@ See README.md for the overview.
data in `PROGMEM`.
- The ESP32 talks to the Nano on `Serial2` (owned by `NanoLink`, GPIO 17 TX / GPIO 16 RX).
`Serial` (UART0/USB) is free for debug output. Never write debug text to `Serial2`: the
Nano runs lines that start with s/S/F/D/P/B/R/T/G/N/E as commands.
Nano runs lines that start with s/S/F/D/P/B/R/T/G/N/E/I/C as commands.
- Open hardware TODOs are listed in the README `TODO` section. Update it when one is done.
- The README is the overview; the documentation is in `docs/` (web UI, pages, Home
Assistant, how it works, wiring, Nano over WiFi, recovery app). Screenshots:
@@ -62,22 +63,28 @@ See README.md for the overview.
- Line based and non-blocking: bytes are collected into a 64-character buffer and a
command runs when `\n` arrives. Bitmap (`B`) pixel data is the exception. It is drawn
while it arrives, so it can be longer than the buffer. Keep `loop()` free of busy-waits.
- A full redraw (`display(true)`) blocks for about 0.5 s, which overflows the 64-byte
serial receive buffer. Only run it when no data is expected: right after a `D`, or after
2 s with no serial data. The ESP only sends a frame (ending with `D`) when the screen
changes, mostly once a minute.
- The driver never blocks while flipping: `display()` only starts a run, `update()` flips
dots in slices of about 1 ms (frames, effects, and the periodic dot refresh from
`startRefresh()`, which waits while a frame is received or shown). Anything that blocks
longer than about 5 ms overflows the 64-byte serial receive buffer; lost bytes shift the
rest of a bitmap. That was the "picture moved up" bug. `test_driver` checks the time per
`update()` call with a simulated panel.
- Flip time is stored in EEPROM as a magic word at address 0 and a `uint16_t` at
address 2. Values outside 50–2000 µs are rejected.
- The Nano only replies `READY` (at boot) and `ERR: ...`. There is no per-frame
acknowledgement. The Nano's TX is wired to the ESP's RX (flashing over WiFi works), but
every wait for a reply still needs a timeout.
- The Nano replies `READY` (at boot), `ERR: ...` and `C xxxx` (checksum of its image, for
`C`). There is no per-frame acknowledgement: the ESP paces frames by time.
`nano_link/nano_monitor` reads the replies and sends `C` after frames;
`logic/frame_check` decides (wrong/missing answer or `READY` while on: send the whole
image again via `controller.pageChanged()`). Every wait for a reply needs a timeout.
- Driver tests: `pio test -e test_driver` runs the real `BROSE9323.cpp` (Nano build)
against `test/driver_mocks/` (Arduino, Adafruit GFX, a panel that decodes the pins).
- Unit tests: `pio test -e test_nano` runs `test/test_nano/` on the PC with the fakes in
`test/mocks/` (Arduino, EEPROM, and a `BROSE9323` that records calls). It needs gcc on
PATH (Strawberry Perl provides it at `C:\Strawberry\c\bin`). Add a test for every
protocol change. The mocks only implement what the Nano code uses, so extend them when
new Arduino or driver functions are called.
- The board environments have `test_ignore = *`, so test firmware is never uploaded. Plain
`pio test` runs nothing; always pass `-e test_nano -e test_esp32`.
`pio test` runs nothing; always pass `-e test_nano -e test_esp32 -e test_driver`.
## ESP32 structure (`src/esp32/`)