Slow mode (Home Assistant switch "FlipDot Display Slow mode", dropdown
"FlipDot Display Slow mode interval", web UI Settings) makes the display
change only every 1, 2, 5, 10, 15, 20, 30 or 60 minutes, on the clock, and
powers it off in between: less noise. Nothing else is drawn meanwhile, also
no page changes. It updates at once after boot, the night, the Power switch
and live mode, and when it is switched off. Rules can override it ("show at
once in slow mode"): while their page is shown the display works normally.
Settings layout 7 and schedule layout 4 (older records are converted; they
are stored again once the firmware is marked good, so a rollback still
finds the old ones). Pages in older formats are rewritten in the current
one, in time-boxed steps. /api/status reports old_data_left.
Version 2.15.0.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Flipdot Display
Firmware for an 84 × 19 Brose flipdot display (three 28 × 19 panels) that runs as a Home Assistant–controlled clock.
The system uses two microcontrollers:
┌──────────────┐ WiFi / MQTT ┌────────────────┐ UART 115200 ┌──────────────┐ GPIO ┌──────────────┐
│ Home │ ◄─────────────► │ ESP32 │ ──────────────► │ Arduino Nano │ ───────► │ Brose │
│ Assistant │ │ (controller) │ text commands │ (driver) │ │ flipdot │
└──────────────┘ └───────┬────────┘ └──────▲───────┘ │ panels 84x19 │
│ GPIO 13 │ └──────▲───────┘
▼ │ power │
┌─────────────┐ │ │
│ Relay │ ─────────────────────────┴─────────────────────────┘
└─────────────┘
The relay powers both the panels and the Nano. When the relay is off, the Nano is off too, and it boots again each time the relay switches on. Only the ESP32 runs all the time.
| Part | Folder | What it does |
|---|---|---|
| Arduino Nano | src/nano | Drives the panel's row and column electronics directly. Keeps a frame buffer and flips only the dots that changed. Takes simple text commands over serial. |
| ESP32 | src/esp32 | Connects to WiFi, gets the time over NTP, shows dynamic pages (a background per time of day with a clock, the date, Home Assistant values or text) or your own bitmaps, has a web UI, exposes switches to Home Assistant over MQTT, switches display power with a relay and supports OTA updates. |
| ESP32 recovery app | src/recovery | Small second app: WiFi setup page and OTA, so the ESP32 stays reachable when the clock firmware fails. |
| BROSE9323 driver | lib/BROSE9323 | Shared Adafruit_GFX display driver. On the Nano it toggles GPIO pins. On the ESP32 (-D ESPSERIAL) it turns every drawing call into a serial command for the Nano. |
Because the driver is an Adafruit_GFX subclass on both boards, the ESP32 can use the
normal GFX API (print, drawBitmap, fillScreen, ...). Each call is sent to the
Nano and drawn there.
Documentation
| Page | About |
|---|---|
| Web UI | The web page of the display, with screenshots of every tab |
| Pages | Pictures of the dynamic pages |
| Home Assistant | Entities, values on pages, rules |
| How it works | Boot, the clock and the schedule, the Nano |
| Wiring | Between the ESP32, the Nano and the relay |
| Flashing the Nano over WiFi | nano_ota, bootloaders, troubleshooting |
| Recovery app | The ESP32's second app: WiFi setup and OTA, one-time USB installation |
| Nano firmware | The serial protocol and the Nano's behaviour |
| ESP32 firmware | The files, keeping OTA alive, tests |
Repository layout
platformio.ini one PlatformIO project, multiple environments
lib/BROSE9323/ shared flipdot driver (used by both targets)
src/nano/ Arduino Nano firmware
src/esp32/ ESP32 clock firmware
secrets.example.h template for WiFi / MQTT credentials
web/ the web UI
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/test_driver/ unit tests for the display driver with a simulated panel
test/mocks/ fake Arduino, EEPROM and display driver for the Nano tests
test/driver_mocks/ fake Arduino and Adafruit GFX for the driver tests
tools/ uploader for flashing the Nano over WiFi, font and picture scripts
docs/ documentation (see below)
Pictures/ photos, pictures of the pages and screenshots of the web UI
examples/ example pages to import in the web UI
archive/ old, unused sketches kept for reference
CLAUDE.md project notes for Claude Code
Building and flashing
Install PlatformIO (the VS Code extension is easiest) and open this folder.
- Copy
src/esp32/secrets.example.htosrc/esp32/secrets.hand fill in your WiFi and MQTT details.secrets.his gitignored. - Check the COM ports and the OTA IP in
platformio.ini. - Build or upload:
| Environment | Board | Upload method |
|---|---|---|
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. |
esp32 |
ESP32 DevKit v1 | OTA over WiFi (10.1.1.198) |
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 |
pio run # build nano + esp32
pio run -e nano -t upload # flash the Nano over USB
pio run -e nano_ota -t upload # flash the Nano over WiFi, through the ESP32
pio run -e esp32 -t upload # flash the ESP32 over the air
pio run -e esp32_usb -t upload # flash the ESP32 over USB
pio device monitor -e nano # serial monitor
pio test -e test_nano -e test_esp32 -e test_driver # run the unit tests on the PC
The unit tests need gcc/g++ on your PATH. On Windows you can get it from
MSYS2 or Strawberry Perl.
In VS Code you can also pick the environment in the PlatformIO status bar.
Tip: The ESP32 → Nano wire goes to the Nano's RX pin, which the Nano's USB chip also uses. Disconnect that wire before you flash the Nano over USB, or the upload may fail. Flashing over WiFi (
nano_ota) doesn't have this problem.
TODO
-
Install the recovery app (one-time USB flash, see One-time installation). 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 web UI on the real hardware: live view, uploading and drawing a bitmap, live mode, choosing a page, settings, Home Assistant settings (restart).
-
Test the dynamic pages on the real hardware: the Clock page after the update (same as before, yawning from 17:00), a new page with date and Full height clock, an uploaded font, a Home Assistant value (token in the Home Assistant tab).
-
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 ofSerial(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 Wiring between ESP32 and Nano. -
Add the reply wire: Nano D1 (TX) → voltage divider → ESP32 GPIO 16 (RX2). With it, the ESP sends the first frame as soon as the Nano is ready, instead of after 3 s, and the Nano can be flashed over WiFi (
nano_ota). -
Test flashing over WiFi on the real hardware. The uploader is tested against a simulated bootloader, not yet against the real Nano. Check especially that the old bootloader starts after the relay power cycle.
-
Tune the flip settings on the real hardware. Flash both the Nano and the ESP32 (the old Nano ignores the new
G/N/Ecommands). With the test patterns (Settings), try flip gap 0/200/500 µs and 1 or 2 pulses, and check that the refresh effects look right and that the off image is complete before the power is cut. -
Check the boot timing. Build the Nano with
MEASURE_REFRESHto see how long a full redraw takes. If the reply wire isn't fitted, check that 3 s (NANO_BOOT_TIMEOUT) is enough for the Nano to boot. -
Watch the frame check. After a few days, look at System, "Frame check": any "wrong" or "no answer" counts mean bytes still get lost somewhere; "Nano restarted" means power dips (e.g. while many dots flip).