Add HA dropdowns, off image, flip fixes and refresh effects

Home Assistant: remove the Clock switch (its entity is deleted on connect),
add an IP sensor and Mode / Off image dropdowns whose options follow the
saved bitmaps. Remove the built-in "Nintendo border" bitmap.

Off image: a bitmap (or blank) left on the dots when the display switches off.

Nano: pause after each flip pulse and repeat pulses for changed dots, both
stored in EEPROM (G/N commands). Refresh effects (E command): instant,
sparkle, wipe, rain, from the middle out, diagonal, spiral, flip all then
reveal, push and random, optionally only for small changes. Effects run in
steps from loop() so serial input keeps flowing.

Web UI: flip settings, refresh effect and test patterns under Settings; the
System tab shows why the ESP32 last restarted. Settings records from
versions 1 and 2 are migrated. Firmware 2.3.0.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
mangavalkandClaude Opus 5.5 committed 2026-10-15 01:29:01 +02:00
1 parent 4a5f6a8b5d
commit 1d3b084de1
42 files changed
+1631 -230

No files matched your search

+29
View File
@@ -168,6 +168,35 @@ void firmwareGuardLoop(bool wifi_connected)
}
}
const char *firmwareGuardResetReason()
{
switch (esp_reset_reason())
{
case ESP_RST_POWERON:
return "power on";
case ESP_RST_EXT:
return "reset pin";
case ESP_RST_SW:
return "restart";
case ESP_RST_PANIC:
return "crash";
case ESP_RST_INT_WDT:
return "watchdog (interrupt)";
case ESP_RST_TASK_WDT:
return "watchdog (task)";
case ESP_RST_WDT:
return "watchdog";
case ESP_RST_DEEPSLEEP:
return "deep sleep";
case ESP_RST_BROWNOUT:
return "brownout (supply voltage dropped)";
case ESP_RST_SDIO:
return "SDIO";
default:
return "unknown";
}
}
const char *firmwareGuardStatus()
{
return status;
+4
View File
@@ -33,3 +33,7 @@ void firmwareGuardLoop(bool wifi_connected);
// Short status for Home Assistant, e.g. "2.1.0, ok, 0 crashes, recovery ok"
const char *firmwareGuardStatus();
// Why the ESP32 restarted last, e.g. "power on", "crash", "brownout". After a rollback
// this is why the new firmware stopped.
const char *firmwareGuardResetReason();
+35 -5
View File
@@ -1,11 +1,26 @@
#include "system/settings_store.h"
#include <Preferences.h>
#include <stddef.h>
#include <string.h>
static const char *NAMESPACE = "flipdot";
static const char *KEY = "settings";
// Increase when the Settings struct changes; old records are then ignored (defaults)
static const uint16_t VERSION = 1;
// Increase when the Settings struct changes; old records are then ignored (defaults),
// unless OLD_VERSIONS says how to read them
static const uint16_t VERSION = 3;
// Older versions: fields were only added at the end, so the start of an old record has
// the same layout. New fields keep their defaults.
struct OldVersion
{
uint16_t version;
size_t fields; // bytes of Settings that version had
};
static const OldVersion OLD_VERSIONS[] = {
{1, offsetof(Settings, off_page)},
{2, offsetof(Settings, drive)},
};
struct StoredSettings
{
@@ -18,13 +33,28 @@ void loadSettings(Settings &settings)
{
settings = Settings();
StoredSettings stored;
memset(&stored, 0, sizeof(stored));
Preferences prefs;
prefs.begin(NAMESPACE, true);
size_t read = prefs.getBytes(KEY, &stored, sizeof(stored));
prefs.end();
if (read == sizeof(stored) && stored.version == VERSION && stored.size == sizeof(Settings) &&
validateSettings(stored.settings) == nullptr)
settings = stored.settings;
Settings loaded;
if (read == sizeof(stored) && stored.version == VERSION && stored.size == sizeof(Settings))
loaded = stored.settings;
else
{
const OldVersion *old = nullptr;
for (const OldVersion &o : OLD_VERSIONS)
if (o.version == stored.version)
old = &o;
if (!old || stored.size < old->fields || stored.size > sizeof(Settings) ||
read != offsetof(StoredSettings, settings) + stored.size)
return;
memcpy((void *)&loaded, &stored.settings, old->fields);
}
if (validateSettings(loaded) == nullptr)
settings = loaded;
}
void saveSettings(const Settings &settings)