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

+15 -15
View File
@@ -1,27 +1,20 @@
// ESP32 firmware: decides what the flipdot display shows (clock, Home Assistant switches)
// and sends it to the Nano, which drives the panels. See README.md.
// ESP32 firmware: decides what the flipdot display shows (clock, bitmaps, web UI, Home
// Assistant) and sends it to the Nano, which drives the panels. See README.md.
// The objects and the actions on them are in app.cpp; this file only sets the order.
#include <Arduino.h>
#include <time.h>
#include "BROSE9323.h"
#include "app.h"
#include "config.h"
#include "display/frames.h"
#include "display/status_screen.h"
#include "logic/display_controller.h"
#include "display/bitmap_store.h"
#include "logic/status_led.h"
#include "nano_link/nano_flash_bridge.h"
#include "nano_link/nano_link.h"
#include "net/home_assistant.h"
#include "net/wifi_ota.h"
#include "system/boot_target.h"
#include "system/firmware_guard.h"
NanoLink nano(Serial2, NANO_RX_PIN, NANO_TX_PIN, RELAY_PIN, NANO_RESET_PIN);
BROSE9323 display(DISPLAY_WIDTH, DISPLAY_HEIGHT, PANEL_WIDTH);
FlipdotFrames frames(display);
StatusScreen status(display, nano);
DisplayController controller(nano, frames);
#include "web/web_ui.h"
// Flashing the Nano over WiFi: restart it into its bootloader, switch it off afterwards.
// The controller then powers it on again with the new firmware.
@@ -39,6 +32,9 @@ void setup()
// Decided first, before anything that could crash
BootMode boot_mode = firmwareGuardBegin();
bitmapStoreBegin();
appLoadSettings();
if (boot_mode == BootMode::Normal)
status.show("Connecting", "to WiFi...", true);
wifiBegin(); // starts the recovery app when it fails
@@ -66,7 +62,8 @@ void setup()
status.show("Getting", "the time...", true);
configTzTime(TIMEZONE, NTP_SERVER);
homeAssistantBegin(controller);
homeAssistantBegin(controller, appSettings().home_assistant);
webUiBegin();
firmwareGuardStartWatchdog();
}
@@ -80,6 +77,8 @@ void loop()
homeAssistantSetFirmwareStatus(firmwareGuardStatus());
homeAssistantLoop();
webUiLoop();
appLoop();
// While the Nano is being flashed, nothing else may talk to it
if (flashBridge.handle())
@@ -89,5 +88,6 @@ void loop()
bool time_valid = getLocalTime(&now, 0);
controller.update(millis(), time_valid, now.tm_hour, now.tm_min, wifiConnected());
digitalWrite(LED_PIN, statusLedLevel(millis(), wifiConnected(), nano.isOn()));
bool led = appSettings().status_led && statusLedLevel(millis(), wifiConnected(), nano.isOn());
digitalWrite(LED_PIN, led);
}