From bf57f342b315ea6992d51c6097becd05e038e79f Mon Sep 17 00:00:00 2001 From: Jared Dohrman Date: Mon, 20 Apr 2026 22:30:06 +1000 Subject: [PATCH] fix: require configured mqtt iata before broker connect --- docs/custom-cli.md | 7 +++ docs/web-panel.md | 4 +- release-notes.yml | 21 ++++++++ src/helpers/mqtt/MQTTPrefs.cpp | 3 ++ src/helpers/mqtt/MQTTPrefs.h | 6 ++- src/helpers/mqtt/MQTTUplink.cpp | 15 +++++- src/helpers/mqtt/MQTTUplink.h | 1 + src/helpers/web/WebPanelServer.cpp | 37 +++++++++++++- variants/eastmesh_mqtt/platformio.ini | 70 +++++++++++++-------------- 9 files changed, 125 insertions(+), 39 deletions(-) diff --git a/docs/custom-cli.md b/docs/custom-cli.md index 8a744438..68a35b6a 100644 --- a/docs/custom-cli.md +++ b/docs/custom-cli.md @@ -14,6 +14,7 @@ These commands are available on `*_repeater_mqtt` firmware targets. - `get mqtt.statuscfg`: shows whether periodic status messages are enabled as a simple `on` or `off` value. Most users can just use `get mqtt.status`. - `get mqtt.iata`: shows the IATA/location code used in MQTT topics. - `set mqtt.iata `: sets the IATA/location code, for example `MEL`. +- `set mqtt.iata UNSET`: marks MQTT IATA as not configured yet. While it is `UNSET`, enabled MQTT brokers stay disconnected until a real code is saved. ### MQTT Identity @@ -43,6 +44,12 @@ These commands are available on `*_repeater_mqtt` firmware targets. - `get mqtt.letsmesh-us` - `set mqtt.letsmesh-us on|off` +Notes: + +- new repeater MQTT installs default `mqtt.iata` to `UNSET` +- `letsmesh-eu` and `letsmesh-us` remain off by default unless already configured in saved prefs +- if `mqtt.iata` is `UNSET`, `eastmesh-au`, `letsmesh-eu`, and `letsmesh-us` will not connect even if they are toggled on + Legacy dotted aliases are also accepted: - `mqtt.eastmesh.au` diff --git a/docs/web-panel.md b/docs/web-panel.md index 50d452c5..8730d8c7 100644 --- a/docs/web-panel.md +++ b/docs/web-panel.md @@ -174,10 +174,12 @@ This section includes: - `mqtt.email`: owner contact email. - MQTT server toggles: `eastmesh-au`, `letsmesh-eu`, and `letsmesh-us`. -`MEL` is used as the default dropdown option until the repeater's saved value is loaded. +`UNSET - To be configured` is the default for new repeater MQTT installs until a real saved value exists. Notes: +- when `mqtt.iata` is `UNSET`, the panel shows a banner at the top reminding you to set it under MQTT Settings +- while `mqtt.iata` is `UNSET`, enabled MQTT brokers do not attempt to connect - the current MQTT server states are loaded when the page opens - you can toggle each MQTT server on or off from this panel - if all three servers are enabled at once, the panel shows a warning recommending two at most diff --git a/release-notes.yml b/release-notes.yml index 92282cd3..71480111 100644 --- a/release-notes.yml +++ b/release-notes.yml @@ -344,3 +344,24 @@ releases: area: web-panel text: "Updated the repeater web panel and custom CLI docs to reflect full web CLI access and the new HTTP-to-HTTPS behavior." breaking_changes: [] + + - track: repeater-mqtt + version: "1.3.7" + tag: "repeater-mqtt-eastmesh-v1.3.7" + date: "2026-04-20" + previous_version: "1.3.6" + summary: "Made MQTT IATA explicit on fresh repeater installs, blocked broker connects until it is configured, and surfaced the missing-IATA state in the web panel and CLI status." + changes: + - type: changed + area: mqtt + text: "Changed fresh repeater_mqtt defaults to use `mqtt.iata=UNSET` instead of pre-filling Melbourne." + - type: fixed + area: mqtt + text: "Blocked EastMesh and LetsMesh broker connection attempts while `mqtt.iata` is unset, and reported `invalid iata` in `get mqtt.status` for enabled brokers until a real code is saved." + - type: changed + area: web-panel + text: "Added a top-of-page warning banner and inline MQTT Settings warning when the repeater still needs its MQTT IATA configured." + - type: docs + area: mqtt + text: "Updated the custom CLI and web panel docs to describe the unset-first MQTT IATA flow and the resulting broker behaviour." + breaking_changes: [] diff --git a/src/helpers/mqtt/MQTTPrefs.cpp b/src/helpers/mqtt/MQTTPrefs.cpp index 5097b4ff..a9dde6b0 100644 --- a/src/helpers/mqtt/MQTTPrefs.cpp +++ b/src/helpers/mqtt/MQTTPrefs.cpp @@ -63,6 +63,9 @@ bool MQTTPrefsStore::load(FILESYSTEM* fs, MQTTPrefs& prefs) { if (prefs.legacy_wifi_powersave > 2) { prefs.legacy_wifi_powersave = 0; } + if (prefs.iata[0] == 0) { + StrHelper::strncpy(prefs.iata, MQTT_DEFAULT_IATA, sizeof(prefs.iata)); + } prefs.status_interval_ms = kFixedStatusIntervalMs; prefs.enabled_mask &= 0x07; return true; diff --git a/src/helpers/mqtt/MQTTPrefs.h b/src/helpers/mqtt/MQTTPrefs.h index 057135d8..b5f253cc 100644 --- a/src/helpers/mqtt/MQTTPrefs.h +++ b/src/helpers/mqtt/MQTTPrefs.h @@ -6,7 +6,11 @@ #include #ifndef MQTT_DEFAULT_IATA - #define MQTT_DEFAULT_IATA "MEL" + #define MQTT_DEFAULT_IATA "UNSET" +#endif + +#ifndef MQTT_UNSET_IATA + #define MQTT_UNSET_IATA "UNSET" #endif struct MQTTPrefs { diff --git a/src/helpers/mqtt/MQTTUplink.cpp b/src/helpers/mqtt/MQTTUplink.cpp index 93b8185b..494a2dae 100644 --- a/src/helpers/mqtt/MQTTUplink.cpp +++ b/src/helpers/mqtt/MQTTUplink.cpp @@ -124,6 +124,10 @@ bool MQTTUplink::hasEnabledBroker() const { return (_prefs.enabled_mask & 0x07) != 0; } +bool MQTTUplink::isUnsetIataValue(const char* iata) { + return iata == nullptr || iata[0] == 0 || strcmp(iata, MQTT_UNSET_IATA) == 0; +} + uint8_t MQTTUplink::normalizeEnabledMask(uint8_t mask) { uint8_t normalized = 0; uint8_t count = 0; @@ -599,7 +603,8 @@ void MQTTUplink::ensureBroker(BrokerState& broker, bool allow_new_connect) { return; } bool enabled = (_prefs.enabled_mask & broker.spec->bit) != 0; - if (!enabled) { + bool iata_configured = !isUnsetIataValue(_prefs.iata); + if (!enabled || !iata_configured) { if (broker.client != nullptr || broker.token != nullptr || broker.connected || broker.connect_announced || broker.reconnect_pending || broker.next_connect_attempt != 0 || broker.last_connect_attempt != 0 || broker.reconnect_failures != 0 || broker.token_expires_at != 0) { @@ -828,6 +833,9 @@ void MQTTUplink::formatStatusReply(char* reply, size_t reply_size) const { if ((_prefs.enabled_mask & bit) == 0) { return "off"; } + if (isUnsetIataValue(_prefs.iata)) { + return "invalid iata"; + } const BrokerState* broker = nullptr; for (const BrokerState& candidate : _brokers) { if (candidate.spec != nullptr && candidate.spec->bit == bit) { @@ -911,6 +919,11 @@ bool MQTTUplink::setIata(const char* iata) { for (size_t i = 0; cleaned[i] != 0; ++i) { cleaned[i] = toupper(static_cast(cleaned[i])); } + if (strcmp(cleaned, MQTT_UNSET_IATA) == 0) { + StrHelper::strncpy(_prefs.iata, MQTT_UNSET_IATA, sizeof(_prefs.iata)); + refreshIdentityStrings(); + return savePrefs(); + } StrHelper::strncpy(_prefs.iata, cleaned, sizeof(_prefs.iata)); refreshIdentityStrings(); return savePrefs(); diff --git a/src/helpers/mqtt/MQTTUplink.h b/src/helpers/mqtt/MQTTUplink.h index 3197d857..6cce049b 100644 --- a/src/helpers/mqtt/MQTTUplink.h +++ b/src/helpers/mqtt/MQTTUplink.h @@ -111,6 +111,7 @@ private: static constexpr uint8_t kLetsmeshUsBit = 0x04; static constexpr uint8_t kMaxEnabledBrokers = 2; static const BrokerSpec kBrokerSpecs[3]; + static bool isUnsetIataValue(const char* iata); BrokerState _brokers[3]; diff --git a/src/helpers/web/WebPanelServer.cpp b/src/helpers/web/WebPanelServer.cpp index 56c7aaab..111322db 100644 --- a/src/helpers/web/WebPanelServer.cpp +++ b/src/helpers/web/WebPanelServer.cpp @@ -431,6 +431,8 @@ const char kWebPanelAppHtml[] PROGMEM = R"HTML( .mode-label.disabled { opacity:.4; } .visually-hidden { position:absolute; width:1px; height:1px; padding:0; margin:-1px; overflow:hidden; clip:rect(0,0,0,0); white-space:nowrap; border:0; } .panel-copy, .panel-note, .panel-status, .panel-warning, .stats-empty, .stats-error, .events-empty, .spark-status { font-size:13px; line-height:1.45; font-weight:400; } + .top-banner { display:none; margin-bottom:18px; padding:12px 14px; border-radius:12px; border:1px solid rgba(212,90,90,.45); background:rgba(212,90,90,.12); color:var(--text); } + .top-banner.visible { display:block; } .panel-warning { min-height:1.4em; color:var(--status-red); } .panel-note { color:var(--text-muted); } .panel-status { min-height:1.4em; color:var(--text-muted); } @@ -528,6 +530,7 @@ const char kWebPanelAppHtml[] PROGMEM = R"HTML(
+
MQTT IATA needs setting under MQTT Settings.