From 371d86c4b17b5ee432316f4dbb770891ec890851 Mon Sep 17 00:00:00 2001 From: mikecarper Date: Wed, 22 Jul 2026 00:43:38 -0700 Subject: [PATCH] Normalize tracked text to ASCII --- .../workflows/build-observer-firmwares.yml | 6 +- .../build-observer-plus-firmwares.yml | 18 +- CHANGELOG.md | 382 +++++++++--------- CONTRIBUTING.md | 8 +- MEMORY_MONITORING.md | 2 +- MQTT_IMPLEMENTATION.md | 54 +-- MQTT_INTERNALS.md | 22 +- README.md | 28 +- RESTORE_UPSTREAM_NOTES.md | 40 +- SECURITY.md | 6 +- docs/cli_commands.md | 42 +- docs/companion_protocol.md | 56 +-- docs/faq.md | 22 +- docs/flood_filtering.md | 18 +- docs/kiss_modem_protocol.md | 20 +- docs/nrf52_power_management.md | 2 +- docs/number_allocations.md | 6 +- docs/ota_protocol.md | 206 +++++----- docs/ota_user_guide.md | 80 ++-- docs/stats_binary_frames.md | 2 +- docs/terminal_chat_cli.md | 2 +- examples/companion_radio/main.cpp | 10 +- examples/simple_repeater/MyMesh.cpp | 12 +- examples/simple_room_server/MyMesh.cpp | 6 +- lib/PsychicMqttClient/CHANGELOG.md | 132 +++--- .../src/PsychicMqttClient.cpp | 6 +- lib/PsychicMqttClient/src/PsychicMqttClient.h | 2 +- platformio.ini | 4 +- scripts/gen_changelog.py | 10 +- scripts/partition_signature.py | 2 +- src/Mesh.cpp | 6 +- src/Mesh.h | 2 +- src/MeshCore.h | 2 +- src/helpers/AlertReporter.cpp | 10 +- src/helpers/AlertReporter.h | 10 +- src/helpers/CommonCLI.cpp | 22 +- src/helpers/CommonCLI.h | 8 +- src/helpers/CommonCLI_Observer.cpp | 18 +- src/helpers/ESP32Board.cpp | 10 +- src/helpers/ESP32Board.h | 2 +- src/helpers/MQTTMessageBuilder.h | 2 +- src/helpers/MQTTPresets.h | 2 +- src/helpers/RegionMap.cpp | 2 +- src/helpers/RxReservePacketManager.h | 4 +- src/helpers/SNMPAgent.h | 2 +- src/helpers/TxtDataHelpers.cpp | 2 +- src/helpers/bridges/MQTTBridge.cpp | 90 ++--- src/helpers/bridges/MQTTBridge.h | 20 +- src/helpers/esp32/WebConfigServer.cpp | 4 +- src/helpers/esp32/WebConfigServer.h | 2 +- src/helpers/nrf52/EthernetCLI.h | 2 +- src/helpers/nrf52/SerialEthernetInterface.cpp | 4 +- src/helpers/ota/BlockBitmap.h | 2 +- src/helpers/ota/FirmwareInfo.cpp | 2 +- src/helpers/ota/FirmwareInfo.h | 8 +- src/helpers/ota/FolderMotaStore.h | 6 +- src/helpers/ota/MerkleTree.h | 2 +- src/helpers/ota/MotaContainer.cpp | 2 +- src/helpers/ota/MotaContainer.h | 4 +- src/helpers/ota/MotaSeederProto.h | 6 +- src/helpers/ota/MotaSourceSerial.cpp | 2 +- src/helpers/ota/MotaSourceSerial.h | 2 +- src/helpers/ota/OtaApply.cpp | 22 +- src/helpers/ota/OtaApply.h | 6 +- src/helpers/ota/OtaBlInfo.h | 2 +- src/helpers/ota/OtaByteIO.h | 4 +- src/helpers/ota/OtaCli.cpp | 34 +- src/helpers/ota/OtaContext.h | 16 +- src/helpers/ota/OtaFlashLayout_nrf52.h | 4 +- src/helpers/ota/OtaFormat.h | 8 +- src/helpers/ota/OtaManager.cpp | 42 +- src/helpers/ota/OtaManager.h | 24 +- src/helpers/ota/OtaProtocol.h | 8 +- src/helpers/ota/OtaSelf.cpp | 10 +- src/helpers/ota/OtaSelf.h | 2 +- src/helpers/ota/OtaSource.h | 8 +- src/helpers/ota/OtaStore.h | 14 +- src/helpers/ota/OtaStoreFlashEsp32.cpp | 2 +- src/helpers/ota/OtaStoreFlashNrf52.h | 12 +- src/helpers/ota/OtaTargets.h | 2 +- src/helpers/ota/OtaVerify.h | 2 +- src/helpers/ota/SignerAllowlist.h | 2 +- src/helpers/radiolib/RadioLibWrappers.cpp | 2 +- .../sensors/EnvironmentSensorManager.cpp | 10 +- src/helpers/sensors/LPPDataHelpers.h | 8 +- src/helpers/sensors/RAK12035_SoilMoisture.cpp | 104 ++--- src/helpers/ui/DisplayDriver.h | 2 +- src/helpers/ui/U8g2Display.h | 2 +- .../test_simple_mesh_tables.cpp | 8 +- test/test_ota/mota_vectors.h | 2 +- test/test_ota/test_ota_core.cpp | 10 +- test/test_ota/test_ota_flashplan.cpp | 8 +- tools/mota/README.md | 10 +- tools/mota/gen_targets.py | 6 +- tools/mota/gen_vectors.py | 2 +- tools/mota/motalib.py | 6 +- tools/mota/pio_endf.py | 6 +- tools/mota/test_mota.py | 2 +- variants/generic-e22/variant.h | 2 +- variants/heltec_t096/platformio.ini | 2 +- variants/heltec_tracker/platformio.ini | 2 +- variants/heltec_tracker/target.cpp | 2 +- variants/heltec_v4/LoRaFEMControl.cpp | 6 +- variants/lilygo_tbeam_1w/TBeam1WBoard.h | 14 +- variants/lilygo_tbeam_1w/platformio.ini | 6 +- variants/lilygo_tbeam_1w/variant.h | 4 +- variants/lilygo_techo_lite/TechoBoard.h | 2 +- variants/lilygo_techo_lite/variant.h | 6 +- variants/lilygo_tlora_v2_1/platformio.ini | 2 +- variants/meshadventurer/variant.h | 2 +- variants/rak3401/RAK3401Board.cpp | 2 +- variants/rak3401/variant.h | 2 +- variants/xiao_nrf52/variant.h | 6 +- webui/index.html | 68 ++-- 114 files changed, 1004 insertions(+), 1004 deletions(-) diff --git a/.github/workflows/build-observer-firmwares.yml b/.github/workflows/build-observer-firmwares.yml index 3c13bf0c..1671010c 100644 --- a/.github/workflows/build-observer-firmwares.yml +++ b/.github/workflows/build-observer-firmwares.yml @@ -10,7 +10,7 @@ on: - mqtt-bridge-implementation-flex # Only rebuild firmware when something that affects the binaries changes. # Docs, the changelog, the changelog generator, CI files, and repo-meta - # files do not change firmware output — docs/changelog are handled by + # files do not change firmware output - docs/changelog are handled by # sync-flasher-content.yml instead. NOTE: a push is skipped only if EVERY # changed file matches a pattern below; one stray unlisted file (e.g. an # earlier .gitignore edit) triggers a full build, so keep meta files listed. @@ -181,7 +181,7 @@ jobs: fi # Upload this build, overwriting same-named assets. Exclude the .partsig - # files — those are consumed locally by the slim-manifest generator below, + # files - those are consumed locally by the slim-manifest generator below, # not published as release assets (and they'd break the hash-based prune). gh release upload "$RELEASE_TAG" $(find out -maxdepth 1 -type f ! -name '*.partsig') --clobber @@ -285,7 +285,7 @@ jobs: run: | # Stage everything: update-firmware.py edits config.json in place, but the # slim manifests (v/*.json) and the build counter can be NEW files, which - # `commit -am` would miss — so add -A and check the staged diff. + # `commit -am` would miss - so add -A and check the staged diff. git add -A if git diff --cached --quiet; then echo "No flasher changes to commit." diff --git a/.github/workflows/build-observer-plus-firmwares.yml b/.github/workflows/build-observer-plus-firmwares.yml index 438f6f62..93005098 100644 --- a/.github/workflows/build-observer-plus-firmwares.yml +++ b/.github/workflows/build-observer-plus-firmwares.yml @@ -1,7 +1,7 @@ name: Build MQTT Observer+ Firmwares (keymind channel) # ============================================================================ -# STATUS: STAGED BUT NOT ENABLED — manual dispatch only. +# STATUS: STAGED BUT NOT ENABLED - manual dispatch only. # # This is the experimental "plus" channel (mqtt-observer-plus = flex + # mcarper/keymindCascade). It mirrors build-observer-firmwares.yml with the @@ -14,13 +14,13 @@ name: Build MQTT Observer+ Firmwares (keymind channel) # - device pull-OTA: OTA_MANIFEST_BASE overridden to .../v-plus so # `ota update` on a plus node can never "update" it back onto the stable # channel (the per-env platformio.ini bakes .../v; the env-injected flag -# below wins because PLATFORMIO_BUILD_FLAGS append after ini flags — +# below wins because PLATFORMIO_BUILD_FLAGS append after ini flags - # verified by building and checking the embedded URL string) # # NOTE ON DISPATCH: GitHub does not index a workflow_dispatch-only workflow # that exists only on a non-default branch and has never run, so this workflow # will not appear in the Actions UI / `gh workflow list` (and cannot be -# dispatched) until its push trigger fires once. That is intentional here — +# dispatched) until its push trigger fires once. That is intentional here - # "staged, not enabled" means nothing can fire it yet. Uncommenting the push # trigger below and pushing both registers it and runs the first build; from # then on `gh workflow run build-observer-plus-firmwares.yml --ref @@ -28,7 +28,7 @@ name: Build MQTT Observer+ Firmwares (keymind channel) # # TO ENABLE THE CHANNEL: # 1. Uncomment the `push:` trigger below. -# 2. Flip SYNC_FLASHER to "true" — but FIRST the flasher repo +# 2. Flip SYNC_FLASHER to "true" - but FIRST the flasher repo # (agessaman/flasher.meshcore.io) needs channel support; until it does, # the sync step below fails closed on purpose so the stable config.json # cannot be clobbered. Flasher-side prerequisites: @@ -37,11 +37,11 @@ name: Build MQTT Observer+ Firmwares (keymind channel) # - SPA: a channel picker (Observer / Observer+ experimental) # - gen-slim-manifests.py output dir flasher/v-plus (arg already # parameterized; wired below) -# - a plus changelog target (CHANGELOG-plus.md) — the stable +# - a plus changelog target (CHANGELOG-plus.md) - the stable # CHANGELOG.md tracks the flex branch only # # Until SYNC_FLASHER is enabled, the per-base build counter has no writer, so -# every manual dispatch publishes as build .1 — fine for hand-flashed testing +# every manual dispatch publishes as build .1 - fine for hand-flashed testing # off the release page. # ============================================================================ @@ -78,7 +78,7 @@ env: # Channel-tagged version: embedded in firmware filenames and (with the # -observer tag + build number appended by build.sh) in `ver`/MQTT/SNMP. FIRMWARE_VERSION: v1.16.0-plus - # Rolling release tag for this channel — distinct from observer-mqtt-latest. + # Rolling release tag for this channel - distinct from observer-mqtt-latest. RELEASE_TAG: observer-mqtt-plus-latest # Channel-isolated pull-OTA manifest base baked into these binaries # (overrides the .../v default from the per-env platformio.ini flags). @@ -88,7 +88,7 @@ env: jobs: - # Discover the *_observer_mqtt envs and shard them across runners — same + # Discover the *_observer_mqtt envs and shard them across runners - same # env set as the stable channel; only version/flags/tag differ. enumerate: runs-on: ubuntu-latest @@ -179,7 +179,7 @@ jobs: steps: - name: Clone Repo uses: actions/checkout@v4 - # Shallow on purpose — see the stable workflow's note: build.sh named + # Shallow on purpose - see the stable workflow's note: build.sh named # the assets from a shallow clone's 7-char short hash. - name: Download All Shard Artifacts diff --git a/CHANGELOG.md b/CHANGELOG.md index f6cbc39b..43f51dea 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,227 +1,227 @@ -# MeshCore Observer — Changelog +# MeshCore Observer - Changelog Changes to the MQTT observer / bridge work that powers the firmware offered on [flasher.meshcore.io](https://flasher.meshcore.io). Newest changes are at the top. -**Legend:** **New** = new capability · **Fix** = bug fix · **Improvement** = enhancement to -existing behavior · **Internal** = refactor / under-the-hood · **Docs** = documentation · -**Build** / **CI** = build system & automation. **⬆ Upstream sync** marks a merge of the +**Legend:** **New** = new capability * **Fix** = bug fix * **Improvement** = enhancement to +existing behavior * **Internal** = refactor / under-the-hood * **Docs** = documentation * +**Build** / **CI** = build system & automation. **[UP] Upstream sync** marks a merge of the upstream MeshCore `dev` branch, which generally pulls in a new MeshCore software version. ### June 2026 -- **New** — AlertReporter integration in MyMesh for Room Server 2026-06-17 · `985fda13` -- **Improvement** — Cumulative packet statistics (`packets_sent` / `packets_received`) added to the status message 2026-06-16 · `8bf590b1` -- **Improvement** — Packet path now published as an array of lowercase hex hop tokens 2026-06-16 · `e80a5ded` -- **CI** — Sync documentation files to the flasher site from the build workflow 2026-06-16 · `0844eee1` -- **CI** — Reworked the GitHub Actions workflow for rolling releases 2026-06-08 · `8fa38707` -- **New** — Radio Watchdog feature for the MQTT observer 2026-06-08 · `002e8d4a` -- **Improvement** — Firmware build-date format and UTC timestamps (always emit `+00:00`) 2026-06-06 · `b45373a3` -- **CI** — Build observer firmwares across 14 shards (up from 4) 2026-06-06 · `c29ede05` -- **CI** — Cache PlatformIO toolchains in CI 2026-06-06 · `6723776f` -- **CI** — Automated firmware builds via GitHub Actions 2026-06-06 · `981ed246` -- ⬆ **Upstream sync** — Synced with upstream MeshCore dev (brings MeshCore v1.16.0) 2026-06-06 · `dcc8f826` +- **New** - AlertReporter integration in MyMesh for Room Server 2026-06-17 * `985fda13` +- **Improvement** - Cumulative packet statistics (`packets_sent` / `packets_received`) added to the status message 2026-06-16 * `8bf590b1` +- **Improvement** - Packet path now published as an array of lowercase hex hop tokens 2026-06-16 * `e80a5ded` +- **CI** - Sync documentation files to the flasher site from the build workflow 2026-06-16 * `0844eee1` +- **CI** - Reworked the GitHub Actions workflow for rolling releases 2026-06-08 * `8fa38707` +- **New** - Radio Watchdog feature for the MQTT observer 2026-06-08 * `002e8d4a` +- **Improvement** - Firmware build-date format and UTC timestamps (always emit `+00:00`) 2026-06-06 * `b45373a3` +- **CI** - Build observer firmwares across 14 shards (up from 4) 2026-06-06 * `c29ede05` +- **CI** - Cache PlatformIO toolchains in CI 2026-06-06 * `6723776f` +- **CI** - Automated firmware builds via GitHub Actions 2026-06-06 * `981ed246` +- [UP] **Upstream sync** - Synced with upstream MeshCore dev (brings MeshCore v1.16.0) 2026-06-06 * `dcc8f826` ### May 2026 -- **New** — NZ Analyzer MQTT preset 2026-05-25 · `c0c845f5` -- **Internal** — Region definition helpers; docs refresh 2026-05-21 · `1296aa77` -- **Docs** — Clarified WiFi configuration instructions (password optional for open networks) 2026-05-21 · `38568eb1` -- **Improvement** — Alert PSK access now restricted by sender timestamp 2026-05-17 · `f717d673` -- **Docs** — Trimmed the fault-alert section in the implementation guide 2026-05-17 · `c41805d0` -- **Change** — Renamed CLI command `region bulk` → `region def` 2026-05-16 · `f3c6c348` -- **Internal** — Timestamp handling in MQTTMessageBuilder 2026-05-15 · `5a004f37` -- **New** — T-LoRa V2.1–1.6 observer configurations 2026-05-13 · `3cde9c10` -- **Fix** · `mqtt` — Hardened the T-LoRa MQTT observer and trimmed MQTT task stack usage 2026-05-13 · `daf9ee47` -- **Internal** — Alert PSK handling is now hex-only 2026-05-12 · `a8668840` -- **Improvement** — Alert PSK accepts both base64 and hex 2026-05-12 · `a865d0a3` -- **New** · `cli` — Bulk region hierarchy command 2026-05-12 · `19f95001` -- **Improvement** — Path hash size included in AlertReporter flood messages 2026-05-11 · `6a3ed5d4` -- **New** — T-Beam 1W observer support and partition handling 2026-05-11 · `fc99d6b3` -- **Improvement** — Fault alerts gained region-based scoping and banned channels 2026-05-11 · `16dc49fa` -- **New** — Fault alerts for WiFi and MQTT disconnections 2026-05-10 · `80355f32` -- **New** — ColoradoMesh MQTT preset 2026-05-10 · `08847342` -- **New** — MQTT origin handling and a string utility 2026-05-09 · `b9f478c9` -- **Improvement** — Repeat status included in the MQTT status message 2026-05-09 · `303cf8c2` -- **Internal** — MQTT origin now uses effective-origin logic 2026-05-09 · `ff031f48` -- ⬆ **Upstream sync** — Synced with upstream MeshCore dev 2026-05-09 · `b37db668` -- **New** · `mqtt` — T-LoRa V2.1-1.6 observer environments 2026-05-02 · `78e1500a` +- **New** - NZ Analyzer MQTT preset 2026-05-25 * `c0c845f5` +- **Internal** - Region definition helpers; docs refresh 2026-05-21 * `1296aa77` +- **Docs** - Clarified WiFi configuration instructions (password optional for open networks) 2026-05-21 * `38568eb1` +- **Improvement** - Alert PSK access now restricted by sender timestamp 2026-05-17 * `f717d673` +- **Docs** - Trimmed the fault-alert section in the implementation guide 2026-05-17 * `c41805d0` +- **Change** - Renamed CLI command `region bulk` -> `region def` 2026-05-16 * `f3c6c348` +- **Internal** - Timestamp handling in MQTTMessageBuilder 2026-05-15 * `5a004f37` +- **New** - T-LoRa V2.1-1.6 observer configurations 2026-05-13 * `3cde9c10` +- **Fix** * `mqtt` - Hardened the T-LoRa MQTT observer and trimmed MQTT task stack usage 2026-05-13 * `daf9ee47` +- **Internal** - Alert PSK handling is now hex-only 2026-05-12 * `a8668840` +- **Improvement** - Alert PSK accepts both base64 and hex 2026-05-12 * `a865d0a3` +- **New** * `cli` - Bulk region hierarchy command 2026-05-12 * `19f95001` +- **Improvement** - Path hash size included in AlertReporter flood messages 2026-05-11 * `6a3ed5d4` +- **New** - T-Beam 1W observer support and partition handling 2026-05-11 * `fc99d6b3` +- **Improvement** - Fault alerts gained region-based scoping and banned channels 2026-05-11 * `16dc49fa` +- **New** - Fault alerts for WiFi and MQTT disconnections 2026-05-10 * `80355f32` +- **New** - ColoradoMesh MQTT preset 2026-05-10 * `08847342` +- **New** - MQTT origin handling and a string utility 2026-05-09 * `b9f478c9` +- **Improvement** - Repeat status included in the MQTT status message 2026-05-09 * `303cf8c2` +- **Internal** - MQTT origin now uses effective-origin logic 2026-05-09 * `ff031f48` +- [UP] **Upstream sync** - Synced with upstream MeshCore dev 2026-05-09 * `b37db668` +- **New** * `mqtt` - T-LoRa V2.1-1.6 observer environments 2026-05-02 * `78e1500a` ### April 2026 -- **Fix** · `mqtt` — Corrected ColoradoMesh preset details 2026-04-30 · `9f8f2d05` -- **New** · `platformio` — PSRAM support for the TBeam Supreme SX1262 variant 2026-04-28 · `0b05aede` -- **New** · `mqtt` — ColoradoMesh preset 2026-04-28 · `457edf69` -- **Fix** · `mqtt` — Added missing MQTTBridge error codes 2026-04-27 · `5a5e5373` -- **New** · `mqtt` — EastIdahoMesh preset and richer CLI preset listing 2026-04-25 · `753eb72f` -- **Fix** · `mqtt` — Removed errant `clearLastWill` from PsychicMqttClient 2026-04-25 · `32449e62` -- **Fix** · `mqtt` — Restored `origin` field position in packet messages 2026-04-25 · `673361b6` -- **Internal** · `mqtt` — Reorganized packet message structure 2026-04-25 · `8be27e4e` -- **Internal** · `mqtt` — Default TX packet settings and documentation 2026-04-25 · `bfcf0176` -- **New** · `platformio` — MQTT observer configs for the Heltec V4 expansion kit 2026-04-24 · `0a4f002f` -- **New** · `cli` — Radio watchdog command 2026-04-24 · `7b21be3e` -- **Internal** · `mqtt` — Standardized MQTT preset formatting 2026-04-24 · `6211885c` -- **Internal** · `heltec_tracker_v2` — Aligned FEM with meshcore-dev KCT8103L 2026-04-24 · `0d280257` -- **New** · `heltec_tracker_v2` — Restored LoRaFEMControl for GC1109 2026-04-24 · `255fce7f` -- ⬆ **Upstream sync** — Synced with upstream MeshCore dev 2026-04-24 · `c1989d6f` -- **New** · `mqtt` — chimesh and meshat.se presets 2026-04-24 · `ee4e39bc` -- **Fix** · `mqtt` — Restored legacy outbox behavior for QoS0 async publishes 2026-04-23 · `70722a58` -- **Fix** · `mqtt` — Config marked dirty on setter calls; applied on connect/reconnect 2026-04-23 · `bb67b04e` -- **Improvement** · `mqtt` — Better QoS handling and retry logic 2026-04-23 · `7d0c5bce` -- **Internal** · `mqtt` — Removed legacy `mqtt.server`/`port`/`username`/`password` CLI 2026-04-21 · `cfec3a5b` -- **Fix** · `mqtt` — Legacy CLI aligned with slot prefs; restored `isConfigValid` 2026-04-21 · `ac2662c5` -- **Internal** — PsychicMqttClient memory management; fixed-size callback arrays (v0.2.2) 2026-04-21 · `e9ff1ae0` -- **Improvement** — MQTTBridge memory management, deferred logging, and QoS on publish 2026-04-19 · `1ff9437f` -- ⬆ **Upstream sync** — Synced with upstream MeshCore dev 2026-04-19 · `563d9a7d` -- **Build** — Disabled MQTT debug config for heltec_v4 builds 2026-04-17 · `0ed29cb7` -- **Change** — gitignore generated cert files; meshmapper timeout reset to 0 2026-04-17 · `ab121708` -- **Change** — MeshMapper preset update; removed generated cert files 2026-04-17 · `074ac3e7` -- **Improvement** — Critical heap monitoring and deferred TLS; hard restart on prolonged low memory 2026-04-17 · `f4a4fd7c` -- **Internal** — Inline `StaticJsonDocument` allocations to reduce heap fragmentation 2026-04-16 · `ab6685a1` -- **New** — nashmesh preset 2026-04-14 · `13fbcd91` -- **Internal** — MQTTMessageBuilder takes an external JsonDocument to cut stack usage 2026-04-14 · `ff2df2b7` -- **Internal** — Removed the raw-data mutex in favor of core-specific staging 2026-04-14 · `0c190dac` -- **Improvement** — Radio watchdog avoids false alarms in quiet meshes 2026-04-14 · `961f8293` -- **New** — Radio diagnostics in MyMesh and CommonCLI 2026-04-10 · `7e4f75c9` -- **Improvement** — Internal heap stats in status messages 2026-04-06 · `74e45a79` -- **New** — TennMesh preset (fixed username/password auth) 2026-04-05 · `3b46e9ee` -- **Docs** — Single `set radio` command in the guide 2026-04-04 · `1c524a93` -- **Internal** — RX/TX packet logging controlled by MQTT settings (`mqtt_rx_enabled` pref) 2026-04-04 · `47b632aa` -- **Improvement** — Runtime-configurable MQTT slot limits based on PSRAM 2026-04-04 · `258a0d72` -- **Improvement** — Disconnect count/time diagnostics; per-variant WiFi TX power 2026-04-02 · `f0962eff` +- **Fix** * `mqtt` - Corrected ColoradoMesh preset details 2026-04-30 * `9f8f2d05` +- **New** * `platformio` - PSRAM support for the TBeam Supreme SX1262 variant 2026-04-28 * `0b05aede` +- **New** * `mqtt` - ColoradoMesh preset 2026-04-28 * `457edf69` +- **Fix** * `mqtt` - Added missing MQTTBridge error codes 2026-04-27 * `5a5e5373` +- **New** * `mqtt` - EastIdahoMesh preset and richer CLI preset listing 2026-04-25 * `753eb72f` +- **Fix** * `mqtt` - Removed errant `clearLastWill` from PsychicMqttClient 2026-04-25 * `32449e62` +- **Fix** * `mqtt` - Restored `origin` field position in packet messages 2026-04-25 * `673361b6` +- **Internal** * `mqtt` - Reorganized packet message structure 2026-04-25 * `8be27e4e` +- **Internal** * `mqtt` - Default TX packet settings and documentation 2026-04-25 * `bfcf0176` +- **New** * `platformio` - MQTT observer configs for the Heltec V4 expansion kit 2026-04-24 * `0a4f002f` +- **New** * `cli` - Radio watchdog command 2026-04-24 * `7b21be3e` +- **Internal** * `mqtt` - Standardized MQTT preset formatting 2026-04-24 * `6211885c` +- **Internal** * `heltec_tracker_v2` - Aligned FEM with meshcore-dev KCT8103L 2026-04-24 * `0d280257` +- **New** * `heltec_tracker_v2` - Restored LoRaFEMControl for GC1109 2026-04-24 * `255fce7f` +- [UP] **Upstream sync** - Synced with upstream MeshCore dev 2026-04-24 * `c1989d6f` +- **New** * `mqtt` - chimesh and meshat.se presets 2026-04-24 * `ee4e39bc` +- **Fix** * `mqtt` - Restored legacy outbox behavior for QoS0 async publishes 2026-04-23 * `70722a58` +- **Fix** * `mqtt` - Config marked dirty on setter calls; applied on connect/reconnect 2026-04-23 * `bb67b04e` +- **Improvement** * `mqtt` - Better QoS handling and retry logic 2026-04-23 * `7d0c5bce` +- **Internal** * `mqtt` - Removed legacy `mqtt.server`/`port`/`username`/`password` CLI 2026-04-21 * `cfec3a5b` +- **Fix** * `mqtt` - Legacy CLI aligned with slot prefs; restored `isConfigValid` 2026-04-21 * `ac2662c5` +- **Internal** - PsychicMqttClient memory management; fixed-size callback arrays (v0.2.2) 2026-04-21 * `e9ff1ae0` +- **Improvement** - MQTTBridge memory management, deferred logging, and QoS on publish 2026-04-19 * `1ff9437f` +- [UP] **Upstream sync** - Synced with upstream MeshCore dev 2026-04-19 * `563d9a7d` +- **Build** - Disabled MQTT debug config for heltec_v4 builds 2026-04-17 * `0ed29cb7` +- **Change** - gitignore generated cert files; meshmapper timeout reset to 0 2026-04-17 * `ab121708` +- **Change** - MeshMapper preset update; removed generated cert files 2026-04-17 * `074ac3e7` +- **Improvement** - Critical heap monitoring and deferred TLS; hard restart on prolonged low memory 2026-04-17 * `f4a4fd7c` +- **Internal** - Inline `StaticJsonDocument` allocations to reduce heap fragmentation 2026-04-16 * `ab6685a1` +- **New** - nashmesh preset 2026-04-14 * `13fbcd91` +- **Internal** - MQTTMessageBuilder takes an external JsonDocument to cut stack usage 2026-04-14 * `ff2df2b7` +- **Internal** - Removed the raw-data mutex in favor of core-specific staging 2026-04-14 * `0c190dac` +- **Improvement** - Radio watchdog avoids false alarms in quiet meshes 2026-04-14 * `961f8293` +- **New** - Radio diagnostics in MyMesh and CommonCLI 2026-04-10 * `7e4f75c9` +- **Improvement** - Internal heap stats in status messages 2026-04-06 * `74e45a79` +- **New** - TennMesh preset (fixed username/password auth) 2026-04-05 * `3b46e9ee` +- **Docs** - Single `set radio` command in the guide 2026-04-04 * `1c524a93` +- **Internal** - RX/TX packet logging controlled by MQTT settings (`mqtt_rx_enabled` pref) 2026-04-04 * `47b632aa` +- **Improvement** - Runtime-configurable MQTT slot limits based on PSRAM 2026-04-04 * `258a0d72` +- **Improvement** - Disconnect count/time diagnostics; per-variant WiFi TX power 2026-04-02 * `f0962eff` ### March 2026 -- **Improvement** — Pre-allocated status JSON buffer in PSRAM; non-blocking NTP refresh 2026-03-31 · `cf4ec3f6` -- **New** — WiFi disconnect diagnostics 2026-03-31 · `0e82271e` -- **Fix** — Audience command parsing handles the 9-character prefix 2026-03-30 · `a4324a83` -- **New** — JWT audience configuration for custom slots 2026-03-30 · `1263e71d` -- **Improvement** — Partition-table guidance, merged-firmware flashing, embedded CA bundle 2026-03-30 · `da775bb2` -- **Docs** — Removed obsolete MQTT design docs 2026-03-28 · `384b80fc` -- **Docs** — README links to the MQTT Implementation Guide 2026-03-28 · `ff49d28f` -- **New** — Optional SNMP monitoring; concurrent MQTT slots 3 → 5 2026-03-28 · `202acacf` -- ⬆ **Upstream sync** — Synced with upstream MeshCore dev 2026-03-28 · `cc17c6b7` -- **New** — meshomatic preset; disable slots with no server configured 2026-03-27 · `3eacf56e` -- **Improvement** — Pre-allocated JSON publish buffer in PSRAM 2026-03-26 · `47dd2182` -- **New** — `reconnect()` added to PsychicMqttClient 2026-03-25 · `1b5884bd` -- **Improvement** — Lightweight JWT reconnect (token refresh without full teardown) 2026-03-25 · `af52a989` -- **Internal** — Full teardown/setup for JWT slots on reconnect 2026-03-24 · `e29c3d96` -- **Internal** — Standardized MQTT slot log format 2026-03-24 · `2bbca206` -- **New** — cascadiamesh preset; keepalive configuration 2026-03-24 · `eaf5fcbc` -- **Improvement** — Re-create JWT tokens when NTP reveals stale time 2026-03-24 · `1c98d1c6` -- **Internal** — Serial-availability checks before logging; circuit-breaker reconnect logic 2026-03-23 · `4a60f166` -- **New** — MeshRank preset (token auth, custom topic templates) 2026-03-22 · `95874f0c` -- **Fix** — Dynamic MQTTBridge allocation to avoid an ESP32 static-init crash 2026-03-21 · `c67fb12b` -- **Build** — Build script: cached project config, artifact handling, ESP32 binary merge 2026-03-21 · `bf8a3a7a` -- **Improvement** — Active connection slots limited by available memory (PSRAM-aware) 2026-03-20 · `542f8a31` -- **New** — Up to 3 configurable connection slots with built-in presets (LetsMesh US/EU, MeshMapper) 2026-03-20 · `b43e9618` -- ⬆ **Upstream sync** — Synced with upstream MeshCore dev 2026-03-20 · `abe6e046` -- **Change** — Stopped enabling MQTT analyzer servers by default in the example sketches 2026-03-07 · `304719e8` -- ⬆ **Upstream sync** — Synced with upstream MeshCore dev 2026-03-06 · `db8b4419` -- ⬆ **Upstream sync** — Synced with upstream MeshCore dev 2026-03-04 · `56ac85fa` -- ⬆ **Upstream sync** — Synced with upstream MeshCore dev 2026-03-03 · `dcbbe5a6` +- **Improvement** - Pre-allocated status JSON buffer in PSRAM; non-blocking NTP refresh 2026-03-31 * `cf4ec3f6` +- **New** - WiFi disconnect diagnostics 2026-03-31 * `0e82271e` +- **Fix** - Audience command parsing handles the 9-character prefix 2026-03-30 * `a4324a83` +- **New** - JWT audience configuration for custom slots 2026-03-30 * `1263e71d` +- **Improvement** - Partition-table guidance, merged-firmware flashing, embedded CA bundle 2026-03-30 * `da775bb2` +- **Docs** - Removed obsolete MQTT design docs 2026-03-28 * `384b80fc` +- **Docs** - README links to the MQTT Implementation Guide 2026-03-28 * `ff49d28f` +- **New** - Optional SNMP monitoring; concurrent MQTT slots 3 -> 5 2026-03-28 * `202acacf` +- [UP] **Upstream sync** - Synced with upstream MeshCore dev 2026-03-28 * `cc17c6b7` +- **New** - meshomatic preset; disable slots with no server configured 2026-03-27 * `3eacf56e` +- **Improvement** - Pre-allocated JSON publish buffer in PSRAM 2026-03-26 * `47dd2182` +- **New** - `reconnect()` added to PsychicMqttClient 2026-03-25 * `1b5884bd` +- **Improvement** - Lightweight JWT reconnect (token refresh without full teardown) 2026-03-25 * `af52a989` +- **Internal** - Full teardown/setup for JWT slots on reconnect 2026-03-24 * `e29c3d96` +- **Internal** - Standardized MQTT slot log format 2026-03-24 * `2bbca206` +- **New** - cascadiamesh preset; keepalive configuration 2026-03-24 * `eaf5fcbc` +- **Improvement** - Re-create JWT tokens when NTP reveals stale time 2026-03-24 * `1c98d1c6` +- **Internal** - Serial-availability checks before logging; circuit-breaker reconnect logic 2026-03-23 * `4a60f166` +- **New** - MeshRank preset (token auth, custom topic templates) 2026-03-22 * `95874f0c` +- **Fix** - Dynamic MQTTBridge allocation to avoid an ESP32 static-init crash 2026-03-21 * `c67fb12b` +- **Build** - Build script: cached project config, artifact handling, ESP32 binary merge 2026-03-21 * `bf8a3a7a` +- **Improvement** - Active connection slots limited by available memory (PSRAM-aware) 2026-03-20 * `542f8a31` +- **New** - Up to 3 configurable connection slots with built-in presets (LetsMesh US/EU, MeshMapper) 2026-03-20 * `b43e9618` +- [UP] **Upstream sync** - Synced with upstream MeshCore dev 2026-03-20 * `abe6e046` +- **Change** - Stopped enabling MQTT analyzer servers by default in the example sketches 2026-03-07 * `304719e8` +- [UP] **Upstream sync** - Synced with upstream MeshCore dev 2026-03-06 * `db8b4419` +- [UP] **Upstream sync** - Synced with upstream MeshCore dev 2026-03-04 * `56ac85fa` +- [UP] **Upstream sync** - Synced with upstream MeshCore dev 2026-03-03 * `dcbbe5a6` ### February 2026 -- ⬆ **Upstream sync** — Synced with upstream MeshCore dev (Packet.cpp fix) 2026-02-26 · `8b359452` -- **Internal** — Multibyte path handling in Packet / MQTTMessageBuilder 2026-02-25 · `c8e220b1` -- ⬆ **Upstream sync** — Synced with upstream MeshCore dev 2026-02-25 · `51e9907f` -- **Change** — Removed an unused preprocessor directive in MyMesh 2026-02-25 · `ee1a9b4a` -- ⬆ **Upstream sync** — Synced with upstream MeshCore dev 2026-02-24 · `397a48fc` -- **Improvement** — Keepalive set to 45s to hold WebSocket connections through Cloudflare 2026-02-16 · `2d4b7a64` -- **Build** — heltec_v4 uses the `heltec_v4_oled` config for observer / room-server 2026-02-16 · `88ce0d33` -- ⬆ **Upstream sync** — Synced with upstream MeshCore dev 2026-02-15 · `e89cbee8` -- **Improvement** — Simplified KISS noise-floor sampling 2026-02-07 · `776131e2` -- **Fix** · `kiss_modem` — Improved RX delivery and noise-floor sampling 2026-02-06 · `f445b5ac` -- **Improvement** — Better NTP synchronization in MQTTBridge 2026-02-06 · `ba4243b5` -- **Improvement** — WiFi management and fragmentation recovery 2026-02-04 · `26aaace9` -- **New** — PSRAM-backed memory management in MQTTBridge 2026-02-04 · `1d0d33f9` -- **New** — PSRAM diagnostics at MQTTBridge init 2026-02-04 · `abcfe609` -- **Build** — mbedTLS configuration tuned across variants 2026-02-04 · `21158e1f` -- **Fix** · `kiss` — Periodic noise-floor calibration and AGC reset 2026-02-03 · `0fb57033` -- **Fix** — Memory use and status reporting in the MQTT bridge 2026-02-02 · `498566e6` +- [UP] **Upstream sync** - Synced with upstream MeshCore dev (Packet.cpp fix) 2026-02-26 * `8b359452` +- **Internal** - Multibyte path handling in Packet / MQTTMessageBuilder 2026-02-25 * `c8e220b1` +- [UP] **Upstream sync** - Synced with upstream MeshCore dev 2026-02-25 * `51e9907f` +- **Change** - Removed an unused preprocessor directive in MyMesh 2026-02-25 * `ee1a9b4a` +- [UP] **Upstream sync** - Synced with upstream MeshCore dev 2026-02-24 * `397a48fc` +- **Improvement** - Keepalive set to 45s to hold WebSocket connections through Cloudflare 2026-02-16 * `2d4b7a64` +- **Build** - heltec_v4 uses the `heltec_v4_oled` config for observer / room-server 2026-02-16 * `88ce0d33` +- [UP] **Upstream sync** - Synced with upstream MeshCore dev 2026-02-15 * `e89cbee8` +- **Improvement** - Simplified KISS noise-floor sampling 2026-02-07 * `776131e2` +- **Fix** * `kiss_modem` - Improved RX delivery and noise-floor sampling 2026-02-06 * `f445b5ac` +- **Improvement** - Better NTP synchronization in MQTTBridge 2026-02-06 * `ba4243b5` +- **Improvement** - WiFi management and fragmentation recovery 2026-02-04 * `26aaace9` +- **New** - PSRAM-backed memory management in MQTTBridge 2026-02-04 * `1d0d33f9` +- **New** - PSRAM diagnostics at MQTTBridge init 2026-02-04 * `abcfe609` +- **Build** - mbedTLS configuration tuned across variants 2026-02-04 * `21158e1f` +- **Fix** * `kiss` - Periodic noise-floor calibration and AGC reset 2026-02-03 * `0fb57033` +- **Fix** - Memory use and status reporting in the MQTT bridge 2026-02-02 * `498566e6` ### January 2026 -- **Internal** — Clarified packet ownership / memory management in MQTTBridge 2026-01-31 · `f91b715e` -- **New** — `recv_errors` in the `CMD_GET_STATS` packets response 2026-01-29 · `019bbf74` -- **New** — Receive-error tracking in Dispatcher and the message builder 2026-01-29 · `ee577314` -- **Fix** — Display header include for heltec_v4 when `DISPLAY_CLASS` is set directly 2026-01-27 · `bb27cded` -- ⬆ **Upstream sync** — Synced with upstream MeshCore dev 2026-01-27 · `d96c900f` -- ⬆ **Upstream sync** — Synced with upstream MeshCore dev 2026-01-26 · `2a84e58b` -- **Improvement** — MQTTBridge memory usage and NTP handling 2026-01-24 · `9e1ce23e` -- **Improvement** — MQTTBridge status handling and publish logic 2026-01-23 · `c67c3295` -- **Internal** — MQTTBridge connection handling for stability / responsiveness 2026-01-22 · `c055228c` -- ⬆ **Upstream sync** — Synced with upstream MeshCore dev 2026-01-21 · `39ba020f` -- **Internal** — MQTTBridge moved onto a FreeRTOS task for responsiveness 2026-01-21 · `ed27ebd0` -- **New** — Configurable WiFi power-save mode preference 2026-01-18 · `56f98997` -- **Internal** — HeltecTrackerV2 / HeltecV4 init for GC1109 FEM 2026-01-13 · `3eaca31f` -- **Docs** — Owner configuration commands documented 2026-01-12 · `58cdd60c` -- **Internal** — Advert timer type consistency in MyMesh / SensorMesh 2026-01-08 · `ffa2a1ec` -- **New** — Device metadata setup at bridge init / restart 2026-01-03 · `2bd61c13` -- **Internal** — Config validation allows optional username / password 2026-01-03 · `d1093b43` -- **Internal** — JWTHelper / MQTTBridge efficiency cleanup 2026-01-02 · `e754317c` -- **Fix** — MQTT connection stability and CLI responsiveness 2026-01-02 · `98f176c7` -- **New** — Neighbour-info sorting; MQTTBridge memory tuning 2026-01-01 · `2185523d` +- **Internal** - Clarified packet ownership / memory management in MQTTBridge 2026-01-31 * `f91b715e` +- **New** - `recv_errors` in the `CMD_GET_STATS` packets response 2026-01-29 * `019bbf74` +- **New** - Receive-error tracking in Dispatcher and the message builder 2026-01-29 * `ee577314` +- **Fix** - Display header include for heltec_v4 when `DISPLAY_CLASS` is set directly 2026-01-27 * `bb27cded` +- [UP] **Upstream sync** - Synced with upstream MeshCore dev 2026-01-27 * `d96c900f` +- [UP] **Upstream sync** - Synced with upstream MeshCore dev 2026-01-26 * `2a84e58b` +- **Improvement** - MQTTBridge memory usage and NTP handling 2026-01-24 * `9e1ce23e` +- **Improvement** - MQTTBridge status handling and publish logic 2026-01-23 * `c67c3295` +- **Internal** - MQTTBridge connection handling for stability / responsiveness 2026-01-22 * `c055228c` +- [UP] **Upstream sync** - Synced with upstream MeshCore dev 2026-01-21 * `39ba020f` +- **Internal** - MQTTBridge moved onto a FreeRTOS task for responsiveness 2026-01-21 * `ed27ebd0` +- **New** - Configurable WiFi power-save mode preference 2026-01-18 * `56f98997` +- **Internal** - HeltecTrackerV2 / HeltecV4 init for GC1109 FEM 2026-01-13 * `3eaca31f` +- **Docs** - Owner configuration commands documented 2026-01-12 * `58cdd60c` +- **Internal** - Advert timer type consistency in MyMesh / SensorMesh 2026-01-08 * `ffa2a1ec` +- **New** - Device metadata setup at bridge init / restart 2026-01-03 * `2bd61c13` +- **Internal** - Config validation allows optional username / password 2026-01-03 * `d1093b43` +- **Internal** - JWTHelper / MQTTBridge efficiency cleanup 2026-01-02 * `e754317c` +- **Fix** - MQTT connection stability and CLI responsiveness 2026-01-02 * `98f176c7` +- **New** - Neighbour-info sorting; MQTTBridge memory tuning 2026-01-01 * `2185523d` ### December 2025 -- **Docs** — WiFi power-save configuration documented 2025-12-30 · `915f8b8e` -- **Internal** — `loadPrefs` handles fresh installs and upgrades; migrates old prefs 2025-12-30 · `8fa99611` -- **Improvement** — Loop prioritizes radio RX for responsiveness 2025-12-29 · `617e3b17` -- **Internal** — Defer broker connect until WiFi + NTP ready; socket error logging 2025-12-17 · `5d4f1e4c` -- **Improvement** — Event-driven WiFi diagnostics and faster reconnect 2025-12-12 · `f7f1f838` -- **New** — WiFi power-save mode configuration (none / min / max) 2025-12-08 · `34c8bea7` -- **Fix** — LPS22HB pressure now reported in hPa (was kPa) 2025-12-08 · `b91b854a` -- **New** — WiFi TX power configuration (default 11 dBm) 2025-12-07 · `b82d7e43` -- **New** — Default bridge settings for fresh installs; JSON reuse to avoid fragmentation 2025-12-07 · `0b7c0888` -- **Improvement** — MQTT debug macros check Serial to prevent hangs 2025-12-06 · `3589043b` -- **Change** — Removed redundant settings writes in `savePrefs` 2025-12-04 · `03d519f8` -- **Build** — Source filter for the Xiao S3 WIO variant 2025-12-01 · `4f548ffc` +- **Docs** - WiFi power-save configuration documented 2025-12-30 * `915f8b8e` +- **Internal** - `loadPrefs` handles fresh installs and upgrades; migrates old prefs 2025-12-30 * `8fa99611` +- **Improvement** - Loop prioritizes radio RX for responsiveness 2025-12-29 * `617e3b17` +- **Internal** - Defer broker connect until WiFi + NTP ready; socket error logging 2025-12-17 * `5d4f1e4c` +- **Improvement** - Event-driven WiFi diagnostics and faster reconnect 2025-12-12 * `f7f1f838` +- **New** - WiFi power-save mode configuration (none / min / max) 2025-12-08 * `34c8bea7` +- **Fix** - LPS22HB pressure now reported in hPa (was kPa) 2025-12-08 * `b91b854a` +- **New** - WiFi TX power configuration (default 11 dBm) 2025-12-07 * `b82d7e43` +- **New** - Default bridge settings for fresh installs; JSON reuse to avoid fragmentation 2025-12-07 * `0b7c0888` +- **Improvement** - MQTT debug macros check Serial to prevent hangs 2025-12-06 * `3589043b` +- **Change** - Removed redundant settings writes in `savePrefs` 2025-12-04 * `03d519f8` +- **Build** - Source filter for the Xiao S3 WIO variant 2025-12-01 * `4f548ffc` ### November 2025 -- **New** — `adc_multiplier` added to NodePrefs 2025-11-23 · `8fbdc98a` -- **Change** — IATA codes uppercased for consistency 2025-11-23 · `5cadf3d9` -- **Change** — `CMD_GET_STATS` split into core / radio / packet sub-types 2025-11-17 · `a3c9a073` -- **New** — IP address shown for MQTT bridge devices in UITask 2025-11-16 · `6b0c0e93` -- **New** — T-Beam ESP32 support; error log when source = tx but TX logging is off 2025-11-16 · `3c7ac9d1` -- **Build** — `MQTT_DEBUG` enabled for several build configs 2025-11-16 · `3726c472` -- **New** — MQTT observer support for Room Servers 2025-11-15 · `0cd0c36a` -- **Change** — Switched MQTT configs to observer mode across variants 2025-11-13 · `304a32db` -- **Internal** — Client version string generation 2025-11-13 · `aa242e78` -- **Improvement** — WiFi power-save + reduced TX power to limit LoRa interference; retry fix when no brokers connected 2025-11-10 · `bafa5426` -- **Internal** — Statistics use binary frames instead of JSON 2025-11-09 · `80d6dd43` -- **New** — IATA validation blocks publishing without an IATA code 2025-11-08 · `25d40f02` -- **Fix** — Restored status-publish retry delay to prevent server spam 2025-11-08 · `9e06fffc` -- **New** — Added missing token-expiration / reconnection member variables 2025-11-08 · `453c4184` -- **Change** — Removed WiFi credentials from platformio.ini (use CLI / build flags) 2025-11-08 · `921c6112` -- **Internal** — MQTT preferences handling in CommonCLI 2025-11-08 · `5a816be8` -- **Internal** — `formatStatsReply` uses the new member variables 2025-11-08 · `c9aa536c` -- **Build** — Explicit `paulstoffregen/Time` dependency to fix PIO compile 2025-11-07 · `72ca50b9` -- **Build** — Dropped the CayenneLPP patch script; updated ArduinoJson 2025-11-07 · `fef9225b` -- **Fix** — Timezone command parsing lengths corrected 2025-11-07 · `b89ceeea` -- **New** — Statistics commands and response handling in MyMesh 2025-11-07 · `df4dab85` -- **New** — MQTT email support in CommonCLI / JWTHelper 2025-11-06 · `1ed18f9e` -- **New** — MQTT owner public-key support 2025-11-06 · `e5dbd56b` -- **Improvement** — Status reporting and stats collection 2025-11-01 · `e4ff06cb` +- **New** - `adc_multiplier` added to NodePrefs 2025-11-23 * `8fbdc98a` +- **Change** - IATA codes uppercased for consistency 2025-11-23 * `5cadf3d9` +- **Change** - `CMD_GET_STATS` split into core / radio / packet sub-types 2025-11-17 * `a3c9a073` +- **New** - IP address shown for MQTT bridge devices in UITask 2025-11-16 * `6b0c0e93` +- **New** - T-Beam ESP32 support; error log when source = tx but TX logging is off 2025-11-16 * `3c7ac9d1` +- **Build** - `MQTT_DEBUG` enabled for several build configs 2025-11-16 * `3726c472` +- **New** - MQTT observer support for Room Servers 2025-11-15 * `0cd0c36a` +- **Change** - Switched MQTT configs to observer mode across variants 2025-11-13 * `304a32db` +- **Internal** - Client version string generation 2025-11-13 * `aa242e78` +- **Improvement** - WiFi power-save + reduced TX power to limit LoRa interference; retry fix when no brokers connected 2025-11-10 * `bafa5426` +- **Internal** - Statistics use binary frames instead of JSON 2025-11-09 * `80d6dd43` +- **New** - IATA validation blocks publishing without an IATA code 2025-11-08 * `25d40f02` +- **Fix** - Restored status-publish retry delay to prevent server spam 2025-11-08 * `9e06fffc` +- **New** - Added missing token-expiration / reconnection member variables 2025-11-08 * `453c4184` +- **Change** - Removed WiFi credentials from platformio.ini (use CLI / build flags) 2025-11-08 * `921c6112` +- **Internal** - MQTT preferences handling in CommonCLI 2025-11-08 * `5a816be8` +- **Internal** - `formatStatsReply` uses the new member variables 2025-11-08 * `c9aa536c` +- **Build** - Explicit `paulstoffregen/Time` dependency to fix PIO compile 2025-11-07 * `72ca50b9` +- **Build** - Dropped the CayenneLPP patch script; updated ArduinoJson 2025-11-07 * `fef9225b` +- **Fix** - Timezone command parsing lengths corrected 2025-11-07 * `b89ceeea` +- **New** - Statistics commands and response handling in MyMesh 2025-11-07 * `df4dab85` +- **New** - MQTT email support in CommonCLI / JWTHelper 2025-11-06 * `1ed18f9e` +- **New** - MQTT owner public-key support 2025-11-06 * `e5dbd56b` +- **Improvement** - Status reporting and stats collection 2025-11-01 * `e4ff06cb` ### October 2025 -- **Improvement** — MQTTBridge logging and connection handling 2025-10-31 · `7df28557` -- **Improvement** — Packet handling and memory management in MQTTMessageBuilder / MQTTBridge 2025-10-31 · `eee02025` -- **New** — Memory monitoring and queue-size reporting 2025-10-29 · `e77beff0` -- **Internal** — In-place base64url encoding; cleanup frees queued packets / timezone objects 2025-10-27 · `1219668f` -- **Change** — Defer MQTT connect until WiFi credentials are valid (requires reboot) 2025-10-27 · `3851a84b` -- **Improvement** — Dynamic build date and client versioning 2025-10-26 · `c23663da` -- **New** — CLI config for `mqtt.server` / `username` / `password` / `port` 2025-10-26 · `eab70c3c` -- **New** — Let's Mesh Analyzer integration 2025-10-26 · `a6453556` -- **New** — Station G2 repeater bridge build target; docs 2025-10-26 · `f6a77d2f` -- **New** — Timezone handling; raw-data packet output with proper hashes / timestamps 2025-10-25 · `787af7b0` -- **New** — Timezone support in the MQTT Bridge and CLI 2025-10-25 · `26c4b970` -- **New** — Initial MQTT Bridge implementation with WiFi CLI configuration 2025-10-25 · `efc15eb6` +- **Improvement** - MQTTBridge logging and connection handling 2025-10-31 * `7df28557` +- **Improvement** - Packet handling and memory management in MQTTMessageBuilder / MQTTBridge 2025-10-31 * `eee02025` +- **New** - Memory monitoring and queue-size reporting 2025-10-29 * `e77beff0` +- **Internal** - In-place base64url encoding; cleanup frees queued packets / timezone objects 2025-10-27 * `1219668f` +- **Change** - Defer MQTT connect until WiFi credentials are valid (requires reboot) 2025-10-27 * `3851a84b` +- **Improvement** - Dynamic build date and client versioning 2025-10-26 * `c23663da` +- **New** - CLI config for `mqtt.server` / `username` / `password` / `port` 2025-10-26 * `eab70c3c` +- **New** - Let's Mesh Analyzer integration 2025-10-26 * `a6453556` +- **New** - Station G2 repeater bridge build target; docs 2025-10-26 * `f6a77d2f` +- **New** - Timezone handling; raw-data packet output with proper hashes / timestamps 2025-10-25 * `787af7b0` +- **New** - Timezone support in the MQTT Bridge and CLI 2025-10-25 * `26c4b970` +- **New** - Initial MQTT Bridge implementation with WiFi CLI configuration 2025-10-25 * `efc15eb6` - + <------- OTA_PROOF(mid, block_idx, n_proof, proof) + (verify proof vs merkle_root -> write block -> write leaves[i]) + when all blocks present: verify full merkle_root + image_hash -> COMPLETE ``` ### 8.4 Message bodies (transfer) @@ -399,14 +399,14 @@ OTA_MANIFEST: manifest_id[4] frag_idx(1) frag_total(1) bytes[] # up OTA_REQ: manifest_id[4] block_idx(uint16) want_mask(uint16) # bit k = send fragment k of block OTA_DATA: manifest_id[4] block_idx(uint16) frag_off(uint16) data[] # up to OTA_FRAG_DATA=160 B OTA_REQ_PROOF: manifest_id[4] block_idx(uint16) -OTA_PROOF: manifest_id[4] block_idx(uint16) n_proof(1) proof[] # n_proof × 4 bytes +OTA_PROOF: manifest_id[4] block_idx(uint16) n_proof(1) proof[] # n_proof x 4 bytes OTA_GET_LEAVES: manifest_id[4] want_mask(uint16) # bit k = send leaves fragment k; 0xFFFF = all OTA_LEAVES: manifest_id[4] frag_idx(1) frag_total(1) bytes[] # up to OTA_LEAVES_FRAG=176 leaf bytes ``` -- **Warm-start / leaf-diff (`OTA_GET_LEAVES`/`OTA_LEAVES`) — motatool folder-capture only.** Capturing a +- **Warm-start / leaf-diff (`OTA_GET_LEAVES`/`OTA_LEAVES`) - motatool folder-capture only.** Capturing a device's firmware into a `motatool serve` folder is slow (a full image is hundreds of blocks). Because - builds here are non-deterministic, you cannot reproduce the exact target on the host — but a *similar* + builds here are non-deterministic, you cannot reproduce the exact target on the host - but a *similar* build (e.g. a fresh recompile) is ~99% identical. So `motatool serve --seed ` stages that build's payload into the destination `.part`, and `ota pull <#> folder validate` makes the fetcher (1) bulk- fetch the target's `leaves[]` via `OTA_GET_LEAVES`/`OTA_LEAVES` (bitmap-fragmented with a `want_mask`, same @@ -414,21 +414,21 @@ OTA_LEAVES: manifest_id[4] frag_idx(1) frag_total(1) bytes[] # up manifest root (authenticate), then (3) keep every seeded block whose leaf matches and pull full `OTA_DATA` only for the blocks that differ. The `want_mask` is a fixed **uint16**, so `leaves[]` is capped at `OTA_LEAVES_MAXFRAG=16` fragments (`OTA_DIFF_MAX_BLOCKS=704` blocks); larger images just fall back to a full - fetch. **Normal P2P nodes never use this** — they target only the blocks they want; the only always-on part + fetch. **Normal P2P nodes never use this** - they target only the blocks they want; the only always-on part is answering `OTA_GET_LEAVES` with leaves the node already holds, so any node's firmware can be captured. -- **Block ⇆ fragments:** a 1 KB block is split into self-describing `OTA_DATA` fragments. `frag_off` is the - byte offset of `data` within the block, so the global position is `block_idx*block_size + frag_off` — +- **Block <-> fragments:** a 1 KB block is split into self-describing `OTA_DATA` fragments. `frag_off` is the + byte offset of `data` within the block, so the global position is `block_idx*block_size + frag_off` - a fragment is self-placing when returned by the source. The fetcher tracks a per-block slice bitmap and reassembles before requesting the proof. - **Fragment-level requests (anti-deadlock + anti-congestion):** both `OTA_REQ` and `OTA_GET_MANIFEST` carry - a `want_mask` — bit *k* asks for fragment *k*. A fetcher requests the **full** mask on the first ask + a `want_mask` - bit *k* asks for fragment *k*. A fetcher requests the **full** mask on the first ask (`(1< 7 frags per 1 KB block | +| `OTA_MANIFEST` | 7 B | `OTA_MF_FRAG = 176` -> signed manifest ~ 2 frags | +| `OTA_HAVE` | 12 B | 12 rows x 14 B per fragment | +| `OTA_PROOF` | 8 B | up to ~44 sibling digests (>> any real tree) | -A served mota supports up to `OTA_MAX_BLOCK/4` leaves in the default 4 KB proof scratch (≤1024 blocks ≈ 1 MB +A served mota supports up to `OTA_MAX_BLOCK/4` leaves in the default 4 KB proof scratch (<=1024 blocks ~ 1 MB payload); larger self-images pass a bigger scratch buffer. ### 8.6 Temporary-radio and transfer boundary @@ -459,16 +459,16 @@ update as one transmitter and one receiver while still allowing active temporary - **`target_id`** (4 B): `sha2-256:4(pio_env_name)` (little-endian uint32). The env name uniquely captures hardware **and** role/partition, so a node auto-fetches only matching firmware (a companion image is not fetched onto a repeater even though it shares `hw_id`). It is **self-described in the firmware's EndF** - (§2, written by `pio_endf.py`) and read via `ota_self_firmware()`, so it is correct on any build; + (Section 2, written by `pio_endf.py`) and read via `ota_self_firmware()`, so it is correct on any build; `-D MOTA_TARGET_ID` / `MainBoard::getOtaTargetId()` is the fallback when no EndF identity is present. `tools/mota` reads it from the firmware's EndF (or `--target-env`). A manual `ota pull`/`want` can override - target (deliberate role switch); the `hw_id` brick-safety gate (§4) still applies at apply time. -- **`target_id` vs `hw_id`** — complementary, not redundant: `target_id` is the fetch-routing key + target (deliberate role switch); the `hw_id` brick-safety gate (Section 4) still applies at apply time. +- **`target_id` vs `hw_id`** - complementary, not redundant: `target_id` is the fetch-routing key (hw + role + partition); `hw_id` is the human-readable brick-safety key (hardware only). Same board, two - roles ⇒ same `hw_id`, different `target_id`. + roles => same `hw_id`, different `target_id`. - **Naming a `target_id` locally:** only the 4-byte `target_id` ever travels on the wire. To show *which* - board/role a target is, a node (and `motatool`) reverse-looks-it-up in `src/helpers/ota/OtaTargets.h` — - a generated `target_id → env-name` table covering every `ENABLE_OTA` env (`tools/mota/gen_targets.py`, + board/role a target is, a node (and `motatool`) reverse-looks-it-up in `src/helpers/ota/OtaTargets.h` - + a generated `target_id -> env-name` table covering every `ENABLE_OTA` env (`tools/mota/gen_targets.py`, resolved from `pio project config`). So `ota ls` can render `[Heltec_v3_repeater]` for a neighbour's beacon without the string being transmitted. Unknown ids show as `other hw` / `N/A`. - **`fw_version`:** packed comparable uint32 (`MAJOR<<24 | MINOR<<16 | PATCH<<8 | pre`); also self-described @@ -480,9 +480,9 @@ update as one transmitter and one receiver while still allowing active temporary - **Signing & allowlist:** a node keeps a runtime allowlist of trusted Ed25519 signer pubkeys (none embedded in firmware; `ota key add/list/rm`). A `.mota` is eligible for **auto-install** only if signed by an allowlisted key, the signature verifies, and `image_hash` matches; otherwise it is manual-apply only with - explicit confirmation. **Transfer needs no trust** — blocks are content-addressed against the signed root. -- **Policies (persisted):** `autofetch` ∈ {off, any, signed} (default off) gates automatic block fetching of - own-target adverts; `autoinstall` ∈ {off, trusted} (default off) gates auto-apply of a COMPLETE signed + + explicit confirmation. **Transfer needs no trust** - blocks are content-addressed against the signed root. +- **Policies (persisted):** `autofetch` in {off, any, signed} (default off) gates automatic block fetching of + own-target adverts; `autoinstall` in {off, trusted} (default off) gates auto-apply of a COMPLETE signed + allowlisted fetch. Conservative defaults: a fresh node discovers + announces but never fetches/installs without operator intent. - **Supersession:** a newer version announced mid-download does not abort the in-progress transfer @@ -501,15 +501,15 @@ targets an external mota. Every fetch message carries `manifest_id`, so dispatch The same host-folder link is also a **pull destination** (the reverse direction): `ota pull <#> folder` fetches a `.mota` off the mesh and streams it onto the host as `.mota` via the seeder STORAGE ops (`OP_STAT/BEGIN/WRITE/SREAD/FIN`, see `MotaSeederProto.h`), using a `FolderMotaStore` as the fetch's -`OtaStore` instead of RAM/flash. This captures an exact copy of a device's firmware — e.g. to build a delta +`OtaStore` instead of RAM/flash. This captures an exact copy of a device's firmware - e.g. to build a delta against firmware you don't have. Resume is bookkeeping-free: `BEGIN` 0xFF-fills the file and, on reconnect -after a link drop (the fetch PAUSES, holding progress on the host — no RAM/flash fallback), `STAT`+`SREAD` +after a link drop (the fetch PAUSES, holding progress on the host - no RAM/flash fallback), `STAT`+`SREAD` let the fetcher recompute and refill only the missing blocks. ### 10.1 The `MotaSource` abstraction (`OtaSource.h`) Transport-agnostic provider of one or more complete `.mota` as random-access bytes. The same serve code -drives USB-serial, BLE, a WiFi URL list, an NFS/samba mount, etc. — only `read()` differs. +drives USB-serial, BLE, a WiFi URL list, an NFS/samba mount, etc. - only `read()` differs. ```cpp struct MotaDesc { // catalog metadata + region offsets (no whole image in RAM) @@ -523,25 +523,25 @@ class MotaSource { }; ``` -To serve an external mota the node reads its manifest-minus-leaves + `leaves[]` into RAM (≤4 KB for ≤1024 +To serve an external mota the node reads its manifest-minus-leaves + `leaves[]` into RAM (<=4 KB for <=1024 blocks) and streams payload blocks from the source on demand; proofs are generated from the read leaves. ### 10.2 The `mota-seeder` transport (`MotaSeederProto.h`) A `MotaSource` is fed by a host that serves a folder over the device's **USB serial** (the same console the -CLI uses — no extra hardware) or, on an ESP32 WiFi companion, over **WiFi (TCP)**. The host is the +CLI uses - no extra hardware) or, on an ESP32 WiFi companion, over **WiFi (TCP)**. The host is the standalone Rust tool [`motatool`](https://github.com/vk496/motatool) (`motatool serve --serial ` / `--tcp `, which also builds + verifies + inspects `.mota`). The device only emits request frames *while actively serving a fetch*, and reads the reply synchronously, so over the shared USB console binary frames coexist with the text CLI/logs (resync on magic + checksum). Little-endian, XOR-checksummed: ``` -request (device → host): 'M' 'S' op(1) args... xsum(1 = XOR of op+args) -response (host → device): 'm' 's' op(1) status(1) payload... xsum(1 = XOR of all prior) +request (device -> host): 'M' 'S' op(1) args... xsum(1 = XOR of op+args) +response (host -> device): 'm' 's' op(1) status(1) payload... xsum(1 = XOR of all prior) -OP_COUNT 0x01 args: - → payload: count(1) -OP_DESCRIBE 0x02 args: idx(1) → payload: MotaDesc wire (38 B) -OP_READ 0x03 args: idx(1) off(4) len(2) → payload: len bytes +OP_COUNT 0x01 args: - -> payload: count(1) +OP_DESCRIBE 0x02 args: idx(1) -> payload: MotaDesc wire (38 B) +OP_READ 0x03 args: idx(1) off(4) len(2) -> payload: len bytes MotaDesc wire (38 B): mid[4] target_id(4) fw_version(4) codec(1) flags(1) total_size(4) leaves_off(4) block_count(4) payload_off(4) payload_size(4) status: 0 = OK, non-zero = error (out of range / past EOF). @@ -558,19 +558,19 @@ Device CLI: `ota folder on` (attach + announce), `ota folder` (list), `ota folde `OTA_FOLDER_SERIAL` (default stream = console `Serial`; override `OTA_FOLDER_SERIAL_STREAM` + define `OTA_FOLDER_SERIAL_BEGIN` for a dedicated UART). On an ESP32 WiFi companion the node also runs a second `WiFiServer` on a **dedicated seeder port** (`OTA_SEEDER_TCP_PORT`, default `5001`), separate from the -companion app port (`TCP_PORT`, default `5000`) — so `motatool serve --tcp` can feed updates while a phone +companion app port (`TCP_PORT`, default `5000`) - so `motatool serve --tcp` can feed updates while a phone app stays connected. The node auto-attaches the source when a seeder client connects and detaches when it closes (no `ota folder on` needed over TCP). Verified on hardware: a RAK4631 relays a host folder to a Heltec V3 over one USB cable, and a host feeds a Heltec V3 over WiFi (`:5001`) while the companion serves a -phone on `:5000` — every block merkle-checked. +phone on `:5000` - every block merkle-checked. **Transport-agnostic by design.** The request/response *semantics* (`COUNT` / `DESCRIBE(idx)` / `READ(idx, off, len)` over a folder catalog) are independent of the link. The 2-byte magic + XOR checksum + resync framing above exists for the shared USB-UART (an unframed byte stream); it is harmless over a -reliable stream and the **WiFi (TCP)** transport reuses it as-is — both ends just treat the socket as a +reliable stream and the **WiFi (TCP)** transport reuses it as-is - both ends just treat the socket as a byte stream (on-device, `SerialMotaSource` runs verbatim over an Arduino `Stream`-compatible `WiFiClient`; `motatool`'s `TcpTransport` mirrors its `SerialTransport`). A future framed link such as **BLE GATT** (an -Android phone relaying a folder) could carry the same ops with no magic/checksum at all — a request +Android phone relaying a folder) could carry the same ops with no magic/checksum at all - a request characteristic write delivers `op + args`, the reply notifies `status + payload`. `motatool` reflects this split: a transport-free `SeederCore` (the catalog logic) under a swappable framing/transport layer. @@ -582,7 +582,7 @@ User-facing OTA data should travel via `CMD_OTA_*` companion binary frames; the debug/operator oriented and replies are `snprintf`-bounded into a 160-byte buffer. Commands take intuitive aliases (matched by the first word; see `is_cmd` in `OtaCli.cpp`) so they're easy -to type and read — `status`/`neighbors`/`pull`/`drop`/`applydelta` are the canonical names, the aliases are +to type and read - `status`/`neighbors`/`pull`/`drop`/`applydelta` are the canonical names, the aliases are the recommended user-facing forms. Output is plain-language (a user-facing guide lives at [ota_user_guide.md](ota_user_guide.md)). @@ -613,19 +613,19 @@ ota dev ... bring-up helpers (stage/recv/serve/verify) the modified bootloader (`Adafruit_nRF52_Bootloader_OTAFIX`). The bootloader: 1. **scans flash for `MAGIC`** to find the staged `.mota` (it must NOT trust any stored size), 2. re-checks `TRAILER`, `image_hash`, `approval == "APRV"`, and that the delta's `base_hash` equals the - running firmware's `EndF.body_hash` (recomputed by scanning for `EndF` — never trust `bank_0_size`), + running firmware's `EndF.body_hash` (recomputed by scanning for `EndF` - never trust `bank_0_size`), 3. applies the in-place codec over the app region and boots only if the result hashes to `image_hash`. -The signature proves author authenticity; `approval` proves local owner consent — both required to apply. +The signature proves author authenticity; `approval` proves local owner consent - both required to apply. -> **Bootloader testing note:** always test apply with a *real different* image (base ≠ target). A same-image -> (X→X) "delta" trivially reproduces the target and gives a false positive. +> **Bootloader testing note:** always test apply with a *real different* image (base != target). A same-image +> (X->X) "delta" trivially reproduces the target and gives a false positive. --- ## 13. Versioning of this spec -`format_ver = 2`. A parser accepts exactly this value and rejects anything else — there is one container +`format_ver = 2`. A parser accepts exactly this value and rejects anything else - there is one container format, fixed-layout, and no compatibility shims to carry. If the format ever needs to change, bump `format_ver`; the multihash `hash_algo` separately allows swapping the digest family without a format bump. Unknown `codec_id` / `ota_msg_type` values are ignored (a node simply won't fetch what it can't apply). diff --git a/docs/ota_user_guide.md b/docs/ota_user_guide.md index 7abcf7d7..b92028c8 100644 --- a/docs/ota_user_guide.md +++ b/docs/ota_user_guide.md @@ -1,7 +1,7 @@ -# Updating your node over the air (OTA) — user guide +# Updating your node over the air (OTA) - user guide This guide is for **node operators**: how to update your MeshCore device's firmware over the radio, in -plain language. No cables, no programmer — your node can download a new firmware from a neighbour and +plain language. No cables, no programmer - your node can download a new firmware from a neighbour and install it. (For the technical wire format, see [the OTA protocol spec](ota_protocol.md).) LoRa OTA is present only in supported Keymind repeater build artifacts whose filename contains `-ota-`. @@ -42,23 +42,23 @@ battery telemetry, and `battery.alert` behavior. ## The important part first: it's safe - **Nothing installs by itself.** Your node can *discover* and *download* an update in the background, but - it only **installs** when you say so (unless you deliberately turn on auto-install — see below). + it only **installs** when you say so (unless you deliberately turn on auto-install - see below). - **Bad downloads can't sneak in.** Every piece of the firmware is checked against a cryptographic fingerprint as it arrives, and the whole image is verified again before install. A corrupt or tampered download is rejected, not installed. - **You choose who to trust.** Updates can be *signed* by their author. You can tell your node to only auto-install firmware signed by keys you've added. -- **It won't disrupt your mesh.** OTA traffic is always the **lowest priority** — your node only spends +- **It won't disrupt your mesh.** OTA traffic is always the **lowest priority** - your node only spends spare airtime on it. Messages and routing always come first; a busy node simply updates later. Think of it as *"eventually upgradable."* - **It can recover.** If an install ever fails, the node falls back to a safe recovery mode (you can - re-flash a known-good firmware over USB) — it won't be left bricked. + re-flash a known-good firmware over USB) - it won't be left bricked. --- ## How to talk to your node -Connect to your node's **console** — usually a USB serial terminal at **115200 baud** (or whatever tool +Connect to your node's **console** - usually a USB serial terminal at **115200 baud** (or whatever tool you already use to manage the node). You type `ota ...` commands and the node replies in plain words. The commands have short, friendly names (and most accept aliases, so you don't have to remember exact @@ -77,14 +77,14 @@ ota status Shows your current firmware version, your node's update "target" (its hardware/role id), and whether a download is in progress. -For a denser admin view — your firmware's content id (`mid`) **and** its body hash, the fingerprint of the -set you're serving, live download progress, and the current policy — use: +For a denser admin view - your firmware's content id (`mid`) **and** its body hash, the fingerprint of the +set you're serving, live download progress, and the current policy - use: ``` ota stats ``` -On a remote node this is **admin-only** (the remote command console requires the admin password) — send it +On a remote node this is **admin-only** (the remote command console requires the admin password) - send it from the app's repeater command screen, or the WiFi/serial OTA console. ### 2. Find updates available near me @@ -93,12 +93,12 @@ from the app's repeater command screen, or the WiFi/serial OTA console. ota ls ``` -Your node asks around and lists the firmware updates other nodes nearby are offering, in plain words — +Your node asks around and lists the firmware updates other nodes nearby are offering, in plain words - each with a **number**, its version, whether it's a full image or a small delta, how many nodes have it, and how recently it was seen. For example: ``` -Updates nearby (2 src) — `ota get <#>` to download: +Updates nearby (2 src) - `ota get <#>` to download: 1) v1.2.3 delta [yours] 3n 5s 2) v1.2.0 full [other hw] 1n 12s [downloading] ``` @@ -106,11 +106,11 @@ Updates nearby (2 src) — `ota get <#>` to download: Each row shows the version, full-vs-delta, **whether it fits your node**, how many nodes have it, and how long ago it was seen. The fit marker: -- **[yours]** — built for your exact hardware **and** role; safe to install. -- **[other hw]** — a different board or role (e.g. a companion image, or another board). Don't install it. -- **[?]** — can't tell (a build with no target id set, e.g. a bare IDE build rather than a release build). +- **[yours]** - built for your exact hardware **and** role; safe to install. +- **[other hw]** - a different board or role (e.g. a companion image, or another board). Don't install it. +- **[?]** - can't tell (a build with no target id set, e.g. a bare IDE build rather than a release build). -Run it again after a few seconds — discovery happens in the background, so the list fills in. Nothing is +Run it again after a few seconds - discovery happens in the background, so the list fills in. Nothing is downloaded yet; this is just looking around. (`ota neighbors` / `ota updates` also work.) ### 3. Download an update @@ -123,32 +123,32 @@ ota pull 1 folder # capture it onto a connected motatool folder as ` and add **`validate`**: the node fetches just the target's block fingerprints, keeps -every block your seed already matches, and pulls over the radio only the handful that actually differ — +every block your seed already matches, and pulls over the radio only the handful that actually differ - turning a ~30-minute capture into seconds. The result is still a byte-exact, verified copy of the target. -*Where the seed comes from:* it is the **`--seed ` you pass to `motatool serve`** — **not** a file you +*Where the seed comes from:* it is the **`--seed ` you pass to `motatool serve`** - **not** a file you drop into the capture (`--dir`) folder, which is only the destination and starts empty. There is exactly one configured seed. When you run `... folder validate`, the node asks motatool to begin the capture and motatool -stamps that seed's payload into the fresh `.part` in the same step — so it is always the file you named, with +stamps that seed's payload into the fresh `.part` in the same step - so it is always the file you named, with no guessing. **`validate` is the switch:** a plain `folder` pull ignores any seed and fetches from scratch; re-running a `validate` pull re-begins fresh (it never resumes a stale partial). Nothing about the seed is -trusted — every kept block is checked against the target's own fingerprints, so a mismatched or missing seed +trusted - every kept block is checked against the target's own fingerprints, so a mismatched or missing seed just means those blocks are fetched over the radio (correct result, only slower). The node fetches from one source, **at low priority**, one block at a time. Mesh repeaters may carry the packets, but only while their temporary-radio windows are active. Check progress with `ota status`. -If a `folder` pull loses its link mid-transfer, `ota status` shows **paused** — the host keeps the +If a `folder` pull loses its link mid-transfer, `ota status` shows **paused** - the host keeps the partial and the pull resumes (filling only what's missing) the moment you reconnect motatool; it never falls back to flash. To **stop** a download you no longer want: @@ -166,7 +166,7 @@ ota install The node verifies the firmware one last time, and if everything checks out it installs it and **reboots into the new version**. If the check fails, it tells you why and does **not** install. (If you haven't -added the signer's key, an unsigned/untrusted image will only install with this explicit command — never +added the signer's key, an unsigned/untrusted image will only install with this explicit command - never automatically.) After it reboots, run `ota status` to confirm the new version. @@ -174,7 +174,7 @@ After it reboots, run `ota status` to confirm the new version. ### 5. If something goes wrong - A download that stalls or gets interrupted just **resumes** later, or you can `ota cancel` and try again. -- If an **install** fails, the node won't boot a broken image — it lands in **recovery mode**: +- If an **install** fails, the node won't boot a broken image - it lands in **recovery mode**: - **nRF52:** it appears as a USB drive; drag a known-good firmware `.uf2` for that exact board onto it to recover. - **ESP32:** it keeps the previous firmware in the other slot and rolls back. @@ -184,7 +184,7 @@ After it reboots, run `ota status` to confirm the new version. ## Optional: let it update automatically -By default your node only *discovers* updates — it won't download or install on its own. If you want more +By default your node only *discovers* updates - it won't download or install on its own. If you want more automation (e.g. for a remote node you can't easily reach), you can opt in. These settings are saved. ``` @@ -230,18 +230,18 @@ install anything yourself, on your own responsibility. ### Relay a folder of firmware from a computer If your node is connected to a computer (e.g. a gateway on a Raspberry Pi), it can **hand out** a whole -folder of firmware files to the mesh — without storing them itself. Useful for seeding a new release to a +folder of firmware files to the mesh - without storing them itself. Useful for seeding a new release to a remote area. -1. Put the firmware files (`.mota` files — see below) in a folder on the computer. -2. Install the helper tool once — the standalone `motatool` CLI () — - then point it at your node and the folder — over the node's **USB serial**, or over **WiFi** if it's +1. Put the firmware files (`.mota` files - see below) in a folder on the computer. +2. Install the helper tool once - the standalone `motatool` CLI () - + then point it at your node and the folder - over the node's **USB serial**, or over **WiFi** if it's an ESP32 companion on your network: ``` git clone https://github.com/vk496/motatool && cargo install --path ./motatool # over USB serial: motatool serve --dir ./my_firmware/ --serial /dev/ttyACM0 -v - # …or over WiFi (ESP32 companion): the seeder is on a DEDICATED port (5001), separate from the + # ...or over WiFi (ESP32 companion): the seeder is on a DEDICATED port (5001), separate from the # phone-app port (5000), so a phone can stay connected while you serve: motatool serve --dir ./my_firmware/ --tcp 192.168.1.50:5001 -v ``` @@ -249,34 +249,34 @@ remote area. `ota get` them like any other. (A WiFi node prints its IP + seeder port to the serial log on connect. Details: .) -To stop, just stop the daemon — over WiFi the node auto-detaches when the connection closes; over USB you +To stop, just stop the daemon - over WiFi the node auto-detaches when the connection closes; over USB you can also run `ota folder off` on the node. `ota folder` on its own lists what your node is offering. ### Everyone helps share You don't have to be a gateway to help. Once **any** node finishes downloading an update, it automatically offers it to *its* neighbours too. So a new firmware spreads outward node-to-node, instead of everyone -hammering the one node that had it first — and no node is ever overloaded, because all of this stays +hammering the one node that had it first - and no node is ever overloaded, because all of this stays lowest-priority. --- ## Where firmware files come from -OTA distributes **`.mota`** files — a packaged, verifiable firmware image (full image or a small "delta" +OTA distributes **`.mota`** files - a packaged, verifiable firmware image (full image or a small "delta" that only contains what changed). You get them by: - **Downloading a build.** This fork publishes a rolling **`dev-latest`** release on GitHub with the current firmware for many boards, each accompanied by a `.full.mota` and a tiny `.delta.mota`. Grab the one for your board to test. -- **Building your own** with the `mota` packaging tool — see [tools/mota/README.md](../tools/mota/README.md) +- **Building your own** with the `mota` packaging tool - see [tools/mota/README.md](../tools/mota/README.md) (this is for people distributing updates, not everyday operators). --- ## Quick reference -| I want to… | Command | +| I want to... | Command | |---|---| | List all commands | `ota help` | | See my firmware + any download | `ota status` (or just `ota`) | @@ -297,13 +297,13 @@ that only contains what changed). You get them by: ## A few terms -- **Firmware** — the software running your node. Updating it can add features or fix bugs. -- **`.mota`** — a packaged firmware update file, with built-in integrity checks. -- **Target** — your node's hardware + role identity. Your node only auto-fetches updates built for the +- **Firmware** - the software running your node. Updating it can add features or fix bugs. +- **`.mota`** - a packaged firmware update file, with built-in integrity checks. +- **Target** - your node's hardware + role identity. Your node only auto-fetches updates built for the same target, so it won't grab firmware meant for a different board. -- **Delta** — a small update containing only the changes from your current firmware (faster to send than a +- **Delta** - a small update containing only the changes from your current firmware (faster to send than a full image). Your node rebuilds the complete firmware from it and verifies the result before installing. -- **Signed** — the update carries the author's cryptographic signature, so you can verify who made it. +- **Signed** - the update carries the author's cryptographic signature, so you can verify who made it. For the full technical details (the file format and the radio protocol), see [the OTA protocol spec](ota_protocol.md). diff --git a/docs/stats_binary_frames.md b/docs/stats_binary_frames.md index 00e00b8f..4170577b 100644 --- a/docs/stats_binary_frames.md +++ b/docs/stats_binary_frames.md @@ -113,7 +113,7 @@ struct StatsRadio { - Counters are cumulative from boot and may wrap. - `recv = flood_rx + direct_rx` - `sent = flood_tx + direct_tx` -- Clients should accept frame length ≥ 26; if length ≥ 30, parse `recv_errors` at offset 26. +- Clients should accept frame length >= 26; if length >= 30, parse `recv_errors` at offset 26. ### Example Structure (C/C++) diff --git a/docs/terminal_chat_cli.md b/docs/terminal_chat_cli.md index 31616144..f9f10fbd 100644 --- a/docs/terminal_chat_cli.md +++ b/docs/terminal_chat_cli.md @@ -35,7 +35,7 @@ Sets the transmit duty cycle limit (1-100%). Example: `set dutycycle 10` for 10% ``` set af {air-time-factor} ``` -Sets the transmit air-time-factor. Deprecated — use `set dutycycle` instead. +Sets the transmit air-time-factor. Deprecated - use `set dutycycle` instead. ``` diff --git a/examples/companion_radio/main.cpp b/examples/companion_radio/main.cpp index 58005f58..67aa6537 100644 --- a/examples/companion_radio/main.cpp +++ b/examples/companion_radio/main.cpp @@ -192,7 +192,7 @@ void halt() { } #endif -/* WIFI OTA SEEDER — relay a host folder of .mota over WiFi (motatool `serve --tcp`), on a DEDICATED port +/* WIFI OTA SEEDER - relay a host folder of .mota over WiFi (motatool `serve --tcp`), on a DEDICATED port separate from the companion (TCP_PORT), so a phone app stays connected while motatool feeds updates. */ #if defined(ESP32) && defined(WIFI_SSID) && defined(ENABLE_OTA) #include @@ -230,7 +230,7 @@ void halt() { if (mesh::ota::ota_ctx().manager.fetchState() == mesh::ota::OtaManager::PAUSED) mesh::ota::ota_ctx().manager.resumeStaged(nullptr); mesh::ota::ota_ctx().manager.announce(); // new served set -> advertise the folder's fw to peers - WIFI_DEBUG_PRINTLN("OTA seeder: client connected (%s) — relay + folder pull-dest ready", di); + WIFI_DEBUG_PRINTLN("OTA seeder: client connected (%s) - relay + folder pull-dest ready", di); } else { ota_seeder_client.stop(); // no free source slot } @@ -238,9 +238,9 @@ void halt() { } #endif -/* WIFI OTA CONSOLE — a tiny text CLI for OTA over WiFi. A WiFi companion has no serial text console, so +/* WIFI OTA CONSOLE - a tiny text CLI for OTA over WiFi. A WiFi companion has no serial text console, so without this its OTA is only reachable through the phone app. Connect with e.g. `nc 5002` and type - `ota status` / `ota ls` / `ota announce` / ... — one client at a time, on a DEDICATED port separate from + `ota status` / `ota ls` / `ota announce` / ... - one client at a time, on a DEDICATED port separate from the companion (5000) and the seeder (5001). */ #if defined(ESP32) && defined(WIFI_SSID) && defined(ENABLE_OTA) #include // mesh::ota::handle_ota_command(line, reply, board) @@ -256,7 +256,7 @@ void halt() { if (!ota_console_client || !ota_console_client.connected()) { WiFiClient c = ota_console_server.available(); if (c) { ota_console_client = c; ota_console_len = 0; - ota_console_client.print("OTA console — type `ota ...` (e.g. ota status / ota ls / ota announce)\r\n> "); } + ota_console_client.print("OTA console - type `ota ...` (e.g. ota status / ota ls / ota announce)\r\n> "); } return; } while (ota_console_client.available()) { diff --git a/examples/simple_repeater/MyMesh.cpp b/examples/simple_repeater/MyMesh.cpp index b97e59e8..4d0d8113 100644 --- a/examples/simple_repeater/MyMesh.cpp +++ b/examples/simple_repeater/MyMesh.cpp @@ -917,7 +917,7 @@ void MyMesh::logRxRaw(float snr, float rssi, const uint8_t raw[], int len) { void MyMesh::logRx(mesh::Packet *pkt, int len, float score) { #ifdef WITH_MQTT_BRIDGE - // MQTT bridge: always feed RX packets — bridge decides based on mqtt.rx setting + // MQTT bridge: always feed RX packets - bridge decides based on mqtt.rx setting if (mqtt_bridge) mqtt_bridge->onPacketReceived(pkt); #elif defined(WITH_BRIDGE) // Non-MQTT bridge: use bridge.source setting @@ -947,7 +947,7 @@ void MyMesh::logRx(mesh::Packet *pkt, int len, float score) { void MyMesh::logTx(mesh::Packet *pkt, int len) { #ifdef WITH_MQTT_BRIDGE - // MQTT bridge: always feed TX packets — bridge decides based on mqtt.tx setting + // MQTT bridge: always feed TX packets - bridge decides based on mqtt.tx setting if (mqtt_bridge) mqtt_bridge->sendPacket(pkt); #elif defined(WITH_BRIDGE) // Non-MQTT bridge: use bridge.source setting @@ -2531,10 +2531,10 @@ MyMesh::MyMesh(mesh::MainBoard &board, mesh::Radio &radio, mesh::MillisecondCloc _prefs.battery_alert_low_percent = BATTERY_ALERT_LOW_PERCENT_DEFAULT; _prefs.battery_alert_critical_percent = BATTERY_ALERT_CRITICAL_PERCENT_DEFAULT; #ifdef WITH_MQTT_BRIDGE - _prefs.agc_reset_interval = 7; // 28 seconds (secs/4) — prevents AGC drift on long-running observers + _prefs.agc_reset_interval = 7; // 28 seconds (secs/4) - prevents AGC drift on long-running observers #endif // Observer defaults (radio_watchdog, alert.*, snmp.*) moved to applyMQTTDefaults() - // in MQTTDefaults.h — they live in /mqtt_prefs now, not NodePrefs. + // in MQTTDefaults.h - they live in /mqtt_prefs now, not NodePrefs. _prefs.rx_ps_rx_us = RX_POWERSAVING_DEFAULT_RX_US; _prefs.rx_ps_sleep_us = RX_POWERSAVING_DEFAULT_SLEEP_US; @@ -2571,7 +2571,7 @@ MyMesh::MyMesh(mesh::MainBoard &board, mesh::Radio &radio, mesh::MillisecondCloc memset(default_scope.key, 0, sizeof(default_scope.key)); } -// OTA mesh-integration (receive/begin/loop) is centralized in mesh::Mesh — no per-example wiring. +// OTA mesh-integration (receive/begin/loop) is centralized in mesh::Mesh - no per-example wiring. void MyMesh::begin(FILESYSTEM *fs) { mesh::Mesh::begin(); // also starts OTA (ota_ctx().begin) for all roles @@ -2721,7 +2721,7 @@ bool MyMesh::sendFloodScoped(const TransportKey& scope, mesh::Packet* pkt, uint3 bool MyMesh::resolveAlertScope(TransportKey& dest) { // Prefer an explicit alert.region override; look it up lazily via // RegionMap so the operator can name a region that doesn't exist yet - // without polluting region_map state — we just silently fall through + // without polluting region_map state - we just silently fall through // to default_scope on miss. #ifdef WITH_MQTT_BRIDGE const char* alert_region = _cli.getObserverPrefs()->alert_region; diff --git a/examples/simple_room_server/MyMesh.cpp b/examples/simple_room_server/MyMesh.cpp index 38ec8bef..8608ff37 100644 --- a/examples/simple_room_server/MyMesh.cpp +++ b/examples/simple_room_server/MyMesh.cpp @@ -231,7 +231,7 @@ void MyMesh::logRxRaw(float snr, float rssi, const uint8_t raw[], int len) { void MyMesh::logRx(mesh::Packet *pkt, int len, float score) { #ifdef WITH_MQTT_BRIDGE - // MQTT bridge: always feed RX packets — bridge decides based on mqtt.rx setting + // MQTT bridge: always feed RX packets - bridge decides based on mqtt.rx setting if (_prefs.bridge_enabled && bridge) bridge->onPacketReceived(pkt); #endif @@ -255,7 +255,7 @@ void MyMesh::logRx(mesh::Packet *pkt, int len, float score) { } void MyMesh::logTx(mesh::Packet *pkt, int len) { #ifdef WITH_MQTT_BRIDGE - // MQTT bridge: always feed TX packets — bridge decides based on mqtt.tx setting + // MQTT bridge: always feed TX packets - bridge decides based on mqtt.tx setting if (_prefs.bridge_enabled && bridge) bridge->sendPacket(pkt); #endif @@ -776,7 +776,7 @@ MyMesh::MyMesh(mesh::MainBoard &board, mesh::Radio &radio, mesh::MillisecondCloc _prefs.advert_loc_policy = ADVERT_LOC_PREFS; _prefs.radio_fem_rxgain = 1; - // Observer defaults (alert.*, etc.) moved to applyMQTTDefaults() — they live + // Observer defaults (alert.*, etc.) moved to applyMQTTDefaults() - they live // in /mqtt_prefs now, not NodePrefs. // bridge defaults (same as repeater) diff --git a/lib/PsychicMqttClient/CHANGELOG.md b/lib/PsychicMqttClient/CHANGELOG.md index 116ad5cc..ff94eba1 100644 --- a/lib/PsychicMqttClient/CHANGELOG.md +++ b/lib/PsychicMqttClient/CHANGELOG.md @@ -1,66 +1,66 @@ -# Changelog - -All notable changes to this project will be documented in this file. - -## [0.2.4] - Fixes - -### Fixed - -- Binary payloads truncated by strcpy and switched to memcpy [18](https://github.com/theelims/PsychicMqttClient/pull/18) -- Fixed broken link to Adafruit cert repository - -### Changed - -- Replaced `mqtt.eclipseprojects.io` with `broker.hivemq.com` -- Replaced debug tag with 🐙 - -## [0.2.3] - Various Fixes and Certificate Validation - -### Fixed - -- Issue [16](https://github.com/theelims/PsychicMqttClient/issues/16) was fixed. - -### Added - -- PR [17](https://github.com/theelims/PsychicMqttClient/pull/17) adds functionality to authenticate via client key + certificate. - -## [0.2.2] - Various Fixes - -### Fixed - -- Issue [14](https://github.com/theelims/PsychicMqttClient/issues/14) was fixed. -- Issue [13](https://github.com/theelims/PsychicMqttClient/issues/13) was fixed. - -### Added - -- The function `forceStop()` was added to address issue [12](https://github.com/theelims/PsychicMqttClient/issues/12). - -## [0.2.1] - Fixing Wildcard Behavior - -### Changed - -- Issue [6](https://github.com/theelims/PsychicMqttClient/issues/6) was fixed. - -### Added - -- Example for wildcards in topics. - -## [0.2.0] - Compatible with Arduino 3 / ESP-IDF 5 - -### Added - -- CI workflow -- ESP-IDF 5 compatibility - -### Changed - -- updated library.json - -## [0.1.1] - Stability Fixes - -### Fixed - -- Fixed subscribe error when not connected -- Freezing of event-loop when disconnect() is called while the MQTT server is not connected - -## [0.1.0] - Initial Release +# Changelog + +All notable changes to this project will be documented in this file. + +## [0.2.4] - Fixes + +### Fixed + +- Binary payloads truncated by strcpy and switched to memcpy [18](https://github.com/theelims/PsychicMqttClient/pull/18) +- Fixed broken link to Adafruit cert repository + +### Changed + +- Replaced `mqtt.eclipseprojects.io` with `broker.hivemq.com` +- Replaced debug tag with [OCTOPUS] + +## [0.2.3] - Various Fixes and Certificate Validation + +### Fixed + +- Issue [16](https://github.com/theelims/PsychicMqttClient/issues/16) was fixed. + +### Added + +- PR [17](https://github.com/theelims/PsychicMqttClient/pull/17) adds functionality to authenticate via client key + certificate. + +## [0.2.2] - Various Fixes + +### Fixed + +- Issue [14](https://github.com/theelims/PsychicMqttClient/issues/14) was fixed. +- Issue [13](https://github.com/theelims/PsychicMqttClient/issues/13) was fixed. + +### Added + +- The function `forceStop()` was added to address issue [12](https://github.com/theelims/PsychicMqttClient/issues/12). + +## [0.2.1] - Fixing Wildcard Behavior + +### Changed + +- Issue [6](https://github.com/theelims/PsychicMqttClient/issues/6) was fixed. + +### Added + +- Example for wildcards in topics. + +## [0.2.0] - Compatible with Arduino 3 / ESP-IDF 5 + +### Added + +- CI workflow +- ESP-IDF 5 compatibility + +### Changed + +- updated library.json + +## [0.1.1] - Stability Fixes + +### Fixed + +- Fixed subscribe error when not connected +- Freezing of event-loop when disconnect() is called while the MQTT server is not connected + +## [0.1.0] - Initial Release diff --git a/lib/PsychicMqttClient/src/PsychicMqttClient.cpp b/lib/PsychicMqttClient/src/PsychicMqttClient.cpp index da276e3c..90fadad3 100644 --- a/lib/PsychicMqttClient/src/PsychicMqttClient.cpp +++ b/lib/PsychicMqttClient/src/PsychicMqttClient.cpp @@ -4,7 +4,7 @@ #include -static const char *TAG = "🐙"; +static const char *TAG = "[OCTOPUS]"; static void log_error_if_nonzero(const char *message, int error_code) { @@ -742,7 +742,7 @@ void PsychicMqttClient::_onError(esp_mqtt_event_handle_t &event) // Broker accepted the socket but rejected the MQTT CONNECT. The return // code distinguishes auth failures (4=bad user/pass, 5=not authorized) // from server unavailable (3), protocol mismatch (1), and client-id - // rejection (2) — the key signal for diagnosing a server-side lockout. + // rejection (2) - the key signal for diagnosing a server-side lockout. ESP_LOGE(TAG, "Connection refused, return code 0x%x", err->connect_return_code); } @@ -798,7 +798,7 @@ bool PsychicMqttClient::_isTopicMatch(const char *topic, const char *subscriptio if (s_done) { - // Subscription ended but topic has more levels → no match, + // Subscription ended but topic has more levels -> no match, // unless the subscription ended with '+' and topic also has no // more levels (already handled above). return false; diff --git a/lib/PsychicMqttClient/src/PsychicMqttClient.h b/lib/PsychicMqttClient/src/PsychicMqttClient.h index 80961d22..890e5243 100644 --- a/lib/PsychicMqttClient/src/PsychicMqttClient.h +++ b/lib/PsychicMqttClient/src/PsychicMqttClient.h @@ -56,7 +56,7 @@ typedef std::function OnPublishUserCallback; typedef std::function OnErrorUserCallback; // Fixed caps sized for MeshCore's usage patterns. All callback and subscription -// storage is inline in the client object — zero dynamic allocation on register. +// storage is inline in the client object - zero dynamic allocation on register. #ifndef PSYCHIC_MAX_CONNECT_CB #define PSYCHIC_MAX_CONNECT_CB 4 #endif diff --git a/platformio.ini b/platformio.ini index 05a50006..72beb1a8 100644 --- a/platformio.ini +++ b/platformio.ini @@ -64,7 +64,7 @@ monitor_filters = esp32_exception_decoder ; deltas/full images of any size fetch over the air (RX-safe, sector-coalesced). The build always includes ; it; for `applydelta` to actually run, the board must use a dual-app/OTA partition table ; (board_build.partitions) with two app slots + otadata (boards without one fetch-refuse cleanly). -; pio_endf appends the EndF self-identity trailer. (nRF52 single-slot OTA is enabled per-board, not here — +; pio_endf appends the EndF self-identity trailer. (nRF52 single-slot OTA is enabled per-board, not here - ; it needs a custom bootloader; RP2040/STM32 have no A/B path yet.) extra_scripts = pre:scripts/generate_webconfig_html.py @@ -118,7 +118,7 @@ lib_deps = ; applied in place by the custom OTAFIX bootloader (in-place detools); the app only stages + verifies + ; approves it. OtaFlashLayout_nrf52.h uses FS_START 0xD4000 as the safe staging ceiling for the supported ; S140 v6/v7 linker scripts. The device must run the matching OTAFIX -; bootloader for `applydelta` to actually apply. (detools.c is intentionally NOT built into the app — +; bootloader for `applydelta` to actually apply. (detools.c is intentionally NOT built into the app - ; only the bootloader decodes.) The [rak4631] base defines this same recipe inline because it carries ; board-specific extras (fix_bsec_lib.py, BSEC lib) and a fixed post-script order. [nrf52_lora_ota] diff --git a/scripts/gen_changelog.py b/scripts/gen_changelog.py index d7cb3d4e..9a470ea1 100644 --- a/scripts/gen_changelog.py +++ b/scripts/gen_changelog.py @@ -50,7 +50,7 @@ UPSTREAM_MERGE = re.compile(r"[Mm]erge.*(?:upstream|origin)/dev") # rebase / merge artifacts that should never appear in the visible log NOISE = re.compile(r"conflict marker|duplicate (?:mqtt )?function|unnecessary comments" r"|from rebase|^revert |whoops|^remove final|^remove remaining", re.I) -HASH_TOKEN = re.compile(r"·\s*`([0-9a-f]{7,40})`") +HASH_TOKEN = re.compile(r"*\s*`([0-9a-f]{7,40})`") MANIFEST_HASH = re.compile(r"\b[0-9a-f]{7,40}\b") @@ -58,7 +58,7 @@ def clean(subj: str) -> str: s = subj.strip().lstrip("* ").strip() s = re.split(r"(?<=[.!?])\s+", s)[0].rstrip(". ") if len(s) > 140: - s = s[:137].rstrip() + "…" + s = s[:137].rstrip() + "..." return s[:1].upper() + s[1:] if s else s @@ -89,7 +89,7 @@ def render(h, date, is_merge, subj): if UPSTREAM_MERGE.search(subj): ver = re.search(r"v\d+\.\d+\.\d+", subj) tail = f" ({ver.group()})" if ver else "" - return f"- ⬆ **Upstream sync** — Synced with upstream MeshCore dev{tail} {date} · `{h}`" + return f"- [UP] **Upstream sync** - Synced with upstream MeshCore dev{tail} {date} * `{h}`" return None if NOISE.search(subj): return None @@ -100,8 +100,8 @@ def render(h, date, is_merge, subj): else: typ = scope = None summary = clean(subj) - sc = f" · `{scope}`" if scope else "" - return f"- **{label_for(typ, summary)}**{sc} — {summary} {date} · `{h}`" + sc = f" * `{scope}`" if scope else "" + return f"- **{label_for(typ, summary)}**{sc} - {summary} {date} * `{h}`" def insert_entry(lines, date, bullet): diff --git a/scripts/partition_signature.py b/scripts/partition_signature.py index 326b9e51..f90fe27d 100644 --- a/scripts/partition_signature.py +++ b/scripts/partition_signature.py @@ -4,7 +4,7 @@ Used to decide OTA partition compatibility: the observer firmware computes the same signature at runtime from its *flashed* partition table (via esp_partition), and `ota update` refuses only when the target build's signature differs from the -running device's — i.e. a real partition-table change, not a blanket flag. +running device's - i.e. a real partition-table change, not a blanket flag. The signature MUST be computed identically here and in firmware (src/helpers/ESP32Board.cpp). Definition: diff --git a/src/Mesh.cpp b/src/Mesh.cpp index 89eb0737..486a5119 100644 --- a/src/Mesh.cpp +++ b/src/Mesh.cpp @@ -872,11 +872,11 @@ DispatcherAction Mesh::onRecvPacket(Packet* pkt) { // to the seen table: a copy heard on the normal channel must not suppress one received after temp radio starts. if (!isTempRadioActive()) break; uint8_t n = pkt->getPathHashCount(); // hops travelled to reach us (flood path-hash count) - // Accept-gate (duty-cycle horizon): ignore OTA from further than our hop limit — neither process nor + // Accept-gate (duty-cycle horizon): ignore OTA from further than our hop limit - neither process nor // relay it. 0 = only directly-received OTA. Runtime-tunable via `ota config hops`. if (n > getOtaHopLimit()) break; // ALWAYS process every accepted copy: OTA handlers are idempotent, and "eventually reliable" retries - // deliberately re-send IDENTICAL requests — if we gated processing on hasSeen(), the dedup would + // deliberately re-send IDENTICAL requests - if we gated processing on hasSeen(), the dedup would // suppress those retries and the transfer could never recover from a lost reply. hasSeen() is used // ONLY to avoid re-flooding the same packet more than once. bool seen = _tables->wasSeen(pkt); @@ -887,7 +887,7 @@ DispatcherAction Mesh::onRecvPacket(Packet* pkt) { ota::ota_ctx().track_session(ota::ota_ctx().manager.fetchState(), _ms->getMillis()); onOtaRecv(pkt); // optional per-example hook // Re-flood at the LOWEST priority and only while still under the hop limit, so OTA never competes with - // mesh traffic. The free-pool guard keeps heavy OTA from monopolising the shared packet pool — dropping + // mesh traffic. The free-pool guard keeps heavy OTA from monopolising the shared packet pool - dropping // a relay is safe (OTA is best-effort; the source retries). if (!seen && pkt->isRouteFlood() && !pkt->isMarkedDoNotRetransmit() && n < getOtaHopLimit() diff --git a/src/Mesh.h b/src/Mesh.h index b6f949d3..78d6dbf7 100644 --- a/src/Mesh.h +++ b/src/Mesh.h @@ -438,7 +438,7 @@ protected: */ virtual void onOtaRecv(Packet* packet) { } - /** \returns max OTA flood reach in hops — accept up to N, relay while < N (0=direct). `ota config hops`. */ + /** \returns max OTA flood reach in hops - accept up to N, relay while < N (0=direct). `ota config hops`. */ virtual uint8_t getOtaHopLimit() const; // OTA mesh-integration is centralized in Mesh::begin()/loop()/dispatch, so every role (repeater, diff --git a/src/MeshCore.h b/src/MeshCore.h index 2ec8aa19..fa0bc7a4 100644 --- a/src/MeshCore.h +++ b/src/MeshCore.h @@ -77,7 +77,7 @@ public: virtual bool canControlLoRaFemLna() const { return false; } virtual bool isLoRaFemLnaEnabled() const { return false; } #if defined(ENABLE_OTA) - // 4-byte build-target discriminator for OTA-over-LoRa (docs/ota_protocol.md §9). Default is the + // 4-byte build-target discriminator for OTA-over-LoRa (docs/ota_protocol.md Section 9). Default is the // MOTA_TARGET_ID build flag injected by build.sh; 0 when unset (e.g. a bare IDE build). virtual uint32_t getOtaTargetId() const { #ifdef MOTA_TARGET_ID diff --git a/src/helpers/AlertReporter.cpp b/src/helpers/AlertReporter.cpp index 89542aad..ba2a74f3 100644 --- a/src/helpers/AlertReporter.cpp +++ b/src/helpers/AlertReporter.cpp @@ -6,7 +6,7 @@ #include // Header layout for PAYLOAD_TYPE_GRP_TXT before encryption: -// [0..3] timestamp (uint32_t LE) — also helps make packet_hash unique +// [0..3] timestamp (uint32_t LE) - also helps make packet_hash unique // [4] TXT_TYPE_PLAIN // [5..] ": " (null-terminated by sender for legacy parsers) #ifndef MAX_ALERT_TEXT_LEN @@ -74,9 +74,9 @@ struct BannedAlertChannel { static const BannedAlertChannel BANNED_ALERT_CHANNELS[] = { // Public group PSK ("izOH6cXN6mrJ5e26oRXNcg==") { "PUBLIC", "8b3387e9c5cdea6ac9e5edbaa115cd72" }, - // sha256("#test")[0..15] — auto-responders in many regions + // sha256("#test")[0..15] - auto-responders in many regions { "#test", "9cd8fcf22a47333b591d96a2b848b73f" }, - // sha256("#bot")[0..15] — generic bot channel, frequent auto-responders + // sha256("#bot")[0..15] - generic bot channel, frequent auto-responders { "#bot", "eb50a1bcb3e4e5d7bf69a57c9dada211" }, }; @@ -102,7 +102,7 @@ const char* alertReporterBannedChannelMatchHex(const char* psk_hex) { bool AlertReporter::resolveChannel(mesh::GroupChannel& out) const { if (!_prefs) return false; - // alert_psk_hex is the single source of truth — `set alert.hashtag` + // alert_psk_hex is the single source of truth - `set alert.hashtag` // pre-derives the hex-encoded PSK from sha256("#name")[0..15] at CLI time. // Only 16-byte secrets (32 hex chars) are supported; 32-byte channel keys // are not used anywhere in MeshCore practice and not represented in the @@ -163,7 +163,7 @@ bool AlertReporter::sendChannel(const char* text) { } // Ride the repeater's default scope (or `alert.region` override) when the - // host MyMesh provides one — same path MyMesh uses for adverts and + // host MyMesh provides one - same path MyMesh uses for adverts and // broadcast channel messages. Falls back to plain (unscoped) flood when // no callbacks are wired or no scope is configured, matching the // pre-scoped behavior on builds without RegionMap. diff --git a/src/helpers/AlertReporter.h b/src/helpers/AlertReporter.h index 390262b6..053bb5af 100644 --- a/src/helpers/AlertReporter.h +++ b/src/helpers/AlertReporter.h @@ -13,14 +13,14 @@ * the channels in the BANNED_ALERT_CHANNELS table (e.g. "PUBLIC", "#test", * "#bot"), or nullptr otherwise. Centralized here so both AlertReporter and * the CommonCLI `set alert.psk` / `set alert.hashtag` handlers can share one - * source of truth — adding a new banned channel is a one-line table edit. + * source of truth - adding a new banned channel is a one-line table edit. */ const char* alertReporterBannedChannelMatch(const uint8_t* secret16); /** * Convenience: hex-decodes \a psk_hex (32 lowercase/uppercase hex chars) and * forwards to alertReporterBannedChannelMatch. Returns nullptr if not banned - * (or if the input isn't a valid 32-char hex string — only 16-byte secrets + * (or if the input isn't a valid 32-char hex string - only 16-byte secrets * are present in the banned table). */ const char* alertReporterBannedChannelMatchHex(const char* psk_hex); @@ -31,13 +31,13 @@ const char* alertReporterBannedChannelMatchHex(const char* psk_hex); * * Polls WiFi and per-MQTT-slot outage timers from MQTTBridge. When any timer * exceeds its configured threshold, floods a single PAYLOAD_TYPE_GRP_TXT - * message on the configured alert channel ("WiFi down 47m — MyObserver"), + * message on the configured alert channel ("WiFi down 47m - MyObserver"), * then arms a "recovered" message for the next state transition. * * The alert channel must be explicitly configured to either a private hex * PSK (`set alert.psk`) or a hashtag name (`set alert.hashtag`); the * well-known PUBLIC group key (and a small list of other auto-responder - * channels — see BANNED_ALERT_CHANNELS in AlertReporter.cpp) are rejected on + * channels - see BANNED_ALERT_CHANNELS in AlertReporter.cpp) are rejected on * purpose so fault alerts never spam community channels. * * Edge-triggered + rate-limited via NodePrefs::alert_min_interval_min so a @@ -56,7 +56,7 @@ public: /** * Wire up the reporter. Must be called from MyMesh::begin() after prefs - * are loaded. \a callbacks is optional — when non-null the reporter uses + * are loaded. \a callbacks is optional - when non-null the reporter uses * it to resolve a TransportKey scope for outgoing alert floods (so the * packet rides the repeater's default scope or an `alert.region` override). */ diff --git a/src/helpers/CommonCLI.cpp b/src/helpers/CommonCLI.cpp index e1984096..19c0f847 100644 --- a/src/helpers/CommonCLI.cpp +++ b/src/helpers/CommonCLI.cpp @@ -854,7 +854,7 @@ void CommonCLI::loadPrefs(FILESYSTEM* fs) { } #ifdef WITH_MQTT_BRIDGE // Load observer preferences (MQTT/WiFi/timezone/SNMP/alert) from /mqtt_prefs. - // Readers (MQTTBridge, AlertReporter, observer CLI) use _mqtt_prefs directly — + // Readers (MQTTBridge, AlertReporter, observer CLI) use _mqtt_prefs directly - // these fields no longer exist in NodePrefs, so there is nothing to sync. loadMQTTPrefs(fs); @@ -950,13 +950,13 @@ void CommonCLI::loadPrefsInt(FILESYSTEM* fs, const char* filename) { file.read((uint8_t *)&_prefs->discovery_mod_timestamp, sizeof(_prefs->discovery_mod_timestamp)); // 162 file.read((uint8_t *)&_prefs->adc_multiplier, sizeof(_prefs->adc_multiplier)); // 166 file.read((uint8_t *)_prefs->owner_info, sizeof(_prefs->owner_info)); // 170 - // MQTT/observer settings are no longer stored in /com_prefs — they live in + // MQTT/observer settings are no longer stored in /com_prefs - they live in // /mqtt_prefs (loaded by loadMQTTPrefs). Old fork firmware wrote a zero-filled // MQTT gap here followed by a trailing observer block; detect that layout by the // extra length, skip the gap, and recover the tail so those settings survive // the upgrade (the file is rewritten in the new layout by loadPrefs afterwards). // Defaults for the trailing fields that older/shorter files may not contain. - // (upstream defaults: FEM RX gain on, CAD off) — overwritten below if present. + // (upstream defaults: FEM RX gain on, CAD off) - overwritten below if present. _prefs->radio_fem_rxgain = 1; _prefs->cad_enabled = 0; _prefs->rx_powersaving_enabled = 0; @@ -1005,7 +1005,7 @@ void CommonCLI::loadPrefsInt(FILESYSTEM* fs, const char* filename) { remaining -= n; } file.read((uint8_t *)&_prefs->rx_boosted_gain, sizeof(_prefs->rx_boosted_gain)); - // Tail layout: flood_max_unscoped, flood_max_advert, then the snmp fields — + // Tail layout: flood_max_unscoped, flood_max_advert, then the snmp fields - // except legacy flex-branch files where snmp starts right after // rx_boosted_gain (no flood_max_*). Same heuristic the old firmware used: // snmp_enabled is 0/1 and the first community char is printable (> 64). @@ -1026,7 +1026,7 @@ void CommonCLI::loadPrefsInt(FILESYSTEM* fs, const char* filename) { _legacy_tail.alert_min_interval_min = 60; #endif if (have_flood_bytes && b1 <= 1 && b2 > 64) { - // Legacy variant: no flood_max_* — b1/b2 are snmp_enabled + community[0] + // Legacy variant: no flood_max_* - b1/b2 are snmp_enabled + community[0] #ifdef WITH_MQTT_BRIDGE _legacy_tail.snmp_enabled = b1; _legacy_tail.snmp_community[0] = (char) b2; @@ -1510,7 +1510,7 @@ void CommonCLI::loadMQTTPrefs(FILESYSTEM* fs) { _mqtt_prefs_hold = false; // Whether the loaded /mqtt_prefs already contained the observer fields (snmp/ - // watchdog/alert) appended in Phase 2 — if not, they may be carried over from an + // watchdog/alert) appended in Phase 2 - if not, they may be carried over from an // old-format /com_prefs trailing block below. bool has_observer_fields = false; @@ -1556,7 +1556,7 @@ void CommonCLI::loadMQTTPrefs(FILESYSTEM* fs) { if (!versioned) { // Headerless (legacy) file. Detect the historical on-disk layout by size, - // migrate it into the compact versioned struct, and re-save — which adds the + // migrate it into the compact versioned struct, and re-save - which adds the // header and drops the vestigial `_legacy_*` fields. Reopen because the peek // above advanced the read cursor past the (non-matching) leading bytes. File file = openMqttPrefsRead(fs); @@ -1621,7 +1621,7 @@ void CommonCLI::loadMQTTPrefs(FILESYSTEM* fs) { saveMQTTPrefs(fs); } } else if (file_size > 0 && file_size <= sizeof(ThreeSlotMQTTPrefs)) { - // 3-slot format → compact 6-slot migration + // 3-slot format -> compact 6-slot migration // Array sizes changed from [3] to [6], shifting all field offsets. // Read into old layout struct and field-copy to new layout. ThreeSlotMQTTPrefs old3; @@ -1669,7 +1669,7 @@ void CommonCLI::loadMQTTPrefs(FILESYSTEM* fs) { } else if (file_size > 0) { // Headerless 6-slot layout as shipped on mqtt-bridge-implementation-flex // (the deployed fleet). Same field order as the compact struct but with the - // vestigial `_legacy_*` block mid-struct and no observer tail — so read it + // vestigial `_legacy_*` block mid-struct and no observer tail - so read it // into Legacy6SlotMQTTPrefs and field-copy across, dropping `_legacy_*`. Legacy6SlotMQTTPrefs old6; memset(&old6, 0, sizeof(old6)); @@ -1718,8 +1718,8 @@ void CommonCLI::loadMQTTPrefs(FILESYSTEM* fs) { } } } else { - // No /mqtt_prefs file — defaults already set. (MQTT slot/WiFi settings from - // pre-/mqtt_prefs-split fork firmware are NOT recovered from /com_prefs — that + // No /mqtt_prefs file - defaults already set. (MQTT slot/WiFi settings from + // pre-/mqtt_prefs-split fork firmware are NOT recovered from /com_prefs - that // offset-based migration was fragile and was removed; those users re-enter // their MQTT config. The observer trailing block IS recovered, below.) } diff --git a/src/helpers/CommonCLI.h b/src/helpers/CommonCLI.h index ad0a07ef..0f23281f 100644 --- a/src/helpers/CommonCLI.h +++ b/src/helpers/CommonCLI.h @@ -159,7 +159,7 @@ struct NodePrefs { // persisted to file float adc_multiplier; char owner_info[120]; // NOTE: member order below matches mcarper/keymindCascade (upstream/dev order). - // It is in-memory only — /com_prefs is read/written field-by-field in loadPrefsInt/ + // It is in-memory only - /com_prefs is read/written field-by-field in loadPrefsInt/ // savePrefs, whose canonical file order (identical to the flex fleet's through // offset 294, keymind retry tail at 295+) is what devices actually persist. uint8_t rx_boosted_gain; // power settings @@ -223,7 +223,7 @@ struct NodePrefs { // persisted to file }; #ifdef WITH_MQTT_BRIDGE -// Old MQTT preferences layout (pre-slot firmware) — used only for migration detection +// Old MQTT preferences layout (pre-slot firmware) - used only for migration detection struct OldMQTTPrefs { char mqtt_origin[32]; char mqtt_iata[8]; @@ -247,7 +247,7 @@ struct OldMQTTPrefs { char mqtt_email[64]; }; -// 3-slot MQTTPrefs layout — used for migrating from 3-slot to 6-slot format. +// 3-slot MQTTPrefs layout - used for migrating from 3-slot to 6-slot format. // Changing array sizes from [3] to [6] shifts all field offsets, so raw file.read() // into the new struct would corrupt data. This struct preserves the old binary layout. struct ThreeSlotMQTTPrefs { @@ -319,7 +319,7 @@ struct Legacy6SlotMQTTPrefs { }; // The legacy layouts above describe files already written to the deployed fleet's -// flash, so their sizes are frozen forever — loadMQTTPrefs() tells the eras apart +// flash, so their sizes are frozen forever - loadMQTTPrefs() tells the eras apart // by file size and reads each file as a raw struct dump. These asserts pin the // layouts on every target toolchain; if one fires, the compiler (or an edit to a // legacy struct or MAX_MQTT_SLOTS) has changed a layout and fleet files would be diff --git a/src/helpers/CommonCLI_Observer.cpp b/src/helpers/CommonCLI_Observer.cpp index 1a694c66..47010cd1 100644 --- a/src/helpers/CommonCLI_Observer.cpp +++ b/src/helpers/CommonCLI_Observer.cpp @@ -1,4 +1,4 @@ -// CommonCLI_Observer.cpp — fork-owned observer/MQTT/WiFi/timezone/alert/SNMP CLI +// CommonCLI_Observer.cpp - fork-owned observer/MQTT/WiFi/timezone/alert/SNMP CLI // command handling, split out of CommonCLI.cpp so the upstream-tracked file carries // only two small delegation hooks. These are CommonCLI member functions, so they // retain full access to _prefs/_callbacks/_board/savePrefs() with no re-plumbing. @@ -431,7 +431,7 @@ bool CommonCLI::handleObserverSetCmd(uint32_t sender_timestamp, const char* conf sprintf(reply, "OK - slot %d JWT audience cleared (using username/password auth)", slot + 1); } } else if (memcmp(subcmd, "audience", 8) == 0 && subcmd[8] == '\0') { - // "set mqttN.audience" with no value — clear the audience + // "set mqttN.audience" with no value - clear the audience _mqtt_prefs.mqtt_slot_audience[slot][0] = '\0'; savePrefs(); _callbacks->restartBridgeSlot(slot); @@ -548,7 +548,7 @@ bool CommonCLI::handleObserverSetCmd(uint32_t sender_timestamp, const char* conf } else { StrHelper::strncpy(_mqtt_prefs.alert_psk_hex, normalized, sizeof(_mqtt_prefs.alert_psk_hex)); // The new PSK is operator-supplied, so any previously-derived - // hashtag name is no longer accurate provenance — drop it. + // hashtag name is no longer accurate provenance - drop it. _mqtt_prefs.alert_hashtag[0] = '\0'; savePrefs(); _callbacks->onAlertConfigChanged(); @@ -585,7 +585,7 @@ bool CommonCLI::handleObserverSetCmd(uint32_t sender_timestamp, const char* conf // Derive the channel key once: first 16 bytes of sha256("#name"), // store hex-encoded in alert_psk_hex. We don't re-derive on every - // send — operators can later override with `set alert.psk` without + // send - operators can later override with `set alert.psk` without // leaving stale hashtag text behind. uint8_t digest[32]; mesh::Utils::sha256(digest, sizeof(digest), @@ -650,7 +650,7 @@ bool CommonCLI::handleObserverSetCmd(uint32_t sender_timestamp, const char* conf } else if (memcmp(config, "alert.interval ", 15) == 0) { int mins = (int)_atoi(&config[15]); // Floor at 60 min: faster re-fires would let a flapping link spam the - // mesh with a fresh GRP_TXT flood every minute — terrible for airtime. + // mesh with a fresh GRP_TXT flood every minute - terrible for airtime. if (mins < 60 || mins > 10080) { strcpy(reply, "Error: alert.interval must be 60-10080 minutes"); } else { @@ -763,7 +763,7 @@ bool CommonCLI::handleObserverGetCmd(uint32_t sender_timestamp, const char* conf if (_mqtt_prefs.mqtt_slot_audience[slot][0] != '\0') { sprintf(reply, "> %s", _mqtt_prefs.mqtt_slot_audience[slot]); } else { - strcpy(reply, "> (not set — custom slots use username/password auth)"); + strcpy(reply, "> (not set - custom slots use username/password auth)"); } } else if (memcmp(subcmd, "diag", 4) == 0) { MQTTBridge::formatSlotDiagReply(reply, 160, slot); @@ -936,13 +936,13 @@ bool CommonCLI::handleObserverCommand(uint32_t sender_timestamp, char* command, } else if (memcmp(command, "ota check", 9) == 0) { // Check is synchronous so its result lands in this reply, and runs with the // MQTT bridge UP: the slim per-variant manifest is tiny, so the fetch only - // costs a single TLS handshake (no large JSON doc) — which fits alongside + // costs a single TLS handshake (no large JSON doc) - which fits alongside // the live MQTT sessions even on no-PSRAM boards. No bridge bounce needed. _board->otaFromManifest(_callbacks->getFirmwareVer(), true, reply); } else { // `ota update`: cheap pre-check first (plain HTTP, bridge stays up). Only - // schedule the real update — which tears the bridge down, flashes, and - // reboots — when an applicable build actually exists. otaFromManifest(dry) + // schedule the real update - which tears the bridge down, flashes, and + // reboots - when an applicable build actually exists. otaFromManifest(dry) // returns true iff so; otherwise it leaves the explanation (up to date / // cable flash / error) in reply, which we send without disturbing the // bridge or misleading the user with a "Beginning update..." that no-ops. diff --git a/src/helpers/ESP32Board.cpp b/src/helpers/ESP32Board.cpp index b23e9d20..00d286b3 100644 --- a/src/helpers/ESP32Board.cpp +++ b/src/helpers/ESP32Board.cpp @@ -417,7 +417,7 @@ static void ota_parseVersion(const char* ver, char* base_out, size_t base_sz, in } } -// Canonical signature of the FLASHED partition table — MUST match +// Canonical signature of the FLASHED partition table - MUST match // scripts/partition_signature.py: each entry "type:subtype:offset:size" in // lowercase hex, sorted by offset, joined by ','. Lets `ota update` compare the // target build's partition layout (carried in the manifest as partSig) against @@ -472,7 +472,7 @@ static void ota_task_entry(void* param) { bool ESP32Board::otaFromManifest(const char* current_ver, bool dry_run, char reply[]) { // The TLS handshake (cert-bundle verify) + JSON parse / HTTPUpdate use far more - // stack than the ~8 KB loop task offers — especially when reached via the deep + // stack than the ~8 KB loop task offers - especially when reached via the deep // mesh-receive call chain (it overflows the loopTask canary). Run the work in a // dedicated 24 KB-stack task and block here until it finishes. The big stack is // freed when the task exits; on a successful update the chip reboots inside it. @@ -510,7 +510,7 @@ bool ESP32Board::otaFromManifestImpl(const char* current_ver, bool dry_run, char } // --- Fetch this variant's slim manifest ---------------------------------- - // /.json — a ~180 byte per-variant file, not the + // /.json - a ~180 byte per-variant file, not the // full config.json. char murl[200]; HTTPClient http; @@ -519,7 +519,7 @@ bool ESP32Board::otaFromManifestImpl(const char* current_ver, bool dry_run, char if (dry_run) { // `ota check`: fetch over PLAIN HTTP. With no TLS handshake the fetch costs // negligible heap, so the check runs with the MQTT bridge UP even on no-PSRAM - // — where the cert-bundle TLS verify would otherwise exhaust internal heap + // - where the cert-bundle TLS verify would otherwise exhaust internal heap // alongside the two live MQTT TLS sessions (free heap collapses to a few KB // and the handshake + the bridge both fail). This only reads version info; the // firmware download below (ota update) is always TLS-verified. Requires the @@ -591,7 +591,7 @@ bool ESP32Board::otaFromManifestImpl(const char* current_ver, bool dry_run, char return false; } - // Partition compatibility: prefer the precise per-build signature — compare the + // Partition compatibility: prefer the precise per-build signature - compare the // target build's partition layout (manifest partSig) to what's actually flashed // on THIS device. Refuse only on a real mismatch (OTA can't rewrite the table). // Fall back to the legacy bool for manifests that predate partSig. diff --git a/src/helpers/ESP32Board.h b/src/helpers/ESP32Board.h index 670410a9..96fa20bd 100644 --- a/src/helpers/ESP32Board.h +++ b/src/helpers/ESP32Board.h @@ -185,7 +185,7 @@ public: bool isOTAUpdateRunning() const override { return ota_server != nullptr; } bool otaFromManifest(const char* current_ver, bool dry_run, char reply[]) override; // Heavy body (TLS + JSON / HTTPUpdate). Runs in a dedicated large-stack task - // spawned by otaFromManifest() — public only so that task entry point can call + // spawned by otaFromManifest() - public only so that task entry point can call // it; not meant to be invoked directly. bool otaFromManifestImpl(const char* current_ver, bool dry_run, char reply[]); diff --git a/src/helpers/MQTTMessageBuilder.h b/src/helpers/MQTTMessageBuilder.h index c3f10b69..39128996 100644 --- a/src/helpers/MQTTMessageBuilder.h +++ b/src/helpers/MQTTMessageBuilder.h @@ -27,7 +27,7 @@ public: * Always UTC with an explicit "+00:00" offset, ISO-8601 * "%Y-%m-%dT%H:%M:%S.uuuuuu+00:00" (matches Python * datetime.now(timezone.utc).isoformat()). The `timezone` parameter is retained - * for API compatibility but ignored — the system clock is UTC. + * for API compatibility but ignored - the system clock is UTC. * * `usec` is the sub-second component in microseconds (0..999999), normally taken * from the same gettimeofday() read as `now` so the two don't tear at a second diff --git a/src/helpers/MQTTPresets.h b/src/helpers/MQTTPresets.h index 9069f650..ed25337a 100644 --- a/src/helpers/MQTTPresets.h +++ b/src/helpers/MQTTPresets.h @@ -3,7 +3,7 @@ #include // Maximum number of configurable MQTT connection slots (available to all builds for struct layout). -// Used in NodePrefs/MQTTPrefs for persistent storage — do NOT change without migration. +// Used in NodePrefs/MQTTPrefs for persistent storage - do NOT change without migration. static const int MAX_MQTT_SLOTS = 6; // Runtime slot array size: fewer slots on non-PSRAM boards to save ~1.2KB of heap. diff --git a/src/helpers/RegionMap.cpp b/src/helpers/RegionMap.cpp index 620320d4..998e1425 100644 --- a/src/helpers/RegionMap.cpp +++ b/src/helpers/RegionMap.cpp @@ -457,7 +457,7 @@ size_t RegionMap::exportTo(char *dest, size_t max_len) const { if (!dest || max_len == 0) return 0; BufStream bs(dest, max_len); - exportTo(bs); // ← reuse existing logic + exportTo(bs); // <- reuse existing logic return bs.length(); } diff --git a/src/helpers/RxReservePacketManager.h b/src/helpers/RxReservePacketManager.h index 88c5a767..fce0672a 100644 --- a/src/helpers/RxReservePacketManager.h +++ b/src/helpers/RxReservePacketManager.h @@ -4,7 +4,7 @@ #include // Fork-owned (not upstream-tracked). Observer builds capture every received packet -// to MQTT, but RX processing needs a free pool packet first — Dispatcher::checkRecv() +// to MQTT, but RX processing needs a free pool packet first - Dispatcher::checkRecv() // discards the received bytes before logRx() when allocNew() fails. Under duty-cycle // throttling the outbound queue can park the entire pool waiting on TX budget, which // starves RX allocation and silently caps MQTT capture at the TX rate (each completed @@ -19,7 +19,7 @@ // own responses/ACKs (pri 0) and login/PATH replies (pri 1) still queue. Below the // smaller emergency floor everything is shed to keep capture alive. // 2. Stale-packet expiry. A queued packet still untransmitted STALE_OUTBOUND_MS past -// its scheduled time is dropped at the next dequeue — a repeat delayed that long is +// its scheduled time is dropped at the next dequeue - a repeat delayed that long is // noise (the flood has long since propagated), and a CLI response that old has // already timed out at the client. Under normal load the queue drains in // milliseconds and this never triggers; under throttle it frees the pool and lets diff --git a/src/helpers/SNMPAgent.h b/src/helpers/SNMPAgent.h index c1bfe0f5..d2488735 100644 --- a/src/helpers/SNMPAgent.h +++ b/src/helpers/SNMPAgent.h @@ -6,7 +6,7 @@ #include #include -// Temporary private enterprise OID base — replace with registered PEN when available. +// Temporary private enterprise OID base - replace with registered PEN when available. // All MeshCore OIDs live under this subtree. #define MESHCORE_OID_BASE ".1.3.6.1.4.1.99999" diff --git a/src/helpers/TxtDataHelpers.cpp b/src/helpers/TxtDataHelpers.cpp index f994c9de..5d9f2ef0 100644 --- a/src/helpers/TxtDataHelpers.cpp +++ b/src/helpers/TxtDataHelpers.cpp @@ -156,7 +156,7 @@ const char* StrHelper::ftoa(float f) { const char* StrHelper::ftoa3(float f) { static char s[16]; - int v = (int)(f * 1000.0f + (f >= 0 ? 0.5f : -0.5f)); // rounded ×1000 + int v = (int)(f * 1000.0f + (f >= 0 ? 0.5f : -0.5f)); // rounded x1000 int w = v / 1000; // whole int d = abs(v % 1000); // decimals snprintf(s, sizeof(s), "%d.%03d", w, d); diff --git a/src/helpers/bridges/MQTTBridge.cpp b/src/helpers/bridges/MQTTBridge.cpp index e41962a9..92da9ec9 100644 --- a/src/helpers/bridges/MQTTBridge.cpp +++ b/src/helpers/bridges/MQTTBridge.cpp @@ -489,7 +489,7 @@ MQTTBridge::MQTTBridge(const MQTTNodeInfo& node_info, MQTTPrefs *obs, _slots[i].enabled = false; _slots[i].client = nullptr; _slots[i].preset = nullptr; - // auth_token[0] == '\0' after memset above — no valid token + // auth_token[0] == '\0' after memset above - no valid token _slots[i].connected = false; _slots[i].initial_connect_done = false; _slots[i].token_expires_at = 0; @@ -530,7 +530,7 @@ MQTTBridge::MQTTBridge(const MQTTNodeInfo& node_info, MQTTPrefs *obs, #endif // On PSRAM boards, allocate raw radio buffer and JSON char buffers in PSRAM to preserve - // internal heap. On non-PSRAM boards these are inline arrays in the class object — + // internal heap. On non-PSRAM boards these are inline arrays in the class object - // no separate allocation needed. #if defined(BOARD_HAS_PSRAM) _last_raw_data = (uint8_t*)psram_malloc(LAST_RAW_DATA_SIZE); @@ -539,7 +539,7 @@ MQTTBridge::MQTTBridge(const MQTTNodeInfo& node_info, MQTTPrefs *obs, #else memset(_last_raw_data, 0, sizeof(_last_raw_data)); #endif - // JSON document scratch space is now a StaticJsonDocument inline class member — + // JSON document scratch space is now a StaticJsonDocument inline class member - // no heap allocation needed; reused via doc.clear() on every publish. } @@ -863,16 +863,16 @@ void MQTTBridge::end() { destroySlotClients(); // Timezone is inline class storage (_timezone_storage) since Phase 3 of - // the MQTT memory-defrag work — nothing to delete. _timezone always + // the MQTT memory-defrag work - nothing to delete. _timezone always // points at &_timezone_storage and stays valid for the bridge lifetime. - // Free PSRAM-backed buffers (non-PSRAM builds use inline class arrays — no free needed) + // Free PSRAM-backed buffers (non-PSRAM builds use inline class arrays - no free needed) #if defined(BOARD_HAS_PSRAM) psram_free(_last_raw_data); _last_raw_data = nullptr; psram_free(_publish_json_buffer); _publish_json_buffer = nullptr; psram_free(_status_json_buffer); _status_json_buffer = nullptr; #endif - // JSON documents are now StaticJsonDocument inline members — no heap allocation to free. + // JSON documents are now StaticJsonDocument inline members - no heap allocation to free. _initialized = false; _slots_setup_done = false; // Reset so deferred setup runs again on next begin() @@ -907,8 +907,8 @@ void MQTTBridge::initializeWiFiInTask() { WiFi.setAutoConnect(true); // Set up WiFi event handlers for better diagnostics and immediate disconnection - // detection. Register ONCE — the bridge is reused across restarts (e.g. stopped - // for `ota check`/`ota update`, or `set mqtt…` reconfigure) and WiFi.onEvent() + // detection. Register ONCE - the bridge is reused across restarts (e.g. stopped + // for `ota check`/`ota update`, or `set mqtt...` reconfigure) and WiFi.onEvent() // never removes prior callbacks, so re-registering leaks handlers and duplicates // every log line. if (!_wifi_event_registered) { @@ -935,7 +935,7 @@ void MQTTBridge::initializeWiFiInTask() { // Only (re)start the WiFi association if it isn't already up. end() leaves the // STA link connected, so on a restart (e.g. after `ota check`) calling - // WiFi.begin() again forces a needless disconnect/reconnect — which also races + // WiFi.begin() again forces a needless disconnect/reconnect - which also races // the MQTT task's first DNS lookup (getaddrinfo fails until WiFi/DNS recovers). // When already connected, the deferred slot setup still fires in mqttTaskLoop() // because _ntp_synced persists across end() (only _slots_setup_done is reset). @@ -944,7 +944,7 @@ void MQTTBridge::initializeWiFiInTask() { WiFi.begin(_obs->wifi_ssid, _obs->wifi_password); } } else if (!_ntp_synced && !_ntp_sync_pending) { - _ntp_sync_pending = true; // already connected but never synced — kick NTP now + _ntp_sync_pending = true; // already connected but never synced - kick NTP now } // NOTE: Slot setup is deferred until after NTP sync in mqttTaskLoop(). @@ -989,7 +989,7 @@ void MQTTBridge::mqttTaskLoop() { unsigned long now = millis(); bool wifi_just_connected = handleWiFiConnection(now); if (wifi_just_connected) { - // WiFi recovered — reset last_reconnect_attempt for disconnected slots so they + // WiFi recovered - reset last_reconnect_attempt for disconnected slots so they // retry immediately rather than waiting up to 5 min for backoff timers to expire. for (int i = 0; i < RUNTIME_MQTT_SLOTS; i++) { if (_slots[i].enabled && _slots[i].initial_connect_done && !_slots[i].connected) { @@ -1025,7 +1025,7 @@ void MQTTBridge::mqttTaskLoop() { } // Process a CLI-requested NTP connectivity diagnostic (queued from Core 1). - // Probe-only — never touches the system clock. + // Probe-only - never touches the system clock. if (_ntp_diag_requested) { _ntp_diag_requested = false; runNtpDiagProbe(); @@ -1066,7 +1066,7 @@ void MQTTBridge::mqttTaskLoop() { } char reason[80]; if (!isSlotReady(i, reason, sizeof(reason))) { - MQTT_DEBUG_PRINTLN("MQTT%d not ready — run '%s' to connect", i + 1, reason); + MQTT_DEBUG_PRINTLN("MQTT%d not ready - run '%s' to connect", i + 1, reason); continue; } setupSlot(i); @@ -1096,7 +1096,7 @@ void MQTTBridge::mqttTaskLoop() { processPacketQueue(); #ifdef WITH_SNMP - // SNMP agent loop — process incoming UDP requests + // SNMP agent loop - process incoming UDP requests if (_snmp_agent) { if (!_snmp_agent->isRunning() && WiFi.isConnected() && _obs->snmp_enabled) { _snmp_agent->begin(_obs->snmp_community); @@ -1117,7 +1117,7 @@ void MQTTBridge::mqttTaskLoop() { // Periodic configuration check (throttled to avoid spam) checkConfigurationMismatch(); - // Periodic NTP refresh (every hour) — lightweight, non-blocking. + // Periodic NTP refresh (every hour) - lightweight, non-blocking. // Uses async SNTP instead of the heavy syncTimeWithNTP() which blocks Core 0 // for up to 20+ seconds with DNS lookups, UDP sockets, and retry loops. if (WiFi.status() == WL_CONNECTED && now - _last_ntp_sync > 3600000) { @@ -1204,7 +1204,7 @@ void MQTTBridge::mqttTaskLoop() { // Allocate one PsychicMqttClient per slot and register its persistent callbacks. // Called exactly once per bridge lifetime from begin(); the objects live until // destroySlotClients(). Reconfiguring a slot (preset change, JWT renewal, -// reconnect) reuses the same client — no delete/new cycles, so the mbedTLS +// reconnect) reuses the same client - no delete/new cycles, so the mbedTLS // context and its ~40 KB of internal-heap buffers are allocated once instead // of every reconfigure. void MQTTBridge::initSlotClients() { @@ -1248,7 +1248,7 @@ void MQTTBridge::initSlotClients() { _slots[index].last_sock_errno = error.esp_transport_sock_errno; _slots[index].last_error_time = millis(); if (error.error_type == MQTT_ERROR_TYPE_CONNECTION_REFUSED) { - // Broker rejected the MQTT CONNECT itself — not a transport failure. + // Broker rejected the MQTT CONNECT itself - not a transport failure. // return code: 1=protocol, 2=client-id rejected, 3=server unavailable, // 4=bad username/password, 5=not authorized. Codes 3/4/5 point at a // server-side lockout or auth problem rather than the network. @@ -1293,9 +1293,9 @@ void MQTTBridge::setupSlot(int index) { } // Persistent client is expected to have been allocated by initSlotClients(). - // If it hasn't, we can't proceed — bail loudly rather than silently leaking. + // If it hasn't, we can't proceed - bail loudly rather than silently leaking. if (slot.client == nullptr) { - MQTT_DEBUG_PRINTLN("MQTT%d: setupSlot before initSlotClients() — skipping", index + 1); + MQTT_DEBUG_PRINTLN("MQTT%d: setupSlot before initSlotClients() - skipping", index + 1); return; } @@ -1367,7 +1367,7 @@ void MQTTBridge::setupSlot(int index) { } } } else { - // Custom broker slot — build persistent URI + // Custom broker slot - build persistent URI // If host already has a scheme (mqtt://, mqtts://, ws://, wss://), preserve the full URI // (including optional path/query) and only inject :port when the authority has no explicit port. // Otherwise, infer protocol from port number. @@ -1447,7 +1447,7 @@ void MQTTBridge::setupSlot(int index) { MQTT_DEBUG_PRINTLN("MQTT%d TLS: no embedded cert bundle available", index + 1); } } else { - // Global bundle already loaded — just attach the callback for this client. + // Global bundle already loaded - just attach the callback for this client. slot.client->attachArduinoCACertBundle(true); } MQTT_DEBUG_PRINTLN("MQTT%d TLS verify: CA bundle %s", index + 1, @@ -1458,7 +1458,7 @@ void MQTTBridge::setupSlot(int index) { // Custom slot authentication: JWT if audience is set, else username/password if (slot.audience[0] != '\0') { - // JWT auth for custom slot — create initial token (buffer is always inline) + // JWT auth for custom slot - create initial token (buffer is always inline) createSlotAuthToken(index); if (slot.auth_token[0] != '\0') { slot.client->setCredentials(_jwt_username, slot.auth_token); @@ -1609,7 +1609,7 @@ void MQTTBridge::maintainSlotConnection(int index, unsigned long now_millis, uns _radio ? _radio->getRadioState() : -1, (_radio && _radio->getLastRecvMillis() > 0) ? (_ms->getMillis() - _radio->getLastRecvMillis()) : 0); } else { - // Token renewed but old one still valid — just update credentials for next reconnect + // Token renewed but old one still valid - just update credentials for next reconnect slot.client->setCredentials(_jwt_username, slot.auth_token); } } else { @@ -1690,7 +1690,7 @@ void MQTTBridge::maintainSlotConnection(int index, unsigned long now_millis, uns if (slot_uses_jwt) { // Always lightweight reconnect on the persistent client. A stale/expired // token is handled by regenerating it in place and updating credentials - // — no teardown is needed because the client and its mbedTLS context + // - no teardown is needed because the client and its mbedTLS context // persist for the bridge lifetime. if (createSlotAuthToken(index)) { slot.client->setCredentials(_jwt_username, slot.auth_token); @@ -1700,7 +1700,7 @@ void MQTTBridge::maintainSlotConnection(int index, unsigned long now_millis, uns } slot.client->reconnect(); } else { - // Non-JWT slots — lightweight reconnect on existing client. + // Non-JWT slots - lightweight reconnect on existing client. MQTT_DEBUG_PRINTLN("MQTT%d reconnect (non-JWT, backoff %d)", index + 1, slot.reconnect_backoff); slot.client->reconnect(); } @@ -1779,7 +1779,7 @@ bool MQTTBridge::publishToSlot(int index, const char* topic, const char* payload } // QoS 0 for the high-rate packet/raw publish paths: no PUBACK, no outbox store, - // no per-message heap alloc — critical for non-PSRAM fragmentation. QoS 1 is used + // no per-message heap alloc - critical for non-PSRAM fragmentation. QoS 1 is used // only for low-rate retained status messages where delivery matters. // // esp_mqtt_client_enqueue return convention: QoS 0 returns msg_id == 0 on success @@ -1920,7 +1920,7 @@ void MQTTBridge::publishStatusToSlot(int index) { char radio_info[64]; // Status timestamp: UTC with explicit +00:00 offset, same as packet/raw JSON - // `timestamp` (system clock is UTC — SNTP offset 0; prefs Timezone is separate). + // `timestamp` (system clock is UTC - SNTP offset 0; prefs Timezone is separate). struct timeval now_tv; gettimeofday(&now_tv, nullptr); MQTTMessageBuilder::formatIsoTimestampForMqtt(now_tv.tv_sec, now_tv.tv_usec, _timezone, timestamp, sizeof(timestamp)); @@ -2055,7 +2055,7 @@ void MQTTBridge::applySlotPreset(int slot_index, const char* preset_name) { if (_initialized) { char reason[80]; if (!isSlotReady(slot_index, reason, sizeof(reason))) { - MQTT_DEBUG_PRINTLN("MQTT%d (%s) not ready — run '%s' to connect", slot_index + 1, preset_name, reason); + MQTT_DEBUG_PRINTLN("MQTT%d (%s) not ready - run '%s' to connect", slot_index + 1, preset_name, reason); return; } setupSlot(slot_index); @@ -2082,11 +2082,11 @@ void MQTTBridge::setSlotCustomBroker(int slot_index, const char* host, uint16_t // --------------------------------------------------------------------------- void MQTTBridge::checkConfigurationMismatch() { - // Warn if packets are enabled but both rx and tx are off — nothing will be published + // Warn if packets are enabled but both rx and tx are off - nothing will be published if (_obs->mqtt_packets_enabled && !_obs->mqtt_rx_enabled && _obs->mqtt_tx_enabled == 0) { unsigned long now = millis(); if (_last_config_warning == 0 || (now - _last_config_warning > CONFIG_WARNING_INTERVAL)) { - MQTT_DEBUG_PRINTLN("MQTT: Both mqtt.rx and mqtt.tx are off — no packets will be published. Run 'set mqtt.rx on' or 'set mqtt.tx on' to fix."); + MQTT_DEBUG_PRINTLN("MQTT: Both mqtt.rx and mqtt.tx are off - no packets will be published. Run 'set mqtt.rx on' or 'set mqtt.tx on' to fix."); _last_config_warning = now; } } else { @@ -2283,7 +2283,7 @@ void MQTTBridge::loop() { // Periodic configuration check (throttled to avoid spam) checkConfigurationMismatch(); - // Periodic NTP refresh (every hour) — lightweight, non-blocking. + // Periodic NTP refresh (every hour) - lightweight, non-blocking. if (WiFi.status() == WL_CONNECTED && millis() - _last_ntp_sync > 3600000) { refreshNTP(); } @@ -2377,7 +2377,7 @@ void MQTTBridge::sendPacket(mesh::Packet *packet) { if (tx_mode == 2) { if (packet->getPayloadType() != PAYLOAD_TYPE_ADVERT) return; if (packet->payload_len < PUB_KEY_SIZE) return; - // Advert payload starts with advertiser's 32-byte public key — compare to our identity + // Advert payload starts with advertiser's 32-byte public key - compare to our identity if (!_identity || memcmp(_identity->pub_key, packet->payload, PUB_KEY_SIZE) != 0) return; } @@ -2458,7 +2458,7 @@ void MQTTBridge::processPacketQueue() { break; } - // Update Core 0-owned last-raw-data for publishStatus() — no mutex needed since + // Update Core 0-owned last-raw-data for publishStatus() - no mutex needed since // _last_raw_data is now written only here (Core 0) and read only by publishStatus() (Core 0). if (!queued.is_tx && queued.has_raw_data && _last_raw_data) { memcpy(_last_raw_data, queued.raw_data, queued.raw_len); @@ -2641,7 +2641,7 @@ bool MQTTBridge::publishStatus() { char radio_info[64]; // Status timestamp: UTC with explicit +00:00 offset, same as packet/raw JSON - // `timestamp` (system clock is UTC — SNTP offset 0; prefs Timezone is separate). + // `timestamp` (system clock is UTC - SNTP offset 0; prefs Timezone is separate). struct timeval now_tv; gettimeofday(&now_tv, nullptr); MQTTMessageBuilder::formatIsoTimestampForMqtt(now_tv.tv_sec, now_tv.tv_usec, _timezone, timestamp, sizeof(timestamp)); @@ -2731,7 +2731,7 @@ bool MQTTBridge::publishPacket(mesh::Packet* packet, bool is_tx, // Memory pressure check: Skip publishes when there's not enough contiguous // heap for the publish itself (JSON buffer + esp-mqtt outbox frame + WiFi TX - // path). Headroom only — NOT an mbedTLS preflight: persistent clients keep + // path). Headroom only - NOT an mbedTLS preflight: persistent clients keep // their TLS contexts allocated for the bridge lifetime, so the old ~52 KB // "reserve space for reconnect" guard is obsolete post Phase 1. Publish // payload is capped at PUBLISH_JSON_BUFFER_SIZE (2 KB); 8 KB is a safe @@ -2780,7 +2780,7 @@ bool MQTTBridge::publishPacket(mesh::Packet* packet, bool is_tx, strncpy(origin_id, _device_id, sizeof(origin_id) - 1); origin_id[sizeof(origin_id) - 1] = '\0'; - // Firmware rebroadcast "score" for this packet — only meaningful for RX packets + // Firmware rebroadcast "score" for this packet - only meaningful for RX packets // (depends on receive SNR). Recomputed here exactly as Dispatcher::checkRecv() does, // via the radio's packetScore(snr, len); NaN signals "omit" (tx, or no radio). // buildPacketMessage scales it x1000 to match the integer in the serial RX log. @@ -2904,14 +2904,14 @@ void MQTTBridge::queuePacket(mesh::Packet* packet, bool is_tx) { QueuedPacket queued; memset(&queued, 0, sizeof(QueuedPacket)); - queued.packet_copy = *packet; // full value copy — safe from Dispatcher free + queued.packet_copy = *packet; // full value copy - safe from Dispatcher free queued.timestamp = millis(); queued.is_tx = is_tx; queued.snr = 0.0f; queued.rssi = 0.0f; // Consume staged raw data (written by storeRawRadioData() on Core 1, same call sequence). - // No mutex needed — both sites run on Core 1 before xQueueSend() crosses the core boundary. + // No mutex needed - both sites run on Core 1 before xQueueSend() crosses the core boundary. if (!is_tx && _staged_raw_valid) { if (_staged_raw_len <= (int)sizeof(queued.raw_data)) { memcpy(queued.raw_data, _staged_raw, _staged_raw_len); @@ -2959,7 +2959,7 @@ void MQTTBridge::queuePacket(mesh::Packet* packet, bool is_tx) { QueuedPacket& queued = _packet_queue[_queue_tail]; memset(&queued, 0, sizeof(QueuedPacket)); - queued.packet_copy = *packet; // full value copy — safe from Dispatcher free + queued.packet_copy = *packet; // full value copy - safe from Dispatcher free queued.timestamp = millis(); queued.is_tx = is_tx; queued.snr = 0.0f; @@ -3143,7 +3143,7 @@ bool MQTTBridge::syncTimeWithNTP(bool force, bool primary_only) { bool slot_jwt = (_slots[i].preset && _slots[i].preset->auth_type == MQTT_AUTH_JWT) || (!_slots[i].preset && _slots[i].audience[0] != '\0'); if (_slots[i].enabled && slot_jwt && _slots[i].client) { - // Token created before NTP corrected the clock — refresh credentials + // Token created before NTP corrected the clock - refresh credentials // in place and reconnect the persistent client. No teardown needed. if (_slots[i].token_expires_at > 0 && current_time > _slots[i].token_expires_at) { MQTT_DEBUG_PRINTLN("MQTT%d token stale after time correction, re-creating", i + 1); @@ -3156,7 +3156,7 @@ bool MQTTBridge::syncTimeWithNTP(bool force, bool primary_only) { } } - // Set timezone from string (with DST support) — only if changed. + // Set timezone from string (with DST support) - only if changed. // Reuses the inline _timezone_storage via setRules() instead of // deleting/newing a Timezone, which was a per-change heap alloc pair. static char last_timezone[64] = ""; @@ -3204,7 +3204,7 @@ bool MQTTBridge::requestForcedNtpSync(uint32_t timeout_ms) { // Runs on the MQTT task (Core 0). Probes every configured NTP server for connectivity // and records the time each reports. Deliberately does NOT call configTime() or update -// the RTC — this is a read-only diagnostic and must leave the system clock untouched. +// the RTC - this is a read-only diagnostic and must leave the system clock untouched. void MQTTBridge::runNtpDiagProbe() { const char* servers[kMaxNtpServers]; int count = 0; @@ -3309,7 +3309,7 @@ bool MQTTBridge::ntpDiag(char* reply, size_t reply_size, bool verbose) { int n = snprintf(reply + used, reply_size - used, "%s%s %s", used ? "\n" : "", r.server, r.ok ? "ok" : "fail"); if (n < 0 || (size_t)n >= reply_size - used) { - reply[used] = '\0'; // out of room — truncate cleanly + reply[used] = '\0'; // out of room - truncate cleanly break; } used += (size_t)n; @@ -3324,7 +3324,7 @@ bool MQTTBridge::ntpDiag(char* reply, size_t reply_size, bool verbose) { // Populates dst_out and std_out with the DST/standard TimeChangeRules for the // given timezone string. Returns true on match, false on unknown strings. Zero -// heap allocation — the caller then passes these into Timezone::setRules() on +// heap allocation - the caller then passes these into Timezone::setRules() on // an existing Timezone object. bool MQTTBridge::timezoneRulesFromString(const char* tz_string, TimeChangeRule& dst_out, TimeChangeRule& std_out) { // GCC refuses to implicitly build a TimeChangeRule temporary from a bare @@ -3492,7 +3492,7 @@ void MQTTBridge::optimizeMqttClientConfig(PsychicMqttClient* client, bool needs_ // Keep the output buffer (used to build the CONNECT/PUBLISH frames) in lockstep // with the input buffer. setBufferSize() above only sets buffer.size, so out_size // must be set here. The previous conditional only ever shrank out_size or set it - // from 0 — when a slot was reconfigured from a small non-JWT buffer (512) up to + // from 0 - when a slot was reconfigured from a small non-JWT buffer (512) up to // the JWT buffer (896), out_size stayed at 512 and the JWT CONNECT frame (username // + ~537-768B token) overflowed it, producing esp-mqtt "Connect message cannot be // created". Always matching MQTT_CLIENT_BUFFER_SIZE fixes the grow case. diff --git a/src/helpers/bridges/MQTTBridge.h b/src/helpers/bridges/MQTTBridge.h index 24bdb1fe..64bf7ae7 100644 --- a/src/helpers/bridges/MQTTBridge.h +++ b/src/helpers/bridges/MQTTBridge.h @@ -111,7 +111,7 @@ private: unsigned long last_log_time; // Throttle disconnect log messages unsigned long last_deferred_log_ms; // Throttle "connect deferred" log spam (Phase 1) - // Last error (stored for CLI diagnostics — serial-free debugging) + // Last error (stored for CLI diagnostics - serial-free debugging) int32_t last_tls_err; // esp_tls_last_esp_err (0 = no error) int32_t last_tls_stack_err; // mbedTLS stack error int last_sock_errno; // socket errno @@ -168,7 +168,7 @@ private: #if defined(BOARD_HAS_PSRAM) static const int MAX_QUEUE_SIZE = 50; #else - // Reduced from 10: raw_data[256] adds ~256 bytes/item; 6×543 ≈ 3.3 KB vs old 10×282 ≈ 2.8 KB. + // Reduced from 10: raw_data[256] adds ~256 bytes/item; 6x543 ~ 3.3 KB vs old 10x282 ~ 2.8 KB. // Net increase is <500 bytes; _last_raw_data (256 bytes) is eliminated to offset further. static const int MAX_QUEUE_SIZE = 6; #endif @@ -252,7 +252,7 @@ private: Timezone* _timezone; // Core 1-only staging: written by storeRawRadioData(), consumed by queuePacket(). - // No mutex needed — both call sites run on Core 1 in guaranteed sequence. + // No mutex needed - both call sites run on Core 1 in guaranteed sequence. static const size_t LAST_RAW_DATA_SIZE = 256; uint8_t _staged_raw[LAST_RAW_DATA_SIZE]; int _staged_raw_len = 0; @@ -260,7 +260,7 @@ private: float _staged_rssi = 0.0f; bool _staged_raw_valid = false; - // Core 0-owned copy of the most recent raw data — written only by processPacketQueue() + // Core 0-owned copy of the most recent raw data - written only by processPacketQueue() // on Core 0, read only by publishStatus() on Core 0. No mutex required. // On PSRAM boards: heap pointer into PSRAM. On non-PSRAM: inline array in class object. static const size_t LAST_RAW_DATA_SIZE_MEMBER = 256; // mirrors LAST_RAW_DATA_SIZE @@ -274,7 +274,7 @@ private: float _last_rssi; unsigned long _last_raw_timestamp; - // JSON publish/status serialization buffers — reused for every publish (no alloc/free churn). + // JSON publish/status serialization buffers - reused for every publish (no alloc/free churn). // On PSRAM boards: heap pointer into PSRAM to save internal heap. On non-PSRAM: inline in // class object so these allocations don't interleave with large TLS buffers at startup. static const size_t PUBLISH_JSON_BUFFER_SIZE = 2048; @@ -287,14 +287,14 @@ private: char _status_json_buffer[STATUS_JSON_BUFFER_SIZE]; #endif - // JSON document scratch space — inline StaticJsonDocument keeps the pool off the MQTT + // JSON document scratch space - inline StaticJsonDocument keeps the pool off the MQTT // task stack and eliminates two separate heap allocations (fragmentation reduction). StaticJsonDocument _packet_json_doc; StaticJsonDocument _status_json_doc; // Memory pressure monitoring (per-publish skip; see publishPacket()). // The broader fragmentation-recovery machinery was removed in Phase 4 of - // the MQTT memory-defrag work — persistent MQTT clients no longer churn + // the MQTT memory-defrag work - persistent MQTT clients no longer churn // the heap, so gray-zone / critical-restart trackers are unnecessary. unsigned long _last_memory_check; int _skipped_publishes; // Exposed via SNMP; count of publishes skipped when max_alloc is too low @@ -330,7 +330,7 @@ private: bool _wifi_status_initialized; unsigned long _wifi_disconnected_time; // 0 when connected unsigned long _last_wifi_reconnect_attempt; - uint8_t _wifi_reconnect_backoff_attempt; // 0..5 → 15s, 30s, 60s, 120s, 300s; reset on connect + uint8_t _wifi_reconnect_backoff_attempt; // 0..5 -> 15s, 30s, 60s, 120s, 300s; reset on connect unsigned long _last_slot_reconnect_ms; // guards against concurrent TLS handshakes (15 s inter-slot gap) // Optional pointers for collecting stats internally (set by mesh if available) @@ -351,7 +351,7 @@ private: // - destroySlotClients() disconnects and deletes each client. Runs once in end(). // - setupSlot() configures an already-allocated client (server, credentials, // CA) and calls connect(). Safe to call multiple times to reconfigure. - // - teardownSlot() only disconnects — it never deletes the client. Leaves + // - teardownSlot() only disconnects - it never deletes the client. Leaves // the mbedTLS/transport state ready for a subsequent setupSlot(). // This avoids delete/new cycles that shed ~40 KB of mbedTLS buffers per // reconfigure and fragment the internal heap on non-PSRAM boards. @@ -464,7 +464,7 @@ public: * - getSlotCurrentOutageStartMs(): millis() of the current outage start * (0 when the slot is connected). Reset on each reconnect. * - isSlotEnabledAndAttempted(): true when the slot is enabled (preset - * != "none") and has reached at least one connect attempt — i.e. it is + * != "none") and has reached at least one connect attempt - i.e. it is * meaningful to alarm on its connection state. * - getSlotPresetName(): preset name for friendly status text. Returns * "custom"/"none"/preset->name; never null. diff --git a/src/helpers/esp32/WebConfigServer.cpp b/src/helpers/esp32/WebConfigServer.cpp index 95c9634a..ee55af86 100644 --- a/src/helpers/esp32/WebConfigServer.cpp +++ b/src/helpers/esp32/WebConfigServer.cpp @@ -517,7 +517,7 @@ void WebConfigServer::drainBatch(uint32_t now) { // Fallback only: the real 3 s reboot timer is armed when the client reads // /api/config/result (handleConfigResult), so the browser gets its // confirmation before the AP/WiFi drops. This covers a client that - // disconnected and never polls — generous enough for a phone that got + // disconnected and never polls - generous enough for a phone that got // bounced off the AP mid-save to rejoin and fetch its confirmation. _reboot_at = now + 30000; if (_reboot_at == 0) _reboot_at = 1; @@ -894,7 +894,7 @@ void WebConfigServer::handleConfigResult(AsyncWebServerRequest* req) { } // State stays DONE (re-readable) until the next POST claims the slot. if (_batch_reboot && !_batch_reboot_armed) { - // Confirmation delivered — reboot 3 s from now (replaces the 30 s + // Confirmation delivered - reboot 3 s from now (replaces the 30 s // drain-time fallback) so the UI can show its countdown first. Armed // once; re-reads must not keep pushing the deadline out. _batch_reboot_armed = true; diff --git a/src/helpers/esp32/WebConfigServer.h b/src/helpers/esp32/WebConfigServer.h index 717f8217..1569c5e4 100644 --- a/src/helpers/esp32/WebConfigServer.h +++ b/src/helpers/esp32/WebConfigServer.h @@ -117,7 +117,7 @@ public: static bool getSetupInfo(char* ssid, size_t ssid_len, char* ip, size_t ip_len); // For the device display: true once a config save completed and the node is - // about to reboot — ground truth for the user even if the browser lost its + // about to reboot - ground truth for the user even if the browser lost its // connection before the confirmation arrived. Loop task only. static bool isRebootPending(); diff --git a/src/helpers/nrf52/EthernetCLI.h b/src/helpers/nrf52/EthernetCLI.h index 34802e56..967e42f0 100644 --- a/src/helpers/nrf52/EthernetCLI.h +++ b/src/helpers/nrf52/EthernetCLI.h @@ -38,7 +38,7 @@ static void ethernet_task(void* param) { Serial.println("ETH: Initializing hardware"); // WB_IO2 (power enable) is already driven HIGH by early constructor // in RAK4631Board.cpp to support POE boot. - // Skip hardware reset — the W5100S comes out of power-on reset cleanly, + // Skip hardware reset - the W5100S comes out of power-on reset cleanly, // and toggling reset kills the PHY link which breaks POE power. pinMode(PIN_ETHERNET_RESET, OUTPUT); digitalWrite(PIN_ETHERNET_RESET, HIGH); diff --git a/src/helpers/nrf52/SerialEthernetInterface.cpp b/src/helpers/nrf52/SerialEthernetInterface.cpp index b25a36b1..fff51cc1 100644 --- a/src/helpers/nrf52/SerialEthernetInterface.cpp +++ b/src/helpers/nrf52/SerialEthernetInterface.cpp @@ -26,7 +26,7 @@ bool SerialEthernetInterface::begin() { // WB_IO2 (power enable) is already driven HIGH by early constructor // in RAK4631Board.cpp to support POE boot. - // Skip hardware reset — the W5100S comes out of power-on reset cleanly, + // Skip hardware reset - the W5100S comes out of power-on reset cleanly, // and toggling reset kills the PHY link which breaks POE power. #ifdef PIN_ETHERNET_RESET pinMode(PIN_ETHERNET_RESET, OUTPUT); @@ -138,7 +138,7 @@ size_t SerialEthernetInterface::checkRecvFrame(uint8_t dest[]) { // Use accept() (not available()) so we only see newly-accepted sockets. // available() also returns existing connected sockets that have data, // which would cause us to treat each inbound packet as a "new client" - // and stop() the underlying socket — disconnecting the companion. + // and stop() the underlying socket - disconnecting the companion. auto newClient = server.accept(); if (newClient) { IPAddress new_ip = newClient.remoteIP(); diff --git a/src/helpers/ota/BlockBitmap.h b/src/helpers/ota/BlockBitmap.h index c22835f5..467cbbf8 100644 --- a/src/helpers/ota/BlockBitmap.h +++ b/src/helpers/ota/BlockBitmap.h @@ -4,7 +4,7 @@ #include #include -// Block-availability helpers (docs/ota_protocol.md §7). +// Block-availability helpers (docs/ota_protocol.md Section 7). // // Availability is *derived* from the staged manifest's leaves[]: block i is present iff its 4-byte // leaf slot is non-erased (!= FF FF FF FF). No separate persistent structure. A compact bitmap diff --git a/src/helpers/ota/FirmwareInfo.cpp b/src/helpers/ota/FirmwareInfo.cpp index c58cbfcc..4b97ffa7 100644 --- a/src/helpers/ota/FirmwareInfo.cpp +++ b/src/helpers/ota/FirmwareInfo.cpp @@ -20,7 +20,7 @@ bool find_self_firmware(const uint8_t* region, uint32_t region_len, if (verify_body) { uint8_t h[8]; mh8(h, region, body_len); - if (memcmp(h, region + off + 8, 8) != 0) continue; // coincidental marker — keep scanning + if (memcmp(h, region + off + 8, 8) != 0) continue; // coincidental marker - keep scanning } out.valid = true; out.endf_offset = off; diff --git a/src/helpers/ota/FirmwareInfo.h b/src/helpers/ota/FirmwareInfo.h index a08bdc75..86ba8776 100644 --- a/src/helpers/ota/FirmwareInfo.h +++ b/src/helpers/ota/FirmwareInfo.h @@ -5,7 +5,7 @@ #include "OtaFormat.h" // Locate the EndF trailer in a firmware image to learn the running firmware's size + identity -// (docs/ota_protocol.md §2). Portable: operates on a contiguous, readable region — on nRF52/ESP32 +// (docs/ota_protocol.md Section 2). Portable: operates on a contiguous, readable region - on nRF52/ESP32 // the application flash is memory-mapped, so the region pointer is just (const uint8_t*)APP_BASE. namespace mesh { @@ -17,15 +17,15 @@ struct SelfFwInfo { uint32_t image_len = 0; // body_len + ENDF_LEN (what a delta base / full image hashes over) uint32_t endf_offset = 0; // offset of the "EndF" marker within the region (== body_len) uint8_t body_hash[8] = {0}; // sha2-256:8 of the body (read from EndF; == a delta's base_hash) - // Self-describing identity — always present in the fixed 56-byte trailer (zero/"" means "unknown", + // Self-describing identity - always present in the fixed 56-byte trailer (zero/"" means "unknown", // e.g. a dev build with no dotted version). uint32_t fw_version = 0; // packed MAJOR<<24|MINOR<<16|PATCH<<8|pre - uint32_t target_id = 0; // sha2-256:4(env) as uint32 — hw+role+partition (fetch routing) + uint32_t target_id = 0; // sha2-256:4(env) as uint32 - hw+role+partition (fetch routing) char hw_id[33] = {0}; // readable hardware tag (NUL-terminated), e.g. "RAK4631" }; // Scan `region[0..region_len)` for the firmware's EndF trailer. The body starts at offset 0, so the -// trailer's offset must equal its stored body_len — this uniquely identifies the running firmware's +// trailer's offset must equal its stored body_len - this uniquely identifies the running firmware's // EndF even if a staged `.mota` (which contains its own embedded EndF) sits higher in the region. // If `verify_body` is true the body hash is recomputed and must match (rules out coincidental markers). bool find_self_firmware(const uint8_t* region, uint32_t region_len, diff --git a/src/helpers/ota/FolderMotaStore.h b/src/helpers/ota/FolderMotaStore.h index 9a3ff673..6e60ffba 100644 --- a/src/helpers/ota/FolderMotaStore.h +++ b/src/helpers/ota/FolderMotaStore.h @@ -5,7 +5,7 @@ // An OtaStore that captures an in-transit `.mota` onto a HOST folder over the mota-seeder link (the WRITE // half of MotaSeederProto: OP_STAT/BEGIN/WRITE/SREAD/FIN). This is the destination for `ota pull <#> folder`: -// blocks stream straight to the host `.mota` — the device holds NO RAM/flash staging for it. +// blocks stream straight to the host `.mota` - the device holds NO RAM/flash staging for it. // // begin(total) -> OP_BEGIN (host creates a 0xFF-filled .mota.part) // write(off,data) -> OP_WRITE (split into <= MOTA_SEEDER_WRITE_MAX chunks) @@ -24,13 +24,13 @@ class FolderMotaStore : public OtaStore { public: explicit FolderMotaStore(Stream& io, uint32_t timeout_ms = 3000) : _io(io), _to(timeout_ms) {} - // The container being pulled — set from the chosen `ota pull` mid before begin()/reopen(). + // The container being pulled - set from the chosen `ota pull` mid before begin()/reopen(). void set_mid(const uint8_t mid[4]) { memcpy(_mid, mid, 4); } bool begin(uint32_t total_size) override; bool write(uint32_t off, const uint8_t* data, uint32_t len) override; bool read(uint32_t off, uint8_t* buf, uint32_t len) const override; - uint32_t capacity() const override { return 0xF0000000u; } // host disk — effectively unbounded + uint32_t capacity() const override { return 0xF0000000u; } // host disk - effectively unbounded uint32_t staged_size() const override { return _total; } void clear() override { _total = 0; } bool finalize() override; diff --git a/src/helpers/ota/MerkleTree.h b/src/helpers/ota/MerkleTree.h index 15987f5b..5c82b6d9 100644 --- a/src/helpers/ota/MerkleTree.h +++ b/src/helpers/ota/MerkleTree.h @@ -3,7 +3,7 @@ #include #include -// Merkle tree over PAYLOAD blocks, sha2-256:4 (4-byte) leaves/nodes. See docs/ota_protocol.md §6. +// Merkle tree over PAYLOAD blocks, sha2-256:4 (4-byte) leaves/nodes. See docs/ota_protocol.md Section 6. // // Scheme: leaf = H(block); node = H(left || right); on an odd level the last node is promoted // unchanged (no duplication). Root = single remaining node. diff --git a/src/helpers/ota/MotaContainer.cpp b/src/helpers/ota/MotaContainer.cpp index dcdd9538..4a4c5ed2 100644 --- a/src/helpers/ota/MotaContainer.cpp +++ b/src/helpers/ota/MotaContainer.cpp @@ -11,7 +11,7 @@ bool MotaManifest::is_approved() const { return approval && memcmp(approval, APPROVAL_YES, 4) == 0; } -// Fixed-layout parse (docs/ota_protocol.md §4): every field sits at a constant offset — base_hash(8), +// Fixed-layout parse (docs/ota_protocol.md Section 4): every field sits at a constant offset - base_hash(8), // signer_pubkey(32) and signature(64) are ALWAYS present (zero-filled when not applicable), so there are // no conditionals. Only leaves[]/payload (after `approval`) is variable, read by the caller. The signature // always covers manifest[0, MOTA_SIGNED_LEN). Returns false on any over-read or bad format_ver. diff --git a/src/helpers/ota/MotaContainer.h b/src/helpers/ota/MotaContainer.h index f344fd97..3f6686da 100644 --- a/src/helpers/ota/MotaContainer.h +++ b/src/helpers/ota/MotaContainer.h @@ -5,7 +5,7 @@ #include "OtaFormat.h" // Parse/validate a `.mota` container that is fully present in a RAM buffer (docs/ota_protocol.md -// §3-§4). Variable-length parts are referenced by pointer into the caller's buffer — no copies, no +// Section 3-Section 4). Variable-length parts are referenced by pointer into the caller's buffer - no copies, no // allocation. (Device flash-backed staging gets a streaming variant in a later milestone; the field // layout here is the single source of truth.) @@ -24,7 +24,7 @@ struct MotaManifest { uint8_t codec_id = 0; uint32_t block_count = 0; - // Fixed layout (docs/ota_protocol.md §4): every field below sits at a constant offset and is ALWAYS + // Fixed layout (docs/ota_protocol.md Section 4): every field below sits at a constant offset and is ALWAYS // present; base_hash/signer_pubkey/signature are zero-filled when not applicable (full / unsigned). const uint8_t* merkle_root = nullptr; // 4 @20 const uint8_t* image_hash = nullptr; // 32 @24 diff --git a/src/helpers/ota/MotaSeederProto.h b/src/helpers/ota/MotaSeederProto.h index e534075b..a5d36593 100644 --- a/src/helpers/ota/MotaSeederProto.h +++ b/src/helpers/ota/MotaSeederProto.h @@ -4,12 +4,12 @@ // Wire contract for the "mota-seeder" link: a device (CLIENT) pulls `.mota` bytes on demand from a host // daemon (SERVER) that owns a folder of `.mota` files. This is the FIRST concrete MotaSource transport -// (docs/ota_protocol.md §9) — the device speaks it over a dedicated Stream (a spare UART / USB-UART), so +// (docs/ota_protocol.md Section 9) - the device speaks it over a dedicated Stream (a spare UART / USB-UART), so // it never contends with the line-based text CLI on the main console. // // The device always initiates; every request gets exactly one response. Framing is resync-safe: the // reader scans for the 2-byte magic, so line noise / a half-read frame just times out and is retried -// (OTA is lowest priority — eventually-upgradable). All multi-byte fields are little-endian. +// (OTA is lowest priority - eventually-upgradable). All multi-byte fields are little-endian. // // request (device -> host): 'M' 'S' op(1) args... xsum(1 = XOR of op+args) // response (host -> device): 'm' 's' op(1) status(1) payload... xsum(1 = XOR of all prior) @@ -50,7 +50,7 @@ static const uint8_t MS_OP_STAT = 0x04; // storage: does .mota exist static const uint8_t MS_OP_BEGIN = 0x05; // storage: create/truncate .mota (0xFF-filled) static const uint8_t MS_OP_WRITE = 0x06; // storage: write bytes at offset static const uint8_t MS_OP_SREAD = 0x07; // storage: read bytes back (resume: recompute missing blocks) -static const uint8_t MS_OP_FIN = 0x08; // storage: transfer complete — validate + make servable +static const uint8_t MS_OP_FIN = 0x08; // storage: transfer complete - validate + make servable static const uint8_t MS_STATUS_OK = 0x00; static const uint8_t MS_STATUS_ERR = 0x01; diff --git a/src/helpers/ota/MotaSourceSerial.cpp b/src/helpers/ota/MotaSourceSerial.cpp index 9b302329..b1ef7a73 100644 --- a/src/helpers/ota/MotaSourceSerial.cpp +++ b/src/helpers/ota/MotaSourceSerial.cpp @@ -79,7 +79,7 @@ bool SerialMotaSource::describe(uint8_t idx, MotaDesc& out) { out.block_count = rd_u32le(w + 22); out.payload_off = rd_u32le(w + 26); out.payload_size = rd_u32le(w + 30); - // bytes [34,38) reserved (zero) — kept for forward compat without changing MOTA_DESC_WIRE + // bytes [34,38) reserved (zero) - kept for forward compat without changing MOTA_DESC_WIRE return true; } diff --git a/src/helpers/ota/MotaSourceSerial.h b/src/helpers/ota/MotaSourceSerial.h index 26b9e298..b4570d3f 100644 --- a/src/helpers/ota/MotaSourceSerial.h +++ b/src/helpers/ota/MotaSourceSerial.h @@ -5,7 +5,7 @@ // A MotaSource backed by a host "mota-seeder" daemon over a dedicated Stream (a spare UART / USB-UART). // The device pulls catalog + bytes on demand (MotaSeederProto.h); the folder image is never held on the -// device — it streams through. Use a stream that is NOT the text-CLI console so the binary framing never +// device - it streams through. Use a stream that is NOT the text-CLI console so the binary framing never // collides with command/log text. Reads block on the Stream up to `timeout_ms` (OTA is lowest priority, // so a serial round-trip's latency is acceptable; keep the daemon on a fast link). diff --git a/src/helpers/ota/OtaApply.cpp b/src/helpers/ota/OtaApply.cpp index 81425f5f..e06afd1c 100644 --- a/src/helpers/ota/OtaApply.cpp +++ b/src/helpers/ota/OtaApply.cpp @@ -147,7 +147,7 @@ static int dt_to_write(void* arg, const uint8_t* buf, size_t size) { // staged container's payload (which lives at/below write_start, disjoint from the working region). The // decoded image is hashed against the signed image_hash BEFORE arming, so a bad decode never boots; the // callbacks are bounded to [0, write_start) so they fail gracefully instead of touching the patch. -// (Sequential is still preferred on ESP32 — it streams straight to the slot with no base-copy; in-place +// (Sequential is still preferred on ESP32 - it streams straight to the slot with no base-copy; in-place // exists only for single-artifact distribution. Requires the patch built with --inplace-segment 4096.) struct InPlaceCtx { const esp_partition_t* slot; // in-place working memory = slot[0, mem_max) @@ -182,7 +182,7 @@ static int ip_mem_erase(void* a, uintptr_t addr, size_t n) { // esp_partition_erase_range requires a SECTOR-aligned size; detools' final in-place segment is partial // (the image tail past the last full sector). addr is sector-aligned (== --inplace-segment), so round // the length UP to a full sector. The over-erased bytes are scratch beyond image_size (never hashed), - // and — since detools processes high→low and erases-before-writing — they are never live patch data. + // and - since detools processes high->low and erases-before-writing - they are never live patch data. const uint32_t SEC = 4096; if ((uint32_t)addr % SEC != 0) return ip_fail(c, "er!align", (uint32_t)addr, n, 0); uint32_t len = ((uint32_t)n + SEC - 1) & ~(SEC - 1); @@ -254,7 +254,7 @@ static bool esp32_inplace_apply(OtaStoreFlashEsp32& store, const MotaManifest& m st.slot_ok = (memcmp(hh, m.image_hash, 32) == 0); if (!st.slot_ok) { strcpy(msg, "image_hash MISMATCH after in-place decode"); return false; } if (esp_ota_set_boot_partition(slot) != ESP_OK) { strcpy(msg, "set_boot failed"); return false; } - sprintf(msg, "verified%s; in-place decoded %u B, image hash OK — armed, rebooting to apply", + sprintf(msg, "verified%s; in-place decoded %u B, image hash OK - armed, rebooting to apply", m.is_signed() ? " (signer trusted)" : " (unsigned)", (unsigned)m.image_size); return true; } @@ -310,7 +310,7 @@ bool ota_apply_detools_mota(OtaStoreFlashEsp32& store, const SignerAllowlist& al st.slot_ok = (memcmp(hh, m.image_hash, 32) == 0); if (!st.slot_ok) { strcpy(msg, "image_hash MISMATCH (slot)"); return false; } if (esp_ota_set_boot_partition(slot) != ESP_OK) { strcpy(msg, "set_boot failed"); return false; } - sprintf(msg, "verified%s full image %u B in slot — armed, rebooting to apply", + sprintf(msg, "verified%s full image %u B in slot - armed, rebooting to apply", m.is_signed() ? " (trusted)" : " (unsigned)", (unsigned)m.image_size); return true; } @@ -348,7 +348,7 @@ bool ota_apply_detools_mota(OtaStoreFlashEsp32& store, const SignerAllowlist& al if (!st.slot_ok) { esp_ota_abort(h); strcpy(msg, "image_hash MISMATCH after decode"); return false; } if (esp_ota_end(h) != ESP_OK) { strcpy(msg, "ota_end failed"); return false; } if (esp_ota_set_boot_partition(slot) != ESP_OK) { strcpy(msg, "set_boot failed"); return false; } - sprintf(msg, "verified%s; decoded %u B, image hash OK — armed, rebooting to apply", + sprintf(msg, "verified%s; decoded %u B, image hash OK - armed, rebooting to apply", m.is_signed() ? " (signer trusted)" : " (unsigned)", (unsigned)m.image_size); return true; } @@ -394,7 +394,7 @@ bool ota_apply_detools_mota(const uint8_t* buf, uint32_t len, const SignerAllowl if (!st.slot_ok) { esp_ota_abort(h); strcpy(msg, "image_hash MISMATCH after decode"); return false; } if (esp_ota_end(h) != ESP_OK) { strcpy(msg, "ota_end failed"); return false; } if (esp_ota_set_boot_partition(out) != ESP_OK) { strcpy(msg, "set_boot failed"); return false; } - sprintf(msg, "verified%s; decoded %u B, image hash OK — armed, rebooting to apply", + sprintf(msg, "verified%s; decoded %u B, image hash OK - armed, rebooting to apply", m.is_signed() ? " (signer trusted)" : " (unsigned)", (unsigned)m.image_size); return true; } @@ -444,13 +444,13 @@ bool ota_apply_mota_nrf52(const uint8_t* buf, uint32_t len, const SignerAllowlis memcpy(st.image_hash, m.image_hash, 32); st.manifest_ok = true; - // 0) THIS device's bootloader must be able to apply this .mota — otherwise staging + approving + rebooting + // 0) THIS device's bootloader must be able to apply this .mota - otherwise staging + approving + rebooting // just bounces back unchanged (a legacy/stock/older-OTAFIX bootloader). Refuse here, before any reboot. { OtaBlCaps bl = ota_bootloader_caps(); - if (!bl.present) { strcpy(msg, "this bootloader has no OTA-apply support — update the bootloader first"); return false; } + if (!bl.present) { strcpy(msg, "this bootloader has no OTA-apply support - update the bootloader first"); return false; } if (bl.apply_abi < m.format_ver || !(bl.codec_mask & (1u << m.codec_id))) { - snprintf(msg, 159, "bootloader too old to apply this update (bl abi=%u codecs=0x%x; need fmt>=%u codec=%u) — update the bootloader", + snprintf(msg, 159, "bootloader too old to apply this update (bl abi=%u codecs=0x%x; need fmt>=%u codec=%u) - update the bootloader", bl.apply_abi, bl.codec_mask, m.format_ver, m.codec_id); return false; } @@ -487,9 +487,9 @@ bool ota_apply_mota_nrf52(const uint8_t* buf, uint32_t len, const SignerAllowlis flash_nrf5x_flush(); if (memcmp((const void*)(uintptr_t)approval_addr, APPROVAL_YES, 4) != 0) { strcpy(msg, "approval not set"); return false; } - // Approved. Do NOT reset here — return so the caller can deliver `msg` to the operator first; the + // Approved. Do NOT reset here - return so the caller can deliver `msg` to the operator first; the // deferred ota_reboot_to_apply() (after the reply is sent) does the actual handoff to the bootloader. - sprintf(msg, "verified%s; applying — rebooting into bootloader once this reply is sent", + sprintf(msg, "verified%s; applying - rebooting into bootloader once this reply is sent", vr.is_signed ? " (signer trusted)" : " (unsigned)"); return true; } diff --git a/src/helpers/ota/OtaApply.h b/src/helpers/ota/OtaApply.h index 42e2f7b1..e6a6b1f1 100644 --- a/src/helpers/ota/OtaApply.h +++ b/src/helpers/ota/OtaApply.h @@ -45,13 +45,13 @@ bool ota_apply_detools_mota(const uint8_t* buf, uint32_t len, // nRF52 (RAK4631) single-slot apply. The running app can't rewrite itself, so it does NOT decode: it // runs the gated verification chain (payload hash -> built-for-this-firmware -> signature/trust) and, -// only if all pass, marks the staged manifest APPROVED in flash. It does NOT reboot — so the caller can -// first send the result back to the operator — the actual handoff is ota_reboot_to_apply() below. +// only if all pass, marks the staged manifest APPROVED in flash. It does NOT reboot - so the caller can +// first send the result back to the operator - the actual handoff is ota_reboot_to_apply() below. // Returns true (msg = "verified...") when approved, false (msg = the first failing gate) otherwise. bool ota_apply_mota_nrf52(const uint8_t* buf, uint32_t len, const SignerAllowlist& allow, ApplyState& st, char* msg); -// Commit the (already approved/armed) update and reboot into it — does NOT return. Call this only after +// Commit the (already approved/armed) update and reboot into it - does NOT return. Call this only after // a successful ota_apply_* AND after the confirmation reply has been delivered, so the operator knows // the apply started (over LoRa the device then goes silent while the bootloader applies). nRF52: set // the GPREGRET apply magic + reset (the bootloader does the in-place decode + verify). ESP32: reboot diff --git a/src/helpers/ota/OtaBlInfo.h b/src/helpers/ota/OtaBlInfo.h index d22468fa..199cde38 100644 --- a/src/helpers/ota/OtaBlInfo.h +++ b/src/helpers/ota/OtaBlInfo.h @@ -8,7 +8,7 @@ // bootloader that silently can't apply (stock Adafruit, or an OLDER OTAFIX predating a `.mota` format // change) and the device would just come back up unchanged. // -// Mirror of Adafruit_nRF52_Bootloader_OTAFIX/src/ota_bl_info.h — keep byte-identical. +// Mirror of Adafruit_nRF52_Bootloader_OTAFIX/src/ota_bl_info.h - keep byte-identical. // nRF52 only (the bootloader flash is memory-mapped + readable by the app); a no-op elsewhere. #if defined(NRF52_PLATFORM) diff --git a/src/helpers/ota/OtaByteIO.h b/src/helpers/ota/OtaByteIO.h index d4834cd8..2665c856 100644 --- a/src/helpers/ota/OtaByteIO.h +++ b/src/helpers/ota/OtaByteIO.h @@ -3,7 +3,7 @@ #include #include -// A tiny bounds-checked little-endian cursor for reading the `.mota` container (docs/ota_protocol.md §3-§4) +// A tiny bounds-checked little-endian cursor for reading the `.mota` container (docs/ota_protocol.md Section 3-Section 4) // in a self-documenting way: each field is read by name in order, instead of hand-computed byte offsets // (`p[0]`, `rd_u32(p+3)`, `p += 89`, `NEED(n)` ...). Any over-read flips `ok` false and yields zero/null, so // callers parse the whole struct then check `r.ok` once. 32-bit offsets (a container can be >64 KB; the @@ -25,7 +25,7 @@ inline void wr_u32le(uint8_t* p, uint32_t v) { p[0] = (uint8_t)v; p[1] = (uint8_t)(v >> 8); p[2] = (uint8_t)(v >> 16); p[3] = (uint8_t)(v >> 24); } -// Round to a multiple of `unit` (a power of two — a flash sector/page size). Names the `& ~(unit-1)` +// Round to a multiple of `unit` (a power of two - a flash sector/page size). Names the `& ~(unit-1)` // idiom so flash-geometry math in the stores reads as intent (align down / align up). inline uint32_t align_down(uint32_t x, uint32_t unit) { return x & ~(unit - 1); } inline uint32_t align_up(uint32_t x, uint32_t unit) { return (x + unit - 1) & ~(unit - 1); } diff --git a/src/helpers/ota/OtaCli.cpp b/src/helpers/ota/OtaCli.cpp index 1fd73751..55a6f942 100644 --- a/src/helpers/ota/OtaCli.cpp +++ b/src/helpers/ota/OtaCli.cpp @@ -47,7 +47,7 @@ static const char* state_word(OtaManager::FetchState s) { case OtaManager::FETCHING: return "downloading"; case OtaManager::COMPLETE: return "ready to install"; case OtaManager::FAILED: return "failed"; - case OtaManager::PAUSED: return "paused (folder link lost — reconnect to resume)"; + case OtaManager::PAUSED: return "paused (folder link lost - reconnect to resume)"; default: return "?"; } } @@ -135,18 +135,18 @@ bool handle_ota_command(const char* command, char* reply, mesh::MainBoard& board c.serving ? "on" : "off", (unsigned)c.manager.servedCount(), (unsigned)c.allow.count(), (unsigned)c.manager.target(), tenv ? tenv : "?"); #if defined(NRF52_PLATFORM) - // nRF52 applies via the bootloader — show (cached) whether it can, so `ota get`/`install` won't surprise. + // nRF52 applies via the bootloader - show (cached) whether it can, so `ota get`/`install` won't surprise. // blrc = the bootloader's last in-place-apply code (diagnostic; 0xB8=success, see ota_delta.c). if (n < 146) n += snprintf(reply + n, 160 - n, " | bl:%s blrc:%02X", c.bootloaderCaps().present ? "apply" : "NONE", ota_bootloader_last_rc()); #endif // ---- admin OTA stats: crypto identities (our fw's content-id + body_hash), serving set, live fetch, - // policy — one dense line. The remote-admin CLI path is admin-gated, so this is admin-only over the + // policy - one dense line. The remote-admin CLI path is admin-gated, so this is admin-only over the // mesh (send it from the app's repeater command screen, or the WiFi/serial OTA console). } else if (is_cmd(a, "stats", &rest)) { // our running fw's merkle content-id (mid) comes from the self serve entry; the EndF body_hash (fw - // identity, matched against a delta base) is separate — surface BOTH (only body_hash showed before). + // identity, matched against a delta base) is separate - surface BOTH (only body_hash showed before). const OtaManager::ServeEntry* self = nullptr; for (uint8_t i = 0; i < c.manager.servedCount(); i++) { const OtaManager::ServeEntry* e = c.manager.servedEntry(i); @@ -178,12 +178,12 @@ bool handle_ota_command(const char* command, char* reply, mesh::MainBoard& board // ---- what's available around me (catalogued from beacons + OTA_HAVE), best/most-recent first ---- } else if (is_cmd(a, "neighbors|nbrs|updates|ls|n", &rest)) { - // Kick a fresh round of catalog queries (async — rows arrive over the next seconds); render what we + // Kick a fresh round of catalog queries (async - rows arrive over the next seconds); render what we // have now in plain words. The reply buffer is 160 B (serial / one LoRa packet for remote-admin), so // writes are bounded and extra rows collapse to "+N more". c.manager.queryAll(); const int CAP = 160; - int n = snprintf(reply, CAP, "Updates nearby (%u src) — `ota get <#>` to download:", + int n = snprintf(reply, CAP, "Updates nearby (%u src) - `ota get <#>` to download:", (unsigned)c.manager.sourceCount()); OtaManager::FetchState fs = c.manager.fetchState(); const uint8_t* cur = (fs != OtaManager::IDLE) ? c.manager.fetchManifestId() : nullptr; @@ -193,7 +193,7 @@ bool handle_ota_command(const char* command, char* reply, mesh::MainBoard& board const OtaManager::CatRow* h = c.manager.catalogRow(i); if (CAP - n < 48) { more++; continue; } bool on = cur && memcmp(cur, h->mid, 4) == 0; - // Tag the active session by real fetch state (not always "downloading" — COMPLETE is ready). + // Tag the active session by real fetch state (not always "downloading" - COMPLETE is ready). const char* tag = ""; if (on) { if (fs == OtaManager::COMPLETE) tag = " [ready]"; @@ -204,7 +204,7 @@ bool handle_ota_command(const char* command, char* reply, mesh::MainBoard& board uint32_t age = (now - h->last_ms) / 1000; if (age > 99999) age = 99999; char ver[20]; ver_str(ver, sizeof ver, h->fw_version); // What is this update for? "yours" if same target (hw+role) as us; else the target's env name when we - // know it (named locally from its 4-byte target_id — no string travels on the wire); else the raw + // know it (named locally from its 4-byte target_id - no string travels on the wire); else the raw // target_id hex (an env this build's OtaTargets.h table doesn't know) or '?' for an unset target. char hwbuf[16]; const char* fit; @@ -218,13 +218,13 @@ bool handle_ota_command(const char* command, char* reply, mesh::MainBoard& board shown++; } if (more && n < CAP) snprintf(reply + n, CAP - n, "\n +%d more", more); - if (shown == 0) strcpy(reply, "No updates seen yet — re-run `ota ls` in a few seconds (just asked around)."); + if (shown == 0) strcpy(reply, "No updates seen yet - re-run `ota ls` in a few seconds (just asked around)."); // ---- start fetching a specific catalogued mOTA (by list index or manifest_id) ---- } else if (is_cmd(a, "pull|get|download", &rest)) { const char* p = rest; while (*p == ' ') p++; // split into " [destination]": selector = #N / N (catalogue index) or mid hex; destination = - // flash | folder (MANDATORY — `folder` captures the .mota onto the connected motatool folder as .mota). + // flash | folder (MANDATORY - `folder` captures the .mota onto the connected motatool folder as .mota). char selstr[24]; int i = 0; while (p[i] && p[i] != ' ' && i < (int)sizeof(selstr) - 1) { selstr[i] = p[i]; i++; } selstr[i] = 0; @@ -248,7 +248,7 @@ bool handle_ota_command(const char* command, char* reply, mesh::MainBoard& board snprintf(reply, 160, "choose a destination: `ota pull %s flash` | `ota pull %s folder` (folder: %s)", selstr, selstr, c.folder_dest_info); else - snprintf(reply, 160, "choose a destination: `ota pull %s flash` (folder: none connected — motatool serve)", + snprintf(reply, 160, "choose a destination: `ota pull %s flash` (folder: none connected - motatool serve)", selstr); return true; } @@ -312,7 +312,7 @@ bool handle_ota_command(const char* command, char* reply, mesh::MainBoard& board } else if (is_cmd(a, "install|apply|applydelta", &rest)) { // Apply the fetched update. Destructive (reflashes + reboots) and GATED, not interactive (no "type - // yes" round-trip — unreliable over LoRa): refuse unless the fetch is COMPLETE, then the apply path + // yes" round-trip - unreliable over LoRa): refuse unless the fetch is COMPLETE, then the apply path // validates in order (payload hash -> built-for-this-firmware -> signature/trust) and returns the // FIRST failing gate, so the operator knows exactly why it refused; it proceeds only if all pass. if (c.manager.fetchState() != OtaManager::COMPLETE || c.fetch_store.staged_size() == 0) { @@ -321,7 +321,7 @@ bool handle_ota_command(const char* command, char* reply, mesh::MainBoard& board (unsigned)c.manager.blocksTotal()); return true; } - // On success the slot is armed but NOT yet rebooted — defer so this reply reaches the operator first; + // On success the slot is armed but NOT yet rebooted - defer so this reply reaches the operator first; // the mesh loop reboots once it has been transmitted (same path used by auto-install). char m2[100]; bool ok = c.apply_fetched(m2); @@ -373,17 +373,17 @@ bool handle_ota_command(const char* command, char* reply, mesh::MainBoard& board long n = atol(p + 11); if (n < 0 || n > 4096) { strcpy(reply, "ERR usage: ota config checkpoint <0..4096> (blocks; 0=never)"); return true; } c.manager.set_checkpoint_blocks((uint16_t)n); c.config_dirty = true; - sprintf(reply, "OK checkpoint every %ld blocks (saved)%s", n, n == 0 ? " — periodic resume disabled" : ""); + sprintf(reply, "OK checkpoint every %ld blocks (saved)%s", n, n == 0 ? " - periodic resume disabled" : ""); } else if (strncmp(p, "advert ", 7) == 0) { // beacon re-advertise cadence (minutes; 0=disable) long m = atol(p + 7); if (m < 0 || m > 10080) { strcpy(reply, "ERR usage: ota config advert <0..10080> (minutes; 0=disable)"); return true; } c.manager.set_advert_mins((uint16_t)m); c.config_dirty = true; - sprintf(reply, "OK re-advertise every %ld min (saved)%s", m, m == 0 ? " — periodic advert disabled" : ""); + sprintf(reply, "OK re-advertise every %ld min (saved)%s", m, m == 0 ? " - periodic advert disabled" : ""); } else if (strncmp(p, "hops ", 5) == 0) { // OTA flood reach in hops (0 = direct only) long h = atol(p + 5); if (h < 0 || h > 8) { strcpy(reply, "ERR usage: ota config hops <0..8> (hops; 0 = direct only)"); return true; } c.manager.set_max_hops((uint8_t)h); c.config_dirty = true; - sprintf(reply, "OK OTA reach = %ld hop%s (saved)%s", h, h == 1 ? "" : "s", h == 0 ? " — direct only" : ""); + sprintf(reply, "OK OTA reach = %ld hop%s (saved)%s", h, h == 1 ? "" : "s", h == 0 ? " - direct only" : ""); } else { // show current policy uint8_t af = c.manager.autofetch(); sprintf(reply, "ota config: autofetch=%s autoinstall=%s checkpoint=%u advert=%umin hops=%u keys=%u (persisted)", @@ -444,7 +444,7 @@ static bool handle_dev(const char* d, char* reply, OtaContext& c) { char midhx[9]; mesh::Utils::toHex(midhx, c.serve_self_manifest + 20, 4); uint32_t img = (uint32_t)c.serve_self_manifest[11] | ((uint32_t)c.serve_self_manifest[12] << 8) | ((uint32_t)c.serve_self_manifest[13] << 16) | ((uint32_t)c.serve_self_manifest[14] << 24); - sprintf(reply, "OK serving self fw mid=%s (%u B, flash-backed) — peers can pull it", midhx, (unsigned)img); + sprintf(reply, "OK serving self fw mid=%s (%u B, flash-backed) - peers can pull it", midhx, (unsigned)img); } else strcpy(reply, "ERR serve self (no EndF / image too big / OOM)"); } else if (strncmp(d, "serve", 5) == 0) { c.serving = c.manager.serve(c.serve_buf, c.serve_expected); diff --git a/src/helpers/ota/OtaContext.h b/src/helpers/ota/OtaContext.h index 70f11269..abd92aeb 100644 --- a/src/helpers/ota/OtaContext.h +++ b/src/helpers/ota/OtaContext.h @@ -7,7 +7,7 @@ #include "SignerAllowlist.h" #include "OtaApply.h" #include "OtaFormat.h" -#include "OtaSelf.h" // ota_self_firmware() — prefer self-describing EndF identity at begin() +#include "OtaSelf.h" // ota_self_firmware() - prefer self-describing EndF identity at begin() #include "OtaBlInfo.h" // bootloader OTA-apply capability marker (nRF52); cached after first read #if defined(NRF52_PLATFORM) && defined(OTA_FLASH_STORE) #include "OtaStoreFlashNrf52.h" @@ -31,7 +31,7 @@ // nRF52 stages into FLASH (OtaStoreFlashNrf52): a delta can be 100 KB+, too big to hold in RAM, and the // COMPLETE container must persist so the bootloader can apply it after reboot. A flash page-erase halts // the CPU (~85 ms) and starves the LoRa RX, so the store COALESCES writes to the 4 KB page (the erase -// unit) and commits each page once, off the per-packet path (see OtaManager.h) — RAM stays O(one page). +// unit) and commits each page once, off the per-packet path (see OtaManager.h) - RAM stays O(one page). // (v1 has no mid-transfer resume; an interrupted fetch simply restarts.) ESP32/native use the RAM store. namespace mesh { @@ -67,7 +67,7 @@ struct OtaContext { bool serving = false; // manager.serve() succeeded // flash-backed self-serve: cached merkle leaves (heap, freed on re-serve) + assembled manifest of our // own running firmware. The payload is read from flash per block; only the metadata is held in RAM. - // serve_self_proof is the proof-gen working buffer (>= block_count*4) — sized to OUR image's block + // serve_self_proof is the proof-gen working buffer (>= block_count*4) - sized to OUR image's block // count (the manager's fixed 4 KB scratch only covers <=1024 blocks; a >1 MB image needs more). uint8_t* serve_self_leaves = nullptr; uint8_t* serve_self_proof = nullptr; @@ -82,7 +82,7 @@ struct OtaContext { char hw_id[33] = {0}; // this device's hardware tag (from board.getOtaHwId(), set in begin) // ---- Pull destinations (`ota pull <#> `): where a fetched .mota is staged. `flash` (fetch_store, - // always present) or `folder` — an external host folder over the seeder link, registered by the app + // always present) or `folder` - an external host folder over the seeder link, registered by the app // while a motatool `serve` connection is attached (else no `folder` dest is offered). Captures the // container to the host as .mota so an exact firmware copy can be pulled for delta-building. ---- FolderMotaStore* folder_dest = nullptr; // non-null == a folder destination is currently connected @@ -107,7 +107,7 @@ struct OtaContext { // so the deferred-reboot path (mesh loop) takes over. Caller ensures the fetch is COMPLETE. Shared by // manual `ota applydelta` and the auto-install path. bool apply_fetched(char* msg) { - // hardware-compatibility gate (brick-safety) — refuse a .mota whose hw_id is for different hardware, + // hardware-compatibility gate (brick-safety) - refuse a .mota whose hw_id is for different hardware, // independent of signature; covers a manual cross-target `ota dev want` onto an incompatible board. { uint8_t hdr[8], mb[256]; @@ -170,7 +170,7 @@ struct OtaContext { manager.clear_sources(); // idempotent re-attach if (!manager.add_source(&src)) { strncpy(msg, "ERR no free source slot", cap); return false; } folder_active = true; - snprintf(msg, cap, "OK folder attached (serial) — serving %u mOTA total (own fw + folder)", + snprintf(msg, cap, "OK folder attached (serial) - serving %u mOTA total (own fw + folder)", (unsigned)manager.servedCount()); return true; } @@ -185,8 +185,8 @@ struct OtaContext { } void begin(uint32_t target_id, OtaSend send, void* ctx, const char* hw = nullptr) { - // Prefer the firmware's SELF-DESCRIBING EndF identity (docs §2) over the build-flag values the caller - // passed — it's correct on any build (build.sh injection, bare IDE build, ...), so `ota ls`/`status` + // Prefer the firmware's SELF-DESCRIBING EndF identity (docs Section 2) over the build-flag values the caller + // passed - it's correct on any build (build.sh injection, bare IDE build, ...), so `ota ls`/`status` // and fetch-routing show the right hardware/role instead of 0 / "". SelfFwInfo _fi; if (ota_self_firmware(_fi) && _fi.valid) { diff --git a/src/helpers/ota/OtaFlashLayout_nrf52.h b/src/helpers/ota/OtaFlashLayout_nrf52.h index 6c4c068c..a73b1a15 100644 --- a/src/helpers/ota/OtaFlashLayout_nrf52.h +++ b/src/helpers/ota/OtaFlashLayout_nrf52.h @@ -68,10 +68,10 @@ inline uint32_t mota_nrf52_stage_capacity(uint32_t app_base) { // Plan where to stage a received `.mota` of `total_size` bytes. It is placed bottom-aligned within the // highest flash page below FS_START (the bootloader scans downward from there), and it must sit // ENTIRELY above both the running image and the in-place decoder workspace, and below the filesystem -// region (ExtraFS/InternalFS — where user prefs live, assumed immutable). Reserving the full workspace +// region (ExtraFS/InternalFS - where user prefs live, assumed immutable). Reserving the full workspace // here prevents accepting a large container that the bootloader would later reject as overlapping it. // Returns false (and leaves out_start untouched) if it does not fit. This is the SINGLE place the FS -// ceiling + app-collision bounds are enforced; begin()/reopen() both go through it. Pure — no flash I/O — +// ceiling + app-collision bounds are enforced; begin()/reopen() both go through it. Pure - no flash I/O - // so it is unit-tested natively in test/test_ota/test_ota_flashplan.cpp. inline bool mota_nrf52_stage_plan(uint32_t total_size, uint32_t app_base, uint32_t app_end, uint32_t& out_start) { diff --git a/src/helpers/ota/OtaFormat.h b/src/helpers/ota/OtaFormat.h index 554899f0..a660455f 100644 --- a/src/helpers/ota/OtaFormat.h +++ b/src/helpers/ota/OtaFormat.h @@ -15,7 +15,7 @@ namespace ota { static const uint8_t MOTA_MAGIC[4] = { 'm', 'O', 'T', 'A' }; // 6D 4F 54 41 static const uint8_t MOTA_TRAILER[5] = { 'v', 'k', '4', '9', '6' }; // 76 6B 34 39 36 static const uint8_t ENDF_MAGIC[4] = { 'E', 'n', 'd', 'F' }; // 45 6E 64 46 -// Fixed 56-byte trailer (docs/ota_protocol.md §2): marker(4) body_len(4) body_hash8(8) + a self-describing +// Fixed 56-byte trailer (docs/ota_protocol.md Section 2): marker(4) body_len(4) body_hash8(8) + a self-describing // identity block fw_version(4) target_id(4) hw_id(32). No optional/variable parts. static const uint32_t ENDF_LEN = 56; @@ -23,12 +23,12 @@ static const uint32_t ENDF_LEN = 56; static const uint8_t MOTA_FORMAT_VER = 2; // fixed-layout manifest (see offsets below) static const uint8_t HASH_ALGO_SHA256 = 0x12; // multihash code -// Fixed manifest layout (manifest-minus-leaves) — every field is present at a constant offset, so the -// parser is plain offset reads (docs/ota_protocol.md §4). base_hash/signer_pubkey/signature are always +// Fixed manifest layout (manifest-minus-leaves) - every field is present at a constant offset, so the +// parser is plain offset reads (docs/ota_protocol.md Section 4). base_hash/signer_pubkey/signature are always // present (zero-filled when not applicable); only leaves[] (after `approval`) is variable. static const uint32_t MOTA_OFF_BASE_HASH = 89; // 8 (zero for a full image) static const uint32_t MOTA_OFF_SIGNER = 97; // 32 (zero when unsigned) -static const uint32_t MOTA_OFF_SIGNATURE = 129; // 64 (zero when unsigned) — covers manifest[0,129) +static const uint32_t MOTA_OFF_SIGNATURE = 129; // 64 (zero when unsigned) - covers manifest[0,129) static const uint32_t MOTA_OFF_APPROVAL = 193; // 4 static const uint32_t MOTA_MFL = 197; // manifest-minus-leaves length (constant) static const uint32_t MOTA_SIGNED_LEN = 129; // bytes the signature covers (manifest[0, signer_end)) diff --git a/src/helpers/ota/OtaManager.cpp b/src/helpers/ota/OtaManager.cpp index b2208bda..a1843a18 100644 --- a/src/helpers/ota/OtaManager.cpp +++ b/src/helpers/ota/OtaManager.cpp @@ -20,7 +20,7 @@ void OtaManager::begin(uint32_t my_target_id, OtaSend send, void* ctx) { // // A node offers a SET of mOTAs: its own firmware (view0) plus any external "folder" sources (OtaSource). // Every fetch message carries the manifest_id, so a request dispatches to the matching ServeView via -// resolve() — view0 is resident; an external mota is (re)loaded on demand into _srcv. The catalog (what +// resolve() - view0 is resident; an external mota is (re)loaded on demand into _srcv. The catalog (what // we advertise / answer OTA_QUERY with) is the lightweight _serve[] registry. bool OtaManager::serve(const uint8_t* mota, uint32_t len) { @@ -112,7 +112,7 @@ OtaManager::ServeView* OtaManager::resolve(const uint8_t* mid) { // Load an external mota into the on-demand _srcv: read its manifest-minus-leaves + leaves[] from the // source into RAM, parse, and wire a payload reader that streams blocks from the source on REQ. (The -// payload itself is NOT held in RAM — only the small head + the leaves, <=4 KB for <=1024 blocks.) +// payload itself is NOT held in RAM - only the small head + the leaves, <=4 KB for <=1024 blocks.) bool OtaManager::loadSource(const ServeEntry& e) { const MotaDesc& d = e.desc; if (d.leaves_off < 8) return false; @@ -145,7 +145,7 @@ bool OtaManager::srcReadTramp(void* c, uint32_t off, uint8_t* buf, uint32_t len) return x->src->read(x->idx, x->payload_off + off, buf, len); } -// sha2-256:4 over the SORTED set of mids we serve — peers use it to tell if our offering changed. Sorting +// sha2-256:4 over the SORTED set of mids we serve - peers use it to tell if our offering changed. Sorting // makes it canonical across nodes regardless of insert order; for a single mota it is mh4(mid) (unchanged). void OtaManager::setDigest(uint8_t out[4]) const { if (_n_serve == 0) { memset(out, 0, 4); return; } @@ -170,7 +170,7 @@ void OtaManager::announce() { // tiny per-node beacon (constant size, inde emit(b, encode_adv(b, sizeof(b), a), true); } -// OTA_QUERY: two roles. (1) OVERHEAR-SUPPRESSION — any node that has a pending query for the same +// OTA_QUERY: two roles. (1) OVERHEAR-SUPPRESSION - any node that has a pending query for the same // {source,digest} cancels it (someone else already asked; the broadcast HAVE is coming). (2) If the query // is addressed to US, reply with our catalog (broadcast, tagged with our digest so every overhearer caches // it). All served mOTAs matching filter_target are returned, fragmented if they exceed one packet. @@ -232,7 +232,7 @@ void OtaManager::handleGetManifest(const uint8_t* m, uint16_t n) { } // Serve the target's merkle leaves[] in fragments (for a motatool folder-capture warm-start). Only the -// fragments set in want_mask are emitted, so a want_mask retry re-sends just the holes — never a full burst +// fragments set in want_mask are emitted, so a want_mask retry re-sends just the holes - never a full burst // (same anti-deadlock rationale as OTA_MANIFEST). This is the only leaf-diff piece that runs on every node. void OtaManager::handleGetLeaves(const uint8_t* m, uint16_t n) { GetLeavesMsg gl; @@ -255,7 +255,7 @@ void OtaManager::handleGetLeaves(const uint8_t* m, uint16_t n) { } // Smallest mask covering `nf` fragments: bit k set for k in [0, nf). Caps at 16 (matches _reasm_mask and -// the manifest reassembly), which bounds a block at 16 fragments — our 1 KB blocks are 7. +// the manifest reassembly), which bounds a block at 16 fragments - our 1 KB blocks are 7. static inline uint16_t frag_full_mask(uint32_t nf) { return (nf >= 16) ? 0xFFFFu : (uint16_t)((1u << nf) - 1); } @@ -340,7 +340,7 @@ void OtaManager::handleAdv(const uint8_t* m, uint16_t n) { if (interested && !s.have_catalog) scheduleQuery(a.seeder_id, a.set_digest); // jittered + suppressible } -// Schedule a catalog query after a random jitter (id ⊕ digest, so neighbours pick different delays). The +// Schedule a catalog query after a random jitter (id +/ digest, so neighbours pick different delays). The // node with the shortest jitter sends; the rest overhear that QUERY (or the broadcast HAVE) and suppress. void OtaManager::scheduleQuery(const uint8_t* seeder, const uint8_t* digest) { if (_pq_active && memcmp(_pq_seeder, seeder, 4) == 0 && memcmp(_pq_digest, digest, 4) == 0) return; // already pending @@ -356,7 +356,7 @@ void OtaManager::sendQuery(const uint8_t* seeder, const uint8_t* digest, uint32_ emit(b, encode_query(b, sizeof(b), q), true); // FLOODED so neighbours overhear it and suppress } -// User-initiated browse (`ota neighbors`): ask every known source now (no jitter — infrequent + explicit). +// User-initiated browse (`ota neighbors`): ask every known source now (no jitter - infrequent + explicit). void OtaManager::queryAll() { for (uint8_t i = 0; i < _n_src; i++) sendQuery(_sources[i].seeder, _sources[i].digest, 0); } // A catalog reply: record each mOTA (deduped by mid; distinct-source count for the UI), and if a row @@ -367,7 +367,7 @@ void OtaManager::handleHave(const uint8_t* m, uint16_t n) { bool have_sid = (_seeder_id[0] | _seeder_id[1] | _seeder_id[2] | _seeder_id[3]) != 0; if (have_sid && memcmp(hv.seeder_id, _seeder_id, 4) == 0) return; // our own catalog // PASSIVE: any overheard HAVE marks its source catalogued + cancels a pending query for it (storm - // suppression) — every node caches the rows below, even one that never queried. + // suppression) - every node caches the rows below, even one that never queried. for (uint8_t i = 0; i < _n_src; i++) if (memcmp(_sources[i].seeder, hv.seeder_id, 4) == 0 && memcmp(_sources[i].digest, hv.set_digest, 4) == 0) _sources[i].have_catalog = true; @@ -429,7 +429,7 @@ void OtaManager::startFetch(const uint8_t* mid, uint32_t target, bool validate) if (!_fetch || _fstate == FETCHING || _fstate == WANT_MANIFEST || _fstate == WANT_LEAVES || _fstate == PAUSED) return; _validate = validate; // motatool folder-capture warm-start (seed leaf-diff) // A validate pull is a FRESH seed capture, not a resume: the store already holds the seed's payload (not a - // real partial), so never adopt it via resumeStaged — always re-begin and run the manifest→leaves→diff. + // real partial), so never adopt it via resumeStaged - always re-begin and run the manifest->leaves->diff. if (!validate && resumeStaged(mid)) return; // (non-validate) resume a partial container left in flash memcpy(_fid, mid, 4); _fstate = WANT_MANIFEST; @@ -470,15 +470,15 @@ void OtaManager::handleManifest(const uint8_t* m, uint16_t n) { const uint8_t* mf = _mf_buf; // fully reassembled manifest-minus-leaves uint32_t mfl = _mf_len; if (mfl != MOTA_MFL) { _fstate = FAILED; return; } // manifest-minus-leaves is a fixed 197 bytes - if (!codecOk(mf[56])) { _fstate = IDLE; return; } // codec we can't apply (lying/stale ADV) — abort + if (!codecOk(mf[56])) { _fstate = IDLE; return; } // codec we can't apply (lying/stale ADV) - abort uint32_t payload_size = rd_u32le(mf + 15); uint8_t bsl = mf[19]; if (bsl >= 32) { _fstate = FAILED; return; } uint32_t bs = 1u << bsl; - // a block must fit our reassembly buffer (and be non-empty) — reject an oversized block_size up front + // a block must fit our reassembly buffer (and be non-empty) - reject an oversized block_size up front if (bs == 0 || bs > OTA_MAX_BLOCK || payload_size == 0) { _fstate = FAILED; return; } uint32_t bc = (payload_size + bs - 1) / bs; - if (bc > 0xFFFFu) { _fstate = FAILED; return; } // block_idx is uint16 on the wire — can't address more + if (bc > 0xFFFFu) { _fstate = FAILED; return; } // block_idx is uint16 on the wire - can't address more memcpy(_froot, mf + 20, 4); uint32_t leaves_off = 8 + mfl; @@ -523,7 +523,7 @@ void OtaManager::freeLeaves() { } // Enter WANT_LEAVES: allocate the target-leaves buffer and request them (bitmap-fragmented). Returns false -// — caller falls back to a normal full fetch — if the image is too big for the fixed uint16 leaves bitmap or +// - caller falls back to a normal full fetch - if the image is too big for the fixed uint16 leaves bitmap or // the heap allocation fails. Called from handleManifest once geometry is known. bool OtaManager::beginLeafDiff() { freeLeaves(); @@ -714,7 +714,7 @@ void OtaManager::handleProof(const uint8_t* m, uint16_t n) { void OtaManager::requestMissing() { if (_fstate != FETCHING) return; // Per-block serial flow (split data/proof). If the current block's data is fully reassembled and we - // are waiting on its proof, (re)send the proof request rather than re-fetching the data — this also + // are waiting on its proof, (re)send the proof request rather than re-fetching the data - this also // recovers from a lost PROOF reply. if (_awaiting_proof && _reasm_block != NO_BLOCK) { ReqProofMsg rp; memcpy(rp.manifest_id, _fid, 4); rp.block_idx = (uint16_t)_reasm_block; @@ -724,14 +724,14 @@ void OtaManager::requestMissing() { return; } // Otherwise request the DATA fragments of the next missing block. One block at a time keeps the - // server's TX queue tiny so OTA never floods the mesh (docs/ota_protocol.md §8); a block's fragments + // server's TX queue tiny so OTA never floods the mesh (docs/ota_protocol.md Section 8); a block's fragments // are self-describing (frag_off) so missing slices can be retried without re-sending a full block. uint32_t start = pickMissingBlock(); if (start >= _fbc) return; _req_start = start; _req_count = 1; // Ask only for fragments we still lack: the holes of the in-flight partial block (_reasm_need & ~mask), // or all fragments of a fresh block. A lost fragment then costs one fragment to re-fetch, not the whole - // block — and the re-REQ can't collide with a multi-fragment burst (half-duplex) as before. + // block - and the re-REQ can't collide with a multi-fragment burst (half-duplex) as before. uint32_t nf = (blockLen(start) + OTA_FRAG_DATA - 1) / OTA_FRAG_DATA; uint16_t need = frag_full_mask(nf); uint16_t have = (start == _reasm_block) ? _reasm_mask : 0; @@ -760,7 +760,7 @@ void OtaManager::loop() { sendQuery(_pq_seeder, _pq_digest, 0); // unfiltered: one broadcast HAVE serves everyone } if (_fstate == WANT_MANIFEST) { - // Retry GET_MANIFEST ONLY when a tick passed with no new fragment — re-bursting every tick would congest + // Retry GET_MANIFEST ONLY when a tick passed with no new fragment - re-bursting every tick would congest // the link and burn the retry cap while fragments are still arriving (mirrors FETCHING + WANT_LEAVES). // Give up after a cap of stalled retries so an unreachable mid doesn't pin the single fetch slot forever. if (_mf_mask == _loop_last_mfmask) { @@ -776,8 +776,8 @@ void OtaManager::loop() { return; } if (_fstate == WANT_LEAVES) { - if (_diffing) { diffStep(); return; } // leaves in — diff the seed a batch at a time (non-blocking) - // Re-request ONLY when a whole tick passed with no new fragment — otherwise re-bursting the holes every + if (_diffing) { diffStep(); return; } // leaves in - diff the seed a batch at a time (non-blocking) + // Re-request ONLY when a whole tick passed with no new fragment - otherwise re-bursting the holes every // tick congests the link (and burns the retry cap) while fragments are still streaming in. On a stall, // ask for just the missing bitmap (anti-burst); give up (FAILED) after a cap of stalled retries. if (_lv_mask == _loop_last_lvmask) { @@ -794,7 +794,7 @@ void OtaManager::loop() { return; } if (_fstate != FETCHING) return; - // retry only when a whole tick passed with NO progress — neither a committed block nor a new fragment + // retry only when a whole tick passed with NO progress - neither a committed block nor a new fragment // of the in-flight block. This avoids re-request spam while a block's fragments are still streaming in. if (_have == _loop_last_have && _reasm_mask == _loop_last_mask) requestMissing(); _loop_last_have = _have; diff --git a/src/helpers/ota/OtaManager.h b/src/helpers/ota/OtaManager.h index 0a04ee4a..7d4a966f 100644 --- a/src/helpers/ota/OtaManager.h +++ b/src/helpers/ota/OtaManager.h @@ -8,7 +8,7 @@ #include "MotaContainer.h" #include "OtaSource.h" -// Transport-agnostic OTA session engine (docs/ota_protocol.md §5/§8). It SERVES a complete `.mota` +// Transport-agnostic OTA session engine (docs/ota_protocol.md Section 5/Section 8). It SERVES a complete `.mota` // (answering GET_MANIFEST / REQ) and/or FETCHES one into an OtaStore (verifying every block against // the signed merkle root via proofs). It is portable (no Arduino / radio / Ed25519) so it can be // driven by a host simulation; a thin Mesh adapter wires it to PAYLOAD_TYPE_OTA on device. @@ -50,7 +50,7 @@ typedef bool (*ServeReadFn)(void* ctx, uint32_t off, uint8_t* buf, uint32_t len) #define OTA_MF_MAXFRAG 4 // max manifest fragments (the fixed 197 B manifest is always 2) #endif #ifndef OTA_MANIFEST_MAX_RETRY -#define OTA_MANIFEST_MAX_RETRY 20 // give up (FAILED) after this many GET_MANIFEST retries — frees the slot +#define OTA_MANIFEST_MAX_RETRY 20 // give up (FAILED) after this many GET_MANIFEST retries - frees the slot #endif // Leaf-diff warm-start (motatool folder-capture only): bulk-fetch the target's leaves[], diff a seed build // locally, pull DATA only for mismatches. OTA_LEAVES is bitmap-fragmented like OTA_MANIFEST so a want_mask @@ -95,7 +95,7 @@ typedef bool (*ServeReadFn)(void* ctx, uint32_t off, uint8_t* buf, uint32_t len) #define OTA_REQ_SPREAD_MS 3000 // initial random hold before a fetch's first REQ (de-sync N fetchers) #endif #ifndef OTA_REQ_SUPPRESS_MS -#define OTA_REQ_SUPPRESS_MS 2500 // after overhearing a peer's REQ for a block, don't also request it — +#define OTA_REQ_SUPPRESS_MS 2500 // after overhearing a peer's REQ for a block, don't also request it - #endif // its DATA is broadcast and will fill our hole too (swarm de-dup) #ifndef OTA_SERVE_SUPPRESS_MS #define OTA_SERVE_SUPPRESS_MS 1500 // don't re-serve a block whose DATA we just overheard another holder send @@ -106,10 +106,10 @@ typedef bool (*ServeReadFn)(void* ctx, uint32_t off, uint8_t* buf, uint32_t len) // nRF52 note: a flash page-erase halts the CPU (~85 ms, code runs from flash) and starves the LoRa RX, // so writing to flash on every received packet drops in-flight DATA and the transfer stalls. The SD-safe // driver (Adafruit flash_nrf5x) always erases on flush, so there is no erase-free write; instead -// OtaStoreFlashNrf52 COALESCES to the 4 KB page (the erase unit) and writes each page once — RAM stays +// OtaStoreFlashNrf52 COALESCES to the 4 KB page (the erase unit) and writes each page once - RAM stays // O(one page), never O(mota). It pins flash page 0 (header+manifest+merkle leaves, which update all // transfer long) in RAM and streams the payload through one sliding page buffer, flushing page 0 and the -// last page at finalize() (radio idle). Flash is then touched ~once per 4 KB (≈1 per 4 blocks), not per +// last page at finalize() (radio idle). Flash is then touched ~once per 4 KB (~1 per 4 blocks), not per // packet; a small delta that fits page 0 does ZERO flash I/O until COMPLETE. (Pacing alone is not enough.) class OtaManager { @@ -160,9 +160,9 @@ public: bool serve_self(const uint8_t* manifest, uint16_t mfl, const uint8_t* leaves, uint32_t block_count, uint8_t* proof_scratch, uint32_t proof_scratch_sz, ServeReadFn read, void* ctx); // Attach an external "folder" of `.mota` images (USB-serial daemon, BLE, WiFi URLs, NFS/samba, ...). - // The node then advertises + RELAYS them transparently alongside its own fw — peers just see more mOTAs. + // The node then advertises + RELAYS them transparently alongside its own fw - peers just see more mOTAs. // Re-enumerates the source into the serve registry. Returns false if no slots remain. (Trustless: the - // fetcher verifies merkle+signature, so the source is never trusted — see OtaSource.h.) + // fetcher verifies merkle+signature, so the source is never trusted - see OtaSource.h.) bool add_source(MotaSource* src); // Re-read every attached source's catalog (call when the folder's contents change). Rebuilds entries // [1..] from the sources; entry 0 (our own fw) is preserved. @@ -176,7 +176,7 @@ public: void servedDigest(uint8_t out[4]) const { setDigest(out); } // Broadcast the tiny per-node BEACON (OTA_ADV): seeder_id + count + set-digest of everything we serve. - // Constant size regardless of how many mOTAs — peers ask for the catalog via OTA_QUERY only on interest. + // Constant size regardless of how many mOTAs - peers ask for the catalog via OTA_QUERY only on interest. void announce(); // --- fetch --- Provide the staging store; fetching starts on a matching OTA_ADV. @@ -212,7 +212,7 @@ public: void pull(const uint8_t* mid, uint32_t target, bool validate = false) { want(target); want_mid(mid); startFetch(mid, target, validate); } - // Ask every known source for its catalog (populates `ota neighbors`). Async — rows arrive via OTA_HAVE. + // Ask every known source for its catalog (populates `ota neighbors`). Async - rows arrive via OTA_HAVE. void queryAll(); // Coarse clock for source/catalog ages + LRU (the Mesh adapter feeds millis; 0 in host tests is fine). void set_clock(uint32_t ms) { _now_ms = ms; } @@ -280,7 +280,7 @@ public: uint32_t target_id, fw_version; uint8_t codec, flags; uint8_t seeders[OTA_CAT_SEEDERS][4]; // distinct sources advertising this mid (deduped; capped) - uint8_t n_seeders; // count of the above (capped at OTA_CAT_SEEDERS) — "N+ nodes have it" + uint8_t n_seeders; // count of the above (capped at OTA_CAT_SEEDERS) - "N+ nodes have it" uint32_t have_max; // best block-count any source reported (== total when a full copy exists) uint32_t last_ms; }; @@ -332,7 +332,7 @@ private: ServeView _srcv; uint8_t _srcv_mid[4] = {0}; SrcReadCtx _srcv_rdctx = {nullptr, 0, 0}; - ServeEntry _serve[OTA_MAX_SERVE]; // catalog (what we advertise) — entry 0 is view0 + ServeEntry _serve[OTA_MAX_SERVE]; // catalog (what we advertise) - entry 0 is view0 uint8_t _n_serve = 0; MotaSource* _src_list[OTA_MAX_SOURCE_OBJ] = {nullptr}; uint8_t _n_src_obj = 0; @@ -393,7 +393,7 @@ private: CatRow _catalog[OTA_MAX_CATALOG]; uint8_t _n_cat = 0; uint32_t _now_ms = 0; // coarse clock (fed by set_clock; for ages/LRU/jitter) - // pending catalog query (jittered + suppressed on overhearing a matching QUERY/HAVE — anti-storm) + // pending catalog query (jittered + suppressed on overhearing a matching QUERY/HAVE - anti-storm) bool _pq_active = false; uint8_t _pq_seeder[4] = {0}; uint8_t _pq_digest[4] = {0}; diff --git a/src/helpers/ota/OtaProtocol.h b/src/helpers/ota/OtaProtocol.h index fddb81c2..623ca222 100644 --- a/src/helpers/ota/OtaProtocol.h +++ b/src/helpers/ota/OtaProtocol.h @@ -4,7 +4,7 @@ #include #include "OtaFormat.h" -// Encode/decode for the OTA LoRa messages (docs/ota_protocol.md §8). Each message is a packet payload: +// Encode/decode for the OTA LoRa messages (docs/ota_protocol.md Section 8). Each message is a packet payload: // [0]=ota_msg_type, then a fixed body. Portable + allocation-free; unit-tested on the host. // // manifest_id == the manifest's merkle_root (4 bytes), a compact content id. @@ -13,7 +13,7 @@ namespace mesh { namespace ota { // ---- OTA_ADV: tiny per-NODE beacon (flood, periodic). CONSTANT size regardless of how many mOTAs a -// node serves — it just says "I'm a source, here's how many + a digest of my set". A peer that's +// node serves - it just says "I'm a source, here's how many + a digest of my set". A peer that's // interested asks for the catalog via OTA_QUERY. (Replaces the old per-mOTA advert so a folder node with // N images costs one 10-byte beacon, not N adverts.) ---- struct AdvMsg { @@ -22,7 +22,7 @@ struct AdvMsg { uint8_t set_digest[4]; // sha2-256:4 over the sorted set of served mids; "did my offering change?" }; -// ---- OTA_QUERY: "list what you serve" — addressed to a source by seeder_id, FLOODED so neighbours +// ---- OTA_QUERY: "list what you serve" - addressed to a source by seeder_id, FLOODED so neighbours // overhear it (storm suppression). set_digest identifies the offering being asked about (so an overhearer // can suppress its own pending query for the same {source,digest}). filter_target=0 = everything. ---- struct QueryMsg { @@ -62,7 +62,7 @@ struct ManifestMsg { // ---- OTA_REQ: request specific fragments of ONE block (direct) ---- // body: manifest_id(4) block_idx(2) want_mask(2). want_mask bit k = "send fragment k" (the slice at byte // offset k*OTA_FRAG_DATA). A fetcher requests the full mask ((1<size && esp_partition_read(p, body_len, tr, ENDF_LEN) == ESP_OK) { @@ -118,7 +118,7 @@ static uint32_t parse_fw_version(const char* s) { else break; } if (dots >= 1) return FwVersion{ (uint8_t)a, (uint8_t)b, (uint8_t)d, 0 }.pack(); - s = p - 1; // a bare number, no dots — keep scanning + s = p - 1; // a bare number, no dots - keep scanning } return 0; } @@ -126,7 +126,7 @@ static uint32_t parse_fw_version(const char* s) { bool ota_serve_self(OtaContext& c, uint32_t fw_version) { // Derive our version from the build string when the caller didn't supply one, so the mOTA we advertise // carries a real version (was hard-coded 0 -> peers saw "v0.0.0"). A dev build with no dotted number - // still reads 0 — the self-describing EndF identity (docs) is the durable fix for that. + // still reads 0 - the self-describing EndF identity (docs) is the durable fix for that. #ifdef FIRMWARE_VERSION if (fw_version == 0) fw_version = parse_fw_version(FIRMWARE_VERSION); #endif @@ -161,7 +161,7 @@ bool ota_serve_self(OtaContext& c, uint32_t fw_version) { uint8_t image_hash[32]; sha.finalize(image_hash, 32); uint8_t root[4]; merkle_root(root, c.serve_self_leaves, bc); - // Prefer the SELF-DESCRIBING identity embedded in our own EndF (docs §2) over build flags / the param — + // Prefer the SELF-DESCRIBING identity embedded in our own EndF (docs Section 2) over build flags / the param - // it's correct regardless of how the firmware was built (build.sh injection, IDE, etc.). uint32_t out_target = fi.target_id ? fi.target_id : c.manager.target(); uint32_t out_ver = fi.fw_version ? fi.fw_version : fw_version; diff --git a/src/helpers/ota/OtaSelf.h b/src/helpers/ota/OtaSelf.h index df855c6f..24575e2e 100644 --- a/src/helpers/ota/OtaSelf.h +++ b/src/helpers/ota/OtaSelf.h @@ -5,7 +5,7 @@ // Device-side accessor for the running firmware's own image (to read its EndF trailer). // Per-platform: ESP32 memory-maps the running app partition; other platforms TBD (nRF52 uses the // bootloader-apply path, so its app-region wiring lands with that work). Not compiled on the native -// host — the portable scan logic in FirmwareInfo.{h,cpp} is what gets unit-tested there. +// host - the portable scan logic in FirmwareInfo.{h,cpp} is what gets unit-tested there. namespace mesh { namespace ota { diff --git a/src/helpers/ota/OtaSource.h b/src/helpers/ota/OtaSource.h index 64c8fce6..88731a69 100644 --- a/src/helpers/ota/OtaSource.h +++ b/src/helpers/ota/OtaSource.h @@ -4,16 +4,16 @@ #include #include "OtaFormat.h" -// Transport-agnostic "folder of firmware" abstraction (docs/ota_protocol.md §9). A node can RELAY -// `.mota` images it does not hold in flash — a user drops several `.mota` (different architectures) into +// Transport-agnostic "folder of firmware" abstraction (docs/ota_protocol.md Section 9). A node can RELAY +// `.mota` images it does not hold in flash - a user drops several `.mota` (different architectures) into // some external store and the node advertises + serves them as if it held them. Peers just see "this node // has N mOTAs"; the node knows they are external. The store is reached through a MotaSource, so the SAME -// serve code drives a USB-serial host daemon, BLE, a WiFi URL list, an NFS/samba mount, ... — only the +// serve code drives a USB-serial host daemon, BLE, a WiFi URL list, an NFS/samba mount, ... - only the // `read()` plumbing differs per transport. // // The relay is TRUSTLESS: the fetcher verifies every block against the signed merkle root, so a source is // never trusted. A wrong descriptor or wrong bytes simply makes the fetch fail its merkle/signature check -// — a malicious or buggy source cannot forge firmware, only deny it. +// - a malicious or buggy source cannot forge firmware, only deny it. namespace mesh { namespace ota { diff --git a/src/helpers/ota/OtaStore.h b/src/helpers/ota/OtaStore.h index 8fb0563b..17d1bd94 100644 --- a/src/helpers/ota/OtaStore.h +++ b/src/helpers/ota/OtaStore.h @@ -5,10 +5,10 @@ #include #include "OtaFormat.h" // MOTA_MAGIC (resume: detect a persisted partial container) -// Staging backend for an in-transit `.mota` (docs/ota_protocol.md §7). Blocks may arrive out of order +// Staging backend for an in-transit `.mota` (docs/ota_protocol.md Section 7). Blocks may arrive out of order // and progress must survive reboots, so the store is random-access. The transfer/verify logic is // written against this interface; concrete impls are per-platform (RAM for tests/bring-up; persistent -// flash — ESP32 OTA slot / nRF52 raw region — for production, dropped in behind the same interface). +// flash - ESP32 OTA slot / nRF52 raw region - for production, dropped in behind the same interface). namespace mesh { namespace ota { @@ -25,8 +25,8 @@ public: virtual void clear() = 0; // Optional: declare the size of the leading metadata (header + manifest + merkle leaves, i.e. - // everything before the payload). A flash-backed store keeps that region — which is updated - // throughout the transfer (a leaf is committed per block) — pinned in one RAM page, so it can + // everything before the payload). A flash-backed store keeps that region - which is updated + // throughout the transfer (a leaf is committed per block) - pinned in one RAM page, so it can // flush the bulk payload page-by-page without re-erasing the leaves' page on every block. // Returns false if the metadata won't fit the store's pinned region (transfer is then refused). virtual bool set_meta_size(uint32_t meta_bytes) { (void)meta_bytes; return true; } @@ -62,8 +62,8 @@ public: } }; -// Fixed-capacity RAM store — for native tests and device bring-up of the transfer/verify path. -// (Does NOT survive reboot; a persistent flash store replaces it for production — see D1.) +// Fixed-capacity RAM store - for native tests and device bring-up of the transfer/verify path. +// (Does NOT survive reboot; a persistent flash store replaces it for production - see D1.) template class OtaStoreRam : public OtaStore { uint8_t _buf[CAP] = {}; // zero-init so a never-written store's reopen() finds no MOTA_MAGIC @@ -88,7 +88,7 @@ public: uint32_t capacity() const override { return CAP; } uint32_t staged_size() const override { return _total; } void clear() override { _total = 0; } - // RAM doesn't survive a real reboot, but the buffer persists within a process — enough to exercise the + // RAM doesn't survive a real reboot, but the buffer persists within a process - enough to exercise the // manager's resume path in native tests. Recover `total` from the stored header so read() bounds work. bool reopen() override { if (memcmp(_buf, MOTA_MAGIC, 4) != 0) return false; diff --git a/src/helpers/ota/OtaStoreFlashEsp32.cpp b/src/helpers/ota/OtaStoreFlashEsp32.cpp index 0fae329c..1be20563 100644 --- a/src/helpers/ota/OtaStoreFlashEsp32.cpp +++ b/src/helpers/ota/OtaStoreFlashEsp32.cpp @@ -197,7 +197,7 @@ void OtaStoreFlashEsp32::checkpoint() { } // Re-attach to a container already staged in the slot (after a reboot), WITHOUT erasing. The header -// (MOTA_MAGIC + total) sits at a sector boundary (meta_part for full, write_start for delta — both +// (MOTA_MAGIC + total) sits at a sector boundary (meta_part for full, write_start for delta - both // sector-aligned), so scan sector starts from the bottom up. A candidate is accepted only if: magic + // plausible total, the manifest parses, the container geometry is self-consistent with the total, AND the // recomputed placement lands the meta exactly where we found the magic. Any miss -> false (fetch fresh), diff --git a/src/helpers/ota/OtaStoreFlashNrf52.h b/src/helpers/ota/OtaStoreFlashNrf52.h index 9e5f7f46..fd235379 100644 --- a/src/helpers/ota/OtaStoreFlashNrf52.h +++ b/src/helpers/ota/OtaStoreFlashNrf52.h @@ -7,12 +7,12 @@ // Persistent flash-backed OtaStore for nRF52840. Stages the received `.mota` in the free flash // below the primary LittleFS (FS_START), bottom-aligned so its trailer ends at FS_START and the -// bootloader can scan for it. Survives reboot — the whole point — so the bootloader can apply the +// bootloader can scan for it. Survives reboot - the whole point - so the bootloader can apply the // staged delta on the next boot. // // RAM is bounded to O(one flash page), NEVER O(mota): a 100 KB+ delta must not live in RAM. // - On nRF52 the flash *erase* unit is one 4 KB page and the only SoftDevice-safe writer -// (Adafruit `flash_nrf5x`) erases the whole page on every flush (~85 ms, CPU stalled → LoRa RX +// (Adafruit `flash_nrf5x`) erases the whole page on every flush (~85 ms, CPU stalled -> LoRa RX // starved). Writing to flash per received packet therefore drops in-flight DATA and the transfer // stalls. The fix: coalesce to the *page*, the hardware-natural unit, and write each page once. // - `_meta_page` pins flash page 0 (header + manifest + the merkle-leaf progress markers, which are @@ -23,9 +23,9 @@ // monotonically with the (mostly in-order) block stream and flushes the page it leaves behind. // Rare out-of-order writes to an already-flushed page go straight to flash as a safe read-modify- // write (flash_nrf5x erases before programming, so re-touching a page never violates the -// writes-per-word limit — it just costs one extra erase). +// writes-per-word limit - it just costs one extra erase). // - The 5-byte trailer is buffered and written at finalize(). -// Net: flash is touched ~once per 4 KB page (≈ 1 per 4 blocks at 1 KB), off the per-packet path; page +// Net: flash is touched ~once per 4 KB page (~ 1 per 4 blocks at 1 KB), off the per-packet path; page // 0 and the last page are written at finalize() with the radio idle. For a small delta (whole .mota // in page 0) there is ZERO flash I/O during the transfer. @@ -47,7 +47,7 @@ class OtaStoreFlashNrf52 : public OtaStore { // Bytes from `pos` that stay in one store region (a single flash page, or the trailer tail). uint32_t run(uint32_t pos, uint32_t remain) const; - // RAM home of byte `pos`: read_slot always resolves (flushed pages → memory-mapped flash); write_slot + // RAM home of byte `pos`: read_slot always resolves (flushed pages -> memory-mapped flash); write_slot // opens/advances the sliding payload page and returns nullptr if `pos` is in an already-flushed page. const uint8_t* read_slot(uint32_t pos) const; uint8_t* write_slot(uint32_t pos); @@ -66,7 +66,7 @@ public: void checkpoint() override; // persist page 0 (leaves) + the open payload page so a reboot can resume bool reopen() override; // re-attach to a container already staged in flash (scan for it) - // Contiguous view (flash is memory-mapped). VALID ONLY AFTER finalize() — before that, page 0 and the + // Contiguous view (flash is memory-mapped). VALID ONLY AFTER finalize() - before that, page 0 and the // tail are still in RAM. OtaManager/OtaCli/verify use this only once the transfer is COMPLETE. const uint8_t* data() const { return (_flushed && _io_ok) ? (const uint8_t*)(uintptr_t)_write_start : nullptr; } uint32_t write_start() const { return _write_start; } diff --git a/src/helpers/ota/OtaTargets.h b/src/helpers/ota/OtaTargets.h index b37c7d2e..300c4c45 100644 --- a/src/helpers/ota/OtaTargets.h +++ b/src/helpers/ota/OtaTargets.h @@ -1,7 +1,7 @@ #pragma once #include -// AUTO-GENERATED by tools/mota/gen_targets.py — do not edit by hand. +// AUTO-GENERATED by tools/mota/gen_targets.py - do not edit by hand. // 542 OTA-capable PlatformIO envs. Maps target_id (= sha2-256:4 of the env name, LE uint32) // to the human-readable env name, so a node/tool can name a target seen over the air WITHOUT // transmitting the string in the .mota / LoRa protocol. Regenerate when the OTA env set changes. diff --git a/src/helpers/ota/OtaVerify.h b/src/helpers/ota/OtaVerify.h index d706827c..1e54f341 100644 --- a/src/helpers/ota/OtaVerify.h +++ b/src/helpers/ota/OtaVerify.h @@ -4,7 +4,7 @@ #include "SignerAllowlist.h" // Full verification of a staged `.mota` (device-side: uses Ed25519 via mesh::Identity, so NOT compiled -// on the native host — the portable integrity checks live in MotaContainer and are unit-tested there). +// on the native host - the portable integrity checks live in MotaContainer and are unit-tested there). namespace mesh { namespace ota { diff --git a/src/helpers/ota/SignerAllowlist.h b/src/helpers/ota/SignerAllowlist.h index 726644fd..f169a588 100644 --- a/src/helpers/ota/SignerAllowlist.h +++ b/src/helpers/ota/SignerAllowlist.h @@ -4,7 +4,7 @@ #include #include -// Runtime-managed allowlist of trusted Ed25519 firmware-signer public keys (docs/ota_protocol.md §9, +// Runtime-managed allowlist of trusted Ed25519 firmware-signer public keys (docs/ota_protocol.md Section 9, // decision D2 + Q1: no key embedded in firmware; only allowlist-signed firmware may auto-apply). // Portable + fixed-capacity (no dynamic allocation). Persistence (load/save) is layered on per-platform. diff --git a/src/helpers/radiolib/RadioLibWrappers.cpp b/src/helpers/radiolib/RadioLibWrappers.cpp index f0b0b726..31be6de7 100644 --- a/src/helpers/radiolib/RadioLibWrappers.cpp +++ b/src/helpers/radiolib/RadioLibWrappers.cpp @@ -550,7 +550,7 @@ bool RadioLibWrapper::setRxPowerSaving(bool enabled, uint32_t rx_us, uint32_t sl int RadioLibWrapper::recvRaw(uint8_t* bytes, int sz) { int len = 0; if (state & STATE_INT_READY) { - last_radio_interrupt_millis = millis(); // ISR fired → radio hardware is alive + last_radio_interrupt_millis = millis(); // ISR fired -> radio hardware is alive if (isPacketReady()) { len = _radio->getPacketLength(); if (len > 0) { diff --git a/src/helpers/sensors/EnvironmentSensorManager.cpp b/src/helpers/sensors/EnvironmentSensorManager.cpp index 6c53b307..f6096678 100644 --- a/src/helpers/sensors/EnvironmentSensorManager.cpp +++ b/src/helpers/sensors/EnvironmentSensorManager.cpp @@ -273,14 +273,14 @@ static void scanI2CBus(TwoWire* wire, bool found[128]) { // ============================================================ // Per-sensor init and query functions // -// init(wire, address) — called only when the address was seen +// init(wire, address) - called only when the address was seen // on the bus. Returns 0 on failure, or the number of // telemetry channels the sensor will consume (1 for all // single-output sensors; INA3221 returns one per enabled // hardware channel; MLX90614 and RAK12035+calibration // return 2). // -// query(channel, sub_channel, lpp) — called once per active +// query(channel, sub_channel, lpp) - called once per active // sensor entry during querySensors(). sub_channel is always // 0 for single-output sensors. // ============================================================ @@ -658,7 +658,7 @@ static const SensorDef SENSOR_TABLE[] = { #if ENV_INCLUDE_RAK12035 { TELEM_RAK12035_ADDRESS,"RAK12035", init_rak12035, query_rak12035 }, #endif - { 0, nullptr, nullptr, nullptr } // sentinel — keeps the array non-empty + { 0, nullptr, nullptr, nullptr } // sentinel - keeps the array non-empty }; static const size_t SENSOR_TABLE_SIZE = (sizeof(SENSOR_TABLE) / sizeof(SENSOR_TABLE[0])) - 1; @@ -676,7 +676,7 @@ static const size_t SENSOR_TABLE_SIZE = (sizeof(SENSOR_TABLE) / sizeof(SENSOR_TA #endif // ============================================================ -// begin() — scan the I2C bus, then initialize only what was +// begin() - scan the I2C bus, then initialize only what was // found. A sensor whose address does not ACK during the scan // is never touched by a library call, preventing hangs or // crashes caused by absent or misbehaving hardware. @@ -729,7 +729,7 @@ bool EnvironmentSensorManager::begin() { } // ============================================================ -// querySensors() — GPS stays on channel 1; each active sensor +// querySensors() - GPS stays on channel 1; each active sensor // gets the next available channel in the order it was // initialized. // ============================================================ diff --git a/src/helpers/sensors/LPPDataHelpers.h b/src/helpers/sensors/LPPDataHelpers.h index 70a036c4..f75a392b 100644 --- a/src/helpers/sensors/LPPDataHelpers.h +++ b/src/helpers/sensors/LPPDataHelpers.h @@ -9,7 +9,7 @@ #define LPP_GENERIC_SENSOR 100 // 4 bytes, unsigned #define LPP_LUMINOSITY 101 // 2 bytes, 1 lux unsigned #define LPP_PRESENCE 102 // 1 byte, bool -#define LPP_TEMPERATURE 103 // 2 bytes, 0.1°C signed +#define LPP_TEMPERATURE 103 // 2 bytes, 0.1degC signed #define LPP_RELATIVE_HUMIDITY 104 // 1 byte, 0.5% unsigned #define LPP_ACCELEROMETER 113 // 2 bytes per axis, 0.001G #define LPP_BAROMETRIC_PRESSURE 115 // 2 bytes 0.1hPa unsigned @@ -24,11 +24,11 @@ #define LPP_ENERGY 131 // 4 byte, 0.001kWh, unsigned #define LPP_DIRECTION 132 // 2 bytes, 1deg, unsigned #define LPP_UNIXTIME 133 // 4 bytes, unsigned -#define LPP_GYROMETER 134 // 2 bytes per axis, 0.01 °/s +#define LPP_GYROMETER 134 // 2 bytes per axis, 0.01 deg/s #define LPP_COLOUR 135 // 1 byte per RGB Color -#define LPP_GPS 136 // 3 byte lon/lat 0.0001 °, 3 bytes alt 0.01 meter +#define LPP_GPS 136 // 3 byte lon/lat 0.0001 deg, 3 bytes alt 0.01 meter #define LPP_SWITCH 142 // 1 byte, 0/1 -#define LPP_POLYLINE 240 // 1 byte size, 1 byte delta factor, 3 byte lon/lat 0.0001° * factor, n (size-8) bytes deltas +#define LPP_POLYLINE 240 // 1 byte size, 1 byte delta factor, 3 byte lon/lat 0.0001deg * factor, n (size-8) bytes deltas // Multipliers #define LPP_DIGITAL_INPUT_MULT 1 diff --git a/src/helpers/sensors/RAK12035_SoilMoisture.cpp b/src/helpers/sensors/RAK12035_SoilMoisture.cpp index 8b291306..44046e34 100644 --- a/src/helpers/sensors/RAK12035_SoilMoisture.cpp +++ b/src/helpers/sensors/RAK12035_SoilMoisture.cpp @@ -5,10 +5,10 @@ * * * Ingo Fischer 11Nov2015 * * https://github.com/Apollon77/I2CSoilMoistureSensor * - * * + * * * Ken Privitt 8Feb2026 * * Adapted for MeshCore Firmware Stack * - * * + * * * MIT license * * * * This file contains a collection of routines to access the * @@ -30,7 +30,7 @@ // Initializes the I2C to null (will be setup later in Wire.Begin() // // No hardware is touched in the constructor. - // I2C communication is deferred until begin() is called. + // I2C communication is deferred until begin() is called. //------------------------------------------------------------------------------ RAK12035_SoilMoisture::RAK12035_SoilMoisture(uint8_t addr) @@ -39,17 +39,17 @@ RAK12035_SoilMoisture::RAK12035_SoilMoisture(uint8_t addr) _i2c = nullptr; // Bus not assigned yet; must be set in begin() } -//------------------------------------------------------------------------------ -// setup() -//------------------------------------------------------------------------------ -// setup(TwoWire &i2c) -// -// Assigns the I2C bus that this driver instance will use. This allows the -// application to choose between Wire, Wire1, or any other TwoWire instance -// supported by the platform. -// -// No I2C communication occurs here; setup() simply stores the pointer so that -// begin() and all register‑level operations know which bus to use. +//------------------------------------------------------------------------------ +// setup() +//------------------------------------------------------------------------------ +// setup(TwoWire &i2c) +// +// Assigns the I2C bus that this driver instance will use. This allows the +// application to choose between Wire, Wire1, or any other TwoWire instance +// supported by the platform. +// +// No I2C communication occurs here; setup() simply stores the pointer so that +// begin() and all register-level operations know which bus to use. //------------------------------------------------------------------------------ void RAK12035_SoilMoisture::setup(TwoWire &i2c) @@ -58,12 +58,12 @@ void RAK12035_SoilMoisture::setup(TwoWire &i2c) _i2c->begin(); // Initialize the bus to Wire or Wire1 } -//------------------------------------------------------------------------------ -// RAK12035 Soil Moisture begin() -//------------------------------------------------------------------------------ -// -// Performs initialization of the RAK12035 soil‑moisture sensor. This -// routine assumes that the application has already selected the I2C bus via +//------------------------------------------------------------------------------ +// RAK12035 Soil Moisture begin() +//------------------------------------------------------------------------------ +// +// Performs initialization of the RAK12035 soil-moisture sensor. This +// routine assumes that the application has already selected the I2C bus via // setup() and that the bus has been initialized externally (Wire.begin()). // It uses the passed in I2C Address (default 0x20) // @@ -74,14 +74,14 @@ void RAK12035_SoilMoisture::setup(TwoWire &i2c) // and currently only supports one address 0x20 (the default). // To support three sensors, EnvironmentSensorManager would need to be modified // to support multiple instances of the RAK12035_SoilMoisture class, -// each with a different address. (0x20, 0x21, 0x22) +// each with a different address. (0x20, 0x21, 0x22) // The begin() function would need to be modified to loop through the three addresses // // DEBUG STATEMENTS: Can be enabled by uncommenting or adding: // File: variants/rak4631 platformio.ini -// Section example: [env:RAK_4631_companion_radio_ble] +// Section example: [env:RAK_4631_companion_radio_ble] // Enable Debug statements: -D MESH_DEBUG=1 -// +// //------------------------------------------------------------------------------ bool RAK12035_SoilMoisture::begin(uint8_t addr) { @@ -105,36 +105,36 @@ bool RAK12035_SoilMoisture::begin(uint8_t addr) * USE the Build Flag: -D ENABLE_RAK12035_CALIBRATION = 1 * OR * Change the value to 1 in the RAK12035_SoilMoisture.h file - * + * * Calibration Procedure: * 1) Flash the Calibration version of the firmware. * 2) Leave the sensor dry, power up the device. * 3) After detecting the RAK12035 this firmware will display calibration data on Channel 3 - * + * * Frequency = Current Capacitance Value * Temperature = Current Wet calibration value * Power = Current Dry calibration value - * + * * 4) Click refresh several times. This will take a capacitance reading and if it is * greater than the current Dry value it will store it in the sensor * The value will bounce a little as you click refresh, but it eventually settles down (a few clicks) * the stored value will stabalize at it's Maximum value. - * - * 5) Put the sensor in water. - * + * + * 5) Put the sensor in water. + * * 6) Click refresh several times. This will take a capacitance reading and if it is * less than the current Wet value it will store it in the sensor * The value will bounce a little as you click refresh, but it eventually settles down (a few clicks) * the stored value will stabalize at it's Minimum value. - * + * * 7) The Sensor is now calibrated, turn off the device. - * + * * 8) Reflash the device with the non-Calibration Firmware, Data will be shown on Channel 2 - * + * *------------------------------------------------------------------------------------------ -*/ +*/ -#if ENABLE_RAK12035_CALIBRATION +#if ENABLE_RAK12035_CALIBRATION uint16_t _wet = 2000; // A high value the should be out of the normal Wet range set_humidity_full(_wet); @@ -143,7 +143,7 @@ bool RAK12035_SoilMoisture::begin(uint8_t addr) #endif /*-------------------------------------------------------------------------------- - * + * * Check if a sensor is present and return true if found, false if not present * *-------------------------------------------------------------------------------- @@ -162,7 +162,7 @@ bool RAK12035_SoilMoisture::begin(uint8_t addr) * * Below are all the routines to execute the various I2C commands supported * by the RAK12035 sensor - * + * *--------------------------------------------------------------------------------*/ uint16_t RAK12035_SoilMoisture::get_sensor_capacitance() //Command 01 - (r) 2 byte @@ -222,7 +222,7 @@ float RAK12035_SoilMoisture::get_sensor_temperature() //Command 05 MESH_DEBUG_PRINTLN("Function 5: get_temperature() FAIL: Bad data returned = %02X %02X", buf[0], buf[1]); return (buf[0] << 8) | buf[1]; // raw data returned for debugging 0XFFFF is error } - // Sensor returns a 16-bit signed integer (°C * 10) + // Sensor returns a 16-bit signed integer (degC * 10) int16_t raw = (buf[0] << 8) | buf[1]; float tempC = raw / 10.0f; MESH_DEBUG_PRINTLN("Function 5: get_temperature() SUCCESS: Raw=%04X Temp=%.1f C", raw, tempC); @@ -283,7 +283,7 @@ bool RAK12035_SoilMoisture::set_humidity_zero(uint16_t zero) //Command uint8_t RAK12035_SoilMoisture::get_sensor_moisture() //Command 09 - (r) 1 byte { -// Load calibration values from sensor +// Load calibration values from sensor _wet_cal = get_humidity_full(); _dry_cal = get_humidity_zero(); @@ -390,26 +390,26 @@ bool RAK12035_SoilMoisture::getEvent(uint8_t *humidity, uint16_t *temp) uint8_t moist = get_sensor_moisture(); if (moist == 0xFF) //error indicator return false; - MESH_DEBUG_PRINTLN("getEvent() - Humidity = %d", moist); + MESH_DEBUG_PRINTLN("getEvent() - Humidity = %d", moist); *humidity = moist; //Read temperature (degrees C) uint16_t t = get_sensor_temperature(); if (t == 0XFFFF) // error indicator return false; - - *temp = t; - MESH_DEBUG_PRINTLN("getEvent() - Temperature = %d", t); + + *temp = t; + MESH_DEBUG_PRINTLN("getEvent() - Temperature = %d", t); return true; } /*------------------------------------------------------------------------------------------* * Sensor Power Management and Reset Routines - * - * These routines manage the power and reset state of the sensor. The sensor_on() routine is - * designed to power on the sensor and wait for it to become responsive, while the reset() + * + * These routines manage the power and reset state of the sensor. The sensor_on() routine is + * designed to power on the sensor and wait for it to become responsive, while the reset() * routine toggles the reset pin and waits for the sensor to respond with a valid version. - * + * * They are for a future sensor power management function. *------------------------------------------------------------------------------------------*/ @@ -432,7 +432,7 @@ bool RAK12035_SoilMoisture::sensor_on() */ uint8_t v = 0; uint32_t timeout = millis(); - while ((!query_sensor())) //Wait for sensor to respond to I2C commands, + while ((!query_sensor())) //Wait for sensor to respond to I2C commands, { //indicating it is ready if ((millis() - timeout) > 50){ //0.5 second timeout for sensor to respond MESH_DEBUG_PRINTLN("reset() - Timeout, no response from I2C commands"); @@ -452,11 +452,11 @@ bool RAK12035_SoilMoisture::reset() // // The Atmel 8495 Microcoltroller: Reset input. A low level on this pin for longer than // the minimum pulse length will generate a reset, even if the clock is not -// running and provided the reset pin has not been disabled. The minimum pulse length is +// running and provided the reset pin has not been disabled. The minimum pulse length is // given in Table 25-5 on page 240. 2000ns = .002mS // Shorter pulses are not guaranteed to generate a reset. // -// Power is never removed so the Sensor reset was removed and is not needed, +// Power is never removed so the Sensor reset was removed and is not needed, // But might be needed if power is ever switched off. Here is tested code. // This has been commented out due to a pin name conflict with the Heltec v3 @@ -477,12 +477,12 @@ bool RAK12035_SoilMoisture::reset() const uint32_t timeout_ms = 500; // Wait for 0.5 seconds uint32_t start = millis(); - + while (true) { if (query_sensor()) { MESH_DEBUG_PRINTLN("reset() - First Pass, Sensor responded with valid version"); uint32_t stop_time = millis(); - MESH_DEBUG_PRINTLN("reset() - Timeout, Stop Time: %d mS", stop_time); + MESH_DEBUG_PRINTLN("reset() - Timeout, Stop Time: %d mS", stop_time); MESH_DEBUG_PRINTLN("reset() - Timeout, Duration: %d mS", (stop_time - start_time)); return true; @@ -490,7 +490,7 @@ bool RAK12035_SoilMoisture::reset() if (millis() - start > timeout_ms) { MESH_DEBUG_PRINTLN("reset() - Timeout waiting for valid sensor version"); uint32_t stop_time = millis(); - MESH_DEBUG_PRINTLN("reset() - Timeout, Stop Time: %d mS", stop_time); + MESH_DEBUG_PRINTLN("reset() - Timeout, Stop Time: %d mS", stop_time); MESH_DEBUG_PRINTLN("reset() - Timeout, Duration: %d mS", (stop_time - start_time)); return false; } diff --git a/src/helpers/ui/DisplayDriver.h b/src/helpers/ui/DisplayDriver.h index dcc5fe03..dccdc518 100644 --- a/src/helpers/ui/DisplayDriver.h +++ b/src/helpers/ui/DisplayDriver.h @@ -51,7 +51,7 @@ public: if (c >= 32 && c <= 126) { dest[j++] = c; // ASCII printable } else if (c >= 0x80) { - dest[j++] = '\xDB'; // CP437 full block █ + dest[j++] = '\xDB'; // CP437 full block # while (src[i+1] && (src[i+1] & 0xC0) == 0x80) i++; // skip UTF-8 continuation bytes } diff --git a/src/helpers/ui/U8g2Display.h b/src/helpers/ui/U8g2Display.h index 73c58936..1741ab8a 100644 --- a/src/helpers/ui/U8g2Display.h +++ b/src/helpers/ui/U8g2Display.h @@ -17,7 +17,7 @@ #endif class U8g2Display : public DisplayDriver { - // U8g2 constructor for SSD1306/SSD1315 72×40 panel — handles all + // U8g2 constructor for SSD1306/SSD1315 72x40 panel - handles all // GDDRAM column/page offsets, SETMULTIPLEX, SETDISPLAYOFFSET internally U8G2_SSD1306_72X40_ER_F_HW_I2C _u8g2; bool _isOn; diff --git a/test/test_mesh_tables/test_simple_mesh_tables.cpp b/test/test_mesh_tables/test_simple_mesh_tables.cpp index aff2fcd9..72c2ce08 100644 --- a/test/test_mesh_tables/test_simple_mesh_tables.cpp +++ b/test/test_mesh_tables/test_simple_mesh_tables.cpp @@ -45,7 +45,7 @@ static Packet makeMultipartAckPacket(uint32_t crc, uint8_t remaining = 1, bool d return p; } -// ── wasSeen: pure query ─────────────────────────────────────────────────────── +// -- wasSeen: pure query ------------------------------------------------------- TEST(SimpleMeshTables, WasSeen_ReturnsFalseForUnseen) { SimpleMeshTables t; @@ -61,7 +61,7 @@ TEST(SimpleMeshTables, WasSeen_IsPureQuery_DoesNotInsert) { EXPECT_FALSE(t.wasSeen(&p)); } -// ── markSeen + wasSeen ─────────────────────────────────────────────────────── +// -- markSeen + wasSeen ------------------------------------------------------- TEST(SimpleMeshTables, MarkSeen_MakesWasSeenReturnTrue) { SimpleMeshTables t; @@ -100,7 +100,7 @@ TEST(SimpleMeshTables, QueryThenMark_WorksCorrectly) { EXPECT_TRUE(t.wasSeen(&p)); } -// ── dup stats ──────────────────────────────────────────────────────────────── +// -- dup stats ---------------------------------------------------------------- TEST(SimpleMeshTables, WasSeen_IncrementsFloodDupStat) { SimpleMeshTables t; @@ -120,7 +120,7 @@ TEST(SimpleMeshTables, WasSeen_IncrementsDirectDupStat) { EXPECT_EQ(1u, t.getNumDirectDups()); } -// ── clear ──────────────────────────────────────────────────────────────────── +// -- clear -------------------------------------------------------------------- TEST(SimpleMeshTables, Clear_RemovesSeenPacket) { SimpleMeshTables t; diff --git a/test/test_ota/mota_vectors.h b/test/test_ota/mota_vectors.h index 48a1e205..b9885946 100644 --- a/test/test_ota/mota_vectors.h +++ b/test/test_ota/mota_vectors.h @@ -1,4 +1,4 @@ -// AUTO-GENERATED by tools/mota/gen_vectors.py — do not edit by hand. +// AUTO-GENERATED by tools/mota/gen_vectors.py - do not edit by hand. // Cross-check vectors: a real .mota from the reference packager (motalib.py). #pragma once #include diff --git a/test/test_ota/test_ota_core.cpp b/test/test_ota/test_ota_core.cpp index 5b45c9cc..9f2d08ac 100644 --- a/test/test_ota/test_ota_core.cpp +++ b/test/test_ota/test_ota_core.cpp @@ -195,7 +195,7 @@ TEST(OtaMerkle, BinaryCounterMatchesLevelByLevel) { } // Verify every block's proof for several tricky counts, using proofs generated by the Python -// reference (the oracle) — covers deep promotion chains (100, 255, 256, ...). +// reference (the oracle) - covers deep promotion chains (100, 255, 256, ...). TEST(OtaMerkle, ReferenceProofsAllIndices) { for (int c = 0; c < N_PROOF_CASES; c++) { const ProofCase& pc = PROOF_CASES[c]; @@ -285,7 +285,7 @@ TEST(OtaFirmwareInfo, ParsesIdentity) { TEST(OtaFirmwareInfo, IgnoresStagedMotaHigherInRegion) { // The firmware's own EndF must win even when a staged .mota (which embeds its own EndF) sits - // above it in the same region — the body_len == offset check disambiguates. + // above it in the same region - the body_len == offset check disambiguates. std::vector body(2000); for (size_t i = 0; i < body.size(); i++) body[i] = (uint8_t)(i ^ 0x5A); std::vector img = make_image(body); @@ -499,7 +499,7 @@ static void pump(OtaManager& client, int guard_max = 200000) { } } -// A test MotaSource backing an external "folder" with one or more complete `.mota` images held in RAM — +// A test MotaSource backing an external "folder" with one or more complete `.mota` images held in RAM - // the simplest concrete transport (a real device uses serial/BLE/WiFi/FS, same interface). describe() // parses each container for the catalog + region offsets; read() is a bounds-checked memcpy. class RamMotaSource : public mesh::ota::MotaSource { @@ -653,7 +653,7 @@ TEST(OtaTransfer, MultiFragmentBlocks) { // Multi-mota folder serve: a node serves its OWN fw (view0) PLUS an external folder (RamMotaSource) of // other `.mota`. Peers discover BOTH via the tiny beacon -> query -> broadcast HAVE catalog, then fetch an -// external mota end-to-end. The relaying node never holds the folder image in RAM — it streams the +// external mota end-to-end. The relaying node never holds the folder image in RAM - it streams the // manifest/leaves/blocks from the source on demand (loadSource + srcReadTramp + proof-gen from read // leaves). The fetched bytes must equal the original `.mota` (proves the trustless relay is byte-exact). TEST(OtaFolder, ServesSelfPlusFolderAndFetchesExternal) { @@ -701,7 +701,7 @@ TEST(OtaFolder, ServesSelfPlusFolderAndFetchesExternal) { } // Fetch-resume across a reboot: a client commits some blocks, "reboots" (a fresh OtaManager on the SAME -// persisted store), and resumeStaged() re-adopts the partial container and finishes the remaining blocks — +// persisted store), and resumeStaged() re-adopts the partial container and finishes the remaining blocks - // without re-fetching the manifest or the blocks already present. TEST(OtaTransfer, ResumeAfterReboot) { g_q.clear(); diff --git a/test/test_ota/test_ota_flashplan.cpp b/test/test_ota/test_ota_flashplan.cpp index 2a501740..5d47447e 100644 --- a/test/test_ota/test_ota_flashplan.cpp +++ b/test/test_ota/test_ota_flashplan.cpp @@ -5,8 +5,8 @@ using namespace mesh::ota; // These lock down the nRF52 single-slot staging geometry that OtaStoreFlashNrf52::begin()/ -// reopen() rely on. A received `.mota` is placed bottom-aligned below the filesystem region — ExtraFS -// (0xD4000) / InternalFS (0xED000), where the node's user preferences live — and above the running image. +// reopen() rely on. A received `.mota` is placed bottom-aligned below the filesystem region - ExtraFS +// (0xD4000) / InternalFS (0xED000), where the node's user preferences live - and above the running image. // The prefs region is assumed IMMUTABLE (its bytes are outside the served/hashed self-image), so staging // or an in-place apply must never reach into it. If a layout constant or the placement math is edited // inconsistently, these fail here instead of silently corrupting prefs / the app on real hardware. @@ -62,7 +62,7 @@ TEST(OtaFlashPlan, RejectsUndersizedContainer) { } // The user-preferences filesystems (ExtraFS @ 0xD4000, InternalFS @ 0xED000) are entirely ABOVE any -// staged container AND above the in-place apply workspace — the immutability the shared-firmware hashes +// staged container AND above the in-place apply workspace - the immutability the shared-firmware hashes // depend on. Hard-code the FS addresses here (independent of the layout header) so a future edit to // FS_START that drifts into a filesystem is caught. TEST(OtaFlashPlan, PrefsRegionNeverStaged) { @@ -89,7 +89,7 @@ TEST(OtaFlashPlan, SupportsS140V7RuntimeBase) { EXPECT_EQ(start + CAP_V7, MOTA_NRF52_FS_START); } -// out_start is only written on success — a rejected plan must not clobber the caller's variable. +// out_start is only written on success - a rejected plan must not clobber the caller's variable. TEST(OtaFlashPlan, LeavesOutputUntouchedOnReject) { uint32_t start = 0x1234ABCD; EXPECT_FALSE(mota_nrf52_stage_plan(CAP_V6 + 1, APP_V6, APP_V6, start)); diff --git a/tools/mota/README.md b/tools/mota/README.md index f57e9808..ac51aecc 100644 --- a/tools/mota/README.md +++ b/tools/mota/README.md @@ -1,7 +1,7 @@ -# `tools/mota/` — OTA Python reference library & build/test tooling +# `tools/mota/` - OTA Python reference library & build/test tooling The Python side of MeshCore's `.mota` OTA system. It is the **reference implementation** of the wire -spec ([`docs/ota_protocol.md`](../../docs/ota_protocol.md)) and the **build + test infrastructure** — it is +spec ([`docs/ota_protocol.md`](../../docs/ota_protocol.md)) and the **build + test infrastructure** - it is no longer a user-facing CLI. > **Want to build / verify / inspect / serve `.mota` from the command line?** Use the standalone @@ -24,8 +24,8 @@ Uses the repo's Python venv (`meshcore/`). Dependencies: `detools` (delta), `cry |---|---| | `motalib.py` | Core logic: multihash, EndF, merkle tree+proofs, manifest/container build/parse/verify. The **reference implementation** of the spec and the unit-test oracle. Imported by everything below. | | `pio_endf.py` | **PlatformIO post-build hook** (wired in `platformio.ini`) that injects the `EndF` self-identity trailer into the flashed firmware (`-D ENABLE_OTA`). | -| `gen_vectors.py` | Generates `test/test_ota/mota_vectors.h` — the cross-check vectors the native C++ tests run against. | -| `gen_targets.py` | Generates `src/helpers/ota/OtaTargets.h` — the `target_id → env-name` table (every `ENABLE_OTA` env, resolved from `pio project config`). Shared by the firmware and `motatool` so a node can name a target seen over the air without sending the string. Regenerate when the OTA env set changes. | +| `gen_vectors.py` | Generates `test/test_ota/mota_vectors.h` - the cross-check vectors the native C++ tests run against. | +| `gen_targets.py` | Generates `src/helpers/ota/OtaTargets.h` - the `target_id -> env-name` table (every `ENABLE_OTA` env, resolved from `pio project config`). Shared by the firmware and `motatool` so a node can name a target seen over the air without sending the string. Regenerate when the OTA env set changes. | | `test_mota.py` | Unit tests for `motalib` (run directly or via pytest). | ## Tests @@ -45,7 +45,7 @@ firmware and matches a delta's `base_hash` against its own `EndF`. Wiring (handl - **ESP32 / RP2040** (emit `firmware.bin`): `post:tools/mota/pio_endf.py` in the env's `extra_scripts` plus `-D ENABLE_OTA=1`. The hook appends the 56-byte `EndF` (with `target_id`/`fw_version`/`hw_id`) to the app `.bin` before merge. -- **nRF52 / STM32** (emit `.hex` → `.uf2`): the same hook rewrites the `.hex` with the trailer at the image +- **nRF52 / STM32** (emit `.hex` -> `.uf2`): the same hook rewrites the `.hex` with the trailer at the image end. The byte logic is `motalib.ensure_endf`, used everywhere. `target_id` = `sha2-256:4(pio_env_name)`, `hw_id` = explicit `-D MOTA_HW_ID` or a role-stripped hardware diff --git a/tools/mota/gen_targets.py b/tools/mota/gen_targets.py index a6a0423d..d30267fc 100644 --- a/tools/mota/gen_targets.py +++ b/tools/mota/gen_targets.py @@ -1,5 +1,5 @@ #!/usr/bin/env python3 -"""Generate src/helpers/ota/OtaTargets.h — the target_id -> env-name table for OTA-capable envs. +"""Generate src/helpers/ota/OtaTargets.h - the target_id -> env-name table for OTA-capable envs. A target_id is sha2-256:4(pio_env_name); it travels in beacons / the .mota manifest as 4 bytes. This table lets a node (and motatool) print the human-readable env name for a target seen over the air, WITHOUT @@ -51,7 +51,7 @@ def main(): rows = [(ml.target_id_for_env(e), e) for e in envs] seen = {} - for tid, e in rows: # sha2-256:4 over ~300 names — verify no collisions + for tid, e in rows: # sha2-256:4 over ~300 names - verify no collisions if tid in seen and seen[tid] != e: print(f"WARNING: target_id collision 0x{tid:08x}: {seen[tid]!r} vs {e!r}", file=sys.stderr) seen[tid] = e @@ -61,7 +61,7 @@ def main(): "#pragma once", "#include ", "", - "// AUTO-GENERATED by tools/mota/gen_targets.py — do not edit by hand.", + "// AUTO-GENERATED by tools/mota/gen_targets.py - do not edit by hand.", f"// {len(rows)} OTA-capable PlatformIO envs. Maps target_id (= sha2-256:4 of the env name, LE uint32)", "// to the human-readable env name, so a node/tool can name a target seen over the air WITHOUT", "// transmitting the string in the .mota / LoRa protocol. Regenerate when the OTA env set changes.", diff --git a/tools/mota/gen_vectors.py b/tools/mota/gen_vectors.py index 35b1a702..70227e56 100644 --- a/tools/mota/gen_vectors.py +++ b/tools/mota/gen_vectors.py @@ -70,7 +70,7 @@ def main(): m.merkle_root, len(leaves)) lines = [ - "// AUTO-GENERATED by tools/mota/gen_vectors.py — do not edit by hand.", + "// AUTO-GENERATED by tools/mota/gen_vectors.py - do not edit by hand.", "// Cross-check vectors: a real .mota from the reference packager (motalib.py).", "#pragma once", "#include ", diff --git a/tools/mota/motalib.py b/tools/mota/motalib.py index a6e18411..1d6bcaf0 100644 --- a/tools/mota/motalib.py +++ b/tools/mota/motalib.py @@ -1,5 +1,5 @@ """ -motalib — build/parse/verify MeshCore ``.mota`` firmware-update containers. +motalib - build/parse/verify MeshCore ``.mota`` firmware-update containers. Pure logic, no CLI. Implements docs/ota_protocol.md (format_ver=2, fixed layout). @@ -136,10 +136,10 @@ def hardware_id_for_env(env_name: str) -> str: @dataclass class FwIdent: - """Self-describing firmware identity carried in the EndF trailer (docs/ota_protocol.md §2) so a node / + """Self-describing firmware identity carried in the EndF trailer (docs/ota_protocol.md Section 2) so a node / the packaging tool reads it straight from the firmware instead of relying on build flags or filenames.""" fw_version: int = 0 # packed MAJOR<<24 | MINOR<<16 | PATCH<<8 | pre - target_id: int = 0 # sha2-256:4(pio_env) as uint32 LE — hw + role + partition (fetch routing) + target_id: int = 0 # sha2-256:4(pio_env) as uint32 LE - hw + role + partition (fetch routing) hw_id: str = "" # readable hardware tag (brick-safety), e.g. "RAK4631" diff --git a/tools/mota/pio_endf.py b/tools/mota/pio_endf.py index b45eedcd..b61294b7 100644 --- a/tools/mota/pio_endf.py +++ b/tools/mota/pio_endf.py @@ -1,6 +1,6 @@ """ PlatformIO post-build extra-script: append the MeshCore ``EndF`` trailer to the -firmware image so a running node can self-locate its size/identity (docs/ota_protocol.md §2). +firmware image so a running node can self-locate its size/identity (docs/ota_protocol.md Section 2). Wire it (ONLY for OTA-enabled builds) from a variant/env, e.g.: @@ -60,7 +60,7 @@ def _version_from_headers(): """FIRMWARE_VERSION is a header ``#define`` in the example (upstream MeshCore convention), not a -D, so _cppdef() can't see it and the EndF version would otherwise default to 0. Read it from the source WITHOUT changing where MeshCore authors it: prefer the example this env actually builds (from build_src_filter), - else fall back to the repo-wide value when it's unambiguous. Purely additive — no MeshCore file changes, + else fall back to the repo-wide value when it's unambiguous. Purely additive - no MeshCore file changes, and a -D override (checked first in _firmware_ident) still wins for release builds.""" import re, glob proj = env["PROJECT_DIR"] # noqa: F821 @@ -102,7 +102,7 @@ def _version_from_headers(): def _firmware_ident(): - """Self-describing identity to embed in EndF (docs/ota_protocol.md §2): target_id is computed from the + """Self-describing identity to embed in EndF (docs/ota_protocol.md Section 2): target_id is computed from the PlatformIO env name (so it's correct even without build.sh's -D MOTA_TARGET_ID), hw_id from MOTA_HW_ID, fw_version from FIRMWARE_VERSION (a -D if set, else the example's header #define).""" import re diff --git a/tools/mota/test_mota.py b/tools/mota/test_mota.py index 9458493b..ceae01fc 100644 --- a/tools/mota/test_mota.py +++ b/tools/mota/test_mota.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 """ -Tests for motalib — run with the meshcore venv: +Tests for motalib - run with the meshcore venv: ./meshcore/bin/python tools/mota/test_mota.py diff --git a/variants/generic-e22/variant.h b/variants/generic-e22/variant.h index 6c850af2..4fe6b7b9 100644 --- a/variants/generic-e22/variant.h +++ b/variants/generic-e22/variant.h @@ -12,7 +12,7 @@ #define BATTERY_PIN 35 // A battery voltage measurement pin, voltage divider connected here to measure battery voltage #define ADC_CHANNEL ADC1_GPIO35_CHANNEL #define ADC_MULTIPLIER 1.85 // (R1 = 470k, R2 = 680k) -#define EXT_PWR_DETECT 4 // Pin to detect connected external power source for LILYGO® TTGO T-Energy T18 and other DIY boards +#define EXT_PWR_DETECT 4 // Pin to detect connected external power source for LILYGO(R) TTGO T-Energy T18 and other DIY boards #define EXT_NOTIFY_OUT 12 // Overridden default pin to use for Ext Notify Module (#975). #define LED_PIN 2 // add status LED (compatible with core-pcb and DIY targets) diff --git a/variants/heltec_t096/platformio.ini b/variants/heltec_t096/platformio.ini index 132a6612..ee18d291 100644 --- a/variants/heltec_t096/platformio.ini +++ b/variants/heltec_t096/platformio.ini @@ -2,7 +2,7 @@ ; nRF52840 (same SoftDevice S140 v6 + flash geometry as the RAK4631), so it reuses the shared ; rak4631_hw OTA recipe (ENABLE_OTA + OtaFlashLayout_nrf52.h). With the OTAFIX bootloader (oltaco ; PR #42 adds the T096 board) it can apply in place; without it the node still discovers/relays/serves -; OTA and just fetch-refuses apply (graceful) — a T096 is a valuable OTA relay/seeder either way. +; OTA and just fetch-refuses apply (graceful) - a T096 is a valuable OTA relay/seeder either way. extends = rak4631_hw board = heltec_t096 board_build.ldscript = boards/nrf52840_s140_v6.ld diff --git a/variants/heltec_tracker/platformio.ini b/variants/heltec_tracker/platformio.ini index a20058f3..43b91794 100644 --- a/variants/heltec_tracker/platformio.ini +++ b/variants/heltec_tracker/platformio.ini @@ -33,7 +33,7 @@ build_flags = -D PIN_TFT_LEDA_CTL=21 ; LEDK (switches on/off via mosfet to create the ground) -D PIN_GPS_RX=33 -D PIN_GPS_TX=34 - -D PIN_GPS_EN=35 ; N-ch MOSFET Q2 drives P-ch high-side switch → active HIGH (default) + -D PIN_GPS_EN=35 ; N-ch MOSFET Q2 drives P-ch high-side switch -> active HIGH (default) -D PIN_GPS_RESET=36 -D PIN_GPS_RESET_ACTIVE=LOW -D GPS_BAUD_RATE=115200 diff --git a/variants/heltec_tracker/target.cpp b/variants/heltec_tracker/target.cpp index 6140298c..46d3dc06 100644 --- a/variants/heltec_tracker/target.cpp +++ b/variants/heltec_tracker/target.cpp @@ -16,7 +16,7 @@ WRAPPER_CLASS radio_driver(radio, board); ESP32RTCClock fallback_clock; AutoDiscoverRTCClock rtc_clock(fallback_clock); -// GPS_EN (GPIO35) drives N-ch MOSFET → P-ch high-side switch; GPS_RESET (GPIO36) active LOW +// GPS_EN (GPIO35) drives N-ch MOSFET -> P-ch high-side switch; GPS_RESET (GPIO36) active LOW MicroNMEALocationProvider nmea = MicroNMEALocationProvider(Serial1, &rtc_clock, GPS_RESET, GPS_EN, &board.periph_power); HWTSensorManager sensors = HWTSensorManager(nmea); diff --git a/variants/heltec_v4/LoRaFEMControl.cpp b/variants/heltec_v4/LoRaFEMControl.cpp index 168185c3..25fbc8b4 100644 --- a/variants/heltec_v4/LoRaFEMControl.cpp +++ b/variants/heltec_v4/LoRaFEMControl.cpp @@ -5,7 +5,7 @@ void LoRaFEMControl::init(void) { - // Power on FEM LDO — set registers before releasing RTC hold for + // Power on FEM LDO - set registers before releasing RTC hold for // atomic transition (no glitch on deep sleep wake). pinMode(P_LORA_PA_POWER, OUTPUT); digitalWrite(P_LORA_PA_POWER, HIGH); @@ -17,8 +17,8 @@ void LoRaFEMControl::init(void) } // Auto-detect FEM type via shared GPIO2 default pull level. - // GC1109 CSD: internal pull-down → reads LOW - // KCT8103L CSD: internal pull-up → reads HIGH + // GC1109 CSD: internal pull-down -> reads LOW + // KCT8103L CSD: internal pull-up -> reads HIGH rtc_gpio_hold_dis((gpio_num_t)P_LORA_KCT8103L_PA_CSD); pinMode(P_LORA_KCT8103L_PA_CSD, INPUT); delay(1); diff --git a/variants/lilygo_tbeam_1w/TBeam1WBoard.h b/variants/lilygo_tbeam_1w/TBeam1WBoard.h index d999dfd4..670f9d40 100644 --- a/variants/lilygo_tbeam_1w/TBeam1WBoard.h +++ b/variants/lilygo_tbeam_1w/TBeam1WBoard.h @@ -8,16 +8,16 @@ // // Power architecture (LDO is separate chip on T-Beam board, not inside XY16P35): // -// VCC (+4.0~+8.0V) ──┬──────────────────► XY16P35 VCC pin 5 (PA direct) -// (USB or Battery) │ -// │ ┌───────────┐ -// └──►│ LDO Chip │──► +3.3V ──► XY16P35 (SX1262 + LNA) -// │ EN=GPIO40 │ -// └───────────┘ +// VCC (+4.0~+8.0V) --+------------------> XY16P35 VCC pin 5 (PA direct) +// (USB or Battery) | +// | +-----------+ +// +-->| LDO Chip |--> +3.3V --> XY16P35 (SX1262 + LNA) +// | EN=GPIO40 | +// +-----------+ // LDO_EN (GPIO 40): H @ +1.2V~VIN, active high, not floating // // Control signals: -// - LDO_EN (GPIO 40): HIGH enables LDO → powers SX1262 + LNA +// - LDO_EN (GPIO 40): HIGH enables LDO -> powers SX1262 + LNA // - TCXO_EN (DIO3): HIGH enables TCXO (set to 1.8V per Meshtastic) // - CTL (GPIO 21): HIGH=RX (LNA on), LOW=TX (LNA off) // - DIO2: AUTO via SX126X_DIO2_AS_RF_SWITCH (TX path) diff --git a/variants/lilygo_tbeam_1w/platformio.ini b/variants/lilygo_tbeam_1w/platformio.ini index d3245fe0..2d2de8bd 100644 --- a/variants/lilygo_tbeam_1w/platformio.ini +++ b/variants/lilygo_tbeam_1w/platformio.ini @@ -21,7 +21,7 @@ build_flags = ; RF switch configuration: ; DIO2 controls TX path (PA enable) via SX126X_DIO2_AS_RF_SWITCH ; GPIO21 controls RX path (LNA enable) via SX126X_RXEN - ; Truth table: DIO2=1,RXEN=0 → TX | DIO2=0,RXEN=1 → RX + ; Truth table: DIO2=1,RXEN=0 -> TX | DIO2=0,RXEN=1 -> RX -D SX126X_DIO2_AS_RF_SWITCH=true -D SX126X_RXEN=21 -D SX126X_DIO3_TCXO_VOLTAGE=3.0 @@ -43,8 +43,8 @@ build_flags = -D PIN_BOARD_SCL=9 ; GPS - L76K module - ; GNSS_TXD (IO5) = GPS transmits → MCU RX - ; GNSS_RXD (IO6) = GPS receives → MCU TX + ; GNSS_TXD (IO5) = GPS transmits -> MCU RX + ; GNSS_RXD (IO6) = GPS receives -> MCU TX -D PIN_GPS_TX=5 -D PIN_GPS_RX=6 -D PIN_GPS_EN=16 diff --git a/variants/lilygo_tbeam_1w/variant.h b/variants/lilygo_tbeam_1w/variant.h index f6807e56..d8608059 100644 --- a/variants/lilygo_tbeam_1w/variant.h +++ b/variants/lilygo_tbeam_1w/variant.h @@ -8,8 +8,8 @@ #define I2C_SCL 9 // GPS - Quectel L76K -// GNSS_TXD (IO5) = GPS transmits → MCU RX (setPins rxPin) -// GNSS_RXD (IO6) = GPS receives → MCU TX (setPins txPin) +// GNSS_TXD (IO5) = GPS transmits -> MCU RX (setPins rxPin) +// GNSS_RXD (IO6) = GPS receives -> MCU TX (setPins txPin) #define PIN_GPS_TX 5 // MCU receives from GPS TX #define PIN_GPS_RX 6 // MCU transmits to GPS RX #define PIN_GPS_PPS 7 // GPS PPS output diff --git a/variants/lilygo_techo_lite/TechoBoard.h b/variants/lilygo_techo_lite/TechoBoard.h index 1e5651e7..055beac8 100644 --- a/variants/lilygo_techo_lite/TechoBoard.h +++ b/variants/lilygo_techo_lite/TechoBoard.h @@ -5,7 +5,7 @@ #include // ============================================================ -// T-Echo Lite battery pins — hardcoded from LilyGo t_echo_lite_config.h +// T-Echo Lite battery pins - hardcoded from LilyGo t_echo_lite_config.h // NOT using any defines from variant.h for battery measurement // ============================================================ #define PIN_VBAT_READ _PINNUM(0, 2) // BATTERY_ADC_DATA diff --git a/variants/lilygo_techo_lite/variant.h b/variants/lilygo_techo_lite/variant.h index 702ab334..8505c5d7 100644 --- a/variants/lilygo_techo_lite/variant.h +++ b/variants/lilygo_techo_lite/variant.h @@ -149,11 +149,11 @@ extern const int SCK; #define PIN_DISPLAY_BUSY DISP_BUSY //////////////////////////////////////////////////////////////////////////////// -// GPS — per LilyGo t_echo_lite_config.h +// GPS - per LilyGo t_echo_lite_config.h // PIN_GPS_TX/RX named from GPS module's perspective -#define PIN_GPS_TX _PINNUM(0, 29) // GPS UART TX → MCU RX -#define PIN_GPS_RX _PINNUM(1, 10) // GPS UART RX ← MCU TX +#define PIN_GPS_TX _PINNUM(0, 29) // GPS UART TX -> MCU RX +#define PIN_GPS_RX _PINNUM(1, 10) // GPS UART RX <- MCU TX #define GPS_EN _PINNUM(1, 11) // GPS RT9080 power enable #define PIN_GPS_STANDBY _PINNUM(1, 13) // GPS wake-up #define PIN_GPS_PPS _PINNUM(1, 15) // GPS 1PPS \ No newline at end of file diff --git a/variants/lilygo_tlora_v2_1/platformio.ini b/variants/lilygo_tlora_v2_1/platformio.ini index 24681fd6..c2860d23 100644 --- a/variants/lilygo_tlora_v2_1/platformio.ini +++ b/variants/lilygo_tlora_v2_1/platformio.ini @@ -1,4 +1,4 @@ -; Pins, radio, OLED — shared by all TLora envs. Does not include sensor_base (saves flash for MQTT observer). +; Pins, radio, OLED - shared by all TLora envs. Does not include sensor_base (saves flash for MQTT observer). [LilyGo_TLora_V2_1_1_6_core] extends = esp32_base board = ttgo-lora32-v1 ; LILYGO T-LoRa V2.1-1.6 ESP32 with SX1276 diff --git a/variants/meshadventurer/variant.h b/variants/meshadventurer/variant.h index 356a3e17..2c34d3fb 100644 --- a/variants/meshadventurer/variant.h +++ b/variants/meshadventurer/variant.h @@ -12,7 +12,7 @@ #define BATTERY_PIN 35 // A battery voltage measurement pin, voltage divider connected here to measure battery voltage #define ADC_CHANNEL ADC1_GPIO35_CHANNEL #define ADC_MULTIPLIER 2 -#define EXT_PWR_DETECT 4 // Pin to detect connected external power source for LILYGO® TTGO T-Energy T18 and other DIY boards +#define EXT_PWR_DETECT 4 // Pin to detect connected external power source for LILYGO(R) TTGO T-Energy T18 and other DIY boards #define EXT_NOTIFY_OUT 12 // Overridden default pin to use for Ext Notify Module (#975). #define LED_PIN 2 // add status LED (compatible with core-pcb and DIY targets) diff --git a/variants/rak3401/RAK3401Board.cpp b/variants/rak3401/RAK3401Board.cpp index cbf7c108..40bcb991 100644 --- a/variants/rak3401/RAK3401Board.cpp +++ b/variants/rak3401/RAK3401Board.cpp @@ -47,7 +47,7 @@ void RAK3401Board::begin() { // PIN_3V3_EN (WB_IO2, P0.34) controls the 3V3_S switched peripheral rail // AND the 5V boost regulator (U5) on the RAK13302 that powers the SKY66122 PA. - // Must stay HIGH during radio operation — do not toggle for power saving. + // Must stay HIGH during radio operation - do not toggle for power saving. pinMode(PIN_3V3_EN, OUTPUT); digitalWrite(PIN_3V3_EN, HIGH); diff --git a/variants/rak3401/variant.h b/variants/rak3401/variant.h index 98827886..66c1e47e 100644 --- a/variants/rak3401/variant.h +++ b/variants/rak3401/variant.h @@ -158,7 +158,7 @@ static const uint8_t AREF = PIN_AREF; // SKY66122-11 FEM control on the RAK13302 module: // CSD + CPS are tied together on the PCB, routed to WisBlock IO3 (P0.21). // Setting IO3 HIGH enables the FEM (LNA for RX, PA path for TX). -// CTX is connected to SX1262 DIO2 — the radio handles TX/RX switching +// CTX is connected to SX1262 DIO2 - the radio handles TX/RX switching // in hardware via SetDIO2AsRfSwitchCtrl (microsecond-accurate, no GPIO needed). // The 5V boost for the PA is enabled by WB_IO2 (P0.34 = PIN_3V3_EN). #define SX126X_POWER_EN (21) // P0.21 = IO3 -> SKY66122 CSD+CPS (FEM enable) diff --git a/variants/xiao_nrf52/variant.h b/variants/xiao_nrf52/variant.h index 25619b9e..63f1d97a 100644 --- a/variants/xiao_nrf52/variant.h +++ b/variants/xiao_nrf52/variant.h @@ -83,11 +83,11 @@ static const uint8_t D10 = 10; #define PWRMGT_LPCOMP_AIN 7 // AIN7 = P0.31 = PIN_VBAT // IMPORTANT: The XIAO exposes battery via a resistor divider (ADC_MULTIPLIER = 3.0). // LPCOMP measures the divided voltage, not the battery voltage directly. -// Vpin = VDD * (REFSEL fraction), and VBAT ≈ Vpin * ADC_MULTIPLIER. +// Vpin = VDD * (REFSEL fraction), and VBAT ~ Vpin * ADC_MULTIPLIER. // // Using 3/8 VDD gives a wake threshold above the boot protection point: -// - If VDD ≈ 3.0V: VBAT ≈ (3.0 * 3/8) * 3 ≈ 3375mV -// - If VDD ≈ 3.3V: VBAT ≈ (3.3 * 3/8) * 3 ≈ 3712mV +// - If VDD ~ 3.0V: VBAT ~ (3.0 * 3/8) * 3 ~ 3375mV +// - If VDD ~ 3.3V: VBAT ~ (3.3 * 3/8) * 3 ~ 3712mV #define PWRMGT_LPCOMP_REFSEL 2 // 3/8 VDD (~3.38-3.71V) static const uint8_t A0 = PIN_A0; diff --git a/webui/index.html b/webui/index.html index 80e63851..7447f374 100644 --- a/webui/index.html +++ b/webui/index.html @@ -304,7 +304,7 @@ canvas{width:100%;height:56px;display:block}

Publishing

-
This node only ever uploads to MQTT — nothing from a broker is injected into the mesh.
+
This node only ever uploads to MQTT - nothing from a broker is injected into the mesh.
Node statusPeriodic health/stats to the status topic
Publish packetsMaster switch for all packet traffic below @@ -473,7 +473,7 @@ function boot(){ api("/api/status").then(function(s){ st.mode=s.mode;st.authed=s.auth;st.hasMqtt=!!s.mqtt;st.caps=s.capabilities||0;st.nslots=s.runtime_slots||0; $("#h-name").textContent=s.name||"MeshCore"; - $("#h-sub").textContent=s.role+" · "+s.fw+" · "+s.board; + $("#h-sub").textContent=s.role+" * "+s.fw+" * "+s.board; var b=$("#h-badge");b.classList.remove("hide"); if(s.mode==="setup"){b.textContent="SETUP";b.classList.add("setup")}else{b.textContent="LAN"} applyFeatures(); @@ -484,7 +484,7 @@ function boot(){ else if(!st.authed){showLogin()} else{enterApp()} }).catch(function(e){ - if(e.message!=="auth"){$("#h-sub").textContent="connection failed — retrying";setTimeout(boot,2000)} + if(e.message!=="auth"){$("#h-sub").textContent="connection failed - retrying";setTimeout(boot,2000)} }); } @@ -567,13 +567,13 @@ function markDirty(k,v,el){ if(k==="af")afHint(); updateSaveBar(); } -// The CLI's `dutycycle` is not its own pref — it just writes airtime_factor as +// The CLI's `dutycycle` is not its own pref - it just writes airtime_factor as // 100/(dc)-1. Showing the equivalent duty cycle beside `af` gives the number // operators actually reason about without a second field fighting over one pref. function afHint(){ var e=$("#af-dc");if(!e)return; var af=parseFloat($('[data-k="af"]').value); - e.textContent=isFinite(af)&&af>=0?"≈ "+(Math.round(1000/(af+1))/10)+"% duty cycle":""; + e.textContent=isFinite(af)&&af>=0?"~ "+(Math.round(1000/(af+1))/10)+"% duty cycle":""; } document.addEventListener("input",function(ev){ var el=ev.target; @@ -608,9 +608,9 @@ function buildSlots(sel){ var host=$(sel);if(!host)return; var html=""; for(var i=1;i<=st.nslots;i++){ - var opts=''; + var opts=''; st.presets.forEach(function(p){opts+=''}); - opts+=''; + opts+=''; html+='
' +'
Slot '+i+'
' +'
' @@ -620,7 +620,7 @@ function buildSlots(sel){ +slotFieldHtml(i,"server","text","Server host","") +slotFieldHtml(i,"port","number","Port","") +slotFieldHtml(i,"topic","text","Topic template","Placeholders: {iata} {device} {type} {token}") - +slotFieldHtml(i,"audience","text","JWT audience","Optional — blank for user/pass auth") + +slotFieldHtml(i,"audience","text","JWT audience","Optional - blank for user/pass auth") +'
'; } host.innerHTML=html; @@ -674,7 +674,7 @@ function saveChanges(extra,cb){ .catch(function(e){ if(e.message==="auth"){$("#save-btn").disabled=false;if(cb)cb(null,false);return} // The POST may have landed even though its response was lost (the save - // itself briefly stalls the AP's WiFi) — poll for the result regardless. + // itself briefly stalls the AP's WiFi) - poll for the result regardless. pollResult(cb,0,0,false); }); } @@ -684,11 +684,11 @@ function saveChanges(extra,cb){ function pollResult(cb,errs,idles,acked){ errs=errs||0;idles=idles||0; function fail(){$("#save-btn").disabled=false; - if(cb)cb(null,acked);else toast(acked?"Saved, but confirmation was lost — reload to verify":"Save failed — check connection and retry")} + if(cb)cb(null,acked);else toast(acked?"Saved, but confirmation was lost - reload to verify":"Save failed - check connection and retry")} api("/api/config/result",{timeout:4000}).then(function(r){ if(r.state==="pending"){setTimeout(function(){pollResult(cb,0,idles,true)},400);return} if(r.state!=="done"){ - // idle: the POST likely never arrived — but give it a moment in case + // idle: the POST likely never arrived - but give it a moment in case // it's still in flight after a connection blip if(idles<4){setTimeout(function(){pollResult(cb,errs,idles+1,acked)},700);return} fail();return; @@ -707,18 +707,18 @@ function pollResult(cb,errs,idles,acked){ }); $("#radio-warn").classList.toggle("hide",!radioReboot); if(cb){cb(r);return} - if(fails)toast(fails+" setting(s) rejected — see fields"); + if(fails)toast(fails+" setting(s) rejected - see fields"); else if(r.reboot){ st.dirty={}; - showReboot("The node is restarting with the saved WiFi settings. Reconnect to that network and open the node at its router-assigned address.",false,"Settings saved ✓ — rebooting"); + showReboot("The node is restarting with the saved WiFi settings. Reconnect to that network and open the node at its router-assigned address.",false,"Settings saved [OK] - rebooting"); return; - }else toast("Saved ✓"); + }else toast("Saved [OK]"); st.dirty={}; loadConfigSoft(); }).catch(function(e){ if(e.message==="auth"){fail();return} // Transient network error: the save itself can knock the phone off the - // AP for a moment (flash writes stall WiFi). Keep polling — the device + // AP for a moment (flash writes stall WiFi). Keep polling - the device // holds the result until we manage to read it. if(errs<20){setTimeout(function(){pollResult(cb,errs+1,idles,acked)},700);return} fail(); @@ -754,16 +754,16 @@ function startStats(){stopStats();pollStats();st.statsTimer=setInterval(pollStat function stopStats(){if(st.statsTimer){clearInterval(st.statsTimer);st.statsTimer=0}} function pollStats(){ api("/api/stats").then(function(s){ - if(s.state==="pending"){$("#stats-age").textContent="collecting…";return} + if(s.state==="pending"){$("#stats-age").textContent="collecting...";return} $("#stats-age").textContent=""; var kb=Math.round(s.heap_free/1024); $("#tiles").innerHTML= tile("Uptime",fmtUp(s.uptime_s))+ tile("Battery",(s.batt_mv/1000).toFixed(2),"V")+ - tile("Free heap",kb,"KB · min "+Math.round(s.heap_min/1024))+ + tile("Free heap",kb,"KB * min "+Math.round(s.heap_min/1024))+ tile("Max alloc",Math.round(s.heap_max_alloc/1024),"KB")+ tile("Noise floor",s.noise,"dBm")+ - tile("Last RSSI",s.rssi,"dBm · SNR "+s.snr)+ + tile("Last RSSI",s.rssi,"dBm * SNR "+s.snr)+ tile("Airtime TX",fmtUp(s.airtime_s))+ tile("Airtime RX",fmtUp(s.rx_airtime_s))+ tile("Packets RX",s.recv,"err "+s.rx_err)+ @@ -773,14 +773,14 @@ function pollStats(){ tile("TX queue",s.tx_queue)+ (st.hasMqtt?tile("MQTT queue",s.mqtt_queue):"")+ tile("WiFi RSSI",s.wifi_rssi,"dBm")+ - tile("IP",s.ip||"—"); + tile("IP",s.ip||"-"); push(st.hist.heap,kb);push(st.hist.noise,s.noise); spark($("#spark-heap"),st.hist.heap);spark($("#spark-noise"),st.hist.noise); if(st.hasMqtt){ var sl=s.slots||[]; $("#stat-slots").innerHTML=sl.length?sl.map(function(x){ var counts=(x.ok==null||x.err==null)?"":" "+x.ok+" ok / "+x.err+" err"; - return'
Slot '+x.n+" · "+esc(x.name)+''+esc(x.state)+''+counts+'
'; + return'
Slot '+x.n+" * "+esc(x.name)+''+esc(x.state)+''+counts+'
'; }).join(""):"No servers configured."; } }).catch(function(){}); @@ -854,7 +854,7 @@ $("#scan-list").addEventListener("click",function(ev){ /* ---------- wizard ---------- */ // Regional radio presets, baked in because the setup AP has no internet. // Source: https://api.meshcore.nz/api/v1/config (config.meshcore.io), Jul 2026. -// f=MHz, bw=kHz — submitted as `set radio f,bw,sf,cr`. +// f=MHz, bw=kHz - submitted as `set radio f,bw,sf,cr`. var RADIO_PRESETS=[ {t:"USA/Canada (Recommended)",f:"910.525",bw:"62.5",sf:"7",cr:"5"}, {t:"Australia",f:"915.800",bw:"250",sf:"10",cr:"5"}, @@ -926,7 +926,7 @@ function wzGo(n){ $("#stp"+i).classList.toggle("on",i<=n); }); if(n===3){ - // Prefill observer name with the node name — only while still blank, so + // Prefill observer name with the node name - only while still blank, so // an existing config or a manual edit is never clobbered. var o=$('#wz3 [data-k="mqtt.origin"]'); var nm=st.dirty.name||st.orig.name||""; @@ -942,23 +942,23 @@ function popOut(){ api("/api/portal/exit",{method:"POST"}).then(function(r){ if(r&&r.url)$("#exit-url").textContent=r.url; $("#exit-ov").classList.add("show"); - }).catch(function(){toast("Couldn't reach the node — try again")}); + }).catch(function(){toast("Couldn't reach the node - try again")}); } function buildReview(){ var rows=[],d=st.dirty; function row(l,v){rows.push('
'+l+""+esc(v)+"
")} row("WiFi network",d["wifi.ssid"]||st.orig["wifi.ssid"]||"(not set)"); - row("WiFi password",d["wifi.pwd"]?"••••••":(st.orig["wifi.pwd"]?"(unchanged)":"(open network)")); + row("WiFi password",d["wifi.pwd"]?"******":(st.orig["wifi.pwd"]?"(unchanged)":"(open network)")); if(d.name||st.orig.name)row("Node name",d.name||st.orig.name); var rc=d.radio||st.orig.radio||"",rp=null; RADIO_PRESETS.forEach(function(p){if(comboEq(presetCombo(p),rc))rp=p}); - row("Radio",(rp?rp.t+" — ":"")+rc.split(",").join(" / ")+(d.radio?"":" (unchanged)")); + row("Radio",(rp?rp.t+" - ":"")+rc.split(",").join(" / ")+(d.radio?"":" (unchanged)")); if(d.tx)row("TX power",d.tx+" dBm"); if(st.hasMqtt){ row("Observer name",d["mqtt.origin"]||st.orig["mqtt.origin"]||"(not set)"); row("IATA",(d["mqtt.iata"]||st.orig["mqtt.iata"]||"(not set)").toUpperCase()); var own=d["mqtt.owner"]||st.orig["mqtt.owner"]; - if(own)row("Owner key",own.length>12?own.slice(0,12)+"…":own); + if(own)row("Owner key",own.length>12?own.slice(0,12)+"...":own); var em=d["mqtt.email"]||st.orig["mqtt.email"]; if(em)row("Owner email",em); for(var i=1;i<=st.nslots;i++){ @@ -972,7 +972,7 @@ function buildReview(){ } function wizardSave(){ if(!st.dirty["wifi.ssid"]&&!st.orig["wifi.ssid"]){$("#wz-err").textContent="Please set a WiFi network (step 1).";return} - if($("#wz-rp").value===""){$("#wz-err").textContent="Please choose a radio preset (step 2) — nodes ship with non-US defaults.";return} + if($("#wz-rp").value===""){$("#wz-err").textContent="Please choose a radio preset (step 2) - nodes ship with non-US defaults.";return} if(st.hasMqtt&&!/^[A-Za-z]{3}$/.test(st.dirty["mqtt.iata"]||st.orig["mqtt.iata"]||"")){ $("#wz-err").textContent="Please enter a 3-letter IATA region code (MQTT step).";return} $("#wz-err").textContent=""; @@ -983,8 +983,8 @@ function wizardSave(){ if(acked){ // The node accepted the batch (202), but without its result we cannot // know whether every field passed CLI validation. - showReboot("The node accepted the settings for processing, but the confirmation reply was lost. If every setting passed, its screen shows “Config saved!” and it reboots within 30 seconds. If it stays in setup mode, reconnect to MeshCore-Setup and correct the rejected field.", - false,"Settings sent — verify the node"); + showReboot("The node accepted the settings for processing, but the confirmation reply was lost. If every setting passed, its screen shows "Config saved!" and it reboots within 30 seconds. If it stays in setup mode, reconnect to MeshCore-Setup and correct the rejected field.", + false,"Settings sent - verify the node"); st.dirty={}; return; } @@ -993,12 +993,12 @@ function wizardSave(){ } var fails=(r.results||[]).filter(function(x){return!/^OK/i.test(x.reply)}); if(fails.length){ - $("#wz-err").textContent=fails.map(function(x){return x.key+": "+x.reply}).join(" · "); + $("#wz-err").textContent=fails.map(function(x){return x.key+": "+x.reply}).join(" * "); return; } - showReboot("The node is joining “"+(st.dirty["wifi.ssid"]||st.orig["wifi.ssid"]) - +"”. Reconnect your phone or laptop to your normal WiFi. Find the node's IP on your router, or run “get webui” over its CLI to see the address.", - false,"Settings saved ✓ — rebooting in a moment"); + showReboot("The node is joining ""+(st.dirty["wifi.ssid"]||st.orig["wifi.ssid"]) + +"". Reconnect your phone or laptop to your normal WiFi. Find the node's IP on your router, or run "get webui" over its CLI to see the address.", + false,"Settings saved [OK] - rebooting in a moment"); st.dirty={}; }); } @@ -1011,7 +1011,7 @@ function doReboot(){ }).catch(function(e){if(e.message!=="auth")toast("Reboot failed: "+e.message)}); } function showReboot(msg,reconnect,title){ - $("#reboot-title").textContent=title||"Rebooting…"; + $("#reboot-title").textContent=title||"Rebooting..."; $("#reboot-msg").textContent=msg; $("#reboot-ov").classList.add("show"); var n=30;$("#reboot-count").textContent=n;