# 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](src/nano/README.md) | 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](src/esp32/README.md) | 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](docs/web-ui.md), exposes switches to Home Assistant over MQTT, switches display power with a relay and supports OTA updates. | | **ESP32 recovery app** | [src/recovery](src/recovery/README.md) | Small second app: WiFi setup page and OTA, so the ESP32 stays reachable when the clock firmware fails. | | **BROSE9323 driver** | [lib/BROSE9323](lib/BROSE9323/README.md) | 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](docs/web-ui.md) | The web page of the display, with screenshots of every tab | | [Pages](docs/pages.md) | Pictures of the dynamic pages | | [Home Assistant](docs/home-assistant.md) | Entities, values on pages, rules | | [How it works](docs/how-it-works.md) | Boot, the clock and the schedule, the Nano | | [Wiring](docs/wiring.md) | Between the ESP32, the Nano and the relay | | [Flashing the Nano over WiFi](docs/nano-ota.md) | `nano_ota`, bootloaders, troubleshooting | | [Recovery app](docs/recovery.md) | The ESP32's second app: WiFi setup and OTA, one-time USB installation | | [Nano firmware](src/nano/README.md) | The serial protocol and the Nano's behaviour | | [ESP32 firmware](src/esp32/README.md) | 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](https://platformio.org/) (the VS Code extension is easiest) and open this folder. 1. Copy `src/esp32/secrets.example.h` to `src/esp32/secrets.h` and fill in your WiFi and MQTT details. `secrets.h` is gitignored. 2. Check the COM ports and the OTA IP in `platformio.ini`. 3. 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](docs/nano-ota.md). | | `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](docs/recovery.md) | ```sh 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](https://www.msys2.org/) 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 - [X] **Install the recovery app** (one-time USB flash, see [One-time installation](docs/recovery.md#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". - [X] **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. - [X] **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 [Wiring between ESP32 and Nano](docs/wiring.md). - [X] **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`). - [X] **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`/`E` commands). 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_REFRESH` to 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).