diff --git a/docs/lora_ota_automation.md b/docs/lora_ota_automation.md index 647a27be3..9cd3abe4b 100644 --- a/docs/lora_ota_automation.md +++ b/docs/lora_ota_automation.md @@ -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 diff --git a/docs/ota_easy.md b/docs/ota_easy.md index fe5e120b8..0251224ae 100644 --- a/docs/ota_easy.md +++ b/docs/ota_easy.md @@ -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 | diff --git a/docs/ota_station_g2_to_ikoka_stick.md b/docs/ota_station_g2_to_ikoka_stick.md new file mode 100644 index 000000000..04df9d3be --- /dev/null +++ b/docs/ota_station_g2_to_ikoka_stick.md @@ -0,0 +1,459 @@ + + + + +# 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(' +Optional: differential firmware upgrade (smaller download) + +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(' 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. + + + +## 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 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 +``` + +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 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. diff --git a/mkdocs.yml b/mkdocs.yml index 2c3b9014c..bd9545c08 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -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