This commit is contained in:
mikecarper committed 2026-08-14 10:01:05 +00:00
1 parent 4cd00b2dd5
commit bcb774394e
3 files changed
+48 -24

No files matched your search

+40 -20
View File
@@ -2159,7 +2159,7 @@ small delta that fits page 0 does zero flash I/O until COMPLETE.</p>
<td><code>OTA_REQ</code></td>
<td>0x06</td>
<td>transfer</td>
<td>request specific DATA fragments of one block (<code>want_mask</code>)</td>
<td>request fragments from an adaptive flight of 1-4 blocks (<code>want_mask</code> per block)</td>
</tr>
<tr>
<td><code>OTA_DATA</code></td>
@@ -2199,8 +2199,10 @@ small delta that fits page 0 does zero flash I/O until COMPLETE.</p>
<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>Reliability is <em>eventual</em>:</strong> the fetcher re-requests only missing fragments after an adaptive deadline
derived from packet airtime, outstanding response packets, duty pacing, and path length. The manager may
still run a one-second maintenance tick, but that tick is not itself a retry timer. 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
@@ -2266,20 +2268,21 @@ pattern), all in <code>OtaManager</code>:</p>
OTA_GET_MANIFEST(mid, want_mask) &gt; (want_mask=0xFFFF first; only missing fragments on retry)
&lt;------- OTA_MANIFEST(mid, frag_idx, frag_total, bytes) x requested frags
(reassemble manifest, verify, compute geometry: BC, block_size, payload_size)
for each missing block:
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)
&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])
for each adaptive flight of missing blocks (starts at 1, grows on clean flights):
OTA_REQ(mid, {block_idx, want_mask}[]) &gt; (one packet; all fragments first, only holes on recovery)
&lt;------- OTA_DATA(mid, block_idx, frag_off, data) x requested frags/block
&lt;------- OTA_PROOF(mid, block_idx, n_proof, proof) x requested blocks
(independently reassemble + verify each block, but remain RX-silent until the flight drains)
[after adaptive deadline: recover one block's holes, or OTA_REQ_PROOF for a missing proof]
(clean flight grows by one block; recovered flight halves the next width)
when all blocks present: verify full merkle_root + image_hash -&gt; COMPLETE
</code></pre>
<h3 id="84-message-bodies-transfer">8.4 Message bodies (transfer)</h3>
<p>All offsets after the 1-byte type. Encoders/decoders in <code>OtaProtocol.cpp</code>; constants in <code>OtaManager.h</code>.</p>
<pre><code>OTA_GET_MANIFEST: manifest_id[4] want_mask(uint16) # bit k = send manifest fragment k; 0xFFFF = all
OTA_MANIFEST: manifest_id[4] frag_idx(1) frag_total(1) bytes[] # up to OTA_MF_FRAG=176 B/frag
OTA_REQ: manifest_id[4] block_idx(uint16) want_mask(uint16) # bit k = send fragment k of block
OTA_REQ: manifest_id[4] { block_idx(uint16) want_mask(uint16) }[1..4]
# one or more rows; bit k = send fragment k of that block
OTA_DATA: manifest_id[4] block_idx(uint16) frag_off(uint16) data[] # up to OTA_FRAG_DATA=160 B
OTA_REQ_PROOF: manifest_id[4] block_idx(uint16)
OTA_PROOF: manifest_id[4] block_idx(uint16) n_proof(1) proof[] # n_proof x 4 bytes
@@ -2307,6 +2310,17 @@ OTA_LEAVES: manifest_id[4] frag_idx(1) frag_total(1) bytes[] # up
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>
</li>
<li><strong>Adaptive flight size is not signed block size.</strong> The container continues to use 1 KiB Merkle leaves and
each slot is one existing 1 KiB block. A clean link changes how many of those blocks one <code>OTA_REQ</code> names:
1, then 2, then 3, then the compiled cap. The manifest stores <code>block_size_log2</code>, so 3 KiB is not a valid
logical geometry; enabling 2 KiB would require larger device reassembly buffers and new-package/bootloader
validation while saving only one request/proof pair per 2 KiB. It does not address premature retries, which
were the dominant packet multiplier.</li>
<li><strong>Append-only request-window compatibility:</strong> the first four-byte request row is exactly the original
<code>block_idx + want_mask</code> body. Old sources decode that row and ignore appended bytes. New sources queue all
rows. If a new fetcher meets an old source, the unserved tail rows eventually time out and are recovered as
ordinary single-row requests; the dirty flight then contracts. Old fetchers continue sending nine-byte
single-row requests, which new sources accept normally.</li>
<li><strong>Fragment-level requests (anti-deadlock + anti-congestion):</strong> <code>OTA_REQ</code>, <code>OTA_GET_MANIFEST</code>, and <code>OTA_QUERY</code>
carry fragment masks. For catalog discovery, <code>want_fragments</code> is a 32-bit bitmap and covers the protocol
maximum 255-row catalog (26 fragments at the current packet size). For block and manifest transfer, the
@@ -2319,10 +2333,10 @@ OTA_LEAVES: manifest_id[4] frag_idx(1) frag_total(1) bytes[] # up
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 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>
<code>OTA_PROOF</code> after each block's requested fragments. The receiver uses an airtime/path-aware proof grace
(never less than 500 ms) and defers <code>OTA_REQ_PROOF</code> while any flight slot still expects DATA. Thus a missing
early proof cannot make the receiver transmit into the rest of a legitimate half-duplex response train.
After the complete-flight deadline, only one slot's missing fragments/proof is requested at a time.</li>
</ul>
<h3 id="85-sizing-against-max_packet_payload-184">8.5 Sizing against <code>MAX_PACKET_PAYLOAD = 184</code></h3>
<table>
@@ -2361,9 +2375,11 @@ 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
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
request flight. Every session starts with one block. A clean completed flight increases the next request by
one block; a flight requiring fragment/proof recovery halves the next width (<code>4 -&gt; 2</code>, <code>3 -&gt; 2</code>, <code>2 -&gt; 1</code>).
The default compiled cap is two blocks; the RAK3401 LoRa-OTA target caps at four, so it probes
<code>1 -&gt; 2 -&gt; 3 -&gt; 4</code>. All rows are sent in one backward-compatible <code>OTA_REQ</code>, and no freed slot is refilled
until the current flight is finished. 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.
@@ -2374,8 +2390,12 @@ public-flood receive holdoff and relay jitter, and do not schedule generic flood
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>
collision jitter and background priority. The fetch deadline uses the active radio's measured maximum-packet
airtime, remaining DATA/PROOF packet count, dispatcher airtime factor, and longest observed path (falling back
to the configured hop horizon before one is observed), with bounded guard time. Faster SF/BW settings
therefore recover loss sooner; slower or multi-hop settings do not spuriously re-request a response still on
air. 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>
+7 -3
View File
@@ -1568,9 +1568,13 @@ 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 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.
<p>The node fetches from one source as <strong>primary traffic</strong>, with bounded adaptive request flights. Every session
probes with one 1 KiB block, then clean flights grow <code>1 -&gt; 2 -&gt; 3 -&gt; 4</code> blocks on RAK3401 OTA builds. All blocks
in a flight share one request packet, and the receiver stays silent until the source/relays finish returning
them. A recovery halves the next flight. Retry timing follows the active SF/BW airtime, duty budget, and path
length, so faster settings recover sooner without a fixed one-second request colliding with a valid multi-hop
response. The signed block size itself remains 1 KiB. 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
+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 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 mqtt.rx 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 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 mqtt.rx Line truncated