Public Access
Restructure ESP32 firmware into modules, fix bugs, add tests
Structure (src/esp32/): - logic/: DisplayController, schedule, power timer, LED pattern; pure C++ - display/: FlipdotFrames and bitmaps - nano_link/: NanoLink (relay, serial, READY, bootloader) and flash bridge - net/: WiFi/OTA/panic window, Home Assistant - config.h with all settings; libs.h (globals in a header) removed Bug fixes: - Home Assistant switches now report their state back and start as ON - loop() no longer blocks: WiFi.waitForConnectResult() (up to 60 s without WiFi) and getLocalTime() (5 s before NTP sync) were called every iteration - Power switch off now powers off after 1 s even when a 30 s countdown is already running - millis() overflow safe power-off timer; no frame catch-up bursts - No garbage time string before NTP sync; 'Connecting...' stays instead - Frames are only sent when the screen changes instead of every second - Reset reasons are logged once instead of every 100 ms - Removed dead code (unused fonts, WiFiMulti, rotated bitmap, ...) Tests: new test_esp32 environment (26 tests); native renamed to test_nano Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
cefb76c1ad
commit
683a47c055
29 files changed
+1289
-755
No files matched your search
@@ -5,9 +5,9 @@ See README.md for the overview.
|
||||
|
||||
## Layout and build
|
||||
|
||||
- `platformio.ini` has the environments `nano`, `esp32` (OTA to 10.1.1.198) and
|
||||
`esp32_usb` (COM11). The sources are split with `build_src_filter` (`src/nano/`,
|
||||
`src/esp32/`).
|
||||
- `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
|
||||
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:
|
||||
- `ESPSERIAL` (ESP32): draw calls become serial commands.
|
||||
@@ -19,36 +19,34 @@ See README.md for the overview.
|
||||
- `nano_ota` flashes the Nano over WiFi: `tools/nano_net_upload.py` (STK500v1 over TCP)
|
||||
talks to `NanoFlashBridge` on the ESP32 (port 2323). The uploader first sends
|
||||
`FLASH <baud>\n`. The ESP power cycles the Nano (plus an optional reset pulse on
|
||||
`nano_reset`), answers `OK\n`, then passes the bytes through at that baud rate.
|
||||
`NANO_RESET_PIN`), answers `OK\n`, then passes the bytes through at that baud rate.
|
||||
`--baud=auto` tries 57600 (old ATmegaBOOT, which also starts after a power cycle), then
|
||||
115200 (Optiboot, which only starts after a reset-pin reset, so it needs `nano_reset`).
|
||||
115200 (Optiboot, which only starts after a reset-pin reset, so it needs `NANO_RESET_PIN`).
|
||||
avrdude 6.3's `net:` port is broken on Windows (`ser_drain()` fails), which is why the
|
||||
uploader is a Python script.
|
||||
The uploader only uses the Python standard library. Test it against a simulated
|
||||
bootloader before changing it.
|
||||
- `src/esp32/libs.h` defines globals in the header, so it can only be included from
|
||||
`main.cpp`. New ESP32 modules must be self-contained (see `nano_flash_bridge.*`).
|
||||
- After any change, build both targets (`pio run`). After Nano changes, also run
|
||||
`pio test -e native`.
|
||||
- After any change, build both targets (`pio run`) and run the tests
|
||||
(`pio test -e test_nano -e test_esp32`).
|
||||
|
||||
## Constraints
|
||||
|
||||
- **Nano RAM is tight** (ATmega328P, 2 KB). The frame buffers are allocated with
|
||||
`calloc`. Avoid `String`, large stack buffers and new globals in Nano code. Put constant
|
||||
data in `PROGMEM`.
|
||||
- The ESP32 talks to the Nano on `Serial2` (`nanoSerial` in `libs.h`, GPIO 17 TX / GPIO 16
|
||||
RX). `Serial` (UART0/USB) is free for debug output. Never write debug text to
|
||||
`nanoSerial`: the Nano runs lines that start with s/S/F/D/P/B/R/T as commands.
|
||||
- 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 as commands.
|
||||
- Open hardware TODOs are listed in the README `TODO` section. Update it when one is done.
|
||||
- `src/esp32/secrets.h` holds the real credentials and is gitignored. Never commit it.
|
||||
Add new secrets to `secrets.example.h` as placeholders.
|
||||
- The relay (ESP32 GPIO 13, `LOW` = on) powers both the panels and the Nano. Each time it
|
||||
switches on, the Nano cold-boots and then sends `READY`. The ESP's `relayOn()` waits for
|
||||
switches on, the Nano cold-boots and then sends `READY`. `NanoLink::on()` waits for
|
||||
that message, at most `NANO_BOOT_TIMEOUT`. The timeout also covers the case where the
|
||||
Nano's TX isn't wired to the ESP's RX.
|
||||
- `relayOff()` ends `nanoSerial` and sets its TX pin to input. Otherwise the idle-high TX line
|
||||
powers the Nano through its RX pin. Never send display commands while `relay_on` is
|
||||
false.
|
||||
- `NanoLink::off()` ends `Serial2` and sets its TX pin to input. Otherwise the idle-high TX
|
||||
line powers the Nano through its RX pin. Never send display commands while
|
||||
`NanoLink::isOn()` is false (`DisplayController` takes care of this).
|
||||
- The Nano keeps no state over a power cycle except the flip time in EEPROM. The driver
|
||||
forces a full redraw on the first `display()` after boot (`_force_next`).
|
||||
- Hardware is not available here. Builds can be checked, but runtime behaviour can't.
|
||||
@@ -62,19 +60,36 @@ See README.md for the overview.
|
||||
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 sends one frame per second, ending with `D`.
|
||||
2 s with no serial data. The ESP only sends a frame (ending with `D`) when the screen
|
||||
changes, mostly once a minute.
|
||||
- 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. Whether the Nano's TX is wired to the ESP's RX is unconfirmed, so every
|
||||
wait for a reply needs a timeout.
|
||||
- Unit tests: `pio test -e native` runs `test/test_nano/` on the PC with the fakes in
|
||||
- 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 native`.
|
||||
`pio test` runs nothing; always pass `-e test_nano -e test_esp32`.
|
||||
|
||||
## ESP32 structure (`src/esp32/`)
|
||||
|
||||
- Folders: `logic/` (pure C++, no Arduino includes, tested in `test/test_esp32`),
|
||||
`display/` (frames and bitmaps), `nano_link/` (relay, serial, flash bridge), `net/`
|
||||
(WiFi/OTA, Home Assistant). Includes are relative to `src/esp32` (`-I src/esp32`), e.g.
|
||||
`#include "logic/schedule.h"`.
|
||||
- `DisplayController` holds the display logic and only uses the `DisplayPower` and
|
||||
`Frames` interfaces. Put new behaviour there, behind those interfaces, so it stays
|
||||
testable. `logic/` must not include Arduino headers, or `test_esp32` stops building.
|
||||
- All settings are in `config.h`. Keep it free of Arduino types for the same reason.
|
||||
- Frames are only sent when the screen changes. The schedule uses 08:00 as the start of
|
||||
the day on purpose (that is what the old code did, and the README documents it).
|
||||
- Home Assistant switches are not optimistic: callbacks must call `sender->setState()`.
|
||||
The unique ids `FlipDotDisplayClock` / `FlipDotDisplayPower` and `HA_DEVICE_ID` must stay
|
||||
the same, or Home Assistant creates new entities.
|
||||
|
||||
## Git
|
||||
|
||||
|
||||
Reference in new issue
Block a user