From 0b75cb9314a32ccbd4d77dbb761b841879bd066d 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 93dbe2790..f9bdf7a18 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 3dd8587b6..4b12f6d0c 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 956b59596..62fe821b2 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 0139fa5a7..34cb29591 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 1e807d414..a21a75c6c 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)");