Add ThermoPro BLE support and dashboard display
- Implement ThermoproBLE class for handling BLE communication with ThermoPro BBQ thermometer. - Create ThermoproReading structure to hold thermometer data. - Develop Dashboard class for displaying thermometer readings and WiFi status on a TFT display. - Configure display settings and touch input handling. - Introduce SettingsManager for managing user settings stored in NVS. - Implement WiFiManager for handling WiFi connections and captive portal setup. - Add HTML interface for user to input WiFi credentials and ThermoPro MAC address. - Ensure proper state management for WiFi and BLE connections in the main application loop.
This commit is contained in:
@@ -0,0 +1,173 @@
|
||||
#include "SettingsManager.hpp"
|
||||
#include <ArduinoJson.h>
|
||||
#include <Preferences.h>
|
||||
|
||||
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;
|
||||
settings_.unit = doc["unit"] | "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
|
||||
Reference in New Issue
Block a user