#include "settings.h" #include "ipv4.h" namespace roro { namespace { enum class Kind : uint8_t { Bool, Int, String }; struct Definition { const char* key; Kind kind; int32_t defaultInt; const char* defaultString; int32_t min; int32_t max; // for strings: max length in bytes }; // Order must match the Setting enum. const Definition kDefinitions[] = { {"setup_done", Kind::Bool, 0, nullptr, 0, 1}, {"long_name", Kind::String, 0, "", 1, 39}, {"short_name", Kind::String, 0, "", 1, 4}, {"region", Kind::String, 0, "EU868", 1, 8}, {"region_ok", Kind::Bool, 0, nullptr, 0, 1}, {"timezone", Kind::String, 0, "CET-1CEST,M3.5.0,M10.5.0/3", 1, 63}, {"brightness", Kind::Int, 60, nullptr, 10, 100}, {"dim_s", Kind::Int, 30, nullptr, 5, 600}, {"off_s", Kind::Int, 60, nullptr, 10, 3600}, {"sound", Kind::Bool, 1, nullptr, 0, 1}, {"probe_mac_raw", Kind::Bool, 1, nullptr, 0, 1}, {"wifi_on", Kind::Bool, 1, nullptr, 0, 1}, {"gnss_on", Kind::Bool, 1, nullptr, 0, 1}, {"coord_dms", Kind::Bool, 0, nullptr, 0, 1}, {"lora_preset", Kind::Int, 0, nullptr, 0, 6}, // LongFast first {"dns1", Kind::String, 0, "9.9.9.9", 7, 15}, // Quad9 {"dns2", Kind::String, 0, "1.1.1.1", 0, 15}, // Cloudflare {"dns_always", Kind::Bool, 0, nullptr, 0, 1}, {"ntp1", Kind::String, 0, "pool.ntp.org", 1, 63}, {"ntp2", Kind::String, 0, "time.cloudflare.com", 0, 63}, {"gnss_quiet", Kind::Bool, 0, nullptr, 0, 1}, // off: GNSS stays on, as decided in M2 (Q58) }; static_assert(sizeof(kDefinitions) / sizeof(kDefinitions[0]) == static_cast(Setting::Count), "every Setting needs a definition"); const char* const kKnownRegions[] = {"EU868"}; const Definition& def(Setting s) { return kDefinitions[static_cast(s)]; } } // namespace Settings::Settings(KeyValueStore& store, EventBus& bus) : store_(store), bus_(bus) {} const char* Settings::key(Setting s) { return def(s).key; } void Settings::load() { for (size_t i = 0; i < values_.size(); i++) { auto s = static_cast(i); const auto& d = def(s); auto& v = values_[i]; if (d.kind == Kind::String) { v.str = d.defaultString; std::string stored; if (store_.getString(d.key, stored) && validString(s, stored)) v.str = stored; } else { v.i = d.defaultInt; int32_t stored; if (store_.getInt(d.key, stored) && validInt(s, stored)) v.i = stored; } } } int32_t Settings::getInt(Setting s) const { return values_[static_cast(s)].i; } bool Settings::getBool(Setting s) const { return getInt(s) != 0; } const std::string& Settings::getString(Setting s) const { return values_[static_cast(s)].str; } bool Settings::validInt(Setting s, int32_t value) const { const auto& d = def(s); if (d.kind == Kind::String || value < d.min || value > d.max) return false; if (s == Setting::DimTimeoutS) return value < getInt(Setting::OffTimeoutS); if (s == Setting::OffTimeoutS) return value > getInt(Setting::DimTimeoutS); return true; } bool Settings::validString(Setting s, const std::string& value) const { const auto& d = def(s); if (d.kind != Kind::String) return false; if (value.size() < static_cast(d.min) || value.size() > static_cast(d.max)) return false; if (s == Setting::Region) { for (auto region : kKnownRegions) if (value == region) return true; return false; } uint32_t address; if (s == Setting::Dns1) return net::parseIpv4(value, address); if (s == Setting::Dns2) return value.empty() || net::parseIpv4(value, address); if (s == Setting::Ntp1) return net::validHost(value); if (s == Setting::Ntp2) return value.empty() || net::validHost(value); return true; } bool Settings::setInt(Setting s, int32_t value) { if (def(s).kind == Kind::Bool) return false; if (!validInt(s, value)) return false; if (getInt(s) == value) return true; values_[static_cast(s)].i = value; store_.putInt(key(s), value); changed(s); return true; } bool Settings::setBool(Setting s, bool value) { if (def(s).kind != Kind::Bool) return false; if (getBool(s) == value) return true; values_[static_cast(s)].i = value ? 1 : 0; store_.putInt(key(s), value ? 1 : 0); changed(s); return true; } bool Settings::setString(Setting s, const std::string& value) { if (!validString(s, value)) return false; if (getString(s) == value) return true; values_[static_cast(s)].str = value; store_.putString(key(s), value); changed(s); return true; } void Settings::changed(Setting s) { bus_.publish(Event::withText(EventType::SettingChanged, key(s), static_cast(s))); } } // namespace roro