diff --git a/include/config.h b/include/config.h index c4b1533..17c810d 100644 --- a/include/config.h +++ b/include/config.h @@ -63,6 +63,7 @@ constexpr uint8_t METER_BATCH_MAX_SAMPLES = 30; constexpr uint8_t BATCH_QUEUE_DEPTH = 10; constexpr BatchRetryPolicy BATCH_RETRY_POLICY = BatchRetryPolicy::Keep; constexpr uint32_t WATCHDOG_TIMEOUT_SEC = 120; +constexpr uint32_t WIFI_RECONNECT_INTERVAL_MS = 60000; // WiFi reconnection retry interval (1 minute) constexpr bool ENABLE_HA_DISCOVERY = true; #ifndef SERIAL_DEBUG_MODE_FLAG #define SERIAL_DEBUG_MODE_FLAG 0 diff --git a/include/wifi_manager.h b/include/wifi_manager.h index 74f288c..27060b6 100644 --- a/include/wifi_manager.h +++ b/include/wifi_manager.h @@ -25,3 +25,5 @@ bool wifi_connect_sta(const WifiMqttConfig &config, uint32_t timeout_ms = 10000) void wifi_start_ap(const char *ap_ssid, const char *ap_pass); bool wifi_is_connected(); String wifi_get_ssid(); +bool wifi_try_reconnect_sta(const WifiMqttConfig &config, uint32_t timeout_ms = 5000); +void wifi_restore_ap_mode(const char *ap_ssid, const char *ap_pass); diff --git a/src/app_context.h b/src/app_context.h index 2d450de..57346d0 100644 --- a/src/app_context.h +++ b/src/app_context.h @@ -4,6 +4,7 @@ #include "config.h" #include "data_model.h" +#include "wifi_manager.h" struct ReceiverSharedState { SenderStatus sender_statuses[NUM_SENDERS]; @@ -24,5 +25,11 @@ struct ReceiverSharedState { uint32_t receiver_last_error_ms; bool receiver_discovery_sent; bool ap_mode; + + // WiFi configuration and reconnection tracking + WifiMqttConfig wifi_config; + uint32_t last_wifi_reconnect_attempt_ms; + char ap_ssid[32]; // AP SSID for restoring AP mode if reconnection fails + char ap_password[32]; // AP password for restoring AP mode }; diff --git a/src/main.cpp b/src/main.cpp index 11dc8f9..e4c354c 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -112,6 +112,11 @@ void setup() { display_set_sender_statuses(g_receiver_shared.sender_statuses, NUM_SENDERS); bool has_cfg = wifi_load_config(g_cfg); + + // Store WiFi config in shared state for later reconnection attempts + g_receiver_shared.wifi_config = g_cfg; + g_receiver_shared.last_wifi_reconnect_attempt_ms = 0; + if (has_cfg && wifi_connect_sta(g_cfg)) { g_receiver_shared.ap_mode = false; time_receiver_init(g_cfg.ntp_server_1.c_str(), g_cfg.ntp_server_2.c_str()); @@ -124,6 +129,13 @@ void setup() { char ap_ssid[32]; snprintf(ap_ssid, sizeof(ap_ssid), "%s%04X", AP_SSID_PREFIX, g_short_id); wifi_start_ap(ap_ssid, AP_PASSWORD); + + // Store AP credentials in shared state for potential reconnection fallback + strncpy(g_receiver_shared.ap_ssid, ap_ssid, sizeof(g_receiver_shared.ap_ssid) - 1); + g_receiver_shared.ap_ssid[sizeof(g_receiver_shared.ap_ssid) - 1] = '\0'; + strncpy(g_receiver_shared.ap_password, AP_PASSWORD, sizeof(g_receiver_shared.ap_password) - 1); + g_receiver_shared.ap_password[sizeof(g_receiver_shared.ap_password) - 1] = '\0'; + if (g_cfg.ntp_server_1.isEmpty()) { g_cfg.ntp_server_1 = "pool.ntp.org"; } diff --git a/src/receiver_pipeline.cpp b/src/receiver_pipeline.cpp index 0ef466c..9e3030d 100644 --- a/src/receiver_pipeline.cpp +++ b/src/receiver_pipeline.cpp @@ -198,7 +198,7 @@ static void write_u32_be(uint8_t *dst, uint32_t value) { dst[3] = static_cast(value & 0xFF); } -static uint32_t read_u32_be(const uint8_t *src) { +uint32_t read_u32_be(const uint8_t *src) { return (static_cast(src[0]) << 24) | (static_cast(src[1]) << 16) | (static_cast(src[2]) << 8) | @@ -303,6 +303,51 @@ static bool process_batch_packet(const LoraPacket &pkt, BatchInput &out_batch, b return false; } +// Helper function to attempt WiFi reconnection when stuck in AP mode +// Retries WiFi connection periodically (configurable WIFI_RECONNECT_INTERVAL_MS) +// to recover from temporary WiFi outages +static void try_wifi_reconnect_if_in_ap_mode() { + if (!g_ap_mode) { + // Already in STA mode, no need to reconnect + return; + } + + if (!g_shared || g_shared->wifi_config.ssid.length() == 0) { + // No valid WiFi config to reconnect with + return; + } + + uint32_t now_ms = millis(); + + if (g_shared->last_wifi_reconnect_attempt_ms == 0 || + now_ms - g_shared->last_wifi_reconnect_attempt_ms >= WIFI_RECONNECT_INTERVAL_MS) { + + // Update the last attempt time + g_shared->last_wifi_reconnect_attempt_ms = now_ms; + + if (SERIAL_DEBUG_MODE) { + serial_debug_printf("wifi_reconnect: attempting to reconnect from AP mode"); + } + + // Try to reconnect with 10 second timeout + if (wifi_try_reconnect_sta(g_shared->wifi_config, 10000)) { + // Reconnection successful! + g_ap_mode = false; + if (SERIAL_DEBUG_MODE) { + serial_debug_printf("wifi_reconnect: reconnection successful, switching from AP to STA mode"); + } + } else { + // Reconnection failed, restore AP mode to ensure web interface is available + if (g_shared->ap_ssid[0] != '\0') { + wifi_restore_ap_mode(g_shared->ap_ssid, g_shared->ap_password); + if (SERIAL_DEBUG_MODE) { + serial_debug_printf("wifi_reconnect: reconnection failed, restored AP mode"); + } + } + } + } +} + static void receiver_loop() { watchdog_kick(); LoraPacket pkt = {}; @@ -468,6 +513,9 @@ static void receiver_loop() { } receiver_loop_done: + // Try to reconnect to WiFi if stuck in AP mode due to unreliable WiFi + try_wifi_reconnect_if_in_ap_mode(); + mqtt_loop(); web_server_loop(); if (ENABLE_HA_DISCOVERY && !g_receiver_discovery_sent) { diff --git a/src/wifi_manager.cpp b/src/wifi_manager.cpp index 7481768..830126c 100644 --- a/src/wifi_manager.cpp +++ b/src/wifi_manager.cpp @@ -143,3 +143,52 @@ bool wifi_is_connected() { String wifi_get_ssid() { return WiFi.SSID(); } + +// Try to reconnect to WiFi with a shorter timeout (for periodic reconnection attempts) +// Called when device is stuck in AP mode and we want to try switching back to STA +bool wifi_try_reconnect_sta(const WifiMqttConfig &config, uint32_t timeout_ms) { + // Only attempt if not already connected and config is valid + if (WiFi.status() == WL_CONNECTED) { + return true; + } + + // Check if config is valid + if (config.ssid.length() == 0 || config.mqtt_host.length() == 0) { + return false; + } + + // Switch to STA mode and attempt connection with shorter timeout + WiFi.mode(WIFI_STA); + WiFi.begin(config.ssid.c_str(), config.password.c_str()); + + uint32_t start = millis(); + while (WiFi.status() != WL_CONNECTED && millis() - start < timeout_ms) { + delay(200); + } + + bool connected = WiFi.status() == WL_CONNECTED; + if (connected) { + esp_wifi_set_ps(WIFI_PS_MIN_MODEM); + if (SERIAL_DEBUG_MODE) { + Serial.printf("wifi_reconnect: success, connected to %s\n", config.ssid.c_str()); + } + } else { + if (SERIAL_DEBUG_MODE) { + Serial.printf("wifi_reconnect: failed, remaining in STA mode\n"); + } + } + + return connected; +} + +// Helper function to restore AP mode when reconnection attempt has failed +void wifi_restore_ap_mode(const char *ap_ssid, const char *ap_pass) { + if (WiFi.status() != WL_CONNECTED) { + // We're not connected to WiFi, restore AP mode + WiFi.mode(WIFI_AP); + WiFi.softAP(ap_ssid, ap_pass); + if (SERIAL_DEBUG_MODE) { + Serial.printf("wifi_restore_ap: AP mode restored\n"); + } + } +}