This commit is contained in:
mikecarper committed 2026-08-14 00:54:08 +00:00
1 parent 4547de0670
commit 3404bc06de
7 files changed
+199 -152

No files matched your search

+4 -3
View File
@@ -1694,9 +1694,10 @@ entry 1, run:</p>
<p>Monitor the transfer:</p>
<pre><code class="language-text">ota status
</code></pre>
<p>The update is ready when the status says <code>ready to install</code>. OTA is deliberately the lowest-priority mesh
traffic. At the temporary-radio settings in this guide, allow roughly <strong>one hour</strong> for a typical ESP32 full
image over a quiet, direct link. That is a planning estimate, not an upper bound: repeaters, retries, weak
<p>The update is ready when the status says <code>ready to install</code>. Discovery is background traffic, while an
active OTA download is primary mesh traffic. At the temporary-radio settings in this guide, allow roughly
<strong>one hour</strong> for a typical ESP32 full image over a quiet, direct link. That is a planning estimate, not an
upper bound: repeaters, retries, weak
links, and normal mesh traffic can extend it well past an hour. The 120-minute example window is
intentional. If necessary, start another overlapping <code>tempradio</code> window; the download resumes rather than
starting over.</p>
+63 -45
View File
@@ -1703,8 +1703,8 @@ document is the technical/wire specification.</p>
<li><strong>Trustless mesh relay:</strong> repeaters may forward packets while the source alone serves firmware data;
integrity is content-addressed
against a signed merkle root, so a relay need not be trusted and never needs the signing keys.</li>
<li><strong>Lowest priority, always:</strong> OTA traffic is enqueued behind all mesh traffic - "eventually upgradable".
A busy node delays OTA indefinitely rather than competing with real traffic.</li>
<li><strong>Primary while transferring:</strong> periodic discovery stays at background priority, but manifest, block,
data, and proof packets for an active fetch use primary queue priority at every relay hop.</li>
<li><strong>Portable:</strong> the engine (<code>src/helpers/ota/OtaManager</code>) is Arduino/radio/crypto-free and host-testable,
so the same logic drives a device, a simulation, or a third-party implementation.</li>
</ul>
@@ -1853,9 +1853,14 @@ document is the technical/wire specification.</p>
<td><code>block_size_log2 = 0x0A</code></td>
</tr>
<tr>
<td>OTA TX priority</td>
<td>OTA discovery TX priority</td>
<td><code>250</code></td>
<td>lowest (<code>OTA_TX_PRIORITY</code>, <code>src/Mesh.h</code>)</td>
<td>background (<code>OTA_TX_PRIORITY</code>, <code>src/Mesh.h</code>)</td>
</tr>
<tr>
<td>OTA active-transfer TX priority</td>
<td><code>0</code></td>
<td>primary (<code>OTA_TRANSFER_TX_PRIORITY</code>, <code>src/Mesh.h</code>)</td>
</tr>
</tbody>
</table>
@@ -2083,9 +2088,10 @@ while n &gt; 1:
idx //= 2 ; n = (n + 1) // 2
accept iff h == merkle_root and p == len(proof)
</code></pre>
<p>Over LoRa, <code>leaves[]</code> are <strong>omitted</strong> from the manifest transfer; a serving node computes a block's proof
on demand from its stored <code>leaves[]</code> (<code>OTA_REQ_PROOF</code>/<code>OTA_PROOF</code>, Section 8.5), and the fetcher fills its own
<code>leaves[i]</code> as each verified block lands.</p>
<p>Over LoRa, <code>leaves[]</code> are <strong>omitted</strong> from the manifest transfer. A serving node computes a block's proof
on demand from its stored <code>leaves[]</code> and normally sends <code>OTA_PROOF</code> immediately after that block's paced
<code>OTA_DATA</code>. <code>OTA_REQ_PROOF</code> remains the fallback for an older source or a lost proactive proof. The fetcher
fills its own <code>leaves[i]</code> as each verified block lands.</p>
<hr />
<h2 id="7-block-availability-staging-resume">7. Block availability, staging &amp; resume</h2>
<p>There is no separate availability structure. <strong>Block <code>i</code> is present &lt;=&gt; <code>leaves[i]</code> is non-erased</strong>
@@ -2122,67 +2128,67 @@ small delta that fits page 0 does zero flash I/O until COMPLETE.</p>
<tr>
<td><code>OTA_ADV</code></td>
<td>0x01</td>
<td>flood</td>
<td>discovery</td>
<td>tiny per-node beacon (discovery tier 1)</td>
</tr>
<tr>
<td><code>OTA_QUERY</code></td>
<td>0x02</td>
<td>flood</td>
<td>discovery</td>
<td>ask a source for its catalog (discovery tier 2)</td>
</tr>
<tr>
<td><code>OTA_HAVE</code></td>
<td>0x03</td>
<td>flood</td>
<td>discovery</td>
<td>the catalog reply (fragmented, digest-tagged)</td>
</tr>
<tr>
<td><code>OTA_GET_MANIFEST</code></td>
<td>0x04</td>
<td>direct</td>
<td>transfer</td>
<td>request a manifest's fragments (<code>want_mask</code>) by <code>manifest_id</code></td>
</tr>
<tr>
<td><code>OTA_MANIFEST</code></td>
<td>0x05</td>
<td>direct</td>
<td>transfer</td>
<td>the manifest-minus-leaves, fragmented</td>
</tr>
<tr>
<td><code>OTA_REQ</code></td>
<td>0x06</td>
<td>direct</td>
<td>transfer</td>
<td>request specific DATA fragments of one block (<code>want_mask</code>)</td>
</tr>
<tr>
<td><code>OTA_DATA</code></td>
<td>0x07</td>
<td>direct</td>
<td>transfer</td>
<td>one self-describing fragment of a block's data</td>
</tr>
<tr>
<td><code>OTA_REQ_PROOF</code></td>
<td>0x08</td>
<td>direct</td>
<td>request the merkle proof for one block</td>
<td>transfer</td>
<td>request/re-request a missing proof</td>
</tr>
<tr>
<td><code>OTA_PROOF</code></td>
<td>0x09</td>
<td>direct</td>
<td>transfer</td>
<td>the merkle proof for one block</td>
</tr>
<tr>
<td><code>OTA_GET_LEAVES</code></td>
<td>0x0A</td>
<td>direct</td>
<td>transfer</td>
<td>request the target's <code>leaves[]</code> fragments (<code>want_mask</code>) - warm-start only</td>
</tr>
<tr>
<td><code>OTA_LEAVES</code></td>
<td>0x0B</td>
<td>direct</td>
<td>transfer</td>
<td>a fragment of the <code>leaves[]</code> array (for host-side seed leaf-diff)</td>
</tr>
</tbody>
@@ -2190,20 +2196,23 @@ small delta that fits page 0 does zero flash I/O until COMPLETE.</p>
<ul>
<li><strong><code>manifest_id</code></strong> = the manifest's <code>merkle_root</code> (4 bytes) - a compact content id present in every
transfer message, so a multi-mota server dispatches each request to the right image.</li>
<li><strong>Priority:</strong> all OTA packets enqueue at <code>OTA_TX_PRIORITY = 250</code> (lowest). OTA never competes with mesh
traffic; on a busy node it is delayed indefinitely.</li>
<li><strong>Reliability is <em>eventual</em>:</strong> the fetcher re-requests missing fragments/blocks after a timeout, possibly
from a different peer. No hard ACKs, no global ordering.</li>
<li><strong>Relay:</strong> replies are flooded, so transparent relay needs no per-requester addressing, and the transfer
is trustless (the fetcher verifies every block against the signed root). Any neighbor may serve any
fragment it has. A repeater without <code>ENABLE_OTA</code> transports <code>PAYLOAD_TYPE_OTA</code> opaquely and does not need
the manager, staging store, installer, or destination bootloader.</li>
<li><strong>Priority:</strong> <code>OTA_ADV</code>, <code>OTA_QUERY</code>, and <code>OTA_HAVE</code> enqueue at background priority 250. Once a fetch is
active, manifest, block request, data, and proof messages enqueue at primary priority 0. Relay-only
nodes classify the wire message identically, so a transfer stays primary across the complete path.</li>
<li><strong>Reliability is <em>eventual</em>:</strong> the fetcher re-requests only missing fragments after a one-second stalled
service tick. No hard ACKs or global ordering are required.</li>
<li><strong>Relay envelope:</strong> OTA still uses a bounded flood-shaped mesh header so the same packets can cross the
configured number of hops without first discovering an addressed return path. During TempRadio each node
forwards one copy; active OTA packets do not use the generic flood-retry subsystem. The fetcher verifies
every block against the signed root, and a repeater without <code>ENABLE_OTA</code> can transport
<code>PAYLOAD_TYPE_OTA</code> opaquely without the manager, staging store, installer, or destination bootloader.</li>
<li><strong>Hop limit + duty cycle:</strong> OTA floods accumulate one path-hash per relay (the mesh's flood routing). A
node with the OTA manager <em>accepts</em> a packet only if it arrived within <code>ota config hops</code> hops (default 3;
<code>0</code> = direct only) and <em>relays</em> it only while still under that limit, appending its own hash. Relay-only
repeaters instead use their ordinary flood limits and forwarding filters. Relays are lowest-priority and
are skipped when the packet pool runs low (the source retries), so heavy OTA can never monopolise a
repeater's RAM or starve real traffic.</li>
repeaters instead use their ordinary flood limits and forwarding filters. Discovery relays remain
lowest-priority and may be skipped when the packet pool runs low. Active-transfer relays bypass that
background pool gate and use priority 0; operators should therefore treat TempRadio as a dedicated OTA
maintenance window because the transfer can delay unrelated mesh traffic.</li>
</ul>
<h3 id="81-two-tier-discovery">8.1 Two-tier discovery</h3>
<p>Because a node may serve <strong>many</strong> mOTAs (its own firmware plus an external folder - Section 10), discovery is split
@@ -2261,8 +2270,8 @@ pattern), all in <code>OtaManager</code>:</p>
OTA_REQ(mid, block_idx, want_mask) &gt; (want_mask=all fragments first; only the holes on retry)
&lt;------- OTA_DATA(mid, block_idx, frag_off, data) x requested frags
(reassemble block from frag_off slices)
OTA_REQ_PROOF(mid, block_idx) ----&gt;
&lt;------- OTA_PROOF(mid, block_idx, n_proof, proof)
&lt;------- OTA_PROOF(mid, block_idx, n_proof, proof) (proactive)
[if proof is absent after grace: OTA_REQ_PROOF(mid, block_idx) -&gt; legacy/loss fallback]
(verify proof vs merkle_root -&gt; write block -&gt; write leaves[i])
when all blocks present: verify full merkle_root + image_hash -&gt; COMPLETE
</code></pre>
@@ -2293,7 +2302,7 @@ OTA_LEAVES: manifest_id[4] frag_idx(1) frag_total(1) bytes[] # up
is answering <code>OTA_GET_LEAVES</code> with leaves the node already holds, so any node's firmware can be captured.</p>
</li>
<li>
<p><strong>Block &lt;-&gt; fragments:</strong> a 1 KB block is split into self-describing <code>OTA_DATA</code> fragments. <code>frag_off</code> is the
<p><strong>Block &lt;-&gt; fragments:</strong> a 1 KB block remains split into self-describing <code>OTA_DATA</code> fragments. <code>frag_off</code> is the
byte offset of <code>data</code> within the block, so the global position is <code>block_idx*block_size + frag_off</code> -
a fragment is self-placing when returned by the source. The fetcher tracks a
per-block slice bitmap and reassembles before requesting the proof.</p>
@@ -2308,10 +2317,12 @@ OTA_LEAVES: manifest_id[4] frag_idx(1) frag_total(1) bytes[] # up
with the tail of the in-flight burst and drop the same fragment forever - a hang. Requesting only the hole
removes the burst, so there is nothing to collide with. The block/manifest mask matches the 16-bit
reassembly bitmap (&lt;=16 fragments/block; 1 KB blocks = 7). <code>OTA_PROOF</code> is a single packet and needs no mask.</li>
<li><strong>Data and proof are separate phases.</strong> <code>OTA_DATA</code> carries no proof; the proof is fetched once per block
via <code>OTA_REQ_PROOF</code>/<code>OTA_PROOF</code> after that block's data is complete. The receiver keeps a bounded two-block
window, so one block can await its proof or flash commit while DATA for the next block arrives. Slots retry
independently and only one stalled slot is retried per service tick.</li>
<li><strong>Data and proof remain separate packets, without a normal extra round trip.</strong> A server retains requested
blocks in a bounded descriptor queue, admits at most one response per main-loop pass, and sends one
<code>OTA_PROOF</code> after the requested fragments. The receiver waits 500 ms after completing a block, then uses
<code>OTA_REQ_PROOF</code> only as a loss/legacy-server fallback. It keeps a bounded multi-block window, so one block
can await proof or flash commit while DATA for the next block arrives. Slots retry independently and only
one stalled slot is retried per service tick.</li>
</ul>
<h3 id="85-sizing-against-max_packet_payload-184">8.5 Sizing against <code>MAX_PACKET_PAYLOAD = 184</code></h3>
<table>
@@ -2350,17 +2361,21 @@ payload); larger self-images pass a bigger scratch buffer.</p>
<h3 id="86-temporary-radio-and-transfer-boundary">8.6 Temporary-radio and transfer boundary</h3>
<p>OTA packets may cross normal mesh relay hops, but each participating node processes or relays them only while
its <code>tempradio</code> window is actually running. A receiver selects missing blocks in serial order into a bounded
two-block pipeline. It never serves partial blocks. A normal install receiver never re-advertises its completed
request pipeline. The default capacity is two blocks. Targets with a larger compiled capacity begin at two,
grow the live window by one after four clean verified blocks, and shrink it after two consecutive no-progress
service ticks; the RAK3401 LoRa-OTA target currently adapts from two through four blocks. It never serves
partial blocks. A normal install receiver never re-advertises its completed
download. An SD archive node is the deliberate exception: after a fully proof-verified container is published
to its persistent archive, it registers that complete file as a MotaSource and advertises it as a new seeder.
This keeps each active transfer as one transmitter and one receiver while still allowing active temporary-radio
repeaters between them and persistent archive nodes to improve future availability.</p>
<p>Because TempRadio is a bounded channel dedicated to this transfer, an accepted OTA flood normally relays after
a randomized 0.25 to 0.5 packet-airtime delay. Repeated requests for the same manifest block or proof provide
congestion feedback without changing the wire format. Frequent retries widen the local relay window through
0.5-1.0, 0.75-2.0, and finally 1.0-3.0 airtimes. The maximum is hard-capped at three packet airtimes. Each
30-second interval without another observed retry lowers the window one level. Other flood payloads retain the
role's normal randomized delay, and CAD still applies when enabled.</p>
<p>TempRadio is treated as a private maintenance network. Active transfer packets use priority 0, bypass the
public-flood receive holdoff and relay jitter, and do not schedule generic flood retries. The bounded serving
queue admits at most two DATA/PROOF packets ahead of the radio while preserving at least four free packet-pool
entries. CAD remains enabled to arbitrate the half-duplex channel, but its busy retry is scaled to one-quarter
of a packet airtime and clamped to 5-50 ms instead of the ordinary 120-360 ms cadence. Discovery traffic keeps
collision jitter and background priority. These changes remove software waits and duplicate bursts; they do
not remove the one required forwarding transmission per hop.</p>
<hr />
<h2 id="9-identity-trust-versioning">9. Identity, trust &amp; versioning</h2>
<ul>
@@ -2513,7 +2528,10 @@ ota dev ... bring-up helpers (stage/recv/serve/verify)
OTAFIX independently
locates the physical EndF, hashes the running app, and compares that value with the package before its
first app write. A physically absent/corrupt EndF or wrong base therefore returns to the unchanged app;
it still requires USB recovery if that app does not already contain this command.</li>
it still requires USB recovery if that app does not already contain this command.
A chain intended to cross historical firmware must introduce this command in its first bridge and retain
it in every later bridge. Manual pulls still use the build-provided target ID when app-side EndF parsing
fails, so a rescue-capable bridge can fetch its exact successor before invoking the guarded command.</li>
<li><strong>MeshTower V2 SD nRF52:</strong> the application stores a contiguous <code>/meshcore-ota.mota</code> on microSD and
publishes its raw sector range in a checksummed handoff record outside the MBR partition. The matching
bootloader reads the card without mounting FAT, supports either a full image or an in-place delta,
+11 -8
View File
@@ -1489,16 +1489,16 @@ battery telemetry, and <code>battery.alert</code> behavior.</p>
<hr />
<h2 id="the-important-part-first-its-safe">The important part first: it's safe</h2>
<ul>
<li><strong>Nothing installs by itself.</strong> Your node can <em>discover</em> and <em>download</em> an update in the background, but
<li><strong>Nothing installs by itself.</strong> Your node can <em>discover</em> and download an update, but
it only <strong>installs</strong> when you say so (unless you deliberately turn on auto-install - see below).</li>
<li><strong>Bad downloads can't sneak in.</strong> Every piece of the firmware is checked against a cryptographic
fingerprint as it arrives, and the whole image is verified again before install. A corrupt or tampered
download is rejected, not installed.</li>
<li><strong>You choose who to trust.</strong> Updates can be <em>signed</em> by their author. You can tell your node to only
auto-install firmware signed by keys you've added.</li>
<li><strong>It won't disrupt your mesh.</strong> OTA traffic is always the <strong>lowest priority</strong> - your node only spends
spare airtime on it. Messages and routing always come first; a busy node simply updates later. Think of
it as <em>"eventually upgradable."</em></li>
<li><strong>Discovery stays quiet; a transfer is deliberate.</strong> Periodic update discovery uses background priority.
Once a download starts, its transfer packets are primary traffic across every relay hop, so use
TempRadio as an OTA maintenance window when delaying unrelated mesh traffic would matter.</li>
<li><strong>It can recover.</strong> If an install ever fails, the node falls back to a safe recovery mode (you can
re-flash a known-good firmware over USB) - it won't be left bricked.</li>
</ul>
@@ -1568,8 +1568,10 @@ no guessing. <strong><code>validate</code> is the switch:</strong> a plain <code
re-running a <code>validate</code> pull re-begins fresh (it never resumes a stale partial). Nothing about the seed is
trusted - every kept block is checked against the target's own fingerprints, so a mismatched or missing seed
just means those blocks are fetched over the radio (correct result, only slower).</p>
<p>The node fetches from one source, <strong>at low priority</strong>, with a bounded two-block receive window. Mesh repeaters may carry the
packets, but only while their temporary-radio windows are active. Check progress with <code>ota status</code>.</p>
<p>The node fetches from one source as <strong>primary traffic</strong>, with a bounded adaptive block-request window.
RAK3401 OTA builds start with two concurrent block requests, grow to four on a clean link, and contract on
repeated stalls. Mesh repeaters carry the packets only while their temporary-radio windows are active.
Check progress with <code>ota status</code>.</p>
<p>If a <code>folder</code> pull loses its link mid-transfer, <code>ota status</code> shows <strong>paused</strong> - the host keeps the
partial and the pull resumes (filling only what's missing) the moment you reconnect motatool; it never
falls back to flash. To <strong>stop</strong> a download you no longer want:</p>
@@ -1597,6 +1599,7 @@ automatically.)</p>
target mismatch, corrupt payload, and invalid/untrusted signatures. The bootloader independently hashes
the running app and rejects a wrong base before writing the app. If the physical EndF is absent or this
command was not already in the running firmware, recover over USB.
Release chains should put this command in their first bridge and keep it in every bridge after that.
- If an <strong>install</strong> fails, the node won't boot a broken image - it lands in <strong>recovery mode</strong>:
- <strong>nRF52:</strong> it appears as a USB drive; drag a known-good firmware <code>.uf2</code> for that exact board onto it
to recover.
@@ -1670,8 +1673,8 @@ already active. Other FULL roles with browser OTA support can use the
<h3 id="everyone-helps-share">Everyone helps share</h3>
<p>You don't have to be a gateway to help. Once <strong>any</strong> node finishes downloading an update, it automatically
offers it to <em>its</em> neighbours too. So a new firmware spreads outward node-to-node, instead of everyone
hammering the one node that had it first - and no node is ever overloaded, because all of this stays
lowest-priority.</p>
hammering the one node that had it first. Discovery remains background traffic; an actual transfer is
primary traffic for the duration of its TempRadio maintenance window.</p>
<hr />
<h2 id="where-firmware-files-come-from">Where firmware files come from</h2>
<p>OTA distributes <strong><code>.mota</code></strong> files - a packaged, verifiable firmware image (full image or a small "delta"
+92 -67
View File
@@ -938,10 +938,21 @@
<ul class="md-nav__list" data-md-component="toc" data-md-scrollfix>
<li class="md-nav__item">
<a href="#corrected-test-candidate-bundle" class="md-nav__link">
<a href="#withdrawn-second-v11701-candidate" class="md-nav__link">
<span class="md-ellipsis">
Corrected test-candidate bundle
Withdrawn second v1.17.01 candidate
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#requirements-for-the-next-chain" class="md-nav__link">
<span class="md-ellipsis">
Requirements for the next chain
</span>
</a>
@@ -1004,10 +1015,10 @@
</li>
<li class="md-nav__item">
<a href="#corrected-release-live-preflight" class="md-nav__link">
<a href="#withdrawn-release-verification" class="md-nav__link">
<span class="md-ellipsis">
Corrected-release live preflight
Withdrawn-release verification
</span>
</a>
@@ -1015,10 +1026,10 @@
</li>
<li class="md-nav__item">
<a href="#direct-lab-template" class="md-nav__link">
<a href="#future-direct-lab-template" class="md-nav__link">
<span class="md-ellipsis">
Direct lab template
Future direct lab template
</span>
</a>
@@ -1163,10 +1174,21 @@
<ul class="md-nav__list" data-md-component="toc" data-md-scrollfix>
<li class="md-nav__item">
<a href="#corrected-test-candidate-bundle" class="md-nav__link">
<a href="#withdrawn-second-v11701-candidate" class="md-nav__link">
<span class="md-ellipsis">
Corrected test-candidate bundle
Withdrawn second v1.17.01 candidate
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#requirements-for-the-next-chain" class="md-nav__link">
<span class="md-ellipsis">
Requirements for the next chain
</span>
</a>
@@ -1229,10 +1251,10 @@
</li>
<li class="md-nav__item">
<a href="#corrected-release-live-preflight" class="md-nav__link">
<a href="#withdrawn-release-verification" class="md-nav__link">
<span class="md-ellipsis">
Corrected-release live preflight
Withdrawn-release verification
</span>
</a>
@@ -1240,10 +1262,10 @@
</li>
<li class="md-nav__item">
<a href="#direct-lab-template" class="md-nav__link">
<a href="#future-direct-lab-template" class="md-nav__link">
<span class="md-ellipsis">
Direct lab template
Future direct lab template
</span>
</a>
@@ -1299,11 +1321,9 @@
<h1 id="rak3401-1w-repeater-lora-update-chain">RAK3401 1W repeater LoRa update chain</h1>
<blockquote>
<p>Status: the corrected v1.17.01 26-package release candidate has passed
complete offline reconstruction, independent container verification, and
both stable OTAFIX 2.4 and Preview 6 C simulations. It still needs its first
complete physical RAK3401 run and is gated behind
<code>--accept-test-candidate</code>; do not use it in production yet.</p>
<p>Status: the second corrected v1.17.01 candidate is withdrawn. A physical
RAK3401 run passed steps 1 through 15, then step 16 booted but could not
validate its EndF. The runner blocks this exact bundle before device access.</p>
</blockquote>
<p>This procedure records the exact 26-step update chain from the release
<a href="https://github.com/mikecarper/MeshCore/releases/tag/rak3401-mota-v1.16.07-c1caa5ad-to-v1.17.01-c96bdd6e"><code>rak3401-mota-v1.16.07-c1caa5ad-to-v1.17.01-c96bdd6e</code></a>.
@@ -1318,27 +1338,37 @@ It is intentionally specific to the following destination firmware:</p>
<code>63d8df6387eaffd2e25db7d2a8ad967a65202182a48d681d7e7a9260f917280d</code></li>
</ul>
<p>Do not use this chain on another node, and do not start it on another RAK3401.
The artifact checks do not replace the pending complete physical-board run.
Use this candidate only for the next recoverable lab test.</p>
<h2 id="corrected-test-candidate-bundle">Corrected test-candidate bundle</h2>
<p>The replacement rebuilds steps 6 through 14 so both <code>Utils::sha256</code> overloads
use software SHA-256; hardware AES and HMAC stay enabled. Temporary steps 14
through 25 have monotonic EndF versions <code>1.16.9.110</code> through <code>1.16.9.121</code>.
The final endpoint is a real v1.17.01 build from <code>c96bdd6e</code>, with both its
runtime string and EndF version set to <code>1.17.1.0</code>. No v1.17.02 endpoint is used.</p>
The physical test has withdrawn this exact chain. Use it only for offline
diagnosis; the runner refuses live deployment even with
<code>--accept-test-candidate</code>.</p>
<h2 id="withdrawn-second-v11701-candidate">Withdrawn second v1.17.01 candidate</h2>
<p>The replacement rebuilt steps 6 through 15 around the first observed SHA
failure. Step 15 used software SHA-256 and passed its physical EndF check.
Step 16 re-enabled CC310 SHA with checked return codes. It downloaded and
applied successfully, booted <code>v1.16.9.112-ea3843e0</code>, then returned <code>ERR no
EndF</code>. This proves that CC310 can report success yet produce a wrong digest
for the memory-mapped application image; return-code fallback is insufficient.</p>
<pre><code class="language-text">/home/mesh/git/MeshCore/out-rak3401-mota-v1.17.01-corrected/RAK3401-update-chain-v1.16.7-c1caa5ad-to-v1.17.01-c96bdd6e.zip
</code></pre>
<p>ZIP SHA-256:
<code>693f08187e42cce72124f01328983965726bfbbb3fef80de503f06c4cbe9256a</code>.
<code>46c7480ed6bdc2aa01fb23a0f70e34c4012ffdd42b616d07bde66cf66d594630</code>.
The largest package is 88,280 bytes, leaving 1,832 bytes below the staging
limit. All 26 deltas reconstruct with both zero-filled and 0xFF-filled
workspaces. The endpoint has whole-image SHA-256
<code>e923f6209e0f071a5862ffbc7690e0b355c1e74829067e0bfdacd0de6c316065</code>
and EndF body hash <code>AC267B02E055F42E</code>.</p>
<p>The candidate includes the exact v1.16.7 test-start UF2/Nordic DFU ZIP, a
<p>The withdrawn bundle includes the exact v1.16.7 test-start UF2/Nordic DFU ZIP, a
diagnostic corrected step-6 image, and the final recovery UF2/ZIP. Restore the
lab node locally to the included test-start image before rerunning the chain.
The bundle's <code>RUNBOOK.md</code> contains the direct and two-hop commands.</p>
The bundle's <code>RUNBOOK.md</code> is retained for diagnosis only.</p>
<h2 id="requirements-for-the-next-chain">Requirements for the next chain</h2>
<p>The next chain must use software SHA-256 for every bridge. CC310 may remain
enabled for AES, HMAC, and entropy, but not for either <code>Utils::sha256</code>
overload. The guarded <code>ota rescue install &lt;base_hash16&gt;</code> command must be in
the first installed bridge (step 1) and every target after it. Offline
validation must reconstruct each target and prove that command is present;
the live runner also probes <code>ota help</code> after every successful transition and
refuses to expose a bridge without the rescue path to another update.</p>
<h2 id="withdrawn-v11702-chain-smoke-test-result">Withdrawn v1.17.02-chain smoke-test result</h2>
<p>The lab topology used a Heltec V4.3 OLED ESP32 Full Companion on
<code>/dev/ttyACM0</code> and a direct LoRa path to <code>RAK3401 1W Repeater</code>. The remote
@@ -1368,21 +1398,24 @@ safety gate and is not an acceptable recovery.</p>
<p>The lab RAK remains alive on v1.16.08.7. Its normal radio is restored to
<code>910.525,62.5,7,5</code>, and <code>system.watchdog</code> is restored and verified <code>on</code>.</p>
<h2 id="recover-the-lab-rak">Recover the lab RAK</h2>
<p>Recovery now requires a local data path to the RAK3401 itself. The Heltec
controller cable cannot recover the remote RAK, and this host currently has no
Bluetooth adapter.</p>
<p>The latest physical run stopped on <code>v1.16.9.112-ea3843e0</code>: normal radio is
restored to <code>910.525,62.5,7,5</code> and <code>system.watchdog</code> is verified <code>on</code>, but the
application reports <code>ERR no EndF</code>. That bridge predates the guarded rescue
command, so recovery requires a local data path to the RAK3401 itself. The
Heltec controller cable cannot recover the remote RAK.</p>
<ol>
<li>Connect the RAK3401 itself to this host by USB.</li>
<li>Enter its exact-board UF2 bootloader, using a local <code>uf2reset</code> command or
the board's double-reset gesture.</li>
<li>Flash the final RAK3401 UF2 from the corrected bundle's <code>recovery/final/</code>
directory:
<code>RAK_3401_repeater_lora_ota_no_external_sensors-ota-v1.17.01-halo-keymind-cascade-dev-c96bdd6e.uf2</code>.</li>
<li>After it boots, require version <code>v1.17.1</code>, target <code>2FA509C1</code>, hardware
<code>RAK_3401</code>, a valid <code>ota self</code> reply, normal radio settings, and watchdog
<code>on</code>.</li>
<li>For another chain test, flash the exact c1caa5ad test-start UF2 from the
withdrawn bundle's <code>recovery/test-start/</code> directory. Do not use its final
recovery image as a production recovery target; it predates the new
software-only SHA rule.</li>
<li>After it boots, require the c1caa5ad start identity, target <code>2FA509C1</code>,
hardware <code>RAK_3401</code>, a valid <code>ota self</code> reply, normal radio settings, and
watchdog <code>on</code>.</li>
</ol>
<p>Do not send step 7, force an apply, or issue a remote recovery reboot. Once the
<p>Do not force an apply or issue a remote recovery reboot. Once the
RAK USB cable is attached, the port and UF2 mount can be detected and the
recovery completed without guessing device paths.</p>
<h2 id="intended-runner-safety-behavior">Intended runner safety behavior</h2>
@@ -1398,9 +1431,10 @@ The dormant live path implements the release's watchdog sequence:</p>
<li>Re-enable the watchdog only after the exact step 26 image boots and passes
both version and EndF body-hash verification.</li>
</ol>
<p>These controls are active for the corrected candidate. They cannot repair the
old bridge whose own EndF check is broken. The candidate must pass a complete
physical-board run before its explicit lab-only gate is removed.</p>
<p>These controls remain requirements for the next candidate. They cannot repair
a running bridge that lacks both valid app-side EndF validation and the rescue
command. A replacement must pass a complete physical-board run before its
lab-only gate is removed.</p>
<p>An interrupted download is resumable. Avoid interrupting power or radio
coverage while <code>ota install</code> is applying a package. Physical USB recovery is
the fallback if an apply is interrupted at the wrong instant.</p>
@@ -1434,7 +1468,7 @@ bandwidth are legal at the deployment location. All nodes return to their
ordinary radio after each step.</p>
<h2 id="verify-without-touching-a-radio">Verify without touching a radio</h2>
<p>The chain runner pins the release ZIP SHA-256 to
<code>693f08187e42cce72124f01328983965726bfbbb3fef80de503f06c4cbe9256a</code>.
<code>46c7480ed6bdc2aa01fb23a0f70e34c4012ffdd42b616d07bde66cf66d594630</code>.
It also pins and verifies the inner checksum list, checks complete checksum
coverage, parses every manifest, verifies the chain continuity, and runs
<code>motatool verify</code> on all 26 containers.</p>
@@ -1446,30 +1480,20 @@ coverage, parses every manifest, verifies the chain continuity, and runs
</code></pre>
<p>An already-downloaded asset can be supplied with <code>--bundle</code>. Only the pinned
ZIP or its extracted root is accepted.</p>
<h2 id="corrected-release-live-preflight">Corrected-release live preflight</h2>
<p>The remote-admin password for the tested RAK is the lowercase default
<code>password</code>. Prefer the environment variable so it is not placed in the child
process command line:</p>
<pre><code class="language-bash">export MESHCORE_ADMIN_PASSWORD='password'
python3 tools/lora_ota/rak3401_mota_chain.py \
<h2 id="withdrawn-release-verification">Withdrawn-release verification</h2>
<p>No password or device connection is needed for offline verification:</p>
<pre><code class="language-bash">python3 tools/lora_ota/rak3401_mota_chain.py \
--work-dir ./rak3401-mota-chain-work \
--controller-serial /dev/ttyACM0 \
--source-tcp 192.168.1.51:5001 \
--source-cli-tcp 192.168.1.51:5002 \
--source-shares-controller \
--ota-hops 3 \
--accept-test-candidate \
--motatool /path/to/motatool \
--preflight-only
--verify-only
</code></pre>
<p>This validates the corrected bundle, Full Companion identity, source, and
destination without changing radio or watchdog settings. Omitting
<code>--accept-test-candidate</code> exits before connecting to a device. The withdrawn
v1.17.02 chain is also recognized and always blocked before device access.</p>
<h2 id="direct-lab-template">Direct lab template</h2>
<p>Use only on a locally recoverable test RAK3401. Keep the work directory for
resume, and retain the explicit <code>--accept-test-candidate</code> lab gate.</p>
<p>Use <code>--verify-only</code> for this withdrawn bundle. Every live invocation exits
before connecting to a device, including one with <code>--accept-test-candidate</code>.
The older failed v1.17.01 and v1.17.02 chains are also recognized and blocked.</p>
<h2 id="future-direct-lab-template">Future direct lab template</h2>
<p>This template becomes usable only after a new ZIP and its hashes are pinned in
the runner. Use it only on a locally recoverable test RAK3401. Keep the work
directory for resume, and retain the explicit <code>--accept-test-candidate</code> gate.</p>
<pre><code class="language-bash">export MESHCORE_ADMIN_PASSWORD='password'
python3 tools/lora_ota/rak3401_mota_chain.py \
@@ -1485,9 +1509,10 @@ python3 tools/lora_ota/rak3401_mota_chain.py \
</code></pre>
<p>The intended runner logs each isolated step attempt below <code>steps/</code>, records every
verified transition in <code>progress.jsonl</code>, and prints the source log path. Rerun
the exact command after a host restart or recoverable RF failure. Its default
60-second status interval deliberately leaves airtime for the low-priority OTA
transfer; avoid shortening it on a busy or relayed mesh.</p>
the exact command after a host restart or recoverable RF failure. Status polling
starts at 60 seconds, expands adaptively when replies are slow or the link is
contended, and contracts after quick replies. The transfer itself is primary
traffic, so reserve the TempRadio window for OTA on a busy or relayed mesh.</p>
<h2 id="relayed-lab-template">Relayed lab template</h2>
<p>Every intermediate relay must enter the same TempRadio tuple. Add one
<code>--relay</code> argument for each intermediate repeater, ordered from the destination
@@ -1529,7 +1554,7 @@ different password.</p>
--motatool /path/to/motatool \
--yes
</code></pre>
<p>Do not begin a production deployment until the corrected bundle passes a
<p>Do not begin a production deployment until a replacement bundle passes a
direct physical smoke test from start to endpoint. Run <code>--preflight-only</code> with
the same relay arguments first. The runner sends
the destination to TempRadio, then the relays farthest-to-nearest while their
+1 -1
View File
@@ -1 +1 @@
{"config":{"lang":["en"],"separator":"[\\s\\-]+","pipeline":["stopWordFilter"],"fields":{"title":{"boost":1000.0},"text":{"boost":1.0},"tags":{"boost":1000000.0}}},"docs":[{"location":"","title":"Introduction","text":"<p>Welcome to the MeshCore documentation.</p> <p>Below are a few quick start guides.</p> <ul> <li>Frequently Asked Questions</li> <li>CLI Commands</li> <li>Filter Policy Playground</li> <li>Telemetry History Decoder</li> <li>CLI Availability by Firmware Build</li> <li>Easy LoRa OTA: ESP32 full images and nRF52 deltas</li> <li>Scripted LoRa OTA: Bash and PowerShell</li> <li>MeshTower V2 microSD self-updates</li> <li>GPS Tracking</li> <li>Companion Protocol</li> <li>Full Companion: ESP32 and nRF52</li> <li>Packet Format</li> <li>QR Codes</li> </ul> <p>If you find a mistake in any of our documentation, or find something is missing, please feel free to open a pull request for us to review.</p> <ul> <li>Documentation Source</li> </ul>"},{"location":"WiFi/","title":"WiFi and MQTT by Firmware Type","text":"<p>MeshCore itself does not require WiFi or the internet. LoRa packet exchange, repeating, room servers, companions, and sensors can all operate without either. WiFi is added by particular ESP32 firmware targets for one or more of these purposes:</p> <ul> <li>a TCP connection between a WiFi companion and a phone or computer;</li> <li>the WebConfig browser interface;</li> <li>MQTT uplinking from the radio to internet or LAN brokers;</li> <li>WiFi-assisted OTA services on builds that include them;</li> <li>ESP-NOW bridging, which uses the ESP32's 2.4 GHz radio but is not a connection to a WiFi access point.</li> </ul> <p>The firmware role and the build profile are separate choices. For example, a logging repeater is not an MQTT observer, and a WiFi companion does not publish to MQTT unless its target name also contains <code>mqtt</code>.</p>"},{"location":"WiFi/#quick-reference","title":"Quick reference","text":"Firmware target or role Infrastructure WiFi MQTT What WiFi does <code>*_repeater</code> Build-dependent on ESP32 No FULL builds provide WebConfig, browser OTA, and the TCP 5001 LoRa-OTA seeder while WiFi is active <code>*_repeater_observer_mqtt</code> Yes Yes Uplinks heard and selected transmitted LoRa packets; repeating remains enabled by default <code>*_room_server</code> Build-dependent on ESP32 No FULL builds provide WebConfig, browser OTA, and the TCP 5001 LoRa-OTA seeder while WiFi is active <code>*_room_server_observer_mqtt</code> Yes Yes Runs the room server and uplinks radio traffic <code>*_companion_radio_wifi</code> Yes No Exposes the MeshCore companion protocol on TCP port 5000 <code>*_companion_radio_wifi_mqtt</code> Yes Yes Runs both the TCP companion interface and the MQTT uplink USB, BLE, or serial companion No No Uses the transport named by the target instead <code>*_repeater_bridge_espnow</code> Build-dependent on ESP32 No Uses ESP-NOW for its bridge; a FULL build also exposes the TCP 5001 LoRa-OTA seeder whenever WiFi is usable RS232 bridge No No Bridges through a serial interface Ethernet repeater/room server No WiFi No Uses wired Ethernet for its role-specific network interface <code>*_sensor</code> Build-dependent on ESP32 No A FULL ESP32 sensor can expose the TCP 5001 LoRa-OTA seeder through its browser-OTA setup AP Terminal-chat or KISS modem No in current targets No Uses LoRa and its role-specific local interface <code>*_lora_ota_no_external_sensors</code> On demand on ESP32 No Lean LoRa-OTA repeater image; ESP32 builds retain the compact <code>start ota</code> browser uploader <p>Direct on-device WiFi and MQTT are currently ESP32 features. nRF52, STM32, and the currently enabled RP2040 targets do not run this MQTT bridge. An nRF52 connected to a Raspberry Pi can still be logged or uplinked by software on the Pi, but that is a separate host-side bridge rather than MQTT running in the radio firmware.</p>"},{"location":"WiFi/#how-the-mqtt-bridge-works","title":"How the MQTT bridge works","text":"<p>The MQTT bridge is an outbound observer. It does not subscribe to MQTT topics and does not inject broker messages into LoRa.</p> <p>For a received packet, the flow is:</p> <pre><code>LoRa radio -&gt; successful packet parse -&gt; MQTT capture queue -&gt; broker slots\n \\----&gt; normal MeshCore filtering/handling\n</code></pre> <p>Packets transmitted by the node can also be queued according to <code>mqtt.tx</code>. Receive capture happens before the packet is passed to the normal MeshCore flood filters and forwarding decision. A packet may therefore be observed on MQTT even when a later scope, path, region, duplicate, or repeat rule prevents the node from forwarding it over LoRa.</p> <p>MQTT publication and LoRa repetition are separate:</p> <ul> <li><code>set repeat on|off</code> controls whether an observer repeats eligible LoRa packets;</li> <li><code>set bridge.enabled on|off</code> starts or stops the MQTT bridge;</li> <li><code>set Line truncated
{"config":{"lang":["en"],"separator":"[\\s\\-]+","pipeline":["stopWordFilter"],"fields":{"title":{"boost":1000.0},"text":{"boost":1.0},"tags":{"boost":1000000.0}}},"docs":[{"location":"","title":"Introduction","text":"<p>Welcome to the MeshCore documentation.</p> <p>Below are a few quick start guides.</p> <ul> <li>Frequently Asked Questions</li> <li>CLI Commands</li> <li>Filter Policy Playground</li> <li>Telemetry History Decoder</li> <li>CLI Availability by Firmware Build</li> <li>Easy LoRa OTA: ESP32 full images and nRF52 deltas</li> <li>Scripted LoRa OTA: Bash and PowerShell</li> <li>MeshTower V2 microSD self-updates</li> <li>GPS Tracking</li> <li>Companion Protocol</li> <li>Full Companion: ESP32 and nRF52</li> <li>Packet Format</li> <li>QR Codes</li> </ul> <p>If you find a mistake in any of our documentation, or find something is missing, please feel free to open a pull request for us to review.</p> <ul> <li>Documentation Source</li> </ul>"},{"location":"WiFi/","title":"WiFi and MQTT by Firmware Type","text":"<p>MeshCore itself does not require WiFi or the internet. LoRa packet exchange, repeating, room servers, companions, and sensors can all operate without either. WiFi is added by particular ESP32 firmware targets for one or more of these purposes:</p> <ul> <li>a TCP connection between a WiFi companion and a phone or computer;</li> <li>the WebConfig browser interface;</li> <li>MQTT uplinking from the radio to internet or LAN brokers;</li> <li>WiFi-assisted OTA services on builds that include them;</li> <li>ESP-NOW bridging, which uses the ESP32's 2.4 GHz radio but is not a connection to a WiFi access point.</li> </ul> <p>The firmware role and the build profile are separate choices. For example, a logging repeater is not an MQTT observer, and a WiFi companion does not publish to MQTT unless its target name also contains <code>mqtt</code>.</p>"},{"location":"WiFi/#quick-reference","title":"Quick reference","text":"Firmware target or role Infrastructure WiFi MQTT What WiFi does <code>*_repeater</code> Build-dependent on ESP32 No FULL builds provide WebConfig, browser OTA, and the TCP 5001 LoRa-OTA seeder while WiFi is active <code>*_repeater_observer_mqtt</code> Yes Yes Uplinks heard and selected transmitted LoRa packets; repeating remains enabled by default <code>*_room_server</code> Build-dependent on ESP32 No FULL builds provide WebConfig, browser OTA, and the TCP 5001 LoRa-OTA seeder while WiFi is active <code>*_room_server_observer_mqtt</code> Yes Yes Runs the room server and uplinks radio traffic <code>*_companion_radio_wifi</code> Yes No Exposes the MeshCore companion protocol on TCP port 5000 <code>*_companion_radio_wifi_mqtt</code> Yes Yes Runs both the TCP companion interface and the MQTT uplink USB, BLE, or serial companion No No Uses the transport named by the target instead <code>*_repeater_bridge_espnow</code> Build-dependent on ESP32 No Uses ESP-NOW for its bridge; a FULL build also exposes the TCP 5001 LoRa-OTA seeder whenever WiFi is usable RS232 bridge No No Bridges through a serial interface Ethernet repeater/room server No WiFi No Uses wired Ethernet for its role-specific network interface <code>*_sensor</code> Build-dependent on ESP32 No A FULL ESP32 sensor can expose the TCP 5001 LoRa-OTA seeder through its browser-OTA setup AP Terminal-chat or KISS modem No in current targets No Uses LoRa and its role-specific local interface <code>*_lora_ota_no_external_sensors</code> On demand on ESP32 No Lean LoRa-OTA repeater image; ESP32 builds retain the compact <code>start ota</code> browser uploader <p>Direct on-device WiFi and MQTT are currently ESP32 features. nRF52, STM32, and the currently enabled RP2040 targets do not run this MQTT bridge. An nRF52 connected to a Raspberry Pi can still be logged or uplinked by software on the Pi, but that is a separate host-side bridge rather than MQTT running in the radio firmware.</p>"},{"location":"WiFi/#how-the-mqtt-bridge-works","title":"How the MQTT bridge works","text":"<p>The MQTT bridge is an outbound observer. It does not subscribe to MQTT topics and does not inject broker messages into LoRa.</p> <p>For a received packet, the flow is:</p> <pre><code>LoRa radio -&gt; successful packet parse -&gt; MQTT capture queue -&gt; broker slots\n \\----&gt; normal MeshCore filtering/handling\n</code></pre> <p>Packets transmitted by the node can also be queued according to <code>mqtt.tx</code>. Receive capture happens before the packet is passed to the normal MeshCore flood filters and forwarding decision. A packet may therefore be observed on MQTT even when a later scope, path, region, duplicate, or repeat rule prevents the node from forwarding it over LoRa.</p> <p>MQTT publication and LoRa repetition are separate:</p> <ul> <li><code>set repeat on|off</code> controls whether an observer repeats eligible LoRa packets;</li> <li><code>set bridge.enabled on|off</code> starts or stops the MQTT bridge;</li> <li><code>set Line truncated
+28 -28
View File
@@ -2,114 +2,114 @@
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
<url>
<loc>https://mikecarper.github.io/MeshCore/</loc>
<lastmod>2026-08-13</lastmod>
<lastmod>2026-08-14</lastmod>
</url>
<url>
<loc>https://mikecarper.github.io/MeshCore/WiFi/</loc>
<lastmod>2026-08-13</lastmod>
<lastmod>2026-08-14</lastmod>
</url>
<url>
<loc>https://mikecarper.github.io/MeshCore/cli_build_matrix/</loc>
<lastmod>2026-08-13</lastmod>
<lastmod>2026-08-14</lastmod>
</url>
<url>
<loc>https://mikecarper.github.io/MeshCore/cli_command_availability/</loc>
<lastmod>2026-08-13</lastmod>
<lastmod>2026-08-14</lastmod>
</url>
<url>
<loc>https://mikecarper.github.io/MeshCore/cli_commands/</loc>
<lastmod>2026-08-13</lastmod>
<lastmod>2026-08-14</lastmod>
</url>
<url>
<loc>https://mikecarper.github.io/MeshCore/companion_protocol/</loc>
<lastmod>2026-08-13</lastmod>
<lastmod>2026-08-14</lastmod>
</url>
<url>
<loc>https://mikecarper.github.io/MeshCore/companion_radio_full/</loc>
<lastmod>2026-08-13</lastmod>
<lastmod>2026-08-14</lastmod>
</url>
<url>
<loc>https://mikecarper.github.io/MeshCore/docs/</loc>
<lastmod>2026-08-13</lastmod>
<lastmod>2026-08-14</lastmod>
</url>
<url>
<loc>https://mikecarper.github.io/MeshCore/faq/</loc>
<lastmod>2026-08-13</lastmod>
<lastmod>2026-08-14</lastmod>
</url>
<url>
<loc>https://mikecarper.github.io/MeshCore/filter_tool/</loc>
<lastmod>2026-08-13</lastmod>
<lastmod>2026-08-14</lastmod>
</url>
<url>
<loc>https://mikecarper.github.io/MeshCore/flood_filtering/</loc>
<lastmod>2026-08-13</lastmod>
<lastmod>2026-08-14</lastmod>
</url>
<url>
<loc>https://mikecarper.github.io/MeshCore/gps_tracking/</loc>
<lastmod>2026-08-13</lastmod>
<lastmod>2026-08-14</lastmod>
</url>
<url>
<loc>https://mikecarper.github.io/MeshCore/halo_keymind_settings/</loc>
<lastmod>2026-08-13</lastmod>
<lastmod>2026-08-14</lastmod>
</url>
<url>
<loc>https://mikecarper.github.io/MeshCore/kiss_modem_protocol/</loc>
<lastmod>2026-08-13</lastmod>
<lastmod>2026-08-14</lastmod>
</url>
<url>
<loc>https://mikecarper.github.io/MeshCore/lora_ota_automation/</loc>
<lastmod>2026-08-13</lastmod>
<lastmod>2026-08-14</lastmod>
</url>
<url>
<loc>https://mikecarper.github.io/MeshCore/nrf52_power_management/</loc>
<lastmod>2026-08-13</lastmod>
<lastmod>2026-08-14</lastmod>
</url>
<url>
<loc>https://mikecarper.github.io/MeshCore/number_allocations/</loc>
<lastmod>2026-08-13</lastmod>
<lastmod>2026-08-14</lastmod>
</url>
<url>
<loc>https://mikecarper.github.io/MeshCore/ota_easy/</loc>
<lastmod>2026-08-13</lastmod>
<lastmod>2026-08-14</lastmod>
</url>
<url>
<loc>https://mikecarper.github.io/MeshCore/ota_meshtower_v2_sdcard/</loc>
<lastmod>2026-08-13</lastmod>
<lastmod>2026-08-14</lastmod>
</url>
<url>
<loc>https://mikecarper.github.io/MeshCore/ota_protocol/</loc>
<lastmod>2026-08-13</lastmod>
<lastmod>2026-08-14</lastmod>
</url>
<url>
<loc>https://mikecarper.github.io/MeshCore/ota_user_guide/</loc>
<lastmod>2026-08-13</lastmod>
<lastmod>2026-08-14</lastmod>
</url>
<url>
<loc>https://mikecarper.github.io/MeshCore/packet_format/</loc>
<lastmod>2026-08-13</lastmod>
<lastmod>2026-08-14</lastmod>
</url>
<url>
<loc>https://mikecarper.github.io/MeshCore/payloads/</loc>
<lastmod>2026-08-13</lastmod>
<lastmod>2026-08-14</lastmod>
</url>
<url>
<loc>https://mikecarper.github.io/MeshCore/qr_codes/</loc>
<lastmod>2026-08-13</lastmod>
<lastmod>2026-08-14</lastmod>
</url>
<url>
<loc>https://mikecarper.github.io/MeshCore/rak3401_mota_chain/</loc>
<lastmod>2026-08-13</lastmod>
<lastmod>2026-08-14</lastmod>
</url>
<url>
<loc>https://mikecarper.github.io/MeshCore/stats_binary_frames/</loc>
<lastmod>2026-08-13</lastmod>
<lastmod>2026-08-14</lastmod>
</url>
<url>
<loc>https://mikecarper.github.io/MeshCore/telemetry_decoder/</loc>
<lastmod>2026-08-13</lastmod>
<lastmod>2026-08-14</lastmod>
</url>
<url>
<loc>https://mikecarper.github.io/MeshCore/terminal_chat_cli/</loc>
<lastmod>2026-08-13</lastmod>
<lastmod>2026-08-14</lastmod>
</url>
</urlset>
BIN
View File
Binary file not shown.