Public Access
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>
6.0 KiB
6.0 KiB
Flipdot display: notes for Claude
A PlatformIO project with two firmware targets for an 84×19 Brose flipdot clock. See README.md for the overview.
Layout and build
platformio.inihas the environmentsnano,nano_ota,esp32(OTA to 10.1.1.198),esp32_usb(COM11), and the PC test environmentstest_nanoandtest_esp32. The sources are split withbuild_src_filter(src/nano/,src/esp32/).lib/BROSE9323/is the shared driver. Its behaviour depends on build flags set inplatformio.ini, not in the header:ESPSERIAL(ESP32): draw calls become serial commands.FLIPDOT_PLCC_ADAPTER(Nano, optional): alternative pinout.
- PlatformIO CLI is not on PATH. Run
~/.platformio/penv/Scripts/pio.exe run(Git Bash) or$env:USERPROFILE\.platformio\penv\Scripts\pio.exe run(PowerShell). - If an ESP32 build fails with
No module named 'intelhex', run~/.platformio/penv/Scripts/python.exe -m pip install intelhex. nano_otaflashes the Nano over WiFi:tools/nano_net_upload.py(STK500v1 over TCP) talks toNanoFlashBridgeon the ESP32 (port 2323). The uploader first sendsFLASH <baud>\n. The ESP power cycles the Nano (plus an optional reset pulse onNANO_RESET_PIN), answersOK\n, then passes the bytes through at that baud rate.--baud=autotries 57600 (old ATmegaBOOT, which also starts after a power cycle), then 115200 (Optiboot, which only starts after a reset-pin reset, so it needsNANO_RESET_PIN). avrdude 6.3'snet: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.- 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. AvoidString, large stack buffers and new globals in Nano code. Put constant data inPROGMEM. - The ESP32 talks to the Nano on
Serial2(owned byNanoLink, GPIO 17 TX / GPIO 16 RX).Serial(UART0/USB) is free for debug output. Never write debug text toSerial2: 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
TODOsection. Update it when one is done. src/esp32/secrets.hholds the real credentials and is gitignored. Never commit it. Add new secrets tosecrets.example.has 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 sendsREADY.NanoLink::on()waits for that message, at mostNANO_BOOT_TIMEOUT. The timeout also covers the case where the Nano's TX isn't wired to the ESP's RX. NanoLink::off()endsSerial2and sets its TX pin to input. Otherwise the idle-high TX line powers the Nano through its RX pin. Never send display commands whileNanoLink::isOn()is false (DisplayControllertakes 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.
Nano serial parser (src/nano/main.cpp)
- Files:
main.cpp(setup/loop, refresh),config.h(all constants and debug defines),command_parser.*(protocol, one method per command),settings.*(EEPROM). - Line based and non-blocking: bytes are collected into a 64-character buffer and a
command runs when
\narrives. Bitmap (B) pixel data is the exception. It is drawn while it arrives, so it can be longer than the buffer. Keeploop()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 aD, or after 2 s with no serial data. The ESP only sends a frame (ending withD) when the screen changes, mostly once a minute. - Flip time is stored in EEPROM as a magic word at address 0 and a
uint16_tat address 2. Values outside 50–2000 µs are rejected. - The Nano only replies
READY(at boot) andERR: .... 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 test_nanorunstest/test_nano/on the PC with the fakes intest/mocks/(Arduino, EEPROM, and aBROSE9323that records calls). It needs gcc on PATH (Strawberry Perl provides it atC:\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. Plainpio testruns nothing; always pass-e test_nano -e test_esp32.
ESP32 structure (src/esp32/)
- Folders:
logic/(pure C++, no Arduino includes, tested intest/test_esp32),display/(frames and bitmaps),nano_link/(relay, serial, flash bridge),net/(WiFi/OTA, Home Assistant). Includes are relative tosrc/esp32(-I src/esp32), e.g.#include "logic/schedule.h". DisplayControllerholds the display logic and only uses theDisplayPowerandFramesinterfaces. Put new behaviour there, behind those interfaces, so it stays testable.logic/must not include Arduino headers, ortest_esp32stops 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 idsFlipDotDisplayClock/FlipDotDisplayPowerandHA_DEVICE_IDmust stay the same, or Home Assistant creates new entities.
Git
- Remote: https://mangavalk.com/mangavalk/Flipdot-display.git, branch
main.