View this page on MeshCore Docs.
RAK3401: 1.16.07 to 1.17.1.7 without a bootloader update
Qualified on MercerWoodMesh on 27 September 2026: all 15 application updates
passed over LoRa, from the exact supplied 1.16.07 image, without a bootloader
update. Use RAK3401-1.16.07-to-1.17.1.7-legacy24-mOTA.zip and verify it against
the accompanying .zip.sha256 file before extracting. Follow the exact
before/after hashes in its CHAIN.csv; this is not a single-step stock update.
Exact compatibility boundary
This is an application-only migration for target 2FA509C1, hardware
RAK_3401_repeater_lora_ota_no_external_sensors. It is not a generic RAK4631
package. The installed application must have body hash 71F4026CBE4B8B74
(1.16.07, full image SHA-256
4301fc63ebd661c70f9bc40e7eca44d3cb8a358cb3c0075e700d9676a57478cb).
The bootloader remains 0.9.2-OTAFIX2.4, with ABI 2 and codec mask 0x4.
Its application staging ceiling is 0xD4000. Every package is a codec-2
CRLE in-place delta. DEFLATE is used only for transport by compatible
intermediate applications; the old bootloader still applies CRLE.
The destination is built from source commit
2d03e098cf75d58adcf35fe3d4dbbeaf0af114e1 with the explicit
MESHCORE_NRF52_LEGACY_FLASH_OTA=1 build selection. This preserves the
ordinary S140 linker and disables hybrid flash/RAM staging and bootloader
self-update. It is deliberately different from the stock hybrid 1.17.1.7
release. The endpoint has:
- Version:
v1.17.1.7-halo-keymind-cascade-legacy24-2d03e098 - Image: 521,496 bytes
- Body hash:
D437D1F17AF5B9F3 - SHA-256:
c9e63a94f58f881b1853fc5d9d348cd75a6cdd454c5b572341d652a6141e3157
Do not replace this endpoint with the stock ZIP. Do not send a bootloader or SoftDevice update. The ZIP/UF2 files used to construct and qualify the chain are not themselves LoRa update containers.
Future application updates for this node must also be qualified against the unchanged OTAFIX2.4 contract. Keep the legacy build selection; do not assume that a later stock release is an interchangeable target for this chain.
What "smallest" means
The search compares an explicit, hash-pinned set of application images and every allowed page-aligned in-place workspace for each increasing-version transition. The starting bootstrap is fixed: its 89,844-byte package is the only admitted first edge. Synthetic bridge version numbers are real compiled versions below 1.17.1.7; their binaries are not relabeled after compilation.
The bytes objective minimizes ideal serialized MeshCore traffic first,
then install count. The packets objective minimizes ideal originated
packets first, then serialized traffic. The older transport objective
minimizes install count before traffic and is not equivalent to either.
Accounting includes manifests, block requests, proof requests/responses, DATA fragments, and MeshCore framing. It uses the actual pinned encoder for compressed blocks. It excludes discovery, retries, admin/control commands, other mesh traffic, flood fan-out, and LoRa preamble/coding overhead. A fixed linear relay path can be accounted for separately. These are finite-search minima, not a proof that no possible custom firmware or protocol could ever use fewer bytes. The ideal compressed counts also assume negotiation remains in that mode. An older receiver can fall back to raw DATA after stalls; the physical-test transmission totals include that extra traffic.
Best lossless byte count is not a guarantee of the fastest real radio link. Use the fastest preset that remains reliable over the actual route. SF5 / 500 kHz is a direct bench measurement, not a recommendation to switch an unreachable field node or unconfigured relay chain to that preset.
Selected route
The completed search covers 52 images and 47,920 flash geometries. The packet-first route has 15 packages, totaling 2,340,160 container bytes. Its ideal direct-link traffic is 12,621 packets / 1,808,961 serialized MeshCore bytes. These totals include traffic in both directions, not just DATA. Fifteen is also the fewest installs among the searched candidates.
The byte-first route uses the same sequence of application images, with four different patch workspaces: 12,625 packets / 1,808,787 bytes. Saving 174 bytes costs four packets. The delivered packages use the packet-first choice; do not substitute packages from the comparison route.
| Step | Resulting version | Container bytes | Ideal packets | Ideal MeshCore bytes |
|---|---|---|---|---|
| 1 | 1.16.7.9 | 89,844 | 878 | 101,203 |
| 2 | 1.16.7.10 | 62,838 | 551 | 70,031 |
| 3 | 1.16.8.7 | 106,125 | 496 | 70,134 |
| 4 | 1.16.9.105 | 149,542 | 728 | 104,992 |
| 5 | 1.16.9.110 | 112,540 | 489 | 67,887 |
| 6 | 1.16.9.111 | 71,070 | 268 | 35,453 |
| 7 | 1.16.9.116 | 138,545 | 692 | 101,308 |
| 8 | 1.16.9.122 | 177,536 | 995 | 148,671 |
| 9 | 1.16.10.1 | 258,398 | 1,477 | 223,042 |
| 10 | 1.16.11.0 | 245,426 | 1,450 | 219,953 |
| 11 | 1.16.12.1 | 217,608 | 1,167 | 172,794 |
| 12 | 1.16.13.1 | 199,395 | 1,024 | 150,005 |
| 13 | 1.16.14.0 | 199,527 | 989 | 143,160 |
| 14 | 1.16.15.0 | 139,188 | 618 | 86,721 |
| 15 | 1.17.1.7 | 172,578 | 799 | 113,607 |
Some bridge versions also exist as different binaries. CHAIN.csv's exact
body hashes and package checksums, not this version-only table, determine
which package is valid at each step. The first two transfers use legacy
1 KiB blocks; subsequent transfers use negotiated 2 KiB blocks with
transport DEFLATE and the unchanged CRLE bootloader format.
The second transfer also omits explicit proof requests: that historical
receiver waits for the current sender's proactive Merkle proofs. The original
1.16.07 receiver does not have this optimization.
Measured MercerWoodMesh replay
All 15 selected packages passed in order on the Pi's debug-wired RAK3401, starting with the exact 1.16.07 image and OTAFIX2.4. Every application payload crossed LoRa from the Heltec T096. There were no debug writes, application substitutions, failed installs, or resumed downloads between the 15 steps.
The direct bench used 909.950 MHz, BW 500 kHz, SF5, CR5, continuous receive, and a 128-symbol source preamble. Observations for both radios totaled:
- 14,069 transmitted frames, including discovery and retries.
- 22 minutes 31 seconds of measured download time.
- 47 minutes 35 seconds summed active test-step time, including configuration, checks, installs, and reboots; deliberate gaps between runs are excluded.
These are bench measurements, not a field-link completion-time promise. The
control commands were sent over USB; a remote field console adds LoRa control
traffic. BENCHMARK.csv separates each step's ideal cost from its observed
frame count and timing. Actual serialized radio bytes were not measured.
Independent debug-port readback matched the complete final application, unchanged bootloader, and unchanged MBR/SoftDevice. UICR matched the normal post-startup baseline throughout this replay. The initial pre-first-boot UICR dump differs because normal Nordic/RAK startup configures the reset pins and 3.3 V regulator; this is documented in the evidence, not a bootloader upgrade.
After a fresh reboot, the watchdog reported on. Both radios returned to
910.525 MHz / BW 62.5 kHz / SF7 / CR5, and a nonce-matched normal-channel trace
through the exact target key prefix completed in 423 ms. The inaccessible
field node has not been touched. The lab RAK remains on this repeater image;
the Pi's mctomqtt.service remains stopped because it previously owned that
serial port. Radios other than the RAK3401 and sending Heltec T096 were left
untouched.
Field preparation
All destination commands below can be sent through the authenticated remote
repeater console; USB/Bluetooth at the destination is not required. Keep a
separate control connection available while the sender owns its companion's
serial port. A local
Full Companion radio attached to the sender runs the bundled motatool
seeder. Resolve ports by device identity, not a remembered ttyACM number.
The qualified source is the Pi's Heltec T096, USB serial
651F8E496197F882, running Full Companion
v1.17.1.8-halo-keymind-cascade-dev-d4b3bb56. It supports the extended
2 KiB transport and host-backed DEFLATE. The destination is RAK3401 USB
serial 0B81C9C68D8D01B4. Older companion firmware or an older motatool
is not an interchangeable sender for this bundle.
Before any change, record ver, ota self, ota status,
get bootloader.ver, get system.watchdog, get radio.rxps,
get powersaving, get rxdelay, get af, and ota config. Match the exact
starting body, hardware, bootloader, and signing-key policy. An unsigned
bundle is not suitable for a node requiring signed packages.
Choose a stable-power maintenance window long enough for all 15 installs;
do not begin an install while the destination is at risk of losing power.
Disable the destination system watchdog with set system.watchdog off.
Allow its one-time hardware reset and wait at least 90 seconds; reconnect
and prove that it reports off. A quick software reboot is not a substitute.
Use continuous receive on the destination and sender throughout the bridge
chain (set radio.rxps off). Record and disable a second scanning radio
profile on the sender if it would take the radio away from the OTA channel.
Record rxdelay, af, power-saving, reach, and relay settings before changing
them. Use hops 0 only when there are no intermediate relays. Obey applicable
airtime/duty restrictions and restore all saved settings when finished.
The direct bench also uses set rxdelay 0 and set af 0 on both radios,
ota speed 1 on the sender, and set powersaving off where supported.
The earliest applications do not recognize the last command; continuous
receive is still selected through set radio.rxps off. The source has
set radio2 off throughout the transfer. Wait for configuration replies to
finish before handing its serial connection to the seeder.
For the qualified direct bench configuration only, the destination uses
tempradio 909.950,500,5,5,120; the tested current Full Companion accepts
tempradio 909.950,500,5,5,120,128 for an explicit 128-symbol preamble.
Historical destination parsers do not accept the sixth argument. The sender
and destination must be on the same live radio preset. Do not assume the
temporary preset survives a bridge reboot.
The newer historical receivers performed poorly with a 16-symbol source
preamble in this test. Use the qualified longer setting. Also avoid frequent
ota status or ota stats polling: these versions rehash the complete running
application while answering, which can interrupt receive processing. The lab
runner observes the sender's local log and checks the receiver near completion
or at a 60-second interval. Set a field maintenance lease appropriate to the
actual preset and route; the 120-minute value above is a bench setting.
Applying a qualified bundle
Verify the outer archive hash and every entry in SHA256SUMS.txt. Verify
each .mota using the included, hash-pinned motatool. CHAIN.csv is the
authoritative ordered list of packages, manifest IDs, and before/after hashes.
On the Pi, use tools/motatool-linux-aarch64 (make it executable after
extracting if necessary). The x86-64 binary is for Linux/WSL, not native
Windows. The provenance images, tools, and evidence are not sent over LoRa;
only the selected .mota in the current step's serving directory is sent.
For every row, including after an interrupted run:
- Prove that
ota selfmatches that row's exact before hash and that the bootloader reportsapply OK. Do not select a row by version alone. - Reconcile
ota status. Resume only that row's known partial MID. If it is already ready, do not redownload it. Stop on an unrelated or ambiguous session. Never send a blindota cancel. - Reapply the agreed radio/receive guardrails after reboot. Start the sender
with only this row's package in its serving directory:
motatool serve --dir STEP_DIRECTORY --serial COMPANION_PORT --baud 115200 -v. - Run
ota lsuntil the expected version is advertised. The old listing shows a version, not a MID. Useota pull EXPECTED_MID flashand pollota statusat a restrained interval. - Require
ready to installwith the exact MID. Stop the seeder gracefully with Ctrl-C. Recheck watchdogoff, then sendota install. - Allow up to five minutes for the application to return. Require the exact
after hash, expected version, and
bootloader: apply OKbefore continuing. Stop if any check fails; do not try the next package against another base.
A retained previous container may be re-discovered after boot. If the exact
previous MID is verifying, wait for it to become ready or idle. Only after
re-proving the current running hash may that attached, completed previous
MID be cancelled. An idle ota cancel does not prove that flash was erased.
After the final hash is proved, restore the destination, sender, and relays
to their recorded normal settings and confirm normal-channel reachability.
Re-enable the system watchdog and require it to report on. The current
endpoint supports normalradio; coordinate channel restoration so that the
remote node does not become unreachable during the handoff.
The endpoint requires a temporary radio channel for OTA operations, so a
normal-channel ota ls is not a reachability test. Use an ordinary mesh trace
or authenticated remote command after restoring normal radio.
The existing rak3401_mota_chain.py pins older release archives. It must not
be pointed at this new bundle or have its safety checks bypassed.
Evidence and reproducibility
build_rak3401_legacy24_bundle.py checks every base/target hash, hardware
identity, increasing version, block capability, flash workspace, and staging
margin. It reconstructs every target with both zero- and erased-flash-filled
scratch space, and applies every package with the actual OTAFIX2.4 release
implementation compiled as a flash simulator. It remeasures transport with
the pinned encoder. Its output remains explicitly offline-only until a
separate physical qualification record is attached.
ACCOUNTING-CORRECTION.json records the proactive-proof accounting correction.
It changed the ideal totals only; all package checksums and the selected
hardware-test sequence remained unchanged.
The archive includes the 52 frozen search images, all 47,920 measured
geometries, encoder binaries, per-step simulator logs, and raw hardware-test
records. Only the selected .mota containers are served over LoRa. Lab
scripts in evidence/ are tied to the Pi's USB/debug fixture and are not a
remote field deployment controller.
Regression checks: 534 LoRa OTA tests ran successfully with one opt-in dependency-install test skipped; all 11 linker-selection tests passed.
All PlatformIO builds must be serialized, including builds in separate checkout directories that share the same PlatformIO package installation.