From 0905aa4bf82d7cb64b10910b084a27993db257ea Mon Sep 17 00:00:00 2001 From: Ryan Malloy Date: Thu, 5 Feb 2026 09:48:49 -0700 Subject: [PATCH] Add exponential backoff to MQTT reconnection Instead of fixed 5s/30s retry intervals, implement exponential backoff: - Initial delay: 1 second - Max delay: 60 seconds - Doubles on each failed attempt - Resets to minimum on successful connection This prevents hammering a down broker while still reconnecting quickly when the issue is transient. Also includes fix for WiFiState/NetworkState enum mismatch in getWiFiStatus() which was already on main. --- src/MQTTBridge.cpp | 17 ++++++++++++++--- src/MQTTBridge.h | 6 ++++++ src/MyMesh.cpp | 6 +++--- 3 files changed, 23 insertions(+), 6 deletions(-) diff --git a/src/MQTTBridge.cpp b/src/MQTTBridge.cpp index 27c532d..94573f8 100644 --- a/src/MQTTBridge.cpp +++ b/src/MQTTBridge.cpp @@ -17,6 +17,7 @@ MQTTBridge::MQTTBridge(INetworkManager* network, mesh::PacketManager* mgr, mesh: _last_status_publish(0), _last_stats_publish(0), _connected_since(0), + _current_backoff_ms(BACKOFF_MIN_MS), _messages_sent(0), _messages_received(0), _reconnect_count(0), @@ -80,7 +81,8 @@ void MQTTBridge::loop() { switch (_state) { case MQTTState::DISCONNECTED: - if (millis() - _last_connect_attempt > 5000) { + // Use exponential backoff for reconnection attempts + if (millis() - _last_connect_attempt > _current_backoff_ms) { attemptConnection(); } break; @@ -93,6 +95,7 @@ void MQTTBridge::loop() { _state = MQTTState::DISCONNECTED; Serial.println("[MQTTS] Connection lost"); _last_connect_attempt = millis(); + // Don't reset backoff on connection loss - broker might be down } else { _mqtt_client.loop(); @@ -104,7 +107,8 @@ void MQTTBridge::loop() { break; case MQTTState::ERROR: - if (millis() - _last_connect_attempt > 30000) { + // Use backoff for error recovery too + if (millis() - _last_connect_attempt > _current_backoff_ms) { _state = MQTTState::DISCONNECTED; } break; @@ -134,7 +138,8 @@ void MQTTBridge::attemptConnection() { _state = MQTTState::CONNECTING; _last_connect_attempt = millis(); - Serial.printf("[MQTTS] Connecting to %s:%d...\n", _config.broker, _config.port); + Serial.printf("[MQTTS] Connecting to %s:%d (backoff=%lums)...\n", + _config.broker, _config.port, _current_backoff_ms); String client_id = String(_config.client_id); if (client_id.length() == 0) { @@ -157,6 +162,8 @@ void MQTTBridge::attemptConnection() { _state = MQTTState::CONNECTED; _connected_since = millis(); _reconnect_count++; + // Reset backoff on successful connection + _current_backoff_ms = BACKOFF_MIN_MS; Serial.println("[MQTTS] Connected!"); subscribeToCommands(); @@ -166,6 +173,10 @@ void MQTTBridge::attemptConnection() { int rc = _mqtt_client.state(); Serial.printf("[MQTTS] Connection failed, rc=%d\n", rc); _state = MQTTState::ERROR; + + // Exponential backoff: double the delay (up to max) + _current_backoff_ms = min(_current_backoff_ms * BACKOFF_MULTIPLIER, BACKOFF_MAX_MS); + Serial.printf("[MQTTS] Next retry in %lums\n", _current_backoff_ms); } } diff --git a/src/MQTTBridge.h b/src/MQTTBridge.h index f495447..725dac1 100644 --- a/src/MQTTBridge.h +++ b/src/MQTTBridge.h @@ -85,6 +85,12 @@ private: unsigned long _last_stats_publish; unsigned long _connected_since; + // Exponential backoff for reconnection + static constexpr uint32_t BACKOFF_MIN_MS = 1000; // Start at 1 second + static constexpr uint32_t BACKOFF_MAX_MS = 60000; // Max 60 seconds + static constexpr uint32_t BACKOFF_MULTIPLIER = 2; // Double each attempt + uint32_t _current_backoff_ms; + uint32_t _messages_sent; uint32_t _messages_received; uint32_t _reconnect_count; diff --git a/src/MyMesh.cpp b/src/MyMesh.cpp index 30c9a12..6447302 100644 --- a/src/MyMesh.cpp +++ b/src/MyMesh.cpp @@ -1289,9 +1289,9 @@ const char* MyMesh::getMQTTStatus() const { const char* MyMesh::getWiFiStatus() const { switch (_wifi_mgr.getState()) { - case WiFiState::CONNECTED: return "connected"; - case WiFiState::CONNECTING: return "connecting"; - case WiFiState::AP_MODE: return "ap_mode"; + case NetworkState::CONNECTED: return "connected"; + case NetworkState::CONNECTING: return "connecting"; + case NetworkState::AP_MODE: return "ap_mode"; default: return "disconnected"; } }