From af91a95b36abb58b68fa6a87b66f8cc5a0d46cbf Mon Sep 17 00:00:00 2001 From: agessaman Date: Fri, 14 Aug 2026 19:43:18 -0700 Subject: [PATCH] fix(mqtt): keep the usable-clock fallback the SNTP strictness removed MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Requiring a real SNTP completion took away something the plausible-clock test was doing by accident. _ntp_synced gates slot setup outright (:1386, :2894), so a device that cannot reach NTP now brings up no slots at all — and a network that blocks UDP/123 while allowing 443 is an ordinary firewall configuration, not a corner case. An RTC-backed observer there used to stay synced and keep minting JWTs against a perfectly good clock. Accept the existing clock explicitly when every server has failed, logged as what it is rather than as a claim about a server that never replied. Excluded from the `set mqtt.ntp` validation path, where the question is whether that server works and the clock cannot answer it. configTime() is now called only when a server did answer, since otherwise there is nothing new to point SNTP at. --- src/helpers/bridges/MQTTBridge.cpp | 28 +++++++++++++++++++++++++--- 1 file changed, 25 insertions(+), 3 deletions(-) diff --git a/src/helpers/bridges/MQTTBridge.cpp b/src/helpers/bridges/MQTTBridge.cpp index 24553a5d7..1a3c2d155 100644 --- a/src/helpers/bridges/MQTTBridge.cpp +++ b/src/helpers/bridges/MQTTBridge.cpp @@ -4012,7 +4012,24 @@ bool MQTTBridge::syncTimeWithNTP(bool force, bool primary_only) { } #endif - if (ntp_ok && ntp_server_used) { + // No server answered, but the clock itself may still be usable. Requiring a real + // SNTP completion above removed something the plausible-clock test was doing by + // accident: an RTC-backed device on a network that blocks NTP (UDP/123) while + // allowing the broker (443) stayed synced and kept minting JWTs. _ntp_synced gates + // slot setup outright, so losing that strands those deployments with no slots at + // all. Keep the behaviour, but as its own decision rather than as a claim about a + // server that never replied. Not on the validation path — `set mqtt.ntp` asks + // whether that server works, and the clock cannot answer for it. + if (!ntp_ok && !primary_only) { + unsigned long existing = (unsigned long)time(nullptr); + if (existing >= kMinValidEpoch) { + epochTime = existing; + ntp_ok = true; + MQTT_DEBUG_PRINTLN("No NTP server answered; continuing on the existing clock: %lu", existing); + } + } + + if (ntp_ok) { // Take ownership of the system clock here, before anything reads it. configTime() // only restarts SNTP and returns, and _rtc reaches settimeofday() on exactly one // path: AutoDiscoverRTCClock writes a detected DS3231/RV3028/PCF8563/RX8130CE chip @@ -4025,7 +4042,11 @@ bool MQTTBridge::syncTimeWithNTP(bool force, bool primary_only) { accepted.tv_usec = 0; settimeofday(&accepted, nullptr); - configTime(0, 0, ntp_server_used); + // Only when a server actually answered: there is nothing to point SNTP at + // otherwise, and the existing configuration is the best guess available. + if (ntp_server_used) { + configTime(0, 0, ntp_server_used); + } if (_rtc) { _rtc->setCurrentTime(epochTime); @@ -4036,7 +4057,8 @@ bool MQTTBridge::syncTimeWithNTP(bool force, bool primary_only) { _last_ntp_sync = millis(); sync_in_progress = false; - MQTT_DEBUG_PRINTLN("Time synced: %lu (via %s)", epochTime, ntp_server_used); + MQTT_DEBUG_PRINTLN("Time synced: %lu (via %s)", epochTime, + ntp_server_used ? ntp_server_used : "existing clock"); // If slots are already set up and the time jumped significantly (e.g., SNTP // initially returned stale RTC time, then a later sync corrected it), re-issue