From e297dbac076de057b623b4f62124da6fbb955cf2 Mon Sep 17 00:00:00 2001 From: Ada Date: Mon, 16 Feb 2026 18:57:49 -0700 Subject: [PATCH 1/2] Fix reconnect bugs, add watchdog, clean dead code MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Registration state wasn't cleared on WiFi disconnect — device would skip re-registration and publish on stale topic paths. Also re-resolve MQTT hostname on broker disconnect in case IP changed. 30s hardware WDT reboots device if loop() stalls. Removed: unused ESPmDNS include, lastPublishedLevel tracking, lastHeartbeatTime, HEARTBEAT_INTERVAL_MS, ADC_MAX, ADC_VREF. Co-Authored-By: Joshua Perry Co-Authored-By: Claude Opus 4.6 --- include/config.h | 7 ++++--- src/main.cpp | 26 +++++++++++++++++--------- 2 files changed, 21 insertions(+), 12 deletions(-) diff --git a/include/config.h b/include/config.h index a8191d7..130a57c 100644 --- a/include/config.h +++ b/include/config.h @@ -38,13 +38,12 @@ // ============================================================================ #define CLIENT_ID "tanksensor" #define DEVICE_VERSION "v1.0.0" +#define MDNS_HOSTNAME "dusa" // ============================================================================ // ADC Configuration // ============================================================================ #define ADC_RESOLUTION 12 // 12-bit ADC (0-4095) -#define ADC_MAX 4095 -#define ADC_VREF 3.3f // ADC reading parameters #define ADC_SAMPLES 64 // Number of samples to average @@ -63,7 +62,6 @@ // ============================================================================ #define READ_INTERVAL_MS 5000 // Read ADC every 5 seconds #define PUBLISH_INTERVAL_MS 30000 // Publish to MQTT every 30 seconds -#define HEARTBEAT_INTERVAL_MS 60000 // Keepalive every 60 seconds // Connection timeouts #define WIFI_CONNECT_TIMEOUT_MS 30000 // WiFi connection timeout @@ -74,6 +72,9 @@ #define WIFI_RECONNECT_DELAY_MS 5000 // Delay between WiFi reconnect attempts #define MQTT_RECONNECT_DELAY_MS 2000 // Delay between MQTT reconnect attempts +// Watchdog +#define WDT_TIMEOUT_S 30 // Hardware watchdog timeout (seconds) + // ============================================================================ // Pin Assignments (XIAO ESP32-S3) // ============================================================================ diff --git a/src/main.cpp b/src/main.cpp index 40226bd..218b804 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -14,7 +14,7 @@ #if MQTT_USE_TLS #include #endif -#include +#include #include #include @@ -34,7 +34,6 @@ struct TankConfig { struct TankState { int rawADC; // Raw ADC reading int level; // Level percentage (0-100) - int lastPublishedLevel; bool registered; char topicPath[64]; // W/portalId/tank/N path from registration int deviceInstance; @@ -86,7 +85,6 @@ IPAddress mqttServerIP; // Timing unsigned long lastReadTime = 0; unsigned long lastPublishTime = 0; -unsigned long lastHeartbeatTime = 0; unsigned long stateEnteredTime = 0; unsigned long lastWiFiAttempt = 0; unsigned long lastMQTTAttempt = 0; @@ -472,8 +470,6 @@ void publishTankViaProxy(int tankIndex) { DEBUG_PRINTF("Publishing %s: %s\n", topic, payload); mqttClient.publish(topic, payload); - - state.lastPublishedLevel = state.level; } void publishAllTanks() { @@ -570,12 +566,15 @@ void runStateMachine() { case STATE_RUNNING: if (!wifiIsConnected()) { + clearRegistration(); changeState(STATE_WIFI_CONNECT); break; } - + if (!mqttClient.connected()) { clearRegistration(); + resolveMqttServer(); + mqttClient.setServer(mqttServerIP, MQTT_PORT); changeState(STATE_MQTT_CONNECT); break; } @@ -679,7 +678,6 @@ void setup() { for (int i = 0; i < TANK_COUNT; i++) { tankStates[i].rawADC = 0; tankStates[i].level = 0; - tankStates[i].lastPublishedLevel = -1; tankStates[i].registered = false; tankStates[i].topicPath[0] = '\0'; tankStates[i].deviceInstance = 0; @@ -689,13 +687,23 @@ void setup() { adcSetup(); wifiSetup(); mqttSetup(); - + + // Hardware watchdog — resets device if loop() stalls + esp_task_wdt_config_t wdtConfig = { + .timeout_ms = WDT_TIMEOUT_S * 1000, + .idle_core_mask = 0, + .trigger_panic = true, + }; + esp_task_wdt_reconfigure(&wdtConfig); + esp_task_wdt_add(NULL); + DEBUG_PRINTLN("Setup complete, starting state machine"); } void loop() { unsigned long now = millis(); - + esp_task_wdt_reset(); + // Always process MQTT messages if (mqttClient.connected()) { mqttClient.loop(); From ad223f858cf3c1dbb0766a1ce793e6b5a85e8548 Mon Sep 17 00:00:00 2001 From: Ada Date: Tue, 17 Feb 2026 20:35:14 -0700 Subject: [PATCH 2/2] HTTP push OTA via mDNS MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Device runs a web server on port 80 after WiFi connects. Push firmware with: curl -F "firmware=@firmware.bin" http://dusa.local/update No reverse connection (unlike espota), so no workstation firewall needed. mDNS init is idempotent — safe across WiFi reconnects. Co-Authored-By: Joshua Perry Co-Authored-By: Claude Opus 4.6 --- include/config.h | 1 + src/main.cpp | 52 +++++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 52 insertions(+), 1 deletion(-) diff --git a/include/config.h b/include/config.h index 130a57c..c02bdcc 100644 --- a/include/config.h +++ b/include/config.h @@ -39,6 +39,7 @@ #define CLIENT_ID "tanksensor" #define DEVICE_VERSION "v1.0.0" #define MDNS_HOSTNAME "dusa" +#define OTA_PORT 80 // ============================================================================ // ADC Configuration diff --git a/src/main.cpp b/src/main.cpp index 218b804..c2c60c6 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -14,6 +14,9 @@ #if MQTT_USE_TLS #include #endif +#include +#include +#include #include #include #include @@ -76,6 +79,8 @@ PubSubClient mqttClient(wifiClient); TankState tankStates[TANK_COUNT]; DeviceState deviceState = STATE_INIT; +WebServer otaServer(OTA_PORT); +bool otaInitialized = false; char portalId[32] = ""; bool allTanksRegistered = false; @@ -276,6 +281,49 @@ bool resolveMqttServer() { return false; } +// ============================================================================ +// OTA Functions +// ============================================================================ + +void otaSetup() { + if (otaInitialized) return; + + MDNS.begin(MDNS_HOSTNAME); + + otaServer.on("/update", HTTP_POST, []() { + bool ok = !Update.hasError(); + otaServer.sendHeader("Connection", "close"); + otaServer.send(ok ? 200 : 500, "text/plain", ok ? "OK\n" : "FAIL\n"); + if (ok) { + delay(500); + ESP.restart(); + } + }, []() { + HTTPUpload& upload = otaServer.upload(); + if (upload.status == UPLOAD_FILE_START) { + DEBUG_PRINTF("OTA update: %s\n", upload.filename.c_str()); + if (!Update.begin(UPDATE_SIZE_UNKNOWN)) { + DEBUG_PRINTF("OTA begin failed: %s\n", Update.errorString()); + } + } else if (upload.status == UPLOAD_FILE_WRITE) { + esp_task_wdt_reset(); + if (Update.write(upload.buf, upload.currentSize) != upload.currentSize) { + DEBUG_PRINTF("OTA write failed: %s\n", Update.errorString()); + } + } else if (upload.status == UPLOAD_FILE_END) { + if (Update.end(true)) { + DEBUG_PRINTF("OTA complete: %u bytes\n", upload.totalSize); + } else { + DEBUG_PRINTF("OTA end failed: %s\n", Update.errorString()); + } + } + }); + + otaServer.begin(); + otaInitialized = true; + DEBUG_PRINTF("OTA ready: http://%s.local/update\n", MDNS_HOSTNAME); +} + // ============================================================================ // MQTT Functions // ============================================================================ @@ -503,7 +551,8 @@ void runStateMachine() { case STATE_WIFI_CONNECT: if (wifiIsConnected()) { DEBUG_PRINTF("WiFi connected, IP: %s\n", WiFi.localIP().toString().c_str()); - + otaSetup(); + // Resolve MQTT server hostname (supports mDNS .local names) if (resolveMqttServer()) { mqttClient.setServer(mqttServerIP, MQTT_PORT); @@ -703,6 +752,7 @@ void setup() { void loop() { unsigned long now = millis(); esp_task_wdt_reset(); + otaServer.handleClient(); // Always process MQTT messages if (mqttClient.connected()) {