From d180f097ea98bd279befa0fba9ec878d4ce24061 Mon Sep 17 00:00:00 2001 From: agessaman Date: Sat, 19 Sep 2026 13:58:49 -0700 Subject: [PATCH] fix(mqtt): an explicit bridge stop cancels a pending late-ack resume setBridgeState(false) returned early when the bridge was already down, so `set bridge.enabled off` after a timed-out restart left the resume pending and MQTT restarted against the operator's choice once the task acknowledged. Disabling now clears the pending resume before that early return, and the resume also requires bridge_enabled. The StopUnproven log and `get mqtt.status` no longer say only "reboot to recover", since a late ack now recovers on its own. Hardware (Heltec V4, 5 live slots, 1 s test stop deadline): a restart went StopUnproven, the task acknowledged 27 s later and the bridge resumed with all slots reconnecting; with `set bridge.enabled off` in between it stayed stopped until `set bridge.enabled on`. --- examples/simple_repeater/MyMesh.cpp | 3 ++- examples/simple_repeater/MyMesh.h | 3 +++ examples/simple_room_server/MyMesh.cpp | 3 ++- examples/simple_room_server/MyMesh.h | 3 +++ src/helpers/bridges/MQTTBridge.cpp | 6 +++--- 5 files changed, 13 insertions(+), 5 deletions(-) diff --git a/examples/simple_repeater/MyMesh.cpp b/examples/simple_repeater/MyMesh.cpp index 93dbe279..f9bdf7a1 100644 --- a/examples/simple_repeater/MyMesh.cpp +++ b/examples/simple_repeater/MyMesh.cpp @@ -1702,7 +1702,8 @@ void MyMesh::loop() { #ifdef WITH_MQTT_BRIDGE // A timed-out stop keeps the bridge down until the MQTT task acknowledges it; // once that late ack lands, restart the bridge that was meant to be running. - if (_bridge_resume_pending && bridge && bridge->stopAcknowledgedLate()) { + if (_bridge_resume_pending && _prefs.bridge_enabled && bridge && + bridge->stopAcknowledgedLate()) { Serial.println("MQTT: stop acknowledged late - resuming bridge"); setBridgeState(true); } diff --git a/examples/simple_repeater/MyMesh.h b/examples/simple_repeater/MyMesh.h index 3dd8587b..4b12f6d0 100644 --- a/examples/simple_repeater/MyMesh.h +++ b/examples/simple_repeater/MyMesh.h @@ -353,6 +353,9 @@ public: #endif if (!bridge) return; } +#ifdef WITH_MQTT_BRIDGE + if (!enable) _bridge_resume_pending = false; // an explicit stop cancels a pending resume +#endif if (enable == bridge->isRunning()) return; if (enable) { diff --git a/examples/simple_room_server/MyMesh.cpp b/examples/simple_room_server/MyMesh.cpp index 956b5959..62fe821b 100644 --- a/examples/simple_room_server/MyMesh.cpp +++ b/examples/simple_room_server/MyMesh.cpp @@ -1518,7 +1518,8 @@ void MyMesh::loop() { #ifdef WITH_MQTT_BRIDGE // A timed-out stop keeps the bridge down until the MQTT task acknowledges it; // once that late ack lands, restart the bridge that was meant to be running. - if (_bridge_resume_pending && bridge && bridge->stopAcknowledgedLate()) { + if (_bridge_resume_pending && _prefs.bridge_enabled && bridge && + bridge->stopAcknowledgedLate()) { Serial.println("MQTT: stop acknowledged late - resuming bridge"); setBridgeState(true); } diff --git a/examples/simple_room_server/MyMesh.h b/examples/simple_room_server/MyMesh.h index 0139fa5a..34cb2959 100644 --- a/examples/simple_room_server/MyMesh.h +++ b/examples/simple_room_server/MyMesh.h @@ -364,6 +364,9 @@ public: #endif if (!bridge) return; } +#ifdef WITH_MQTT_BRIDGE + if (!enable) _bridge_resume_pending = false; // an explicit stop cancels a pending resume +#endif if (enable == bridge->isRunning()) return; if (enable) { diff --git a/src/helpers/bridges/MQTTBridge.cpp b/src/helpers/bridges/MQTTBridge.cpp index 1e807d41..a21a75c6 100644 --- a/src/helpers/bridges/MQTTBridge.cpp +++ b/src/helpers/bridges/MQTTBridge.cpp @@ -272,7 +272,7 @@ void MQTTBridge::formatMqttStatusReply(char* buf, size_t bufsize, const MQTTPref const char* msgs = (obs && obs->mqtt_status_enabled) ? "on" : "off"; if (s_mqtt_bridge_instance == nullptr || !s_mqtt_bridge_instance->_initialized) { snprintf(buf, bufsize, "> msgs: %s (bridge %s)", msgs, - s_stop_unproven ? "stopped: previous stop unproven, reboot to recover" + s_stop_unproven ? "stopped: waiting for the previous stop to finish; reboot if it persists" : "not running"); return; } @@ -903,7 +903,7 @@ void MQTTBridge::begin() { // refuse. Recovery is the task finishing, or a reboot. pollLateStopAck(); if (!_lifecycle.mayRestart()) { - MQTT_DEBUG_PRINTLN("MQTT Bridge start refused: previous stop unproven (%s) - reboot to recover", + MQTT_DEBUG_PRINTLN("MQTT Bridge start refused: previous stop unproven (%s) - waiting for the task to acknowledge", MQTTLifecycle::stateName(_lifecycle.state())); return; } @@ -1230,7 +1230,7 @@ void MQTTBridge::end() { s_stop_unproven = _lifecycle.isStopUnproven(); if (_lifecycle.isStopUnproven()) { MQTT_DEBUG_PRINTLN("MQTT Bridge stop UNPROVEN after %lu ms: task did not acknowledge. " - "Nothing released, restart refused, OTA blocked - reboot to recover.", + "Nothing released, OTA blocked; restart waits for its late ack (reboot if it never comes).", (unsigned long)_lifecycle.stopTimeoutMs()); } else { MQTT_DEBUG_PRINTLN("MQTT Bridge stopped (clean)");