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,222 @@
|
||||
#include "WiFiManager.hpp"
|
||||
#include <ArduinoJson.h>
|
||||
|
||||
namespace cyd {
|
||||
|
||||
namespace {
|
||||
constexpr const char* setupHtml = R"rawliteral(
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>CYD ThermoPro Setup</title>
|
||||
<style>
|
||||
body { font-family: system-ui, sans-serif; margin: 1rem; max-width: 500px; }
|
||||
label { display: block; margin-top: 1rem; font-weight: bold; }
|
||||
input { width: 100%; padding: 0.5rem; box-sizing: border-box; }
|
||||
button { margin-top: 1.5rem; padding: 0.6rem 1.2rem; font-size: 1rem; }
|
||||
.note { color: #555; font-size: 0.9rem; margin-top: 0.25rem; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>CYD ThermoPro Setup</h1>
|
||||
<p>Enter your WiFi credentials and ThermoPro MAC address, then save.</p>
|
||||
<form method="POST" action="/setup">
|
||||
<label>WiFi Network (SSID)</label>
|
||||
<input type="text" name="ssid" required>
|
||||
<label>WiFi Password</label>
|
||||
<input type="password" name="pass" required>
|
||||
<label>ThermoPro MAC</label>
|
||||
<input type="text" name="mac" placeholder="C9:48:1D:B1:E1:E7" required>
|
||||
<div class="note">Format: AA:BB:CC:DD:EE:FF. Use a BLE scanner if you don't know it.</div>
|
||||
<button type="submit">Save & Reboot</button>
|
||||
</form>
|
||||
</body>
|
||||
</html>
|
||||
)rawliteral";
|
||||
} // namespace
|
||||
|
||||
WiFiManager::WiFiManager(SettingsManager& settings) : settings_(settings) {
|
||||
}
|
||||
|
||||
void WiFiManager::begin() {
|
||||
WiFi.disconnect(true);
|
||||
delay(100);
|
||||
|
||||
if (settings_.hasWifiCredentials()) {
|
||||
Serial.printf("WiFiManager: loaded SSID '%s', connecting...\n", settings_.getWifiSsid().c_str());
|
||||
startStation_();
|
||||
} else {
|
||||
Serial.println("WiFiManager: no credentials stored, starting setup AP");
|
||||
startAccessPoint_();
|
||||
}
|
||||
}
|
||||
|
||||
void WiFiManager::update() {
|
||||
switch (state_) {
|
||||
case State::Uninitialized:
|
||||
break;
|
||||
|
||||
case State::Connecting:
|
||||
if (WiFi.status() == WL_CONNECTED) {
|
||||
Serial.printf("WiFiManager: connected, IP %s\n", WiFi.localIP().toString().c_str());
|
||||
state_ = State::StationConnected;
|
||||
if (modeCallback_) {
|
||||
modeCallback_(false);
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (millis() - connectStartMillis_ > kConnectTimeoutMs) {
|
||||
Serial.println("WiFiManager: station connection timeout");
|
||||
WiFi.disconnect(true);
|
||||
state_ = State::StationFailed;
|
||||
startAccessPoint_();
|
||||
}
|
||||
break;
|
||||
|
||||
case State::StationConnected:
|
||||
if (WiFi.status() != WL_CONNECTED) {
|
||||
Serial.println("WiFiManager: station lost connection, retrying");
|
||||
state_ = State::Connecting;
|
||||
connectStartMillis_ = millis();
|
||||
WiFi.reconnect();
|
||||
}
|
||||
break;
|
||||
|
||||
case State::AccessPoint:
|
||||
case State::FallbackToAp:
|
||||
if (portalRunning_) {
|
||||
dnsServer_.processNextRequest();
|
||||
portalServer_.handleClient();
|
||||
}
|
||||
break;
|
||||
|
||||
case State::StationFailed:
|
||||
// Should have transitioned to AP above.
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
String WiFiManager::getIpAddress() const {
|
||||
if (isApMode()) {
|
||||
return WiFi.softAPIP().toString();
|
||||
}
|
||||
if (state_ == State::StationConnected) {
|
||||
return WiFi.localIP().toString();
|
||||
}
|
||||
return "0.0.0.0";
|
||||
}
|
||||
|
||||
void WiFiManager::enterSetupMode() {
|
||||
if (state_ == State::StationConnected || state_ == State::Connecting) {
|
||||
WiFi.disconnect(true);
|
||||
}
|
||||
startAccessPoint_();
|
||||
}
|
||||
|
||||
void WiFiManager::configureAndConnect(const String& ssid, const String& pass) {
|
||||
settings_.setWifiSsid(ssid);
|
||||
settings_.setWifiPass(pass);
|
||||
settings_.save();
|
||||
stopAccessPoint_();
|
||||
startStation_();
|
||||
}
|
||||
|
||||
void WiFiManager::startStation_() {
|
||||
state_ = State::Connecting;
|
||||
connectStartMillis_ = millis();
|
||||
|
||||
WiFi.mode(WIFI_STA);
|
||||
WiFi.begin(settings_.getWifiSsid().c_str(), settings_.getWifiPass().c_str());
|
||||
|
||||
if (modeCallback_) {
|
||||
modeCallback_(false);
|
||||
}
|
||||
}
|
||||
|
||||
void WiFiManager::startAccessPoint_() {
|
||||
state_ = State::AccessPoint;
|
||||
|
||||
WiFi.mode(WIFI_AP);
|
||||
WiFi.softAP(kApSsid, kApPass);
|
||||
|
||||
IPAddress ip = WiFi.softAPIP();
|
||||
Serial.printf("WiFiManager: AP '%s' started at %s\n", kApSsid, ip.toString().c_str());
|
||||
|
||||
dnsServer_.start(53, "*", ip);
|
||||
|
||||
portalServer_.on("/", HTTP_GET, [this]() { handlePortalRoot_(); });
|
||||
portalServer_.on("/setup", HTTP_POST, [this]() { handlePortalSubmit_(); });
|
||||
portalServer_.onNotFound([this]() { handlePortalNotFound_(); });
|
||||
portalServer_.begin();
|
||||
portalRunning_ = true;
|
||||
|
||||
if (modeCallback_) {
|
||||
modeCallback_(true);
|
||||
}
|
||||
}
|
||||
|
||||
void WiFiManager::stopAccessPoint_() {
|
||||
portalRunning_ = false;
|
||||
portalServer_.close();
|
||||
dnsServer_.stop();
|
||||
WiFi.softAPdisconnect(true);
|
||||
}
|
||||
|
||||
void WiFiManager::handlePortalRoot_() {
|
||||
portalServer_.send(200, "text/html", setupHtml);
|
||||
}
|
||||
|
||||
void WiFiManager::handlePortalSubmit_() {
|
||||
String ssid = portalServer_.arg("ssid");
|
||||
String pass = portalServer_.arg("pass");
|
||||
String mac = portalServer_.arg("mac");
|
||||
|
||||
if (ssid.length() == 0 || mac.length() == 0) {
|
||||
portalServer_.send(400, "text/html", "<h1>Error</h1><p>SSID and MAC are required.</p><a href='/'>Go back</a>");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!SettingsManager::isValidMac(mac)) {
|
||||
portalServer_.send(422, "text/html", "<h1>Error</h1><p>Invalid MAC address format.</p><a href='/'>Go back</a>");
|
||||
return;
|
||||
}
|
||||
|
||||
settings_.setWifiSsid(ssid);
|
||||
settings_.setWifiPass(pass);
|
||||
settings_.setThermoproMac(mac);
|
||||
settings_.save();
|
||||
|
||||
String html = R"rawliteral(
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>Saved</title>
|
||||
<style>body { font-family: system-ui, sans-serif; margin: 1rem; }</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Settings saved</h1>
|
||||
<p>The CYD will reboot and connect to your WiFi network.</p>
|
||||
<p>If it fails to connect, it will reopen this setup network.</p>
|
||||
</body>
|
||||
</html>
|
||||
)rawliteral";
|
||||
portalServer_.send(200, "text/html", html);
|
||||
|
||||
delay(500);
|
||||
ESP.restart();
|
||||
}
|
||||
|
||||
void WiFiManager::handlePortalNotFound_() {
|
||||
redirectToPortal_(portalServer_);
|
||||
}
|
||||
|
||||
void WiFiManager::redirectToPortal_(WebServer& server) {
|
||||
server.sendHeader("Location", "http://" + String(kSetupDomain) + "/", true);
|
||||
server.send(302, "text/plain", "Redirecting to setup portal...");
|
||||
}
|
||||
|
||||
} // namespace cyd
|
||||
Reference in New Issue
Block a user