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

+59
View File
@@ -0,0 +1,59 @@
#pragma once
#include <stdint.h>
// Everything that can be changed in the web UI. Stored in NVS (system/settings_store),
// the defaults are the original behaviour. Pure logic, tested in test/test_esp32.
//
// Times are minutes since midnight (8:00 = 480).
struct ScheduleSettings
{
bool enabled = true; // false: the display is always on
uint16_t day_start = 8 * 60; // display on from
uint16_t late_start = 22 * 60; // from here only at the quarter hours (see quarter_hours)
bool quarter_hours = true; // false: on all the time until night_start
uint16_t night_start = 23 * 60 + 31; // display off from (up to 24:00 = never)
};
struct ClockSettings
{
bool evening_enabled = true; // yawning Pikachu in the evening
uint16_t evening_start = 17 * 60;
bool no_wifi_screen = true; // "No WiFi" instead of the clock when WiFi is down
};
struct HomeAssistantSettings
{
bool enabled = true;
char host[64] = ""; // empty: BROKER_ADDR from secrets.h
uint16_t port = 1883;
char user[32] = "";
char password[64] = "";
};
// What is shown during the day: "clock" or the name of a bitmap
const int PAGE_NAME_SIZE = 25;
#define PAGE_CLOCK "clock"
struct Settings
{
char page[PAGE_NAME_SIZE] = PAGE_CLOCK;
ScheduleSettings schedule;
ClockSettings clock;
HomeAssistantSettings home_assistant;
uint16_t flip_time = 225; // flip pulse length in us, sent to the Nano
bool status_led = true;
};
const uint16_t MINUTES_PER_DAY = 24 * 60;
const uint16_t SETTINGS_FLIP_TIME_MIN = 50;
const uint16_t SETTINGS_FLIP_TIME_MAX = 2000;
// Checks the values. Returns nullptr when they are fine, otherwise a message for the user.
const char *validateSettings(const Settings &settings);
// "HH:MM" (00:00 - 24:00) <-> minutes since midnight
bool parseTime(const char *text, uint16_t &minutes);
// out must hold 6 characters
void formatTime(uint16_t minutes, char *out);