Prechádzať zdrojové kódy

feat: add mqtt client version to repeater app and cli

Jared Dohrman 3 mesiacov pred
rodič
commit
46f0496865

+ 3 - 2
docs/custom-cli.md

@@ -12,6 +12,7 @@ These commands are available on `*_repeater_mqtt` firmware targets.
 
 
 - `get mqtt.status`: shows WiFi, NTP, IATA, endpoint status, status publishing state, and TX state.
 - `get mqtt.status`: shows WiFi, NTP, IATA, endpoint status, status publishing state, and TX state.
 - `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.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.client_version`: shows the MQTT `client_version` string published by the repeater.
 - `get mqtt.iata`: shows the IATA/location code used in MQTT topics.
 - `get mqtt.iata`: shows the IATA/location code used in MQTT topics.
 - `set mqtt.iata <code>`: sets the IATA/location code, for example `MEL`.
 - `set mqtt.iata <code>`: 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.
 - `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.
@@ -68,7 +69,7 @@ Legacy dotted aliases are also accepted:
 ### Web Panel Controls
 ### Web Panel Controls
 
 
 - `get web`
 - `get web`
-- `get web.status`: shows whether the local HTTPS panel is available. After `start ota`, this reports `web:suspended ota` until the repeater reboots.
+- `get web.status`: shows whether the local HTTPS panel is available.
 - `get web.stats.status`: shows whether the dedicated stats page and history subsystem are enabled, whether recent history is active, whether PSRAM-backed history is available, and whether the SD-backed archive is mounted. When enabled, the history capture now covers supported environment telemetry too, not just the original battery/radio series. GPS-enabled boards also record per-minute satellites samples for the `/stats` history view.
 - `get web.stats.status`: shows whether the dedicated stats page and history subsystem are enabled, whether recent history is active, whether PSRAM-backed history is available, and whether the SD-backed archive is mounted. When enabled, the history capture now covers supported environment telemetry too, not just the original battery/radio series. GPS-enabled boards also record per-minute satellites samples for the `/stats` history view.
 - `set web on|off`
 - `set web on|off`
 - `set.web on|off`: enables or disables the local HTTPS panel.
 - `set.web on|off`: enables or disables the local HTTPS panel.
@@ -122,7 +123,7 @@ Notes:
 - the panel still uses the repeater admin password for access
 - the panel still uses the repeater admin password for access
 - commands run with the same care as if you typed them into the repeater CLI directly
 - commands run with the same care as if you typed them into the repeater CLI directly
 - this is intended for local admin use on a trusted network
 - this is intended for local admin use on a trusted network
-- `start ota` suspends the local repeater web panel until reboot so the OTA HTTP listener can take over port `80`
+- `start ota` releases the local HTTP redirect listener on port `80` so the OTA HTTP listener can take over without stopping the rest of the repeater services
 
 
 ## Companion WiFi Rescue Commands
 ## Companion WiFi Rescue Commands
 
 

+ 11 - 2
docs/web-panel.md

@@ -130,7 +130,7 @@ This section runs common read-only commands for:
 - Wi-Fi
 - Wi-Fi
 - MQTT
 - MQTT
 
 
-These are useful for quick checks without typing into the CLI field.
+These are useful for quick checks without typing into the CLI field. The MQTT quick actions include `mqtt.status`, `mqtt.client_version`, `mqtt.iata`, `mqtt.owner`, and `mqtt.email`.
 
 
 ## Run CLI Command
 ## Run CLI Command
 
 
@@ -149,6 +149,7 @@ This makes it easy to see exactly what the panel sent to the repeater.
 This section includes:
 This section includes:
 
 
 - Device Name
 - Device Name
+- Clock UTC
 - Latitude
 - Latitude
 - Longitude
 - Longitude
 - Guest Password
 - Guest Password
@@ -165,6 +166,14 @@ Notes:
 - the refresh buttons load the current value from the repeater
 - the refresh buttons load the current value from the repeater
 - the save buttons send the matching CLI command immediately
 - the save buttons send the matching CLI command immediately
 
 
+## Info
+
+This section shows:
+
+- `Version`: firmware version with build date
+- `Client Version`: MQTT client version string
+- `Public Key`
+
 ## Ghost Node Mode
 ## Ghost Node Mode
 
 
 Ghost Node Mode is a convenience control on `/app` for a repeater that should stay on Wi-Fi and MQTT, but should not actively behave like another nearby repeater.
 Ghost Node Mode is a convenience control on `/app` for a repeater that should stay on Wi-Fi and MQTT, but should not actively behave like another nearby repeater.
@@ -303,7 +312,7 @@ On mobile:
 
 
 1. Press `Start OTA`.
 1. Press `Start OTA`.
 2. Confirm the action.
 2. Confirm the action.
-3. The local repeater web panel is suspended until reboot so OTA can take over HTTP on port `80`.
+3. The local HTTP redirect listener on port `80` is released so OTA can take over that port.
 4. Continue with your normal OTA workflow.
 4. Continue with your normal OTA workflow.
 
 
 ### Use Historical Stats
 ### Use Historical Stats

+ 2 - 3
examples/simple_repeater/MyMesh.cpp

@@ -2056,9 +2056,6 @@ void MyMesh::clearStats() {
 }
 }
 
 
 void MyMesh::prepareForOTAStart() {
 void MyMesh::prepareForOTAStart() {
-#if defined(ESP_PLATFORM) && WITH_WEB_PANEL
-  web.suspendForOTA();
-#endif
 }
 }
 
 
 void MyMesh::handleCommand(uint32_t sender_timestamp, char *command, char *reply) {
 void MyMesh::handleCommand(uint32_t sender_timestamp, char *command, char *reply) {
@@ -2288,6 +2285,8 @@ void MyMesh::handleCommand(uint32_t sender_timestamp, char *command, char *reply
     sprintf(reply, "> %s", mqtt.isStatusEnabled() ? "on" : "off");
     sprintf(reply, "> %s", mqtt.isStatusEnabled() ? "on" : "off");
   } else if (strcmp(command, "get mqtt.status") == 0) {
   } else if (strcmp(command, "get mqtt.status") == 0) {
     mqtt.formatStatusReply(reply, 160);
     mqtt.formatStatusReply(reply, 160);
+  } else if (strcmp(command, "get mqtt.client_version") == 0) {
+    sprintf(reply, "> %s", mqtt.getClientVersion());
   } else if (memcmp(command, "get mqtt.iata", 13) == 0) {
   } else if (memcmp(command, "get mqtt.iata", 13) == 0) {
     sprintf(reply, "> %s", mqtt.getIata());
     sprintf(reply, "> %s", mqtt.getIata());
   } else if (memcmp(command, "get mqtt.owner", 14) == 0) {
   } else if (memcmp(command, "get mqtt.owner", 14) == 0) {

+ 10 - 1
release-notes.yml

@@ -442,7 +442,16 @@ releases:
       - type: fixed
       - type: fixed
         area: web-panel
         area: web-panel
         text: "Corrected `/stats` packet activity and GPS satellites bar charts so restored zero values no longer draw phantom bars and the final bar uses the same spacing as the rest of the series."
         text: "Corrected `/stats` packet activity and GPS satellites bar charts so restored zero values no longer draw phantom bars and the final bar uses the same spacing as the rest of the series."
+      - type: added
+        area: mqtt
+        text: "Added a `get mqtt.client_version` repeater CLI command and exposed the same value in the `/app` quick MQTT command buttons."
+      - type: changed
+        area: web-panel
+        text: "Reworked the `/app` Info panel to show `Version` and `Client Version`, and moved `Clock UTC` into Repeater Settings beside Device Name."
+      - type: fixed
+        area: web-panel
+        text: "Narrowed `start ota` handling so the repeater only releases the local HTTP redirect listener on port `80`, allowing ElegantOTA to start without unnecessarily stopping the rest of the repeater services."
       - type: docs
       - type: docs
         area: docs
         area: docs
-        text: "Updated the web panel and API docs to describe the board-aware battery percentage display fields used by the repeater stats UI."
+        text: "Updated the web panel, API, and custom CLI docs to describe the board-aware battery display, the new MQTT client-version command, and the revised `/app` layout."
     breaking_changes: []
     breaking_changes: []

+ 11 - 2
src/helpers/ESP32Board.cpp

@@ -12,7 +12,11 @@
 
 
 bool ESP32Board::startOTAUpdate(const char* id, char reply[]) {
 bool ESP32Board::startOTAUpdate(const char* id, char reply[]) {
   inhibit_sleep = true;   // prevent sleep during OTA
   inhibit_sleep = true;   // prevent sleep during OTA
-  WiFi.softAP("MeshCore-OTA", NULL);
+  WiFi.mode(WIFI_AP_STA);
+  if (!WiFi.softAP("MeshCore-OTA", NULL)) {
+    strcpy(reply, "Error - OTA AP start failed");
+    return false;
+  }
 
 
   sprintf(reply, "Started: http://%s/update", WiFi.softAPIP().toString().c_str());
   sprintf(reply, "Started: http://%s/update", WiFi.softAPIP().toString().c_str());
   MESH_DEBUG_PRINTLN("startOTAUpdate: %s", reply);
   MESH_DEBUG_PRINTLN("startOTAUpdate: %s", reply);
@@ -22,7 +26,12 @@ bool ESP32Board::startOTAUpdate(const char* id, char reply[]) {
   static char home_buf[90];
   static char home_buf[90];
   sprintf(home_buf, "<H2>Hi! I am a MeshCore Repeater. ID: %s</H2>", id);
   sprintf(home_buf, "<H2>Hi! I am a MeshCore Repeater. ID: %s</H2>", id);
 
 
-  AsyncWebServer* server = new AsyncWebServer(80);
+  static AsyncWebServer* server = nullptr;
+  if (server != nullptr) {
+    delete server;
+    server = nullptr;
+  }
+  server = new AsyncWebServer(80);
 
 
   server->on("/", HTTP_GET, [](AsyncWebServerRequest *request) {
   server->on("/", HTTP_GET, [](AsyncWebServerRequest *request) {
     request->send(200, "text/html", home_buf);
     request->send(200, "text/html", home_buf);

+ 4 - 0
src/helpers/mqtt/MQTTUplink.cpp

@@ -116,6 +116,10 @@ MQTTUplink::MQTTUplink(mesh::RTCClock& rtc, mesh::LocalIdentity& identity)
   MQTT_LOG("uplink init");
   MQTT_LOG("uplink init");
 }
 }
 
 
+const char* MQTTUplink::getClientVersion() const {
+  return CLIENT_VERSION;
+}
+
 bool MQTTUplink::savePrefs() {
 bool MQTTUplink::savePrefs() {
   return MQTTPrefsStore::save(_fs, _prefs);
   return MQTTPrefsStore::save(_fs, _prefs);
 }
 }

+ 1 - 0
src/helpers/mqtt/MQTTUplink.h

@@ -55,6 +55,7 @@ public:
   bool isTxEnabled() const { return _prefs.tx_enabled != 0; }
   bool isTxEnabled() const { return _prefs.tx_enabled != 0; }
   bool setIata(const char* iata);
   bool setIata(const char* iata);
   const char* getIata() const { return _prefs.iata; }
   const char* getIata() const { return _prefs.iata; }
+  const char* getClientVersion() const;
   void setNodeNameSource(const char* node_name) { _node_name = node_name; }
   void setNodeNameSource(const char* node_name) { _node_name = node_name; }
   bool setOwnerPublicKey(const char* owner_public_key);
   bool setOwnerPublicKey(const char* owner_public_key);
   const char* getOwnerPublicKey() const { return _prefs.owner_public_key; }
   const char* getOwnerPublicKey() const { return _prefs.owner_public_key; }

+ 24 - 15
src/helpers/web/WebPanelServer.cpp

@@ -576,6 +576,7 @@ const char kWebPanelAppHtml[] PROGMEM = R"HTML(
           <span class="label">MQTT</span>
           <span class="label">MQTT</span>
           <div class="quick">
           <div class="quick">
             <button data-cmd="get mqtt.status">mqtt.status</button>
             <button data-cmd="get mqtt.status">mqtt.status</button>
+            <button data-cmd="get mqtt.client_version">mqtt.client_version</button>
             <button data-cmd="get mqtt.iata">mqtt.iata</button>
             <button data-cmd="get mqtt.iata">mqtt.iata</button>
             <button data-cmd="get mqtt.owner">mqtt.owner</button>
             <button data-cmd="get mqtt.owner">mqtt.owner</button>
             <button data-cmd="get mqtt.email">mqtt.email</button>
             <button data-cmd="get mqtt.email">mqtt.email</button>
@@ -599,18 +600,15 @@ const char kWebPanelAppHtml[] PROGMEM = R"HTML(
       <div class="stack">
       <div class="stack">
         <div class="row">
         <div class="row">
           <div class="field-card">
           <div class="field-card">
-            <label class="label" for="roleValue">Role</label>
+            <label class="label" for="versionValue">Version</label>
             <div class="fieldline">
             <div class="fieldline">
-              <input id="roleValue" readonly disabled>
-              <span class="placeholder-slot" aria-hidden="true"></span>
+              <input id="versionValue" readonly disabled>
             </div>
             </div>
           </div>
           </div>
           <div class="field-card">
           <div class="field-card">
-            <label class="label" for="clockUtc">Clock UTC</label>
-            <div class="inline-actions">
-              <input id="clockUtc" readonly disabled>
-              <button class="iconbtn" data-load-cmd="clock" data-load-input="clockUtc" title="Refresh clock UTC">&#8635;</button>
-              <button id="syncClockBtn" class="savebtn">Sync</button>
+            <label class="label" for="clientVersionValue">Client Version</label>
+            <div class="fieldline">
+              <input id="clientVersionValue" readonly disabled>
             </div>
             </div>
           </div>
           </div>
         </div>
         </div>
@@ -629,12 +627,22 @@ const char kWebPanelAppHtml[] PROGMEM = R"HTML(
       <div class="stack">
       <div class="stack">
         <div class="section-group">
         <div class="section-group">
           <h3>Repeater Settings</h3>
           <h3>Repeater Settings</h3>
-          <div class="field-card">
-            <label class="label" for="nodeName">Device Name</label>
-            <div class="inline-actions">
-              <input id="nodeName" placeholder="MeshCore-HOWL">
-              <button class="iconbtn" data-load-cmd="get name" data-load-input="nodeName" title="Refresh device name">&#8635;</button>
-              <button class="savebtn" data-prefix="set name " data-input="nodeName">Save</button>
+          <div class="row">
+            <div class="field-card">
+              <label class="label" for="nodeName">Device Name</label>
+              <div class="inline-actions">
+                <input id="nodeName" placeholder="MeshCore-HOWL">
+                <button class="iconbtn" data-load-cmd="get name" data-load-input="nodeName" title="Refresh device name">&#8635;</button>
+                <button class="savebtn" data-prefix="set name " data-input="nodeName">Save</button>
+              </div>
+            </div>
+            <div class="field-card">
+              <label class="label" for="clockUtc">Clock UTC</label>
+              <div class="inline-actions">
+                <input id="clockUtc" readonly disabled>
+                <button class="iconbtn" data-load-cmd="clock" data-load-input="clockUtc" title="Refresh clock UTC">&#8635;</button>
+                <button id="syncClockBtn" class="savebtn">Sync</button>
+              </div>
             </div>
             </div>
           </div>
           </div>
           <div class="row">
           <div class="row">
@@ -2531,7 +2539,8 @@ const char kWebPanelAppHtml[] PROGMEM = R"HTML(
       const quiet = { recordHistory:false, updateInput:false };
       const quiet = { recordHistory:false, updateInput:false };
       try {
       try {
         await loadSection("Loading info...", [
         await loadSection("Loading info...", [
-          () => loadField("get role", "roleValue", null, quiet),
+          () => loadField("ver", "versionValue", null, quiet),
+          () => loadField("get mqtt.client_version", "clientVersionValue", null, quiet),
           () => loadField("clock", "clockUtc", null, quiet),
           () => loadField("clock", "clockUtc", null, quiet),
           () => loadField("get public.key", "publicKey", "uppercase", quiet)
           () => loadField("get public.key", "publicKey", "uppercase", quiet)
         ]);
         ]);