feat: add Bluetooth-controlled LoRa OTA

Add the framed BLE mOTA transport and companion control path, complete the full-profile build and runtime fixes, and document and test the OTA workflow.
This commit is contained in:
mikecarper committed 2026-08-27 01:22:18 -07:00
1 parent bd3a94bddb
commit 69ded9d6c7
79 files changed
+3178 -304

No files matched your search

+5 -5
View File
@@ -431,8 +431,8 @@ meaning can change when the service reorders or adds presets.
| Build profile | WiFi/MQTT behavior |
|---|---|
| Standard | Uses the selected target's role. Ordinary legacy-slot ESP32 repeater/room-server artifacts omit WebConfig when needed to fit. ESP32 MQTT observer and ESP-NOW bridge targets are automatically promoted to FULL; WiFi-companion targets keep their companion partition profile. |
| Logging | Enables USB/debug packet logging and disables the MQTT bridge. CommonCLI roles persist `get/set usb.logging`; logging output itself is not a direct MQTT uplink. |
| Standard | Uses the selected target's role. Where USB is a safe plaintext console, the same artifact embeds debug/packet logging behind persistent `get/set usb.logging`. Ordinary legacy-slot ESP32 repeater/room-server artifacts omit WebConfig when needed to fit. ESP32 MQTT observer and ESP-NOW bridge targets are automatically promoted to FULL; WiFi-companion targets keep their companion partition profile. |
| Legacy logging | No separate artifact is emitted; USB logging is part of the ordinary image. Logging output itself is not a direct MQTT uplink. |
| MQTT | Builds explicit MQTT observer or WiFi-companion-MQTT targets with USB packet logging off. Non-companion ESP32 MQTT observers always use FULL expanded partitions. |
| FULL ESP32 USB + WiFi | Uses the board's MQTT target with USB packet logging and direct WiFi MQTT together, expanded dual-OTA partitions, up to 254 neighbors, LoRa OTA, and full-size ESP32 features such as WebConfig where supported. `get/set logging.output off\|usb\|wifi\|both` persists the active paths. Classic T-Beam MQTT observers retain their 50-entry table because their persistent discovery state exhausts internal DRAM at 254. |
| FULL ESP32 logging fallback | Uses the board's non-MQTT target only when no matching WiFi MQTT environment exists. It keeps debug and packet logging, expanded dual-OTA partitions, up to 254 neighbors, and LoRa OTA. Persistent `usb.logging off` also provides normal output-off operation, so ESP-NOW FULL roles need no second non-logging image. |
@@ -445,9 +445,9 @@ persistent MQTT discovery state leaves insufficient internal-DRAM margin at 254.
The interactive Option 1 **FULL everything** choice and the standalone FULL
command select the unified USB + WiFi image when a matching MQTT target exists;
otherwise they select the logging fallback. The build matrix no longer emits a
separate standard logging image or non-MQTT FULL twin for a role covered by the
unified image. All FULL profiles include LoRa OTA, WebConfig where supported,
otherwise they select the logging fallback. The build matrix no longer emits
any separate standard logging image, or a non-MQTT FULL twin for a role covered
by the unified image. All FULL profiles include LoRa OTA, WebConfig where supported,
up to 254 neighbors, and expanded dual-OTA partitions. Target-specific
internal-DRAM limits still apply.
+91 -6
View File
@@ -53,6 +53,7 @@
full: "Full Companion transports",
ble: "Bluetooth LE",
usb: "USB",
"usb-ble": "USB + Bluetooth LE",
wifi: "Wi-Fi",
serial: "Serial / UART",
ethernet: "Ethernet",
@@ -109,6 +110,7 @@
// Exact aliases of the unsuffixed Heltec V4 USB/BLE recipes.
"heltec_v4_companion_radio_usb_femon",
"heltec_v4_companion_radio_ble_femon",
"ikoka_handheld_nrf_e22_30dbm_096_rotated_companion_radio_full",
]);
let pickerInstanceCount = 0;
@@ -273,6 +275,7 @@
const value = String(tail).toLowerCase().replace(/^[_-]+/, "");
if (role === "companion") {
if (/^full(?:$|[_-])/.test(value)) return "full";
if (/^usb_ble(?:$|[_-])/.test(value)) return "usb-ble";
if (/^ble(?:$|[_-])/.test(value)) return "ble";
if (/^usb(?:$|[_-])/.test(value)) return "usb";
if (/^wifi(?:$|[_-])/.test(value)) return "wifi";
@@ -291,7 +294,7 @@
let value = String(tail);
if (role === "companion") {
value = value.replace(
/^(?:full|ble|usb|wifi|serial|ethernet)(?=$|[_-])/i,
/^(?:full|usb_ble|ble|usb|wifi|serial|ethernet)(?=$|[_-])/i,
" "
);
// Power saving and controllable FEM gain are persisted settings. Legacy
@@ -414,13 +417,69 @@
}).map(function (profile) {
return profile.hardware + "\n" + profile.variant;
}));
const combinedUsbBleKeys = new Set((profiles || []).filter(
function (profile) {
return profile.role === "companion" && profile.mode === "usb-ble";
}
).map(function (profile) {
return profile.hardware + "\n" + profile.variant;
}));
const terminalReplacementKeys = new Set((profiles || []).filter(
function (profile) {
return profile.role === "companion" &&
(profile.mode === "full" || profile.mode === "usb" ||
profile.mode === "usb-ble");
}
).map(function (profile) {
return profile.hardware + "\n" + profile.variant;
}));
const targets = new Set((profiles || []).map(function (profile) {
return String(profile.target || "").toLowerCase();
}));
const mergedRs232Targets = {
heltec_t096_repeater_bridge_rs232: "heltec_t096_repeater",
heltec_t096_repeater_bridge_rs232_lora_ota_no_external_sensors:
"heltec_t096_repeater_lora_ota_no_external_sensors",
rak_4631_repeater_bridge_rs232_serial1_lora_ota_no_external_sensors:
"rak_4631_repeater_lora_ota_no_external_sensors",
rak_4631_repeater_bridge_rs232_serial2_lora_ota_no_external_sensors:
"rak_4631_repeater_lora_ota_no_external_sensors",
rak_4631_repeater_bridge_rs232_serial1: "rak_4631_repeater",
rak_4631_repeater_bridge_rs232_serial2: "rak_4631_repeater",
promicro_repeater_bridge_rs232_serial1: "promicro_repeater",
heltec_t114_without_display_repeater_bridge_rs232:
"heltec_t114_without_display_repeater",
heltec_t114_repeater_bridge_rs232: "heltec_t114_repeater",
rak_3112_repeater_bridge_rs232: "rak_3112_repeater",
rak_11310_repeater_bridge_rs232: "rak_11310_repeater",
waveshare_rp2040_lora_repeater_bridge_rs232:
"waveshare_rp2040_lora_repeater",
solarxiao_30s_repeater_bridge_rs232: "solarxiao_30s_repeater",
solarxiao_33s_repeater_bridge_rs232: "solarxiao_33s_repeater",
heltec_v3_repeater_bridge_rs232: "heltec_v3_repeater",
heltec_wsl3_repeater_bridge_rs232: "heltec_wsl3_repeater",
lilygo_tlora_v2_1_1_6_repeater_bridge_rs232:
"lilygo_tlora_v2_1_1_6_repeater",
};
return (profiles || []).filter(function (profile) {
const key = profile.hardware + "\n" + profile.variant;
const replacedAttachedTransport = profile.role === "companion" &&
(profile.mode === "usb" || profile.mode === "ble" ||
profile.mode === "wifi");
return !(replacedAttachedTransport && fullKeys.has(key));
profile.mode === "wifi" || profile.mode === "serial" ||
profile.mode === "ethernet" || profile.mode === "usb-ble");
const replacedByUsbBle = profile.role === "companion" &&
(profile.mode === "usb" || profile.mode === "ble") &&
combinedUsbBleKeys.has(key);
const replacedTerminal = profile.role === "terminal" &&
terminalReplacementKeys.has(key);
const rs232Replacement = mergedRs232Targets[
String(profile.target || "").toLowerCase()
];
const replacedRs232 = Boolean(rs232Replacement &&
targets.has(rs232Replacement));
return !(replacedAttachedTransport && fullKeys.has(key)) &&
!replacedByUsbBle && !replacedTerminal && !replacedRs232;
});
}
@@ -440,6 +499,24 @@
});
}
function applyMergedStandardUsbLoggingCapabilities(profiles) {
return (profiles || []).map(function (profile) {
const safeRole = [
"companion", "repeater", "room", "sensor", "terminal",
].includes(profile.role);
const unsafeCompanionMode = profile.role === "companion" &&
(profile.mode === "ble" || profile.mode === "full");
if (profile.logging !== "none" || profile.ota !== "none" ||
!safeRole || unsafeCompanionMode) {
return profile;
}
profile.logging = "usb-runtime";
profile.loggingModes = ["none", "usb"];
return profile;
});
}
function hardwareFamilyFor(hardware, hardwareNames) {
const value = String(hardware || "");
const lowerValue = value.toLowerCase();
@@ -539,8 +616,10 @@
}).filter(function (profile) {
return !isHiddenLegacyProfile(profile);
});
const profiles = omitTransportsReplacedByFull(
applyFullCompanionCapabilities(visibleProfiles)
const profiles = applyMergedStandardUsbLoggingCapabilities(
omitTransportsReplacedByFull(
applyFullCompanionCapabilities(visibleProfiles)
)
).sort(function (a, b) {
return a.target.localeCompare(b.target, undefined, {
numeric: true,
@@ -738,7 +817,11 @@
"With no saved SSID, the setup AP stays available for 30 minutes after each boot, then Wi-Fi powers off automatically until reboot or an explicit start webconfig command. A configured Wi-Fi mode keeps reconnecting instead."
);
} else if (profile.logging === "usb-runtime") {
if (profile.dedicatedUsbLogging) {
if (!isFullCompanion(profile)) {
extra.push(
"This ordinary image includes USB logging. Use get usb.logging and set usb.logging off|on to select and save normal or logging operation."
);
} else if (profile.dedicatedUsbLogging) {
extra.push(
"Full Companion starts with USB logging off and only interface 00. Use get usb.logging, or set usb.logging on reboot to add interface 02; set usb.logging off reboot removes it again."
);
@@ -1118,6 +1201,8 @@
parseFirmwareAsset: parseFirmwareAsset,
parseTargetProfile: parseTargetProfile,
applyFullCompanionCapabilities: applyFullCompanionCapabilities,
applyMergedStandardUsbLoggingCapabilities:
applyMergedStandardUsbLoggingCapabilities,
applyDualCdcFullCompanionCapabilities: applyFullCompanionCapabilities,
applyNrf52FullCompanionCapabilities:
applyFullCompanionCapabilities,
+21 -7
View File
@@ -49,7 +49,7 @@ an old or failed artifact without a verified sidecar.
| Repeater | Full repeater administration surface, subject to the profile differences below |
| Room server | Room-server administration surface, subject to the profile differences below |
| Sensor | Sensor command surface; it does not acquire the repeater administration tree |
| Serial, USB, BLE, or WiFi companion | Uses the companion protocol; any serial diagnostics are target-specific |
| Full, serial, Ethernet, USB, BLE, or WiFi companion | Uses the companion protocol; Full combines every qualified transport for that exact board |
| KISS modem | Uses the KISS/TNC frame interface, not the repeater text CLI |
| Bridge | Uses its base role plus commands for the bridge transport compiled into that target |
@@ -65,8 +65,8 @@ retain 50 because their MQTT discovery tables are constrained by internal DRAM.
| Build/profile | Command availability |
|---|---|
| Standard non-MQTT repeater or room server | Keeps the normal role CLI. The explicitly selected portable policy can omit WebConfig and browser WiFi OTA, so those commands are unavailable and the omission is recorded in the capability manifest. |
| Standard logging | Logging does not remove commands by itself. It has the same CLI as the selected role/profile and adds compiled logging behavior. CommonCLI roles persist `get/set usb.logging`. Roles covered by a Full image with runtime logging are not duplicated here. |
| Standard non-MQTT repeater or room server | Keeps the normal role CLI and, where USB is a safe plaintext console, embeds debug/packet logging behind persistent `get/set usb.logging`. The explicitly selected portable policy can omit WebConfig and browser WiFi OTA, so those commands are unavailable and the omission is recorded in the capability manifest. |
| Legacy standard logging | No longer emitted separately. Its behavior is compiled into the ordinary artifact. Size-constrained STM32 targets embed packet logging without verbose `MESH_DEBUG`. |
| LoRa-OTA (`-ota-`) | LoRa OTA adds the `ota ...` commands; it does not otherwise reduce the role CLI. ESP32 `no_external_sensors` artifacts retain the compact browser WiFi uploader, the complete CLI, and a 254-entry neighbor table. |
| Internal-flash nRF52 repeater auto pair | `full-ota` retains the board's external-sensor drivers; `reduced-ota` omits the declared optional sensors to leave additional internal-flash staging room. RAK3401 and RAK4631 reduced builds retain INA219, INA226, INA260, and INA3221 I2C voltage/current monitors at a measured 4,808-byte flash cost. Both artifacts carry the same stable OTA target identity and are checked for `ota ...` and `retry.preset`; RAK artifacts also verify the retained monitor drivers. |
| ESP32 MQTT observer or ESP-NOW bridge | Always uses the expanded FULL partition profile. The build never substitutes a reduced CLI to fit the legacy application slot. |
@@ -74,10 +74,11 @@ retain 50 because their MQTT discovery tables are constrained by internal DRAM.
| FULL ESP32 logging fallback | Uses the matching non-MQTT target only when no WiFi MQTT sibling exists, with debug and packet logging enabled and the complete command surface supported by that role and hardware. Its persistent USB gate also covers output-off operation, avoiding a second FULL ESP-NOW image. |
| Dual-CDC Full Companion | nRF52 and qualified native-USB ESP32-S3 Full images use one physical USB connection. Fresh installs expose only interface `00` for framed Companion/terminal/mOTA traffic. Enabling logging and rebooting adds interface `02` for plaintext logs. They also provide BLE and source-only LoRa OTA; ESP32 additionally provides WiFi. `get/set usb.logging` persistently controls whether the logging interface is present. |
| Single-TTY Full Companion | ESP32 Full images without dual CDC start with framed Companion on their one TTY. `set usb.logging on` switches it to an input-capable plaintext logging terminal; `set usb.logging off` replies and then restores framed Companion automatically. BLE, WiFi, and source-only LoRa OTA remain available. |
| `no_external_sensors` | Removes optional external-sensor drivers and their settings; it does not remove core repeater discovery or routing commands. RAK3401 and RAK4631 profiles retain the four common INA I2C voltage/current monitors. GPS-preserving RAK nRF52 OTA profiles also retain their GPS commands and provider. The RAK4631 Serial1 RS232 bridge remains GPS-off because both features require Serial1. The legacy target suffix is retained for OTA identity compatibility. |
| `no_external_sensors` | Removes optional external-sensor drivers and their settings; it does not remove core repeater discovery, routing, or runtime RS-232 commands. RAK3401 and RAK4631 profiles retain the four common INA I2C voltage/current monitors. GPS-preserving RAK nRF52 OTA profiles retain their GPS commands and provider; RAK4631 defaults the bridge to UART 2 because GPS uses UART 1. Legacy target suffixes remain stable for OTA identity compatibility. |
`logging`, `OTA`, and `FULL` describe independent build features. Do not infer
that a command is missing merely because `logging` appears in the filename.
`logging`, `OTA`, and `FULL` describe independent build features in historical
filenames. Current standard artifacts use no `-logging-` infix because their
USB logging is runtime controlled.
## Canonical bulk-build policy
@@ -94,7 +95,8 @@ available from a canonical image:
persisted `radio.rxgain on|off` setting; the G3 alias changed only the
advertised default name.
- When a board has a Full Companion, that one artifact replaces its
separate USB, BLE, ordinary WiFi, and USB packet-logging Companion artifacts.
separate USB, BLE, ordinary WiFi, hardware-serial, Ethernet Companion, and
USB packet-logging Companion artifacts.
It provides BLE and source-only LoRa OTA; ESP32 also provides ordinary WiFi.
Dual-CDC builds keep framed traffic on interface `00` and add logging on
interface `02` after a reboot. Single-TTY builds switch interface `00` into
@@ -108,6 +110,18 @@ available from a canonical image:
available as explicit build targets, but are not canonical release artifacts
when the qualified Full image fits.
- Standalone Terminal Chat is omitted when the same exact hardware has either
Full Companion or a USB Companion, because their local text terminal supplies
the same role. Heltec E290 and T190 similarly publish one combined USB + BLE
Companion. RAK4631 repeater and room-server Ethernet builds stay separate;
only the RAK4631 Ethernet Companion is folded into Full Companion.
- Matching RS-232 bridge roles are compiled into the normal repeater and
selected at runtime with `bridge.enabled`, `bridge.baud`, and `bridge.uart`.
Historical bridge names remain directly buildable but are omitted from bulk
releases. Wio-E5 remains separate because its normal image has only 916
bytes free, while the combined image exceeds the fixed 240 KiB application
partition by 2,192 bytes.
The old aliases still work with `build-firmware` and
`build-matching-firmwares`. Dedicated repeater LoRa OTA receiver images are not
collapsed; they retain their exact storage, bootloader, role, and target
+8 -6
View File
@@ -11,10 +11,10 @@ own interface, so those build roles are not represented here.
Build columns mean:
- **Standard** - the ordinary non-MQTT artifact, without the `logging` or
explicit `ota` filename marker.
- **Logging** - the ordinary non-MQTT `-logging-` artifact. Logging does not
remove commands by itself.
- **Standard** - the ordinary non-MQTT artifact, without an explicit `ota`
filename marker. Safe plaintext-USB roles embed runtime USB logging.
- **Logging** - the legacy `-logging-` profile, now represented by the same
ordinary artifact and retained as a comparison column only.
- **LoRa OTA** - the explicit `-ota-` repeater or repeater-bridge artifact. Its
optional external-sensor drivers are removed, but onboard GPS is retained.
- **FULL unified** - the expanded-partition ESP32 artifact with LoRa OTA, the
@@ -72,7 +72,7 @@ fix, no WiFi connection, an inactive bridge, or an nRF52 bootloader without
| Statistics | [`set telemetry.gps`; `get/set/send telemetry.tx`](cli_commands.md#read-repeater-telemetry-history) | Non-STM32 repeater; GPS setting requires a 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 |
| Logging | [`get/set usb.logging`; unified FULL `get/set logging.output`](cli_commands.md#control-live-usb-logging) | Logging artifacts; CommonCLI USB gate is persistent; unified ESP32 FULL selects off/USB/WiFi/both; Full Companion uses either a reboot-controlled second CDC or an input-capable single-TTY logging terminal | No | Yes | No |
| Logging | [`get/set usb.logging`; unified FULL `get/set logging.output`](cli_commands.md#control-live-usb-logging) | Ordinary safe-USB artifacts; CommonCLI USB gate is persistent; unified ESP32 FULL selects off/USB/WiFi/both; Full Companion uses either a reboot-controlled second CDC or an input-capable single-TTY logging terminal | Yes | Yes | No |
| Radio | [`get radio`; `set radio ...`](cli_commands.md#view-or-change-this-nodes-radio-parameters) | All text CLI roles | Yes | Yes | Yes |
| Radio | [`get tx`; `set tx <dbm>`](cli_commands.md#view-or-change-this-nodes-transmit-power) | Board TX-power limits apply | Yes | Yes | Yes |
| Radio | [`tempradio ...`; `normalradio`](cli_commands.md#change-the-radio-parameters-for-a-set-duration) | Full parser | Yes | Yes | Yes |
@@ -155,6 +155,7 @@ fix, no WiFi connection, an inactive bridge, or an nRF52 bootloader without
| Bridge | [`get/set bridge.delay`](cli_commands.md#add-a-delay-to-packets-routed-through-this-bridge) | Compiled bridge | Feature | Feature | Feature |
| Bridge | [`get/set bridge.source`](cli_commands.md#view-or-change-the-source-of-packets-bridged-to-the-external-interface) | Compiled bridge | Feature | Feature | Feature |
| Bridge | [`get/set bridge.baud`](cli_commands.md#view-or-change-the-speed-of-the-bridge-rs-232-only) | RS-232 bridge | Feature | Feature | Feature |
| Bridge | [`get/set bridge.uart`](cli_commands.md#view-or-change-the-uart-used-by-the-bridge-rs-232-only) | RS-232 bridge | Feature | Feature | Feature |
| Bridge | [`get/set bridge.channel`](cli_commands.md#view-or-change-the-channel-used-for-bridging-espnow-only) | ESP-NOW is ESP32 only | No | No | No |
| Bridge | [`get/set bridge.secret`](cli_commands.md#set-the-esp-now-secret) | ESP-NOW is ESP32 only | No | No | No |
| Board | [`get bootloader.ver`](cli_commands.md#view-the-bootloader-version-nrf52-only) | nRF52 bootloader metadata | Yes | Yes | Yes |
@@ -235,7 +236,7 @@ fix, no WiFi connection, an inactive bridge, or an nRF52 bootloader without
| Statistics | [`set telemetry.gps`; `get/set/send telemetry.tx`](cli_commands.md#read-repeater-telemetry-history) | Non-STM32 repeater; GPS setting requires a provider; remote access requires administrator | Yes | Yes | Yes | Yes | Yes |
| Logging | [`log start`; `log stop`; `log erase`](cli_commands.md#logging) | Storage-backed roles retain data | Yes | Yes | Yes | Yes | Yes |
| Logging | [`log`](cli_commands.md#print-the-captured-log-to-the-serial-terminal) | Local serial | Serial | Serial | Serial | Serial | Serial |
| Logging | [`get/set usb.logging`; unified FULL `get/set logging.output`](cli_commands.md#control-live-usb-logging) | Logging artifacts; CommonCLI USB gate is persistent; unified ESP32 FULL selects off/USB/WiFi/both; Full Companion uses either a reboot-controlled second CDC or an input-capable single-TTY logging terminal | No | Yes | No | No | Yes |
| Logging | [`get/set usb.logging`; unified FULL `get/set logging.output`](cli_commands.md#control-live-usb-logging) | Ordinary safe-USB artifacts; CommonCLI USB gate is persistent; unified ESP32 FULL selects off/USB/WiFi/both; Full Companion uses either a reboot-controlled second CDC or an input-capable single-TTY logging terminal | Yes | Yes | No | No | Yes |
| Radio | [`get radio`; `set radio ...`](cli_commands.md#view-or-change-this-nodes-radio-parameters) | All text CLI roles | Yes | Yes | Yes | Yes | Yes |
| Radio | [`get tx`; `set tx <dbm>`](cli_commands.md#view-or-change-this-nodes-transmit-power) | Board TX-power limits apply | Yes | Yes | Yes | Yes | Yes |
| Radio | [`tempradio ...`; `normalradio`](cli_commands.md#change-the-radio-parameters-for-a-set-duration) | Full parser | Yes | Yes | Yes | Yes | Yes |
@@ -318,6 +319,7 @@ fix, no WiFi connection, an inactive bridge, or an nRF52 bootloader without
| Bridge | [`get/set bridge.delay`](cli_commands.md#add-a-delay-to-packets-routed-through-this-bridge) | Compiled bridge | Feature | Feature | Feature | Yes | Feature |
| Bridge | [`get/set bridge.source`](cli_commands.md#view-or-change-the-source-of-packets-bridged-to-the-external-interface) | Compiled bridge | Feature | Feature | Feature | Yes | Feature |
| Bridge | [`get/set bridge.baud`](cli_commands.md#view-or-change-the-speed-of-the-bridge-rs-232-only) | RS-232 bridge | Feature | Feature | Feature | No | Feature |
| Bridge | [`get/set bridge.uart`](cli_commands.md#view-or-change-the-uart-used-by-the-bridge-rs-232-only) | RS-232 bridge | Feature | Feature | Feature | No | Feature |
| Bridge | [`get/set bridge.channel`](cli_commands.md#view-or-change-the-channel-used-for-bridging-espnow-only) | ESP-NOW bridge | No | No | No | Feature | Feature |
| Bridge | [`get/set bridge.secret`](cli_commands.md#set-the-esp-now-secret) | ESP-NOW bridge | No | No | No | Feature | Feature |
| Board | [`get bootloader.ver`](cli_commands.md#view-the-bootloader-version-nrf52-only) | nRF52 only | No | No | No | No | No |
+41 -6
View File
@@ -562,6 +562,23 @@ set flag bit 0.
---
## Set Companion display rotation
SSD1306 Full Companion builds support a persisted runtime orientation:
```text
get display.rotation
set display.rotation 0
set display.rotation 90
set display.rotation 180
set display.rotation 270
```
The values are clockwise degrees. `0` clears the override and restores the
board's compiled default. Unsupported display drivers return an error.
---
## Logging
Builds compiled with `MESH_PACKET_LOGGING` emit one `RAW:` line for every
@@ -576,8 +593,9 @@ timing, hash, type, route, and payload information. Frames that cannot be
decoded still emit their `RAW:` line. Transmitted packets emit the decoded TX
summary.
Ordinary `-logging-` artifacts keep packet logging separate from LoRa OTA.
Use the separately named `-ota-` artifact when LoRa OTA is required. A
Ordinary non-OTA artifacts compile packet logging into the canonical image and
control its live USB output at runtime; no separate `-logging-` artifact is
emitted. Use the separately named `-ota-` artifact when LoRa OTA is required. A
`-full-usb-wifi-ota-` artifact combines USB packet logging, direct WiFi MQTT,
LoRa OTA, and the expanded FULL feature set. A `-full-logging-ota-` artifact is
emitted only when that hardware/role has no matching WiFi MQTT environment.
@@ -594,10 +612,11 @@ set usb.logging on reboot
set usb.logging off reboot
```
These commands are compiled into logging artifacts and every Full Companion.
They control live USB debug and packet output. CommonCLI roles save the setting
in `/com_prefs`, so it survives reboot; their first boot defaults to on. Full
Companion starts off on a fresh installation.
These commands are compiled into ordinary USB-loggable artifacts and every
Full Companion. They control live USB debug and packet output. CommonCLI roles
save the setting in `/com_prefs`, so it survives reboot; their first boot
defaults to on. Full Companion and ordinary USB Companion start off on a fresh
installation so diagnostics cannot corrupt framed traffic.
On Full Companion these lines belong to its text terminal, not `meshcli`'s
Binary `get/set` parameter namespace. Open interface `00`, send
@@ -3446,6 +3465,22 @@ Requires WiFi connected and the MQTT bridge running.
---
#### View or change the UART used by the bridge (RS-232 only)
**Usage:**
- `get bridge.uart`
- `set bridge.uart <port>`
**Parameters:**
- `port`: Hardware UART number compiled for the board. Most boards expose one
fixed UART. RAK4631 accepts `1` or `2`; UART 2 is the default so UART 1 can
remain available to GPS.
The setting is persistent and restarts an enabled bridge immediately. Normal
repeater artifacts start with `bridge.enabled off`; configure the UART and baud
rate before running `set bridge.enabled on`.
---
#### View or change the channel used for bridging (ESPNow only)
**Usage:**
- `get bridge.channel`
+96 -5
View File
@@ -1,7 +1,7 @@
# Companion Protocol
- **Last Updated**: 2026-08-18
- **Protocol Version**: 13 (`FIRMWARE_VER_CODE`)
- **Last Updated**: 2026-08-26
- **Protocol Version**: 14 (`FIRMWARE_VER_CODE`)
> The command and response catalogs track
> `examples/companion_radio/MyMesh.cpp`. Applications should negotiate the
@@ -60,6 +60,22 @@ MeshCore Companion devices expose a BLE service with the following UUIDs:
- **RX Characteristic** (App -> Firmware): `6E400002-B5A3-F393-E0A9-E50E24DCCA9E`
- **TX Characteristic** (Firmware -> App): `6E400003-B5A3-F393-E0A9-E50E24DCCA9E`
An nRF52 Full Companion also exposes a separate LoRa mOTA source service. It
does not replace or multiplex the normal Companion UART service:
- **mOTA Service**: `14518FC2-7E7A-4D84-8CAE-6664B0234CF2`
- **Device Request** (notify): `2BFAA1EE-7030-459A-B65A-E7CFD5B09735`
- **Host Response** (write with response): `ACF38A51-DD58-4DCE-917F-0B1135E41B1A`
All three mOTA attributes require an encrypted, MITM-authenticated connection
using the Companion's six-digit PIN. The source remains inactive until the
client subscribes to Device Request and explicitly starts it with command
`0x4B`. See [Bluetooth LoRa mOTA source](#bluetooth-lora-mota-source).
ESP32 and nRF52 Companion UART characteristics require the same PIN-protected,
MITM-authenticated link. ESP32 advertises DisplayOnly capability so a central
must enter the PIN shown by the Companion; a Just Works bond is insufficient.
### Connection Steps
1. **Scan for Devices**
@@ -170,7 +186,7 @@ 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
The first byte selects the command. This is the current protocol-v14 command
catalog; bytes `0x2C`-`0x31` are parked and `0x35` is unused.
| Byte | Firmware name | Purpose |
@@ -222,12 +238,14 @@ catalog; bytes `0x2C`-`0x31` are parked and `0x35` is unused.
| `0x44` / `0x45` | `CMD_GET_RADIO_RXGAIN` / `CMD_SET_RADIO_RXGAIN` | Read or set the radio chip's boosted receive-gain mode. |
| `0x46` / `0x47` | `CMD_GET_WIFI_POWER_SAVE` / `CMD_SET_WIFI_POWER_SAVE` | Read or set ESP32 Companion WiFi modem sleep. |
| `0x48` / `0x49` | `CMD_GET_BLUETOOTH_NAME` / `CMD_SET_BLUETOOTH_NAME` | Read or set the independent Bluetooth device name. |
| `0x4A` | `CMD_EXEC_LOCAL_OTA_CONTROL` | Run one bounded local TempRadio or OTA command on a Full Companion. |
| `0x4B` | `CMD_BLE_MOTA_SOURCE` | Query, start, or stop an nRF52 Full Companion's Bluetooth-backed LoRa mOTA source. |
The sections below detail the most common frames. Refer to the source named
above for command bodies that are not expanded here.
The additive hardware-setting commands `0x42`-`0x47` do not change any existing
version-13 frame layout. Clients should probe the command they need and treat
legacy frame layout. Clients should probe the command they need and treat
`ERR_CODE_UNSUPPORTED_CMD` as feature absence.
Both gain-command pairs can be used over the normal binary Companion
@@ -273,6 +291,79 @@ override and restores `MeshCore-<advert name>`. A successful SET replies with
control characters, malformed UTF-8, or oversized values return
`ERR_CODE_ILLEGAL_ARG`; a storage failure returns `ERR_CODE_BAD_STATE`.
### Bluetooth LoRa mOTA source
Protocol v14 lets a phone use an nRF52 Full Companion as the source for a
remote repeater update without a USB computer. The normal Companion service
still carries contacts, repeater login, CLI messages, and these two control
commands. The separate mOTA service carries only host-folder request/response
frames.
`CMD_EXEC_LOCAL_OTA_CONTROL` (`0x4A`) is followed by 1-174 printable ASCII
bytes. Full Companion accepts only these local command families:
```text
tempradio <freq_kHz>,<bw_kHz>,<sf>,<cr>,<minutes>
normalradio
ota ...
```
`ota folder ...` is deliberately rejected because USB and Bluetooth source
ownership must not be changed through the wrong transport. Embedded NUL, CR,
LF, other control bytes, non-ASCII bytes, empty commands, and oversized frames
return `ERR_CODE_ILLEGAL_ARG`. A recognized command replies with
`RESP_CODE_OK`, one unsigned reply-length byte, and exactly that many printable
result bytes. Shell metacharacters are rejected as well; the text is dispatched
only to the in-firmware parser and is never passed to a host shell. Firmware
without the Full Companion feature returns `ERR_CODE_UNSUPPORTED_CMD`.
`CMD_BLE_MOTA_SOURCE` (`0x4B`) has one action byte:
| Action | Meaning |
| ---: | --- |
| `0` | Read status without changing it. |
| `1` | Attach and enumerate the subscribed Bluetooth host's `.mota` catalog. |
| `2` | Detach the Bluetooth source. |
Current firmware returns eleven bytes (legacy protocol-v14 previews returned
the seven-byte prefix only):
```text
00 action flags offered_le16 advertised_le16 source_packets_sent_le32
```
Flag bit `0x01` means the encrypted GATT channel is connected and Device
Request notifications are enabled. Bit `0x02` means the Bluetooth catalog is
attached. Bit `0x04` means USB or another folder transport currently owns the
source slot. Start without a ready subscription, or while another source link
owns the slot, returns `ERR_CODE_BAD_STATE`. A non-nRF52 Full Companion returns
`ERR_CODE_UNSUPPORTED_CMD`. `source_packets_sent` is a per-attachment count of
OTA packets accepted by the Companion's LoRa transmit adapter, including
catalog/manifest traffic, data, proofs, and retries. It wraps as an unsigned
32-bit value. Clients should accept the legacy seven-byte response and display
the packet counter as unavailable.
After a successful start, the device sends the same bounded seeder frames used
by `motatool serve` on Device Request:
```text
device -> host: 'M' 'S' op args... xor(op || args)
host -> device: 'm' 's' op status payload... xor(all prior bytes)
```
Device requests are at most 11 bytes. A source response is at most 197 bytes.
The host may split one response across multiple write-with-response operations
when the negotiated ATT payload is smaller; it must preserve byte order and
must not interleave another response. Bad checksums, partial frames, overflow,
unsubscribe, loss of encryption, or disconnect fail closed. The firmware then
detaches the catalog and stops advertising its entries. USB and Bluetooth
folder sources are mutually exclusive.
A Linux reference controller and seeder is provided at
`tools/ble_mota/ble_mota_seeder.py`. It verifies every input with `motatool`
before offering it. A mobile implementation should apply the same complete
container verification before serving files.
### 1. App Start
**Purpose**: Initialize communication with the device. Must be sent first after connection.
@@ -305,7 +396,7 @@ Byte 1: Highest companion protocol version understood by the app
**Example** (hex):
```
16 0D
16 0E
```
**Response**: `PACKET_DEVICE_INFO` (0x0D) with device information
+77 -3
View File
@@ -12,8 +12,10 @@ firmware as an mOTA image.
| BLE Binary Companion | Yes | Yes |
| USB ASCII terminal | Yes | Yes |
| Dedicated USB plaintext logging | Qualified native-USB ESP32-S3 profiles | Yes |
| Host-backed LoRa mOTA source | WiFi TCP 5001 | Exclusive USB mode |
| Host-backed LoRa mOTA source | WiFi TCP 5001 | Exclusive USB mode or encrypted BLE |
| WiFi Companion/WebConfig | Yes | No - nRF52840 has no WiFi |
| Hardware serial Companion | On targets with assigned serial pins | On targets with assigned serial pins |
| Ethernet Companion | On targets with an Ethernet module | On RAK4631 with RAK13800 |
| LoRa self-update | No | No |
## Build and install
@@ -63,8 +65,11 @@ Canonical Companion bulk builds also omit legacy `_ps` and `_femoff` aliases.
Power saving and controllable FEM receive gain are persisted runtime settings;
the old names remain available through an explicit `build-firmware` command
for compatibility. Full Companion replaces separate USB, BLE, ordinary WiFi,
and USB-only packet-logging release artifacts whenever the exact board supports
the combined profile. Dual-CDC builds separate framed traffic and logs;
hardware-serial, Ethernet Companion, Terminal Chat, and USB-only packet-logging
release artifacts whenever the exact board supports those combined transports.
Direct builds of the legacy targets remain available. RAK4631 repeater and room
server Ethernet builds remain separate because they are different standalone
roles, not Companion transports. Dual-CDC builds separate framed traffic and logs;
single-TTY builds make those modes mutually exclusive. In WebConfig, use the
**FEM RX boost** switch. From the text terminal (USB, or TCP 5002 on ESP32), use:
@@ -85,6 +90,22 @@ commands control the external receive and transmit paths. The selected states
are applied immediately and retained after reboot. FEM TX gain is reported as
unsupported on boards without software-selectable PA gain.
SSD1306 display builds also persist a runtime orientation. This replaces the
separate rotated Full Companion release image:
```text
get display.rotation
set display.rotation 90
set display.rotation 180
set display.rotation 270
set display.rotation 0
```
`0` resets the screen to that board's compiled default orientation.
Heltec E290 and T190 use one `usb_ble` Companion artifact for simultaneous USB
and BLE rather than publishing separate USB-only and BLE-only images.
Device power saving is separate from LoRa RXPS. It can be changed in WebConfig
with the **Device power saving** switch or from the text terminal:
@@ -219,6 +240,7 @@ itself.
| ESP32 | TCP 5001 | Host `.mota` folder from `motatool serve --tcp` |
| ESP32 | TCP 5002 | Full Companion text terminal; same role commands as the USB terminal |
| nRF52 | USB mOTA mode | Host `.mota` folder from `motatool serve --serial` |
| nRF52 | Encrypted BLE mOTA service | Paired phone/tablet/Linux host `.mota` catalog |
Delivery-required replies are returned only to the interface which supplied the
latest command. A contact-list stream keeps that route locked from
@@ -356,6 +378,10 @@ only one may own the terminal at a time. Entering USB terminal mode closes an
active TCP terminal session. Disconnecting TCP clears pending terminal-only
state without cancelling Binary Companion delivery or radio retries.
Both terminal transports accept `reboot`. The reply is sent first and the
device reboots one second later, so a script can distinguish an accepted reboot
from an abruptly lost connection.
Port 5002 is plaintext and has no device-local login gate. A remote-admin
password entered with `login` is sent across the LAN connection as typed even
though the terminal does not echo it. Use port 5002 only on a trusted LAN or a
@@ -459,6 +485,50 @@ While mOTA mode owns USB:
No manual mode token or modified `motatool` build is required.
## nRF52 Bluetooth mOTA source
Protocol v14 also lets a phone, tablet, or Bluetooth-capable Linux host feed
the `.mota` catalog to an nRF52 Full Companion. Normal Companion commands stay
on the Nordic UART service. Firmware data uses a separate GATT service, so
binary app traffic cannot be mistaken for a firmware block.
The client must pair with the Companion PIN, subscribe to the mOTA Device
Request characteristic, and send `CMD_BLE_MOTA_SOURCE` action `start` over the
normal Binary Companion connection. The source is available only while that
encrypted MITM-authenticated connection remains active. Disconnecting,
unsubscribing, overflowing a frame, or receiving malformed data automatically
detaches the catalog. USB and BLE source modes are mutually exclusive.
The included Raspberry Pi reference client validates each `.mota` with
`motatool`, schedules the local TempRadio window, serves until interrupted,
then detaches and restores the normal radio tuple:
```bash
python3 tools/ble_mota/ble_mota_seeder.py \
--device MeshCore-MyCompanion \
--dir ./motas \
--local 'tempradio 909.950,250,5,5,120'
```
Prefer this relative `tempradio` form when the phone/Pi and radio clocks may
disagree. It starts a duration on the Companion and does not compare their
wall clocks. Use the absolute `tempradioat` scheduler only after synchronizing
the participating nodes.
Use `--pair` when the Linux host has not already bonded. BlueZ must have an
agent capable of entering or confirming the six-digit PIN. Use `--source
status` without `--dir` for a read-only channel/status check. The complete
UUID, frame, action, and status definitions are in the
[Companion protocol](./companion_protocol.md#bluetooth-lora-mota-source).
This reference process stands in for the phone application. A mobile app can
use the same sequence while retaining its normal contact and Repeater Admin
UI: log in to the destination, put each required node on the same bounded
TempRadio tuple, start the local Bluetooth catalog, then send the normal remote
`ota ls`, `ota pull`, and `ota install` commands. The destination still checks
container geometry, hardware identity, hashes, signature policy, and the
OTAFIX bootloader before installation.
## Serve mOTA images manually
First put the destination, required relays, controller, and source on the same
@@ -514,6 +584,10 @@ Ctrl-C to detach the folder. Reopen the terminal and use `normalradio` if the
source should return early; otherwise the saved radio settings return when the
bounded window expires.
As a cable-free alternative, keep the normal Companion BLE session open and
run the Bluetooth reference client shown in the nRF52 Bluetooth section. Do
not run the USB seeder at the same time.
Both platforms intentionally refuse firmware installation commands such as:
```text
+23 -5
View File
@@ -140,8 +140,12 @@ from the published firmware assets.
| LoRa OTA source only | Full Companion serving a host-supplied update to another node without self-installing it |
Connection and bridge choices depend on the selected role. Companion firmware
may offer Full, Bluetooth, USB, Wi-Fi, serial, or Ethernet transports.
Repeaters may offer standard, ESP-NOW bridge, RS-232 bridge, Ethernet, or MQTT
may offer Full, combined USB + Bluetooth, Bluetooth, USB, Wi-Fi, serial, or
Ethernet transports.
Normal repeater firmware includes runtime-controlled RS-232 support where the
board has room; use `set bridge.enabled on` after configuring `bridge.uart` and
`bridge.baud`. The Wio-E5 remains the capacity exception and offers a separate
RS-232 image. Repeaters may also offer separate ESP-NOW, Ethernet, or MQTT
observer modes.
## FULL versus standard
@@ -169,9 +173,23 @@ available as an override. A saved SSID switches to the normal indefinite
reconnect behavior instead.
Full Companion profiles use one binary for USB, BLE, ordinary Wi-Fi on ESP32,
source-only LoRa OTA, and optional USB packet logging. The picker therefore
omits separate attached-transport and USB-logging choices whenever the exact
Full recipe exists. Fresh installs default to logging off.
source-only LoRa OTA, Terminal Chat, optional USB packet logging, and any
board-qualified serial or Ethernet Companion transport. Bulk builds therefore
omit separate attached-transport, Terminal Chat, and USB-logging artifacts
whenever the exact Full recipe exists. RAK4631 repeater and room-server
Ethernet images remain separate roles. Fresh installs default to logging off.
When Full Companion does not fit but a matching USB Companion does, that USB
artifact also supplies Terminal Chat and replaces its standalone release image.
Heltec E290 and T190 publish a combined USB + BLE Companion. SSD1306 Full
Companion builds use `set display.rotation 90|180|270`; `0` restores the board
default, so a separate rotated release image is not recommended.
Ordinary non-OTA roles also use one artifact for normal operation and USB
logging. Select the saved mode with `set usb.logging off|on`; no `-logging-`
artifact is emitted. KISS, BLE-only Companion, and constrained LoRa-OTA
receiver images retain their protocol/partition contracts and do not inherit
plaintext USB logging.
Dual-CDC nRF52 and qualified native-USB ESP32-S3 builds keep Binary Companion
on interface `00`; `set usb.logging on reboot` adds plaintext interface `02`.
+4 -4
View File
@@ -21,10 +21,10 @@ example and is disabled unless the endpoint is started with `--allow-reboot`.
Text such as `reboot now`, `cpu-temp; reboot`, and embedded newlines is not a
command: the endpoint accepts only an exact allowlist match.
The bridge is included in normal repeater firmware. The specialized Wio-E5
RS232 bridge image omits it because the RS232 application already fills its
fixed 240 KiB application partition; use the normal Wio-E5 repeater image when
the USB/MQTT host service is needed.
The bridge is included in normal repeater firmware except on Wio-E5. Its
specialized RS232 bridge image omits the host service because the combined
application exceeds the fixed 240 KiB partition; use the normal Wio-E5
repeater image when the USB/MQTT host service is needed.
## Run alongside meshcoretomqtt
+17 -2
View File
@@ -90,8 +90,9 @@ environmental sensors while retaining board-native features such as displays, bu
and GPS where the target uses the GPS-preserving lean profile. RAK3401 and RAK4631 reduced builds also
retain INA219, INA226, INA260, and INA3221 I2C voltage/current monitors; together these drivers add 4,808
bytes over the otherwise reduced image. The RAK3401 OTA repeater retains RAK12501 support in sensor slot A;
slot D conflicts with the RAK13302 radio's BUSY/DIO1 lines. The RAK4631 OTA repeater retains GPS except for
its Serial1 RS232 bridge, which uses the same UART. Selected nRF52 boards with matched external
slot D conflicts with the RAK13302 radio's BUSY/DIO1 lines. The RAK4631 OTA repeater retains GPS and defaults
its runtime RS-232 bridge to Serial2; Serial1 and GPS use the same UART and must not be enabled together.
Selected nRF52 boards with matched external
QSPI application and bootloader support can instead make the normal full-sensor
repeater install-capable; those targets do not need to reserve internal flash
for the downloaded container. SolarXiao 30S and 33S use this matched external-QSPI
@@ -156,6 +157,10 @@ An nRF52 `companion_radio_full` starts in USB Binary mode. Use
`+++MESHCORE-TERM-STOP`. When `motatool serve --serial` opens the port, its
automatic `ota folder on` command selects exclusive mOTA mode; stopping the
tool or disconnecting resets USB to Binary. BLE remains available throughout.
Protocol v14 can instead take the catalog from a paired phone or Linux host
over a separate encrypted BLE mOTA service while Binary Companion remains
active. USB and BLE catalog sources are mutually exclusive. See the
[Full Companion Bluetooth source guide](./companion_radio_full.md#nrf52-bluetooth-mota-source).
For an ESP32 WiFi companion or FULL ESP32 source with active WiFi, use its dedicated OTA seeder:
@@ -356,6 +361,16 @@ Replace `/dev/ttyACM0` with the USB serial device of the source companion select
`motatool` attaches the folder to the source, which advertises the update over LoRa while its temporary-radio
window is active. KISS modem serial ports cannot be used here.
For an nRF52 Full Companion and a Bluetooth-capable host, the equivalent
cable-free source is:
```bash
python3 tools/ble_mota/ble_mota_seeder.py \
--device MeshCore-MyCompanion \
--dir ./motas \
--local 'tempradio 909.950,250,5,5,120'
```
Leave this command running until the destination finishes downloading.
### 3. Find and download the update
+22 -10
View File
@@ -133,8 +133,8 @@ matched external QSPI staging, so their ordinary full-sensor repeater is already
redundant lean sibling is generated. Integrated GPS and other
board-native telemetry remain enabled where the target selects the GPS-preserving lean profile. The RAK3401
OTA repeater also retains RAK12501 GPS support; install that module in sensor slot A because slot D conflicts
with the RAK13302 radio's BUSY/DIO1 lines. RAK4631 OTA repeaters retain GPS except for the Serial1 RS232
bridge, whose bridge and GPS would contend for the same UART.
with the RAK13302 radio's BUSY/DIO1 lines. RAK4631 OTA repeaters retain GPS and default the runtime RS-232
bridge to UART 2; do not enable GPS and an explicitly selected UART 1 bridge at the same time.
ESP32 siblings retain the compact browser WiFi updater and use the full
254-entry neighbor table. RP2040 and STM32 targets are not offered because
those platforms do not yet have a safe bootloader/apply path.
@@ -161,8 +161,9 @@ Option 3 in `build.sh` emits one `*-full-usb-wifi-ota-*` ESP32 artifact for each
FULL-capable non-companion hardware/role that has a matching MQTT environment.
It compiles USB packet logging and direct WiFi MQTT together. A
`*-full-logging-ota-*` fallback is emitted only when there is no MQTT sibling;
the old separate standard-logging and non-MQTT FULL twins are skipped for
covered ESP32 roles. MQTT observers and ESP-NOW bridges are emitted only with
ordinary non-OTA roles compile runtime USB logging into their canonical image,
so separate standard-logging artifacts are not emitted. Non-MQTT FULL twins
are also skipped for covered ESP32 roles. MQTT observers and ESP-NOW bridges are emitted only with
expanded FULL partitions. Menu option 8, or `build-full-esp32-firmwares`,
builds the unified profiles plus necessary fallbacks. Menu option 9, or
`build-full-esp32-logging-firmwares`, builds only those fallbacks.
@@ -686,13 +687,15 @@ 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 <mid8> folder`
The USB/TCP host-folder link can also be 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
against firmware you don't have. Resume is bookkeeping-free: `BEGIN` 0xFF-fills the file and, on reconnect
after a link drop (the fetch PAUSES, holding progress on the host - no RAM/flash fallback), `STAT`+`SREAD`
let the fetcher recompute and refill only the missing blocks.
The phone-oriented BLE link is deliberately source-only and does not register
a folder destination.
### 10.1 The `MotaSource` abstraction (`OtaSource.h`)
@@ -717,7 +720,8 @@ blocks) and streams payload blocks from the source on demand; proofs are generat
### 10.2 The `mota-seeder` transport (`MotaSeederProto.h`)
A `MotaSource` is fed by a host that serves a folder over the device's **USB serial** (the same console the
CLI uses - no extra hardware) or, on an ESP32 WiFi companion or FULL ESP32 role, over **WiFi (TCP)**. The
CLI uses - no extra hardware), on an ESP32 WiFi companion or FULL ESP32 role over **WiFi (TCP)**, or on an
nRF52 Full Companion over an encrypted **BLE GATT** service. The
host is the
standalone Rust tool [`motatool`](https://github.com/vk496/motatool) (`motatool serve --serial <port>` /
`--tcp <host[:port]>`, which also builds + verifies + inspects `.mota`). The device only emits request frames *while
@@ -761,6 +765,10 @@ deployment runner temporarily uses `0.3` on managed relays.
work**: KISS firmware exposes a TNC/KISS frame interface, not the MeshCore CLI and `mota-seeder`
request/response transport. An ESP32 WiFi companion or FULL ESP32 role with active WiFi is the alternative
source connection: use its dedicated seeder port with `motatool serve --tcp <host>:5001`.
An nRF52 Full Companion can instead pair with a phone or Linux host, subscribe
to its mOTA request characteristic, and use protocol-v14
`CMD_BLE_MOTA_SOURCE`. That BLE path is source-only; it does not expose the
reverse `FolderMotaStore` capture operations.
Device CLI: `ota folder on` (attach + announce), `ota folder` (list), `ota folder off`. Build flag
`OTA_FOLDER_SERIAL` (default stream = console `Serial`; override `OTA_FOLDER_SERIAL_STREAM` + define
@@ -784,10 +792,14 @@ disappearing. Operators should split a large chain or use a higher-capacity/SD s
resync framing above exists for the shared USB-UART (an unframed byte stream); it is harmless over a
reliable stream and the **WiFi (TCP)** transport reuses it as-is - both ends just treat the socket as a
byte stream (on-device, `SerialMotaSource` runs verbatim over an Arduino `Stream`-compatible `WiFiClient`;
`motatool`'s `TcpTransport` mirrors its `SerialTransport`). A future framed link such as **BLE GATT** (an
Android phone relaying a folder) could carry the same ops with no magic/checksum at all - a request
characteristic write delivers `op + args`, the reply notifies `status + payload`. `motatool` reflects this
split: a transport-free `SeederCore` (the catalog logic) under a swappable framing/transport layer.
`motatool`'s `TcpTransport` mirrors its `SerialTransport`). The nRF52 Full
Companion's **BLE GATT** path also reuses the exact frame and checksum. Device
requests are notifications on a dedicated characteristic and host responses
are ordered write-with-response fragments on a second characteristic. Keeping
the same framing makes retries and corruption handling identical across USB,
TCP, and GATT. The Linux reference implementation is
`tools/ble_mota/ble_mota_seeder.py`; a phone app can implement the same
transport-free catalog operations.
---
+8 -5
View File
@@ -55,9 +55,9 @@ the GPS in sensor slot A. Slot D's reset/PPS lines conflict with the RAK13302
radio's BUSY/DIO1 lines.
The RAK4631 internal-flash OTA repeater likewise retains GPS. A RAK12501 can
use sensor slot A or D; a RAK12500 can use slot A or C. The RAK4631 Serial1
RS232 OTA bridge is the exception because the bridge owns the GPS UART. The
Serial2 RS232 OTA bridge retains GPS on Serial1.
use sensor slot A or D; a RAK12500 can use slot A or C. Its runtime RS-232
bridge defaults to Serial2 so GPS can retain Serial1. If you explicitly select
Serial1 with `set bridge.uart 1`, turn GPS off before enabling the bridge.
Selected nRF52 repeaters with dedicated external QSPI can now stage the
complete package off-chip, so their normal full-sensor repeater build can
@@ -394,14 +394,17 @@ remote area.
1. Put the firmware files (`.mota` files - see below) in a folder on the computer.
2. Install the helper tool once - the standalone `motatool` CLI (<https://github.com/vk496/motatool>) -
then point it at your node and the folder - over the node's **USB serial**, or over **WiFi** if it is
an ESP32 WiFi companion or FULL ESP32 node:
then point it at your node and the folder - over the node's **USB serial**, over **WiFi** if it is
an ESP32 WiFi companion or FULL ESP32 node, or over encrypted **Bluetooth** if it is an nRF52 Full Companion:
```
git clone https://github.com/vk496/motatool && cargo install --path ./motatool
# over USB serial:
motatool serve --dir ./my_firmware/ --serial /dev/ttyACM0 -v
# ...or over WiFi: the seeder is on dedicated TCP port 5001:
motatool serve --dir ./my_firmware/ --tcp 192.168.1.50:5001 -v
# ...or over paired BLE to an nRF52 Full Companion (protocol v14):
python3 tools/ble_mota/ble_mota_seeder.py \
--device MeshCore-MyCompanion --dir ./my_firmware/
```
It answers the node's requests; your node then advertises those updates to neighbours, who can
`ota get` them like any other. (A WiFi node prints its IP + seeder port to the serial log on connect.
+10 -2
View File
@@ -107,13 +107,21 @@ GPS idle behavior; it does not change LoRa RXPS or WiFi modem sleep.
get usb.logging
set usb.logging {on|off} [reboot]
```
Shows or changes persistent live USB debug and packet output in a Companion
logging artifact or Full Companion. Full starts off on a fresh install.
Shows or changes persistent live USB debug and packet output in an ordinary
USB-loggable Companion or Full Companion. USB Companion and Full start off on
a fresh install with logging disabled to protect framed traffic.
Dual-CDC Full changes its interface count after a reboot and keeps Companion on
interface `00`. Single-TTY Full needs no reboot: logging uses the active text
terminal, which continues accepting `set usb.logging off` and automatically
restores Binary Companion after that reply.
```
reboot
```
Sends an acknowledgement, then reboots the Companion one second later. The
delay gives either the USB terminal or the Full Companion TCP terminal on port
5002 time to deliver the reply before its transport disappears.
```
get radio.rxps
get radio.rxps.config