The project moved to meshcore-dev/meshcore, but two links still pointed at
ripplebiz/MeshCore: the GitHub Issues link in README and the git clone in the
FAQ build instructions.
Also fixes the FAQ build recipe:
- `pio run -e RAK_4631_Repeater` does not exist; the env is spelled
RAK_4631_repeater (variants/rak4631/platformio.ini:40).
- LORA_FREQ was given as 867.5; the default in [arduino_base] is 869.618
(platformio.ini:29). Reworded to point at the flag rather than a value that
drifts with the default preset.
- Renamed the venv so it no longer collides with the cloned directory name.
And one more env-name casing slip in the Raspberry Pi flashing instructions:
`Heltec_V3_companion_radio_ble` should be `Heltec_v3_...`. Release artifacts are
named directly from the env name (build.sh:147), and the two neighbouring
examples in the same block already use the lowercase spelling.
f6338430 added get/set dutycycle to CommonCLI and updated both CLI docs, but
Terminal Chat does not use CommonCLI -- simple_secure_chat/main.cpp:479-507 has
its own `set` handler supporting only af, name, lat, lon, tx and freq. Typing
`set dutycycle` there returns "ERROR: unknown config".
Restores `set af` as the documented command, points readers at cli_commands.md
for the roles that do have dutycycle, and documents `help`
(simple_secure_chat/main.cpp:510), which the client implements but the doc
never listed.
The doc claimed every pwrmgt command except `get pwrmgt.support` returns
"ERROR: Power management not supported" when NRF52_POWER_MANAGEMENT is not
defined. `get pwrmgt.bootreason` is not inside the #ifdef (CommonCLI.cpp:787)
and answers on all boards; only pwrmgt.source and pwrmgt.bootmv are gated.
Three factual errors in companion_protocol.md:
- The channel-datagram payload cap was given as 163 (MAX_FRAME_SIZE - 9 when
MAX_FRAME_SIZE was still 172). It went 172 -> 176 in 62f1b11d, but simply
updating the arithmetic to 167 would be worse than the stale value: 167 is
only the host-frame bound (MyMesh.cpp:1265). The radio-side bound is
MAX_GROUP_DATA_LENGTH = 184 - 16 - 3 = 165 (MeshCore.h:21, enforced at
BaseChatMesh.cpp:544). A 166- or 167-byte payload passes the frame check,
fails the radio check, and comes back as ERR_CODE_TABLE_FULL -- which this
same doc describes as "retry later", so a conforming client would retry a
permanently failing send forever. Documents 165 as the limit to enforce and
calls out the 166-167 band explicitly.
- Channel index was documented as 0-7 throughout. The bound is
MAX_GROUP_CHANNELS (BaseChatMesh.cpp:927,936), which is 40 on most current
variants, 8 on some and 1 on others. Clients should read max_channels from
byte 3 of PACKET_DEVICE_INFO instead. Index 0 is pre-populated with the
built-in Public channel but is not reserved.
- The secret field was documented as "all zeros" for public channels. The
firmware always hashes a real 16-byte key; the public channel ships with
izOH6cXN6mrJ5e26oRXNcg== (companion_radio/MyMesh.cpp:111,1040). An all-zero
secret is not a private channel and not an inert one: SHA256 over 16 zero
bytes is a fixed global constant, giving a well-known channel with an
all-zero AES key. searchChannelsByHash skips unnamed slots for exactly this
reason (BaseChatMesh.cpp:392-401), but a named slot with a zero secret is
not skipped and will absorb null-key group traffic from any node. Now
documented as something not to do.
Five "Default:" values in cli_commands.md no longer matched the firmware:
- radio / freq: the default preset moved to EU/UK (Narrow) in b777a7c6,
changing LORA_FREQ/BW/SF from 869.525/250/11 to 869.618/62.5/8
(platformio.ini:29-31). No variant overrides these, and LORA_CR is 5
on every path, so the full preset is 869.618,62.5,8,5.
- flood.advert.interval: raised to 47 hours in 40180b8f for both repeater
and room server; sensor leaves it disabled.
- advert.interval: prefs store minutes/2 and the getter doubles on read, so a
factory-fresh node reports 2, not 0. But savePrefs() zeroes any interval
below the 60 minute minimum (CommonCLI.cpp:162-165), and it is called from
every `set` handler -- so the value becomes 0 as soon as the node is
configured. Documented both states, since neither alone is the whole story.
- direct.txdelay: repeater defaults to 0.3, room server and sensor to 0.2.
Sources: platformio.ini:29-31, simple_repeater/MyMesh.cpp:893,903-904,
simple_room_server/MyMesh.cpp:650,661-662, simple_sensor/SensorMesh.cpp:716,
726-727, CommonCLI.cpp:162-165,680-681.
PA PL1 re-targets the PA's DC-DC supply rail rather than selecting a
logic-level gain, and the serial CLI is serviced on every main-loop pass
regardless of whether a transmit is in flight. A `set radio.fem.txgain`
write could therefore move the rail mid-transmit, while the SX1262 was
still driving the PA at full input power.
Record the requested level in setPAGainEnable() and drive the pin from
setTxModeEnable(), which runs from onBeforeTransmit() ahead of
startTransmit(). The level only matters while transmitting, so deferring
costs nothing.
Document that the pref is saved immediately but applied at the next
transmit, so `get radio.fem.txgain` can lead the hardware until then.
The host-side build/verify/inspect/serve CLI now lives as a standalone Rust
project at https://github.com/vk496/motatool. Remove the in-tree C++ copy
(tools/motatool/) and repoint the docs (ota_protocol.md, ota_user_guide.md,
tools/mota/README.md) at the standalone repo.
No cross-dependency either direction: MeshCore's firmware build never invoked
motatool (only tools/mota/ Python glue runs in the build), and motatool depends
only on the shared .mota wire spec in docs/ota_protocol.md. tools/mota/
(pio_endf.py EndF hook, motalib.py reference lib, gen_targets.py) is unchanged.
(cherry picked from commit 872413231d)
Spell out how the warm-start seed is actually applied (a user asked): the seed is the --seed <file> given to `motatool serve`, NOT a file dropped into the --dir destination (which starts empty). motatool stamps that one seed into the fresh .part on every `… folder validate` begin, so it's always the named file — no guessing. `validate` is the switch (a plain folder pull fetches from 0); a re-pull re-begins fresh (never resumes a stale partial); a mismatched/absent seed just falls back to fetching those blocks over the radio (correct result, only slower).
(cherry picked from commit cbdc629f20)
Add an admin-only `ota stats` reply: one dense line with the running firmware's merkle content-id (mid) AND its EndF body_hash (only body_hash was surfaced before), version, served-set count + digest, live fetch state/progress, and policy — snprintf-bounded to the 160-byte reply. The remote CLI path is already admin-gated, so it's admin-only over the mesh (send it from the app's repeater command screen, or the WiFi/serial OTA console). A new servedDigest() accessor exposes the beacon set-digest.
HW-verified on RAK4631: reports the fw identity + live fetch state (incl. during a warm-start capture).
(cherry picked from commit b159737ede)
Protocol: OTA_REQ and OTA_GET_MANIFEST now carry a want_mask bitmap, so a fetcher asks for specific fragments (all on the first request, only the still-missing holes on a retry) instead of a whole block/manifest window. The WANT_MANIFEST and FETCHING retry loops re-ask only on a no-progress tick, so a lost fragment costs one fragment to recover and a re-request can't collide with an in-flight multi-fragment burst on half-duplex radios.
Warm-start (motatool folder-capture only): new OTA_GET_LEAVES/OTA_LEAVES let `ota pull <#> folder validate` bulk-fetch the target's merkle leaves, authenticate them against the manifest root, diff a similar seed build already staged in the destination, and pull DATA over LoRa only for the blocks that differ. Leaves are bitmap-fragmented + no-progress retry-gated like the manifest, capped at OTA_DIFF_MAX_BLOCKS so the want_mask stays a fixed uint16, and the diff runs a bounded batch per loop tick so it never starves the mesh loop. motatool `serve --seed <build.mota>` injects the seed payload into the destination .part on OP_BEGIN.
(cherry picked from commit 8953214761)
Document `ota pull <#> <dest>` (flash|folder, destination mandatory), the folder
pull that captures a device's exact firmware to the host as <mid>.mota (for
delta-building), the paused/resume-on-reconnect behaviour, and that a `motatool
serve` link doubles as the pull-to-folder store. (protocol §10, user guide,
motatool README)
(cherry picked from commit 49820b57aa)
Bound OTA-over-LoRa duty cycle across repeaters with one runtime-tunable,
persisted limit (OtaManager::max_hops, `ota config hops <0..8>`, default 3):
- Accept-gate: a node ignores OTA that arrived from more than max_hops hops
away (neither processes nor relays it). 0 = direct only.
- Forward-cap: relay a flood only while still under max_hops, appending this
node's path-hash (hop count increments like the mesh flood routing).
- RAM guard: relay an OTA flood only while more than OTA_FWD_MIN_FREE packet-
pool slots stay free, so heavy OTA (best-effort, lowest-priority) can never
monopolise the shared pool and starve real traffic — a dropped relay is
re-requested by the source.
Persisted in NodePrefs (CommonCLI) and shown in `ota config`. Docs updated.
(cherry picked from commit 259802b706)
The discovery beacon previously re-announced at a random 3-10 min interval.
Replace that with a fixed, user-configurable cadence:
- OtaManager::advert_mins() — re-advertise every N minutes after the boot
burst; 0 disables periodic re-advertise (boot burst only). Default 24h.
- Persisted in NodePrefs (CommonCLI) and runtime-tunable: `ota config advert
<minutes>` (0..10080; 0 = off), and shown in `ota config`.
- When periodic advert is disabled, the scheduler still re-checks the config
on a slow timer, so a later `ota config advert <mins>` takes effect live.
Also advertise immediately whenever the served set changes — when a motatool
folder is attached to / detached from the ESP32 WiFi seeder — so peers learn
about newly-available firmware without waiting for the next interval (the
`ota folder` serial path already announced on attach).
Docs: protocol beacon-cadence note + user-guide `ota config advert`.
(cherry picked from commit 9ff1c883c2)
Extends the USB-serial folder relay to WiFi so an ESP32 companion can both
serve .mota and be operated headlessly:
- motatool `serve --tcp <host[:port]>`: a TcpTransport sibling of the serial
transport (default port 5001). SeederCore/Folder are reused unchanged — the
COUNT/DESCRIBE/READ protocol is transport-agnostic.
- ESP32 companion: a dedicated OTA seeder port (5001) for `serve --tcp`, plus
an OTA text console on 5002 (`nc <ip> 5002` -> `ota status|ls|announce|...`,
the same handle_ota_command CLI serial nodes have). Both run alongside the
phone-app port (5000); all three coexist.
- WiFi.setSleep(false): ESP32 STA mode's modem power-save periodically sleeps
the modem/CPU and stalls the SX1262 SPI+DIO servicing, leaving LoRa deaf
while WiFi is associated. Disabling it restores the radio (HW-validated:
a V3 WiFi companion is then discovered over LoRa and discovers its peers).
- docs: serving .mota over WiFi (protocol §10.2 + user guide).
(cherry picked from commit 280b9b1555)
mkdocs will only consider the first H1 (if any) and subheaders under it for the table of contents
this increases the header levels of everything below "important concepts" by 1 so that the table of contents correctly resolves them
Switch ESP32-S3 KISS modem environments to HWCDC and move outbound KISS writes to a non-blocking queued frame path so loop() and TX completion keep progressing when the host reads slowly. Add native backpressure regression tests and correct the native_kiss_modem test filter so this suite runs directly with pio test.