Merge remote-tracking branch 'origin/keymindCascade' into keymindCascade

This commit is contained in:
mikecarper committed 2026-10-26 18:36:04 -07:00
commit f1feed086b
4 files changed
+470

No files matched your search

+3
View File
@@ -4,6 +4,9 @@
# Scripted LoRa OTA from start to finish
For a manual example using a Pi-connected repeater and the app's admin console,
see [Station G2 to Ikoka Stick](ota_station_g2_to_ikoka_stick.md).
For **[1.18.0.1 USA Cascade](releases/1.18.0.1.md)**, download the exact board,
role and storage profile from the [firmware picker](firmware_picker.md).
For a new nRF52 installation, use the matching
+4
View File
@@ -22,6 +22,10 @@ LoRa. Choose the package type for the **destination** node:
For an end-to-end controller that accepts a release ZIP or ready mOTA, see
[Scripted LoRa OTA from start to finish](lora_ota_automation.md).
For a complete manual example with one Pi-connected seeder, see
[Station G2 to Ikoka Stick](ota_station_g2_to_ikoka_stick.md), including both
bootloader and firmware installation.
| Destination | Update type | Files needed to build the `.mota` | Installer |
| --- | --- | --- | --- |
| ESP32 | Full firmware | New non-merged application `.bin` | ESP32 A/B firmware slots |
+459
View File
@@ -0,0 +1,459 @@
<!-- meshcore-hosted-doc-link:start -->
<p class="meshcore-hosted-doc-link"><a href="https://mikecarper.github.io/MeshCore/ota_station_g2_to_ikoka_stick/">View this page on MeshCore Docs</a>.</p>
<!-- meshcore-hosted-doc-link:end -->
# LoRa OTA example: Station G2 on a Pi to an Ikoka Stick
Update the Ikoka Stick's bootloader to **OTAFIX 2.4.12**, then install
**1.18.0.1 Full Repeater** firmware. The Station G2 stays connected to the Pi
by USB and serves the files. Use the app's authenticated Repeater Admin console for the Ikoka
Stick, through your existing Companion.
| Board | Job |
| --- | --- |
| Raspberry Pi | Runs the file server |
| Station G2 Repeater | USB-connected LoRa seeder |
| Ikoka Stick 33 dBm Repeater | Receives and installs both updates |
| Companion | Keeps the app connected to the Ikoka Stick's admin console |
This example requires a **direct LoRa link** between the Station G2 and Ikoka
Stick. It keeps their existing main-channel settings during a four-hour
TempRadio window. The Companion stays on that same channel. Only the Station
G2 needs a USB connection to the Pi. For a route through intermediate
repeaters, every relay also needs an overlapping TempRadio window; see the
[scripted update guide](lora_ota_automation.md).
## 1. Check the Ikoka Stick
Run these in **Ikoka Stick — Repeater Admin** and save the replies:
```text
ver
get bootloader.ver
ota self
ota qspi
ota bootloader status
ota config
ota key list
get radio
get radio.rxps.config
```
The example starts with these reported values:
| Check | Required value for this example |
| --- | --- |
| Application hardware / target | `ikoka_stick_nrf_33dbm` / `E5331F02` |
| Installed bootloader | OTAFIX 2.4.11 |
| Bootloader identity | Board and target `28860044`, device `XIAO_DFU` |
| Bootloader support | `abi=3 caps=0E`, `QSPI apply OK`, `codecs=0x5` |
| Storage | `QSPI store:2048K`, working `ota qspi` probe |
These values support a remote bootloader upgrade. A stock bootloader or
missing QSPI apply support needs the matching local installation first.
The Stick uses its onboard flash; no additional storage is needed.
If `get radio.rxps.config` is unavailable, record `get radio.rxps` instead.
Keep each board's original RXPS and `ota config` replies for cleanup.
## 2. Install the Pi tools
On **Raspberry Pi — terminal**, install the host dependencies:
```bash
sudo apt update
sudo apt install -y build-essential pkg-config libudev-dev libssl-dev curl unzip pipx python3-venv
pipx ensurepath
pipx install 'meshcore-cli>=1.6.0'
```
Install stable [Rust](https://rustup.rs/) if it is not already installed:
```bash
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs -o /tmp/meshcore-rustup.sh
sh /tmp/meshcore-rustup.sh -y --profile minimal
```
Open a new terminal so the Pi finds `meshcli` and Cargo. Check that both Rust
and Cargo are **1.86 or newer**, then install the supported motatool revision:
```bash
rustc --version
cargo --version
cargo install --git https://github.com/mikecarper/motatool.git \
--rev 8c38369e7d35ad50cf74261869676d52dd24adf7 --locked --bin motatool
meshcli -v
motatool --version
mkdir -p ~/ikoka-stick-ota/downloads ~/ikoka-stick-ota/inputs ~/ikoka-stick-ota/serve
cd ~/ikoka-stick-ota
```
## 3. Prepare the two update files
On **Raspberry Pi — terminal**, download the signed bootloader bundle:
```bash
BOOT_URL=https://github.com/mikecarper/Adafruit_nRF52_Bootloader_OTAFIX/releases/download/v0.11.0-OTAFIX2.4.12
curl -fL "$BOOT_URL/OTAFIX-2.4.12-bootloader-mota.zip" -o downloads/bootloader-2.4.12.zip
unzip -q downloads/bootloader-2.4.12.zip -d inputs/bootloader-2.4.12
cp inputs/bootloader-2.4.12/mota/update-xiao_nrf52840_ble_bootloader-v0.11.0-OTAFIX2.4.12.mota serve/
BOOT_MOTA=serve/update-xiao_nrf52840_ble_bootloader-v0.11.0-OTAFIX2.4.12.mota
OTAFIX_PUBLIC_KEY=272564CC588D3D122285A15E6E2566D2ABE7177BB7EA1D1E41B23B29F0F85D2D
BOOT_KEY_FILE=inputs/bootloader-2.4.12/OTAFIX_MOTA_SIGNING_PUBLIC_KEY.txt
test "$(tr -d '[:space:]' < "$BOOT_KEY_FILE")" = "$OTAFIX_PUBLIC_KEY"
motatool verify "$BOOT_MOTA" --pub "$BOOT_KEY_FILE"
motatool inspect "$BOOT_MOTA"
```
The bootloader package must be signed, format 3, for **XIAO nRF52840 BLE** /
`XIAO_DFU`, target `28860044`, version 2.4.12. The key comparison and package
verification must both pass before continuing.
Download the exact ordinary **Ikoka Stick 33 dBm Full Repeater** application
DFU ZIP on **Raspberry Pi — terminal**:
```bash
APP_URL=https://github.com/mikecarper/MeshCore/releases/download/lora-ota-2-v1.18.0.1-halo-keymind-cascade-dev-03214a16
curl -fL "$APP_URL/ikoka_stick_nrf_33dbm_repeater-full-ota-v1.18.0.1-halo-keymind-cascade-dev-03214a16.zip" \
-o downloads/ikoka-stick-33dbm-repeater.zip
```
For a later release, use the [firmware picker](firmware_picker.md) to select
the same board, Repeater role, Full profile, and application DFU ZIP.
On **Raspberry Pi — terminal**, extract the application binary named by the
DFU manifest and check its firmware identity trailer:
```bash
python3 - <<'PY'
import hashlib, json, struct, zipfile
from pathlib import Path
with zipfile.ZipFile('downloads/ikoka-stick-33dbm-repeater.zip') as archive:
app = json.loads(archive.read('manifest.json'))['manifest']['application']
image = archive.read(app['bin_file'])
trailer = image[-56:]
assert len(trailer) == 56 and trailer[:4] == b'EndF', 'Missing EndF'
assert struct.unpack_from('<I', trailer, 4)[0] == len(image) - 56
assert hashlib.sha256(image[:-56]).digest()[:8] == trailer[8:16], 'Body hash mismatch'
assert struct.unpack_from('<I', trailer, 20)[0] == 0xE5331F02, 'Wrong application target'
assert trailer[24:].rstrip(b'\0') == b'ikoka_stick_nrf_33dbm', 'Wrong board'
Path('inputs/ikoka-stick-33dbm-repeater.bin').write_bytes(image)
print('Expected post-install base_hash:', trailer[8:16].hex().upper())
PY
```
Save the printed `base_hash`. Stop if extraction or any identity check fails.
Then make a full application package from that binary:
```bash
motatool build --fw inputs/ikoka-stick-33dbm-repeater.bin \
--out serve/ikoka-stick-33dbm-repeater.mota
motatool verify serve/ikoka-stick-33dbm-repeater.mota
motatool inspect serve/ikoka-stick-33dbm-repeater.mota
```
Check application target `E5331F02`, hardware `ikoka_stick_nrf_33dbm`, and the
expected newer firmware version. Save its eight-character `merkle_root` as the application
manifest ID. The package's `image_hash` covers the complete image; the
post-install `base_hash` is the body hash printed above. A full QSPI
application update needs no copy of the old firmware.
This locally packaged application is unsigned and uses explicit manual
installation; the separate bootloader package remains signed.
<details id="differential-upgrade" markdown="1">
<summary>Optional: differential firmware upgrade (smaller download)</summary>
A differential update sends a patch from the Ikoka Stick's exact running
application to the new application. It can reduce LoRa transfer time. The
bootloader update in step 7 still uses the signed full bootloader package.
Prepare the new binary and full package above first. Use this option only
when you also have the **original application DFU ZIP for the firmware
currently running on the Ikoka Stick**. A matching version label or a rebuild
of the same commit is not enough. If that original file is unavailable, use
the full update above.
On **Ikoka Stick — Repeater Admin**, get its current identity:
```text
ver
ota self
```
Copy the original application DFU ZIP onto **Raspberry Pi** as
`downloads/ikoka-stick-33dbm-repeater-running.zip`. In the Pi terminal, paste
the 16-character `base_hash` from that fresh `ota self` reply when prompted:
```bash
read -r -p 'Current Ikoka Stick base_hash (16 hex characters): ' IKOKA_BASE_HASH
export IKOKA_BASE_HASH
python3 - <<'PY'
import hashlib, json, os, re, struct, zipfile
from pathlib import Path
expected = os.environ['IKOKA_BASE_HASH'].strip().upper()
assert re.fullmatch(r'[0-9A-F]{16}', expected), 'Invalid base_hash'
with zipfile.ZipFile('downloads/ikoka-stick-33dbm-repeater-running.zip') as archive:
app = json.loads(archive.read('manifest.json'))['manifest']['application']
image = archive.read(app['bin_file'])
trailer = image[-56:]
assert len(trailer) == 56 and trailer[:4] == b'EndF', 'Missing EndF'
assert struct.unpack_from('<I', trailer, 4)[0] == len(image) - 56
assert hashlib.sha256(image[:-56]).digest()[:8] == trailer[8:16], 'Body hash mismatch'
assert struct.unpack_from('<I', trailer, 20)[0] == 0xE5331F02, 'Wrong application target'
assert trailer[24:].rstrip(b'\0') == b'ikoka_stick_nrf_33dbm', 'Wrong board'
assert trailer[8:16].hex().upper() == expected, 'This is not the running firmware'
Path('inputs/ikoka-stick-33dbm-repeater-running.bin').write_bytes(image)
print('Exact running application confirmed:', expected)
PY
```
Stop if any check fails. Then build and check the **in-place** nRF52 delta:
```bash
motatool build \
--base inputs/ikoka-stick-33dbm-repeater-running.bin \
--fw inputs/ikoka-stick-33dbm-repeater.bin \
--patch-type in-place \
--out inputs/ikoka-stick-33dbm-repeater-delta.mota
motatool verify inputs/ikoka-stick-33dbm-repeater-delta.mota
motatool inspect inputs/ikoka-stick-33dbm-repeater-delta.mota
ls -lh serve/ikoka-stick-33dbm-repeater.mota inputs/ikoka-stick-33dbm-repeater-delta.mota
```
Check `codec_id` is `2 (detools-in-place)`, `base_hash` matches the live Ikoka
Stick, target is `E5331F02`, and the new version/hardware match the full
package. Let motatool derive the safe workspace from the firmware; do not
override its memory or target checks. Compare package sizes and keep the full
update if the delta is not smaller. `verify` checks the delta container's
integrity; it does not prove that you selected the running base image.
To serve the delta, replace the full application package in the serving
folder **before starting motatool**:
```bash
cp inputs/ikoka-stick-33dbm-repeater-delta.mota serve/ikoka-stick-33dbm-repeater.mota
```
Continue with steps 4–9. In step 8, choose the **application, delta** row and
use its own eight-character manifest ID with `ota pull <APP_MID8> flash`,
then `ota install` when ready. The target's `base_hash` must still match
immediately before that pull. Updating only the bootloader in step 7 leaves
the running application unchanged, so it does not invalidate the delta base.
After installation, check the same new firmware version and expected
post-install body hash printed earlier in step 3.
</details>
## 4. Identify the Station G2's USB port
On **Raspberry Pi — terminal**:
```bash
ls -l /dev/serial/by-id/
```
Select the Station G2's actual entry. Set `G2_PORT` to that complete path;
`/dev/ttyUSB0` below is only an example:
```bash
G2_PORT=/dev/ttyUSB0
meshcli -r -s "$G2_PORT" -b 115200 "board"
meshcli -r -s "$G2_PORT" -b 115200 "ver"
meshcli -r -s "$G2_PORT" -b 115200 "ota status"
meshcli -r -s "$G2_PORT" -b 115200 "get radio"
meshcli -r -s "$G2_PORT" -b 115200 "get radio.rxps.config"
meshcli -r -s "$G2_PORT" -b 115200 "ota config"
```
Confirm that this is the Station G2 and that its firmware includes LoRa OTA.
Save its radio, RXPS, and OTA settings. If access is denied, add your Pi login
to `dialout` with `sudo usermod -aG dialout "$(id -un)"`, then log out and in.
Close other programs using that USB port.
## 5. Open the OTA windows and trust the bootloader signer
The commands below assume the Station G2, Ikoka Stick, and Companion already
use **910.525 MHz, BW 62.5 kHz, SF7, CR5**. If yours differ, use their current
shared tuple in every TempRadio command below. Preserve an explicit preamble
if one is configured by adding it after the duration. This keeps admin access
on the existing channel; TempRadio is still required to enable LoRa OTA.
On **Ikoka Stick — Repeater Admin**:
```text
ota key add 272564CC588D3D122285A15E6E2566D2ABE7177BB7EA1D1E41B23B29F0F85D2D
ota key list
ota config autofetch off
ota config autoinstall off
ota config hops 0
set radio.rxps off
get radio.rxps
tempradio 910.525,62.5,7,5,240
get tempradio
```
On **Raspberry Pi — Station G2 USB CLI**:
```bash
meshcli -r -s "$G2_PORT" -b 115200 "ota config autofetch off"
meshcli -r -s "$G2_PORT" -b 115200 "ota config autoinstall off"
meshcli -r -s "$G2_PORT" -b 115200 "ota config hops 0"
meshcli -r -s "$G2_PORT" -b 115200 "set radio.rxps off"
meshcli -r -s "$G2_PORT" -b 115200 "get radio.rxps"
meshcli -r -s "$G2_PORT" -b 115200 "tempradio 910.525,62.5,7,5,240"
meshcli -r -s "$G2_PORT" -b 115200 "get tempradio"
```
Both receivers must report RXPS off and an active temporary tuple. Wait if
`get tempradio` says `pending`; do not start serving while it says `off`.
The `hops 0` setting is for this direct link. These configuration changes are
saved; the TempRadio window is temporary.
## 6. Start the Station G2 seeder
On **Raspberry Pi — terminal**, leave this running in the foreground:
```bash
motatool serve --dir ./serve --serial "$G2_PORT" --baud 115200 -v
```
It attaches the folder and the Station G2 advertises the two packages. Wait
for a successful folder attachment/catalog count. While motatool owns USB,
send later commands through **Ikoka Stick — Repeater Admin**. Keep the Pi and
both repeaters powered through each transfer and installation.
## 7. Update the Ikoka Stick bootloader
On **Ikoka Stick — Repeater Admin**:
```text
ota ls
```
Discovery is asynchronous. Wait a few seconds and repeat it. Select the
**bootloader** row for XIAO target `28860044`, version 2.4.12. Copy its
eight-character manifest ID into `BOOT_MID8` below; placeholders must be
replaced with the actual values, without angle brackets:
```text
ota pull <BOOT_MID8> flash
ota status
```
Check progress until it reports **ready to install**, then run:
```text
ota bootloader status
```
Confirm `staged:ready`. Copy that reply's exact `mid` and 16-character `hash`:
```text
ota bootloader install <BOOT_MID8> <BOOT_HASH16>
```
Wait for the reboot, reconnect in the app, and check:
```text
get bootloader.ver
ota bootloader status
ota status
ver
```
Expect **OTAFIX 2.4.12**, the same `XIAO_DFU` identity, ABI 3 / capabilities
`0E`, and `blup:C8`. The application version should still be the old version.
## 8. Update the Ikoka Stick firmware
The bootloader reboot cleared the Ikoka Stick's temporary window. On
**Ikoka Stick — Repeater Admin**, reopen it before downloading firmware:
```text
get radio.rxps
tempradio 910.525,62.5,7,5,240
get tempradio
```
Wait until `get tempradio` shows the active tuple, repeating the query if it
says `pending`. Then run:
```text
ota ls
```
Keep the Station G2 seeder running. Select the **application, full** row with
target `E5331F02` and the version inspected in step 3. Use its stable manifest
ID, not its list position or the bootloader ID:
```text
ota pull <APP_MID8> flash
ota status
```
Repeat `ota status` until **ready to install**, then run:
```text
ota install
```
After the application reboots, reconnect and check:
```text
ver
get bootloader.ver
ota self
ota status
```
Confirm the selected firmware version, bootloader **2.4.12**, working QSPI
apply support, and the `base_hash` recorded from the application package.
The Ikoka Stick has returned to its saved main radio settings.
## 9. Stop serving and restore settings
On **Raspberry Pi — terminal**, press **Ctrl+C** and wait for motatool to
detach the folder and return to the shell. Then run:
```bash
meshcli -r -s "$G2_PORT" -b 115200 "ota folder"
meshcli -r -s "$G2_PORT" -b 115200 "normalradio"
meshcli -r -s "$G2_PORT" -b 115200 "get tempradio"
```
Confirm the host folder detached. Restore each board's original RXPS, hop
limit, autofetch, and autoinstall
settings from steps 1 and 4. For the example starting values, on **Ikoka
Stick — Repeater Admin**:
```text
set radio.rxps level 8 preamble 16
ota config hops 3
ota config autofetch off
ota config autoinstall off
get radio.rxps.config
ota config
get tempradio
```
Use the Station G2's own recorded values through `meshcli -r` as in step 5.
An original RXPS value of `off` stays off; for an automatic preamble (`0`),
restore with `set radio.rxps level N`; fixed manual timings use
`set radio.rxps RX_US SLEEP_US`. Retain the official signing key for future
bootloader updates. Both boards should now report `get tempradio` as `off`.
## If a transfer stops
- **No update rows:** check the seeder attachment, matching radio settings,
direct reachability, and active TempRadio windows on both boards.
- **Four-hour window expired:** stop motatool before reopening the G2 USB
CLI, renew both windows, restart the same seeder, and pull the same MID.
Keep the same package; current firmware can resume committed blocks.
- **Signature/key error:** verify the signed package against its trusted
publisher key. Do not substitute an arbitrary key from an update row.
- **Download failed or wrong selection:** inspect `ota status`; use
`ota cancel` to discard that download before selecting another package.
- **No completed install:** keep the running firmware until the destination
reports a verified, ready package. See the
[bootloader guide](ota_nrf52_bootloader_update.md) or
[firmware guide](ota_user_guide.md) for the reported error.
+4
View File
@@ -65,6 +65,7 @@ nav:
- ota_user_guide.md
- ota_easy.md
- lora_ota_automation.md
- ota_station_g2_to_ikoka_stick.md
- ota_nrf52_bootloader_update.md
- ota_nrf52_qspi.md
- ota_meshtower_v2_sdcard.md
@@ -111,6 +112,9 @@ nav:
- Older releases: old-releases/index.md
- Test Results: test-results/index.md
markdown_extensions:
- md_in_html
theme:
name: material
logo: _assets/meshcore.svg