Public Access
Add dynamic pages with clock, date, Home Assistant values and fonts
The hardcoded clock is now the built-in dynamic page "Clock". A dynamic page has a background bitmap per time of day and up to 8 elements (clock, date, Home Assistant value, text), each with position, alignment, font and overlay. Pages are drawn on the ESP32, also for the web previews. - Pages tab in the web UI: page editor with drag and drop, font upload (TTF/OTF converted in the browser to the FDF1 dot font format) - Built-in fonts Small, Normal, Large and Full height (generated by tools/make_builtin_fonts.py), built-in bitmaps Pikachu and Pikachu yawning - Home Assistant values over the REST API (URL and token in the HA tab) - Settings version 4: evening settings removed (now time slots of the Clock page), old records are converted - HA Mode dropdown lists the dynamic pages and bitmaps Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
1d3b084de1
commit
0411804a2f
49 files changed
+3058
-331
No files matched your search
@@ -7,21 +7,77 @@
|
||||
static const char *NAMESPACE = "flipdot";
|
||||
static const char *KEY = "settings";
|
||||
// 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;
|
||||
// unless the code below says how to read them. When fields are only added at the end,
|
||||
// an old record is the start of the new layout: add an OldVersion for it like v3 does.
|
||||
static const uint16_t VERSION = 4;
|
||||
|
||||
// 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
|
||||
size_t fields; // bytes of the settings struct that version had
|
||||
};
|
||||
static const OldVersion OLD_VERSIONS[] = {
|
||||
|
||||
// Versions 1-3: the layout before the dynamic pages (version 4 removed the evening
|
||||
// settings of the clock and added the Home Assistant API). Frozen copy, read as a prefix
|
||||
// (version 1 lacked off_page and drive, version 2 drive) and converted.
|
||||
namespace v3
|
||||
{
|
||||
struct ClockSettings
|
||||
{
|
||||
bool evening_enabled = true;
|
||||
uint16_t evening_start = 17 * 60;
|
||||
bool no_wifi_screen = true;
|
||||
};
|
||||
|
||||
struct HomeAssistantSettings
|
||||
{
|
||||
bool enabled = true;
|
||||
char host[64] = "";
|
||||
uint16_t port = 1883;
|
||||
char user[32] = "";
|
||||
char password[64] = "";
|
||||
};
|
||||
|
||||
struct Settings
|
||||
{
|
||||
char page[25] = "clock";
|
||||
ScheduleSettings schedule;
|
||||
ClockSettings clock;
|
||||
HomeAssistantSettings home_assistant;
|
||||
uint16_t flip_time = 225;
|
||||
bool status_led = true;
|
||||
char off_page[25] = "";
|
||||
DriveSettings drive;
|
||||
};
|
||||
|
||||
static const OldVersion VERSIONS[] = {
|
||||
{1, offsetof(Settings, off_page)},
|
||||
{2, offsetof(Settings, drive)},
|
||||
{3, sizeof(Settings)},
|
||||
};
|
||||
|
||||
static void convert(const Settings &old, ::Settings &s)
|
||||
{
|
||||
s = ::Settings();
|
||||
memcpy(s.page, old.page, sizeof(s.page));
|
||||
s.schedule = old.schedule;
|
||||
s.clock.no_wifi_screen = old.clock.no_wifi_screen;
|
||||
const HomeAssistantSettings &ha = old.home_assistant;
|
||||
s.home_assistant.enabled = ha.enabled;
|
||||
memcpy(s.home_assistant.host, ha.host, sizeof(ha.host));
|
||||
s.home_assistant.port = ha.port;
|
||||
memcpy(s.home_assistant.user, ha.user, sizeof(ha.user));
|
||||
memcpy(s.home_assistant.password, ha.password, sizeof(ha.password));
|
||||
s.flip_time = old.flip_time;
|
||||
s.status_led = old.status_led;
|
||||
memcpy(s.off_page, old.off_page, sizeof(s.off_page));
|
||||
s.drive = old.drive;
|
||||
// The evening (yawning Pikachu from 17:00) is now a time slot of the built-in Clock page
|
||||
}
|
||||
} // namespace v3
|
||||
|
||||
static_assert(sizeof(v3::Settings) <= sizeof(Settings), "the old record must fit the buffer");
|
||||
|
||||
struct StoredSettings
|
||||
{
|
||||
uint16_t version;
|
||||
@@ -29,6 +85,16 @@ struct StoredSettings
|
||||
Settings settings;
|
||||
};
|
||||
|
||||
// Reads a record whose first `fields` bytes have the layout of T
|
||||
template <typename T> static bool readOld(const StoredSettings &stored, size_t read, size_t fields, T &out)
|
||||
{
|
||||
if (stored.size < fields || stored.size > sizeof(T) || read != offsetof(StoredSettings, settings) + stored.size)
|
||||
return false;
|
||||
out = T();
|
||||
memcpy((void *)&out, &stored.settings, fields);
|
||||
return true;
|
||||
}
|
||||
|
||||
void loadSettings(Settings &settings)
|
||||
{
|
||||
settings = Settings();
|
||||
@@ -44,14 +110,17 @@ void loadSettings(Settings &settings)
|
||||
loaded = stored.settings;
|
||||
else
|
||||
{
|
||||
const OldVersion *old = nullptr;
|
||||
for (const OldVersion &o : OLD_VERSIONS)
|
||||
bool ok = false;
|
||||
for (const OldVersion &o : v3::VERSIONS)
|
||||
if (o.version == stored.version)
|
||||
old = &o;
|
||||
if (!old || stored.size < old->fields || stored.size > sizeof(Settings) ||
|
||||
read != offsetof(StoredSettings, settings) + stored.size)
|
||||
{
|
||||
v3::Settings old;
|
||||
ok = readOld(stored, read, o.fields, old);
|
||||
if (ok)
|
||||
v3::convert(old, loaded);
|
||||
}
|
||||
if (!ok)
|
||||
return;
|
||||
memcpy((void *)&loaded, &stored.settings, old->fields);
|
||||
}
|
||||
if (validateSettings(loaded) == nullptr)
|
||||
settings = loaded;
|
||||
|
||||
Reference in new issue
Block a user