Add external QSPI staging for nRF52 OTA

This commit is contained in:
mikecarper committed 2026-08-20 17:51:52 -07:00
1 parent 56afcb8fd9
commit 509f831442
40 files changed
+1671 -157

No files matched your search

+1 -1
View File
@@ -41,7 +41,7 @@ retain 50 because their MQTT discovery tables are constrained by internal DRAM.
| 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. |
| FULL ESP32 | Uses the matching MQTT target with logging off and keeps the complete command surface supported by that role and hardware. |
| FULL ESP32 logging | Uses the matching non-MQTT target with debug and packet logging enabled, session-only `get/set usb.logging` control, and the complete command surface supported by that role and hardware. |
| `no_external_sensors` | Removes optional external-sensor drivers and their settings; it does not remove core repeater discovery or routing commands. |
| `no_external_sensors` | Removes optional external-sensor drivers and their settings; it does not remove core repeater discovery or routing commands. On RAK3401, this profile also compiles out GPS, so RAK12501 requires the ordinary full-sensor build. |
`logging`, `OTA`, and `FULL` describe independent build features. Do not infer
that a command is missing merely because `logging` appears in the filename.
+1 -1
View File
@@ -163,7 +163,7 @@ fix, no WiFi connection, an inactive bridge, or an nRF52 bootloader without
| LoRa OTA | [`ota`; `ota status`; `ota st`](ota_protocol.md#11-cli-surface-otaclicpp) | LoRa OTA build | No | No | Yes |
| LoRa OTA | [`ota stats`](ota_user_guide.md#1-see-what-im-running-and-whether-anything-is-going-on) | LoRa OTA build | No | No | Yes |
| 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 get`; `ota pull`; `ota download`](ota_protocol.md#11-cli-surface-otaclicpp) | LoRa OTA build; internal-store nRF52 installs in-place deltas, while matched SD/QSPI nRF52 also accepts full images | 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 cancel`; `ota drop`; `ota stop`](ota_protocol.md#11-cli-surface-otaclicpp) | LoRa OTA build | No | No | Yes |
+2 -1
View File
@@ -9,8 +9,9 @@ Below are a few quick start guides.
- [Filter Policy Playground](./filter_tool.md)
- [Telemetry Decoder](./telemetry_decoder.md)
- [CLI Availability by Firmware Build](./cli_build_matrix.md)
- [Easy LoRa OTA: ESP32 full images and nRF52 deltas](./ota_easy.md)
- [Easy LoRa OTA: ESP32 and nRF52 firmware updates](./ota_easy.md)
- [Scripted LoRa OTA: Bash and PowerShell](./lora_ota_automation.md)
- [nRF52 repeater OTA with external QSPI](./ota_nrf52_qspi.md)
- [MeshTower V2 microSD self-updates](./ota_meshtower_v2_sdcard.md)
- [GPS Tracking](./gps_tracking.md)
- [Companion Protocol](./companion_protocol.md)
+18 -7
View File
@@ -61,6 +61,7 @@ while the same link carries framed folder requests. BLE remains available.
| --- | --- | --- | --- |
| ESP32 | Full application image | OTA-enabled image with an A/B partition table | Builds a full mOTA from the matching non-merged application `.bin` |
| nRF52, internal flash | In-place delta | Exact-board OTAFIX bootloader with mOTA apply support | Requires `--base` with the exact image currently running |
| nRF52 repeater, external QSPI | Full image or in-place delta | QSPI-aware exact-board OTAFIX bootloader and matched repeater build | Builds a full mOTA; adding `--base` requests a delta |
| MeshTower V2 nRF52, microSD | Full image or in-place delta | SD-aware exact-board OTAFIX bootloader and compatible card | Builds a full mOTA; adding `--base` requests a delta |
The firmware inside a raw ZIP must have a valid MeshCore `EndF` trailer. An
@@ -68,7 +69,7 @@ ESP32 merged/factory image is not an application image and is rejected. A
generic vendor DFU ZIP may also be unusable if it does not contain the raw
EndF-bearing `.hex` or `.bin`.
For an internal-flash nRF52, the exact base image is irreducible information.
For an internal-staging nRF52, the exact base image is irreducible information.
The node reports its eight-byte body hash, but that hash cannot reconstruct the
firmware bytes needed to create a delta. Keep the `.pio/build/ENV/firmware.hex`
that was actually flashed. A matching filename or version alone is not enough.
@@ -177,7 +178,8 @@ ota stats
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
report `bootloader: apply OK`, `bootloader: QSPI 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
@@ -238,7 +240,7 @@ Prefer the environment variable or the interactive password prompt. Passing
local processes. The runner keeps the password out of child `meshcli` command
lines and removes its protected temporary command file after each call.
## 5. Run an internal-flash nRF52 update
## 5. Run an nRF52 update
If the input ZIP already contains a compatible in-place delta `.mota`, no
base argument is needed: its embedded base hash is compared with the live
@@ -258,16 +260,25 @@ node. If the ZIP contains raw new firmware, supply the exact running image:
--source-serial COM8
```
Before building the delta, the runner proves that the base's target ID,
Before building a delta, the runner proves that the base's target ID,
hardware identity, firmware version when available, and `EndF` body hash match
the live destination. It then asks `motatool` for codec 2, the nRF52 in-place
format. The normal workspace is `0x98000`.
For the SD-backed MeshTower V2 target, a raw ZIP becomes a full image without
`--base`. Supplying an exact base requests a smaller in-place delta and
automatically selects its `0xC7000` workspace. An explicit
For a QSPI-backed repeater or the SD-backed MeshTower V2 target, a raw ZIP
becomes a full image without `--base`. Supplying an exact base requests a
smaller in-place delta and automatically selects the conservative external
workspace `0xC6000`, which is safe for S140 v6 and v7 application layouts. An explicit
`--inplace-memory` overrides the automatic value.
The live runner detects QSPI from `ota self` (`QSPI apply OK`) or `ota status`
(`bl:QSPI`) and refuses an explicit `QSPI store:ERR 0K` report even when the
bootloader itself advertises QSPI apply support. Offline `--prepare-only` runs
must supply `--nrf-qspi`; do not use that switch for a board that only exposes
QSPI pins or uses the chip as a
Companion filesystem. The application and bootloader must both be from the
matched repeater list in [the nRF52 QSPI guide](ota_nrf52_qspi.md).
## 6. Add intermediate relays
List relays from farthest to nearest so each command is sent before its route
+81 -37
View File
@@ -9,12 +9,14 @@ For an end-to-end controller that accepts a release ZIP or ready mOTA, see
| Destination | Update type | Files needed to build the `.mota` | Installer |
| --- | --- | --- | --- |
| ESP32 | Full firmware | New non-merged application `.bin` | ESP32 A/B firmware slots |
| nRF52 | In-place delta | Exact running `firmware.hex` and new `firmware.hex` | Exact-board OTAFIX bootloader |
| nRF52, internal staging | In-place delta | Exact running `firmware.hex` and new `firmware.hex` | Exact-board OTAFIX bootloader |
| Supported nRF52 QSPI repeater | Full firmware or in-place delta | New `firmware.hex`; a delta also needs the exact running `firmware.hex` | Matching QSPI-aware OTAFIX bootloader |
| MeshTower V2 SD target | Full firmware or in-place delta | New `firmware.hex`; a delta also needs the exact running `firmware.hex` | Matching SD-aware OTAFIX bootloader |
A normal nRF52 target cannot install a full-image container. It deliberately accepts only an in-place
delta built against its exact running firmware. The MeshTower V2 microSD target is the exception because
it stages the complete container off-chip; see [MeshTower V2 microSD LoRa OTA](ota_meshtower_v2_sdcard.md).
An internal-staging nRF52 target accepts only an in-place delta built against its exact running firmware.
Matched QSPI repeater targets and the MeshTower V2 microSD target can also install a full image because the
complete container stays off-chip. See [nRF52 repeater OTA with external QSPI](ota_nrf52_qspi.md) and
[MeshTower V2 microSD LoRa OTA](ota_meshtower_v2_sdcard.md).
## Temporary OTA channel used in this guide
@@ -44,10 +46,11 @@ configurations. Confirm that it is permitted in your location and change it when
Both paths require:
- An OTA-enabled build whose artifact filename contains `-ota-` on the destination. The `-ota-` stamp confirms
that the node can discover, download, verify, and install LoRa OTA. Intermediate repeaters do **not** need an
OTA-enabled build: current repeater firmware relays OTA packets opaquely without storing or installing them.
Standard logging and untagged builds cannot install LoRa OTA; FULL MQTT and FULL logging OTA builds can.
- A destination artifact explicitly identified by its release table as install-capable. Some lean internal
nRF52 builds carry `lora_ota_no_external_sensors` in the filename, while matched QSPI boards enable install
support in the normal full-sensor repeater artifact, so filename text alone is not authoritative. Confirm
support with `ota self` and `ota status`. Intermediate repeaters do **not** need an install-capable build:
current repeater firmware relays OTA packets opaquely without storing or installing them.
- An OTA-enabled MeshCore source connected to the computer by USB serial, or
an ESP32 WiFi companion/FULL source connected over WiFi as described below.
- Overlapping `tempradio` windows on the source, destination, and every repeater needed between them.
@@ -57,14 +60,23 @@ apply their normal forwarding filters, duplicate checks, and flood limits; they
payload. If any required window closes, the transfer stops making progress and can resume during a later
overlapping window.
`build.sh` provides a `*_repeater_lora_ota_no_external_sensors` build for every standalone ESP32 and nRF52
repeater target. The normal repeater build keeps its external-sensor support and can serve as an intermediate
OTA relay, but it cannot download or install an update for itself. The `-ota-` sibling omits optional external
I2C environmental sensors to preserve the update workspace, while retaining board-native features such as its
display, buttons, battery monitoring, and integrated GPS. ESP32 `-ota-` siblings also retain the lightweight
browser WiFi uploader (`start ota`), the complete CLI, and a 254-entry neighbor table. RP2040 and STM32 repeaters do not
currently have a safe self-apply path, but current repeater firmware can still relay OTA packets opaquely
during TempRadio.
`build.sh` provides a `*_repeater_lora_ota_no_external_sensors` build for standalone ESP32 and nRF52 repeater
targets that need a smaller internal update workspace. Those siblings omit optional external I2C
environmental sensors while retaining board-native features such as displays, buttons, battery monitoring,
and integrated GPS where the target uses the GPS-preserving lean profile. The
RAK3401 target is an explicit exception:
`RAK_3401_repeater_lora_ota_no_external_sensors` compiles out GPS and cannot
use a RAK12501. Use the ordinary full-sensor `RAK_3401_repeater` build with a
RAK12501 in sensor slot A when GPS is required; slot D conflicts with the
RAK13302 radio's BUSY/DIO1 lines. 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. Other normal repeaters can still serve as
intermediate relays but cannot necessarily install an update themselves. ESP32
`-ota-` siblings also retain the lightweight browser WiFi uploader (`start
ota`), the complete CLI, and a 254-entry neighbor table. RP2040 and STM32
repeaters do not currently have a safe self-apply path, but current repeater
firmware can still relay OTA packets opaquely during TempRadio.
nRF52 `-ota-` siblings are compiled with size optimization instead of the Adafruit platform's default
speed optimization. This prevents the retained software Ed25519 fallback from expanding beyond the fixed
@@ -153,16 +165,17 @@ identity trailer so `motatool` and the destination can verify the target, hardwa
Do not continue if `motatool verify` reports a failure.
## nRF52: package an in-place delta
## nRF52: package a full image or in-place delta
### 1. Install and check the OTAFIX bootloader
This is a one-time prerequisite. Install the OTAFIX bootloader built for the destination's **exact board**
from the
[OTAFIX 2.4 nRF52 bootloader release](https://github.com/mikecarper/Adafruit_nRF52_Bootloader_OTAFIX/releases/tag/0.9.2-OTAFIX2.4).
Follow the release's board-specific installation and erase instructions. If it does not contain the
destination's exact board, this LoRa install path is not yet available for that board; never substitute a
similar board's bootloader.
[OTAFIX nRF52 bootloader releases](https://github.com/mikecarper/Adafruit_nRF52_Bootloader_OTAFIX/releases).
Internal-staging delta updates require OTAFIX 2.4 or newer. External QSPI and SD staging require OTAFIX
2.4.1 or newer and release notes that explicitly list the exact board and storage mode. Follow the release's
board-specific installation and erase instructions. If it does not contain the destination's exact board,
this LoRa install path is not yet available for that board; never substitute a similar board's bootloader.
Before preparing or downloading a LoRa update, run this on the destination:
@@ -172,17 +185,33 @@ ota self
```
The first command identifies the installed nRF52 bootloader. Continue only if
the `ota self` reply includes:
the `ota self` reply includes the apply mode required by this target:
```text
bootloader: apply OK
bootloader: QSPI apply OK
bootloader: SD apply OK
```
The reply also contains the running firmware's `base_hash`. Save it for the package check below. A stock,
legacy, or older OTAFIX bootloader without `.mota` in-place-apply support will report that apply support is
missing, and `ota install` will refuse to reboot into it.
### 2. Keep the exact current and new application images
### 2. Choose full or delta packaging
For a QSPI- or SD-backed target, a full update needs only the new raw
`firmware.hex`:
```bash
mkdir -p ./motas
motatool build --fw ./LilyGo_T-Echo_repeater-new.hex --out-dir ./motas
motatool verify ./motas/*.mota
```
Use a full package when an exact base image is unavailable. Use the delta path
below when reducing airtime is worth retaining the exact running image.
### 3. Keep the exact current and new application images for a delta
You need the raw `.pio/build/<environment>/firmware.hex` from the build that is **actually running**, plus
the corresponding `firmware.hex` from the new build. Save the current file before building the new version,
@@ -190,13 +219,16 @@ because PlatformIO reuses that path. For example:
```bash
# Save this immediately after building/flashing the version now running on the node.
cp .pio/build/Heltec_t114_repeater/firmware.hex ./Heltec_t114_repeater-running.hex
cp .pio/build/Heltec_t114_repeater_lora_ota_no_external_sensors/firmware.hex \
./Heltec_t114_repeater_lora_ota_no_external_sensors-running.hex
# After checking out and building the new version, save its image separately.
cp .pio/build/Heltec_t114_repeater/firmware.hex ./Heltec_t114_repeater-new.hex
cp .pio/build/Heltec_t114_repeater_lora_ota_no_external_sensors/firmware.hex \
./Heltec_t114_repeater_lora_ota_no_external_sensors-new.hex
```
Replace `Heltec_t114_repeater` with the destination's exact PlatformIO environment. The two images must be
Replace `Heltec_t114_repeater_lora_ota_no_external_sensors` with the destination's exact PlatformIO
environment. The two images must be
for the same board and role, and both must contain their `EndF` trailers. Do not pass a release `.uf2` or
BLE-DFU `.zip` to `motatool`; those are installation containers rather than raw application images.
@@ -206,14 +238,19 @@ running application. The hash check in the next step proves that it is the right
On RAK4631 repeaters, use the
`RAK_4631_repeater_lora_ota_no_external_sensors` environment. It retains built-in battery monitoring but
omits optional external environmental sensor packages so the delta fits the safe in-place workspace.
If the device has a RAK15001 installed in sensor slot C and the matching
RAK15001 OTAFIX bootloader, use
`RAK_4631_repeater_rak15001_slot_c_lora_ota` instead. That target retains the
full sensor/GPS set and can install either a full image or a delta from the
external 2 MiB store.
### 3. Build and check the in-place delta
### 4. Build and check the in-place delta
```bash
mkdir -p ./motas
motatool build \
--base ./Heltec_t114_repeater-running.hex \
--fw ./Heltec_t114_repeater-new.hex \
--base ./Heltec_t114_repeater_lora_ota_no_external_sensors-running.hex \
--fw ./Heltec_t114_repeater_lora_ota_no_external_sensors-new.hex \
--patch-type in-place \
--out-dir ./motas
motatool verify ./motas/*.mota
@@ -233,9 +270,12 @@ Check all three of these before serving it:
firmware version identify the intended board and role. If `inspect` shows `N/A` for the human-readable
target name, the tool's name table is older than that environment; the numeric IDs still must match.
The default `--inplace-memory 0x98000` and 4096-byte segment size match the supported MeshCore OTAFIX
builds; do not override them for this normal nRF52 flow. Do not continue if verification or any identity
check fails.
Current layout-aware `motatool` derives the safe workspace from the new firmware's authenticated EndF layout
record, so the normal recipe deliberately omits `--inplace-memory`. If older tooling requires an explicit
override, use `0xC6000` only for matched external SD/QSPI staging and `0x98000` for an internal-staging nRF52
such as the RAK4631 lean OTA build. Do not substitute the external value on an internal target, and do not
override either value unless you have verified the exact app base, bootloader, and package geometry. Do not
continue if verification or any identity check fails.
## Transfer and install either package
@@ -290,8 +330,9 @@ ota ls
```
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).
shows an older row. Select `[same target]`: it can say `full` for ESP32 and external SD/QSPI nRF52 targets,
or `delta` for any supported nRF52 target. Do not select `[unsupported]` (for example, a source's self-served
full image on an internal-staging nRF52).
Use the row's stable eight-hex manifest ID rather than its changing list position:
```text
@@ -335,8 +376,9 @@ ota install
```
The destination verifies the complete package again before approving it. ESP32 installs the full image into
its inactive A/B slot. nRF52 checks the base hash and bootloader capability, then reboots into OTAFIX; the
bootloader independently rechecks the package, applies the delta in place, and verifies the resulting image.
its inactive A/B slot. nRF52 checks the bootloader and storage capabilities and, for a delta, the base hash,
then reboots into OTAFIX. The bootloader independently rechecks the package, installs the external full image
or applies the delta in place, and verifies the resulting image.
Pre-install failures leave the running firmware unchanged and report the reason. If power is lost after an
nRF52 in-place apply has begun, OTAFIX will not boot a partial image; it enters recovery DFU so a known-good
application can be restored.
@@ -357,7 +399,9 @@ ota status
- **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.
A matched QSPI repeater or MeshTower V2 microSD target accepts full images with its corresponding bootloader.
- **A QSPI nRF52 reports `QSPI store:ERR 0K` or `bl:NO-QSPI`:** do not download an install package. Install
the exact QSPI-aware bootloader and check that the selected application matches the board's flash wiring.
- **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
+190
View File
@@ -0,0 +1,190 @@
# nRF52 repeater LoRa OTA with external QSPI
Selected nRF52840 repeater builds use their dedicated external QSPI NOR flash as
a raw LoRa OTA staging device. This removes the internal-flash staging conflict:
the complete `.mota` stays off-chip, and the bootloader can use the entire
internal application region while it installs either a full image or an
in-place delta.
This is a matched application-and-bootloader feature. A board merely having an
nRF52840, free RAM, or pins named QSPI is not enough. Both halves must use the
exact flash wiring, and `ota self` must confirm the store and bootloader before
an update is downloaded.
## Supported repeater families
QSPI staging is enabled only for repeater-role environments on these currently
matched families:
- Seeed XIAO nRF52840 and XIAO nRF52840 Sense modules, including the
`Xiao_nrf52`, SolarXiao 30S/33S, and XIAO-module Ikoka handheld, Nano, and
Stick repeaters
- original LilyGo T-Echo
- Elecrow ThinkNode M1 and M6
- Seeed Wio Tracker L1
- Seeed SenseCAP Solar Node P1
- RAK4631 with a RAK15001 in WisBlock sensor slot C, using the dedicated
`RAK_4631_repeater_rak15001_slot_c_lora_ota` application and matching
`wiscore_rak4631_board_rak15001_slot_c` OTAFIX bootloader
Heltec T114 is intentionally not in this list. Its public V1, V2.0, and V2.1
schematics show U9 (MX25R1635F) as an optional QSPI footprint, so standard T114
application and bootloader targets do not assume that external NOR is populated.
The XIAO-module derivatives use the matching XIAO or XIAO Sense OTAFIX
bootloader shown by the module's `INFO_UF2.TXT`. Do not substitute a similar
bootloader for a board with different QSPI pins. For example, the T-Echo Card
and T-Echo Lite have different flash wiring and are not enabled by the original
T-Echo target.
Companion builds are intentionally excluded. Some companion targets use the
same external QSPI as a LittleFS message/data store, where raw OTA staging
would corrupt the filesystem; other companions simply do not assign that chip
to OTA. Room-server, sensor, KISS, and repeater-bridge roles are also unchanged.
Raw QSPI OTA is scoped to the explicitly matched repeater environments.
### RAK15001 placement and module conflicts
[RAK15001](https://docs.rakwireless.com/product-categories/wisblock/rak15001/datasheet/)
is a 2 MiB GD25Q16C **standard SPI** module, not a quad-I/O flash. The dedicated
RAK4631 target uses the nRF52840 QSPI peripheral in its single-data-line
FAST_READ/page-program modes at 8 MHz and accepts only the module's exact
`C8 40 15` JEDEC ID. `ota self` reports `QSPI store:2048K` only when the
expected module responds. An empty slot or a different SPI device reports
`QSPI store:ERR 0K`, and install is refused.
The module is electrically usable in sensor slot A-D because those slots share
the SPI signals and RAK15001 has onboard 10 kOhm pull-ups on WP# and HOLD#.
The supported MeshCore/OTAFIX combination nevertheless requires **slot C**. It
is the placement that remains safe when a
[RAK12501 GNSS](https://docs.rakwireless.com/product-categories/wisblock/rak12501/datasheet/)
is fitted in either of its supported slots, A or D: slot C avoids the GNSS
PPS/reset nets on IO1/IO2 and IO5/IO6. In particular, GPS in A plus flash in B
would share IO1/IO2 through the modules' auxiliary pins.
Only one device that uses the shared WisBlock SPI chip-select may be fitted.
Do not combine this target with RAK13800 Ethernet, RAK15002 SD, or another SPI
module. RAK13800 and RAK15001 cannot coexist because they use the same SPI
chip-select. Update a RAK13800 Ethernet build locally over USB using the
release's **Manual UF2** or **Serial DFU (.zip)** download; it cannot use this
RAK15001 LoRa-OTA staging target.
RAK3401 is intentionally unsupported. Its external RAK13302 1 W radio already
uses the same WisBlock SPI clock/data pins **and the same chip-select** as
RAK15001. Firmware cannot independently select or detect the two chips, so a
stock RAK3401 + RAK15001 assembly cannot provide reliable OTA staging without
a hardware chip-select rework.
## One-time prerequisite
Install a QSPI-capable OTAFIX 2.4.1 preview.8 or newer bootloader for the exact
board from the
[OTAFIX releases](https://github.com/mikecarper/Adafruit_nRF52_Bootloader_OTAFIX/releases)
before using LoRa OTA. The release notes must explicitly list that board's QSPI
mode. Also install the SoftDevice version expected by that target. The
application refuses the install handoff when the bootloader does not advertise
QSPI support.
For the first migration from the ordinary `wiscore_rak4631_board` bootloader to
`wiscore_rak4631_board_rak15001_slot_c`, use Nordic serial DFU or a compatible
BLE DFU client with the exact slot-C OTAFIX **combined bootloader + SoftDevice
DFU package** (or use SWD), then reinstall the slot-C MeshCore application. The
release filename has this form:
```text
wiscore_rak4631_board_rak15001_slot_c_bootloader-<OTAFIX-version>_s140_6.1.1.zip
```
Do not copy the slot-C bootloader-update UF2 onto the stock UF2 drive. The
stock loader is bound to `DEVICE_NAME=4631_DFU`, while the slot-C image is bound
to `4631_15001C_DFU`, so that UF2 is intentionally rejected. After the one-time
DFU/SWD migration, later canonical slot-C bootloader UF2 files work normally.
The combined OTAFIX package above is not the MeshCore application's Serial DFU
`.zip`; an application package does not migrate the bootloader.
After installing the repeater application, check:
```text
get bootloader.ver
ota self
ota status
```
A ready target reports all of the following:
```text
QSPI store:2048K
bootloader: QSPI apply OK
bl:QSPI
```
Other supported boards can report a capacity different from 2048K; the
RAK15001 target must report exactly 2048K. `QSPI store:ERR 0K`, `NO QSPI`, or
`bl:NO-QSPI` means the flash wiring, flash power, or bootloader does not match.
Do not start an install in that state.
## Capacity and package types
The store reads the JEDEC capacity at runtime and accepts supported 1 MiB
through 16 MiB devices using 24-bit addressing. QSPI capacity is not the final
firmware limit. The reconstructed application, including its 56-byte `EndF`
trailer, must fit below InternalFS at `0xED000`:
| SoftDevice layout | Application region | Maximum image |
| --- | --- | --- |
| S140 v7, app base `0x27000` | `0x27000..0xED000` | `0xC6000` (811,008 bytes) |
| S140 v6, app base `0x26000` | `0x26000..0xED000` | `0xC7000` (815,104 bytes) |
A full package needs only the new raw `firmware.hex` or non-merged application
image. An in-place delta still needs the exact image currently running. The
automation uses a conservative `0xC6000` detools workspace for external nRF52
staging so one package setting is safe for both layouts.
For online automation, QSPI is detected from `ota self` or `ota status`. For
offline preparation, identify it explicitly:
```bash
./tools/lora_ota/lora_ota.sh ./release.zip target-name \
--prepare-only \
--platform nrf52 \
--nrf-qspi \
--target-id 12345678 \
--target-hw Xiao_nrf52
```
Use the real target ID and hardware identity from the destination. A ready
full `.mota` normally uses that target ID for discovery and routing. An
operator can deliberately override the routing target for a role change, so
`target_id` is not an apply-time safety assertion; the destination still
enforces the package's hardware identity before approval.
## Storage ownership and recovery
The QSPI store is raw, not a file inside LittleFS. It owns the flash from
offset zero, erases 4 KiB sectors as blocks arrive, writes data before progress
metadata, and verifies every programmed page. A previously interrupted
download is reopened only when its header and trailer are valid; every claimed
block is re-hashed before it is trusted. Between a probe, transfer operation,
or checkpoint, firmware puts the NOR into deep power-down, deactivates the nRF
QSPI peripheral, and turns off a board-provided flash power-enable pin. The
next operation powers and identifies the chip again, so merely running
`ota self` does not leave QSPI drawing active-mode current.
Installing a QSPI repeater build over a former companion build therefore
repurposes the external flash and destroys companion filesystem data as OTA
sectors are written. Back up anything important first. Returning to a companion
build may require formatting its external data store.
Before changing internal application flash, the application verifies package
integrity, hardware identity, signature policy, and bootloader capabilities.
The target ID selects discovery/fetch routing and can be deliberately
overridden; it is not a second hardware gate. The bootloader then verifies a
full payload before its first application erase. For a delta, the application
rejects invalid detools geometry before approval and the bootloader independently
repeats the base and geometry checks before applying it.
It clears the one-shot approval marker before invalidating the running image.
If power is lost after application writes begin, the bank remains invalid and
OTAFIX enters USB/BLE recovery rather than booting a partial image.
See [Easy firmware updates over LoRa](ota_easy.md) for the transfer commands and
[the OTA protocol](ota_protocol.md) for the container and handoff details.
+45 -25
View File
@@ -126,9 +126,13 @@ established target-specific 1920 KiB or larger A/B app layout and are checked ag
partition. For every standalone ESP32 and nRF52 repeater, `build.sh` also exposes an explicit
`*_lora_ota_no_external_sensors` artifact: the ordinary repeater remains sensor-enabled, while that sibling
disables optional external environmental-sensor drivers for LoRa distribution. Integrated GPS and other
board-native telemetry remain enabled. 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.
board-native telemetry remain enabled where the target selects the GPS-preserving lean profile. RAK3401 is
the explicit exception: `RAK_3401_repeater_lora_ota_no_external_sensors` undefines `ENV_INCLUDE_GPS`, so it
does not detect or use a RAK12501. RAK12501 GPS requires the ordinary full-sensor `RAK_3401_repeater` build
and sensor slot A; slot D conflicts with the RAK13302 radio's BUSY/DIO1 lines.
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.
nRF52 LoRa-OTA siblings use size optimization rather than the Adafruit platform's default `-Ofast`. This
keeps the runtime software Ed25519 fallback from being expanded into tens of kilobytes of repeated curve
@@ -257,7 +261,7 @@ cover `approval` or `leaves[]`:
| `codec_id` | Meaning | Used by |
|---|---|---|
| 0 | full / raw | PAYLOAD = reconstructed image (`BODY||EndF`). ESP32 A/B or the SD-backed MeshTower V2 target. |
| 0 | full / raw | PAYLOAD = reconstructed image (`BODY||EndF`). ESP32 A/B or an external SD/QSPI nRF52 target. |
| 1 | detools **sequential** | random read of base + sequential write of result -> ESP32 A->B inactive slot. |
| 2 | detools **in-place** | bounded scratch; rewrites the app region in place -> nRF52 single-slot. |
@@ -266,9 +270,9 @@ delta only if `base_hash` matches its own `EndF.body_hash`. After applying, the
(sha2-256:32) to `image_hash` before it is booted - the hard security gate.
**A fetcher only requests firmware it can apply.** Each node declares the codec(s) it can apply
(`set_apply_codec`/`set_apply_codec2`): ESP32 accepts `full` + `sequential` (+ `in-place`). Normal nRF52
targets accept only `in-place` because internal flash cannot stage a full application image. The
MeshTower V2 SD target accepts `full` + `in-place` because the card holds the container. A `.mota` with
(`set_apply_codec`/`set_apply_codec2`): ESP32 accepts `full` + `sequential` (+ `in-place`). Internal-staging
nRF52 targets accept only `in-place` because internal flash cannot hold a second full application image.
Matched SD and raw-QSPI nRF52 targets accept `full` + `in-place` because external media holds the container. A `.mota` with
an unsupported codec is rejected at discovery time, before any blocks are requested. A manual pull to
an external folder may accept other codecs because that path captures bytes and never installs them.
@@ -599,8 +603,9 @@ transmission per hop.
independent of signature.
- **Signing & allowlist:** a node keeps a runtime allowlist of trusted Ed25519 signer pubkeys (none embedded
in firmware; `ota key add/list/rm`). A `.mota` is eligible for **auto-install** only if signed by an
allowlisted key, the signature verifies, and `image_hash` matches; otherwise it is manual-apply only with
explicit confirmation. **Transfer needs no trust** - blocks are content-addressed against the signed root.
allowlisted key, the signature verifies, and `image_hash` matches. Manual install permits unsigned packages,
but a package that claims to be signed must have a valid signature from an allowlisted key or it is rejected.
**Transfer needs no trust** - blocks are content-addressed against the manifest's merkle root.
- **Policies (persisted):** `autofetch` in {off, any, signed} (default off) gates automatic block fetching of
own-target adverts; `autoinstall` in {off, trusted} (default off) gates auto-apply of a COMPLETE signed +
allowlisted fetch. Conservative defaults: a fresh node discovers + announces but never fetches/installs
@@ -758,26 +763,31 @@ ota dev ... bring-up helpers (stage/recv/serve/verify)
- **ESP32 (A/B):** applied in-firmware via the detools decoder into the inactive OTA slot
(`OtaApply.cpp::ota_apply_detools_mota` + `OtaStoreFlashEsp32`), then set-boot + reboot (power-safe,
rollback-capable). No bootloader changes. Erase ranges must be sector-aligned (4096).
- **nRF52 (single-slot):** the running firmware **never** flashes the app. `ota applydelta` verifies fully
(`image_hash`, `base_hash`, signature/allowlist, `hw_id`), writes `approval = "APRV"`, then reboots into
the modified bootloader (`Adafruit_nRF52_Bootloader_OTAFIX`). The bootloader:
1. **scans flash for `MAGIC`** to find the staged `.mota` (it must NOT trust any stored size),
2. re-checks `TRAILER`, `image_hash`, `approval == "APRV"`, and that the delta's `base_hash` equals the
running firmware's `EndF.body_hash` (recomputed by scanning for `EndF` - never trust `bank_0_size`),
3. applies the in-place codec over the app region and boots only if the result hashes to `image_hash`.
- **nRF52 internal staging ceiling:** the application derives the ceiling from facts available in every
build, not a board-name list. A companion that actually links the internal ExtraFS datastore stays below
`0xD4000`; a default linker region, QSPI secondary storage, or a role that does not mount ExtraFS can
reclaim the unused 100 KiB through `0xED000`. The application uses the larger window only when the
- **nRF52 (single-slot):** the running firmware **never** flashes the app. `ota install` verifies the
container fully (`image_hash`, codec, signature/allowlist, `hw_id`, and `base_hash` for a delta), writes
`approval = "APRV"`, then reboots into the modified bootloader
(`Adafruit_nRF52_Bootloader_OTAFIX`). The bootloader:
1. locates the staged `.mota` in the approved internal, raw-SD, or raw-QSPI store without trusting an
unchecked stored size,
2. re-checks `TRAILER`, `image_hash`, and `approval == "APRV"`; for a delta it also checks that
`base_hash` equals the running firmware's `EndF.body_hash` (recomputed by scanning for `EndF` - never
trust `bank_0_size`),
3. writes a full external-media payload or applies the in-place codec over the app region, then boots only
if the result hashes to `image_hash`.
- **nRF52 internal staging ceiling:** an internal-store application derives the ceiling from facts available
in every build, not a board-name list. A companion that actually links the internal ExtraFS datastore stays
below `0xD4000`; a default linker region or a role that does not mount ExtraFS can reclaim the unused
100 KiB through `0xED000`. The application uses the larger window only when the
installed bootloader advertises the GPREGRET2 ceiling-handoff capability. The bootloader treats every
unknown/legacy handoff value as
`0xD4000`, and accepts a container only at the bottom-aligned position for the selected ceiling.
- **nRF52 dynamic apply window:** the post-build hook records the resolved app base, linked app end,
storage flags, and desired staging ceiling immediately before `EndF`. `motatool` reads that authenticated
internal-ExtraFS/SD/QSPI storage flags, and desired staging ceiling immediately before `EndF`. `motatool` reads that authenticated
firmware record and chooses `memory_size` from the actual patch size and bottom-aligned stage address;
firmware without the record retains the conservative `0x98000` default. Before writing `APRV`, the app
verifies the detools header fits below the staged container, and the bootloader independently repeats
the geometry check before its first application write. Expanded auto-sized packages require a bootloader
firmware without the record retains the conservative `0x98000` default. Before writing `APRV`, an
internal-store app validates the staged-address bound; an external SD/QSPI app validates the full detools
geometry against the application workspace. The bootloader independently parses and validates the same
geometry before its first application write. Expanded auto-sized packages require a bootloader
with the ceiling-handoff capability; use `--inplace-memory 0x98000` when intentionally targeting an older
bootloader and the images still fit that window.
- **nRF52 EndF rescue:** `ota rescue install <base_hash16>` is a pre-provisioned recovery path for an
@@ -798,8 +808,18 @@ ota dev ... bring-up helpers (stage/recv/serve/verify)
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,
verifies the staged/full result hash, and never writes through `0xED000` where InternalFS begins.
- **Matched external-QSPI nRF52 repeaters:** the application reserves the board's dedicated QSPI NOR as a
raw store beginning at offset zero. It obtains a 1-16 MiB capacity from JEDEC, checkpoints payload before
leaf metadata, and verifies each erased/programmed page. GPREGRET2 `0x51` selects QSPI only when the
matching bootloader advertises the QSPI storage bit; legacy markers retain the internal scan path. The
bootloader pre-hashes a full payload before invalidating the app, or applies an in-place delta with the
complete internal application region as workspace. Companion builds never enable this raw store: some use
QSPI as a filesystem, while others simply leave that chip outside OTA ownership. See
[the nRF52 QSPI guide](ota_nrf52_qspi.md).
The signature proves author authenticity; `approval` proves local owner consent - both required to apply.
A signature, when present, proves author authenticity and must pass the device allowlist. Unsigned packages
remain installable when local policy permits them. The one-shot `approval` marker records local consent for
either form before the bootloader may apply it.
> **Bootloader testing note:** always test apply with a *real different* image (base != target). A same-image
> (X->X) "delta" trivially reproduces the target and gives a false positive.
+45 -18
View File
@@ -4,9 +4,12 @@ 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 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
LoRa OTA download and installation are present only in supported Keymind destination artifacts; the receiver
must already be running one of those install-capable builds. Some internal-staging nRF52 targets use a lean
`lora_ota_no_external_sensors` target, while matched external-QSPI boards can retain their normal full-sensor
repeater features. Release filenames include an OTA marker, but capability must still be confirmed on the
running device. 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
@@ -21,23 +24,45 @@ tempradio 909.950,250,5,5,120
Use the node's current permitted regional frequency in place of `909.950` when necessary.
> **Can my node install the update?** Choose a supported repeater artifact carrying the `-ota-` filename stamp.
> **Can my node install the update?** Choose a release-table artifact explicitly labelled LoRa-OTA capable,
> then confirm `ota self` and `ota status` expose install support; do not infer support from the filename alone.
> LoRa OTA firmware is available for supported **ESP32** boards and nRF52 repeater targets. Every nRF52
> installation also requires the OTAFIX bootloader built for that exact board; having an OTA-capable
> application image alone is not enough. An intermediate repeater only relays packets and needs neither the
> `-ota-` image nor OTAFIX. Check the bootloader release for an exact board match before attempting an update.
> application image alone is not enough. An intermediate repeater only relays packets and needs neither an
> install-capable image nor OTAFIX. Check the bootloader release for an exact board match before attempting an update.
The following nRF52 repeater targets gained firmware-side LoRa OTA support in this release without losing
their normal external-sensor support:
The following nRF52 repeater families gained firmware-side LoRa OTA targets in
this release. Their ordinary repeater remains the full-sensor build; the
install-capable `lora_ota_no_external_sensors` sibling is smaller:
- Heltec Mesh Solar, T1, and Tower V2
- Keepteen LT1, LilyGo T-Impulse Plus, Mesh Pocket, and Nano G2 Ultra
- Minewsemi ME25LS01, RAK3401, SenseCAP Solar, and Wio WM1110
The full-sensor `RAK_4631_repeater` image is too large for the safe nRF52 in-place update limit. Use
`RAK_4631_repeater_lora_ota_no_external_sensors` when LoRa OTA is required. That target removes optional
external environmental/GPS sensor packages, but retains the RAK4631's built-in battery-voltage reading,
battery telemetry, and `battery.alert` behavior.
RAK3401 is an important GPS exception: its
`RAK_3401_repeater_lora_ota_no_external_sensors` image compiles out GPS support
as well as the optional environmental sensors. It will not detect or use a
RAK12501. For RAK12501 GPS, use the ordinary full-sensor
`RAK_3401_repeater` build and install the GPS in sensor slot A. Slot D's GPS
reset/PPS lines conflict with the RAK13302 radio's BUSY/DIO1 lines. The
full-sensor RAK3401 build is not the self-updating target described by the
RAK3401 compact OTA chain.
Selected nRF52 repeaters with dedicated external QSPI can now stage the
complete package off-chip, so their normal full-sensor repeater build can
install a full image or an in-place delta. The current matched families are
XIAO nRF52840 and its XIAO-module derivatives, original LilyGo T-Echo,
ThinkNode M1/M6, Wio Tracker L1, SenseCAP Solar, and the dedicated RAK4631 +
RAK15001 slot-C target. These require the corresponding QSPI-aware OTAFIX bootloader; see
[the nRF52 QSPI guide](ota_nrf52_qspi.md).
The ordinary full-sensor `RAK_4631_repeater` image remains too large for the
safe internal in-place update limit. Without external flash, use
`RAK_4631_repeater_lora_ota_no_external_sensors`; it removes optional external
environmental/GPS packages but retains battery monitoring. A RAK4631 fitted
with RAK15001 in sensor slot C can instead use
`RAK_4631_repeater_rak15001_slot_c_lora_ota` to retain the full sensor/GPS set
and stage full images or deltas off-chip.
---
@@ -112,7 +137,8 @@ long ago it was seen. The fit marker:
- **[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.
example is the source node's self-served **full** image on an internal-staging nRF52, which needs an
in-place delta. A matched external-QSPI nRF52 can accept that full codec.
- **[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.
@@ -184,9 +210,9 @@ ota install
```
The node verifies the firmware one last time, and if everything checks out it installs it and **reboots
into the new version**. If the check fails, it tells you why and does **not** install. (If you haven't
added the signer's key, an unsigned/untrusted image will only install with this explicit command - never
automatically.)
into the new version**. If the check fails, it tells you why and does **not** install. Unsigned images
install only through this explicit command. A signed image whose signer is not in the device allowlist is
rejected; trusted signed images can auto-install only when that policy is enabled.
After it reboots, run `ota status` to confirm the new version.
@@ -263,8 +289,9 @@ ota key list # show trusted signers
ota key rm <public-key-hex> # stop trusting one
```
Only updates signed by a trusted key are eligible for auto-install. Manual `ota install` still lets you
install anything yourself, on your own responsibility.
Only updates signed by a trusted key are eligible for auto-install. Manual `ota install` permits an unsigned
package after all integrity, hardware, base, and bootloader checks pass. A signed package whose signer is not
in the device allowlist is rejected rather than silently treated as unsigned.
---
+10
View File
@@ -34,6 +34,16 @@ target:
- endpoint packed version: `0x01110102` (EndF tools render this as `1.17.1.2`)
- deployment target key: `63d8df6387eaffd2e25db7d2a8ad967a65202182a48d681d7e7a9260f917280d`
### GPS limitation
This chain's exact `RAK_3401_repeater_lora_ota_no_external_sensors` endpoint
compiles with `ENV_INCLUDE_GPS` removed. It will not detect, configure, or read
a RAK12501 GPS module. GPS requires the ordinary full-sensor
`RAK_3401_repeater` firmware and the RAK12501 must be installed in sensor slot
A. Do not use slot D with the RAK13302 1 W radio: the GPS reset/PPS signals
would overlap the radio's BUSY/DIO1 signals. The full-sensor build is a
different target and is not an endpoint of this compact OTA chain.
Do not use the chain on another target ID, hardware family, starting image, or
firmware body hash. The runner checks all four.