Public Access
Add M0 services: settings, battery, clock, storage, power
lib/services holds the host-tested logic: - Settings: typed, validated, persisted, SettingChanged events, transmit gated on a confirmed Region - BatteryEstimator: LiPo curve, median smoothing against TX sags - ClockModel: source priority GNSS > NTP > mesh, relative ages, Europe/Brussels local time via POSIX TZ - StorageMonitor: warning once per boot at 80%, Logs stop at 90%, Captures stop with < 2 MiB left - PowerPolicy / PowerButton: dim/off timeouts, wake key swallowed only when the screen was off, G0 long press src/services wires them to the hardware (NVS, battery ADC, SD on the shared SPI bus with the LoRa CS held high, backlight, deep sleep), and main.cpp is a temporary diagnostics screen for the hardware checks. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EhqxQ49eCju4CzKYNjZzwT
This commit is contained in:
@@ -0,0 +1,118 @@
|
||||
#include "settings.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},
|
||||
};
|
||||
static_assert(sizeof(kDefinitions) / sizeof(kDefinitions[0]) == static_cast<size_t>(Setting::Count),
|
||||
"every Setting needs a definition");
|
||||
|
||||
const char* const kKnownRegions[] = {"EU868"};
|
||||
|
||||
const Definition& def(Setting s) { return kDefinitions[static_cast<size_t>(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<Setting>(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<size_t>(s)].i; }
|
||||
bool Settings::getBool(Setting s) const { return getInt(s) != 0; }
|
||||
const std::string& Settings::getString(Setting s) const { return values_[static_cast<size_t>(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<size_t>(d.min) || value.size() > static_cast<size_t>(d.max)) return false;
|
||||
if (s == Setting::Region) {
|
||||
for (auto region : kKnownRegions)
|
||||
if (value == region) return true;
|
||||
return false;
|
||||
}
|
||||
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<size_t>(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<size_t>(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<size_t>(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<int32_t>(s)));
|
||||
}
|
||||
|
||||
} // namespace roro
|
||||
Reference in New Issue
Block a user