2026-10-13 01:52:34 +02:00
|
|
|
#include "system/settings_store.h"
|
|
|
|
|
|
|
|
|
|
#include <Preferences.h>
|
2026-10-15 01:29:01 +02:00
|
|
|
#include <stddef.h>
|
|
|
|
|
#include <string.h>
|
2026-10-13 01:52:34 +02:00
|
|
|
|
|
|
|
|
static const char *NAMESPACE = "flipdot";
|
|
|
|
|
static const char *KEY = "settings";
|
2026-10-15 01:29:01 +02:00
|
|
|
// Increase when the Settings struct changes; old records are then ignored (defaults),
|
2026-10-15 19:43:59 +02:00
|
|
|
// 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;
|
2026-10-15 01:29:01 +02:00
|
|
|
|
|
|
|
|
struct OldVersion
|
|
|
|
|
{
|
|
|
|
|
uint16_t version;
|
2026-10-15 19:43:59 +02:00
|
|
|
size_t fields; // bytes of the settings struct that version had
|
2026-10-15 01:29:01 +02:00
|
|
|
};
|
2026-10-15 19:43:59 +02:00
|
|
|
|
|
|
|
|
// 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[] = {
|
2026-10-15 01:29:01 +02:00
|
|
|
{1, offsetof(Settings, off_page)},
|
|
|
|
|
{2, offsetof(Settings, drive)},
|
2026-10-15 19:43:59 +02:00
|
|
|
{3, sizeof(Settings)},
|
2026-10-15 01:29:01 +02:00
|
|
|
};
|
2026-10-13 01:52:34 +02:00
|
|
|
|
2026-10-15 19:43:59 +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;
|
|
|
|
|
};
|
|
|
|
|
|
2026-10-15 19:43:59 +02:00
|
|
|
// 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;
|
2026-10-15 01:29:01 +02:00
|
|
|
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();
|
2026-10-15 01:29:01 +02:00
|
|
|
|
|
|
|
|
Settings loaded;
|
|
|
|
|
if (read == sizeof(stored) && stored.version == VERSION && stored.size == sizeof(Settings))
|
|
|
|
|
loaded = stored.settings;
|
|
|
|
|
else
|
|
|
|
|
{
|
2026-10-15 19:43:59 +02:00
|
|
|
bool ok = false;
|
|
|
|
|
for (const OldVersion &o : v3::VERSIONS)
|
2026-10-15 01:29:01 +02:00
|
|
|
if (o.version == stored.version)
|
2026-10-15 19:43:59 +02:00
|
|
|
{
|
|
|
|
|
v3::Settings old;
|
|
|
|
|
ok = readOld(stored, read, o.fields, old);
|
|
|
|
|
if (ok)
|
|
|
|
|
v3::convert(old, loaded);
|
|
|
|
|
}
|
|
|
|
|
if (!ok)
|
2026-10-15 01:29:01 +02:00
|
|
|
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();
|
|
|
|
|
}
|