diff --git a/include/config.h b/include/config.h index a8191d7..c02bdcc 100644 --- a/include/config.h +++ b/include/config.h @@ -38,13 +38,13 @@ // ============================================================================ #define CLIENT_ID "tanksensor" #define DEVICE_VERSION "v1.0.0" +#define MDNS_HOSTNAME "dusa" +#define OTA_PORT 80 // ============================================================================ // 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 +63,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 +73,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..c2c60c6 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -15,6 +15,9 @@ #include #endif #include +#include +#include +#include #include #include @@ -34,7 +37,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; @@ -77,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; @@ -86,7 +90,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; @@ -278,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 // ============================================================================ @@ -472,8 +518,6 @@ void publishTankViaProxy(int tankIndex) { DEBUG_PRINTF("Publishing %s: %s\n", topic, payload); mqttClient.publish(topic, payload); - - state.lastPublishedLevel = state.level; } void publishAllTanks() { @@ -507,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); @@ -570,12 +615,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 +727,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 +736,24 @@ 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(); + otaServer.handleClient(); + // Always process MQTT messages if (mqttClient.connected()) { mqttClient.loop();