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:
Keith Solomon
2026-07-19 19:33:18 -05:00
parent 801e16f7ff
commit 9a3a8f9368
18 changed files with 2212 additions and 0 deletions
+173
View File
@@ -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