#include "system/settings_store.h" #include #include #include 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)}, }; 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; uint16_t size; Settings settings; }; // Reads a record whose first `fields` bytes have the layout of T template 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(); StoredSettings stored; memset(&stored, 0, sizeof(stored)); 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; } 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(); }