fix: require configured mqtt iata before broker connect

This commit is contained in:
Jared Dohrman
2026-04-20 22:30:06 +10:00
parent 073280638e
commit bf57f342b3
9 changed files with 125 additions and 39 deletions
+3
View File
@@ -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;
+5 -1
View File
@@ -6,7 +6,11 @@
#include <stdint.h>
#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 {
+14 -1
View File
@@ -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<unsigned char>(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();
+1
View File
@@ -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];
+36 -1
View File
@@ -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(
</head>
<body>
<main>
<section class="top-banner" id="mqttIataBanner">MQTT IATA needs setting under MQTT Settings.</section>
<section class="card" id="login" style="display:none">
<h1>Repeater Config</h1>
<p>Use the repeater admin password to unlock the command console.</p>
@@ -813,6 +816,9 @@ const char kWebPanelAppHtml[] PROGMEM = R"HTML(
<label class="label" for="mqttIata">MQTT IATA</label>
<div class="inline-actions">
<select id="mqttIata">
<optgroup label="Configuration">
<option value="UNSET">UNSET - To be configured</option>
</optgroup>
<optgroup label="ACT">
<option value="CBR">CBR - Canberra</option>
</optgroup>
@@ -872,7 +878,7 @@ const char kWebPanelAppHtml[] PROGMEM = R"HTML(
<option value="AVV">AVV - Avalon</option>
<option value="GEX">GEX - Geelong West</option>
<option value="MEB">MEB - Essendon Fields</option>
<option value="MEL" selected>MEL - Melbourne</option>
<option value="MEL">MEL - Melbourne</option>
<option value="MQL">MQL - Mildura</option>
</optgroup>
</select>
@@ -1940,6 +1946,7 @@ const char kWebPanelAppHtml[] PROGMEM = R"HTML(
).join("");
}
function showAuthedUi(show) {
const mqttIataBanner = document.getElementById("mqttIataBanner");
document.getElementById("login").style.display = show ? "none" : "block";
document.getElementById("actionsPanel").style.display = show ? "block" : "none";
document.getElementById("cliPanel").style.display = show && !isStatsPage ? "block" : "none";
@@ -1950,6 +1957,7 @@ const char kWebPanelAppHtml[] PROGMEM = R"HTML(
document.getElementById("statsPagePanel").style.display = show && isStatsPage ? "block" : "none";
document.getElementById("repeaterSettingsPanel").style.display = show && !isStatsPage ? "block" : "none";
if (!show) {
if (mqttIataBanner) mqttIataBanner.classList.remove("visible");
commandQueue = Promise.resolve();
const passwordEl = document.getElementById("password");
if (passwordEl) passwordEl.value = "";
@@ -1961,6 +1969,23 @@ const char kWebPanelAppHtml[] PROGMEM = R"HTML(
if (trendsEl) trendsEl.innerHTML = "";
}
}
function isUnsetMqttIata(value) {
return String(value || "").trim().toUpperCase() === "UNSET";
}
function refreshMqttIataWarning() {
const input = document.getElementById("mqttIata");
const banner = document.getElementById("mqttIataBanner");
const inlineWarning = document.getElementById("mqttBrokerWarning");
const showWarning = !!(input && isUnsetMqttIata(input.value));
if (banner) {
banner.classList.toggle("visible", showWarning && !isStatsPage);
}
if (inlineWarning) {
inlineWarning.textContent = showWarning
? "MQTT IATA is unset. Set it before enabling EastMesh or LetsMesh brokers."
: "";
}
}
function queueCommand(task) {
const next = commandQueue.then(task, task);
commandQueue = next.catch(() => {});
@@ -1998,6 +2023,9 @@ const char kWebPanelAppHtml[] PROGMEM = R"HTML(
if (value !== input.value) input.value = value;
const result = await runCommand(prefix + value);
if (!result.ok) return;
if (inputId === "mqttIata") {
refreshMqttIataWarning();
}
if (inputId === "nodeName") {
await loadField("get name", "nodeName", null, { recordHistory:false, updateInput:false });
}
@@ -2012,6 +2040,9 @@ const char kWebPanelAppHtml[] PROGMEM = R"HTML(
value = value.toUpperCase();
}
document.getElementById(inputId).value = value;
if (inputId === "mqttIata") {
refreshMqttIataWarning();
}
if (inputId === "nodeName") {
updatePanelTitle(value);
}
@@ -2251,6 +2282,10 @@ const char kWebPanelAppHtml[] PROGMEM = R"HTML(
document.querySelectorAll("[data-cmd]").forEach((btn) => btn.onclick = () => runCommand(btn.dataset.cmd));
document.querySelectorAll("[data-prefix]").forEach((btn) => btn.onclick = () => runPrefixed(btn.dataset.prefix, btn.dataset.input));
document.querySelectorAll("[data-load-cmd]").forEach((btn) => btn.onclick = () => loadField(btn.dataset.loadCmd, btn.dataset.loadInput, btn.dataset.loadFormat));
const mqttIataSelect = document.getElementById("mqttIata");
if (mqttIataSelect) {
mqttIataSelect.addEventListener("change", refreshMqttIataWarning);
}
async function setEastmeshMode(enabled) {
if (enabled && getLetsmeshMode() === "both") {
await setLetsmeshMode("eu");