Files
Flipdot-display/src/esp32/system/settings_store.cpp
T

139 lines
3.7 KiB
C++
Raw Normal View History

2026-10-13 01:52:34 +02:00
#include "system/settings_store.h"
#include <Preferences.h>
#include <stddef.h>
#include <string.h>
2026-10-13 01:52:34 +02:00
static const char *NAMESPACE = "flipdot";
static const char *KEY = "settings";
// Increase when the Settings struct changes; old records are then ignored (defaults),
// 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;
struct OldVersion
{
uint16_t version;
size_t fields; // bytes of the settings struct that version had
};
// 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)},
};
2026-10-13 01:52:34 +02:00
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");
2026-10-13 01:52:34 +02:00
struct StoredSettings
{
uint16_t version;
uint16_t size;
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;
}
2026-10-13 01:52:34 +02:00
void loadSettings(Settings &settings)
{
settings = Settings();
StoredSettings stored;
memset(&stored, 0, sizeof(stored));
2026-10-13 01:52:34 +02:00
Preferences prefs;
prefs.begin(NAMESPACE, true);
size_t read = prefs.getBytes(KEY, &stored, sizeof(stored));
prefs.end();
Settings loaded;
if (read == sizeof(stored) && stored.version == VERSION && stored.size == sizeof(Settings))
loaded = stored.settings;
else
{
bool ok = false;
for (const OldVersion &o : v3::VERSIONS)
if (o.version == stored.version)
{
v3::Settings old;
ok = readOld(stored, read, o.fields, old);
if (ok)
v3::convert(old, loaded);
}
if (!ok)
return;
}
if (validateSettings(loaded) == nullptr)
settings = loaded;
2026-10-13 01:52:34 +02:00
}
void saveSettings(const Settings &settings)
{
StoredSettings stored;
stored.version = VERSION;
stored.size = sizeof(Settings);
stored.settings = settings;
Preferences prefs;
prefs.begin(NAMESPACE, false);
prefs.putBytes(KEY, &stored, sizeof(stored));
prefs.end();
}