Fix Cascade power saving, USB Companion, and mOTA flows

This commit is contained in:
mikecarper committed 2026-08-18 14:02:10 -07:00
1 parent 21bfc97712
commit 7a4da3ccfb
59 files changed
+3946 -2437

No files matched your search

+13 -7
View File
@@ -93,7 +93,7 @@ best effort rather than durable storage. Each enabled slot publishes
independently, so one failed broker does not intentionally stop the other
slots.
See [MQTT_IMPLEMENTATION.md](../MQTT_IMPLEMENTATION.md) for the complete preset
See [MQTT_IMPLEMENTATION.md](https://github.com/mikecarper/MeshCore/blob/keymindCascade/MQTT_IMPLEMENTATION.md) for the complete preset
list, custom broker configuration, topic formats, authentication, diagnostics,
and memory limits.
@@ -203,12 +203,18 @@ WiFi companions do not have the repeater/room-server admin CLI password model,
so their LAN WebConfig page is intentionally unauthenticated. Use them only on
a trusted LAN.
On radio chips that support receive duty cycling, the WebConfig **Advanced**
card also exposes RX power saving. Its master switch selects continuous receive
when off or RX/sleep duty cycling when on. Levels 1-10, automatic or explicit
16/32-symbol preambles, and manual receive/sleep windows are persisted across
reboots. This radio setting is separate from whole-device sleep; the WiFi
companion remains awake so its TCP service and configuration page stay reachable.
The WebConfig **Advanced** card exposes device power saving on WiFi Companion,
repeater, and room-server builds. It also exposes RX power saving on radio chips
that support receive duty cycling. RXPS can select continuous receive, levels
1-10, automatic or explicit 16/32-symbol preambles, or manual receive/sleep
windows. Both settings are persisted across reboots.
The two settings are independent. A WiFi Companion keeps its transports
available while device power saving reduces CPU and GPS idle power. An
infrastructure node can sleep when device power saving is enabled, so its WiFi
services may be temporarily unavailable. RXPS only duty-cycles the LoRa
receiver. Fresh Cascade-profile builds default to device power saving on and
RXPS on at level 8 with a 16-symbol preamble.
When `ENABLE_OTA` is included, a WiFi companion also listens on:
+6 -4
View File
@@ -75,8 +75,10 @@ matching FULL ESP32 build when those commands are required.
Some observer commands have their own hardware limit:
- MQTT neighbor-table publishing requires PSRAM. `discover.scopes` also
requires PSRAM and the FULL MQTT parser; the portable MQTT profile omits it.
- MQTT neighbor-table publishing and `discover.scopes` require the compiled
`WITH_MQTT_NEIGHBORS` feature. PSRAM boards enable it automatically; selected
non-PSRAM variants opt in with `MQTT_NEIGHBORS_WITHOUT_PSRAM`. The commands
can therefore be present in either portable or FULL MQTT profiles.
- `discover.neighbors` does **not** require MQTT or PSRAM.
- full NTP connectivity diagnostics are omitted from the portable profile.
@@ -113,8 +115,8 @@ does not exist on that target:
room-server builds support the corresponding WiFi setters and status
commands.
- MQTT commands require an MQTT observer target.
- `discover.scopes` requires a FULL MQTT build, MQTT neighbor support, and
PSRAM.
- `discover.scopes` requires an MQTT observer with compiled neighbor support;
it does not independently require PSRAM or the FULL parser.
- GPS and external-sensor commands require their drivers and pins.
- Ethernet and bridge commands require the corresponding transport.
- LoRa OTA commands require an artifact with OTA enabled.
+36 -37
View File
@@ -32,7 +32,6 @@ Cell values mean:
- **Feature** - available only when the target compiles the feature or hardware
named in Scope.
- **Serial** - available only from the local serial console.
- **PSRAM** - available only on an MQTT target with PSRAM neighbor support.
- **Manifest** - available only when the MQTT target defines
`OTA_MANIFEST_BASE`.
- **Limited** - the family exists, but the limitation in Scope applies.
@@ -63,12 +62,12 @@ fix, no WiFi connection, an inactive bridge, or an nRF52 bootloader without
| Neighbors | [`neighbors`](cli_commands.md#list-nearby-neighbors) | Role with a neighbor table | Yes | Yes | Yes |
| Neighbors | [`neighbor.remove <pubkey_prefix>`](cli_commands.md#remove-a-neighbor) | Role with a mutable neighbor table | Yes | Yes | Yes |
| Neighbors | [`discover.neighbors`](cli_commands.md#discover-zero-hop-neighbors) | Repeater; some MQTT room servers | Yes | Yes | Yes |
| Neighbors | [`discover.scopes`](cli_commands.md#discover-neighbor-scopes-mqtt-observer-psram-only) | MQTT observer with PSRAM | No | No | No |
| Neighbors | [`discover.scopes`](cli_commands.md#discover-neighbor-scopes-mqtt-observer-neighbors-feature) | MQTT observer with compiled neighbor support | No | No | No |
| Statistics | [`clear stats`](cli_commands.md#clear-stats) | All full-parser text CLI roles | Yes | Yes | Yes |
| Statistics | [`stats-core`](cli_commands.md#system-stats---battery-uptime-queue-length-and-debug-flags) | Local serial | Serial | Serial | Serial |
| Statistics | [`stats-radio`](cli_commands.md#radio-stats---noise-floor-last-rssisnr-airtime-receive-errors) | Local serial | Serial | Serial | Serial |
| Statistics | [`stats-core`](cli_commands.md#stats-core) | Local serial | Serial | Serial | Serial |
| Statistics | [`stats-radio`](cli_commands.md#stats-radio) | Local serial | Serial | Serial | Serial |
| Statistics | [`stats-radio-diag`](#stats-radio-diag) | Local serial | Serial | Serial | Serial |
| Statistics | [`stats-packets`](cli_commands.md#packet-stats---packet-counters-received-sent) | Local serial | Serial | Serial | Serial |
| Statistics | [`stats-packets`](cli_commands.md#stats-packets) | Local serial | Serial | Serial | Serial |
| Statistics | [`get telemetry.temp/volt`; optional GPS history; `get/set telemetry.tx`](cli_commands.md#read-repeater-telemetry-history) | Non-STM32 repeater; GPS commands require a GPS provider; remote access requires administrator | Yes | Yes | Yes |
| Logging | [`log start`; `log stop`; `log erase`](cli_commands.md#logging) | Storage-backed roles retain data; other roles can return empty data | Yes | Yes | Yes |
| Logging | [`log`](cli_commands.md#print-the-captured-log-to-the-serial-terminal) | Local serial | Serial | Serial | Serial |
@@ -160,7 +159,7 @@ fix, no WiFi connection, an inactive bridge, or an nRF52 bootloader without
| Ethernet | [`eth.status`](cli_commands.md#view-ethernet-connection-status) | Ethernet target | Feature | Feature | No |
| Browser OTA | [`start ota [ap]`; `stop ota`](cli_commands.md#start-or-stop-an-over-the-air-ota-firmware-update) | ESP32 browser uploader | No | No | No |
| WebConfig | [`start webconfig [ap]`; `stop webconfig`; `get/set webui`](cli_commands.md#browser-configuration-portal-esp32-repeater-and-room-server) | ESP32 WebConfig | No | No | No |
| WiFi | [`get/set wifi.ssid`; `set wifi.pwd`; `get wifi.status`; `get/set wifi.powersave`](../MQTT_IMPLEMENTATION.md#wifi-commands) | ESP32 WiFi | No | No | No |
| WiFi | [`get/set wifi.ssid`; `set wifi.pwd`; `get wifi.status`; `get/set wifi.powersave`](https://github.com/mikecarper/MeshCore/blob/keymindCascade/MQTT_IMPLEMENTATION.md#wifi-commands) | ESP32 WiFi | No | No | No |
| WiFi | [`get/set wifi.cli`](cli_commands.md#browser-configuration-portal-esp32-repeater-and-room-server) | ESP32 WebConfig | No | No | No |
| LoRa OTA | [`ota help`; `ota ?`; `ota h`](ota_protocol.md#11-cli-surface-otaclicpp) | LoRa OTA build | No | No | Yes |
| LoRa OTA | [`ota`; `ota status`; `ota st`](ota_protocol.md#11-cli-surface-otaclicpp) | LoRa OTA build | No | No | Yes |
@@ -168,7 +167,7 @@ fix, no WiFi connection, an inactive bridge, or an nRF52 bootloader without
| LoRa OTA | [`ota ls`; `ota neighbors`; `ota nbrs`; `ota updates`; `ota n`](ota_protocol.md#11-cli-surface-otaclicpp) | LoRa OTA build | No | No | Yes |
| LoRa OTA | [`ota get`; `ota pull`; `ota download`](ota_protocol.md#11-cli-surface-otaclicpp) | LoRa OTA build; nRF52 installs in-place deltas | No | No | Yes |
| LoRa OTA | [`ota install`; `ota apply`; `ota applydelta`](ota_protocol.md#11-cli-surface-otaclicpp) | Compatible bootloader and completed update | No | No | Yes |
| LoRa OTA | [`ota rescue install <base_hash16>`](ota_protocol.md#12-apply--bootloader-contract) | Internal-flash nRF52 LoRa OTA build with failed app-side EndF validation | No | No | Feature |
| LoRa OTA | [`ota rescue install <base_hash16>`](ota_protocol.md#12-apply-bootloader-contract) | Internal-flash nRF52 LoRa OTA build with failed app-side EndF validation | No | No | Feature |
| LoRa OTA | [`ota cancel`; `ota drop`; `ota stop`](ota_protocol.md#11-cli-surface-otaclicpp) | LoRa OTA build | No | No | Yes |
| LoRa OTA | [`ota announce`; `ota adv`](ota_protocol.md#11-cli-surface-otaclicpp) | LoRa OTA build | No | No | Yes |
| LoRa OTA | [`ota self`; `ota id`](ota_protocol.md#11-cli-surface-otaclicpp) | Firmware with EndF trailer | No | No | Yes |
@@ -176,24 +175,24 @@ fix, no WiFi connection, an inactive bridge, or an nRF52 bootloader without
| LoRa OTA | [`ota config`; `ota cfg`; `ota set`](ota_protocol.md#11-cli-surface-otaclicpp) | LoRa OTA build | No | No | Yes |
| LoRa OTA | [`ota key`; `ota keys`](ota_protocol.md#11-cli-surface-otaclicpp) | LoRa OTA build | No | No | Yes |
| LoRa OTA | [`ota dev ...`](ota_protocol.md#11-cli-surface-otaclicpp) | Developer diagnostics | No | No | Yes |
| MQTT | [`get/set mqttN.preset`](../MQTT_IMPLEMENTATION.md#mqtt-slot-commands) | MQTT observer | No | No | No |
| MQTT | [`get/set mqttN.server`; `get/set mqttN.port`; `get/set mqttN.username`; `get/set mqttN.password`](../MQTT_IMPLEMENTATION.md#mqtt-slot-commands) | MQTT observer | No | No | No |
| MQTT | [`get/set mqttN.token`; `get/set mqttN.topic`; `get/set mqttN.audience`](../MQTT_IMPLEMENTATION.md#mqtt-slot-commands) | MQTT observer | No | No | No |
| MQTT | [`get/set mqttN.preset`](https://github.com/mikecarper/MeshCore/blob/keymindCascade/MQTT_IMPLEMENTATION.md#mqtt-slot-commands) | MQTT observer | No | No | No |
| MQTT | [`get/set mqttN.server`; `get/set mqttN.port`; `get/set mqttN.username`; `get/set mqttN.password`](https://github.com/mikecarper/MeshCore/blob/keymindCascade/MQTT_IMPLEMENTATION.md#mqtt-slot-commands) | MQTT observer | No | No | No |
| MQTT | [`get/set mqttN.token`; `get/set mqttN.topic`; `get/set mqttN.audience`](https://github.com/mikecarper/MeshCore/blob/keymindCascade/MQTT_IMPLEMENTATION.md#mqtt-slot-commands) | MQTT observer | No | No | No |
| MQTT | [`get mqttN.diag`](#mqtt-slot-diagnostics) | MQTT observer | No | No | No |
| MQTT | [`get/set mqtt.origin`; `get/set mqtt.iata`; `get mqtt.presets`](../MQTT_IMPLEMENTATION.md#mqtt-shared-commands) | MQTT observer | No | No | No |
| MQTT | [`get/set mqtt.origin`; `get/set mqtt.iata`; `get mqtt.presets`](https://github.com/mikecarper/MeshCore/blob/keymindCascade/MQTT_IMPLEMENTATION.md#mqtt-shared-commands) | MQTT observer | No | No | No |
| MQTT | [`get mqtt.stats`](#mqtt-stats) | MQTT observer | No | No | No |
| MQTT | [`get/set mqtt.status`; `get/set mqtt.packets`; `get/set mqtt.raw`; `get/set mqtt.interval`](../MQTT_IMPLEMENTATION.md#mqtt-shared-commands) | MQTT observer | No | No | No |
| MQTT | [`get/set mqtt.status`; `get/set mqtt.packets`; `get/set mqtt.raw`; `get/set mqtt.interval`](https://github.com/mikecarper/MeshCore/blob/keymindCascade/MQTT_IMPLEMENTATION.md#mqtt-shared-commands) | MQTT observer | No | No | No |
| MQTT | [`get/set mqtt.rx`](cli_commands.md#view-or-change-mqtt-rx-packet-uplinking); [`get/set mqtt.tx`](cli_commands.md#view-or-change-mqtt-tx-packet-uplinking) | MQTT observer | No | No | No |
| MQTT | [`get/set mqtt.neighbors`](cli_commands.md#view-or-change-periodic-neighbors-publishing-mqtt-observer-psram-only); [`get/set mqtt.neighbors.interval`](cli_commands.md#view-or-change-the-neighbors-publish-interval-mqtt-observer-psram-only) | MQTT observer with PSRAM | No | No | No |
| MQTT | [`get/set mqtt.neighbors`](cli_commands.md#view-or-change-periodic-neighbors-publishing-mqtt-observer-neighbors-feature); [`get/set mqtt.neighbors.interval`](cli_commands.md#view-or-change-the-neighbors-publish-interval-mqtt-observer-neighbors-feature) | MQTT observer with compiled neighbor support | No | No | No |
| MQTT | [`get/set mqtt.ntp`](cli_commands.md#view-or-change-the-ntp-server-mqtt-observer-only) | MQTT observer | No | No | No |
| MQTT | [`get mqtt.ntp.diag`](cli_commands.md#diagnose-ntp-server-connectivity-mqtt-observer-only) | Full MQTT observer | No | No | No |
| MQTT | [`get/set timezone`; `get/set timezone.offset`](../MQTT_IMPLEMENTATION.md#timezone-commands) | MQTT observer | No | No | No |
| MQTT | [`get/set mqtt.analyzer.us`; `get/set mqtt.analyzer.eu`](../MQTT_IMPLEMENTATION.md#migration-from-old-configuration) | Legacy MQTT aliases | No | No | No |
| MQTT | [`get/set mqtt.owner`; `get/set mqtt.email`](../MQTT_IMPLEMENTATION.md#mqtt-shared-commands) | MQTT observer; `get` is local serial only | No | No | No |
| MQTT | [`get/set timezone`; `get/set timezone.offset`](https://github.com/mikecarper/MeshCore/blob/keymindCascade/MQTT_IMPLEMENTATION.md#timezone-commands) | MQTT observer | No | No | No |
| MQTT | [`get/set mqtt.analyzer.us`; `get/set mqtt.analyzer.eu`](https://github.com/mikecarper/MeshCore/blob/keymindCascade/MQTT_IMPLEMENTATION.md#migration-from-old-configuration) | Legacy MQTT aliases | No | No | No |
| MQTT | [`get/set mqtt.owner`; `get/set mqtt.email`](https://github.com/mikecarper/MeshCore/blob/keymindCascade/MQTT_IMPLEMENTATION.md#mqtt-shared-commands) | MQTT observer; `get` is local serial only | No | No | No |
| MQTT | [`get mqtt.config.valid`](#mqtt-config-valid) | MQTT observer | No | No | No |
| SNMP | [`get/set snmp`; `get/set snmp.community`](../MQTT_SNMP.md#cli-commands) | MQTT target compiled with SNMP | No | No | No |
| Alerts | [`get/set alert`; `get/set alert.psk`; `get/set alert.hashtag`; `get/set alert.region`; `get/set alert.wifi`; `get/set alert.mqtt`; `get/set alert.interval`](../ALERTS.md#cli) | MQTT observer | No | No | No |
| Alerts | [`alert test [message]`](../ALERTS.md#cli) | MQTT observer with configured alert channel | No | No | No |
| SNMP | [`get/set snmp`; `get/set snmp.community`](https://github.com/mikecarper/MeshCore/blob/keymindCascade/MQTT_SNMP.md#cli-commands) | MQTT target compiled with SNMP | No | No | No |
| Alerts | [`get/set alert`; `get/set alert.psk`; `get/set alert.hashtag`; `get/set alert.region`; `get/set alert.wifi`; `get/set alert.mqtt`; `get/set alert.interval`](https://github.com/mikecarper/MeshCore/blob/keymindCascade/ALERTS.md#cli) | MQTT observer | No | No | No |
| Alerts | [`alert test [message]`](https://github.com/mikecarper/MeshCore/blob/keymindCascade/ALERTS.md#cli) | MQTT observer with configured alert channel | No | No | No |
| TLS | [`tls.bundletest <host>`](#tls-bundle-test) | MQTT target with embedded certificate bundle | No | No | No |
| Manifest OTA | [`ota check`](#manifest-ota) | MQTT target with `OTA_MANIFEST_BASE` | No | No | No |
| Manifest OTA | [`ota update`](#manifest-ota) | MQTT target with `OTA_MANIFEST_BASE` | No | No | No |
@@ -220,12 +219,12 @@ fix, no WiFi connection, an inactive bridge, or an nRF52 bootloader without
| Neighbors | [`neighbors`](cli_commands.md#list-nearby-neighbors) | Role with a neighbor table | Yes | Yes | Yes | Yes | Yes | Yes | Yes |
| Neighbors | [`neighbor.remove <pubkey_prefix>`](cli_commands.md#remove-a-neighbor) | Role with a mutable neighbor table | Yes | Yes | Yes | No | No | Yes | Yes |
| Neighbors | [`discover.neighbors`](cli_commands.md#discover-zero-hop-neighbors) | Repeater; some MQTT room servers | Yes | Yes | Yes | Yes | Yes | Yes | Yes |
| Neighbors | [`discover.scopes`](cli_commands.md#discover-neighbor-scopes-mqtt-observer-psram-only) | MQTT observer with PSRAM | No | No | No | No | No | PSRAM | No |
| Neighbors | [`discover.scopes`](cli_commands.md#discover-neighbor-scopes-mqtt-observer-neighbors-feature) | MQTT observer with compiled neighbor support | No | No | No | Feature | No | Feature | No |
| Statistics | [`clear stats`](cli_commands.md#clear-stats) | Full parser | Yes | Yes | Yes | No | No | Yes | Yes |
| Statistics | [`stats-core`](cli_commands.md#system-stats---battery-uptime-queue-length-and-debug-flags) | Local serial | Serial | Serial | Serial | No | No | Serial | Serial |
| Statistics | [`stats-radio`](cli_commands.md#radio-stats---noise-floor-last-rssisnr-airtime-receive-errors) | Local serial | Serial | Serial | Serial | No | No | Serial | Serial |
| Statistics | [`stats-core`](cli_commands.md#stats-core) | Local serial | Serial | Serial | Serial | No | No | Serial | Serial |
| Statistics | [`stats-radio`](cli_commands.md#stats-radio) | Local serial | Serial | Serial | Serial | No | No | Serial | Serial |
| Statistics | [`stats-radio-diag`](#stats-radio-diag) | Local serial | Serial | Serial | Serial | No | No | Serial | Serial |
| Statistics | [`stats-packets`](cli_commands.md#packet-stats---packet-counters-received-sent) | Local serial | Serial | Serial | Serial | No | No | Serial | Serial |
| Statistics | [`stats-packets`](cli_commands.md#stats-packets) | Local serial | Serial | Serial | Serial | No | No | Serial | Serial |
| Statistics | [`get telemetry.temp/volt`; optional GPS history; `get/set telemetry.tx`](cli_commands.md#read-repeater-telemetry-history) | Non-STM32 repeater; GPS commands require a GPS provider; remote access requires administrator | Yes | Yes | Yes | Yes | Yes | Yes | Yes |
| Logging | [`log start`; `log stop`; `log erase`](cli_commands.md#logging) | Storage-backed roles retain data | Yes | Yes | Yes | No | No | Yes | Yes |
| Logging | [`log`](cli_commands.md#print-the-captured-log-to-the-serial-terminal) | Local serial | Serial | Serial | Serial | No | No | Serial | Serial |
@@ -317,7 +316,7 @@ fix, no WiFi connection, an inactive bridge, or an nRF52 bootloader without
| Ethernet | [`eth.status`](cli_commands.md#view-ethernet-connection-status) | Ethernet target | Feature | Feature | No | No | No | No | Feature |
| Browser OTA | [`start ota [ap]`; `stop ota`](cli_commands.md#start-or-stop-an-over-the-air-ota-firmware-update) | Compiled browser uploader | No | No | Yes | Yes | No | Feature | Feature |
| WebConfig | [`start webconfig [ap]`; `stop webconfig`; `get/set webui`](cli_commands.md#browser-configuration-portal-esp32-repeater-and-room-server) | Compiled WebConfig | No | No | No | No | No | Feature | Feature |
| WiFi | [`get/set wifi.ssid`; `set wifi.pwd`; `get wifi.status`; `get/set wifi.powersave`](../MQTT_IMPLEMENTATION.md#wifi-commands) | MQTT WiFi, or standalone FULL WebConfig; standalone has no `get wifi.pwd` | No | No | No | Yes | No | Yes | Feature |
| WiFi | [`get/set wifi.ssid`; `set wifi.pwd`; `get wifi.status`; `get/set wifi.powersave`](https://github.com/mikecarper/MeshCore/blob/keymindCascade/MQTT_IMPLEMENTATION.md#wifi-commands) | MQTT WiFi, or standalone FULL WebConfig; standalone has no `get wifi.pwd` | No | No | No | Yes | No | Yes | Feature |
| WiFi | [`get/set wifi.cli`](cli_commands.md#browser-configuration-portal-esp32-repeater-and-room-server) | Compiled WebConfig | No | No | No | No | No | Feature | Feature |
| LoRa OTA | [`ota help`; `ota ?`; `ota h`](ota_protocol.md#11-cli-surface-otaclicpp) | LoRa OTA build | No | No | Yes | No | No | Yes | Yes |
| LoRa OTA | [`ota`; `ota status`; `ota st`](ota_protocol.md#11-cli-surface-otaclicpp) | LoRa OTA build | No | No | Yes | No | No | Yes | Yes |
@@ -325,7 +324,7 @@ fix, no WiFi connection, an inactive bridge, or an nRF52 bootloader without
| LoRa OTA | [`ota ls`; `ota neighbors`; `ota nbrs`; `ota updates`; `ota n`](ota_protocol.md#11-cli-surface-otaclicpp) | LoRa OTA build | No | No | Yes | No | No | Yes | Yes |
| LoRa OTA | [`ota get`; `ota pull`; `ota download`](ota_protocol.md#11-cli-surface-otaclicpp) | LoRa OTA build | No | No | Yes | No | No | Yes | Yes |
| LoRa OTA | [`ota install`; `ota apply`; `ota applydelta`](ota_protocol.md#11-cli-surface-otaclicpp) | Compatible completed update | No | No | Yes | No | No | Yes | Yes |
| LoRa OTA | [`ota rescue install <base_hash16>`](ota_protocol.md#12-apply--bootloader-contract) | Internal-flash nRF52 LoRa OTA build with failed app-side EndF validation | No | No | Feature | No | No | No | No |
| LoRa OTA | [`ota rescue install <base_hash16>`](ota_protocol.md#12-apply-bootloader-contract) | Internal-flash nRF52 LoRa OTA build with failed app-side EndF validation | No | No | Feature | No | No | No | No |
| LoRa OTA | [`ota cancel`; `ota drop`; `ota stop`](ota_protocol.md#11-cli-surface-otaclicpp) | LoRa OTA build | No | No | Yes | No | No | Yes | Yes |
| LoRa OTA | [`ota announce`; `ota adv`](ota_protocol.md#11-cli-surface-otaclicpp) | LoRa OTA build | No | No | Yes | No | No | Yes | Yes |
| LoRa OTA | [`ota self`; `ota id`](ota_protocol.md#11-cli-surface-otaclicpp) | Firmware with EndF trailer | No | No | Yes | No | No | Yes | Yes |
@@ -333,24 +332,24 @@ fix, no WiFi connection, an inactive bridge, or an nRF52 bootloader without
| LoRa OTA | [`ota config`; `ota cfg`; `ota set`](ota_protocol.md#11-cli-surface-otaclicpp) | LoRa OTA build | No | No | Yes | No | No | Yes | Yes |
| LoRa OTA | [`ota key`; `ota keys`](ota_protocol.md#11-cli-surface-otaclicpp) | LoRa OTA build | No | No | Yes | No | No | Yes | Yes |
| LoRa OTA | [`ota dev ...`](ota_protocol.md#11-cli-surface-otaclicpp) | Developer diagnostics | No | No | Yes | No | No | Yes | Yes |
| MQTT | [`get/set mqttN.preset`](../MQTT_IMPLEMENTATION.md#mqtt-slot-commands) | MQTT observer | No | No | No | Yes | No | Yes | No |
| MQTT | [`get/set mqttN.server`; `get/set mqttN.port`; `get/set mqttN.username`; `get/set mqttN.password`](../MQTT_IMPLEMENTATION.md#mqtt-slot-commands) | MQTT observer | No | No | No | Yes | No | Yes | No |
| MQTT | [`get/set mqttN.token`; `get/set mqttN.topic`; `get/set mqttN.audience`](../MQTT_IMPLEMENTATION.md#mqtt-slot-commands) | MQTT observer | No | No | No | Yes | No | Yes | No |
| MQTT | [`get/set mqttN.preset`](https://github.com/mikecarper/MeshCore/blob/keymindCascade/MQTT_IMPLEMENTATION.md#mqtt-slot-commands) | MQTT observer | No | No | No | Yes | No | Yes | No |
| MQTT | [`get/set mqttN.server`; `get/set mqttN.port`; `get/set mqttN.username`; `get/set mqttN.password`](https://github.com/mikecarper/MeshCore/blob/keymindCascade/MQTT_IMPLEMENTATION.md#mqtt-slot-commands) | MQTT observer | No | No | No | Yes | No | Yes | No |
| MQTT | [`get/set mqttN.token`; `get/set mqttN.topic`; `get/set mqttN.audience`](https://github.com/mikecarper/MeshCore/blob/keymindCascade/MQTT_IMPLEMENTATION.md#mqtt-slot-commands) | MQTT observer | No | No | No | Yes | No | Yes | No |
| MQTT | [`get mqttN.diag`](#mqtt-slot-diagnostics) | MQTT observer | No | No | No | Yes | No | Yes | No |
| MQTT | [`get/set mqtt.origin`; `get/set mqtt.iata`; `get mqtt.presets`](../MQTT_IMPLEMENTATION.md#mqtt-shared-commands) | MQTT observer | No | No | No | Yes | No | Yes | No |
| MQTT | [`get/set mqtt.origin`; `get/set mqtt.iata`; `get mqtt.presets`](https://github.com/mikecarper/MeshCore/blob/keymindCascade/MQTT_IMPLEMENTATION.md#mqtt-shared-commands) | MQTT observer | No | No | No | Yes | No | Yes | No |
| MQTT | [`get mqtt.stats`](#mqtt-stats) | MQTT observer | No | No | No | Yes | No | Yes | No |
| MQTT | [`get/set mqtt.status`; `get/set mqtt.packets`; `get/set mqtt.raw`; `get/set mqtt.interval`](../MQTT_IMPLEMENTATION.md#mqtt-shared-commands) | MQTT observer | No | No | No | Yes | No | Yes | No |
| MQTT | [`get/set mqtt.status`; `get/set mqtt.packets`; `get/set mqtt.raw`; `get/set mqtt.interval`](https://github.com/mikecarper/MeshCore/blob/keymindCascade/MQTT_IMPLEMENTATION.md#mqtt-shared-commands) | MQTT observer | No | No | No | Yes | No | Yes | No |
| MQTT | [`get/set mqtt.rx`](cli_commands.md#view-or-change-mqtt-rx-packet-uplinking); [`get/set mqtt.tx`](cli_commands.md#view-or-change-mqtt-tx-packet-uplinking) | MQTT observer | No | No | No | Yes | No | Yes | No |
| MQTT | [`get/set mqtt.neighbors`](cli_commands.md#view-or-change-periodic-neighbors-publishing-mqtt-observer-psram-only); [`get/set mqtt.neighbors.interval`](cli_commands.md#view-or-change-the-neighbors-publish-interval-mqtt-observer-psram-only) | MQTT observer with PSRAM | No | No | No | PSRAM | No | PSRAM | No |
| MQTT | [`get/set mqtt.neighbors`](cli_commands.md#view-or-change-periodic-neighbors-publishing-mqtt-observer-neighbors-feature); [`get/set mqtt.neighbors.interval`](cli_commands.md#view-or-change-the-neighbors-publish-interval-mqtt-observer-neighbors-feature) | MQTT observer with compiled neighbor support | No | No | No | Feature | No | Feature | No |
| MQTT | [`get/set mqtt.ntp`](cli_commands.md#view-or-change-the-ntp-server-mqtt-observer-only) | MQTT observer | No | No | No | Yes | No | Yes | No |
| MQTT | [`get mqtt.ntp.diag`](cli_commands.md#diagnose-ntp-server-connectivity-mqtt-observer-only) | Full MQTT observer; intentionally cut from portable | No | No | No | No | No | Yes | No |
| MQTT | [`get/set timezone`; `get/set timezone.offset`](../MQTT_IMPLEMENTATION.md#timezone-commands) | MQTT observer | No | No | No | Yes | No | Yes | No |
| MQTT | [`get/set mqtt.analyzer.us`; `get/set mqtt.analyzer.eu`](../MQTT_IMPLEMENTATION.md#migration-from-old-configuration) | Legacy MQTT aliases | No | No | No | Yes | No | Yes | No |
| MQTT | [`get/set mqtt.owner`; `get/set mqtt.email`](../MQTT_IMPLEMENTATION.md#mqtt-shared-commands) | MQTT observer; `get` is local serial only | No | No | No | Yes | No | Yes | No |
| MQTT | [`get/set timezone`; `get/set timezone.offset`](https://github.com/mikecarper/MeshCore/blob/keymindCascade/MQTT_IMPLEMENTATION.md#timezone-commands) | MQTT observer | No | No | No | Yes | No | Yes | No |
| MQTT | [`get/set mqtt.analyzer.us`; `get/set mqtt.analyzer.eu`](https://github.com/mikecarper/MeshCore/blob/keymindCascade/MQTT_IMPLEMENTATION.md#migration-from-old-configuration) | Legacy MQTT aliases | No | No | No | Yes | No | Yes | No |
| MQTT | [`get/set mqtt.owner`; `get/set mqtt.email`](https://github.com/mikecarper/MeshCore/blob/keymindCascade/MQTT_IMPLEMENTATION.md#mqtt-shared-commands) | MQTT observer; `get` is local serial only | No | No | No | Yes | No | Yes | No |
| MQTT | [`get mqtt.config.valid`](#mqtt-config-valid) | MQTT observer | No | No | No | Yes | No | Yes | No |
| SNMP | [`get/set snmp`; `get/set snmp.community`](../MQTT_SNMP.md#cli-commands) | MQTT target compiled with SNMP | No | No | No | No | No | Feature | No |
| Alerts | [`get/set alert`; `get/set alert.psk`; `get/set alert.hashtag`; `get/set alert.region`; `get/set alert.wifi`; `get/set alert.mqtt`; `get/set alert.interval`](../ALERTS.md#cli) | MQTT observer | No | No | No | Yes | No | Yes | No |
| Alerts | [`alert test [message]`](../ALERTS.md#cli) | MQTT observer with configured alert channel | No | No | No | Yes | No | Yes | No |
| SNMP | [`get/set snmp`; `get/set snmp.community`](https://github.com/mikecarper/MeshCore/blob/keymindCascade/MQTT_SNMP.md#cli-commands) | MQTT target compiled with SNMP | No | No | No | No | No | Feature | No |
| Alerts | [`get/set alert`; `get/set alert.psk`; `get/set alert.hashtag`; `get/set alert.region`; `get/set alert.wifi`; `get/set alert.mqtt`; `get/set alert.interval`](https://github.com/mikecarper/MeshCore/blob/keymindCascade/ALERTS.md#cli) | MQTT observer | No | No | No | Yes | No | Yes | No |
| Alerts | [`alert test [message]`](https://github.com/mikecarper/MeshCore/blob/keymindCascade/ALERTS.md#cli) | MQTT observer with configured alert channel | No | No | No | Yes | No | Yes | No |
| TLS | [`tls.bundletest <host>`](#tls-bundle-test) | MQTT target with embedded certificate bundle | No | No | No | Feature | No | Feature | No |
| Manifest OTA | [`ota check`](#manifest-ota) | MQTT target with `OTA_MANIFEST_BASE` | No | No | No | Manifest | No | Manifest | No |
| Manifest OTA | [`ota update`](#manifest-ota) | MQTT target with `OTA_MANIFEST_BASE` | No | No | No | Manifest | No | Manifest | No |
+14 -6
View File
@@ -260,7 +260,7 @@ require MQTT or PSRAM.
---
### Discover neighbor scopes (MQTT observer, PSRAM only)
### Discover neighbor scopes (MQTT observer, neighbors feature)
Refreshes the zero-hop neighbor table, then queries each neighbor for its region
scopes and publishes the assembled table to the MQTT `neighbors` topic once.
@@ -282,6 +282,7 @@ Elsewhere it replies `Err - neighbors not enabled in this build`. If a
---
<a id="stats-core"></a>
### System Stats - Battery, Uptime, Queue Length and Debug Flags
**Usage:**
- `stats-core`
@@ -290,6 +291,7 @@ Elsewhere it replies `Err - neighbors not enabled in this build`. If a
---
<a id="stats-radio"></a>
### Radio Stats - Noise floor, Last RSSI/SNR, Airtime, Receive errors
**Usage:** `stats-radio`
@@ -297,6 +299,7 @@ Elsewhere it replies `Err - neighbors not enabled in this build`. If a
---
<a id="stats-packets"></a>
### Packet stats - Packet counters: Received, Sent
**Usage:** `stats-packets`
@@ -645,6 +648,7 @@ Station G2/G3 targets default to `off`.
- Its default `off` state keeps the host-controlled SX1262 receive path enabled during RX duty-cycle mode. Setting it to `on` reproduces the old missing-RF_RX behavior and can significantly reduce receive sensitivity, making remote commands harder to receive.
- `radio.rxps.rfrx_disabled` is supported only on SX1262 targets with a host-controlled RX enable pin.
- `on` and `conservative` select level `1` with a 16-symbol preamble; `balanced` selects level `5` with a 16-symbol preamble.
- Fresh Cascade-profile builds start with RXPS on at level `8` and a 16-symbol preamble. Saved operator settings still take precedence after an upgrade.
- Level-based settings automatically recalculate their timings when the spreading factor or bandwidth changes. Custom `<rx_us> <sleep_us>` timings remain fixed.
- The selected mode is applied immediately, persisted, and restored after reboot.
@@ -1081,16 +1085,18 @@ get clock.sync.status
- `powersaving`
- `powersaving on`
- `powersaving off`
- `set powersaving on`
- `set powersaving off`
**Parameters:**
- `on`: enable power saving
- `off`: disable power saving
**Default:** `off` for infrastructure roles; `on` for Companion firmware
**Default:** `on` for fresh Cascade-profile builds and Companion firmware; `off` for other infrastructure profiles
**Note:** Infrastructure firmware enters sleep between radio transmissions. It refuses to enable power saving from the local serial console or while an active USB serial data connection is detected; USB power alone does not block power saving.
Companion firmware defaults this setting to `on`. Full Companion accepts the command from its local USB terminal and exposes the same setting in WebConfig. On ESP32, it lowers the CPU clock to 80 MHz, enables idle yielding, and enables the configured GPS duty cycle. USB, BLE, and WiFi remain available. `powersaving off` restores the board's normal CPU clock and disables the GPS duty cycle. This device setting is separate from LoRa RXPS (`radio.rxps`) and WiFi modem power save (`wifi.powersave`).
Companion firmware defaults this setting to `on`. Full Companion accepts the command from its local USB terminal and exposes the same setting in WebConfig. On ESP32, it lowers the CPU clock to 80 MHz, enables idle yielding, and enables the configured GPS duty cycle. USB, BLE, and WiFi remain available. `powersaving off` restores the board's normal CPU clock and disables the GPS duty cycle. This device setting is separate from LoRa RXPS (`radio.rxps`) and WiFi modem power save (`wifi.powersave`). Infrastructure WebConfig uses the `set powersaving` form; enabling it can put the node to sleep and make WiFi temporarily unavailable.
---
@@ -3035,7 +3041,7 @@ sleep, this command schedules a sync and wakes it; after `gps off`, it reports
---
#### View or change periodic neighbors publishing (MQTT observer, PSRAM only)
#### View or change periodic neighbors publishing (MQTT observer, neighbors feature)
**Usage:**
- `get mqtt.neighbors`
- `set mqtt.neighbors <on|off>`
@@ -3058,7 +3064,7 @@ sleep, this command schedules a sync and wakes it; after `gps off`, it reports
---
#### View or change the neighbors publish interval (MQTT observer, PSRAM only)
#### View or change the neighbors publish interval (MQTT observer, neighbors feature)
**Usage:**
- `get mqtt.neighbors.interval`
- `set mqtt.neighbors.interval <hours>`
@@ -3068,7 +3074,9 @@ sleep, this command schedules a sync and wakes it; after `gps off`, it reports
**Default:** `24` (hours)
> **Note:** Out-of-range values are rejected (not clamped). Requires a PSRAM board.
> **Note:** Out-of-range values are rejected (not clamped). Requires a build
> with `WITH_MQTT_NEIGHBORS`; PSRAM boards enable it automatically and selected
> non-PSRAM variants opt in with `MQTT_NEIGHBORS_WITHOUT_PSRAM`.
---
+164 -55
View File
@@ -1,13 +1,23 @@
# Companion Protocol
- **Last Updated**: 2026-03-08
- **Protocol Version**: Companion Firmware v1.12.0+
- **Last Updated**: 2026-08-18
- **Protocol Version**: 13 (`FIRMWARE_VER_CODE`)
> NOTE: This document is still in development. Some information may be inaccurate.
> The command and response catalogs track
> `examples/companion_radio/MyMesh.cpp`. Applications should negotiate the
> protocol and validate lengths because older firmware exposes a subset.
This document provides a comprehensive guide for communicating with MeshCore devices over Bluetooth Low Energy (BLE).
This document is a practical guide to MeshCore's binary companion protocol.
The same protocol frames can be carried by the enabled BLE, USB serial, Wi-Fi,
or Ethernet companion interface; connection details differ by build.
It is platform-agnostic and can be used for Android, iOS, Python, JavaScript, or any other platform that supports BLE.
On builds exposing more than one transport, delivery-required replies follow
the interface which supplied the command. The multi-frame contact-list response
holds that route until `END_OF_CONTACTS`; best-effort asynchronous observations
may still be broadcast to enabled clients. Treat the device as one Companion
session rather than as independent per-transport sessions.
The examples focus on BLE, but the packet formats are transport-independent.
## Official Libraries
@@ -160,6 +170,59 @@ The first byte indicates the packet type (see [Response Parsing](#response-parsi
## Commands
The first byte selects the command. This is the current protocol-v13 command
catalog; bytes `0x2C`-`0x31` are parked and `0x35` is unused.
| Byte | Firmware name | Purpose |
|---|---|---|
| `0x01` | `CMD_APP_START` | Start an app session and request self information. |
| `0x02` | `CMD_SEND_TXT_MSG` | Send text to a contact. |
| `0x03` | `CMD_SEND_CHANNEL_TXT_MSG` | Send channel text. |
| `0x04` | `CMD_GET_CONTACTS` | Enumerate contacts, optionally modified since a timestamp. |
| `0x05` / `0x06` | `CMD_GET_DEVICE_TIME` / `CMD_SET_DEVICE_TIME` | Read or set the device clock. |
| `0x07` / `0x08` | `CMD_SEND_SELF_ADVERT` / `CMD_SET_ADVERT_NAME` | Advertise self or change the advertised name. |
| `0x09` | `CMD_ADD_UPDATE_CONTACT` | Add or update a contact. |
| `0x0A` | `CMD_SYNC_NEXT_MESSAGE` | Dequeue the next pending message. |
| `0x0B` / `0x0C` | `CMD_SET_RADIO_PARAMS` / `CMD_SET_RADIO_TX_POWER` | Set radio parameters or transmit power. |
| `0x0D` | `CMD_RESET_PATH` | Reset a contact's learned path. |
| `0x0E` | `CMD_SET_ADVERT_LATLON` | Set advertised coordinates. |
| `0x0F` | `CMD_REMOVE_CONTACT` | Remove a contact. |
| `0x10` / `0x11` / `0x12` | `CMD_SHARE_CONTACT` / `CMD_EXPORT_CONTACT` / `CMD_IMPORT_CONTACT` | Share, export, or import contact data. |
| `0x13` | `CMD_REBOOT` | Reboot after the required confirmation body. |
| `0x14` | `CMD_GET_BATT_AND_STORAGE` | Read battery and storage usage. |
| `0x15` | `CMD_SET_TUNING_PARAMS` | Set tuning parameters. |
| `0x16` | `CMD_DEVICE_QUERY` | Negotiate protocol support and read device information. |
| `0x17` / `0x18` | `CMD_EXPORT_PRIVATE_KEY` / `CMD_IMPORT_PRIVATE_KEY` | Export or import identity key material when enabled. |
| `0x19` | `CMD_SEND_RAW_DATA` | Send an application raw-data packet. |
| `0x1A`-`0x1D` | `CMD_SEND_LOGIN` through `CMD_LOGOUT` | Manage a server connection. |
| `0x1E` | `CMD_GET_CONTACT_BY_KEY` | Look up a contact by public-key prefix. |
| `0x1F` / `0x20` | `CMD_GET_CHANNEL` / `CMD_SET_CHANNEL` | Read or write a channel slot. |
| `0x21`-`0x23` | `CMD_SIGN_START` through `CMD_SIGN_FINISH` | Stream data for identity signing. |
| `0x24` | `CMD_SEND_TRACE_PATH` | Trace a direct route. |
| `0x25` | `CMD_SET_DEVICE_PIN` | Set or clear the device PIN. |
| `0x26` | `CMD_SET_OTHER_PARAMS` | Set telemetry, location, ACK, and related preferences. |
| `0x27` | `CMD_SEND_TELEMETRY_REQ` | Send the legacy telemetry request. |
| `0x28` / `0x29` | `CMD_GET_CUSTOM_VARS` / `CMD_SET_CUSTOM_VAR` | Read or set custom variables. |
| `0x2A` | `CMD_GET_ADVERT_PATH` | Read a cached advertisement path. |
| `0x2B` | `CMD_GET_TUNING_PARAMS` | Read tuning parameters. |
| `0x32` | `CMD_SEND_BINARY_REQ` | Send an application binary request. |
| `0x33` | `CMD_FACTORY_RESET` | Factory-reset after the required confirmation body. |
| `0x34` | `CMD_SEND_PATH_DISCOVERY_REQ` | Request path discovery. |
| `0x36` | `CMD_SET_FLOOD_SCOPE_KEY` | Select scoped or unscoped flood behavior. |
| `0x37` | `CMD_SEND_CONTROL_DATA` | Send zero-hop control data. |
| `0x38` | `CMD_GET_STATS` | Read core, radio, or packet statistics. |
| `0x39` | `CMD_SEND_ANON_REQ` | Send an anonymous request. |
| `0x3A` / `0x3B` | `CMD_SET_AUTOADD_CONFIG` / `CMD_GET_AUTOADD_CONFIG` | Write or read automatic-contact policy. |
| `0x3C` | `CMD_GET_ALLOWED_REPEAT_FREQ` | Read allowed client-repeat frequency ranges. |
| `0x3D` | `CMD_SET_PATH_HASH_MODE` | Set path-hash width mode. |
| `0x3E` | `CMD_SEND_CHANNEL_DATA` | Send a channel binary datagram. |
| `0x3F` / `0x40` | `CMD_SET_DEFAULT_FLOOD_SCOPE` / `CMD_GET_DEFAULT_FLOOD_SCOPE` | Write or read the default flood scope. |
| `0x41` | `CMD_SEND_RAW_PACKET` | Queue a fully encoded raw mesh packet. |
| `0x42` / `0x43` | `CMD_GET_RADIO_FEM_RXGAIN` / `CMD_SET_RADIO_FEM_RXGAIN` | Read or set FEM receive gain. |
The sections below detail the most common frames. Refer to the source named
above for command bodies that are not expanded here.
### 1. App Start
**Purpose**: Initialize communication with the device. Must be sent first after connection.
@@ -187,12 +250,12 @@ Bytes 8+: Application name (UTF-8, optional)
**Command Format**:
```
Byte 0: 0x16
Byte 1: 0x03
Byte 1: Highest companion protocol version understood by the app
```
**Example** (hex):
```
16 03
16 0D
```
**Response**: `PACKET_DEVICE_INFO` (0x0D) with device information
@@ -206,7 +269,7 @@ Byte 1: 0x03
**Command Format**:
```
Byte 0: 0x1F
Byte 1: Channel Index (0-7)
Byte 1: Channel index (0 through max_channels - 1)
```
**Example** (get channel 1):
@@ -225,16 +288,17 @@ Byte 1: Channel Index (0-7)
**Command Format**:
```
Byte 0: 0x20
Byte 1: Channel Index (0-7)
Byte 1: Channel index (0 through max_channels - 1)
Bytes 2-33: Channel Name (32 bytes, UTF-8, null-padded)
Bytes 34-49: Secret (16 bytes)
```
**Total Length**: 50 bytes
**Channel Index**:
- Index 0: Reserved for public channels (no secret)
- Indices 1-7: Available for private channels
**Channel index**:
- Slot count is build-specific. Read `max_channels` from byte 3 of
`PACKET_DEVICE_INFO`; current profiles commonly expose 1, 8, or 40 slots.
- No slot number has an intrinsic public/private meaning.
**Channel Name**:
- UTF-8 encoded
@@ -242,10 +306,12 @@ Bytes 34-49: Secret (16 bytes)
- Padded with null bytes (0x00) if shorter
**Secret Field** (16 bytes):
- For **private channels**: 16-byte secret
- For **public channels**: All zeros (0x00)
- Supply the exact 16-byte channel key. A private channel normally uses a
cryptographically random key; known public and hashtag channels use their
defined or derived key.
- An all-zero key is not the public-channel key.
**Example** (create channel "YourChannelName" at index 1 with secret):
**Example** (create channel "SMS" at index 1 with secret):
```
20 01 53 4D 53 00 00 ... (name padded to 32 bytes)
[16 bytes of secret]
@@ -265,7 +331,7 @@ Bytes 34-49: Secret (16 bytes)
```
Byte 0: 0x03
Byte 1: 0x00
Byte 2: Channel Index (0-7)
Byte 2: Channel index (0 through max_channels - 1)
Bytes 3-6: Timestamp (32-bit little-endian Unix timestamp, seconds)
Bytes 7+: Message Text (UTF-8, variable length)
```
@@ -288,13 +354,19 @@ Bytes 7+: Message Text (UTF-8, variable length)
**Command Format**:
```
Byte 0: 0x3E
Byte 1: Channel Index (0-7)
Byte 2: Path Length (0xFF = flood, otherwise actual path length)
Bytes 3 .. 2+path_len: Path (omitted when path_len == 0xFF)
Byte 1: Channel index (0 through max_channels - 1)
Byte 2: Encoded path descriptor (0xFF = flood)
Bytes 3+: Encoded path bytes (omitted for 0xFF)
Next 2 bytes (little-endian): Data Type (`data_type`, uint16)
Remaining bytes: Binary payload (variable length)
```
For a direct send, the descriptor's low six bits are the hash count and its
high two bits are the hash size minus one. Current mesh packets accept one-,
two-, or three-byte hashes; the four-byte code is reserved. The following path
therefore occupies `hash_count * hash_size` bytes; the descriptor itself is not
a raw byte count.
**Example** (flood, `DATA_TYPE_DEV`, payload `A1 B2 C3`, channel 1):
```
3E 01 FF FF FF A1 B2 C3
@@ -303,10 +375,10 @@ Remaining bytes: Binary payload (variable length)
**Data Type / Transport Mapping**:
- `0x0000` (`DATA_TYPE_RESERVED`) is invalid and rejected with `PACKET_ERROR`.
- `0xFFFF` (`DATA_TYPE_DEV`) is the developer namespace for experimenting and developing apps.
- Values `0x0001`-`0xFFFE` are available for registered application/community namespaces. See the [Registered data_type values](#registered-data_type-values) table below.
- Registered application/community namespaces occupy `0x0100`-`0xFEFF`; the remaining nonzero ranges are reserved for internal or development use. See the [Registered data_type values](#registered-data_type-values) table below.
**Limits**:
- Maximum payload length is `MAX_CHANNEL_DATA_LENGTH = MAX_FRAME_SIZE - 9 = 163` bytes.
- Maximum payload length is `MAX_CHANNEL_DATA_LENGTH = MAX_FRAME_SIZE - 9 = 167` bytes.
- Larger payloads are rejected with `PACKET_ERROR` (`ERR_CODE_ILLEGAL_ARG`).
**Response**: `PACKET_OK` (0x00) on success, or `PACKET_ERROR` (0x01) with one of:
@@ -341,7 +413,7 @@ Inbound group datagrams (radio-level `PAYLOAD_TYPE_GRP_DATA`, 0x06) are forwarde
Byte 0: 0x1B (packet type)
Byte 1: SNR (signed int8, scaled x4 - divide by 4.0 to recover dB)
Bytes 2-3: Reserved (clients MUST ignore)
Byte 4: Channel Index (0-7)
Byte 4: Channel index (0 through max_channels - 1)
Byte 5: Path Length (actual path length when flooded, otherwise 0xFF for direct)
Bytes 6-7: Data Type (uint16 little-endian)
Byte 8: Data Length
@@ -452,7 +524,8 @@ Byte 0: 0x14
### Channel Lifecycle
1. **Set Channel**:
- Fetch all channel slots, and find one with empty name and all-zero secret
- Read `max_channels` from device info, fetch those slots, and choose an
unused slot (normally an empty name and zeroed key)
- Generate or provide a 16-byte secret
- Send `CMD_SET_CHANNEL` with name and a 16-byte secret
2. **Get Channel**:
@@ -550,7 +623,7 @@ def parse_contact_message(data):
**Standard Format** (`PACKET_CHANNEL_MSG_RECV`, 0x08):
```
Byte 0: 0x08 (packet type)
Byte 1: Channel Index (0-7)
Byte 1: Channel index (0 through max_channels - 1)
Byte 2: Path Length
Byte 3: Text Type
Bytes 4-7: Timestamp (32-bit little-endian)
@@ -562,7 +635,7 @@ Bytes 8+: Message Text (UTF-8)
Byte 0: 0x11 (packet type)
Byte 1: SNR (signed byte, multiplied by 4)
Bytes 2-3: Reserved
Byte 4: Channel Index (0-7)
Byte 4: Channel index (0 through max_channels - 1)
Byte 5: Path Length
Byte 6: Text Type
Bytes 7-10: Timestamp (32-bit little-endian)
@@ -600,8 +673,11 @@ def parse_channel_message(data):
Use the `SEND_CHANNEL_MESSAGE` command (see [Commands](#commands)).
**Important**:
- Messages are limited to 133 characters per MeshCore specification
- Long messages should be split into chunks
- The shared text envelope permits up to 160 UTF-8 bytes. For channel text,
firmware prepends `<sender name>: ` inside that envelope, so the available
message body is `160 - prefix_bytes` and varies with the configured name.
- Count encoded UTF-8 bytes, not Unicode characters. Split a longer message at
valid UTF-8 boundaries.
- Include a chunk indicator (e.g., "[1/3] message text")
---
@@ -617,31 +693,57 @@ This document uses a spec-level naming convention (`PACKET_*`) for bytes the fir
Byte values are authoritative; names are aliases. When reading firmware source, `RESP_CODE_X` / `PUSH_CODE_X` correspond to this doc's `PACKET_X` of the same numeric value.
### Packet Types
### Response types
| Value | Name | Description |
|-------|----------------------------|-------------------------------|
| 0x00 | PACKET_OK | Command succeeded |
| 0x01 | PACKET_ERROR | Command failed |
| 0x02 | PACKET_CONTACT_START | Start of contact list |
| 0x03 | PACKET_CONTACT | Contact information |
| 0x04 | PACKET_CONTACT_END | End of contact list |
| 0x05 | PACKET_SELF_INFO | Device self-information |
| 0x06 | PACKET_MSG_SENT | Message sent confirmation |
| 0x07 | PACKET_CONTACT_MSG_RECV | Contact message (standard) |
| 0x08 | PACKET_CHANNEL_MSG_RECV | Channel message (standard) |
| 0x09 | PACKET_CURRENT_TIME | Current time response |
| 0x0A | PACKET_NO_MORE_MSGS | No more messages available |
| 0x0C | PACKET_BATTERY | Battery level |
| 0x0D | PACKET_DEVICE_INFO | Device information |
| 0x10 | PACKET_CONTACT_MSG_RECV_V3 | Contact message (V3 with SNR) |
| 0x11 | PACKET_CHANNEL_MSG_RECV_V3 | Channel message (V3 with SNR) |
| 0x12 | PACKET_CHANNEL_INFO | Channel information |
| 0x1B | PACKET_CHANNEL_DATA_RECV | Channel data datagram |
| 0x80 | PACKET_ADVERTISEMENT | Advertisement packet |
| 0x82 | PACKET_ACK | Acknowledgment |
| 0x83 | PACKET_MESSAGES_WAITING | Messages waiting notification |
| 0x88 | PACKET_LOG_DATA | RF log data (can be ignored) |
| Value | Firmware name | Description |
|---|---|---|
| `0x00` | `RESP_CODE_OK` | Command succeeded. |
| `0x01` | `RESP_CODE_ERR` | Command failed; byte 1 is the error code. |
| `0x02` | `RESP_CODE_CONTACTS_START` | Contact enumeration started. |
| `0x03` | `RESP_CODE_CONTACT` | One contact record. |
| `0x04` | `RESP_CODE_END_OF_CONTACTS` | Contact enumeration ended. |
| `0x05` | `RESP_CODE_SELF_INFO` | Device self-information. |
| `0x06` | `RESP_CODE_SENT` | Send accepted, with route/tag/timeout data. |
| `0x07` / `0x08` | `RESP_CODE_CONTACT_MSG_RECV` / `RESP_CODE_CHANNEL_MSG_RECV` | Queued legacy-format message. |
| `0x09` | `RESP_CODE_CURR_TIME` | Current device time. |
| `0x0A` | `RESP_CODE_NO_MORE_MESSAGES` | Offline queue is empty. |
| `0x0B` | `RESP_CODE_EXPORT_CONTACT` | Exported contact bytes. |
| `0x0C` | `RESP_CODE_BATT_AND_STORAGE` | Battery and storage values. |
| `0x0D` | `RESP_CODE_DEVICE_INFO` | Protocol and build information. |
| `0x0E` | `RESP_CODE_PRIVATE_KEY` | Exported identity key, when enabled. |
| `0x0F` | `RESP_CODE_DISABLED` | Requested sensitive feature is disabled. |
| `0x10` / `0x11` | `RESP_CODE_CONTACT_MSG_RECV_V3` / `RESP_CODE_CHANNEL_MSG_RECV_V3` | Queued message with SNR fields. |
| `0x12` | `RESP_CODE_CHANNEL_INFO` | Channel slot information. |
| `0x13` / `0x14` | `RESP_CODE_SIGN_START` / `RESP_CODE_SIGNATURE` | Signing capacity or completed signature. |
| `0x15` | `RESP_CODE_CUSTOM_VARS` | Custom-variable data. |
| `0x16` | `RESP_CODE_ADVERT_PATH` | Cached advertisement path. |
| `0x17` | `RESP_CODE_TUNING_PARAMS` | Tuning parameters. |
| `0x18` | `RESP_CODE_STATS` | Requested statistics subtype. |
| `0x19` | `RESP_CODE_AUTOADD_CONFIG` | Automatic-contact policy. |
| `0x1A` | `RESP_ALLOWED_REPEAT_FREQ` | Allowed repeat-frequency ranges. |
| `0x1B` | `RESP_CODE_CHANNEL_DATA_RECV` | Queued channel datagram. |
| `0x1C` | `RESP_CODE_DEFAULT_FLOOD_SCOPE` | Default flood-scope data. |
### Asynchronous push types
| Value | Firmware name | Description |
|---|---|---|
| `0x80` | `PUSH_CODE_ADVERT` | Advertisement received. |
| `0x81` | `PUSH_CODE_PATH_UPDATED` | A contact path changed. |
| `0x82` | `PUSH_CODE_SEND_CONFIRMED` | A sent message was acknowledged. |
| `0x83` | `PUSH_CODE_MSG_WAITING` | One or more offline frames are waiting. |
| `0x84` | `PUSH_CODE_RAW_DATA` | Raw application data received. |
| `0x85` / `0x86` | `PUSH_CODE_LOGIN_SUCCESS` / `PUSH_CODE_LOGIN_FAIL` | Server login result. |
| `0x87` | `PUSH_CODE_STATUS_RESPONSE` | Server status response. |
| `0x88` | `PUSH_CODE_LOG_RX_DATA` | Radio receive log data. |
| `0x89` | `PUSH_CODE_TRACE_DATA` | Completed trace data. |
| `0x8A` | `PUSH_CODE_NEW_ADVERT` | Newly stored contact advertisement. |
| `0x8B` | `PUSH_CODE_TELEMETRY_RESPONSE` | Telemetry response. |
| `0x8C` | `PUSH_CODE_BINARY_RESPONSE` | Binary request response. |
| `0x8D` | `PUSH_CODE_PATH_DISCOVERY_RESPONSE` | Path-discovery response. |
| `0x8E` | `PUSH_CODE_CONTROL_DATA` | Control/discovery data. |
| `0x8F` | `PUSH_CODE_CONTACT_DELETED` | Oldest contact was deleted while making room. |
| `0x90` | `PUSH_CODE_CONTACTS_FULL` | Contact storage is full. |
### Parsing Responses
@@ -700,6 +802,11 @@ def parse_device_info(data):
info['fw_build'] = data[8:20].decode('utf-8').rstrip('\x00').strip()
info['model'] = data[20:60].decode('utf-8').rstrip('\x00').strip()
info['ver'] = data[60:80].decode('utf-8').rstrip('\x00').strip()
if fw_ver >= 9 and len(data) >= 81:
info['client_repeat'] = data[80] != 0
if fw_ver >= 10 and len(data) >= 82:
info['path_hash_mode'] = data[81]
return info
```
@@ -801,10 +908,11 @@ Bytes 2-5: Tag / Expected ACK (4 bytes, little-endian)
Bytes 6-9: Suggested Timeout (32-bit little-endian, milliseconds)
```
**PACKET_ACK** (0x82):
**PACKET_SEND_CONFIRMED** (0x82):
```
Byte 0: 0x82
Bytes 1-6: ACK Code (6 bytes, hex)
Bytes 1-4: ACK code (32-bit little-endian)
Bytes 5-8: Round-trip time (32-bit little-endian, milliseconds)
```
### Error Codes
@@ -835,7 +943,8 @@ BLE implementations enqueue and deliver one protocol frame per BLE write/notific
1. **Command-Response Pattern**:
- Send command via RX characteristic
- Wait for response via TX characteristic (notification)
- Match response to command using sequence numbers or command type
- Match the response by the expected response type; frames do not carry a
general command sequence number
- Handle timeout (typically 5 seconds)
- Use command queue to prevent concurrent commands
@@ -902,7 +1011,7 @@ secret_hex = secret_16_bytes.hex()
# 2. Build SET_CHANNEL command
channel_name = "YourChannelName"
channel_index = 1 # Use 1-7 for private channels
channel_index = choose_unused_slot(max_channels)
command = build_set_channel(channel_index, channel_name, secret_16_bytes)
# 3. Send command
+33 -4
View File
@@ -74,6 +74,21 @@ powersaving on
powersaving off
```
On radios with RX duty-cycle support, WebConfig and the USB terminal also
expose the persisted RXPS setting:
```text
get radio.rxps
set radio.rxps off
set radio.rxps on
set radio.rxps level 8 preamble 16
set radio.rxps 65625 60000
```
Fresh Cascade-profile Full Companion builds start with RXPS on at level 8 and
a 16-symbol preamble. Changing it takes effect immediately and remains selected
after reboot.
Companion firmware defaults device power saving to on. Version 1.17.1.2 also
turns it on once when upgrading an older Companion preference file, including
one written by the short-lived default-off regression. After that one-time
@@ -83,7 +98,11 @@ On ESP32, enabling it lowers the CPU clock to 80 MHz, enables idle yielding,
and enables the configured GPS duty cycle. Disabling it restores the normal CPU
clock and keeps GPS awake. Full Companion transports remain available in both
states; WiFi modem sleep stays enabled when BLE is present because coexistence
requires it. The selected state is retained after reboot.
requires it. While a native-USB host is enumerated, the platform sleep attempt
is held off so USB CDC remains responsive; detaching the host releases that
guard. CPU, radio-modem, and GPS power-saving settings remain active, and USB
power from a charger alone does not create a Companion session. The selected
state is retained after reboot.
On the LilyGo T-Beam 1W Full Companion, press the physical `BOOT` button once
to turn the ESP32 WiFi radio and all WiFi services off or on. The screen confirms
@@ -123,9 +142,19 @@ itself.
| ESP32 | TCP 5002 | Local `ota`, `tempradio`, and `normalradio` console |
| nRF52 | USB mOTA mode | Host `.mota` folder from `motatool serve --serial` |
Binary Companion replies are broadcast through the multi-interface manager,
so use one active Companion application at a time. On nRF52, BLE remains
available while USB is in terminal or mOTA mode.
Delivery-required replies are returned only to the interface which supplied the
latest command. A contact-list stream keeps that route locked from
`CONTACTS_START` through `END_OF_CONTACTS`; commands waiting on another
interface are read after the stream finishes. Best-effort asynchronous
observations such as adverts remain broadcast so passive clients can refresh
their views. Companion session state is device-wide, so use one active
Companion application at a time. On nRF52, BLE remains available while USB is
in terminal or mOTA mode.
USB Binary output is queued as complete length-prefixed frames. Temporary CDC
or UART backpressure pauses the contact stream; a frame may drain through a
smaller hardware FIFO in ordered chunks, but its remainder is retained and no
later frame can interleave with it or cause it to be discarded.
When a BLE client requests pairing, a display-equipped build wakes the screen,
switches to the first home page, and keeps the active six-digit PIN visible
+105 -84
View File
@@ -31,7 +31,7 @@ A list of frequently-asked questions and answers for MeshCore
- [3.9.2. Q: **What determines a packet's path hash size?**](#392-q-what-determines-a-packets-path-hash-size)
- [3.9.3. Q: **How do I change my companion's path hash size?**](#393-q-how-do-i-change-my-companions-path-hash-size)
- [3.9.4. Q: **What does the CLI command `path.hash.mode` do on a repeater?**](#394-q-what-does-the-cli-command-pathhashmode-do-on-a-repeater)
- [3.9.5. Q: **Why use 2- or 3-byte path hash for adverts?**](#395-q-why-use-2--or-3-byte-path-hash-for-adverts)
- [3.9.5. Q: **Why use 2- or 3-byte path hash for adverts?**](#path-hash-size-adverts)
- [3.9.6. Q: **When can we move away from 1-byte path hash for channel and direct messages?**](#396-q-when-can-we-move-away-from-1-byte-path-hash-for-channel-and-direct-messages)
- [4. T-Deck Related](#4-t-deck-related)
- [4.1. Q: Is there a user guide for T-Deck, T-Pager, T-Watch, or T-Display Pro?](#41-q-is-there-a-user-guide-for-t-deck-t-pager-t-watch-or-t-display-pro)
@@ -94,7 +94,7 @@ MeshCore is free and open source:
- MeshCore is the routing and firmware etc., available on GitHub under MIT license
- There are clients made by the community, such as the web clients, these are free to use, and some are open source too
- The cross-platform mobile app developed by [Liam Cottle](https://liamcottle.net) for Android/iOS/PC etc. is free to download and use
- The cross-platform mobile app developed by [Liam Cottle](https://liamcottle.com/) for Android/iOS/PC etc. is free to download and use
- The T-Deck firmware is developed by Scott at Ripple Radios, the creator of MeshCore, is also free to flash on your devices and use
@@ -124,18 +124,19 @@ For an up-to-date list of supported devices, please go to <https://flasher.meshc
To use MeshCore without using a phone as the client interface, you can run MeshCore on a LilyGo T-Deck, T-Deck Plus, T-Pager, T-Watch, or T-Display Pro. MeshCore Ultra firmware running on these devices is a complete off-grid secure communication solution.
#### 1.2.2. Firmware
MeshCore has four firmware types that are not available on other LoRa systems. MeshCore has the following:
MeshCore firmware is organized by role and, for companions, by host transport.
The main roles are Companion, Repeater, Room Server, and Sensor. The tree also
contains specialized terminal-chat and KISS-modem builds. Availability depends
on the board; use the flasher or that board's PlatformIO environments as the
current source of truth.
#### 1.2.3. Companion Radio Firmware
Companion radios are for connecting to the Android app or web app as a messenger client. There are two different companion radio firmware versions:
1. **BLE Companion**
BLE Companion firmware runs on a supported LoRa device and connects to a smart device running the Android or iOS MeshCore client over BLE
<https://meshcore.io>
2. **USB Serial Companion**
USB Serial Companion firmware runs on a supported LoRa device and connects to a smart device or a computer over USB Serial running the MeshCore web client
<https://app.meshcore.nz>
Companion radios connect MeshCore client software to LoRa. Depending on the
board and build profile, the companion protocol can be exposed over BLE, USB
serial, Wi-Fi, or Ethernet. Some boards also provide a Full Companion profile
with additional local administration features. Check the exact build name and
transport before flashing. See <https://meshcore.io> and the web client at
<https://app.meshcore.nz>.
#### 1.2.4. Repeater
Repeaters are used to extend the range of a MeshCore network. Repeater firmware runs on the same devices that run client firmware. A repeater's job is to forward MeshCore packets to the destination device. It does **not** forward or retransmit every packet it receives, unlike other LoRa mesh systems.
@@ -185,13 +186,15 @@ The T-Deck firmware is free to download and most features are available without
### 2.3. Q: What frequencies are supported by MeshCore?
**A:** It supports the 868MHz range in the UK/EU and the 915MHz range in New Zealand, Australia, and the USA. Countries and regions in these two frequency ranges are also supported.
**A:** Supported frequencies depend on the radio hardware and the rules for the
country in which it operates. Common MeshCore hardware covers portions of the
433, 868, and 915 MHz ISM bands.
Use the smartphone client or the repeater setup feature on the web flasher to set your radios' RF settings by choosing the preset for your regions.
Recently, as of October 2025, many regions have moved to the "narrow" setting, aka using BW62.5 and a lower SF number (instead of the original SF11). For example, USA/Canada (Recommended) preset is 910.525MHz, SF7, BW62.5, CR5.
After extensive testing, many regions have switched or about to switch over to BW62.5 and SF7, 8, or 9. Narrower bandwidth setting and lower SF setting allow MeshCore's radio signals to fit between interference in the ISM band, provide for a lower noise floor, better SNR, and faster transmissions.
Use the current regional preset offered by the client or flasher, confirm it
with the local MeshCore community, and comply with local frequency, bandwidth,
duty-cycle, and power limits. Do not copy an old frequency from this FAQ.
Narrow presets commonly use BW62.5 with a lower spreading factor; their exact
frequency and SF are region-specific and can change as deployments coordinate.
If you have consensus from your community in your region to update your region's preset recommendation, please post your update request on the [#meshcore-app](https://discord.com/channels/1343693475589263471/1391681655911088241) channel on the [MeshCore Discord server ](https://meshcore.gg) to let Liam Cottle know.
@@ -284,7 +287,10 @@ Best practice is when you set up a new repeater, choose a public key that is not
The `<number>` unit is in seconds and is incremented by 4. `set agc.reset.interval 4` works well to cure deafness.
This is a very low-cost operation. AGC reset is done by simply setting `state = STATE_IDLE;` in function `RadioLibWrapper::resetAGC()` in `RadioLibWrappers.cpp`
The reset is skipped while a packet is pending, being received, or being
transmitted. When safe, `RadioLibWrapper::resetAGC()` warm-sleeps the radio,
returns the wrapper to idle so receive mode is re-armed, reapplies the cached RX
boost setting, and starts a fresh noise-floor calibration.
### 3.8. Q: How do I make my repeater an observer on the mesh?
@@ -328,6 +334,7 @@ Usage: `set path.hash.mode {0|1|2}`:
It is safe to set your 1.14+ repeaters to mode 1 or 2.
<a id="path-hash-size-adverts"></a>
### 3.9.5. Q: **Why use 2- or 3-byte path hash for adverts?**
A longer path hash helps tools like the LetsMesh.net Analyzer and MeshMapper disambiguate repeaters more reliably. With only 1 byte, the chance of different repeaters having the same first byte in their public key is high, making it harder to tell them apart in mesh network analysis. Since this only affects adverts, there's no downside. 2- and 3-byte adverts don't travel as far as 1-byte adverts, but it is not important for MeshCore nodes to hear a repeater's advert that is 21 or 32 hops away.
@@ -409,16 +416,26 @@ Unlock page: <https://buymeacoffee.com/ripplebiz/e/249834>
See here for packet-type:
<https://github.com/meshcore-dev/MeshCore/blob/main/src/Packet.h#L19>
```
#define PAYLOAD_TYPE_REQ 0x00 // request (prefixed with dest/src hashes, MAC) (enc data: timestamp, blob)
#define PAYLOAD_TYPE_RESPONSE 0x01 // response to REQ or ANON_REQ (prefixed with dest/src hashes, MAC) (enc data: timestamp, blob)
#define PAYLOAD_TYPE_TXT_MSG 0x02 // a plain text message (prefixed with dest/src hashes, MAC) (enc data: timestamp, text)
#define PAYLOAD_TYPE_ACK 0x03 // a simple ack #define PAYLOAD_TYPE_ADVERT 0x04 // a node advertising its Identity
#define PAYLOAD_TYPE_GRP_TXT 0x05 // an (unverified) group text message (prefixed with channel hash, MAC) (enc data: timestamp, "name: msg")
#define PAYLOAD_TYPE_GRP_DATA 0x06 // an (unverified) group datagram (prefixed with channel hash, MAC) (enc data: data_type, data_len, blob)
#define PAYLOAD_TYPE_ANON_REQ 0x07 // generic request (prefixed with dest_hash, ephemeral pub_key, MAC) (enc data: ...)
#define PAYLOAD_TYPE_PATH 0x08 // returned path (prefixed with dest/src hashes, MAC) (enc data: path, extra)
```
| Value | Payload |
|---|---|
| `0x00` | Request |
| `0x01` | Response |
| `0x02` | Plain text message |
| `0x03` | Acknowledgment |
| `0x04` | Node advertisement |
| `0x05` | Group text |
| `0x06` | Group datagram |
| `0x07` | Anonymous request |
| `0x08` | Returned path |
| `0x09` | Trace |
| `0x0A` | Multipart |
| `0x0B` | Control/discovery data |
| `0x0C` | OTA-over-LoRa data |
| `0x0D`-`0x0E` | Reserved |
| `0x0F` | Raw custom data |
See [Packet Format](packet_format.md) and [Payload Format](payloads.md) for the
maintained descriptions.
[Source](https://discord.com/channels/1343693475589263471/1343693475589263474/1350611321040932966)
@@ -474,8 +491,11 @@ So, it's a balancing act between speed of the transmission and resistance to noi
The Things Network is mainly focused on LoRaWAN, but the LoRa low-level stuff still checks out for any LoRa project
### 5.2. Q: Do MeshCore clients repeat?
**A:** No, MeshCore clients do not repeat. This is the core of MeshCore's messaging-first design. This is to avoid devices flooding the airwaves and create endless collisions, so messages sent aren't received.
In MeshCore, only repeaters and room servers with `set repeat on` repeat.
**A:** Companion clients do not repeat by default. Supported builds can opt into
bounded client repeating on permitted frequencies, and the emergency-channel
path has a bounded delayed relay behavior. Dedicated repeaters remain the
normal and recommended way to extend network coverage. Room servers can repeat
when explicitly configured, but separating the roles is generally preferable.
### 5.3. Q: What happens when a node learns a route via a mobile repeater, and that repeater is gone?
**A:** If you used to reach a node through a repeater and the repeater is no longer reachable, the client will send the message using the existing (but now broken) known path, the message will fail after 3 retries, and the app will reset the path and send the message as flood on the last retry by default. This can be turned off in settings. If the destination is reachable directly or through another repeater, the new path will be used going forward. Or you can set the path manually if you know a specific repeater to use to reach that destination.
@@ -525,7 +545,7 @@ Support Rastislav Vysoky (recrof)'s flasher website and the map website developm
Build instructions for MeshCore:
For Windows, first install WSL and Python+pip via: <https://plainenglish.io/blog/setting-up-python-on-windows-subsystem-for-linux-wsl-26510f1b2d80>
For Windows, first install WSL and Python+pip via: <https://plainenglish.io/python/setting-up-python-on-windows-subsystem-for-linux-wsl-26510f1b2d80>
(Linux, Windows+WSL) In the terminal/shell:
```
@@ -540,34 +560,32 @@ Then it should be the same for all platforms:
python3 -m venv meshcore
cd meshcore && source bin/activate
pip install -U platformio
git clone https://github.com/ripplebiz/MeshCore.git
git clone https://github.com/meshcore-dev/MeshCore.git
cd MeshCore
```
open platformio.ini and in `[arduino_base]` edit the `LORA_FREQ=867.5`
save, then run:
Choose an exact environment listed by `pio project config` or in the target
board's `variants/*/platformio.ini`. For example:
```
pio run -e RAK_4631_Repeater
pio run -e RAK_4631_repeater
```
then you'll find `firmware.zip` in `.pio/build/RAK_4631_Repeater`
The output is under `.pio/build/RAK_4631_repeater/`. Set the radio parameters to
your current regional preset after flashing; if changing build defaults, keep
that local change out of commits unless it is intended for every user.
### 5.10. Q: Are there other MeshCore related open source projects?
**A:** [Liam Cottle](https://liamcottle.net)'s MeshCore web client and MeshCore JavaScript library are open source under MIT license.
**A:** [Liam Cottle](https://liamcottle.com/)'s MeshCore web client and MeshCore JavaScript library are open source under MIT license.
Web client: <https://github.com/liamcottle/meshcore-web>
Javascript: <https://github.com/liamcottle/meshcore.js>
JavaScript: <https://github.com/meshcore-dev/meshcore.js>
### 5.11. Q: Does MeshCore support ATAK?
**A:** ATAK is not currently on MeshCore's roadmap.
MeshCore would not be best suited to ATAK because MeshCore:
- clients do not repeat and therefore you would need a network of repeaters in place
- will not have a stable path where all clients are constantly moving between repeaters
MeshCore clients would need to reset path constantly and flood traffic across the network which could lead to lots of collisions with something as chatty as ATAK.
This could change in the future if MeshCore develops a client firmware that repeats.
**A:** This repository does not ship or document an official ATAK integration.
An external integration must account for LoRa airtime, moving endpoints, stale
direct paths, and the collision cost of frequent flood fallback. Optional,
bounded Companion repeating does not turn every mobile client into a dedicated
repeater and does not remove those capacity constraints. Check current
community projects before designing a deployment.
[Source](https://discord.com/channels/826570251612323860/1330643963501351004/1354780032140054659)
@@ -590,47 +608,42 @@ Below are the instructions to flash firmware onto a supported LoRa device using
For ESP-based devices (e.g. Heltec V3) you need:
1. Download the firmware file from <https://flasher.meshcore.io>.
- Go to the website in a browser and find the section that has the firmware you need.
- Click the Download button, right-click on the file you need, for example:
- `Heltec_V3_companion_radio_ble-v1.7.1-165fb33.bin`
- Non-merged bin keeps the existing Bluetooth pairing database.
- `Heltec_v3_companion_radio_usb-v1.7.1-165fb33-merged.bin`
- Merged bin overwrites everything including the bootloader and existing Bluetooth pairing database, but keeps configurations.
- Right-click on the file name and copy the link. Here is an example: `https://flasher.meshcore.io/releases/download/companion-v1.7.1/Heltec_v3_companion_radio_ble-v1.7.1-165fb33.bin`
- Run:
- `wget https://flasher.meshcore.io/releases/download/companion-v1.7.1/Heltec_v3_companion_radio_ble-v1.7.1-165fb33.bin` to download the firmware file for your device type or the version you need: USB, BLE, Repeater, Room Server, merged bin or non-merged bin.
- If the above wget command only downloads a very small file (10K bytes instead of more than 100K byte), use this command instead:
- `wget --user-agent="Mozilla/5.0" --content-disposition "https://flasher.meshcore.io/releases/download/companion-v1.7.1/Heltec_v3_companion_radio_usb-v1.7.1-165fb33.bin"`
- Select the exact board and role. Artifact names and release URLs include a
changing version and commit, so copy the current download URL instead of
using an example URL from a guide.
- A non-merged application image is for an already compatible bootloader
and partition table. A merged image also contains low-level flash data.
Back up settings before either operation; do not assume a merged flash
preserves configuration, pairing data, or a previous partition layout.
- To download a copied URL from the shell, use
`curl -L '<copied-url>' -o <firmware>.bin`.
2. Confirm the `ttyXXXX` device path on your Raspberry Pi.
- Go to the `/dev` directory and run the `ls` command to find your device path.
- It is usually `/dev/ttyUSB0` for ESP devices.
3. Install esptool from the shell.
- `pip install esptool --break-system-packages`
- Run `ls /dev/ttyACM* /dev/ttyUSB* 2>/dev/null` before and after connecting
the board. ESP devices can appear under either name.
3. Install esptool in a virtual environment.
- `python3 -m venv .venv && . .venv/bin/activate`
- `python -m pip install --upgrade esptool`
4. Flash the firmware.
- For non-merged bin:
- `esptool.py -p /dev/ttyUSB0 --chip esp32-s3 write_flash 0x10000 <non-merged_firmware>.bin`
- `python -m esptool --port /dev/ttyUSB0 --chip auto write-flash 0x10000 <non-merged-firmware>.bin`
- For merged bin:
- `esptool.py -p /dev/ttyUSB0 --chip esp32-s3 write_flash 0x00000 <merged_firmware>.bin`
- `python -m esptool --port /dev/ttyUSB0 --chip auto write-flash 0x00000 <merged-firmware>.bin`
**Instructions for nRF devices:**
For nRF devices (e.g. RAK, Heltec T114) you need the following:
1. Download the firmware file from <https://flasher.meshcore.io>.
- Go to the website in a browser and find the section that has the firmware you need.
- You need the ZIP version for the adafruit flash tool below.
- Click the Download button, right-click on the ZIP file, for example:
- `RAK_4631_companion_radio_ble-v1.7.1-165fb33.zip`
- Right-click on the file name and copy the link. Here is an example: `https://flasher.meshcore.io/releases/download/companion-v1.7.1/RAK_4631_companion_radio_ble-v1.7.1-165fb33.zip`
- Run:
- `wget https://flasher.meshcore.io/releases/download/companion-v1.7.1/RAK_4631_companion_radio_ble-v1.7.1-165fb33.zip` to download the firmware file for your device type or the version you need: USB, BLE, Repeater, Room Server, ZIP file only.
- Select the exact board and role, and download its current ZIP package.
Do not rename a package from another board or reuse a static versioned URL.
2. Confirm the `ttyXXXX` device path on your Raspberry Pi.
- Go to the `/dev` directory and run the `ls` command to find your device path.
- It is usually `/dev/ttyACM0` for nRF devices.
3. Install adafruit-nrfutil.
- `pip install adafruit-nrfutil --break-system-packages`
- Run `ls /dev/ttyACM* /dev/ttyUSB* 2>/dev/null`; nRF bootloaders commonly
appear as `/dev/ttyACM0`, but the number can change.
3. In a virtual environment, install adafruit-nrfutil:
- `python3 -m venv .venv && . .venv/bin/activate`
- `python -m pip install --upgrade adafruit-nrfutil`
4. Flash the nRF device.
- `adafruit-nrfutil --verbose dfu serial --package RAK_4631_companion_radio_usb-v1.7.1-165fb33.zip -p /dev/ttyACM0 -b 115200 --singlebank --touch 1200`
- `adafruit-nrfutil --verbose dfu serial --package <firmware-package>.zip -p /dev/ttyACM0 -b 115200 --singlebank --touch 1200`
To manage a repeater or room server connected to a Pi over USB serial using shell commands, you need to install `picocom`. To install `picocom`, run the following command:
@@ -663,7 +676,7 @@ Both the Windows and Mac versions of the client app are fully unlocked and are f
- The Comms Channel on YouTube: <https://www.youtube.com/watch?v=guDoKGs02Us>
- MeshCore Advantages by MCarper: <https://github.com/mikecarper/meshfirmware/blob/main/MeshCoreAdvantages.md>
- MeshCore vs Meshtastic by austinmesh.org: <https://www.austinmesh.org/learn/meshcore-vs-meshtastic>
- MeshCore vs Meshtastic by austinmesh.org: <https://www.austinmesh.org/about/meshcore-vs-meshtastic/>
---
@@ -744,7 +757,9 @@ Allow the browser user on it:
12. If it fails, try toggling Bluetooth on your phone. If that doesn't work, try rebooting your phone. If you keep getting failures at the "Enabling Bootloader" step, try forgetting the nRF board in your iOS or Android device's Bluetooth settings and re-pair it through the DFU app.
13. Wait for the update to complete. It can take a few minutes.
14. It is strongly recommended that you install and use the OTAFIX bootloader at <https://github.com/oltaco/Adafruit_nRF52_Bootloader_OTAFIX>.
15. To update a companion node over OTA, it must be running companion firmware v1.15 or greater.
15. A companion must be running a build that exposes `start ota`; consult the
current flasher release notes rather than relying on a version threshold in
this FAQ.
16. Please see the MeshCore Blog for additional information on OTA firmware flashing:
- <https://blog.meshcore.io/2026/04/06/otafix-bootloader>
- <https://blog.meshcore.io/2026/04/02/nrf-ota-update>
@@ -760,7 +775,9 @@ After this bootloader is flashed onto the device, you can trigger an over-the-ai
### 7.2. Q: How to update ESP32-based devices over the air?
**A:** For ESP32-based devices (e.g. Heltec V3):
1. On <https://flasher.meshcore.io>, download the **non-merged** version of the firmware for your ESP32 device (e.g. `Heltec_v3_repeater-v1.6.2-4449fd3.bin`, no `"merged"` in the file name).
1. On <https://flasher.meshcore.io>, download the current **non-merged**
application image for the exact ESP32 board and role (no `merged` in the
filename).
2. From the MeshCore app, log in remotely to the repeater you want to update with admin privileges.
3. Go to the Command Line tab, type `start ota` and hit enter.
4. You should see `OK` to confirm the repeater device is now in OTA mode.
@@ -779,14 +796,18 @@ Refer to <https://github.com/oltaco/Adafruit_nRF52_Bootloader_OTAFIX> for the la
Currently, the following boards are supported:
- Elecrow ThinkNode M1, M3, and M6
- Heltec Automation Mesh Node T114 / HT-nRF5262
- LilyGO T-Echo
- Minewsemi MX25LE01
- Nologo ProMicro NRF52840 (aka SuperMini NRF52840)
- RAK4631
- RAK WisMesh Tag
- Seeed Studio SenseCAP Card Tracker T1000-E
- Seeed Studio SenseCAP Solar Node P1
- Seeed Studio Wio Tracker L1
- Seeed Studio XIAO nRF52840 BLE
- Seeed Studio XIAO nRF52840 BLE SENSE
- RAK 4631
- RAK WisMesh Tag (new 28/11/2025)
### 7.4. Q: Are the MeshCore logo and font available?
**A:** Yes, it is on the MeshCore GitHub repo here: <https://github.com/meshcore-dev/MeshCore/tree/main/logo>
@@ -841,7 +862,7 @@ For companion radios, you can set these radios' transmit power in the smartphone
| Device / Model | Region / Description | In-App Setting (dBm) | Target Radio Output | Notes |
|:-----------------------------------------------------------------------------------|:------------------------------------|:---------------------|:-----------------------|:-----------------------------------------------------------------------------------------------------------------------------------------------|
| **Station G2** <br> [Reference](https://wiki.uniteng.com/en/meshtastic/station-g2) | US915 Max Output | 19 dBm | 36.5 dBm (4.46W) | |
| **Station G2** <br> [Reference](https://wiki.bqvoy.com/en/meshtastic/station-g2) | US915 Max Output | 19 dBm | 36.5 dBm (4.46W) | |
| | US915 Max at 1dB compression point | 16 dBm | 35 dBm (3.16W) | 1dB compression point |
| | EU868 Max at 1dB compression point | 15 dBm | 34.5 dBm (2.82W) | 1dB compression point |
| | US915 1W Output | 10 dBm | 1W | Refer to your local government's requirements |
+12 -6
View File
@@ -5,8 +5,8 @@ physical failure of the withdrawn 26-step migration and the corrected
v1.17.01 test candidate. Its runner blocks the withdrawn chain and requires an
explicit lab-only gate for the corrected chain until physical testing passes.
[`tools/lora_ota/lora_ota.sh`](../tools/lora_ota/lora_ota.sh) and
[`tools/lora_ota/lora_ota.ps1`](../tools/lora_ota/lora_ota.ps1) automate a
[`tools/lora_ota/lora_ota.sh`](https://github.com/mikecarper/MeshCore/blob/keymindCascade/tools/lora_ota/lora_ota.sh) and
[`tools/lora_ota/lora_ota.ps1`](https://github.com/mikecarper/MeshCore/blob/keymindCascade/tools/lora_ota/lora_ota.ps1) automate a
MeshCore LoRa firmware update from a release `.zip` or ready `.mota`. They
identify the destination, validate the hardware and running firmware, prepare
the right container, move the participating nodes to a temporary radio
@@ -168,13 +168,18 @@ on the normal channel. The script runs these authenticated checks itself:
```text
ota status
get bootloader.ver
ota self
ota stats
```
For nRF52, `ota self` must report `bootloader: apply OK` or
`bootloader: SD apply OK`. The script also checks the reported bootloader ABI
and codec mask against the selected package.
The script uses `get bootloader.ver` to distinguish ESP32 from nRF52 and, for
nRF52, report the installed bootloader version. It then requires `ota self` to
report `bootloader: apply OK` or `bootloader: SD apply OK` and checks the
reported bootloader ABI and codec mask against the selected package. If the
version command is unavailable on older firmware, the script warns and falls
back to the legacy `ota self` platform marker. If an nRF52 bootloader lacks
the required capabilities, install the exact-board OTAFIX bootloader first.
The default TempRadio tuple is:
@@ -401,7 +406,8 @@ the destination.
1. Validate the input paths and host tools, then prove the source is either an
OTA-enabled raw CLI or a source-only full Companion control interface.
2. Authenticate to the target and query its target ID, hardware, running body
hash, version, and nRF52 bootloader capabilities.
hash, firmware version, bootloader version, and nRF52 bootloader
capabilities.
3. Select or build one compatible mOTA and verify all block hashes, Merkle
root, full-image hash where applicable, identity fields, signature, codec,
base, and the firmware's 1024-byte maximum block size.
+3 -3
View File
@@ -216,6 +216,6 @@ DEBUG: PWRMGT: LPCOMP wake configured (AIN7, ref=3/8 VDD)
## References
- [nRF52840 Product Specification - POWER](https://infocenter.nordicsemi.com/topic/ps_nrf52840/power.html)
- [nRF52840 Product Specification - LPCOMP](https://infocenter.nordicsemi.com/topic/ps_nrf52840/lpcomp.html)
- [SoftDevice S140 API - Power Management](https://infocenter.nordicsemi.com/topic/sdk_nrf5_v17.1.0/group__nrf__sdm__api.html)
- [nRF52840 Product Specification - POWER](https://docs.nordicsemi.com/bundle/ps_nrf52840/page/power.html)
- [nRF52840 Product Specification - LPCOMP](https://docs.nordicsemi.com/bundle/ps_nrf52840/page/lpcomp.html)
- [SoftDevice S140 API - Power Management](https://docs.nordicsemi.com/bundle/sdk_nrf5_v17.1.0/page/group__nrf__sdm__api.html)
+1 -1
View File
@@ -17,7 +17,7 @@ Once you have a working app/project, you need to be able to demonstrate it exist
| 0000 - 00FF | -reserved for internal use- | |
| 0100 | MeshCore Open | zsylvester@monitormx.com - https://github.com/zjs81/meshcore-open |
| 0110 - 011F | Ripple | ripple_biz@protonmail.com - https://buymeacoffee.com/ripplebiz |
| 0120 | MCO Advanced | most.original.address@gmail.com - https://hdden.ru/MCOa/ |
| 0120 | MCO Advanced | most.original.address@gmail.com - https://github.com/HDDen/meshcore-open/tree/rename-mco-advanced |
| FF00 - FFFF | -reserved for testing/dev- | |
(add rows, inside the range 0100 - FEFF for custom apps)
+16 -8
View File
@@ -164,10 +164,12 @@ similar board's bootloader.
Before preparing or downloading a LoRa update, run this on the destination:
```text
get bootloader.ver
ota self
```
Continue only if the reply includes:
The first command identifies the installed nRF52 bootloader. Continue only if
the `ota self` reply includes:
```text
bootloader: apply OK
@@ -272,14 +274,19 @@ ota status
ota ls
```
Discovery is asynchronous. Wait a few seconds and run `ota ls` again if the list is initially empty. Select
the entry marked `[yours]`: it should say `full` for the ESP32 path or `delta` for the nRF52 path. If it is
entry 1, run:
Discovery is asynchronous. `ota ls` says `refreshing`; wait a few seconds and run it again even if it first
shows an older row. Select `[same target]`: it should say `full` for the ESP32 path or `delta` for the nRF52
path. Do not select `[unsupported]` (for example, a source's self-served full image on a single-slot nRF52).
Use the row's stable eight-hex manifest ID rather than its changing list position:
```text
ota pull 1 flash
ota pull 838B8169 flash
```
If an internal-flash nRF52 reports `no EndF`, only a row marked `[rescue]` is eligible. Current rescue-capable
firmware requires `ota pull <mid8> flash rescue`, followed after completion by
`ota rescue install <base_hash16>`. Older running firmware without those commands must be recovered over USB.
Monitor the transfer:
```text
@@ -322,9 +329,10 @@ ota status
- **The CLI says LoRa OTA is not included:** that firmware does not contain the LoRa OTA feature.
If it is the source or destination, install a supported `-ota-` build over WiFi or USB first. An intermediate
repeater does not need the OTA CLI and can relay opaquely while its matching `tempradio` window is active.
- **The update is marked `[other hw]`:** it is for a different board or firmware role. Do not install it.
- **An nRF52 node does not list a full update:** this is intentional for internal-flash targets. The
MeshTower V2 microSD target accepts full images with its matching SD-aware bootloader.
- **The update shows another environment or a raw `[hw XXXXXXXX]`:** it is for a different board or firmware
role. Do not install it.
- **An internal-flash nRF52 marks a full update `[unsupported]`:** it can install only an in-place delta.
The MeshTower V2 microSD target accepts full images with its matching SD-aware bootloader.
- **nRF52 reports no bootloader apply support:** install the exact-board in-place-delta OTAFIX bootloader
before trying LoRa OTA.
- **nRF52 reports a base mismatch:** the file passed to `--base` is not the exact application running on
+6 -5
View File
@@ -264,15 +264,16 @@ then run:
```text
ota ls
ota ls 2
ota get 1 flash
ota get <mid8> flash
ota status
```
Use `ota ls 2`, `ota ls 3`, and so on when the source advertises more than the
two rows that fit in one remote CLI reply. The update numbers are global across
pages, so select the displayed number for the desired target instead of assuming
it is always `1`. A receiver retains the complete protocol catalog and verifies
every transferred block. It still applies its normal target, hardware, codec,
two rows that fit in one remote CLI reply. Each row includes a stable
eight-hex-digit manifest ID. Use that ID instead of a list number, because
asynchronous catalog refreshes can reorder rows between the list and pull
commands. A receiver retains the complete protocol catalog and verifies every
transferred block. It still applies its normal target, hardware, codec,
signature, and installation checks. The SD
repeater may advertise images for many hardware families; it never installs
those archive files merely because it serves them.
+22 -9
View File
@@ -2,8 +2,10 @@
This is the **single source of truth** for MeshCore's over-the-air firmware update system ("mOTA"). It is
written for developers who want to implement an interoperable peer (server, fetcher, relay, or host tool)
in another codebase or project. Everything below is implemented and hardware-verified in this repository;
where a section names a source file, that file is the authoritative reference for byte-level details.
in another codebase or project. Everything below is implemented in this repository and covered by host,
simulation, build, or hardware tests as noted in the relevant section. Hardware qualification is target- and
chain-specific; do not infer it from implementation alone. Where a section names a source file, that file is
the authoritative reference for byte-level details.
> **Just want to update your node?** See the plain-language [OTA user guide](ota_user_guide.md) - this
> document is the technical/wire specification.
@@ -486,7 +488,7 @@ OTA_LEAVES: manifest_id[4] frag_idx(1) frag_total(1) bytes[] # up
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*
build (e.g. a fresh recompile) is ~99% identical. So `motatool serve --seed <similar.mota>` stages that
build's payload into the destination `.part`, and `ota pull <#> folder validate` makes the fetcher (1) bulk-
build's payload into the destination `.part`, and `ota pull <mid8> 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
anti-burst rule as `OTA_MANIFEST`), (2) recompute the merkle root from them and check it equals the
manifest root (authenticate), then (3) keep every seeded block whose leaf matches and pull full `OTA_DATA`
@@ -584,10 +586,12 @@ transmission per hop.
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`.
beacon without the string being transmitted. Unknown IDs show as raw `hw XXXXXXXX` / `N/A` values.
- **`fw_version`:** packed comparable uint32 (`MAJOR<<24 | MINOR<<16 | PATCH<<8 | pre`); also self-described
in EndF. `ota ls` decodes it for display and flags each update `[yours]` / `[other hw]` / `[?]` by
comparing the advertised `target_id` to the node's own.
in EndF. `ota ls` prints the stable eight-hex manifest ID and uses `[same target]`, `[unsupported]`, or
`[rescue]` after combining target equality with the local codec, bootloader, and EndF preflight. A known
different target is rendered by environment name; an unknown/unset target remains raw or `?`. Target
equality is routing information, not by itself an assertion that an image is safe to install.
- **`hw_id`:** 32-byte NUL-padded ASCII hardware tag inside the signed head. The applier refuses a `.mota`
whose `hw_id` differs from the device's own tag (empty on either side = permissive). Brick-safety
independent of signature.
@@ -612,7 +616,7 @@ verify everything). The serve side (`OtaManager`) keeps a lightweight registry o
resident "views": `view0` (its own firmware) and one on-demand view loaded from a source when a request
targets an external mota. Every fetch message carries `manifest_id`, so dispatch is a registry lookup.
The same host-folder link is also a **pull destination** (the reverse direction): `ota pull <#> folder`
The same host-folder link is also a **pull destination** (the reverse direction): `ota pull <mid8> folder`
fetches a `.mota` off the mesh and streams it onto the host as `<mid>.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
@@ -682,6 +686,11 @@ 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.
The attach reply and bare `ota folder` report `host=advertised/offered`. The registry is RAM-bounded
(`OTA_MAX_SERVE`, with the node's own firmware consuming one slot), so a host may correctly index more valid
files than this particular firmware can advertise. Omitted entries are now reported instead of silently
disappearing. Operators should split a large chain or use a higher-capacity/SD seeder when the two counts differ.
**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
@@ -708,7 +717,8 @@ the recommended user-facing forms. Output is plain-language (a user-facing guide
ota help | ? | h list the commands
ota status | st (or bare `ota`) plain-language: running fw, the one fetch session (state/%/id), serving, keys
ota ls | neighbors | nbrs | updates | n [page] paged updates (queries sources; rows arrive async via OTA_HAVE)
ota get | pull | download <#|mid8> fetch a chosen mOTA (manual; works regardless of autofetch)
ota get | pull | download <mid8|#index> flash [rescue] | folder [validate]
fetch by stable mid8 (preferred) or current page index
ota install | apply | applydelta verify + approve + (ESP32) apply / (nRF52) reboot-to-bootloader
ota rescue install <base_hash16> internal-flash nRF52 only: recover from failed app-side EndF validation
ota cancel | drop | stop drop the current fetch session (frees the slot)
@@ -722,6 +732,7 @@ ota dev ... bring-up helpers (stage/recv/serve/verify)
---
<a id="12-apply-bootloader-contract"></a>
## 12. Apply & bootloader contract
- **ESP32 (A/B):** applied in-firmware via the detools decoder into the inactive OTA slot
@@ -745,7 +756,9 @@ ota dev ... bring-up helpers (stage/recv/serve/verify)
it still requires USB recovery if that app does not already contain this command.
A chain intended to cross historical firmware must introduce this command in its first bridge and retain
it in every later bridge. Manual pulls still use the build-provided target ID when app-side EndF parsing
fails, so a rescue-capable bridge can fetch its exact successor before invoking the guarded command.
fails, so a rescue-capable bridge can fetch its exact successor before invoking the guarded command. Such
a node must acknowledge the condition up front with `ota pull <mid8> flash rescue`; an ordinary flash pull
refuses before altering staged data. Firmware that predates both rescue commands still requires USB recovery.
- **MeshTower V2 SD nRF52:** the application stores a contiguous `/meshcore-ota.mota` on microSD and
publishes its raw sector range in a checksummed handoff record outside the MBR partition. The matching
bootloader reads the card without mounting FAT, supports either a full image or an in-place delta,
+43 -26
View File
@@ -4,12 +4,13 @@ This guide is for **node operators**: how to update your MeshCore device's firmw
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 download and installation are present only in supported Keymind artifacts whose filename contains
`-ota-`. Use an `-ota-` build on the source and receiver. Intermediate repeaters do not need OTA-enabled
firmware: current repeater builds transport OTA floods opaquely, subject to their normal forwarding filters,
duplicate checks, and flood limits. OTA radio traffic is accepted, generated, and relayed only while
`tempradio` is actually running on that node. Every source, receiver, and intermediate repeater must therefore
have an overlapping temporary-radio window.
LoRa OTA download and installation are present only in supported Keymind destination artifacts whose filename
contains `-ota-`; the receiver must already be running one of those install-capable builds. A source can be an
OTA-enabled infrastructure node or a source-only Full Companion backed by `motatool`. Intermediate repeaters
do not need OTA-enabled firmware: current repeater builds transport OTA floods opaquely, subject to their normal
forwarding filters, duplicate checks, and flood limits. OTA radio traffic is accepted, generated, and relayed
only while `tempradio` is actually running on that node. Every source, receiver, and intermediate repeater must
therefore have an overlapping temporary-radio window.
The recommended temporary OTA settings use 250 kHz bandwidth, SF5, CR5, and a 120-minute window. For a
North American node currently configured for 909.950 MHz, run this on every participating node:
@@ -96,38 +97,46 @@ ota ls 2 # page 2 when more than two updates are available
```
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:
each with a temporary **number**, a stable eight-hex **manifest ID**, 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 1/1 (2 src) - `ota get <#>`:
1) v1.2.3 delta [yours] 3n 5s
2) v1.2.0 full [other hw] 1n 12s [downloading]
Updates 1/1 (2 src; refreshing):
1) 838B8169 v1.2.3 delta [same target] 3n 5s
2) BF0AB0C4 v1.2.0 full [unsupported] 1n 12s
```
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.
- **[same target]** - the advertised target ID matches this hardware-and-role build. Download and apply
still enforce codec, bootloader, signed hardware tag, base hash, and integrity checks.
- **[unsupported]** - the target may match, but this build or its bootloader cannot apply that codec. A common
example is the source node's self-served **full** image on a single-slot nRF52, which needs an in-place delta.
- **[rescue]** - an installable in-place nRF52 delta for the same target, but this running firmware has no
valid app-side EndF. It requires the explicit rescue download and install flow below.
- **[name]** - a different known board or role (for example `[ProMicro_companion_radio_usb]`). 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
downloaded yet; this is just looking around. Two updates fit in each remote CLI reply; use `ota ls 2`,
`ota ls 3`, and so on for later pages. The displayed update numbers remain global across pages.
downloaded yet; this is just looking around. `refreshing` means the command has just sent asynchronous
catalog queries, so run it again even when an older row is already visible. Two updates fit in each remote
CLI reply; use `ota ls 2`, `ota ls 3`, and so on for later pages. Catalog rows can change while replies arrive,
so use the displayed manifest ID for scripts and important operations rather than a numeric position.
(`ota neighbors` / `ota updates` also work.)
### 3. Download an update
Pick one from the list by its **number**, and say **where** to put it:
Pick one from the list by its stable **manifest ID** (a number also works for interactive use), and say
**where** to put it:
```
ota pull 1 flash # stage it in this node's flash, to install here
ota pull 1 folder # capture it onto a connected motatool folder as <id>.mota (don't install here)
ota pull 1 folder validate # same capture, warm-started from a motatool --seed build (much faster; below)
ota pull 838B8169 flash # stage it in this node's flash, to install here
ota pull 838B8169 folder # capture it onto a connected motatool folder as <id>.mota
ota pull 838B8169 folder validate # warm-start capture from a motatool --seed build (much faster; below)
```
The destination is required - `ota pull 1` on its own just shows the choices. **`flash`** is always
The destination is required - `ota pull 838B8169` on its own just shows the choices. **`flash`** is always
available (stage here, then `ota install`). **`folder`** appears only while a `motatool serve` link is
attached (it shows the link, e.g. `folder: tcp 192.168.4.5`); it streams the firmware straight onto the
host folder - nothing is staged on this node. That's how you grab an **exact copy of another device's
@@ -185,17 +194,20 @@ After it reboots, run `ota status` to confirm the new version.
- A download that stalls or gets interrupted just **resumes** later, or you can `ota cancel` and try again.
- An internal-flash **nRF52** that still runs but reports `no EndF` can use the pre-provisioned rescue path
if its physical EndF is intact and only app-side validation is failing. Fetch the exact in-place delta,
obtain its 16-hex-digit `base_hash` from the package metadata, then run:
if its physical EndF is intact and only app-side validation is failing. Fetch the exact `[rescue]`
in-place delta with an explicit acknowledgement, obtain its 16-hex-digit `base_hash` from the package
metadata, then run:
```text
ota pull <mid8> flash rescue
# wait for ota status to say ready to install
ota rescue install <base_hash16>
```
This is not a force option. It refuses a normally valid EndF, a different package hash, hardware or
target mismatch, corrupt payload, and invalid/untrusted signatures. The bootloader independently hashes
the running app and rejects a wrong base before writing the app. If the physical EndF is absent or this
command was not already in the running firmware, recover over USB.
the running app and rejects a wrong base before writing the app. If the physical EndF is absent or the
rescue commands were not already in the running firmware, recover over USB.
Release chains should put this command in their first bridge and keep it in every bridge after that.
- 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
@@ -279,6 +291,11 @@ remote area.
`ota get` them like any other. (A WiFi node prints its IP + seeder port to the serial log on connect.
Details: <https://github.com/vk496/motatool>.)
Check the device's attach reply or run `ota folder`: `host=X/Y` means the firmware is advertising `X` of
the `Y` valid entries reported by the host. Serve registries are deliberately RAM-bounded on smaller builds,
and the node's own firmware also consumes a slot. If `X < Y`, split the chain across seeders/folders or use
a higher-capacity seeder; `motatool` saying that every file is valid does not mean every file fit on-device.
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.
On a FULL repeater or room server, run `start webconfig` first if WiFi is not
@@ -304,7 +321,7 @@ 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](https://github.com/mikecarper/MeshCore/blob/keymindCascade/tools/mota/README.md)
(this is for people distributing updates, not everyday operators).
---
@@ -317,7 +334,7 @@ that only contains what changed). You get them by:
| See my firmware + any download | `ota status` (or just `ota`) |
| Admin: ids/hashes + serving + policy | `ota stats` (admin-only remotely) |
| Find updates nearby | `ota ls` |
| Download update #1 for installation | `ota get 1 flash` |
| Download a listed update for installation | `ota get <mid8> flash` |
| Cancel a download | `ota cancel` |
| Install a finished download | `ota install` |
| Recover app-side `no EndF` on internal nRF52 | `ota rescue install <base_hash16>` |
+4 -3
View File
@@ -9,7 +9,8 @@ This document describes the MeshCore packet format.
## Version 1 Packet Format
This is the protocol level packet structure used in MeshCore firmware v1.12.0
This is the current version-1 MeshCore wire packet structure. Older firmware may
support only a subset of the path encodings and payload types described here.
```
[header][transport_codes(optional)][path_length][path][payload]
@@ -35,7 +36,7 @@ This is the protocol level packet structure used in MeshCore firmware v1.12.0
- `0x09`/`0b1001` - `PAYLOAD_TYPE_TRACE` - Trace a path, collecting SNR for each hop
- `0x0A`/`0b1010` - `PAYLOAD_TYPE_MULTIPART` - Packet is part of a sequence of packets
- `0x0B`/`0b1011` - `PAYLOAD_TYPE_CONTROL` - Control packet data (unencrypted)
- `0x0C`/`0b1100` - reserved
- `0x0C`/`0b1100` - `PAYLOAD_TYPE_OTA` - OTA-over-LoRa firmware distribution
- `0x0D`/`0b1101` - reserved
- `0x0E`/`0b1110` - reserved
- `0x0F`/`0b1111` - `PAYLOAD_TYPE_RAW_CUSTOM` - Custom packet (raw bytes, custom encryption)
@@ -137,7 +138,7 @@ Examples:
| `0x09` | `PAYLOAD_TYPE_TRACE` | Trace a path, collecting SNR for each hop |
| `0x0A` | `PAYLOAD_TYPE_MULTIPART` | Packet is part of a sequence of packets |
| `0x0B` | `PAYLOAD_TYPE_CONTROL` | Control packet data (unencrypted) |
| `0x0C` | reserved | reserved |
| `0x0C` | `PAYLOAD_TYPE_OTA` | OTA-over-LoRa firmware distribution |
| `0x0D` | reserved | reserved |
| `0x0E` | reserved | reserved |
| `0x0F` | `PAYLOAD_TYPE_RAW_CUSTOM` | Custom packet (raw bytes, custom encryption) |
+51 -16
View File
@@ -2,20 +2,16 @@
Inside each [MeshCore Packet](./packet_format.md) is a payload, identified by the payload type in the packet header. The types of payloads are:
* Node advertisement.
* Acknowledgment.
* Returned path.
* Request (destination/source hashes + MAC).
* Response to REQ or ANON_REQ.
* Plain text message.
* Anonymous request.
* Group text message (unverified).
* Group datagram (unverified).
* Multi-part packet
* Control data packet
* Custom packet (raw bytes, custom encryption).
* Request, response, and plain text (`0x00`-`0x02`).
* Acknowledgment and node advertisement (`0x03`-`0x04`).
* Group text and group datagram (`0x05`-`0x06`).
* Anonymous request and returned path (`0x07`-`0x08`).
* Trace, multipart, control, and OTA (`0x09`-`0x0C`).
* Custom raw data (`0x0F`).
This document defines the structure of each of these payload types.
This document describes the shared payload envelopes implemented by the core.
Application-specific request, response, control, and custom bodies can add their
own formats.
NOTE: all 16 and 32-bit integer fields are Little Endian.
@@ -91,9 +87,9 @@ Returned path messages provide a description of the route a packet took from the
| Field | Size (bytes) | Description |
|-------------|--------------|----------------------------------------------------------------------------------------------------------------------|
| path length | 1 | length of next field |
| path | see above | a list of node hashes (one byte each) |
| extra type | 1 | extra, bundled payload type, eg., acknowledgement or response. Same values as in [Packet Format](./packet_format.md) |
| path descriptor | 1 | low 6 bits are the hash count; high 2 bits encode hash size minus one |
| path | count × size | encoded node-hash prefixes, each 1-3 bytes; the four-byte code is reserved |
| extra type | 1 | low nibble is the bundled payload type, such as acknowledgment or response; high nibble is reserved |
| extra | rest of data | extra, bundled payload content, follows same format as main content defined by this document |
### Request
@@ -297,6 +293,45 @@ The data contained in the ciphertext uses the format below:
| pubkey | 8 or 32 | node's ID (or prefix) |
## Trace
Trace packets use direct routing. Their normal direct-path header is empty;
instead, the intended route follows a fixed nine-byte trace header in the
payload. Each forwarding node appends its received SNR multiplied by four to
the packet's route-accumulator field.
| Field | Size (bytes) | Description |
|-------------|--------------|-------------|
| tag | 4 | Sender-selected trace identifier. |
| auth code | 4 | Application-defined authentication/correlation value. |
| flags | 1 | Low two bits encode the route hash size. Current senders use the legacy `1 << value` interpretation (1, 2, 4, or 8 bytes); receivers also accept the packed 1-4-byte interpretation where it is unambiguous. |
| route | rest | Concatenated node-hash prefixes for the requested direct route. |
At the destination, the application receives the tag, auth code, flags,
accumulated SNR bytes, and original route bytes. Trace therefore differs from a
normal direct packet whose path is carried entirely in the packet route field.
## Multipart
The first payload byte identifies the inner payload and how many packets remain:
| Field | Size (bytes) | Description |
|---|---|---|
| remaining + type | 1 | Upper nibble is the remaining-packet count; lower nibble is the inner payload type. |
| inner data | rest | Data for the inner payload type. |
The core currently creates and consumes multipart acknowledgments. For that
form, the low nibble is `0x03` and the next four bytes are the acknowledgment
CRC. Other multipart inner types are reserved for application or future use.
## OTA
An OTA payload contains one OTA protocol message and is normally sent by flood.
Its message types, integrity fields, and transfer state are defined in the
[OTA-over-LoRa protocol](ota_protocol.md). Builds without OTA support can still
relay an opaque OTA packet when their routing policy permits it.
## Custom packet
Custom packets have no defined format.
+4 -1
View File
@@ -126,7 +126,10 @@ sequence has exhaustive offline validation but not yet a complete physical run.
Restore the test RAK locally with the ZIP's
`recovery/test-start/RAK3401-test-start-v1.16.7-c1caa5ad.uf2`. Before starting,
require start version `1.16.7.0`, body hash `71F4026CBE4B8B74`, target
`2FA509C1`, hardware `RAK_3401`, and OTAFIX mOTA ABI 2 with codec 2.
`2FA509C1`, hardware `RAK_3401`, and OTAFIX mOTA ABI 2 with codec 2. The live
runner queries `get bootloader.ver`, reports the installed version, and then
uses `ota self` to verify those apply capabilities before changing any radio
or watchdog setting.
For the tested direct topology: