Compare commits
2
Commits
9a3a8f9368
...
f03cebf322
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f03cebf322 | ||
|
|
8e007de5a1 |
+11
-2
@@ -76,7 +76,7 @@ Runtime settings (endpoint, token, probe names, units, polling interval) can be
|
|||||||
|
|
||||||
## Building and flashing
|
## Building and flashing
|
||||||
|
|
||||||
Requires [PlatformIO](https://platformio.org/) installed locally (not available in this cloud environment).
|
Requires [PlatformIO](https://platformio.org/).
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
cd cyd-bridge
|
cd cyd-bridge
|
||||||
@@ -102,9 +102,18 @@ Before relying on the dashboard, verify the pinout matches your exact board revi
|
|||||||
2. Confirm `TFT_CS`, `TFT_DC`, `TFT_RST`, `TFT_BL`, `TOUCH_CS`, and the SPI pins match the silkscreen or schematic of your ESP32-2432S028R.
|
2. Confirm `TFT_CS`, `TFT_DC`, `TFT_RST`, `TFT_BL`, `TOUCH_CS`, and the SPI pins match the silkscreen or schematic of your ESP32-2432S028R.
|
||||||
3. If the screen stays white or touch is inverted, adjust `cfg.offset_rotation` in `DisplayConfig.hpp` or `setRotation()` in `Dashboard::begin()`.
|
3. If the screen stays white or touch is inverted, adjust `cfg.offset_rotation` in `DisplayConfig.hpp` or `setRotation()` in `Dashboard::begin()`.
|
||||||
|
|
||||||
|
## Memory usage
|
||||||
|
|
||||||
|
With the default partition table the firmware uses approximately:
|
||||||
|
|
||||||
|
- **RAM:** ~18 % (~58 kB of 320 kB)
|
||||||
|
- **Flash:** ~92 % (~1.2 MB of 1.3 MB)
|
||||||
|
|
||||||
|
There is enough headroom to run, but large future features (e.g. local graphing, OTA) may require switching to a larger app partition or dropping unused NimBLE options.
|
||||||
|
|
||||||
## Development notes
|
## Development notes
|
||||||
|
|
||||||
- LovyanGFX driver/pin configuration lives in `src/display/DisplayConfig.hpp` and is tuned for the ESP32-2432S028R variant.
|
- LovyanGFX driver/pin configuration lives in `src/display/DisplayConfig.hpp` and is tuned for the ESP32-2432S028R variant.
|
||||||
- BLE uses `NimBLE-Arduino` to leave enough RAM for the web server and UI.
|
- BLE uses `NimBLE-Arduino` to leave enough RAM for the web server and UI.
|
||||||
- All user settings are stored in ESP32 NVS via `Preferences`.
|
- All user settings are stored in ESP32 NVS via `Preferences`.
|
||||||
- The firmware has not yet been compiled in this environment due to missing PlatformIO tooling; it should be built locally on your development machine before flashing.
|
- The firmware compiles successfully in PlatformIO; the remaining work is on-device testing and pinout verification.
|
||||||
|
|||||||
@@ -43,6 +43,7 @@ void ApiServer::begin(uint16_t port) {
|
|||||||
server_.on("/api/latest", HTTP_GET, [this]() { handleLatest_(); });
|
server_.on("/api/latest", HTTP_GET, [this]() { handleLatest_(); });
|
||||||
server_.on("/api/settings", HTTP_GET, [this]() { handleGetSettings_(); });
|
server_.on("/api/settings", HTTP_GET, [this]() { handleGetSettings_(); });
|
||||||
server_.on("/api/settings", HTTP_POST, [this]() { handlePostSettings_(); });
|
server_.on("/api/settings", HTTP_POST, [this]() { handlePostSettings_(); });
|
||||||
|
server_.on("/setup", HTTP_POST, [this]() { handleSetupForm_(); });
|
||||||
server_.onNotFound([this]() { handleNotFound_(); });
|
server_.onNotFound([this]() { handleNotFound_(); });
|
||||||
|
|
||||||
server_.begin(port_);
|
server_.begin(port_);
|
||||||
@@ -116,8 +117,12 @@ void ApiServer::fillLatestJson_(JsonObject& obj) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Timestamps are relative for the device; we add ISO-like strings where possible.
|
// Timestamps are relative for the device; we add device millis where possible.
|
||||||
obj["readingTime"] = reading.lastUpdateMillis > 0 ? String(reading.lastUpdateMillis) : nullptr;
|
if (reading.lastUpdateMillis > 0) {
|
||||||
|
obj["readingTime"] = String(reading.lastUpdateMillis);
|
||||||
|
} else {
|
||||||
|
obj["readingTime"] = nullptr;
|
||||||
|
}
|
||||||
obj["bridgeTime"] = String(millis());
|
obj["bridgeTime"] = String(millis());
|
||||||
obj["source"] = "cyd-bridge";
|
obj["source"] = "cyd-bridge";
|
||||||
}
|
}
|
||||||
@@ -220,11 +225,54 @@ void ApiServer::handleNotFound_() {
|
|||||||
server_.send(404, "application/json", "{\"ok\":false,\"error\":\"Not found\"}");
|
server_.send(404, "application/json", "{\"ok\":false,\"error\":\"Not found\"}");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ApiServer::handleSetupForm_() {
|
||||||
|
String ssid = server_.arg("ssid");
|
||||||
|
String pass = server_.arg("pass");
|
||||||
|
String mac = server_.arg("mac");
|
||||||
|
|
||||||
|
if (ssid.length() == 0 || mac.length() == 0) {
|
||||||
|
server_.send(400, "text/html", "<h1>Error</h1><p>SSID and MAC are required.</p><a href='/'>Go back</a>");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!SettingsManager::isValidMac(mac)) {
|
||||||
|
server_.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";
|
||||||
|
server_.send(200, "text/html", html);
|
||||||
|
|
||||||
|
if (rebootCallback_) {
|
||||||
|
rebootCallback_();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
String ApiServer::buildSetupPage_() {
|
String ApiServer::buildSetupPage_() {
|
||||||
String page = htmlHead;
|
String page = htmlHead;
|
||||||
page += "<h1>CYD ThermoPro Setup</h1>\n";
|
page += "<h1>CYD ThermoPro Setup</h1>\n";
|
||||||
page += "<p>Connect this CYD to your WiFi network and enter your ThermoPro details.</p>\n";
|
page += "<p>Connect this CYD to your WiFi network and enter your ThermoPro details.</p>\n";
|
||||||
page += "<form method=\"POST\" action=\"/api/settings\" onsubmit=\"return submitForm()\">\n";
|
page += "<form method=\"POST\" action=\"/setup\" onsubmit=\"return submitForm()\">\n";
|
||||||
page += R"rawliteral(
|
page += R"rawliteral(
|
||||||
<label>WiFi SSID</label>
|
<label>WiFi SSID</label>
|
||||||
<input type="text" name="wifiSsid" id="wifiSsid" required>
|
<input type="text" name="wifiSsid" id="wifiSsid" required>
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
#include "settings/SettingsManager.hpp"
|
#include "settings/SettingsManager.hpp"
|
||||||
#include "ble/ThermoproBLE.hpp"
|
#include "ble/ThermoproBLE.hpp"
|
||||||
|
#include <ArduinoJson.h>
|
||||||
#include <WebServer.h>
|
#include <WebServer.h>
|
||||||
|
|
||||||
namespace cyd {
|
namespace cyd {
|
||||||
@@ -38,6 +39,7 @@ private:
|
|||||||
void handleLatest_();
|
void handleLatest_();
|
||||||
void handleGetSettings_();
|
void handleGetSettings_();
|
||||||
void handlePostSettings_();
|
void handlePostSettings_();
|
||||||
|
void handleSetupForm_();
|
||||||
void handleNotFound_();
|
void handleNotFound_();
|
||||||
|
|
||||||
String buildSetupPage_();
|
String buildSetupPage_();
|
||||||
|
|||||||
@@ -5,6 +5,12 @@ namespace cyd {
|
|||||||
|
|
||||||
ThermoproBLE* ThermoproBLE::instance_ = nullptr;
|
ThermoproBLE* ThermoproBLE::instance_ = nullptr;
|
||||||
|
|
||||||
|
const NimBLEUUID ThermoproBLE::kServiceUuid = NimBLEUUID("1086FFF0-3343-4817-8BB2-B32206336CE8");
|
||||||
|
const NimBLEUUID ThermoproBLE::kNotifyUuid = NimBLEUUID("1086FFF2-3343-4817-8BB2-B32206336CE8");
|
||||||
|
const NimBLEUUID ThermoproBLE::kWriteUuid = NimBLEUUID("1086FFF1-3343-4817-8BB2-B32206336CE8");
|
||||||
|
|
||||||
|
constexpr uint8_t ThermoproBLE::kHandshake[];
|
||||||
|
|
||||||
ThermoproBLE::ThermoproBLE() {
|
ThermoproBLE::ThermoproBLE() {
|
||||||
instance_ = this;
|
instance_ = this;
|
||||||
}
|
}
|
||||||
@@ -13,17 +19,30 @@ void ThermoproBLE::setAddress(const String& mac) {
|
|||||||
address_ = mac;
|
address_ = mac;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ThermoproBLE::init(const String& deviceName) {
|
const char* ThermoproBLE::getStateName() const {
|
||||||
if (address_.length() == 0) {
|
switch (state_) {
|
||||||
Serial.println("ThermoproBLE: no MAC address configured");
|
case State::Idle: return "idle";
|
||||||
return false;
|
case State::Scanning: return "scanning";
|
||||||
|
case State::Connecting: return "connecting";
|
||||||
|
case State::Discovering: return "discovering";
|
||||||
|
case State::EnablingNotify: return "enabling-notify";
|
||||||
|
case State::SendingHandshake: return "sending-handshake";
|
||||||
|
case State::Running: return "running";
|
||||||
|
case State::Reconnecting: return "reconnecting";
|
||||||
|
}
|
||||||
|
return "unknown";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool ThermoproBLE::init(const String& deviceName) {
|
||||||
NimBLEDevice::init(std::string(deviceName.c_str()));
|
NimBLEDevice::init(std::string(deviceName.c_str()));
|
||||||
// We do not need a security/bonded pairing for the ThermoPro protocol.
|
// We do not need a security/bonded pairing for the ThermoPro protocol.
|
||||||
NimBLEDevice::setSecurityAuth(false, false, false);
|
NimBLEDevice::setSecurityAuth(false, false, false);
|
||||||
|
|
||||||
Serial.printf("ThermoproBLE: BLE initialized as '%s', target %s\n", deviceName.c_str(), address_.c_str());
|
if (address_.length() > 0) {
|
||||||
|
Serial.printf("ThermoproBLE: BLE initialized as '%s', target MAC %s\n", deviceName.c_str(), address_.c_str());
|
||||||
|
} else {
|
||||||
|
Serial.printf("ThermoproBLE: BLE initialized as '%s', will scan for ThermoPro service UUID\n", deviceName.c_str());
|
||||||
|
}
|
||||||
state_ = State::Idle;
|
state_ = State::Idle;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -51,16 +70,56 @@ void ThermoproBLE::update() {
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case State::Scanning:
|
case State::Scanning:
|
||||||
|
// If we found a candidate during the scan, stop scanning and connect.
|
||||||
|
if (pendingConnectFound_) {
|
||||||
|
if (millis() - pendingConnectSeenMillis_ >= 200) {
|
||||||
|
NimBLEDevice::getScan()->stop();
|
||||||
|
state_ = State::Connecting;
|
||||||
|
stateStartMillis_ = millis();
|
||||||
|
connectToAddress_(pendingConnectAddress_);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (millis() - stateStartMillis_ > 25000) {
|
||||||
|
Serial.println("ThermoproBLE: scan timeout, forcing reconnect");
|
||||||
|
NimBLEDevice::getScan()->stop();
|
||||||
|
setConnected_(false);
|
||||||
|
startReconnectDelay_();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
case State::Connecting:
|
case State::Connecting:
|
||||||
|
if (client_ && client_->isConnected()) {
|
||||||
|
Serial.println("ThermoproBLE: connected to server");
|
||||||
|
if (!setupConnection_(client_)) {
|
||||||
|
Serial.println("ThermoproBLE: setup failed");
|
||||||
|
cleanupClient_();
|
||||||
|
setConnected_(false);
|
||||||
|
startReconnectDelay_();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
state_ = State::Running;
|
||||||
|
stateStartMillis_ = millis();
|
||||||
|
lastActivityMillis_ = millis();
|
||||||
|
reconnectFailures_ = 0;
|
||||||
|
reconnectDelayMs_ = 5000;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (millis() - stateStartMillis_ > 25000) {
|
||||||
|
Serial.println("ThermoproBLE: connect timeout");
|
||||||
|
cleanupClient_();
|
||||||
|
setConnected_(false);
|
||||||
|
startReconnectDelay_();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
case State::Discovering:
|
case State::Discovering:
|
||||||
case State::EnablingNotify:
|
case State::EnablingNotify:
|
||||||
case State::SendingHandshake:
|
case State::SendingHandshake:
|
||||||
// Time out stuck states and restart the reconnect cycle.
|
|
||||||
if (millis() - stateStartMillis_ > 15000) {
|
if (millis() - stateStartMillis_ > 15000) {
|
||||||
Serial.printf("ThermoproBLE: state timeout in state %d, forcing reconnect\n", static_cast<int>(state_));
|
Serial.printf("ThermoproBLE: setup state timeout in state %d\n", static_cast<int>(state_));
|
||||||
if (client_ && client_->isConnected()) {
|
cleanupClient_();
|
||||||
client_->disconnect();
|
|
||||||
}
|
|
||||||
setConnected_(false);
|
setConnected_(false);
|
||||||
startReconnectDelay_();
|
startReconnectDelay_();
|
||||||
}
|
}
|
||||||
@@ -69,14 +128,14 @@ void ThermoproBLE::update() {
|
|||||||
case State::Running: {
|
case State::Running: {
|
||||||
if (!client_ || !client_->isConnected()) {
|
if (!client_ || !client_->isConnected()) {
|
||||||
Serial.println("ThermoproBLE: connection lost, reconnecting");
|
Serial.println("ThermoproBLE: connection lost, reconnecting");
|
||||||
|
cleanupClient_();
|
||||||
setConnected_(false);
|
setConnected_(false);
|
||||||
startReconnectDelay_();
|
startReconnectDelay_();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
// If we haven't seen any notification in 30 seconds, reconnect.
|
|
||||||
if (millis() - lastActivityMillis_ > 30000) {
|
if (millis() - lastActivityMillis_ > 30000) {
|
||||||
Serial.println("ThermoproBLE: no notifications for 30s, reconnecting");
|
Serial.println("ThermoproBLE: no notifications for 30s, reconnecting");
|
||||||
client_->disconnect();
|
cleanupClient_();
|
||||||
setConnected_(false);
|
setConnected_(false);
|
||||||
startReconnectDelay_();
|
startReconnectDelay_();
|
||||||
}
|
}
|
||||||
@@ -93,9 +152,7 @@ void ThermoproBLE::update() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void ThermoproBLE::disconnect() {
|
void ThermoproBLE::disconnect() {
|
||||||
if (client_ && client_->isConnected()) {
|
cleanupClient_();
|
||||||
client_->disconnect();
|
|
||||||
}
|
|
||||||
NimBLEDevice::deinit(true);
|
NimBLEDevice::deinit(true);
|
||||||
setConnected_(false);
|
setConnected_(false);
|
||||||
state_ = State::Idle;
|
state_ = State::Idle;
|
||||||
@@ -105,22 +162,34 @@ void ThermoproBLE::ScanCallbacks::onResult(NimBLEAdvertisedDevice* device) {
|
|||||||
if (ThermoproBLE::instance_ == nullptr) {
|
if (ThermoproBLE::instance_ == nullptr) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
auto* self = ThermoproBLE::instance_;
|
||||||
String addr(device->getAddress().toString().c_str());
|
String addr(device->getAddress().toString().c_str());
|
||||||
String name(device->getName().c_str());
|
String name(device->getName().c_str());
|
||||||
|
|
||||||
bool matchesAddress = addr.equalsIgnoreCase(ThermoproBLE::instance_->address_);
|
bool matchesAddress = !self->address_.isEmpty()
|
||||||
|
&& addr.equalsIgnoreCase(self->address_);
|
||||||
bool matchesName = isThermoproDevice_(name);
|
bool matchesName = isThermoproDevice_(name);
|
||||||
|
bool matchesService = device->isAdvertisingService(ThermoproBLE::kServiceUuid);
|
||||||
|
|
||||||
if (matchesAddress) {
|
// Verbose scan logging during bringup. Print every device address + name.
|
||||||
Serial.printf("ThermoproBLE: found target device %s (%s)\n", name.c_str(), addr.c_str());
|
Serial.printf("[ble-scan] %s rssi=%d name='%s' service_match=%s\n",
|
||||||
NimBLEDevice::getScan()->stop();
|
addr.c_str(), device->getRSSI(), name.c_str(),
|
||||||
connectToServer_(device, ThermoproBLE::instance_);
|
matchesService ? "yes" : "no");
|
||||||
} else if (matchesName) {
|
|
||||||
Serial.printf("ThermoproBLE: discovered ThermoPro %s (%s)\n", name.c_str(), addr.c_str());
|
if (matchesAddress || matchesName || matchesService) {
|
||||||
|
Serial.printf("ThermoproBLE: discovered candidate %s (%s), addrType=%d\n",
|
||||||
|
name.c_str(), addr.c_str(), device->getAddress().getType());
|
||||||
|
// Record the address for a connect initiated from update() once the
|
||||||
|
// scan has been stopped cleanly. Don't connect from inside the callback.
|
||||||
|
self->pendingConnectAddress_ = device->getAddress();
|
||||||
|
self->pendingConnectFound_ = true;
|
||||||
|
self->pendingConnectSeenMillis_ = millis();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ThermoproBLE::scanAndConnect_() {
|
bool ThermoproBLE::scanAndConnect_() {
|
||||||
|
pendingConnectFound_ = false;
|
||||||
|
|
||||||
NimBLEScan* scanner = NimBLEDevice::getScan();
|
NimBLEScan* scanner = NimBLEDevice::getScan();
|
||||||
if (!callbacksRegistered_) {
|
if (!callbacksRegistered_) {
|
||||||
scanner->setAdvertisedDeviceCallbacks(&scanCallbacks_, false);
|
scanner->setAdvertisedDeviceCallbacks(&scanCallbacks_, false);
|
||||||
@@ -133,46 +202,48 @@ bool ThermoproBLE::scanAndConnect_() {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ThermoproBLE::connectToServer_(NimBLEAdvertisedDevice* device, void* userData) {
|
bool ThermoproBLE::connectToAddress_(const NimBLEAddress& address) {
|
||||||
auto* self = static_cast<ThermoproBLE*>(userData);
|
state_ = State::Connecting;
|
||||||
if (!self) {
|
stateStartMillis_ = millis();
|
||||||
|
|
||||||
|
bool addrTypeRandom = (address.getType() == BLE_ADDR_RANDOM);
|
||||||
|
Serial.printf("ThermoproBLE: connecting to %s, addrType=%s\n",
|
||||||
|
address.toString().c_str(), addrTypeRandom ? "random" : "public");
|
||||||
|
|
||||||
|
// The scan must already be stopped before this is called.
|
||||||
|
delay(300);
|
||||||
|
|
||||||
|
client_ = NimBLEDevice::createClient();
|
||||||
|
// Conservative connection parameters.
|
||||||
|
client_->setConnectionParams(12, 12, 0, 160);
|
||||||
|
client_->setConnectTimeout(20);
|
||||||
|
|
||||||
|
bool ok = client_->connect(address, false);
|
||||||
|
if (!ok) {
|
||||||
|
Serial.println("ThermoproBLE: connect call rejected immediately");
|
||||||
|
cleanupClient_();
|
||||||
|
setConnected_(false);
|
||||||
|
startReconnectDelay_();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
self->state_ = State::Connecting;
|
Serial.println("ThermoproBLE: connect call accepted");
|
||||||
self->stateStartMillis_ = millis();
|
|
||||||
|
|
||||||
self->client_ = NimBLEDevice::createClient();
|
|
||||||
self->client_->setConnectionParams(12, 12, 0, 51);
|
|
||||||
self->client_->setConnectTimeout(10);
|
|
||||||
|
|
||||||
if (!self->client_->connect(device)) {
|
|
||||||
Serial.println("ThermoproBLE: connect failed");
|
|
||||||
NimBLEDevice::deleteClient(self->client_);
|
|
||||||
self->client_ = nullptr;
|
|
||||||
self->setConnected_(false);
|
|
||||||
self->startReconnectDelay_();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
Serial.println("ThermoproBLE: connected to server");
|
|
||||||
if (!self->setupConnection_(self->client_)) {
|
|
||||||
Serial.println("ThermoproBLE: setup failed");
|
|
||||||
NimBLEDevice::deleteClient(self->client_);
|
|
||||||
self->client_ = nullptr;
|
|
||||||
self->setConnected_(false);
|
|
||||||
self->startReconnectDelay_();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
self->state_ = State::Running;
|
|
||||||
self->stateStartMillis_ = millis();
|
|
||||||
self->lastActivityMillis_ = millis();
|
|
||||||
self->reconnectFailures_ = 0;
|
|
||||||
self->reconnectDelayMs_ = 5000;
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ThermoproBLE::cleanupClient_() {
|
||||||
|
if (client_) {
|
||||||
|
if (client_->isConnected()) {
|
||||||
|
client_->disconnect();
|
||||||
|
delay(100);
|
||||||
|
}
|
||||||
|
NimBLEDevice::deleteClient(client_);
|
||||||
|
client_ = nullptr;
|
||||||
|
}
|
||||||
|
notifyChar_ = nullptr;
|
||||||
|
writeChar_ = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
bool ThermoproBLE::setupConnection_(NimBLEClient* client) {
|
bool ThermoproBLE::setupConnection_(NimBLEClient* client) {
|
||||||
state_ = State::Discovering;
|
state_ = State::Discovering;
|
||||||
stateStartMillis_ = millis();
|
stateStartMillis_ = millis();
|
||||||
@@ -203,7 +274,7 @@ bool ThermoproBLE::setupConnection_(NimBLEClient* client) {
|
|||||||
state_ = State::SendingHandshake;
|
state_ = State::SendingHandshake;
|
||||||
stateStartMillis_ = millis();
|
stateStartMillis_ = millis();
|
||||||
|
|
||||||
if (!writeChar_->write(const_cast<uint8_t*>(kHandshake), sizeof(kHandshake), true)) {
|
if (!writeChar_->writeValue(const_cast<uint8_t*>(kHandshake), sizeof(kHandshake), true)) {
|
||||||
Serial.println("ThermoproBLE: handshake write failed");
|
Serial.println("ThermoproBLE: handshake write failed");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -34,14 +34,17 @@ public:
|
|||||||
// same core/loop in Arduino by default.
|
// same core/loop in Arduino by default.
|
||||||
ThermoproReading getReading() const { return reading_; }
|
ThermoproReading getReading() const { return reading_; }
|
||||||
|
|
||||||
|
// Human-readable current connection state name for diagnostics.
|
||||||
|
const char* getStateName() const;
|
||||||
|
|
||||||
// Register a callback invoked whenever the reading state changes.
|
// Register a callback invoked whenever the reading state changes.
|
||||||
void onStateChange(StateChangeCallback cb) { stateCallback_ = cb; }
|
void onStateChange(StateChangeCallback cb) { stateCallback_ = cb; }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
// BLE UUIDs (ThermoPro custom service)
|
// BLE UUIDs (ThermoPro custom service)
|
||||||
static inline const NimBLEUUID kServiceUuid = NimBLEUUID("1086FFF0-3343-4817-8BB2-B32206336CE8");
|
static const NimBLEUUID kServiceUuid;
|
||||||
static inline const NimBLEUUID kNotifyUuid = NimBLEUUID("1086FFF2-3343-4817-8BB2-B32206336CE8");
|
static const NimBLEUUID kNotifyUuid;
|
||||||
static inline const NimBLEUUID kWriteUuid = NimBLEUUID("1086FFF1-3343-4817-8BB2-B32206336CE8");
|
static const NimBLEUUID kWriteUuid;
|
||||||
|
|
||||||
// Static handshake bytes captured from the Android app.
|
// Static handshake bytes captured from the Android app.
|
||||||
static constexpr uint8_t kHandshake[] = { 0x01, 0x09, 0x8a, 0x7a, 0x13, 0xb7, 0x3e, 0xd6, 0x8b, 0x67, 0xc2, 0xa0 };
|
static constexpr uint8_t kHandshake[] = { 0x01, 0x09, 0x8a, 0x7a, 0x13, 0xb7, 0x3e, 0xd6, 0x8b, 0x67, 0xc2, 0xa0 };
|
||||||
@@ -72,9 +75,16 @@ private:
|
|||||||
unsigned long reconnectDelayMs_ = 5000;
|
unsigned long reconnectDelayMs_ = 5000;
|
||||||
bool callbacksRegistered_ = false;
|
bool callbacksRegistered_ = false;
|
||||||
|
|
||||||
|
// Address discovered during a scan that we want to connect to.
|
||||||
|
// The callback records it; update() initiates the connect after stopping the scan.
|
||||||
|
NimBLEAddress pendingConnectAddress_;
|
||||||
|
bool pendingConnectFound_ = false;
|
||||||
|
unsigned long pendingConnectSeenMillis_ = 0;
|
||||||
|
|
||||||
bool scanAndConnect_();
|
bool scanAndConnect_();
|
||||||
static bool connectToServer_(NimBLEAdvertisedDevice* device, void* userData);
|
bool connectToAddress_(const NimBLEAddress& address);
|
||||||
bool setupConnection_(NimBLEClient* client);
|
bool setupConnection_(NimBLEClient* client);
|
||||||
|
void cleanupClient_();
|
||||||
void setConnected_(bool connected);
|
void setConnected_(bool connected);
|
||||||
void startReconnectDelay_();
|
void startReconnectDelay_();
|
||||||
|
|
||||||
|
|||||||
@@ -9,8 +9,18 @@ Dashboard::Dashboard(CYD_Display& display, SettingsManager& settings, ThermoproB
|
|||||||
}
|
}
|
||||||
|
|
||||||
void Dashboard::begin() {
|
void Dashboard::begin() {
|
||||||
display_.init();
|
Serial.println("Dashboard: initializing display with LGFX_AUTODETECT...");
|
||||||
display_.initBacklight();
|
|
||||||
|
if (!display_.init()) {
|
||||||
|
Serial.println("ERROR: display init failed");
|
||||||
|
} else {
|
||||||
|
Serial.println("Dashboard: display init OK");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Backlight on GPIO 21, active HIGH.
|
||||||
|
pinMode(GPIO_NUM_21, OUTPUT);
|
||||||
|
digitalWrite(GPIO_NUM_21, HIGH);
|
||||||
|
|
||||||
display_.setRotation(0); // Portrait 240x320
|
display_.setRotation(0); // Portrait 240x320
|
||||||
display_.fillScreen(TFT_BLACK);
|
display_.fillScreen(TFT_BLACK);
|
||||||
display_.setTextDatum(middle_center);
|
display_.setTextDatum(middle_center);
|
||||||
|
|||||||
@@ -1,90 +1,23 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
// LovyanGFX configuration for the ESP32-2432S028R "Cheap Yellow Display".
|
// LovyanGFX configuration for the ESP32-2432S028R "Cheap Yellow Display".
|
||||||
// This board uses:
|
|
||||||
// - ESP32-WROOM-32
|
|
||||||
// - 320x240 TFT driven by ILI9341
|
|
||||||
// - Resistive touch panel on XPT2046 (shared SPI bus with display)
|
|
||||||
//
|
//
|
||||||
// Pinout (ESP32-2432S028R variant):
|
// We use LovyanGFX's built-in autodetection for the CYD board. The autodetect
|
||||||
// SDO/MISO GPIO 19
|
// table knows the correct SPI buses, pins, panel driver, and touch controller
|
||||||
// LED GPIO 21 (backlight, active high)
|
// for the common ILI9341 and ST7789 variants.
|
||||||
// SCK GPIO 18
|
//
|
||||||
// SDI/MOSI GPIO 23
|
// Reference:
|
||||||
// DC GPIO 2
|
// https://github.com/embedded-kiddie/Arduino-CYD-2432S028R
|
||||||
// RESET GPIO 4
|
// https://github.com/lovyan03/LovyanGFX/issues/693
|
||||||
// CS GPIO 15
|
|
||||||
// TOUCH CS GPIO 5
|
|
||||||
// TOUCH IRQ GPIO - (not used by default)
|
|
||||||
|
|
||||||
#define LGFX_USE_V1
|
#define LGFX_USE_V1
|
||||||
|
#define LGFX_AUTODETECT
|
||||||
#include <LovyanGFX.hpp>
|
#include <LovyanGFX.hpp>
|
||||||
|
#include <LGFX_AUTODETECT.hpp>
|
||||||
|
|
||||||
namespace cyd {
|
namespace cyd {
|
||||||
|
|
||||||
class CYD_Display : public lgfx::LGFX_Device {
|
// With LGFX_AUTODETECT we can simply use the stock LGFX class.
|
||||||
public:
|
using CYD_Display = lgfx::LGFX;
|
||||||
CYD_Display() {
|
|
||||||
// Panel configuration
|
|
||||||
auto cfg = _panel_instance.config();
|
|
||||||
cfg.pin_cs = GPIO_NUM_15;
|
|
||||||
cfg.pin_dc = GPIO_NUM_2;
|
|
||||||
cfg.pin_rst = GPIO_NUM_4;
|
|
||||||
cfg.bus_shared = true;
|
|
||||||
cfg.panel_width = 240;
|
|
||||||
cfg.panel_height = 320;
|
|
||||||
cfg.offset_x = 0;
|
|
||||||
cfg.offset_y = 0;
|
|
||||||
cfg.offset_rotation = 0;
|
|
||||||
cfg.dummy_read_pixel = 8;
|
|
||||||
cfg.dummy_read_bits = 1;
|
|
||||||
cfg.readable = true;
|
|
||||||
cfg.invert = false;
|
|
||||||
cfg.rgb_order = false;
|
|
||||||
cfg.dlen_16bit = false;
|
|
||||||
_panel_instance.config(cfg);
|
|
||||||
|
|
||||||
// SPI bus configuration
|
|
||||||
auto bus_cfg = _bus_instance.config();
|
|
||||||
bus_cfg.spi_host = HSPI_HOST;
|
|
||||||
bus_cfg.spi_mode = 0;
|
|
||||||
bus_cfg.freq_write = 40000000;
|
|
||||||
bus_cfg.freq_read = 16000000;
|
|
||||||
bus_cfg.pin_sclk = GPIO_NUM_18;
|
|
||||||
bus_cfg.pin_mosi = GPIO_NUM_23;
|
|
||||||
bus_cfg.pin_miso = GPIO_NUM_19;
|
|
||||||
bus_cfg.pin_dc = GPIO_NUM_2;
|
|
||||||
_bus_instance.config(bus_cfg);
|
|
||||||
_panel_instance.setBus(&_bus_instance);
|
|
||||||
|
|
||||||
// Touch panel configuration (XPT2046)
|
|
||||||
auto touch_cfg = _touch_instance.config();
|
|
||||||
touch_cfg.x_min = 0;
|
|
||||||
touch_cfg.x_max = 239;
|
|
||||||
touch_cfg.y_min = 0;
|
|
||||||
touch_cfg.y_max = 319;
|
|
||||||
touch_cfg.pin_int = -1;
|
|
||||||
touch_cfg.bus_shared = true;
|
|
||||||
touch_cfg.offset_rotation = 0;
|
|
||||||
touch_cfg.spi_host = HSPI_HOST;
|
|
||||||
touch_cfg.freq = 1000000;
|
|
||||||
touch_cfg.pin_sclk = GPIO_NUM_18;
|
|
||||||
touch_cfg.pin_mosi = GPIO_NUM_23;
|
|
||||||
touch_cfg.pin_miso = GPIO_NUM_19;
|
|
||||||
touch_cfg.pin_cs = GPIO_NUM_5;
|
|
||||||
_touch_instance.config(touch_cfg);
|
|
||||||
_panel_instance.setTouch(&_touch_instance);
|
|
||||||
}
|
|
||||||
|
|
||||||
void initBacklight() {
|
|
||||||
pinMode(GPIO_NUM_21, OUTPUT);
|
|
||||||
digitalWrite(GPIO_NUM_21, HIGH);
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
lgfx::Bus_SPI _bus_instance;
|
|
||||||
lgfx::Panel_ILI9341 _panel_instance;
|
|
||||||
lgfx::Touch_XPT2046 _touch_instance;
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace cyd
|
} // namespace cyd
|
||||||
|
|||||||
+20
-4
@@ -35,6 +35,7 @@ enum class AppState {
|
|||||||
AppState appState = AppState::Init;
|
AppState appState = AppState::Init;
|
||||||
unsigned long lastHeapLog = 0;
|
unsigned long lastHeapLog = 0;
|
||||||
unsigned long lastDashboardUpdate = 0;
|
unsigned long lastDashboardUpdate = 0;
|
||||||
|
unsigned long lastBleStartAttempt = 0;
|
||||||
|
|
||||||
void requestReboot() {
|
void requestReboot() {
|
||||||
delay(500);
|
delay(500);
|
||||||
@@ -102,11 +103,12 @@ void loop() {
|
|||||||
// Heartbeat logging
|
// Heartbeat logging
|
||||||
if (millis() - lastHeapLog >= 10000) {
|
if (millis() - lastHeapLog >= 10000) {
|
||||||
lastHeapLog = millis();
|
lastHeapLog = millis();
|
||||||
Serial.printf("[heartbeat] state=%d free_heap=%d wifi=%s ble=%s\n",
|
Serial.printf("[heartbeat] state=%d free_heap=%d wifi=%s ble=%s/%s\n",
|
||||||
static_cast<int>(appState),
|
static_cast<int>(appState),
|
||||||
ESP.getFreeHeap(),
|
ESP.getFreeHeap(),
|
||||||
wifi.isConnected() ? "connected" : "not-connected",
|
wifi.isConnected() ? "connected" : "not-connected",
|
||||||
bleClient.getReading().connected ? "connected" : "disconnected");
|
bleClient.getReading().connected ? "connected" : "disconnected",
|
||||||
|
bleClient.getStateName());
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (appState) {
|
switch (appState) {
|
||||||
@@ -144,13 +146,27 @@ void loop() {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Start BLE if not already trying.
|
|
||||||
ThermoproReading reading = bleClient.getReading();
|
|
||||||
if (!settings.hasThermoproMac()) {
|
if (!settings.hasThermoproMac()) {
|
||||||
dashboard.showMessage("No MAC configured", "Open setup at", wifi.getIpAddress());
|
dashboard.showMessage("No MAC configured", "Open setup at", wifi.getIpAddress());
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Proactively (re)start BLE connect if it is not doing anything.
|
||||||
|
if (!bleClient.getReading().connected) {
|
||||||
|
const char* state = bleClient.getStateName();
|
||||||
|
if (strcmp(state, "idle") == 0 || strcmp(state, "reconnecting") == 0) {
|
||||||
|
if (millis() - lastBleStartAttempt >= 5000) {
|
||||||
|
lastBleStartAttempt = millis();
|
||||||
|
Serial.printf("Main: requesting BLE connect to %s (state=%s)\n",
|
||||||
|
settings.getThermoproMac().c_str(), state);
|
||||||
|
bleClient.setAddress(settings.getThermoproMac());
|
||||||
|
if (!bleClient.startConnect()) {
|
||||||
|
Serial.println("Main: BLE startConnect returned false");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Dashboard refresh
|
// Dashboard refresh
|
||||||
if (millis() - lastDashboardUpdate >= 1000) {
|
if (millis() - lastDashboardUpdate >= 1000) {
|
||||||
lastDashboardUpdate = millis();
|
lastDashboardUpdate = millis();
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
#include "SettingsManager.hpp"
|
#include "SettingsManager.hpp"
|
||||||
#include <ArduinoJson.h>
|
#include <ArduinoJson.h>
|
||||||
#include <Preferences.h>
|
#include <Preferences.h>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
namespace cyd {
|
namespace cyd {
|
||||||
|
|
||||||
@@ -75,7 +76,8 @@ bool SettingsManager::load() {
|
|||||||
settings_.apiEndpoint = doc["apiEndpoint"] | "";
|
settings_.apiEndpoint = doc["apiEndpoint"] | "";
|
||||||
settings_.apiToken = doc["apiToken"] | "";
|
settings_.apiToken = doc["apiToken"] | "";
|
||||||
settings_.pollIntervalSec = doc["pollIntervalSec"] | 15;
|
settings_.pollIntervalSec = doc["pollIntervalSec"] | 15;
|
||||||
settings_.unit = doc["unit"] | "F";
|
String unit = doc["unit"] | "F";
|
||||||
|
settings_.unit = unit.length() > 0 ? unit.charAt(0) : 'F';
|
||||||
settings_.deviceId = doc["deviceId"] | "cyd-smoker";
|
settings_.deviceId = doc["deviceId"] | "cyd-smoker";
|
||||||
|
|
||||||
JsonArray names = doc["probeNames"].as<JsonArray>();
|
JsonArray names = doc["probeNames"].as<JsonArray>();
|
||||||
|
|||||||
@@ -1,42 +1,7 @@
|
|||||||
#include "WiFiManager.hpp"
|
#include "WiFiManager.hpp"
|
||||||
#include <ArduinoJson.h>
|
|
||||||
|
|
||||||
namespace cyd {
|
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) {
|
WiFiManager::WiFiManager(SettingsManager& settings) : settings_(settings) {
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -86,9 +51,8 @@ void WiFiManager::update() {
|
|||||||
|
|
||||||
case State::AccessPoint:
|
case State::AccessPoint:
|
||||||
case State::FallbackToAp:
|
case State::FallbackToAp:
|
||||||
if (portalRunning_) {
|
if (apRunning_) {
|
||||||
dnsServer_.processNextRequest();
|
dnsServer_.processNextRequest();
|
||||||
portalServer_.handleClient();
|
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
@@ -144,13 +108,10 @@ void WiFiManager::startAccessPoint_() {
|
|||||||
IPAddress ip = WiFi.softAPIP();
|
IPAddress ip = WiFi.softAPIP();
|
||||||
Serial.printf("WiFiManager: AP '%s' started at %s\n", kApSsid, ip.toString().c_str());
|
Serial.printf("WiFiManager: AP '%s' started at %s\n", kApSsid, ip.toString().c_str());
|
||||||
|
|
||||||
|
// DNS redirect: any hostname requested while connected to the AP resolves
|
||||||
|
// to the CYD. The HTTP server in ApiServer serves the setup page at /.
|
||||||
dnsServer_.start(53, "*", ip);
|
dnsServer_.start(53, "*", ip);
|
||||||
|
apRunning_ = true;
|
||||||
portalServer_.on("/", HTTP_GET, [this]() { handlePortalRoot_(); });
|
|
||||||
portalServer_.on("/setup", HTTP_POST, [this]() { handlePortalSubmit_(); });
|
|
||||||
portalServer_.onNotFound([this]() { handlePortalNotFound_(); });
|
|
||||||
portalServer_.begin();
|
|
||||||
portalRunning_ = true;
|
|
||||||
|
|
||||||
if (modeCallback_) {
|
if (modeCallback_) {
|
||||||
modeCallback_(true);
|
modeCallback_(true);
|
||||||
@@ -158,65 +119,9 @@ void WiFiManager::startAccessPoint_() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void WiFiManager::stopAccessPoint_() {
|
void WiFiManager::stopAccessPoint_() {
|
||||||
portalRunning_ = false;
|
apRunning_ = false;
|
||||||
portalServer_.close();
|
|
||||||
dnsServer_.stop();
|
dnsServer_.stop();
|
||||||
WiFi.softAPdisconnect(true);
|
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
|
} // namespace cyd
|
||||||
|
|||||||
@@ -3,7 +3,6 @@
|
|||||||
#include "settings/SettingsManager.hpp"
|
#include "settings/SettingsManager.hpp"
|
||||||
#include <WiFi.h>
|
#include <WiFi.h>
|
||||||
#include <DNSServer.h>
|
#include <DNSServer.h>
|
||||||
#include <WebServer.h>
|
|
||||||
#include <functional>
|
#include <functional>
|
||||||
|
|
||||||
namespace cyd {
|
namespace cyd {
|
||||||
@@ -52,23 +51,17 @@ private:
|
|||||||
unsigned long connectStartMillis_ = 0;
|
unsigned long connectStartMillis_ = 0;
|
||||||
static constexpr unsigned long kConnectTimeoutMs = 20000;
|
static constexpr unsigned long kConnectTimeoutMs = 20000;
|
||||||
|
|
||||||
// Captive portal
|
// Captive portal DNS redirect. The actual HTTP server lives in ApiServer
|
||||||
|
// so we don't bind port 80 in two places.
|
||||||
DNSServer dnsServer_;
|
DNSServer dnsServer_;
|
||||||
WebServer portalServer_{80};
|
bool apRunning_ = false;
|
||||||
bool portalRunning_ = false;
|
|
||||||
unsigned long lastPortalClientMillis_ = 0;
|
|
||||||
|
|
||||||
static constexpr const char* kApSsid = "CYD-ThermoPro-Setup";
|
static constexpr const char* kApSsid = "CYD-ThermoPro-Setup";
|
||||||
static constexpr const char* kApPass = "";
|
static constexpr const char* kApPass = "";
|
||||||
static constexpr const char* kSetupDomain = "cyd.setup";
|
|
||||||
|
|
||||||
void startStation_();
|
void startStation_();
|
||||||
void startAccessPoint_();
|
void startAccessPoint_();
|
||||||
void stopAccessPoint_();
|
void stopAccessPoint_();
|
||||||
void handlePortalRoot_();
|
|
||||||
void handlePortalSubmit_();
|
|
||||||
void handlePortalNotFound_();
|
|
||||||
static void redirectToPortal_(WebServer& server);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace cyd
|
} // namespace cyd
|
||||||
|
|||||||
Reference in New Issue
Block a user