From f886bb32446f84a03c61f8a5fe86b024e7a3175d Mon Sep 17 00:00:00 2001 From: Jared Dohrman Date: Tue, 28 Apr 2026 20:07:49 +1000 Subject: [PATCH 1/6] fix: hide inactive GPS stats --- docs/custom-cli.md | 2 +- docs/web-panel.md | 6 +++--- examples/simple_repeater/MyMesh.cpp | 9 ++++++--- release-notes.yml | 12 ++++++++++++ 4 files changed, 22 insertions(+), 7 deletions(-) diff --git a/docs/custom-cli.md b/docs/custom-cli.md index 4469651c..0da69ef0 100644 --- a/docs/custom-cli.md +++ b/docs/custom-cli.md @@ -70,7 +70,7 @@ Legacy dotted aliases are also accepted: - `get web` - `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-active boards also record per-minute satellites samples for the `/stats` history view. - `set web on|off` - `set.web on|off`: enables or disables the local HTTPS panel. - `set web.stats on|off` diff --git a/docs/web-panel.md b/docs/web-panel.md index 3eb2d7ba..3d983831 100644 --- a/docs/web-panel.md +++ b/docs/web-panel.md @@ -229,13 +229,13 @@ The `/stats` page currently shows: - `Services`: MQTT, web, archive, neighbour count, and, when mounted, card and archive capacity - optional full-width `Environment` summary card on boards that report GPS or environmental telemetry -- `Trends`: battery, heap free, packet activity, signal, noise floor, and, when GPS is enabled, satellites +- `Trends`: battery, heap free, packet activity, signal, noise floor, and, when GPS is active, satellites - `Neighbours`: current neighbour table with ID, SNR, heard age, and advert age - `Events`: current boot/session events Planned MQTT JWT rotations are shown as `mqtt_token_refreshed` events instead of a short `mqtt_disconnected` / `mqtt_connected` pair. -For boards that expose extra telemetry, the optional `Environment` summary card can show current values such as GPS fix state, latitude, longitude, GPS altitude, voltage, sensor temperature, humidity, barometer, pressure-derived altitude, and MCU temperature. +For boards that expose extra telemetry, the optional `Environment` summary card can show current values such as GPS active/fix state, latitude, longitude, GPS altitude, voltage, sensor temperature, humidity, barometer, pressure-derived altitude, and MCU temperature. Metrics with no current value are hidden rather than showing placeholder rows, so the cards vary by board and by current sensor state. @@ -248,7 +248,7 @@ The trend graphs load sequentially rather than as one large payload: 3. memory 4. packet activity 5. signal -6. satellites when GPS is enabled +6. satellites when GPS is active This keeps browser-side and device-side memory use lower than the previous in-page stats view. diff --git a/examples/simple_repeater/MyMesh.cpp b/examples/simple_repeater/MyMesh.cpp index fb6a6586..4b2a0714 100644 --- a/examples/simple_repeater/MyMesh.cpp +++ b/examples/simple_repeater/MyMesh.cpp @@ -55,10 +55,13 @@ WebSensorSnapshot collectWebSensorSnapshot(mesh::MainBoard& board, SensorManager LocationProvider* location = sensors.getLocationProvider(); if (location != nullptr) { snapshot.has_gps = true; - snapshot.gps_enabled = location->isEnabled(); - snapshot.gps_fix = location->isValid(); + const bool provider_enabled = location->isEnabled(); + const char* gps_setting = sensors.getSettingByKey("gps"); + const bool setting_active = (gps_setting == nullptr) || (strcmp(gps_setting, "1") == 0); + snapshot.gps_enabled = provider_enabled && setting_active; + snapshot.gps_fix = snapshot.gps_enabled && location->isValid(); const long satellites = location->satellitesCount(); - if (snapshot.gps_enabled || satellites > 0) { + if (snapshot.gps_enabled) { snapshot.has_satellites = true; snapshot.satellites = max(satellites, 0); } diff --git a/release-notes.yml b/release-notes.yml index 64ad8772..93126460 100644 --- a/release-notes.yml +++ b/release-notes.yml @@ -527,3 +527,15 @@ releases: area: readme text: "Updated the README to describe EastMesh as an Eastern Australia community mesh network, introduce CoreScope telemetry, and warn that MQTT repeater builds are intended for Eastern Australia repeaters only." breaking_changes: [] + + - track: repeater-mqtt + version: "1.3.14" + tag: "repeater-mqtt-eastmesh-v1.3.14" + date: "2026-04-28" + previous_version: "1.3.13" + summary: "TODO: Change me later" + changes: + - type: fixed + area: stats + text: "Updated `/stats` GPS gauges and satellite trends to respect the active GPS setting so boards that report GPS hardware as on but deactivated after `gps off` no longer keep satellite metrics visible." + breaking_changes: [] From 4d873b60f497c2049e36d42253fecc824827b1ed Mon Sep 17 00:00:00 2001 From: "Valentin V. Bartenev" Date: Tue, 28 Apr 2026 21:49:28 +0300 Subject: [PATCH 2/6] fix: truncated LWT message That JSON includes eight fields: status, timestamp (26 chars), origin (up to 80 chars, escaped), origin_id (64-char hex public key), model (up to 40 chars), firmware_version, radio (up to 48 chars), and client_version (up to 96 chars). In the worst case the formatted string approaches ~490 bytes. Even in a typical scenario: JSON skeleton and status (126 chars), origin (~20 chars), origin_id (64 chars), model (~16 chars), firmware_version (~7 chars), radio (~16 chars), client_version (20 chars) the formatted string is around 269 chars, causing snprintf() to silently truncate the payload and results in malformed JSON. --- src/helpers/mqtt/MQTTUplink.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/helpers/mqtt/MQTTUplink.h b/src/helpers/mqtt/MQTTUplink.h index 619f7907..33a1ff56 100644 --- a/src/helpers/mqtt/MQTTUplink.h +++ b/src/helpers/mqtt/MQTTUplink.h @@ -94,7 +94,7 @@ private: char* token; char client_id[48]; char status_topic[128]; - char offline_payload[256]; + char offline_payload[512]; }; #endif From 4cefd24721710b3e269ed9794a75600cff5d982d Mon Sep 17 00:00:00 2001 From: Jared Dohrman Date: Thu, 30 Apr 2026 11:16:01 +1000 Subject: [PATCH 3/6] docs: update repeater-mqtt-eastmesh-v1.3.14 release notes --- release-notes.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/release-notes.yml b/release-notes.yml index 93126460..a5f38770 100644 --- a/release-notes.yml +++ b/release-notes.yml @@ -538,4 +538,7 @@ releases: - type: fixed area: stats text: "Updated `/stats` GPS gauges and satellite trends to respect the active GPS setting so boards that report GPS hardware as on but deactivated after `gps off` no longer keep satellite metrics visible." + - type: fixed + area: mqtt + text: "Increased the MQTT offline last-will payload buffer so the retained offline status JSON is not truncated when release metadata and identity fields are near their configured limits." breaking_changes: [] From 3b84f050c9626d597c7daf2263f7aa3993b02740 Mon Sep 17 00:00:00 2001 From: Jared Dohrman Date: Thu, 30 Apr 2026 14:46:52 +1000 Subject: [PATCH 4/6] fix: improve no-psram stats, web stability, and absent sensor probes --- docs/api.md | 15 ++ docs/web-panel.md | 6 +- src/helpers/StatsHistory.cpp | 4 +- .../sensors/EnvironmentSensorManager.cpp | 48 ++++--- src/helpers/web/WebPanelServer.cpp | 134 +++++++++++++++--- src/helpers/web/WebPanelServer.h | 1 + 6 files changed, 169 insertions(+), 39 deletions(-) diff --git a/docs/api.md b/docs/api.md index f9313785..81811b0f 100644 --- a/docs/api.md +++ b/docs/api.md @@ -102,6 +102,21 @@ Example: curl -sk -X POST https:///login --data '' ``` +### `GET /api/session` + +Check whether a stored session token is still valid. + +Headers: + +```text +X-Auth-Token: +``` + +Response: + +- JSON `{"authenticated":true}` on success +- `401 Unauthorized` if the token is missing or stale after lock, restart, or reflash + ### `POST /api/command` Run a repeater CLI command remotely. diff --git a/docs/web-panel.md b/docs/web-panel.md index 3d983831..dea1ac2d 100644 --- a/docs/web-panel.md +++ b/docs/web-panel.md @@ -72,6 +72,8 @@ Example: - the connection is HTTPS, but the certificate is self-signed - browsers will warn the first time you connect - the panel exposes the repeater CLI after login +- the browser keeps the session token while changing between `/app` and `/stats`; the page switches views in place until logout, idle lock, or server restart +- after a device restart or reflash, the app checks the stored token before loading settings and returns to login if the session is stale This is intended for local admin use on a trusted network, not for open internet exposure. @@ -267,13 +269,13 @@ Current in-memory history caps are: | `4 MB` to less than `8 MB` PSRAM | `480` | `192` | About `8` hours | | `8 MB` PSRAM or more | `720` | `288` | About `12` hours | -On boards with roughly `2 MB` PSRAM or more, stats history starts capturing from boot when `web.stats` is enabled, even if `/stats` has not been opened yet. +When `web.stats` is enabled, stats history starts capturing from boot or from the time it is enabled, even if `/stats` has not been opened yet. No-PSRAM boards use the smaller live-only buffer shown above. Archive-backed restore requires `web.stats` enabled plus a mounted SD card on boards that support the EastMesh archive path. The main purpose of the SD card is to let the repeater retain and restore stats history for `/stats`. The archive keeps fast `.latest` snapshot files for quick restore and UTC-dated daily `.log` files for longer-term history. As a secondary option, those files can also be removed and inspected on a computer for deeper manual review. -On no-PSRAM boards, `/stats` can still show recent graphs while the stats view is active, but the history is smaller and does not provide the same archive-backed behaviour as PSRAM-capable boards. +On no-PSRAM boards, `/stats` can still show recent graphs, but the history is smaller and does not provide the same archive-backed behaviour as PSRAM-capable boards. Useful CLI commands: diff --git a/src/helpers/StatsHistory.cpp b/src/helpers/StatsHistory.cpp index 538d2dbe..a39a8bc4 100644 --- a/src/helpers/StatsHistory.cpp +++ b/src/helpers/StatsHistory.cpp @@ -537,7 +537,7 @@ bool StatsHistory::supportsPersistence() const { } bool StatsHistory::shouldAutoActivateFromBoot() const { - return _enabled && !_live_only && hasBootAutoCapturePsram(); + return _enabled && (_live_only || hasBootAutoCapturePsram()); } bool StatsHistory::isBootAutoCaptureExpected() const { @@ -556,7 +556,7 @@ bool StatsHistory::isAccessActive(uint32_t now_ms) const { if (!_live_only) { return true; } - return _last_access_ms != 0 && (now_ms - _last_access_ms) < kLiveOnlyIdleTimeoutMs; + return _activated || (_last_access_ms != 0 && (now_ms - _last_access_ms) < kLiveOnlyIdleTimeoutMs); } void StatsHistory::noteAccess(uint32_t now_ms) { diff --git a/src/helpers/sensors/EnvironmentSensorManager.cpp b/src/helpers/sensors/EnvironmentSensorManager.cpp index 19472406..1b580a9c 100644 --- a/src/helpers/sensors/EnvironmentSensorManager.cpp +++ b/src/helpers/sensors/EnvironmentSensorManager.cpp @@ -161,6 +161,18 @@ public: static RAK12500LocationProvider RAK12500_provider; #endif +namespace { + +bool i2cDevicePresent(TwoWire* wire, uint8_t address) { + if (wire == nullptr) { + return false; + } + wire->beginTransmission(address); + return wire->endTransmission() == 0; +} + +} // namespace + bool EnvironmentSensorManager::begin() { #if ENV_INCLUDE_GPS #ifdef RAK_WISBLOCK_GPS @@ -182,7 +194,7 @@ bool EnvironmentSensorManager::begin() { #endif #if ENV_INCLUDE_AHTX0 - if (AHTX0.begin(TELEM_WIRE, 0, TELEM_AHTX_ADDRESS)) { + if (i2cDevicePresent(TELEM_WIRE, TELEM_AHTX_ADDRESS) && AHTX0.begin(TELEM_WIRE, 0, TELEM_AHTX_ADDRESS)) { MESH_DEBUG_PRINTLN("Found AHT10/AHT20 at address: %02X", TELEM_AHTX_ADDRESS); AHTX0_initialized = true; } else { @@ -192,7 +204,7 @@ bool EnvironmentSensorManager::begin() { #endif #if ENV_INCLUDE_BME680 - if (BME680.begin(TELEM_BME680_ADDRESS)) { + if (i2cDevicePresent(TELEM_WIRE, TELEM_BME680_ADDRESS) && BME680.begin(TELEM_BME680_ADDRESS)) { MESH_DEBUG_PRINTLN("Found BME680 at address: %02X", TELEM_BME680_ADDRESS); BME680_initialized = true; } else { @@ -202,7 +214,7 @@ bool EnvironmentSensorManager::begin() { #endif #if ENV_INCLUDE_BME280 - if (BME280.begin(TELEM_BME280_ADDRESS, TELEM_WIRE)) { + if (i2cDevicePresent(TELEM_WIRE, TELEM_BME280_ADDRESS) && BME280.begin(TELEM_BME280_ADDRESS, TELEM_WIRE)) { MESH_DEBUG_PRINTLN("Found BME280 at address: %02X", TELEM_BME280_ADDRESS); MESH_DEBUG_PRINTLN("BME sensor ID: %02X", BME280.sensorID()); // Reduce self-heating: single-shot conversions, light oversampling, long standby. @@ -220,7 +232,7 @@ bool EnvironmentSensorManager::begin() { #endif #if ENV_INCLUDE_BMP280 - if (BMP280.begin(TELEM_BMP280_ADDRESS)) { + if (i2cDevicePresent(TELEM_WIRE, TELEM_BMP280_ADDRESS) && BMP280.begin(TELEM_BMP280_ADDRESS)) { MESH_DEBUG_PRINTLN("Found BMP280 at address: %02X", TELEM_BMP280_ADDRESS); MESH_DEBUG_PRINTLN("BMP sensor ID: %02X", BMP280.sensorID()); BMP280_initialized = true; @@ -231,7 +243,7 @@ bool EnvironmentSensorManager::begin() { #endif #if ENV_INCLUDE_SHTC3 - if (SHTC3.begin(TELEM_WIRE)) { + if (i2cDevicePresent(TELEM_WIRE, 0x70) && SHTC3.begin(TELEM_WIRE)) { MESH_DEBUG_PRINTLN("Found sensor: SHTC3"); SHTC3_initialized = true; } else { @@ -242,10 +254,14 @@ bool EnvironmentSensorManager::begin() { #if ENV_INCLUDE_SHT4X - SHT4X.begin(*TELEM_WIRE, TELEM_SHT4X_ADDRESS); uint32_t serialNumber = 0; int16_t sht4x_error; - sht4x_error = SHT4X.serialNumber(serialNumber); + if (i2cDevicePresent(TELEM_WIRE, TELEM_SHT4X_ADDRESS)) { + SHT4X.begin(*TELEM_WIRE, TELEM_SHT4X_ADDRESS); + sht4x_error = SHT4X.serialNumber(serialNumber); + } else { + sht4x_error = -1; + } if (sht4x_error == 0) { MESH_DEBUG_PRINTLN("Found SHT4X at address: %02X", TELEM_SHT4X_ADDRESS); SHT4X_initialized = true; @@ -256,7 +272,7 @@ bool EnvironmentSensorManager::begin() { #endif #if ENV_INCLUDE_LPS22HB - if (LPS22HB.begin()) { + if (i2cDevicePresent(TELEM_WIRE, 0x5C) && LPS22HB.begin()) { MESH_DEBUG_PRINTLN("Found sensor: LPS22HB"); LPS22HB_initialized = true; } else { @@ -266,7 +282,7 @@ bool EnvironmentSensorManager::begin() { #endif #if ENV_INCLUDE_INA3221 - if (INA3221.begin(TELEM_INA3221_ADDRESS, TELEM_WIRE)) { + if (i2cDevicePresent(TELEM_WIRE, TELEM_INA3221_ADDRESS) && INA3221.begin(TELEM_INA3221_ADDRESS, TELEM_WIRE)) { MESH_DEBUG_PRINTLN("Found INA3221 at address: %02X", TELEM_INA3221_ADDRESS); MESH_DEBUG_PRINTLN("%04X %04X", INA3221.getDieID(), INA3221.getManufacturerID()); @@ -281,7 +297,7 @@ bool EnvironmentSensorManager::begin() { #endif #if ENV_INCLUDE_INA219 - if (INA219.begin(TELEM_WIRE)) { + if (i2cDevicePresent(TELEM_WIRE, TELEM_INA219_ADDRESS) && INA219.begin(TELEM_WIRE)) { MESH_DEBUG_PRINTLN("Found INA219 at address: %02X", TELEM_INA219_ADDRESS); INA219_initialized = true; } else { @@ -291,7 +307,7 @@ bool EnvironmentSensorManager::begin() { #endif #if ENV_INCLUDE_INA260 - if (INA260.begin(TELEM_INA260_ADDRESS, TELEM_WIRE)) { + if (i2cDevicePresent(TELEM_WIRE, TELEM_INA260_ADDRESS) && INA260.begin(TELEM_INA260_ADDRESS, TELEM_WIRE)) { MESH_DEBUG_PRINTLN("Found INA260 at address: %02X", TELEM_INA260_ADDRESS); INA260_initialized = true; } else { @@ -301,7 +317,7 @@ bool EnvironmentSensorManager::begin() { #endif #if ENV_INCLUDE_INA226 - if (INA226.begin()) { + if (i2cDevicePresent(TELEM_WIRE, TELEM_INA226_ADDRESS) && INA226.begin()) { MESH_DEBUG_PRINTLN("Found INA226 at address: %02X", TELEM_INA226_ADDRESS); INA226.setMaxCurrentShunt(TELEM_INA226_MAX_AMP, TELEM_INA226_SHUNT_VALUE); INA226_initialized = true; @@ -312,7 +328,7 @@ bool EnvironmentSensorManager::begin() { #endif #if ENV_INCLUDE_MLX90614 - if (MLX90614.begin(TELEM_MLX90614_ADDRESS, TELEM_WIRE)) { + if (i2cDevicePresent(TELEM_WIRE, TELEM_MLX90614_ADDRESS) && MLX90614.begin(TELEM_MLX90614_ADDRESS, TELEM_WIRE)) { MESH_DEBUG_PRINTLN("Found MLX90614 at address: %02X", TELEM_MLX90614_ADDRESS); MLX90614_initialized = true; } else { @@ -322,7 +338,7 @@ bool EnvironmentSensorManager::begin() { #endif #if ENV_INCLUDE_VL53L0X - if (VL53L0X.begin(TELEM_VL53L0X_ADDRESS, false, TELEM_WIRE)) { + if (i2cDevicePresent(TELEM_WIRE, TELEM_VL53L0X_ADDRESS) && VL53L0X.begin(TELEM_VL53L0X_ADDRESS, false, TELEM_WIRE)) { MESH_DEBUG_PRINTLN("Found VL53L0X at address: %02X", TELEM_VL53L0X_ADDRESS); VL53L0X_initialized = true; } else { @@ -334,7 +350,7 @@ bool EnvironmentSensorManager::begin() { #if ENV_INCLUDE_BMP085 // First argument is MODE (aka oversampling) // choose ULTRALOWPOWER - if (BMP085.begin(0, TELEM_WIRE)) { + if (i2cDevicePresent(TELEM_WIRE, 0x77) && BMP085.begin(0, TELEM_WIRE)) { MESH_DEBUG_PRINTLN("Found sensor BMP085"); BMP085_initialized = true; } else { @@ -345,7 +361,7 @@ bool EnvironmentSensorManager::begin() { #if ENV_INCLUDE_RAK12035 RAK12035.setup(*TELEM_WIRE); - if (RAK12035.begin(TELEM_RAK12035_ADDRESS)) { + if (i2cDevicePresent(TELEM_WIRE, TELEM_RAK12035_ADDRESS) && RAK12035.begin(TELEM_RAK12035_ADDRESS)) { MESH_DEBUG_PRINTLN("Found sensor RAK12035 at address: %02X", TELEM_RAK12035_ADDRESS); RAK12035_initialized = true; } else { diff --git a/src/helpers/web/WebPanelServer.cpp b/src/helpers/web/WebPanelServer.cpp index 39e74c5e..21881eec 100644 --- a/src/helpers/web/WebPanelServer.cpp +++ b/src/helpers/web/WebPanelServer.cpp @@ -255,7 +255,12 @@ const char kWebPanelLoginHtml[] PROGMEM = R"HTML( @@ -2737,12 +2816,14 @@ bool WebPanelServer::start() { httpd_uri_t app_uri = {.uri = "/app", .method = HTTP_GET, .handler = &WebPanelServer::handleApp, .user_ctx = &_route_context}; httpd_uri_t stats_page_uri = {.uri = "/stats", .method = HTTP_GET, .handler = &WebPanelServer::handleStatsPage, .user_ctx = &_route_context}; httpd_uri_t login_uri = {.uri = "/login", .method = HTTP_POST, .handler = &WebPanelServer::handleLogin, .user_ctx = &_route_context}; + httpd_uri_t session_uri = {.uri = "/api/session", .method = HTTP_GET, .handler = &WebPanelServer::handleSession, .user_ctx = &_route_context}; httpd_uri_t command_uri = {.uri = "/api/command", .method = HTTP_POST, .handler = &WebPanelServer::handleCommand, .user_ctx = &_route_context}; httpd_uri_t stats_uri = {.uri = "/api/stats", .method = HTTP_GET, .handler = &WebPanelServer::handleStats, .user_ctx = &_route_context}; httpd_register_uri_handler(_server, &index_uri); httpd_register_uri_handler(_server, &app_uri); httpd_register_uri_handler(_server, &stats_page_uri); httpd_register_uri_handler(_server, &login_uri); + httpd_register_uri_handler(_server, &session_uri); httpd_register_uri_handler(_server, &command_uri); httpd_register_uri_handler(_server, &stats_uri); @@ -2894,6 +2975,21 @@ esp_err_t WebPanelServer::handleLogin(httpd_req_t* req) { return httpd_resp_sendstr(req, ctx->self->_token); } +esp_err_t WebPanelServer::handleSession(httpd_req_t* req) { + auto* ctx = static_cast(req->user_ctx); + if (ctx == nullptr || ctx->self == nullptr) { + return httpd_resp_send_500(req); + } + if (!ctx->self->isAuthorized(req)) { + return httpd_resp_send_err(req, HTTPD_401_UNAUTHORIZED, "Unauthorized"); + } + + ctx->self->noteActivity(); + httpd_resp_set_type(req, "application/json; charset=utf-8"); + httpd_resp_set_hdr(req, "Cache-Control", "no-store"); + return httpd_resp_sendstr(req, "{\"authenticated\":true}"); +} + esp_err_t WebPanelServer::handleCommand(httpd_req_t* req) { auto* ctx = static_cast(req->user_ctx); if (ctx == nullptr || ctx->self == nullptr || ctx->self->_runner == nullptr) { diff --git a/src/helpers/web/WebPanelServer.h b/src/helpers/web/WebPanelServer.h index 7c76ed08..e1d0ce58 100644 --- a/src/helpers/web/WebPanelServer.h +++ b/src/helpers/web/WebPanelServer.h @@ -65,6 +65,7 @@ private: static esp_err_t handleApp(httpd_req_t* req); static esp_err_t handleStatsPage(httpd_req_t* req); static esp_err_t handleLogin(httpd_req_t* req); + static esp_err_t handleSession(httpd_req_t* req); static esp_err_t handleCommand(httpd_req_t* req); static esp_err_t handleStats(httpd_req_t* req); From 6b43aebb9d32c536c64a4d7b8ccdd242a19d9488 Mon Sep 17 00:00:00 2001 From: Jared Dohrman Date: Thu, 30 Apr 2026 15:02:36 +1000 Subject: [PATCH 5/6] docs: update repeater mqtt v1.3.14 notes --- release-notes.yml | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/release-notes.yml b/release-notes.yml index a5f38770..854b0504 100644 --- a/release-notes.yml +++ b/release-notes.yml @@ -531,14 +531,29 @@ releases: - track: repeater-mqtt version: "1.3.14" tag: "repeater-mqtt-eastmesh-v1.3.14" - date: "2026-04-28" + date: "2026-04-30" previous_version: "1.3.13" - summary: "TODO: Change me later" + summary: "Fixes `/stats` GPS visibility and no-PSRAM trend capture, improves web-panel session recovery and App/Stats navigation on constrained boards, quiets absent I2C sensor probes, and prevents truncated MQTT offline status payloads." changes: - type: fixed area: stats text: "Updated `/stats` GPS gauges and satellite trends to respect the active GPS setting so boards that report GPS hardware as on but deactivated after `gps off` no longer keep satellite metrics visible." + - type: fixed + area: stats + text: "Allowed no-PSRAM boards to collect the small live `/stats` history buffer whenever `web.stats` is enabled, so Heltec v3 targets can show recent trend samples without archive storage." + - type: fixed + area: web-panel + text: "Improved App/Stats navigation and session recovery by preserving the login token across panel views, validating stale browser tokens before loading settings, and clearing expired sessions quickly after a reflash." + - type: fixed + area: web-panel + text: "Delayed the red MQTT IATA warning until the panel has loaded the configured value, avoiding a misleading unset banner while `/app` is still fetching settings." + - type: fixed + area: sensors + text: "Probed optional I2C environmental sensor addresses before driver initialization so boards without those sensors no longer emit repeated Wire read errors during boot." - type: fixed area: mqtt text: "Increased the MQTT offline last-will payload buffer so the retained offline status JSON is not truncated when release metadata and identity fields are near their configured limits." + - type: docs + area: web-panel + text: "Documented no-PSRAM `/stats` history behavior, stale browser-token recovery, and the lightweight `/api/session` endpoint used by the web panel." breaking_changes: [] From ac3552383dd4aa66cf59276a2b97a1823ccaed40 Mon Sep 17 00:00:00 2001 From: Jared Dohrman Date: Thu, 30 Apr 2026 15:11:06 +1000 Subject: [PATCH 6/6] fix: include Wire for sensor I2C probe --- src/helpers/sensors/EnvironmentSensorManager.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/helpers/sensors/EnvironmentSensorManager.cpp b/src/helpers/sensors/EnvironmentSensorManager.cpp index 1b580a9c..da63837d 100644 --- a/src/helpers/sensors/EnvironmentSensorManager.cpp +++ b/src/helpers/sensors/EnvironmentSensorManager.cpp @@ -1,5 +1,7 @@ #include "EnvironmentSensorManager.h" +#include + #if ENV_PIN_SDA && ENV_PIN_SCL #define TELEM_WIRE &Wire1 // Use Wire1 as the I2C bus for Environment Sensors #else