Add PlatformIO project for the ESP32-2432S028R that: - Reads ThermoPro BLE BBQ thermometers via NimBLE-Arduino - Exposes /api/health, /api/latest, and /api/settings HTTP endpoints - Serves a captive-portal setup UI and touch dashboard - Persists WiFi/BLE/settings in NVS via Preferences Builds cleanly with pio run.
176 lines
4.6 KiB
C++
176 lines
4.6 KiB
C++
#include "SettingsManager.hpp"
|
|
#include <ArduinoJson.h>
|
|
#include <Preferences.h>
|
|
#include <vector>
|
|
|
|
namespace cyd {
|
|
|
|
SettingsManager::SettingsManager() = default;
|
|
|
|
SettingsManager::~SettingsManager() {
|
|
end();
|
|
}
|
|
|
|
bool SettingsManager::begin() {
|
|
if (begun_) {
|
|
return true;
|
|
}
|
|
begun_ = prefs_.begin(kNamespace, false);
|
|
if (begun_) {
|
|
load();
|
|
}
|
|
return begun_;
|
|
}
|
|
|
|
void SettingsManager::end() {
|
|
if (begun_) {
|
|
prefs_.end();
|
|
begun_ = false;
|
|
}
|
|
}
|
|
|
|
void SettingsManager::resetToDefaults() {
|
|
Settings defaults;
|
|
settings_ = defaults;
|
|
}
|
|
|
|
bool SettingsManager::load() {
|
|
if (!begun_) {
|
|
return false;
|
|
}
|
|
|
|
size_t len = prefs_.getBytesLength(kSettingsKey);
|
|
if (len == 0) {
|
|
Serial.println("Settings: no stored settings found, using defaults");
|
|
resetToDefaults();
|
|
return true;
|
|
}
|
|
|
|
std::vector<char> buffer;
|
|
buffer.resize(len);
|
|
size_t read = prefs_.getBytes(kSettingsKey, buffer.data(), len);
|
|
if (read != len) {
|
|
Serial.println("Settings: read size mismatch, resetting");
|
|
resetToDefaults();
|
|
return false;
|
|
}
|
|
|
|
JsonDocument doc;
|
|
DeserializationError err = deserializeJson(doc, buffer.data(), len);
|
|
if (err != DeserializationError::Ok) {
|
|
Serial.printf("Settings: JSON parse error: %s\n", err.c_str());
|
|
resetToDefaults();
|
|
return false;
|
|
}
|
|
|
|
uint16_t storedVersion = doc["version"] | 0;
|
|
if (storedVersion != kCurrentVersion) {
|
|
Serial.printf("Settings: migrating from version %d to %d\n", storedVersion, kCurrentVersion);
|
|
migrateFrom(storedVersion);
|
|
return true;
|
|
}
|
|
|
|
settings_.wifiSsid = doc["wifiSsid"] | "";
|
|
settings_.wifiPass = doc["wifiPass"] | "";
|
|
settings_.thermoproMac = doc["thermoproMac"] | "";
|
|
settings_.apiEndpoint = doc["apiEndpoint"] | "";
|
|
settings_.apiToken = doc["apiToken"] | "";
|
|
settings_.pollIntervalSec = doc["pollIntervalSec"] | 15;
|
|
String unit = doc["unit"] | "F";
|
|
settings_.unit = unit.length() > 0 ? unit.charAt(0) : 'F';
|
|
settings_.deviceId = doc["deviceId"] | "cyd-smoker";
|
|
|
|
JsonArray names = doc["probeNames"].as<JsonArray>();
|
|
for (size_t i = 0; i < settings_.probeNames.size(); ++i) {
|
|
if (i < names.size()) {
|
|
settings_.probeNames[i] = names[i].as<String>();
|
|
} else {
|
|
settings_.probeNames[i] = "Probe " + String(i + 1);
|
|
}
|
|
}
|
|
|
|
Serial.println("Settings: loaded from NVS");
|
|
return true;
|
|
}
|
|
|
|
bool SettingsManager::save() {
|
|
if (!begun_) {
|
|
return false;
|
|
}
|
|
|
|
JsonDocument doc;
|
|
doc["version"] = kCurrentVersion;
|
|
doc["wifiSsid"] = settings_.wifiSsid;
|
|
doc["wifiPass"] = settings_.wifiPass;
|
|
doc["thermoproMac"] = settings_.thermoproMac;
|
|
doc["apiEndpoint"] = settings_.apiEndpoint;
|
|
doc["apiToken"] = settings_.apiToken;
|
|
doc["pollIntervalSec"] = settings_.pollIntervalSec;
|
|
doc["unit"] = String(settings_.unit);
|
|
doc["deviceId"] = settings_.deviceId;
|
|
|
|
JsonArray names = doc["probeNames"].to<JsonArray>();
|
|
for (const auto& name : settings_.probeNames) {
|
|
names.add(name);
|
|
}
|
|
|
|
size_t size = measureJson(doc);
|
|
std::vector<char> buffer;
|
|
buffer.resize(size);
|
|
serializeJson(doc, buffer.data(), size);
|
|
|
|
prefs_.putBytes(kSettingsKey, buffer.data(), size);
|
|
Serial.println("Settings: saved to NVS");
|
|
return true;
|
|
}
|
|
|
|
String SettingsManager::getProbeName(size_t index) const {
|
|
if (index < settings_.probeNames.size()) {
|
|
return settings_.probeNames[index];
|
|
}
|
|
return "Probe " + String(index + 1);
|
|
}
|
|
|
|
void SettingsManager::setProbeName(size_t index, const String& value) {
|
|
if (index < settings_.probeNames.size()) {
|
|
settings_.probeNames[index] = value.length() ? value : ("Probe " + String(index + 1));
|
|
}
|
|
}
|
|
|
|
bool SettingsManager::hasThermoproMac() const {
|
|
return isValidMac(settings_.thermoproMac);
|
|
}
|
|
|
|
bool SettingsManager::hasWifiCredentials() const {
|
|
return settings_.wifiSsid.length() > 0;
|
|
}
|
|
|
|
bool SettingsManager::isValidMac(const String& mac) {
|
|
if (mac.length() != 17) {
|
|
return false;
|
|
}
|
|
for (int i = 0; i < mac.length(); ++i) {
|
|
char c = mac.charAt(i);
|
|
if ((i + 1) % 3 == 0) {
|
|
if (c != ':') {
|
|
return false;
|
|
}
|
|
} else {
|
|
if (!isxdigit(c)) {
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
void SettingsManager::migrateFrom(uint16_t oldVersion) {
|
|
// For now, any version mismatch simply resets to defaults. As the schema
|
|
// evolves we can add per-version migrations here.
|
|
(void)oldVersion;
|
|
resetToDefaults();
|
|
save();
|
|
}
|
|
|
|
} // namespace cyd
|