Add a web UI to the clock firmware

- Web UI on port 80: live view of the display, choose the page (clock or
  a bitmap), bitmaps (list, show, live, edit, delete), draw / image upload
  with conversion in the browser, live mode, settings, Home Assistant
- Live view from a shadow buffer in the BROSE9323 driver (shownPixel)
- Settings in NVS with the original behaviour as defaults: schedule times,
  quarter hours, yawning Pikachu, No WiFi screen, flip time, status LED,
  Home Assistant broker (restart on change)
- Bitmaps on LittleFS; new 320 KB partition, ota slots now 1.31 MB
- DisplayController: pages, live mode, settings; app.cpp holds the objects
- README for the recovery app; docs for the web UI
- Tests: settings, time parsing, bitmap format and names, schedule with
  settings, pages and live mode (95 total)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
mangavalkandClaude Opus 5.5 committed 2026-10-13 01:52:34 +02:00
1 parent c9e0800e36
commit 2b1ac12ed7
34 files changed
+1989 -117

No files matched your search

+25 -9
View File
@@ -11,12 +11,15 @@ debug output. Wiring is described in the [main README](../../README.md#wiring-be
| File | Contents |
|------|----------|
| `main.cpp` | Creates the objects, `setup()` and `loop()` |
| `config.h` | All settings: pins, timings, timezone, Home Assistant device id |
| `main.cpp` | `setup()` and `loop()`: the order of things at boot and in the loop |
| `app.*` | The objects (display, Nano link, controller, ...) and the actions the web UI uses: settings, page, live mode |
| `config.h` | Fixed settings: pins, timings, timezone, Home Assistant device id |
| `credentials.h` | Includes `secrets.h` (WiFi and MQTT credentials, **gitignored**); template: `secrets.example.h` |
| **`logic/`** | **Pure logic without Arduino code, tested on the PC** |
| `logic/display_controller.*` | Decides what is shown and when the display is powered (`DisplayController`) |
| `logic/schedule.*` | When the clock is shown (night, clock, standby) |
| `logic/display_controller.*` | Decides what is shown and when the display is powered: page, live mode, schedule, Home Assistant switches (`DisplayController`) |
| `logic/settings.*` | The settings of the web UI with their defaults and checks |
| `logic/schedule.*` | When the display is on (night, clock, standby), from the settings |
| `logic/bitmap_format.*` | 84 x 19 bitmap format, hex encoding, bitmap names |
| `logic/power_timer.*` | Countdown to switching the display off |
| `logic/status_led.h` | Blink pattern of the built-in LED |
| `logic/boot_policy.*` | How to start after a reset: normal, OTA-only window, safe mode |
@@ -24,8 +27,10 @@ debug output. Wiring is described in the [main README](../../README.md#wiring-be
| `system/firmware_guard.*` | Rollback of bad OTA firmware, watchdog, crash counter, power-cycle trigger, status for Home Assistant |
| `system/boot_target.*` | Switching between the clock firmware and the recovery app |
| `system/system_store.*` | Settings in NVS: WiFi, recovery reason, firmware slot and version, power-on count |
| `system/settings_store.*` | The web UI settings in NVS |
| **`display/`** | **What is drawn** |
| `display/frames.*` | The screens of the clock: clock, "No WiFi", logo, blank (`FlipdotFrames`) |
| `display/frames.*` | The screens: clock, "No WiFi", page bitmap, live image, logo, blank (`FlipdotFrames`) |
| `display/bitmap_store.*` | Built-in bitmaps and the uploaded ones on LittleFS |
| `display/status_screen.*` | Two-line status messages at boot, after a crash and in the recovery app (`StatusScreen`) |
| `display/text.*` | Centered text lines on the 84 x 19 display |
| `display/bitmaps.h` | 1-bit bitmaps (Pikachu, thought bubble, Nintendo logo, ...) |
@@ -35,6 +40,9 @@ debug output. Wiring is described in the [main README](../../README.md#wiring-be
| **`net/`** | **Network** |
| `net/wifi_ota.*` | WiFi (with a restart when it stays down), ESP32 OTA updates, OTA-only mode |
| `net/home_assistant.*` | MQTT and the Home Assistant entities (two switches, firmware sensor, recovery button) |
| **`web/`** | **Web UI** (see the [main README](../../README.md#web-ui)) |
| `web/web_ui.*` | Web server and JSON API (the endpoints are listed in `web_ui.h`) |
| `web/web_page.h` | The page: HTML and JavaScript; the browser converts images and draws the previews |
`DisplayController` only talks to two small interfaces, `DisplayPower` (implemented by
`NanoLink`) and `Frames` (implemented by `FlipdotFrames`). The tests replace both with
@@ -130,7 +138,15 @@ controller (power on/off, redraws, both Home Assistant switches).
## Adding bitmaps
Convert an image with [image2cpp](https://javl.github.io/image2cpp/) to "Arduino code",
horizontal, 1 bit per pixel. Paste the array into `display/bitmaps.h` and draw it in
`display/frames.cpp` with `_display.drawBitmap(x, y, bitmap, w, h, 1)`. The display is
84 × 19 pixels.
The easy way is the web UI (Draw / Live tab): load an image, adjust it, save it.
To build a bitmap into the firmware instead: convert it with
[image2cpp](https://javl.github.io/image2cpp/) to "Arduino code", horizontal, 1 bit per
pixel, 84 x 19. Paste the array into `display/bitmaps.h` and add it to `BUILTIN` in
`display/bitmap_store.cpp`.
## Live view of the display
The BROSE9323 driver keeps a shadow copy of what was sent to the Nano (`shownPixel()`),
updated on every `display()`. `GET /api/screen` returns it, and the web UI polls it. It
also keeps the last image while the display is switched off, like the dots do.