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.
This commit is contained in:
Ryan Malloy 2026-02-05 09:48:49 -07:00
parent 7516c808a7
commit 0905aa4bf8
3 changed files with 23 additions and 6 deletions

View File

@ -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);
}
}

View File

@ -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;

View File

@ -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";
}
}