diff --git a/eastmesh-build.sh b/eastmesh-build.sh index d49f2cf4..48955a21 100755 --- a/eastmesh-build.sh +++ b/eastmesh-build.sh @@ -216,7 +216,7 @@ build_firmware() { FIRMWARE_FILENAME="$1-${FIRMWARE_VERSION_STRING}" # add build metadata to end of existing platformio build flags in environment vars - export PLATFORMIO_BUILD_FLAGS="${PLATFORMIO_BUILD_FLAGS} -DFIRMWARE_BUILD_DATE='\"${FIRMWARE_BUILD_DATE}\"' -DFIRMWARE_VERSION='\"${FIRMWARE_VERSION}\"' -DCLIENT_VERSION='\"${CLIENT_VERSION_STRING}\"'" + export PLATFORMIO_BUILD_FLAGS="${PLATFORMIO_BUILD_FLAGS} -DFIRMWARE_BUILD_DATE='\"${FIRMWARE_BUILD_DATE}\"' -DFIRMWARE_VERSION='\"${FIRMWARE_VERSION}\"' -DCLIENT_VERSION='\"${CLIENT_VERSION_STRING}\"' -DCLIENT_ENV='\"${1}\"'" # disable debug flags if requested disable_debug_flags diff --git a/eastmesh-docs/api.md b/eastmesh-docs/api.md index b2c08fab..b9e1978c 100644 --- a/eastmesh-docs/api.md +++ b/eastmesh-docs/api.md @@ -166,6 +166,30 @@ curl -sk https:///api/command \ --data 'get wifi.status' ``` +### `POST /api/firmware-update` + +Upload an app firmware `.bin` through the HTTPS web panel and reboot after a successful update. + +Headers: + +```text +X-Auth-Token: +X-Firmware-MD5: <32-character md5> +Content-Type: application/octet-stream +``` + +Request body: + +```text +raw firmware .bin bytes +``` + +Response: + +- `OK` after the firmware is written and the reboot is scheduled +- `401 Unauthorized` if the token is missing or stale +- `400` or `500` if the upload or firmware verification fails + ### `GET /api/stats` Fetch the current summary payload used by the dedicated `/stats` page. diff --git a/eastmesh-docs/custom-cli.md b/eastmesh-docs/custom-cli.md index f2647ba6..4b67c044 100644 --- a/eastmesh-docs/custom-cli.md +++ b/eastmesh-docs/custom-cli.md @@ -37,6 +37,7 @@ No-argument `get` commands must be entered exactly as shown. - `get mqtt.status`: shows Wi-Fi, 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.client_version`: shows the MQTT `client_version` string published by the repeater. +- `get mqtt.client_env`: shows the PlatformIO env used to build the repeater firmware. - `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. diff --git a/eastmesh-docs/web-panel.md b/eastmesh-docs/web-panel.md index 5528ee2e..355fad9f 100644 --- a/eastmesh-docs/web-panel.md +++ b/eastmesh-docs/web-panel.md @@ -150,7 +150,9 @@ It includes: Use `Start OTA` only when you intend to update firmware. -If the browser can reach GitHub, the app page checks the EastMesh release version from `mqtt.client_version` against published MeshCore-EastMesh releases. When a newer EastMesh release tag is available, a firmware update notice appears at the top of the page. +If the browser can reach the EastMesh flasher firmware manifest, the app page checks the EastMesh release version from `mqtt.client_version` against published MeshCore-EastMesh releases. When a newer EastMesh release tag is available, a firmware update notice appears at the top of the page. + +For builds that include `CLIENT_ENV`, the notice can show `Update now`. This downloads the matching non-merged `.bin` from the EastMesh flasher firmware mirror, shows progress in the banner, uploads the firmware through the HTTPS web panel, and lets the device reboot. This requires CORS headers on the flasher `/firmwares/` paths. ### `/stats` diff --git a/examples/simple_repeater/MyMesh.cpp b/examples/simple_repeater/MyMesh.cpp index d4711d53..94760994 100644 --- a/examples/simple_repeater/MyMesh.cpp +++ b/examples/simple_repeater/MyMesh.cpp @@ -2335,6 +2335,8 @@ void MyMesh::handleCommand(uint32_t sender_timestamp, char *command, char *reply mqtt.formatStatusReply(reply, 160); } else if (strcmp(command, "get mqtt.client_version") == 0) { sprintf(reply, "> %s", mqtt.getClientVersion()); + } else if (strcmp(command, "get mqtt.client_env") == 0) { + sprintf(reply, "> %s", mqtt.getClientEnv()); } else if (strcmp(command, "get mqtt.iata") == 0) { sprintf(reply, "> %s", mqtt.getIata()); } else if (strcmp(command, "get mqtt.owner") == 0) { diff --git a/src/helpers/mqtt/MQTTUplink.cpp b/src/helpers/mqtt/MQTTUplink.cpp index 4bef6239..2b39d900 100644 --- a/src/helpers/mqtt/MQTTUplink.cpp +++ b/src/helpers/mqtt/MQTTUplink.cpp @@ -35,6 +35,10 @@ #define CLIENT_VERSION "eastmesh-observer" #endif +#ifndef CLIENT_ENV + #define CLIENT_ENV "unknown" +#endif + #ifndef MQTT_DEBUG #define MQTT_DEBUG 0 #endif @@ -148,6 +152,10 @@ const char* MQTTUplink::getClientVersion() const { return CLIENT_VERSION; } +const char* MQTTUplink::getClientEnv() const { + return CLIENT_ENV; +} + bool MQTTUplink::savePrefs() { return MQTTPrefsStore::save(_fs, _prefs); } diff --git a/src/helpers/mqtt/MQTTUplink.h b/src/helpers/mqtt/MQTTUplink.h index e708ae81..f0c00191 100644 --- a/src/helpers/mqtt/MQTTUplink.h +++ b/src/helpers/mqtt/MQTTUplink.h @@ -57,6 +57,7 @@ public: bool setIata(const char* iata); const char* getIata() const { return _prefs.iata; } const char* getClientVersion() const; + const char* getClientEnv() const; void setNodeNameSource(const char* node_name) { _node_name = node_name; } bool setOwnerPublicKey(const char* owner_public_key); const char* getOwnerPublicKey() const { return _prefs.owner_public_key; } diff --git a/src/helpers/web/WebPanelServer.cpp b/src/helpers/web/WebPanelServer.cpp index dc8b4f2f..8320cf05 100644 --- a/src/helpers/web/WebPanelServer.cpp +++ b/src/helpers/web/WebPanelServer.cpp @@ -6,7 +6,10 @@ #include #include #include +#include +#include #include +#include #include "../mqtt/generated/WebPanelCert.h" @@ -27,6 +30,7 @@ constexpr size_t kWebReplyBufferSize = 256; constexpr size_t kWebStatsQueryBufferSize = 96; constexpr size_t kWebStatsReplyBufferSize = 4608; constexpr size_t kWebPageChunkSize = 768; +constexpr size_t kWebFirmwareChunkSize = 4096; constexpr unsigned long kWebIdleTimeoutMs = WEB_PANEL_IDLE_TIMEOUT_MS; #if defined(MQTT_DEBUG) && MQTT_DEBUG @@ -49,6 +53,11 @@ void freeScratchBuffer(void* ptr) { } } +void rebootAfterFirmwareUpdateTask(void*) { + vTaskDelay(pdMS_TO_TICKS(1200)); + esp_restart(); +} + void bytesToHexUpper(const uint8_t* src, size_t len, char* dst, size_t dst_size) { if (dst_size == 0) { return; @@ -441,7 +450,7 @@ const char kWebPanelAppHtml[] PROGMEM = R"HTML( .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; } - .top-banner a { color:var(--text); font-weight:700; } + .top-banner a { color:var(--text); font-weight:700; margin-left:10px; } .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); } @@ -540,7 +549,7 @@ const char kWebPanelAppHtml[] PROGMEM = R"HTML(
-
View releases Open flasher
+
View releases Open flasher
MQTT IATA needs setting under MQTT Settings.