diff --git a/CNAME b/CNAME deleted file mode 100644 index 1b7d1671..00000000 --- a/CNAME +++ /dev/null @@ -1 +0,0 @@ -docs.meshcore.nz \ No newline at end of file diff --git a/docs/_assets/meshcore_tm.svg b/docs/_assets/meshcore_tm.svg deleted file mode 100644 index b7e252d9..00000000 --- a/docs/_assets/meshcore_tm.svg +++ /dev/null @@ -1,14 +0,0 @@ - - - - - - - - - - - - - - diff --git a/docs/custom-cli.md b/docs/custom-cli.md index 7e2a358a..56b6f549 100644 --- a/docs/custom-cli.md +++ b/docs/custom-cli.md @@ -11,7 +11,7 @@ These commands are available on `*_repeater_mqtt` firmware targets. ### MQTT Status And Routing - `get mqtt.status` - - shows WiFi, NTP, IATA, endpoint status, and TX state + - shows WiFi, NTP, IATA, endpoint status, status publishing state, and TX state - `get mqtt.statuscfg` - shows whether periodic status messages are enabled - `get mqtt.iata` @@ -107,6 +107,7 @@ When the repeater web panel is enabled, it only allows a limited command set. That allowlist currently includes: +- `clock` - `get mqtt.status` - `get web` - `get web.status` diff --git a/docs/web-panel.md b/docs/web-panel.md index 479764f7..20a5ec5e 100644 --- a/docs/web-panel.md +++ b/docs/web-panel.md @@ -90,6 +90,7 @@ This is a small terminal for allowlisted commands. - press `Enter` to run the command - command history is shown in the terminal box below - save buttons elsewhere in the page also show the generated command and the reply here +- `clock` is available here if you want to check the repeater's current board time This makes it easy to see exactly what the panel sent to the repeater. @@ -201,7 +202,7 @@ Use the repeater admin password, not the guest password. ### A command says it is not allowlisted -The panel intentionally limits what can be run from the browser. Use the serial CLI for commands outside the web allowlist. +The panel intentionally limits what can be run from the browser. Use the serial CLI for commands outside the web allowlist. `clock` is included, but most maintenance and debug commands are still serial-only. ### Stats or settings do not refresh diff --git a/examples/simple_repeater/MyMesh.cpp b/examples/simple_repeater/MyMesh.cpp index 827d16dd..e5ab7ca7 100644 --- a/examples/simple_repeater/MyMesh.cpp +++ b/examples/simple_repeater/MyMesh.cpp @@ -1317,7 +1317,15 @@ void MyMesh::handleCommand(uint32_t sender_timestamp, char *command, char *reply } else { strcpy(reply, "Err - bad mqtt.email"); } - } else if (memcmp(command, "get mqtt.status", 15) == 0) { + } else if (strcmp(command, "send mqtt.status") == 0) { + if (mqtt.sendStatusNow()) { + strcpy(reply, "OK"); + } else { + strcpy(reply, "Err - mqtt status unavailable"); + } + } else if (strcmp(command, "get mqtt.statuscfg") == 0) { + sprintf(reply, "> %s", mqtt.isStatusEnabled() ? "on" : "off"); + } else if (strcmp(command, "get mqtt.status") == 0) { mqtt.formatStatusReply(reply, 160); } else if (strcmp(command, "get web.status") == 0 || strcmp(command, "get web") == 0) { mqtt.formatWebStatusReply(reply, 160); @@ -1337,8 +1345,6 @@ void MyMesh::handleCommand(uint32_t sender_timestamp, char *command, char *reply sprintf(reply, "> %s", mqtt.isPacketsEnabled() ? "on" : "off"); } else if (memcmp(command, "get mqtt.raw", 12) == 0) { sprintf(reply, "> %s", mqtt.isRawEnabled() ? "on" : "off"); - } else if (memcmp(command, "get mqtt.statuscfg", 18) == 0) { - sprintf(reply, "> %s", mqtt.isStatusEnabled() ? "on" : "off"); } else if (memcmp(command, "get mqtt.tx", 11) == 0) { sprintf(reply, "> %s", mqtt.isTxEnabled() ? "on" : "off"); } else if (memcmp(command, "get mqtt.eastmesh-au", 20) == 0 || memcmp(command, "get mqtt.eastmesh.au", 20) == 0) { @@ -1436,6 +1442,7 @@ void MyMesh::runWebCommand(const char* command, char* reply, size_t reply_size) }; bool allowed = + matches_exact("clock") || matches_exact("get mqtt.status") || matches_exact("get web.status") || matches_exact("get web") || diff --git a/src/helpers/mqtt/MQTTPrefs.cpp b/src/helpers/mqtt/MQTTPrefs.cpp index a9b1173f..8076a137 100644 --- a/src/helpers/mqtt/MQTTPrefs.cpp +++ b/src/helpers/mqtt/MQTTPrefs.cpp @@ -44,14 +44,20 @@ bool MQTTPrefsStore::load(FILESYSTEM* fs, MQTTPrefs& prefs) { return false; } - size_t bytes_to_read = min(static_cast(file.size()), sizeof(prefs)); - bool ok = bytes_to_read >= sizeof(prefs.magic) && - file.read(reinterpret_cast(&prefs), bytes_to_read) == bytes_to_read; + MQTTPrefs persisted{}; + size_t bytes_to_read = min(static_cast(file.size()), sizeof(persisted)); + bool ok = bytes_to_read >= sizeof(persisted.magic) && + file.read(reinterpret_cast(&persisted), bytes_to_read) == bytes_to_read; file.close(); - if (!ok || prefs.magic != kMagic) { + if (!ok || persisted.magic != kMagic) { + // Legacy or corrupt MQTT prefs should not survive migration. Remove the + // stale file and persist a clean current-format default copy immediately. + fs->remove(kFilename); + save(fs, prefs); return false; } + prefs = persisted; if (prefs.wifi_powersave > 2) { prefs.wifi_powersave = 0; } diff --git a/src/helpers/mqtt/MQTTUplink.cpp b/src/helpers/mqtt/MQTTUplink.cpp index 80abf71d..92a51191 100644 --- a/src/helpers/mqtt/MQTTUplink.cpp +++ b/src/helpers/mqtt/MQTTUplink.cpp @@ -164,6 +164,27 @@ void MQTTUplink::reconnectWifi() { _last_wifi_attempt = 0; } +bool MQTTUplink::sendStatusNow() { + if (!_running || !_have_time_sync || WiFi.status() != WL_CONNECTED) { + return false; + } + + bool any_connected = false; + for (const BrokerState& broker : _brokers) { + if ((_prefs.enabled_mask & broker.spec->bit) != 0 && broker.connected && broker.client != nullptr) { + any_connected = true; + break; + } + } + if (!any_connected) { + return false; + } + + publishStatus(true); + _last_status_publish = millis(); + return true; +} + void MQTTUplink::makeSafeToken(const char* input, char* output, size_t output_size) { if (output_size == 0) { return; @@ -334,6 +355,7 @@ void MQTTUplink::destroyBroker(BrokerState& broker) { broker.client = nullptr; } broker.connected = false; + broker.connect_announced = false; } void MQTTUplink::queuePublish(BrokerState& broker, const char* topic, const char* payload, bool retain) { @@ -728,10 +750,13 @@ void MQTTUplink::loop(const MQTTStatusSnapshot& snapshot) { ensureWebServer(); for (BrokerState& broker : _brokers) { - bool was_connected = broker.connected; ensureBroker(broker); - if (!was_connected && broker.connected) { + if (broker.connected && !broker.connect_announced) { publishOnlineStatus(broker); + broker.connect_announced = true; + _last_status_publish = millis(); + } else if (!broker.connected) { + broker.connect_announced = false; } } @@ -808,10 +833,10 @@ void MQTTUplink::formatStatusReply(char* reply, size_t reply_size) const { return "retry"; }; - snprintf(reply, reply_size, "> wifi:%s ntp:%s iata:%s eastmesh-au:%s letsmesh-eu:%s letsmesh-us:%s tx:%s", + snprintf(reply, reply_size, "> wifi:%s ntp:%s iata:%s eastmesh-au:%s letsmesh-eu:%s letsmesh-us:%s status:%s tx:%s", getWifiStateLabel(_prefs, _wifi_started), _have_time_sync ? "up" : "wait", _prefs.iata, broker_state(_brokers[0]), broker_state(_brokers[1]), broker_state(_brokers[2]), - _prefs.tx_enabled ? "on" : "off"); + _prefs.status_enabled ? "on" : "off", _prefs.tx_enabled ? "on" : "off"); } void MQTTUplink::formatWebStatusReply(char* reply, size_t reply_size) const { @@ -877,9 +902,7 @@ bool MQTTUplink::setWebEnabled(bool enabled) { #if WITH_WEB_PANEL _prefs.web_enabled = enabled ? 1 : 0; bool ok = savePrefs(); - if (_prefs.web_enabled == 0) { - stopWebServer(); - } else { + if (_prefs.web_enabled != 0) { ensureWebServer(); } return ok; @@ -960,9 +983,7 @@ bool MQTTUplink::setOwnerPublicKey(const char* owner_public_key) { if (owner_public_key[0] == 0) { _prefs.owner_public_key[0] = 0; - bool ok = savePrefs(); - reconnectWifi(); - return ok; + return savePrefs(); } if (strlen(owner_public_key) != 64) { @@ -976,9 +997,7 @@ bool MQTTUplink::setOwnerPublicKey(const char* owner_public_key) { _prefs.owner_public_key[i] = toupper(static_cast(owner_public_key[i])); } _prefs.owner_public_key[64] = 0; - bool ok = savePrefs(); - reconnectWifi(); - return ok; + return savePrefs(); } bool MQTTUplink::setOwnerEmail(const char* owner_email) { @@ -986,9 +1005,7 @@ bool MQTTUplink::setOwnerEmail(const char* owner_email) { return false; } StrHelper::strncpy(_prefs.owner_email, owner_email, sizeof(_prefs.owner_email)); - bool ok = savePrefs(); - reconnectWifi(); - return ok; + return savePrefs(); } void MQTTUplink::formatWifiStatusReply(char* reply, size_t reply_size) const { @@ -1077,6 +1094,7 @@ bool MQTTUplink::setOwnerPublicKey(const char*) { return false; } bool MQTTUplink::setOwnerEmail(const char*) { return false; } void MQTTUplink::formatWifiStatusReply(char* reply, size_t reply_size) const { snprintf(reply, reply_size, "> unsupported"); } void MQTTUplink::reconnectWifi() {} +bool MQTTUplink::sendStatusNow() { return false; } #endif diff --git a/src/helpers/mqtt/MQTTUplink.h b/src/helpers/mqtt/MQTTUplink.h index 3b4191ea..b8a4cba2 100644 --- a/src/helpers/mqtt/MQTTUplink.h +++ b/src/helpers/mqtt/MQTTUplink.h @@ -77,6 +77,7 @@ public: const char* getOwnerEmail() const { return _prefs.owner_email; } void formatWifiStatusReply(char* reply, size_t reply_size) const; void reconnectWifi(); + bool sendStatusNow(); private: #if defined(ESP_PLATFORM) @@ -92,6 +93,7 @@ private: const BrokerSpec* spec; esp_mqtt_client_handle_t client; bool connected; + bool connect_announced; unsigned long last_connect_attempt; time_t token_expires_at; char username[70]; diff --git a/src/helpers/web/WebPanelServer.cpp b/src/helpers/web/WebPanelServer.cpp index 79275c73..36c19cd4 100644 --- a/src/helpers/web/WebPanelServer.cpp +++ b/src/helpers/web/WebPanelServer.cpp @@ -159,13 +159,13 @@ const char kWebPanelHtml[] PROGMEM = R"HTML( --button-secondary-text:#e6eaf0; } html { min-height:100%; background:linear-gradient(180deg,var(--background),var(--surface2)); background-repeat:no-repeat; background-attachment:fixed; } - body { min-height:100vh; margin:0; font:16px/1.4 ui-monospace,SFMono-Regular,Menlo,monospace; background:transparent; color:var(--text); transition:background .2s ease,color .2s ease; } + body { min-height:100vh; margin:0; background:transparent; color:var(--text); font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono",monospace; font-size:16px; line-height:1.4; transition:background .2s ease,color .2s ease; } main { max-width:920px; margin:0 auto; padding:24px; } - .theme-fab { position:fixed; top:18px; right:18px; width:48px; height:48px; border-radius:999px; display:flex; align-items:center; justify-content:center; z-index:20; box-shadow:0 12px 28px rgba(0,0,0,.18); font-size:22px; line-height:1; } .card { background:var(--card-bg); border:1px solid var(--border); border-radius:14px; padding:18px; margin-bottom:18px; } h1,h2,h3 { margin:0 0 12px; font-size:18px; } p { color:var(--text-muted); margin:8px 0 0; } - input, textarea, button, select { width:100%; box-sizing:border-box; border-radius:10px; border:1px solid var(--border); background:var(--input-bg); color:var(--text); padding:12px; font:inherit; } + input:not([type="checkbox"]), textarea, button, select { width:100%; min-height:44px; box-sizing:border-box; border-radius:10px; border:1px solid var(--border); background:var(--input-bg); color:var(--text); padding:12px; font-family:inherit; font-size:inherit; line-height:inherit; } + input:not([type="checkbox"]), textarea, select, button { -webkit-appearance:none; appearance:none; } textarea { min-height:100px; resize:vertical; } button { width:auto; cursor:pointer; background:var(--accent); color:var(--button-text); border:none; font-weight:700; transition:background .2s ease,color .2s ease,border-color .2s ease; } button:hover { background:var(--accent-hover); } @@ -187,12 +187,26 @@ const char kWebPanelHtml[] PROGMEM = R"HTML( .label { font-size:12px; color:var(--text-muted); margin-bottom:6px; display:block; } .fieldline { display:grid; grid-template-columns:1fr auto; gap:8px; align-items:center; } .iconbtn { width:44px; padding:12px 0; } + .placeholder-slot { display:block; width:44px; height:44px; } .savebtn { width:100%; background:var(--accent); color:var(--button-text); border:none; } .savebtn:hover { background:var(--accent-hover); } + .broker-grid { display:grid; grid-template-columns:repeat(3,minmax(0,1fr)); gap:10px; } + .broker-card { background:var(--surface2); border:1px solid var(--border); border-radius:12px; padding:12px; display:grid; gap:10px; } + .broker-row { display:flex; align-items:center; justify-content:space-between; gap:12px; } + .broker-copy { display:grid; gap:4px; min-width:0; } + .broker-title { font-size:12px; color:var(--text-muted); text-transform:uppercase; letter-spacing:.06em; } + .broker-state { font-size:13px; color:var(--text); } + .broker-state.on { color:var(--accent); } + .switch { position:relative; display:inline-flex; width:54px; height:32px; flex:0 0 auto; } + .switch input { position:absolute; opacity:0; width:0; height:0; min-height:0; padding:0; border:0; -webkit-appearance:none; appearance:none; } + .slider { position:absolute; inset:0; border-radius:999px; background:var(--background); border:1px solid var(--border); transition:background .2s ease,border-color .2s ease; cursor:pointer; } + .slider::before { content:""; position:absolute; width:24px; height:24px; left:3px; top:3px; border-radius:50%; background:var(--surface1); box-shadow:0 2px 8px rgba(0,0,0,.18); transition:transform .2s ease,background .2s ease; } + .switch input:checked + .slider { background:var(--accent); border-color:transparent; } + .switch input:checked + .slider::before { transform:translateX(22px); background:#fff; } + .panel-warning { min-height:1.4em; font-size:13px; color:var(--status-red); } .themebtn { padding:10px 14px; } - #app { display:none; } #status { white-space:pre-wrap; color:var(--text-muted); min-height:1.4em; } - .terminal { background:var(--terminal-bg); border:1px solid var(--terminal-border); border-radius:12px; padding:14px; min-height:180px; max-height:320px; overflow:auto; font:14px/1.45 ui-monospace,SFMono-Regular,Menlo,monospace; } + .terminal { background:var(--terminal-bg); border:1px solid var(--terminal-border); border-radius:12px; padding:14px; min-height:180px; max-height:320px; overflow:auto; font-family:inherit; font-size:14px; line-height:1.45; } .term-entry { margin:0 0 12px; } .term-cmd { color:var(--terminal-cmd); } .term-out { white-space:pre-wrap; color:var(--text); } @@ -231,7 +245,7 @@ const char kWebPanelHtml[] PROGMEM = R"HTML( body { font-size:15px; } main { padding:16px; } .card { padding:16px; margin-bottom:14px; } - .row, .row3, .row-command, .metric-grid, .hud-grid-1, .hud-grid-2, .hud-grid-3, .core-grid, .core-metrics { grid-template-columns:1fr; } + .row, .row3, .row-command, .metric-grid, .hud-grid-1, .hud-grid-2, .hud-grid-3, .core-grid, .core-metrics, .broker-grid { grid-template-columns:1fr; } .inline-actions { grid-template-columns:minmax(0,1fr) auto auto; } .fieldline { grid-template-columns:minmax(0,1fr) auto; align-items:center; } .row-command button { width:100%; } @@ -247,6 +261,7 @@ const char kWebPanelHtml[] PROGMEM = R"HTML( .actions-group.center { grid-template-columns:1fr; justify-items:center; } #actionsPanel .actions-group button { width:100%; } #actionsPanel .actions-group.center button { width:auto; min-width:56px; } + .broker-row { gap:10px; } .row > div + div, .row3 > div + div { margin-top:4px; } .row > div[style*="align-self:end"] { padding-top:4px; } } @@ -303,7 +318,7 @@ const char kWebPanelHtml[] PROGMEM = R"HTML( - + Run CLI Command @@ -350,7 +365,7 @@ const char kWebPanelHtml[] PROGMEM = R"HTML( Guest Password - + Save @@ -358,7 +373,7 @@ const char kWebPanelHtml[] PROGMEM = R"HTML( Private Key - + Save Changing the private key requires a reboot to apply. @@ -488,16 +503,58 @@ const char kWebPanelHtml[] PROGMEM = R"HTML( Save - - MQTT Email - - - ↻ - Save - - - - + + MQTT Email + + + ↻ + Save + + + + MQTT Servers + + + + + EastMesh AU + Off + + + + + + + + + + + LetsMesh EU + Off + + + + + + + + + + + LetsMesh US + Off + + + + + + + + + + + + Stats @@ -846,16 +903,44 @@ const char kWebPanelHtml[] PROGMEM = R"HTML( return JSON.parse(text); }); } - function applyBootstrapData(data) { - if (!data) return; - if (typeof data.name === "string") document.getElementById("nodeName").value = data.name; - if (typeof data.mqtt_iata === "string" && data.mqtt_iata.length) document.getElementById("mqttIata").value = data.mqtt_iata; - if (typeof data.mqtt_owner === "string") document.getElementById("mqttOwner").value = data.mqtt_owner; - if (typeof data.mqtt_email === "string") document.getElementById("mqttEmail").value = data.mqtt_email; - if (typeof data.advert_interval === "string") document.getElementById("advertInterval").value = data.advert_interval; - if (typeof data.flood_interval === "string") document.getElementById("floodInterval").value = data.flood_interval; - if (typeof data.flood_max === "string") document.getElementById("floodMax").value = data.flood_max; - } + function applyBootstrapData(data) { + if (!data) return; + if (typeof data.name === "string") document.getElementById("nodeName").value = data.name; + if (typeof data.mqtt_iata === "string" && data.mqtt_iata.length) document.getElementById("mqttIata").value = data.mqtt_iata; + if (typeof data.mqtt_owner === "string") document.getElementById("mqttOwner").value = data.mqtt_owner; + if (typeof data.mqtt_email === "string") document.getElementById("mqttEmail").value = data.mqtt_email; + if (typeof data.advert_interval === "string") document.getElementById("advertInterval").value = data.advert_interval; + if (typeof data.flood_interval === "string") document.getElementById("floodInterval").value = data.flood_interval; + if (typeof data.flood_max === "string") document.getElementById("floodMax").value = data.flood_max; + if (typeof data.mqtt_eastmesh_au === "string") setBrokerToggle("mqttEastmeshAu", data.mqtt_eastmesh_au); + if (typeof data.mqtt_letsmesh_eu === "string") setBrokerToggle("mqttLetsmeshEu", data.mqtt_letsmesh_eu); + if (typeof data.mqtt_letsmesh_us === "string") setBrokerToggle("mqttLetsmeshUs", data.mqtt_letsmesh_us); + updateBrokerWarning(); + } + function setBrokerToggle(inputId, state) { + const input = document.getElementById(inputId); + if (!input) return; + const enabled = state === "on"; + input.checked = enabled; + const label = document.getElementById(inputId + "State"); + if (label) { + label.textContent = enabled ? "On" : "Off"; + label.classList.toggle("on", enabled); + } + } + function updateBrokerWarning() { + const enabled = ["mqttEastmeshAu", "mqttLetsmeshEu", "mqttLetsmeshUs"].filter((inputId) => { + const input = document.getElementById(inputId); + return input && input.checked; + }).length; + document.getElementById("mqttBrokerWarning").textContent = enabled >= 3 ? "Running all 3 MQTT servers is not recommended. Prefer 2 at most." : ""; + } + async function loadBrokerState(cmd, inputId) { + const result = await runCommand(cmd); + if (!result.ok) return; + setBrokerToggle(inputId, parseReplyValue(result.text)); + updateBrokerWarning(); + } async function refreshStats() { const getStatsBtn = document.getElementById("getStatsBtn"); getStatsBtn.disabled = true; @@ -898,19 +983,22 @@ const char kWebPanelHtml[] PROGMEM = R"HTML( } token = text.trim(); showAuthedUi(true); - try { - applyBootstrapData(await fetchJson("/api/bootstrap")); - } catch (_) { - await Promise.all([ - loadField("get name", "nodeName"), - loadField("get mqtt.iata", "mqttIata"), - loadField("get mqtt.owner", "mqttOwner"), - loadField("get mqtt.email", "mqttEmail"), - loadField("get advert.interval", "advertInterval"), - loadField("get flood.advert.interval", "floodInterval"), - loadField("get flood.max", "floodMax") - ]); - } + try { + applyBootstrapData(await fetchJson("/api/bootstrap")); + } catch (_) { + await Promise.all([ + loadField("get name", "nodeName"), + loadField("get mqtt.iata", "mqttIata"), + loadField("get mqtt.owner", "mqttOwner"), + loadField("get mqtt.email", "mqttEmail"), + loadBrokerState("get mqtt.eastmesh-au", "mqttEastmeshAu"), + loadBrokerState("get mqtt.letsmesh-eu", "mqttLetsmeshEu"), + loadBrokerState("get mqtt.letsmesh-us", "mqttLetsmeshUs"), + loadField("get advert.interval", "advertInterval"), + loadField("get flood.advert.interval", "floodInterval"), + loadField("get flood.max", "floodMax") + ]); + } }; document.getElementById("password").addEventListener("keydown", (event) => { if (event.key === "Enter") { @@ -926,13 +1014,22 @@ const char kWebPanelHtml[] PROGMEM = R"HTML( document.getElementById("runBtn").click(); } }); - 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)); - document.getElementById("saveOwnerInfo").onclick = () => { - const value = document.getElementById("ownerInfo").value.replace(/\n/g, "|"); - runCommand("set owner.info " + value); - }; + 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)); + document.querySelectorAll("[data-broker-on]").forEach((input) => input.addEventListener("change", () => { + const label = document.getElementById(input.id + "State"); + if (label) { + label.textContent = input.checked ? "On" : "Off"; + label.classList.toggle("on", input.checked); + } + updateBrokerWarning(); + runCommand(input.checked ? input.dataset.brokerOn : input.dataset.brokerOff); + })); + document.getElementById("saveOwnerInfo").onclick = () => { + const value = document.getElementById("ownerInfo").value.replace(/\n/g, "|"); + runCommand("set owner.info " + value); + }; document.getElementById("rebootBtn").onclick = async () => { if (confirm("Reboot the repeater now?")) { await runCommand("reboot"); @@ -1127,15 +1224,18 @@ esp_err_t WebPanelServer::handleBootstrap(httpd_req_t* req) { const struct { const char* key; const char* command; - } fields[] = { - {"name", "get name"}, - {"mqtt_iata", "get mqtt.iata"}, - {"mqtt_owner", "get mqtt.owner"}, - {"mqtt_email", "get mqtt.email"}, - {"advert_interval", "get advert.interval"}, - {"flood_interval", "get flood.advert.interval"}, - {"flood_max", "get flood.max"}, - }; + } fields[] = { + {"name", "get name"}, + {"mqtt_iata", "get mqtt.iata"}, + {"mqtt_owner", "get mqtt.owner"}, + {"mqtt_email", "get mqtt.email"}, + {"mqtt_eastmesh_au", "get mqtt.eastmesh-au"}, + {"mqtt_letsmesh_eu", "get mqtt.letsmesh-eu"}, + {"mqtt_letsmesh_us", "get mqtt.letsmesh-us"}, + {"advert_interval", "get advert.interval"}, + {"flood_interval", "get flood.advert.interval"}, + {"flood_max", "get flood.max"}, + }; size_t offset = 0; json[offset++] = '{'; diff --git a/variants/eastmesh_mqtt/platformio.ini b/variants/eastmesh_mqtt/platformio.ini index ac425f1c..6bf75c79 100644 --- a/variants/eastmesh_mqtt/platformio.ini +++ b/variants/eastmesh_mqtt/platformio.ini @@ -464,6 +464,7 @@ build_flags = -D WIFI_PWD='"mypwd"' -D MQTT_DEFAULT_IATA='"MEL"' -D CONFIG_MBEDTLS_CERTIFICATE_BUNDLE=1 + -D ARDUINO_USB_CDC_ON_BOOT=1 build_src_filter = ${heltec_v4_oled.build_src_filter} + +
Changing the private key requires a reboot to apply.