mirror of
https://github.com/mikecarper/MeshCore.git
synced 2026-09-17 06:24:19 +00:00
deploy: b40d2e6cb2
This commit is contained in:
@@ -1872,6 +1872,21 @@ back on its normal radio. Current firmware exposes this complete state through
|
||||
runner can restore those periods but cannot reconstruct an unreported saved
|
||||
level. A radio change later recalculates from a preserved saved minimum, so
|
||||
moving back to a slower tuple returns to the operator's saved level.</p>
|
||||
<p>The OTA source has a stricter policy than the destination. For every source
|
||||
with a managed serial or TCP CLI, the runner reads and retains its exact RXPS
|
||||
preference, using the legacy fixed-period query only when the detailed query is
|
||||
unavailable. It then disables RXPS and verifies the readback before any target
|
||||
discovery or TempRadio change. Source RXPS stays off through catalog serving,
|
||||
download, installation, and post-install identity verification. Cleanup first
|
||||
proves that the source has returned to its normal radio, then restores and
|
||||
verifies the exact saved level/preamble or fixed-period state once. A source
|
||||
whose RXPS state cannot be read, disabled, or restored safely fails closed.
|
||||
If current firmware explicitly rejects an RXPS disable or restore with <code>radio
|
||||
busy; retry</code>, the runner retries that idempotent mutation at staggered
|
||||
210–378 ms intervals. All 32 delays are distinct and contribute about 9.4
|
||||
seconds of waits; source-CLI command round-trip time is additional. This avoids
|
||||
repeatedly sampling one radio phase while retaining a strict attempt cap. Other
|
||||
rejections are not replayed.</p>
|
||||
<h2 id="4-run-an-esp32-update">4. Run an ESP32 update</h2>
|
||||
<p>The ZIP can contain a compatible ready <code>.mota</code> or the exact board-and-role
|
||||
non-merged application <code>.bin</code>:</p>
|
||||
@@ -1995,7 +2010,9 @@ later as a catalog timeout.</p>
|
||||
<p>If the source is already on the exact TempRadio tuple through a scheduled or
|
||||
manual operation, <code>--source-already-temp</code> lets a TCP source run without a raw
|
||||
CLI link. The script cannot verify, extend, or shorten that source window, so
|
||||
leave a comfortable time margin.</p>
|
||||
leave a comfortable time margin. It also cannot inspect or change that
|
||||
unmanaged source's RXPS state; disable source RXPS yourself before starting
|
||||
the run and restore it only after returning the source to its normal radio.</p>
|
||||
<p>Use <code>--controller-baud</code> or <code>--source-baud</code> only for a build whose corresponding
|
||||
interface is genuinely configured to another speed.</p>
|
||||
<h2 id="package-selection-and-safety-gates">Package selection and safety gates</h2>
|
||||
@@ -2019,7 +2036,9 @@ well as individual ZIP members, are rejected above 64 MiB before being loaded.</
|
||||
<li><code>--no-install</code> downloads and verifies the image but leaves it staged. By
|
||||
default the runner then schedules the target, relays, and a script-configured
|
||||
source back to their normal radios. Combining it with
|
||||
<code>--leave-controller-radio</code> deliberately preserves the TempRadio topology.
|
||||
<code>--leave-controller-radio</code> deliberately preserves the destination, relays,
|
||||
and separate controller on the TempRadio topology; a managed source is still
|
||||
returned to normal so its exact RXPS setting can be restored.
|
||||
If the version gate required RXPS off, it stays off while that topology is
|
||||
preserved; use <code>target-rxps-settings.json</code> to restore it only after sending
|
||||
<code>normalradio</code>.</li>
|
||||
@@ -2028,10 +2047,14 @@ well as individual ZIP members, are rejected above 64 MiB before being loaded.</
|
||||
downloading or staged on the target. Without it, that update is preserved.</li>
|
||||
<li><code>--source-shares-controller</code> is for a Full Companion whose USB Binary API is
|
||||
the controller while its TCP port <code>5001</code> is the source. It verifies that the
|
||||
source's port-<code>5000</code> public key equals the controller key. The Binary API
|
||||
first moves the shared physical radio, port <code>5002</code> then enables the local OTA
|
||||
egress gate, and cleanup sends <code>normalradio</code> before restoring the saved
|
||||
Binary radio tuple.</li>
|
||||
source's port-<code>5000</code> public key equals the controller key. Port <code>5002</code> uses a
|
||||
bounded local <code>tempradio</code> override to move the shared physical radio without
|
||||
overwriting its saved normal tuple; the Binary API remains the authenticated
|
||||
transport. Cleanup sends local <code>normalradio</code>, proves that override inactive,
|
||||
and then reasserts the saved Binary tuple. It cannot be combined with
|
||||
<code>--leave-controller-radio</code>,
|
||||
because exact source RXPS restoration requires that shared physical radio to
|
||||
be back on its verified normal tuple.</li>
|
||||
<li><code>--require-system-watchdog-off</code> checks <code>get system.watchdog</code> immediately
|
||||
before every <code>ota install</code> transmission and refuses installation unless the
|
||||
destination reports <code>> off</code>. Use it for nRF52 chains whose bootloader cannot
|
||||
@@ -2068,12 +2091,14 @@ the destination.</p>
|
||||
hashes, Merkle root, full-image hash where applicable, identity fields,
|
||||
signature, codec, base, and the firmware's 1024-byte maximum block size.
|
||||
Version-3 bootloader packages are refused before any target state changes.</li>
|
||||
<li>Save the controller's normal radio tuple, read every participant's version,
|
||||
save the destination's RXPS state, select the qualified RXPS policy, and
|
||||
show the confirmation prompt.</li>
|
||||
<li>Apply and verify that RXPS policy, then start TempRadio on the target,
|
||||
far-to-near relays, and the source;
|
||||
finally switch the controller to the same tuple and read it back. The runner
|
||||
<li>Read and save a managed source's exact RXPS preference, disable and verify
|
||||
source RXPS, save the controller's normal radio tuple, read every
|
||||
participant's version, save the destination's RXPS state, select the
|
||||
qualified destination policy, and show the confirmation prompt.</li>
|
||||
<li>Apply and verify the destination RXPS policy, then start TempRadio on the
|
||||
target, far-to-near relays, and source. A separate controller is moved and
|
||||
read back through Binary; a shared Full Companion instead schedules its
|
||||
bounded local override while Binary remains the transport. The runner
|
||||
rejects a TempRadio window that cannot cover setup, seeder startup,
|
||||
discovery, the transfer timeout, final polling, and install checks.</li>
|
||||
<li>Start <code>motatool serve</code>, discover the exact eight-hex manifest ID, request
|
||||
@@ -2084,14 +2109,17 @@ the destination.</p>
|
||||
<li>Recheck that exact ID, give the target a short final TempRadio safety window,
|
||||
and request <code>ota install</code>. Then shorten each relay's TempRadio window so the
|
||||
normal multi-hop route returns, stop the seeder, shorten the source window,
|
||||
restore the controller, and probe <code>ota self</code> at 10 and 20 seconds instead of
|
||||
sleeping for 90 seconds. The exact new body hash is the readiness signal;
|
||||
restore the controller, and probe <code>ota self</code> every 10 seconds through the
|
||||
configured readiness window (five minutes by default). The exact new body
|
||||
hash is the readiness signal;
|
||||
only then does the runner require the exact package version. A relayed run
|
||||
continues the 10-second probes through the mandatory relay-return window. A
|
||||
source supplied with <code>--source-already-temp</code> is never modified.
|
||||
<code>--leave-controller-radio</code> moves the controller back to TempRadio only after
|
||||
this normal-channel verification. Restore the destination's exact original
|
||||
RXPS setting after normal-channel identity is proven.</li>
|
||||
RXPS setting after normal-channel identity is proven. A managed source stays
|
||||
RXPS-off through that verification and its exact setting is restored only
|
||||
after its own TempRadio state is proven inactive.</li>
|
||||
</ol>
|
||||
<p>Remote replies are matched only after queued messages have been drained and
|
||||
only when they come from the intended contact and fit the command. A ready
|
||||
@@ -2118,23 +2146,50 @@ temporary outage does not silently abandon a resumable transfer. Enter <code>s</
|
||||
budget. If a bounded window expires, rerun the same package after the nodes
|
||||
return to their normal channel; the manifest-ID check resumes its partial
|
||||
download without replacing it.</p>
|
||||
<p>The working directory is retained and printed at exit. It contains the exact
|
||||
<p>The working directory is created before a managed source can be changed, then
|
||||
retained and printed at exit. It contains the exact
|
||||
served mOTA, <code>motatool-serve.log</code>, extracted build inputs when needed, and
|
||||
<code>controller-radio.txt</code>. When the destination started with RXPS enabled it also
|
||||
contains protected <code>target-rxps-settings.json</code> for manual recovery. It contains
|
||||
no saved admin password.</p>
|
||||
<code>controller-radio.txt</code>. A managed source also gets a protected
|
||||
<code>source-rxps-settings.json</code> containing its exact original preference and
|
||||
idempotent restore command. Its contents and directory entry are flushed before
|
||||
RXPS is disabled. When the destination
|
||||
started with RXPS enabled, protected <code>target-rxps-settings.json</code> records its
|
||||
manual recovery state. The RAK3401 chain points every nested step at one
|
||||
chain-root source record, so a rerun after host power loss does not adopt the
|
||||
temporary RXPS-off state as the original. A retained record is accepted only
|
||||
for the same managed CLI endpoint. These files contain no saved admin password.
|
||||
After exact source restoration, a standalone run atomically retires its record;
|
||||
the chain retains its shared record between steps and retires it only after the
|
||||
verified endpoint restoration completes.</p>
|
||||
<h2 id="interruption-and-recovery">Interruption and recovery</h2>
|
||||
<p>Ctrl-C stops the seeder, detaches its serial folder, makes one best-effort
|
||||
request to shorten a source TempRadio window started by the script, and attempts
|
||||
to restore the controller. The target and relays remain on TempRadio only until
|
||||
their bounded windows end; rebooting also restores their saved radio settings.
|
||||
A normal cleanup restores the destination's exact RXPS periods. If that remote
|
||||
restore cannot be confirmed, use <code>target-rxps-settings.json</code> after the target
|
||||
returns to its normal channel.
|
||||
<p>Ctrl-C stops the seeder, detaches its serial folder, makes a bounded attempt to
|
||||
shorten a source TempRadio window started by the script, and attempts to
|
||||
restore the controller. For a managed source it leaves RXPS off until the
|
||||
source is proven back on its normal radio, then restores and verifies the
|
||||
saved source preference. A transient success-path restore failure remains
|
||||
armed for one more idempotent cleanup attempt. The target and relays remain on
|
||||
TempRadio only until their bounded windows end; rebooting also restores their
|
||||
saved radio settings. A normal cleanup restores the destination's exact RXPS
|
||||
periods. If that remote restore cannot be confirmed, use
|
||||
<code>target-rxps-settings.json</code> after the target returns to its normal channel.
|
||||
A partial download remains safe. Once the target is reachable again (after its
|
||||
TempRadio window ends, or after putting the controller back on that tuple),
|
||||
rerunning the same package recognizes its manifest ID and resumes the existing
|
||||
session instead of clearing it.</p>
|
||||
<p>When a chained run has already proved the newly running body hash, its retained
|
||||
previous package can briefly report <code>verifying staged blocks</code> after TempRadio
|
||||
reactivates the OTA manager. The runner waits only through the configured
|
||||
discovery timeout, keeps checking source liveness, and accepts only the same
|
||||
manifest becoming <code>ready to install</code> or the manager becoming idle. A changed
|
||||
ID, failed or incomplete state, or timeout stops the chain. It then proves the
|
||||
exact installed body again. If that same session is still attached and ready,
|
||||
the runner detaches it with <code>ota cancel</code>; if the manager has become idle, it
|
||||
sends no cancel. An ordinary-channel <code>no download</code> status proves only that the
|
||||
manager is idle, not that persistent staging was erased. The runner therefore
|
||||
does not issue or describe an IDLE cancel as durable cleanup. The next chain
|
||||
transition explicitly re-adopts and proves the expected previous MID before detaching it;
|
||||
after the final install OTAFIX has consumed the approval word, so any retained
|
||||
container is inert and is replaced by the next valid pull.</p>
|
||||
<p>A hard process kill or host power loss cannot run cleanup. Recover a serial
|
||||
controller using the tuple saved in the printed work directory:</p>
|
||||
<pre><code class="language-bash">radio=$(tr -d '\r\n' < ./meshcore-lora-ota-20260807-123456-1234/controller-radio.txt)
|
||||
@@ -2143,6 +2198,11 @@ meshcli -s /dev/ttyACM0 set radio "$radio"
|
||||
<pre><code class="language-powershell">$radio = (Get-Content '.\meshcore-lora-ota-...\controller-radio.txt' -Raw).Trim()
|
||||
meshcli -s COM7 set radio $radio
|
||||
</code></pre>
|
||||
<p>For a managed source, first return it to its ordinary radio, then inspect
|
||||
<code>source-rxps-settings.json</code> and issue its exact <code>restore_command</code> through the
|
||||
same serial or TCP-console endpoint recorded in that file. The command is
|
||||
idempotent; confirm the full setting with <code>get radio.rxps.config</code> before
|
||||
resuming an update.</p>
|
||||
<p>If you stop during final confirmation, reconnect on the node's normal channel
|
||||
and run <code>ota self</code> and <code>ver</code>. A completed run returns success only when
|
||||
<code>ota self</code> reports a valid new body hash and <code>ver</code> exactly matches the package;
|
||||
|
||||
@@ -2846,7 +2846,7 @@ ota rescue install <base_hash16> internal-flash nRF52 only: recover from
|
||||
ota bootloader [status] capable allowlisted nRF52 repeater: installed BL identity/caps + staged confirmation
|
||||
ota bootloader install <MID8> <HASH16>
|
||||
explicitly verify/arm one complete trusted v3 package; never automatic
|
||||
ota cancel | drop | stop drop the current fetch session (frees the slot)
|
||||
ota cancel | drop | stop drop the fetch; durably invalidate device staging, or retain a folder partial for resume
|
||||
ota announce | adv serve self + send a beacon now
|
||||
ota self | id print this firmware's EndF (body/image size, base_hash)
|
||||
ota qspi | storage QSPI nRF52 only: JEDEC/SR1/stage/latched storage error (read-only)
|
||||
@@ -2855,6 +2855,14 @@ ota config | cfg | set [autofetch|autoinstall|checkpoint] ... show/set persist
|
||||
ota key | keys [add|rm <hex>] trusted signer allowlist ; bare = list
|
||||
ota dev ... bring-up helpers (stage/recv/serve/resume <MID8>/verify)
|
||||
</code></pre>
|
||||
<p>For a device-backed pull, current firmware returns success only after the
|
||||
persistent store can no longer be reopened; flash/SD/QSPI I/O or readback
|
||||
failure is reported as an error even though the in-memory manager session was
|
||||
dropped. For a <code>folder</code> pull, cancellation detaches the live transfer but
|
||||
deliberately leaves the host <code>.part</code> file available for a later resume.
|
||||
If the shared receive engine is currently performing the MeshTower SD
|
||||
auto-archive capture, cancellation likewise detaches that archive transfer and
|
||||
retains its <code>.part</code> file; it does not erase the unrelated manual-install store.</p>
|
||||
<hr />
|
||||
<p><a id="12-apply-bootloader-contract"></a></p>
|
||||
<h2 id="12-apply-bootloader-contract">12. Apply & bootloader contract</h2>
|
||||
|
||||
@@ -1836,6 +1836,14 @@ partial and the pull resumes (filling only what's missing) the moment you reconn
|
||||
falls back to flash. To <strong>stop</strong> a download you no longer want:</p>
|
||||
<pre><code>ota cancel
|
||||
</code></pre>
|
||||
<p>For a download staged on the node, success means current firmware also
|
||||
invalidated the persistent flash/SD/QSPI copy; it reports an error if that
|
||||
media operation cannot be verified. For a <code>folder</code> capture, cancel only
|
||||
detaches the live session and retains the host partial so it can be resumed or
|
||||
removed on the host deliberately. On a MeshTower SD auto-archive capture,
|
||||
cancel also detaches the live archive session and retains its card partial for
|
||||
the archive service to resume later; use <code>ota cache off</code> to stop new archive
|
||||
captures.</p>
|
||||
<h3 id="4-install-a-downloaded-update">4. Install a downloaded update</h3>
|
||||
<p>Once <code>ota status</code> shows the download is <strong>ready to install</strong>:</p>
|
||||
<pre><code>ota install
|
||||
@@ -1901,7 +1909,9 @@ See <a href="../ota_nrf52_bootloader_update/">the nRF52 bootloader-update guide<
|
||||
complete target inventory, storage layouts, and safety contract.</p>
|
||||
<h3 id="5-if-something-goes-wrong">5. If something goes wrong</h3>
|
||||
<ul>
|
||||
<li>A download that stalls or gets interrupted just <strong>resumes</strong> later, or you can <code>ota cancel</code> and try again.</li>
|
||||
<li>A download that stalls or gets interrupted just <strong>resumes</strong> later. Use
|
||||
<code>ota cancel</code> to durably discard device staging; a folder capture keeps its
|
||||
host partial for a later resume.</li>
|
||||
<li>A legacy app-only internal-flash <strong>nRF52</strong> that still runs but reports <code>no EndF</code> can use the pre-provisioned rescue path
|
||||
if its physical EndF is intact and only app-side validation is failing. Fetch the exact <code>[rescue]</code>
|
||||
in-place delta with an explicit acknowledgement, obtain its 16-hex-digit <code>base_hash</code> from the package
|
||||
|
||||
+400
-150
@@ -1141,10 +1141,10 @@
|
||||
<ul class="md-nav__list">
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="#historical-endpoint-gps-limitation" class="md-nav__link">
|
||||
<a href="#retained-and-omitted-hardware-support" class="md-nav__link">
|
||||
<span class="md-ellipsis">
|
||||
|
||||
Historical endpoint GPS limitation
|
||||
Retained and omitted hardware support
|
||||
|
||||
</span>
|
||||
</a>
|
||||
@@ -1157,10 +1157,10 @@
|
||||
</li>
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="#why-the-old-bootloader-works" class="md-nav__link">
|
||||
<a href="#why-preview-5-and-the-old-staging-ceiling-work" class="md-nav__link">
|
||||
<span class="md-ellipsis">
|
||||
|
||||
Why the old bootloader works
|
||||
Why Preview 5 and the old staging ceiling work
|
||||
|
||||
</span>
|
||||
</a>
|
||||
@@ -1190,14 +1190,31 @@
|
||||
</li>
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="#direct-recoverable-bench-run" class="md-nav__link">
|
||||
<a href="#current-guarded-reproduction-command" class="md-nav__link">
|
||||
<span class="md-ellipsis">
|
||||
|
||||
Direct recoverable bench run
|
||||
Current guarded reproduction command
|
||||
|
||||
</span>
|
||||
</a>
|
||||
|
||||
<nav class="md-nav" aria-label="Current guarded reproduction command">
|
||||
<ul class="md-nav__list">
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="#measured-direct-link-timing" class="md-nav__link">
|
||||
<span class="md-ellipsis">
|
||||
|
||||
Measured direct-link timing
|
||||
|
||||
</span>
|
||||
</a>
|
||||
|
||||
</li>
|
||||
|
||||
</ul>
|
||||
</nav>
|
||||
|
||||
</li>
|
||||
|
||||
<li class="md-nav__item">
|
||||
@@ -1235,10 +1252,10 @@
|
||||
</li>
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="#3-install-all-nine-packages-in-order" class="md-nav__link">
|
||||
<a href="#3-install-all-ten-packages-in-order" class="md-nav__link">
|
||||
<span class="md-ellipsis">
|
||||
|
||||
3. Install all nine packages in order
|
||||
3. Install all ten packages in order
|
||||
|
||||
</span>
|
||||
</a>
|
||||
@@ -1422,10 +1439,10 @@
|
||||
<ul class="md-nav__list">
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="#historical-endpoint-gps-limitation" class="md-nav__link">
|
||||
<a href="#retained-and-omitted-hardware-support" class="md-nav__link">
|
||||
<span class="md-ellipsis">
|
||||
|
||||
Historical endpoint GPS limitation
|
||||
Retained and omitted hardware support
|
||||
|
||||
</span>
|
||||
</a>
|
||||
@@ -1438,10 +1455,10 @@
|
||||
</li>
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="#why-the-old-bootloader-works" class="md-nav__link">
|
||||
<a href="#why-preview-5-and-the-old-staging-ceiling-work" class="md-nav__link">
|
||||
<span class="md-ellipsis">
|
||||
|
||||
Why the old bootloader works
|
||||
Why Preview 5 and the old staging ceiling work
|
||||
|
||||
</span>
|
||||
</a>
|
||||
@@ -1471,14 +1488,31 @@
|
||||
</li>
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="#direct-recoverable-bench-run" class="md-nav__link">
|
||||
<a href="#current-guarded-reproduction-command" class="md-nav__link">
|
||||
<span class="md-ellipsis">
|
||||
|
||||
Direct recoverable bench run
|
||||
Current guarded reproduction command
|
||||
|
||||
</span>
|
||||
</a>
|
||||
|
||||
<nav class="md-nav" aria-label="Current guarded reproduction command">
|
||||
<ul class="md-nav__list">
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="#measured-direct-link-timing" class="md-nav__link">
|
||||
<span class="md-ellipsis">
|
||||
|
||||
Measured direct-link timing
|
||||
|
||||
</span>
|
||||
</a>
|
||||
|
||||
</li>
|
||||
|
||||
</ul>
|
||||
</nav>
|
||||
|
||||
</li>
|
||||
|
||||
<li class="md-nav__item">
|
||||
@@ -1516,10 +1550,10 @@
|
||||
</li>
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="#3-install-all-nine-packages-in-order" class="md-nav__link">
|
||||
<a href="#3-install-all-ten-packages-in-order" class="md-nav__link">
|
||||
<span class="md-ellipsis">
|
||||
|
||||
3. Install all nine packages in order
|
||||
3. Install all ten packages in order
|
||||
|
||||
</span>
|
||||
</a>
|
||||
@@ -1602,23 +1636,31 @@
|
||||
|
||||
<h1 id="rak3401-1w-repeater-compact-lora-update-chain">RAK3401 1W repeater compact LoRa update chain</h1>
|
||||
<blockquote>
|
||||
<p>Status: physically qualified on 19-Aug-2026. All nine exact transitions
|
||||
completed on a deployed RAK3401 1W with its existing bootloader, using a
|
||||
Heltec V4 source at 909.950 MHz / 500 kHz / SF5 / CR5. Every post-boot EndF
|
||||
hash matched. The same files also pass independent reconstruction, container
|
||||
verification, and both deployed Preview 6 and current OTAFIX simulators.</p>
|
||||
<p>Status: <strong>unreleased candidate with physically passed package transitions.</strong>
|
||||
On 29-Aug-2026 all ten exact package transitions completed on the target
|
||||
RAK3401 with its deployed <code>0.9.2-OTAFIX2.4</code> bootloader and a Heltec V4 source at
|
||||
909.950 MHz / 500 kHz / SF5 / CR5. Every intermediate EndF hash matched.
|
||||
Independent SWD readback then matched the endpoint application, original
|
||||
bootloader, SoftDevice/MBR, and UICR byte-for-byte. The host runner received
|
||||
cleanup/recovery fixes after that test and has not had a new clean end-to-end
|
||||
physical rerun. Multi-hop and alternate-bandwidth estimates remain planning
|
||||
data, not physical qualification.</p>
|
||||
</blockquote>
|
||||
<p>Use this asset:</p>
|
||||
<pre><code class="language-text">RAK3401-update-chain-v1.16.7-c1caa5ad-to-v1.17.1.02-e742333a.zip
|
||||
<p>The candidate is a local qualification artifact, not a GitHub release:</p>
|
||||
<pre><code class="language-text">RAK3401-update-chain-v1.16.7-c1caa5ad-to-v1.17.1.5-fd98bc90.zip
|
||||
</code></pre>
|
||||
<ul>
|
||||
<li>ZIP SHA-256: <code>9f80eef191b88833bf4d2e4fea559cf5233ca53f9266ba310d447f37fa445f3a</code></li>
|
||||
<li>inner <code>SHA256SUMS.txt</code> SHA-256: <code>73d96e23237896a3e342fe736be12d94087a813bf09ad609fb55330bbe586055</code></li>
|
||||
<li>endpoint image SHA-256: <code>2784e4b645bc3dc198de0b8b18d3d7369cd02eca61cd71c46a51b61854da5345</code></li>
|
||||
<li>endpoint EndF body hash: <code>4BB1526BF647547D</code></li>
|
||||
<li>candidate ZIP SHA-256: <code>c0b33f4568985e8b2b8dc99411295907212cf2bad21764b6333d5e0ba298fd61</code></li>
|
||||
<li>inner <code>SHA256SUMS.txt</code> SHA-256: <code>3f8c4af8096b96a4aa6506825c387cc8a06f74d5213a29c9387bd11689546881</code></li>
|
||||
<li>endpoint image SHA-256: <code>31c182c888ceb1135e5afb2376610d93cee2e807b556c838e07fd4486c79d095</code></li>
|
||||
<li>endpoint EndF body hash: <code>9179B98432895924</code></li>
|
||||
</ul>
|
||||
<p>Until that asset is uploaded to the release tag pinned by the runner, pass its
|
||||
local path with <code>--bundle</code>.</p>
|
||||
<p>Always pass the exact local path with <code>--bundle</code>. The runner has no download
|
||||
URL for this candidate. <code>--verify-only</code> remains the safe default. The hidden
|
||||
<code>--accept-test-candidate</code> override is intentionally absent from <code>--help</code> and
|
||||
remains required until this exact artifact is published with its qualification
|
||||
record; direct physical success does not silently turn a local file into a
|
||||
released deployment asset.</p>
|
||||
<h2 id="exact-destination">Exact destination</h2>
|
||||
<p>This chain is intentionally specific to one firmware lineage and hardware
|
||||
target:</p>
|
||||
@@ -1627,25 +1669,28 @@ target:</p>
|
||||
<li>hardware: <code>RAK_3401</code></li>
|
||||
<li>role: <code>RAK_3401_repeater_lora_ota_no_external_sensors</code></li>
|
||||
<li>start: <code>v1.16.7.0-c1caa5ad</code>, EndF <code>71F4026CBE4B8B74</code></li>
|
||||
<li>endpoint source: MeshCore <code>e742333a0ee393b0d55d2414f25b28f2f67e1ea1</code></li>
|
||||
<li>endpoint label: <code>v1.17.1.02-halo-keymind-cascade-dev-e742333a</code></li>
|
||||
<li>endpoint packed version: <code>0x01110102</code> (EndF tools render this as <code>1.17.1.2</code>)</li>
|
||||
<li>endpoint source: MeshCore <code>fd98bc90f7fb25d5956facd550e227b217abbe95</code></li>
|
||||
<li>endpoint label: <code>v1.17.1.5-halo-keymind-cascade-dev-fd98bc90</code></li>
|
||||
<li>endpoint packed version: <code>0x01110105</code></li>
|
||||
<li>deployment target key: <code>63d8df6387eaffd2e25db7d2a8ad967a65202182a48d681d7e7a9260f917280d</code></li>
|
||||
</ul>
|
||||
<h3 id="historical-endpoint-gps-limitation">Historical endpoint GPS limitation</h3>
|
||||
<p>This chain's exact <code>RAK_3401_repeater_lora_ota_no_external_sensors</code> endpoint
|
||||
compiles with <code>ENV_INCLUDE_GPS</code> removed. It will not detect, configure, or read
|
||||
a RAK12501 GPS module. GPS requires the ordinary full-sensor
|
||||
<code>RAK_3401_repeater</code> 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.</p>
|
||||
<p>This limitation describes the immutable released chain endpoint identified
|
||||
above. Newly built RAK3401 OTA repeater firmware retains RAK12501 support in
|
||||
slot A, but that newer image is not retroactively part of this exact chain.</p>
|
||||
<h3 id="retained-and-omitted-hardware-support">Retained and omitted hardware support</h3>
|
||||
<p>Despite the legacy <code>no_external_sensors</code> target name, the endpoint retains the
|
||||
RAK12501 GPS provider and the common INA3221, INA219, INA226, and INA260 I2C
|
||||
voltage/current monitors. Install RAK12501 in sensor slot A. Do not use slot D
|
||||
with the RAK13302 1 W radio because the GPS reset/PPS signals overlap the
|
||||
radio's BUSY/DIO1 wiring.</p>
|
||||
<p>The reduced profile omits optional external environmental and ranging devices:
|
||||
AHTX0, BME280, BMP280, SHTC3, SHT4x, LPS22HB, MLX90614, VL53L0X, BME680,
|
||||
BMP085, RAK12035, and BME680 BSEC. Board radio, display, buttons, battery
|
||||
telemetry, GPS, and the INA monitor family remain. No additional “remove I2C
|
||||
temperature” image is needed: the optional I2C temperature/environment drivers
|
||||
are already in the omitted set, while removing the remaining I2C/INA support
|
||||
would discard desired voltage/current monitoring and create another firmware
|
||||
identity without helping this route.</p>
|
||||
<p>Do not use the chain on another target ID, hardware family, starting image, or
|
||||
firmware body hash. The runner checks all four.</p>
|
||||
<h2 id="why-the-old-bootloader-works">Why the old bootloader works</h2>
|
||||
<h2 id="why-preview-5-and-the-old-staging-ceiling-work">Why Preview 5 and the old staging ceiling work</h2>
|
||||
<p>The deployed bootloader is not changed and no package uses the newer expanded
|
||||
<code>0xED000</code> ceiling. Each package remains bottom-aligned below the old
|
||||
bootloader's <code>0xD4000</code> scan ceiling. Its detools workspace is page-aligned and
|
||||
@@ -1656,16 +1701,26 @@ must satisfy:</p>
|
||||
before its first destructive write. The previous application receiver was the
|
||||
limiting part: it reserved a fixed <code>0x98000</code> workspace before accepting a
|
||||
download. Step 1 therefore remains byte-for-byte identical to the physically
|
||||
passed 89,844-byte package. Step 2 is a 46,363-byte fixed-workspace package
|
||||
passed 89,844-byte package. Step 2 is a 46,364-byte fixed-workspace package
|
||||
that installs the compatibility receiver. Later receivers stage above the
|
||||
real EndF image extent and let the bootloader enforce each package's selected
|
||||
workspace.</p>
|
||||
<p>An exhaustive search tested every page-aligned workspace relevant to a route
|
||||
shorter than nine packages: 9,430 candidate geometries across 272 possible
|
||||
layer-skipping edges. None fit. The 61 edges on the shortest-path graph were
|
||||
then swept across another 2,296 workspace choices; 840 nine-package routes
|
||||
were compared by total transfer size. The selected route is the byte-minimum
|
||||
shortest route:</p>
|
||||
<p>Every package in this candidate is an ordinary application container using
|
||||
format ABI 2 and in-place codec 2. The historical bridge applications scan the
|
||||
bootloader capability marker byte-by-byte, so they recognize the released
|
||||
Preview 5 RAK4631 marker at absolute address <code>0xFCDEA</code> even though it is two
|
||||
bytes off a word boundary. The <code>fd98bc90</code> endpoint deliberately retains that
|
||||
exact Preview 5 application-update fallback. Its separately compiled internal
|
||||
bootloader-update feature remains unavailable on Preview 5 because privileged
|
||||
self-update requires a current ABI 3 marker with the exact storage profile.
|
||||
Installing this chain therefore does not replace the bootloader, and the final
|
||||
application can still accept a future valid ABI-2/codec-2 application update.</p>
|
||||
<p>The schema-2 exhaustive search checked 17,402 candidate geometries and found
|
||||
121 feasible forward edges in the declared 33-image inventory. Fourteen
|
||||
ten-package routes tied for the minimum package count; the selected route has
|
||||
the smallest total container size among them. That minimum applies to the
|
||||
declared inventory, not every conceivable firmware image. The selected route
|
||||
is:</p>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
@@ -1691,7 +1746,7 @@ shortest route:</p>
|
||||
<td>1.16.7.9</td>
|
||||
<td>1.16.7.10</td>
|
||||
<td style="text-align: right;"><code>0x98000</code></td>
|
||||
<td style="text-align: right;">46,363</td>
|
||||
<td style="text-align: right;">46,364</td>
|
||||
<td style="text-align: right;">40,960</td>
|
||||
</tr>
|
||||
<tr>
|
||||
@@ -1699,7 +1754,7 @@ shortest route:</p>
|
||||
<td>1.16.7.10</td>
|
||||
<td>1.16.8.7</td>
|
||||
<td style="text-align: right;"><code>0x91000</code></td>
|
||||
<td style="text-align: right;">106,030</td>
|
||||
<td style="text-align: right;">106,029</td>
|
||||
<td style="text-align: right;">12,288</td>
|
||||
</tr>
|
||||
<tr>
|
||||
@@ -1715,44 +1770,52 @@ shortest route:</p>
|
||||
<td>1.16.9.105</td>
|
||||
<td>1.16.9.110</td>
|
||||
<td style="text-align: right;"><code>0x8E000</code></td>
|
||||
<td style="text-align: right;">111,307</td>
|
||||
<td style="text-align: right;">111,312</td>
|
||||
<td style="text-align: right;">16,384</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="text-align: right;">6</td>
|
||||
<td>1.16.9.110</td>
|
||||
<td>1.16.9.111</td>
|
||||
<td style="text-align: right;"><code>0x8D000</code></td>
|
||||
<td style="text-align: right;">70,679</td>
|
||||
<td style="text-align: right;">61,440</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="text-align: right;">7</td>
|
||||
<td>1.16.9.111</td>
|
||||
<td>1.16.9.116</td>
|
||||
<td style="text-align: right;"><code>0x89000</code></td>
|
||||
<td style="text-align: right;">143,441</td>
|
||||
<td>1.16.9.113</td>
|
||||
<td style="text-align: right;"><code>0x8F000</code></td>
|
||||
<td style="text-align: right;">120,624</td>
|
||||
<td style="text-align: right;">4,096</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="text-align: right;">7</td>
|
||||
<td>1.16.9.113</td>
|
||||
<td>1.16.9.117</td>
|
||||
<td style="text-align: right;"><code>0x8B000</code></td>
|
||||
<td style="text-align: right;">139,637</td>
|
||||
<td style="text-align: right;">0</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="text-align: right;">8</td>
|
||||
<td>1.16.9.116</td>
|
||||
<td>1.16.9.118</td>
|
||||
<td style="text-align: right;"><code>0x81000</code></td>
|
||||
<td style="text-align: right;">98,188</td>
|
||||
<td style="text-align: right;">86,016</td>
|
||||
<td>1.16.9.117</td>
|
||||
<td>1.16.10.0</td>
|
||||
<td style="text-align: right;"><code>0x7E000</code></td>
|
||||
<td style="text-align: right;">174,202</td>
|
||||
<td style="text-align: right;">20,480</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="text-align: right;">9</td>
|
||||
<td>1.16.9.118</td>
|
||||
<td>1.17.1.02</td>
|
||||
<td style="text-align: right;"><code>0x7D000</code></td>
|
||||
<td style="text-align: right;">186,385</td>
|
||||
<td style="text-align: right;">12,288</td>
|
||||
<td>1.16.10.0</td>
|
||||
<td>1.17.1.3</td>
|
||||
<td style="text-align: right;"><code>0x6B000</code></td>
|
||||
<td style="text-align: right;">271,569</td>
|
||||
<td style="text-align: right;">0</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="text-align: right;">10</td>
|
||||
<td>1.17.1.3</td>
|
||||
<td>1.17.1.5</td>
|
||||
<td style="text-align: right;"><code>0x74000</code></td>
|
||||
<td style="text-align: right;">173,334</td>
|
||||
<td style="text-align: right;">61,440</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<p>Total mOTA transfer data is 1,002,164 bytes. <code>ROUTE.json</code>, <code>CHAIN.csv</code>, and
|
||||
<p>Total mOTA transfer data is 1,382,842 bytes. <code>ROUTE.json</code>, <code>CHAIN.csv</code>, and
|
||||
<code>validation-results.json</code> in the bundle pin the exact geometry and image hash
|
||||
for every transition.</p>
|
||||
<h2 id="host-requirements">Host requirements</h2>
|
||||
@@ -1761,21 +1824,30 @@ and an OTA-enabled Full Companion or repeater that can seed mOTA files.</p>
|
||||
<h2 id="verify-offline">Verify offline</h2>
|
||||
<p>No password or device is needed:</p>
|
||||
<pre><code class="language-bash">python3 tools/lora_ota/rak3401_mota_chain.py \
|
||||
--bundle /path/to/RAK3401-update-chain-v1.16.7-c1caa5ad-to-v1.17.1.02-e742333a.zip \
|
||||
--bundle /path/to/RAK3401-update-chain-v1.16.7-c1caa5ad-to-v1.17.1.5-fd98bc90.zip \
|
||||
--work-dir ./rak3401-mota-chain-work \
|
||||
--motatool /path/to/motatool \
|
||||
--verify-only
|
||||
</code></pre>
|
||||
<p>The runner checks the outer ZIP, complete inner checksum coverage, all nine
|
||||
manifests, continuity, image anchors, final recovery image, and every container
|
||||
with <code>motatool</code>.</p>
|
||||
<h2 id="direct-recoverable-bench-run">Direct recoverable bench run</h2>
|
||||
<p>When given the ZIP, the runner checks its pinned outer hash and binds the
|
||||
extraction cache to that exact archive. An explicitly supplied extracted root
|
||||
starts at the pinned inner checksum list. In either case it checks complete
|
||||
inner coverage, all ten manifests, continuity, image anchors, both recovery
|
||||
images, and every container with <code>motatool</code>. Passing this command means offline
|
||||
and simulator qualification only; it does not unlock live use.</p>
|
||||
<h2 id="current-guarded-reproduction-command">Current guarded reproduction command</h2>
|
||||
<p>These are the same direct-link arguments used for the physical transition run,
|
||||
but the current host runner also contains later cleanup and recovery fixes.
|
||||
That revised runner has not been cleanly rerun through all ten transitions.
|
||||
Keep local USB/SWD recovery available while the artifact remains unreleased.
|
||||
The hidden override bypasses only the candidate-status block; all identity,
|
||||
checksum, bootloader, route, watchdog, and post-boot gates still run.</p>
|
||||
<p>Restore the test start locally with
|
||||
<code>recovery/test-start/RAK3401-test-start-v1.16.7-c1caa5ad.uf2</code>, then run:</p>
|
||||
<pre><code class="language-bash">export MESHCORE_ADMIN_PASSWORD='password'
|
||||
|
||||
python3 tools/lora_ota/rak3401_mota_chain.py \
|
||||
--bundle /path/to/RAK3401-update-chain-v1.16.7-c1caa5ad-to-v1.17.1.02-e742333a.zip \
|
||||
--bundle /path/to/RAK3401-update-chain-v1.16.7-c1caa5ad-to-v1.17.1.5-fd98bc90.zip \
|
||||
--work-dir ./rak3401-mota-chain-work \
|
||||
--controller-serial /dev/ttyACM0 \
|
||||
--source-tcp 192.168.1.51:5001 \
|
||||
@@ -1786,6 +1858,7 @@ python3 tools/lora_ota/rak3401_mota_chain.py \
|
||||
--ota-hops 0 \
|
||||
--legacy-full-airtime \
|
||||
--motatool /path/to/motatool \
|
||||
--accept-test-candidate \
|
||||
--yes
|
||||
</code></pre>
|
||||
<p>Keep the work directory. Rerunning the same command resumes only when the live
|
||||
@@ -1794,18 +1867,142 @@ EndF body hash matches an exact chain node. Never manually skip a package.
|
||||
zero and restores it at the endpoint. Use that option only where the selected
|
||||
frequency and local duty-cycle rules permit a full transmit budget; omit it
|
||||
otherwise.</p>
|
||||
<h3 id="measured-direct-link-timing">Measured direct-link timing</h3>
|
||||
<p>The first <code>ready to install</code> transfer ages were:</p>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="text-align: right;">Step</th>
|
||||
<th style="text-align: right;">Blocks</th>
|
||||
<th style="text-align: right;">Transfer</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td style="text-align: right;">1</td>
|
||||
<td style="text-align: right;">88</td>
|
||||
<td style="text-align: right;">598 s</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="text-align: right;">2</td>
|
||||
<td style="text-align: right;">45</td>
|
||||
<td style="text-align: right;">42 s</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="text-align: right;">3</td>
|
||||
<td style="text-align: right;">103</td>
|
||||
<td style="text-align: right;">81 s</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="text-align: right;">4</td>
|
||||
<td style="text-align: right;">146</td>
|
||||
<td style="text-align: right;">105 s</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="text-align: right;">5</td>
|
||||
<td style="text-align: right;">109</td>
|
||||
<td style="text-align: right;">76 s</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="text-align: right;">6</td>
|
||||
<td style="text-align: right;">118</td>
|
||||
<td style="text-align: right;">94 s</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="text-align: right;">7</td>
|
||||
<td style="text-align: right;">136</td>
|
||||
<td style="text-align: right;">102 s</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="text-align: right;">8</td>
|
||||
<td style="text-align: right;">170</td>
|
||||
<td style="text-align: right;">127 s</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="text-align: right;">9</td>
|
||||
<td style="text-align: right;">264</td>
|
||||
<td style="text-align: right;">187 s</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="text-align: right;">10</td>
|
||||
<td style="text-align: right;">169</td>
|
||||
<td style="text-align: right;">193 s</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<p>The physically observed bulk-transfer total for those exact packages is 1,605
|
||||
seconds (26m45s). The log came from the pre-cleanup-fix runner revision. Step 1
|
||||
was measured before the host began forcing source RXPS off and is therefore a
|
||||
conservative outlier. A new complete run with that fix is projected at roughly
|
||||
18–19 minutes of bulk transfer, but that projection is not a substitute for
|
||||
the measured total. Allow about 70–75 minutes direct at BW500 for validation,
|
||||
ten transfers, installs/reboots, retained-store checks, and final restoration.</p>
|
||||
<p>Step 10's longer per-block time was not a V4 TempRadio delay: the source RXPS
|
||||
readback was off. Its v1.17.1.3 requester uses non-overlapping adaptive flights
|
||||
and a five-second recovery floor and logged more block reloads than step 8.
|
||||
The V4 itself intentionally schedules its local radio handoff after 1.5 seconds
|
||||
so the normal-channel command reply can drain; the host waits three seconds and
|
||||
then checks the live TempRadio state.</p>
|
||||
<p>For planning only, scale the conservative measured 26m45s bulk baseline by the
|
||||
qualified adaptive-preamble airtime and by each additional relay transmission:</p>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="text-align: right;">Bandwidth, SF5/CR5</th>
|
||||
<th style="text-align: right;">Direct / 0 relays</th>
|
||||
<th style="text-align: right;">1 relay</th>
|
||||
<th style="text-align: right;">2 relays</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td style="text-align: right;">500 kHz</td>
|
||||
<td style="text-align: right;">26m45s</td>
|
||||
<td style="text-align: right;">53m30s</td>
|
||||
<td style="text-align: right;">1h20m15s</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="text-align: right;">250 kHz</td>
|
||||
<td style="text-align: right;">46m51s</td>
|
||||
<td style="text-align: right;">1h33m42s</td>
|
||||
<td style="text-align: right;">2h20m33s</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="text-align: right;">125 kHz</td>
|
||||
<td style="text-align: right;">1h27m03s</td>
|
||||
<td style="text-align: right;">2h54m07s</td>
|
||||
<td style="text-align: right;">4h21m11s</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="text-align: right;">62.5 kHz</td>
|
||||
<td style="text-align: right;">2h54m07s</td>
|
||||
<td style="text-align: right;">5h48m15s</td>
|
||||
<td style="text-align: right;">8h42m23s</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<p>These are transfer-only estimates, not measured alternate-bandwidth or relay
|
||||
results. Packet loss, relay contention, discovery, administrative commands,
|
||||
staged verification, and ten reboot cycles add time. The bandwidth factors are
|
||||
not simple powers of two because the adaptive wire preamble is 128 symbols at
|
||||
500 kHz, 64 at 250 kHz, and 32 at 125/62.5 kHz.</p>
|
||||
<h2 id="manual-operation">Manual operation</h2>
|
||||
<p>The pinned runner is the safer method. If it cannot be used, follow every
|
||||
check below and keep a written copy of each original setting. Do not skip a
|
||||
step even when a later package appears in <code>ota ls</code>.</p>
|
||||
<p>The automated runner is preferred because it binds every transition to the
|
||||
expected MID and body hash. The manual details below document the checks used
|
||||
during qualification and recovery; they are not permission to skip the
|
||||
runner's candidate gate. Keep a written copy of each original setting and
|
||||
never skip a step even when a later package appears in <code>ota ls</code>.</p>
|
||||
<h3 id="1-verify-and-extract-the-asset">1. Verify and extract the asset</h3>
|
||||
<pre><code class="language-bash">sha256sum RAK3401-update-chain-v1.16.7-c1caa5ad-to-v1.17.1.02-e742333a.zip
|
||||
unzip RAK3401-update-chain-v1.16.7-c1caa5ad-to-v1.17.1.02-e742333a.zip
|
||||
cd RAK3401-update-chain-v1.16.7-c1caa5ad-to-v1.17.1.02-e742333a
|
||||
<pre><code class="language-bash">sha256sum RAK3401-update-chain-v1.16.7-c1caa5ad-to-v1.17.1.5-fd98bc90.zip
|
||||
unzip RAK3401-update-chain-v1.16.7-c1caa5ad-to-v1.17.1.5-fd98bc90.zip
|
||||
cd RAK3401-update-chain-v1.16.7-c1caa5ad-to-v1.17.1.5-fd98bc90
|
||||
sha256sum -c SHA256SUMS.txt
|
||||
for package in motas/*.mota; do motatool verify "$package" || exit 1; done
|
||||
</code></pre>
|
||||
<p>The outer hash must be <code>9f80eef191b88833bf4d2e4fea559cf5233ca53f9266ba310d447f37fa445f3a</code>.
|
||||
<p>The outer hash must be
|
||||
<code>c0b33f4568985e8b2b8dc99411295907212cf2bad21764b6333d5e0ba298fd61</code>,
|
||||
and the SHA-256 of the extracted <code>SHA256SUMS.txt</code> must be
|
||||
<code>3f8c4af8096b96a4aa6506825c387cc8a06f74d5213a29c9387bd11689546881</code>.
|
||||
Do not continue after any checksum or verification error.</p>
|
||||
<h3 id="2-record-and-prepare-the-destination">2. Record and prepare the destination</h3>
|
||||
<p>Before changing anything, save the complete replies from:</p>
|
||||
@@ -1848,13 +2045,12 @@ and do not substitute a normal reboot for the one-time watchdog reset. Direct
|
||||
tests use <code>hops 0</code>: <code>hops 1</code> makes the target echo each source response despite
|
||||
there being no intermediate relay, increasing half-duplex loss and legacy
|
||||
three-second retries.</p>
|
||||
<p>The exact compact chain never qualifies for fast RXPS: its bridge and endpoint
|
||||
versions predate the v1.17.1.5 adaptive-preamble contract. Keep RXPS off for
|
||||
this historical chain whether using SF5/BW250 or the faster direct-bench
|
||||
SF5/BW500 tuple. For a later update whose target, controller, source, and
|
||||
relays are all v1.17.1.5 or newer, use the generic LoRa OTA runner; it can
|
||||
retain RXPS at the qualified level-8/preamble-64 boundary for SF5/BW250 or the
|
||||
level-8/preamble-128 boundary for SF5/BW500.</p>
|
||||
<p>The exact candidate cannot use fast RXPS throughout because its historical
|
||||
bridge applications predate the v1.17.1.5 adaptive-preamble contract. Keep
|
||||
RXPS off for all ten steps whether using SF5/BW250 or the faster direct-bench
|
||||
SF5/BW500 tuple. After the final endpoint is proven, a later update whose
|
||||
target, controller, source, and relays are all v1.17.1.5 or newer can use the
|
||||
generic LoRa OTA runner and its qualified adaptive-preamble boundaries.</p>
|
||||
<p>Put the source on the identical TempRadio tuple. A current ASCII-first Full
|
||||
Companion recognizes <code>motatool</code>'s initial <code>ota folder on</code> line directly:</p>
|
||||
<pre><code class="language-bash">motatool serve \
|
||||
@@ -1868,7 +2064,7 @@ where the idle parser recognizes the identical preamble. Restart <code>motatool<
|
||||
every step so the source emits a fresh catalog advert. Leave it running during
|
||||
the download and stop it with Ctrl-C only after the destination reports <code>ready
|
||||
to install</code>.</p>
|
||||
<h3 id="3-install-all-nine-packages-in-order">3. Install all nine packages in order</h3>
|
||||
<h3 id="3-install-all-ten-packages-in-order">3. Install all ten packages in order</h3>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
@@ -1889,69 +2085,89 @@ to install</code>.</p>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="text-align: right;">2</td>
|
||||
<td><code>C510B628</code></td>
|
||||
<td><code>BEB578FC</code></td>
|
||||
<td><code>42BC53A64288E845</code></td>
|
||||
<td><code>1.16.7.10</code></td>
|
||||
<td><code>6F6E51D63BF3E93F</code></td>
|
||||
<td><code>1AD2AA8B6C478DA6</code></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="text-align: right;">3</td>
|
||||
<td><code>8CAD7045</code></td>
|
||||
<td><code>6F6E51D63BF3E93F</code></td>
|
||||
<td><code>6D8EF814</code></td>
|
||||
<td><code>1AD2AA8B6C478DA6</code></td>
|
||||
<td><code>1.16.8.7</code></td>
|
||||
<td><code>F04DA8DB515E5C6D</code></td>
|
||||
<td><code>D709A25308BC1831</code></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="text-align: right;">4</td>
|
||||
<td><code>8B1EEFF6</code></td>
|
||||
<td><code>F04DA8DB515E5C6D</code></td>
|
||||
<td><code>40AC4CEB</code></td>
|
||||
<td><code>D709A25308BC1831</code></td>
|
||||
<td><code>1.16.9.105</code></td>
|
||||
<td><code>3894A4D7B11ED282</code></td>
|
||||
<td><code>457BEDA5E6406C81</code></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="text-align: right;">5</td>
|
||||
<td><code>687E3BEA</code></td>
|
||||
<td><code>3894A4D7B11ED282</code></td>
|
||||
<td><code>F6C8890B</code></td>
|
||||
<td><code>457BEDA5E6406C81</code></td>
|
||||
<td><code>1.16.9.110</code></td>
|
||||
<td><code>CDC5CA630881DA15</code></td>
|
||||
<td><code>E76BFA205634CAB8</code></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="text-align: right;">6</td>
|
||||
<td><code>0D59A34B</code></td>
|
||||
<td><code>CDC5CA630881DA15</code></td>
|
||||
<td><code>1.16.9.111</code></td>
|
||||
<td><code>D0CC4A9E135B9A4D</code></td>
|
||||
<td><code>82405E89</code></td>
|
||||
<td><code>E76BFA205634CAB8</code></td>
|
||||
<td><code>1.16.9.113</code></td>
|
||||
<td><code>65328FC8A1FBED2D</code></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="text-align: right;">7</td>
|
||||
<td><code>2C5C2082</code></td>
|
||||
<td><code>D0CC4A9E135B9A4D</code></td>
|
||||
<td><code>1.16.9.116</code></td>
|
||||
<td><code>66272A0E7FEF2773</code></td>
|
||||
<td><code>80F40DB6</code></td>
|
||||
<td><code>65328FC8A1FBED2D</code></td>
|
||||
<td><code>1.16.9.117</code></td>
|
||||
<td><code>AF7532E13337FADD</code></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="text-align: right;">8</td>
|
||||
<td><code>994AB743</code></td>
|
||||
<td><code>66272A0E7FEF2773</code></td>
|
||||
<td><code>1.16.9.118</code></td>
|
||||
<td><code>8E00019AA08E00BF</code></td>
|
||||
<td><code>DBF6310D</code></td>
|
||||
<td><code>AF7532E13337FADD</code></td>
|
||||
<td><code>1.16.10.0</code></td>
|
||||
<td><code>22002359BBDFA76E</code></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="text-align: right;">9</td>
|
||||
<td><code>FE986948</code></td>
|
||||
<td><code>8E00019AA08E00BF</code></td>
|
||||
<td><code>1.17.1.02</code></td>
|
||||
<td><code>4BB1526BF647547D</code></td>
|
||||
<td><code>C2979E08</code></td>
|
||||
<td><code>22002359BBDFA76E</code></td>
|
||||
<td><code>1.17.1.3</code></td>
|
||||
<td><code>FE65A6135A1E7B3F</code></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="text-align: right;">10</td>
|
||||
<td><code>319DEC92</code></td>
|
||||
<td><code>FE65A6135A1E7B3F</code></td>
|
||||
<td><code>1.17.1.5</code></td>
|
||||
<td><code>9179B98432895924</code></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<p>For each row, first prove that <code>ota self</code> exactly matches its <strong>Before hash</strong>.
|
||||
Then run:</p>
|
||||
<pre><code class="language-text">ota cancel
|
||||
ota ls
|
||||
<p>For each row, first prove that <code>ota self</code> exactly matches its <strong>Before hash</strong>,
|
||||
then inspect the manager before changing it:</p>
|
||||
<pre><code class="language-text">ota status
|
||||
</code></pre>
|
||||
<p>Proceed to discovery only after that status is reconciled. When the manager is
|
||||
idle, run:</p>
|
||||
<pre><code class="language-text">ota ls
|
||||
ota pull MANIFEST_ID flash
|
||||
ota status
|
||||
</code></pre>
|
||||
<p>If the first status reports this row's MID as partial, use <code>ota pull</code> with that
|
||||
same MID to resume it. If this row's MID is already ready, do not pull or
|
||||
cancel it; continue with the install checks below. If status reports the
|
||||
immediately previous row's MID as <code>verifying staged blocks</code>, wait through the
|
||||
bounded discovery window; require either that exact MID to become <code>ready to
|
||||
install</code> or the manager to become <code>no download</code>, and re-prove the current
|
||||
<strong>Before hash</strong>. Cancel only the same previous MID while it is visibly
|
||||
attached and ready. If it has become idle, send no cancel. Stop on any other
|
||||
MID, failed or incomplete state, ambiguous reply, or timeout. Never issue a
|
||||
blind <code>ota cancel</code>: it can discard a resumable or unrelated session.</p>
|
||||
<p>Repeat <code>ota status</code> at a restrained interval until it says <code>ready to install</code>.
|
||||
If the manifest is initially absent, wait for a fresh source advert, run
|
||||
<code>ota ls</code> again, or restart <code>motatool</code>; do not pull a different ID. Stop the
|
||||
@@ -1959,18 +2175,34 @@ seeder and install:</p>
|
||||
<pre><code class="language-text">get system.watchdog
|
||||
ota install
|
||||
</code></pre>
|
||||
<p>The watchdog reply must still be <code>> off</code>. Allow up to 90 seconds for the USB
|
||||
port to disappear and return. Then run <code>ver</code>, <code>ota self</code>, and <code>ota help</code>.
|
||||
<p>The watchdog reply must still be <code>> off</code>. Allow the current default five-minute
|
||||
readiness window for the USB/LoRa identity to return; automation probes every
|
||||
10 seconds and returns early as soon as the exact body is visible. Then run
|
||||
<code>ver</code>, <code>ota self</code>, and <code>ota help</code>.
|
||||
Require the row's exact <strong>After hash</strong>, require <code>bootloader: apply OK</code>, and
|
||||
require <code>rescue install <hash16></code> in the help before exposing that bridge to
|
||||
the next package. Clear only the now-proven retained download with <code>ota cancel</code>,
|
||||
reapply the transfer guardrails and TempRadio tuple, restart <code>motatool</code>, and
|
||||
continue with the next row.</p>
|
||||
the next package. This rescue-help gate applies to steps 1-9; step 10 is the
|
||||
endpoint and is not exposed to another package in this chain. A normal-radio
|
||||
<code>ota status</code> response of <code>no download</code> proves only that the manager is idle;
|
||||
legacy internal-flash firmware does not durably erase a retained container by
|
||||
accepting an IDLE <code>ota cancel</code>. Do not claim otherwise. When the same expected
|
||||
manifest reports <code>verifying staged blocks</code> after the next TempRadio starts,
|
||||
wait only for the bounded discovery window and require that it become <code>ready
|
||||
to install</code> or <code>no download</code>; stop on a changed ID, failed/incomplete state, or
|
||||
timeout. Prove the exact running body before cancelling an attached completed
|
||||
session. OTAFIX consumes the approval word during a successful install, so a
|
||||
retained endpoint container is inert and a later valid pull replaces it.
|
||||
Reapply the transfer
|
||||
guardrails and TempRadio tuple, restart <code>motatool</code>, and continue with the next
|
||||
row.</p>
|
||||
<p>If the board boots but <code>ota self</code> says the EndF is invalid, stop. Only when
|
||||
<code>ota status</code> still identifies that row's complete staged package may the
|
||||
guarded <code>ota rescue install BEFORE_HASH</code> command be used. It is not a force
|
||||
option and must use that row's exact Before hash. If the board does not boot,
|
||||
recover the documented start/recovery UF2 locally over USB.</p>
|
||||
recover the documented start/recovery UF2 locally over USB. The fd98 endpoint
|
||||
deliberately disables rescue on its shared internal bootloader-update build;
|
||||
if its EndF is invalid, use local DFU/SWD rather than attempting another LoRa
|
||||
package.</p>
|
||||
<h3 id="4-intermediate-relays-and-restoration">4. Intermediate relays and restoration</h3>
|
||||
<p>On every managed intermediate relay, save <code>get rxdelay</code> and <code>get txdelay</code>, then
|
||||
use the airtime-scaled collision window during the maintenance session:</p>
|
||||
@@ -1982,13 +2214,13 @@ set txdelay 0.3
|
||||
window. The source's manifest-fragment gap independently follows active packet
|
||||
airtime and duty spacing, clamped to 100-1000 ms. These two delays solve
|
||||
different problems.</p>
|
||||
<p>After step 9 is proven, restore each saved destination and relay value exactly,
|
||||
<p>After step 10 is proven, restore each saved destination and relay value exactly,
|
||||
including <code>rxdelay</code>, RXPS, CPU power saving, <code>af</code>, <code>ota config hops</code>, and relay
|
||||
timing. Current full-parser repeater firmware accepts <code>normalradio</code> and restores
|
||||
the saved tuple after replying on the temporary channel:</p>
|
||||
<pre><code class="language-text">normalradio
|
||||
</code></pre>
|
||||
<p>The historical endpoint in this pinned bundle predates that command. If it
|
||||
<p>The fd98 endpoint supports that command. If a recovery build unexpectedly
|
||||
replies <code>Unknown command</code>, shorten its lease to one minute with the same tuple:</p>
|
||||
<pre><code class="language-text">tempradio 909.950,500,5,5,1
|
||||
</code></pre>
|
||||
@@ -2000,16 +2232,22 @@ get system.watchdog
|
||||
<p>A Full Companion source is the exception: its local TCP console supports
|
||||
<code>normalradio</code> and the automated runner uses it before restoring the shared
|
||||
Binary API radio tuple.</p>
|
||||
<p>Final success requires version <code>v1.17.1.02-halo-keymind-cascade-dev-e742333a</code>,
|
||||
body hash <code>4BB1526BF647547D</code>, target <code>2FA509C1</code>, hardware <code>RAK_3401</code>, and
|
||||
<p>Final lab success requires version
|
||||
<code>v1.17.1.5-halo-keymind-cascade-dev-fd98bc90</code>, body hash
|
||||
<code>9179B98432895924</code>, target <code>2FA509C1</code>, hardware <code>RAK_3401</code>, and
|
||||
<code>get system.watchdog</code> reporting <code>> on</code>. If a relay cannot be restored before
|
||||
its TempRadio lease ends, wait for it to return to the normal channel and
|
||||
restore its saved <code>rxdelay</code> and <code>txdelay</code> there.</p>
|
||||
<h2 id="two-relay-deployment">Two-relay deployment</h2>
|
||||
<p>For two intermediate relays, list them farthest-to-nearest and use three OTA
|
||||
hops. Run a non-mutating preflight first:</p>
|
||||
<p>The exact direct package transitions above were physically observed; this
|
||||
post-run host-runner revision and multi-hop operation are not cleanly
|
||||
end-to-end qualified. A controlled, recoverable multi-hop lab can exercise two
|
||||
intermediate relays by
|
||||
listing them farthest-to-nearest and using three OTA hops. Run a non-mutating
|
||||
preflight first; even preflight requires the hidden candidate override because
|
||||
the artifact remains unreleased and the command connects to live devices:</p>
|
||||
<pre><code class="language-bash">python3 tools/lora_ota/rak3401_mota_chain.py \
|
||||
--bundle /path/to/RAK3401-update-chain-v1.16.7-c1caa5ad-to-v1.17.1.02-e742333a.zip \
|
||||
--bundle /path/to/RAK3401-update-chain-v1.16.7-c1caa5ad-to-v1.17.1.5-fd98bc90.zip \
|
||||
--work-dir ./rak3401-mota-chain-work \
|
||||
--controller-serial /dev/ttyACM0 \
|
||||
--source-tcp SOURCE_IP:5001 \
|
||||
@@ -2021,6 +2259,7 @@ hops. Run a non-mutating preflight first:</p>
|
||||
--temp-radio 909.950,250,5,5,120 \
|
||||
--ota-hops 3 \
|
||||
--motatool /path/to/motatool \
|
||||
--accept-test-candidate \
|
||||
--preflight-only
|
||||
</code></pre>
|
||||
<p>After preflight succeeds, rerun with <code>--yes</code> in place of
|
||||
@@ -2028,15 +2267,26 @@ hops. Run a non-mutating preflight first:</p>
|
||||
legal at the deployment location.</p>
|
||||
<h2 id="rescue-and-completion">Rescue and completion</h2>
|
||||
<p>The guarded <code>ota rescue install <base_body_hash></code> command is present after
|
||||
step 1. It is not a force command: it refuses a valid normal EndF, a base
|
||||
mismatch, wrong target/hardware, or invalid payload. A failure before step 1
|
||||
completes still requires local USB recovery because the deployed start image
|
||||
predates the rescue command.</p>
|
||||
step 1 and remains available through the step-9 bridge. It is not a force
|
||||
command: it refuses a valid normal EndF, a base mismatch, wrong
|
||||
target/hardware, or invalid payload. A failure before step 1 completes still
|
||||
requires local USB recovery because the deployed start image predates the
|
||||
rescue command. The final fd98 endpoint uses the stricter shared-slot profile;
|
||||
its invalid-EndF recovery is local DFU/SWD, not the rescue command.</p>
|
||||
<p>Before the first mutation, the runner saves the destination's RXPS periods,
|
||||
CPU power-saving state, RX flood delay, airtime factor, and OTA hop reach in
|
||||
the persistent work directory. It reads the destination, controller, source,
|
||||
and relay versions before applying the same RXPS policy as the generic runner.
|
||||
The pinned historical chain therefore verifies <code>radio.rxps off</code> after every
|
||||
the persistent work directory, flushing both the file and its directory entry
|
||||
before changing a guardrail. It also saves the managed source's exact RXPS
|
||||
preference in the chain-root <code>source-rxps-settings.json</code> before disabling it.
|
||||
Every step reuses that protected record, so a rerun after a killed host process
|
||||
restores the chain-start preference instead of treating the temporary off state
|
||||
as original. The record is accepted only for the same CLI endpoint. The runner
|
||||
retires that chain-root record only after it has proved the source is back on
|
||||
its normal radio and restored the exact preference at the verified endpoint.
|
||||
The runner
|
||||
reads the destination, controller, source, and relay versions before applying
|
||||
the same RXPS policy as the generic runner.
|
||||
The mixed historical chain therefore verifies <code>radio.rxps off</code> after every
|
||||
bridge reboot; a future all-v1.17.1.5-or-newer SF5/BW250 chain would instead
|
||||
keep RXPS on under the qualified level-8/preamble-64 boundary. It also verifies
|
||||
<code>powersaving off</code> and <code>rxdelay 0</code>, plus <code>af 0</code> when
|
||||
@@ -2053,8 +2303,8 @@ settings remain conservative isolation and fast-link reliability guardrails.</p>
|
||||
the relay leaves TempRadio.</p>
|
||||
<p>The runner also keeps the watchdog off across the chain, rechecks identity and
|
||||
OTA reach before every package, requires the exact post-boot EndF hash, and
|
||||
only re-enables the watchdog after step 9. Success requires endpoint body hash
|
||||
<code>4BB1526BF647547D</code>, target <code>2FA509C1</code>, hardware <code>RAK_3401</code>, normal radio
|
||||
only re-enables the watchdog after step 10. Lab success requires endpoint body
|
||||
hash <code>9179B98432895924</code>, target <code>2FA509C1</code>, hardware <code>RAK_3401</code>, normal radio
|
||||
<code>910.525 MHz / 62.5 kHz / SF7 / CR5</code>, and the watchdog verified on.</p>
|
||||
<h2 id="external-radio-startup-and-manual-usb-recovery">External-radio startup and manual USB recovery</h2>
|
||||
<p>RAK3401 is a distinct target from RAK4631. Its RAK13300/RAK13302 radio is on
|
||||
|
||||
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user