Compare commits
317
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0030aec8dd | ||
|
|
675e40575d | ||
|
|
f88c6c64e5 | ||
|
|
eaea32fcc6 | ||
|
|
0e477426f5 | ||
|
|
c1683e333f | ||
|
|
6693dba8cf | ||
|
|
a2f51253d8 | ||
|
|
af08c02a00 | ||
|
|
378925638b | ||
|
|
3963048226 | ||
|
|
0be4c7488c | ||
|
|
8c698dc2d1 | ||
|
|
9ce2a076eb | ||
|
|
a31b19ed8a | ||
|
|
3e0ce84486 | ||
|
|
015a3e8e05 | ||
|
|
6b3f7df4d0 | ||
|
|
f62cc7b527 | ||
|
|
a52059e603 | ||
|
|
3d3d5827ba | ||
|
|
331ba22a90 | ||
|
|
cbafd44047 | ||
|
|
926238123d | ||
|
|
f304b8ff77 | ||
|
|
41f48344bd | ||
|
|
32a994e838 | ||
|
|
3e8c637866 | ||
|
|
33204b768a | ||
|
|
a43f48f63c | ||
|
|
f7b8777e9a | ||
|
|
584fc9a4d7 | ||
|
|
d11bf3615d | ||
|
|
937f8610c7 | ||
|
|
5a031f7674 | ||
|
|
ef80f29337 | ||
|
|
7acc637468 | ||
|
|
9a176964dd | ||
|
|
4606f3da60 | ||
|
|
0eb1d03a16 | ||
|
|
5cb8367d12 | ||
|
|
04f98ea44c | ||
|
|
894d033503 | ||
|
|
d031609f26 | ||
|
|
a2a63ea5cc | ||
|
|
6e4b501dd6 | ||
|
|
7bdd9adec1 | ||
|
|
6b7812bfc2 | ||
|
|
ddf625e9d0 | ||
|
|
f79a316da8 | ||
|
|
466eb008c7 | ||
|
|
fdad79eaf7 | ||
|
|
d74a0f8b44 | ||
|
|
89f26fe1c9 | ||
|
|
b0c02b1e47 | ||
|
|
d83fa7cf8e | ||
|
|
a14bbdc58f | ||
|
|
7a463937ca | ||
|
|
df88a6ef95 | ||
|
|
57036b1d66 | ||
|
|
396474bf9c | ||
|
|
c07d16e398 | ||
|
|
4bbd613417 | ||
|
|
d2b6244ae0 | ||
|
|
88c8510800 | ||
|
|
8c4e09275e | ||
|
|
bd4cbf281f | ||
|
|
4c5f58f4b5 | ||
|
|
3ab690b508 | ||
|
|
af56405d5e | ||
|
|
80dba6298d | ||
|
|
69869ef398 | ||
|
|
1e421ff4fc | ||
|
|
745aeb1758 | ||
|
|
aebab7d46b | ||
|
|
87d51d3d64 | ||
|
|
c9ca39b5dd | ||
|
|
c5fe8a0edd | ||
|
|
70bf4bdfeb | ||
|
|
be2ab346b0 | ||
|
|
3ede9b2155 | ||
|
|
5d5941fc4d | ||
|
|
52c70994d8 | ||
|
|
0724e2efa3 | ||
|
|
7d0984cc90 | ||
|
|
771e86741c | ||
|
|
3bb7c7bdd8 | ||
|
|
825eb592f7 | ||
|
|
1ecceeb686 | ||
|
|
6978b354cc | ||
|
|
20a75af1fa | ||
|
|
9c54621b63 | ||
|
|
99ec3a9bbf | ||
|
|
f8e04f090b | ||
|
|
219e936ef8 | ||
|
|
97f16dedd0 | ||
|
|
5a7c07a6ce | ||
|
|
d6ec787ba7 | ||
|
|
77f0842830 | ||
|
|
1adf0c6d01 | ||
|
|
79167af8e6 | ||
|
|
a5f4158b36 | ||
|
|
ad2bc929d6 | ||
|
|
f5cda7337c | ||
|
|
c97dbd791a | ||
|
|
9202fc2a96 | ||
|
|
08bf174ea3 | ||
|
|
19379eb9b8 | ||
|
|
db42a998f8 | ||
|
|
5990b4c5fd | ||
|
|
7581fa3abd | ||
|
|
df5e591319 | ||
|
|
2ad479ce0e | ||
|
|
2ee62b9814 | ||
|
|
24a223314c | ||
|
|
ba6e48b5ce | ||
|
|
3baa169fc6 | ||
|
|
78e134b2ba | ||
|
|
8d31b3932d | ||
|
|
3a7d273d49 | ||
|
|
ebf2f0823e | ||
|
|
c88885a315 | ||
|
|
95913c5301 | ||
|
|
d2983762bc | ||
|
|
e971beabc5 | ||
|
|
757f15fcb6 | ||
|
|
8fe83ebc29 | ||
|
|
77f8416bb6 | ||
|
|
8842b86a46 | ||
|
|
1d0ae06800 | ||
|
|
933bdf7cbe | ||
|
|
1db09f41ac | ||
|
|
46e6bb7b71 | ||
|
|
8af0511f54 | ||
|
|
51d74a200e | ||
|
|
28023c7302 | ||
|
|
bec874f917 | ||
|
|
5f10e7d601 | ||
|
|
7ef67c49f5 | ||
|
|
30fe0d617b | ||
|
|
7d3ee7200f | ||
|
|
853bf2e1d9 | ||
|
|
b4a77c6996 | ||
|
|
5f20fee396 | ||
|
|
e3cb2c2bba | ||
|
|
631f2ad973 | ||
|
|
0b9a80d7ed | ||
|
|
49f698b452 | ||
|
|
b259b10553 | ||
|
|
a8b39daafb | ||
|
|
c8c5f87ab9 | ||
|
|
163364e528 | ||
|
|
a4e3a530cb | ||
|
|
68a27a2ebb | ||
|
|
af759be715 | ||
|
|
7821329851 | ||
|
|
e95cf6d618 | ||
|
|
8fbf948d68 | ||
|
|
64a09cbada | ||
|
|
0529a461f2 | ||
|
|
a294f01a5d | ||
|
|
bd7ce50e7b | ||
|
|
ec83da079d | ||
|
|
c38ba5f8d0 | ||
|
|
2c806f19f9 | ||
|
|
efed592fdb | ||
|
|
64885dab28 | ||
|
|
74a2bb0723 | ||
|
|
19d02c0dfa | ||
|
|
982f96ca96 | ||
|
|
cc4bcb9cad | ||
|
|
835c7a8e6e | ||
|
|
4939de4c61 | ||
|
|
672e0ae777 | ||
|
|
a3d8045199 | ||
|
|
a24dade1c0 | ||
|
|
274997e9ae | ||
|
|
841571a7d8 | ||
|
|
a14a0ad81d | ||
|
|
7a6c7c2b40 | ||
|
|
e1b439ab82 | ||
|
|
c747c15ffc | ||
|
|
b9ba35f9d7 | ||
|
|
0cc561b0d8 | ||
|
|
d46fb879f1 | ||
|
|
e4420519a4 | ||
|
|
9268c57f8c | ||
|
|
ec8f82e918 | ||
|
|
6516c719e1 | ||
|
|
9c43efb0bc | ||
|
|
8bc93822ef | ||
|
|
15bdb7fc4d | ||
|
|
55e4f47338 | ||
|
|
62d76b7bab | ||
|
|
85ff9e321e | ||
|
|
b1119e30ea | ||
|
|
fd142084d3 | ||
|
|
c1a80e95c7 | ||
|
|
0a3187ac30 | ||
|
|
7e1750ceac | ||
|
|
9414de8593 | ||
|
|
4f114e914b | ||
|
|
a71b99fd62 | ||
|
|
b9b7a7b7e1 | ||
|
|
1e03eb3056 | ||
|
|
67408ffd13 | ||
|
|
638e8cf3b7 | ||
|
|
c316e9fb3c | ||
|
|
b128ac9a70 | ||
|
|
0d5a6e6fc8 | ||
|
|
057f1824a0 | ||
|
|
8586ef5f51 | ||
|
|
63f4642252 | ||
|
|
3e3150c83f | ||
|
|
72eb37c229 | ||
|
|
30de48d27e | ||
|
|
c3d3c4e294 | ||
|
|
46df653dbc | ||
|
|
38563a9828 | ||
|
|
54e5b0b996 | ||
|
|
59e761262d | ||
|
|
2817e180ec | ||
|
|
9693c87002 | ||
|
|
161600b4b7 | ||
|
|
399464ea37 | ||
|
|
ea938e8e84 | ||
|
|
f19733e478 | ||
|
|
d29cba046b | ||
|
|
e368b52f7d | ||
|
|
eab6676316 | ||
|
|
7945d5bf5a | ||
|
|
326f8cdf78 | ||
|
|
eee4fa9e98 | ||
|
|
72898f7dd5 | ||
|
|
4fca38363d | ||
|
|
a02554dd69 | ||
|
|
f0828e025a | ||
|
|
d4f88680e7 | ||
|
|
4837265648 | ||
|
|
ecdaacdd58 | ||
|
|
d9088f9e86 | ||
|
|
1d05ef9e86 | ||
|
|
81676be25c | ||
|
|
071ea533b0 | ||
|
|
581339efe7 | ||
|
|
06fc2d58d1 | ||
|
|
18f7edcf82 | ||
|
|
a928498c46 | ||
|
|
5ee6059564 | ||
|
|
eeb25a8b5f | ||
|
|
45d8f15098 | ||
|
|
8ec31dda71 | ||
|
|
7803d4fb07 | ||
|
|
a0856bed72 | ||
|
|
baf01a5c16 | ||
|
|
ed8af1b7ca | ||
|
|
65512661c3 | ||
|
|
9b82c74275 | ||
|
|
f652e7d185 | ||
|
|
a1943a8eac | ||
|
|
3add4695cc | ||
|
|
ee88192acb | ||
|
|
65d80bc908 | ||
|
|
9c65e198f1 | ||
|
|
4afb3f7e3f | ||
|
|
2633b4267c | ||
|
|
763ba835c1 | ||
|
|
ea541e182a | ||
|
|
942a19eb63 | ||
|
|
3910489414 | ||
|
|
f04d163bea | ||
|
|
b4c8c0ab23 | ||
|
|
fc182b7769 | ||
|
|
bd55369aa9 | ||
|
|
4086222d85 | ||
|
|
c917158167 | ||
|
|
f0199b3111 | ||
|
|
43f474a806 | ||
|
|
42d6bfd560 | ||
|
|
d4d318814a | ||
|
|
488d51979c | ||
|
|
a0105731f5 | ||
|
|
7324bf06ef | ||
|
|
d084f5b837 | ||
|
|
946354760b | ||
|
|
eec86a994c | ||
|
|
9b17596049 | ||
|
|
dbff241118 | ||
|
|
88a1cbfa04 | ||
|
|
890e593d0f | ||
|
|
fad0ffb7f1 | ||
|
|
0e1ba7102d | ||
|
|
35e10a9c75 | ||
|
|
ea0213754f | ||
|
|
f35216799a | ||
|
|
1af2760c88 | ||
|
|
a706d1d943 | ||
|
|
914143cb7e | ||
|
|
ce266c00e5 | ||
|
|
2ce37ec8cc | ||
|
|
1e23d7c1e4 | ||
|
|
7d0a602b20 | ||
|
|
f37445f8a7 | ||
|
|
6390763c71 | ||
|
|
73098a44b6 | ||
|
|
7004b81c0c | ||
|
|
383339eccc | ||
|
|
f7ed2f176d | ||
|
|
43b55de26f | ||
|
|
a91009e3dd | ||
|
|
91d9db9c7f | ||
|
|
33f1b1096d | ||
|
|
6baffa753c | ||
|
|
61fcc49c8a | ||
|
|
fe72d0321a | ||
|
|
27feb73d4e | ||
|
|
e525200ce1 |
No files matched your search
@@ -14,3 +14,6 @@ tools/hil/preamble_stationary*.json -text whitespace=cr-at-eol
|
||||
tools/hil/sf6_125*.json -text whitespace=cr-at-eol
|
||||
tools/hil/sf8_125*.json -text whitespace=cr-at-eol
|
||||
tools/hil/sf5_250*.json -text whitespace=cr-at-eol
|
||||
|
||||
# Preserve the pinned upstream FS/newlib fixtures byte-for-byte.
|
||||
test/fixtures/esp32_file_buffer/* -text whitespace=-blank-at-eol,-blank-at-eof
|
||||
@@ -67,6 +67,10 @@ env:
|
||||
# The channel itself. Firmware fetches <OTA_MANIFEST_BASE>/<OTA_VARIANT>.json,
|
||||
# so this URL is what keeps beta nodes on beta.
|
||||
OTA_MANIFEST_BASE_URL: https://observer.gessaman.com/beta/v
|
||||
# Both channel bases are baked into every build so `ota branch prod|beta` can
|
||||
# move a node between channels. Identical in both observer workflows.
|
||||
OTA_MANIFEST_BASE_STABLE_URL: https://observer.gessaman.com/v
|
||||
OTA_MANIFEST_BASE_DEV_URL: https://observer.gessaman.com/beta/v
|
||||
# Marks the embedded version, e.g. v1.16.0.3-observer-beta-dev-abc1234, so `ver`
|
||||
# (and the MQTT firmware_version / SNMP) identify BOTH the channel and its
|
||||
# provenance: this channel is built from the upstream-dev-merged line, so "dev"
|
||||
@@ -168,20 +172,9 @@ jobs:
|
||||
run: /usr/bin/env bash build.sh build-firmware ${{ matrix.shard.envs }}
|
||||
|
||||
- name: Verify beta channel is baked in
|
||||
shell: bash
|
||||
run: |
|
||||
# Fail fast rather than publish firmware that would OTA itself onto the
|
||||
# production channel. Checks one built binary actually carries the beta
|
||||
# manifest URL and does NOT carry the production one.
|
||||
BIN=$(find .pio/build -name firmware.elf | head -1)
|
||||
if [ -z "$BIN" ]; then echo "no ELF found to verify" >&2; exit 1; fi
|
||||
if ! strings "$BIN" | grep -qF "$OTA_MANIFEST_BASE_URL"; then
|
||||
echo "ERROR: beta manifest base missing from $BIN" >&2; exit 1
|
||||
fi
|
||||
if strings "$BIN" | grep -qE 'https://observer\.gessaman\.com/v"?$'; then
|
||||
echo "ERROR: production manifest base present in a beta build" >&2; exit 1
|
||||
fi
|
||||
echo "OK: $BIN carries $OTA_MANIFEST_BASE_URL"
|
||||
# Fail fast rather than publish firmware that would OTA itself onto the
|
||||
# production channel: checks every .bin the shard publishes.
|
||||
run: python3 scripts/verify_ota_channel.py --expect beta --required-cap keymind1
|
||||
|
||||
- name: Upload Shard Artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
@@ -258,7 +251,7 @@ jobs:
|
||||
# this channel's dropdown changelog. Non-fatal -- stale notes beat a
|
||||
# red build whose binaries are already live.
|
||||
{
|
||||
printf '%s' '<p><b>⚠ DEV/BETA CHANNEL:</b> pre-release firmware. A node flashed from this channel keeps receiving OTA updates from this channel until re-flashed by cable.</p>'
|
||||
printf '%s' '<p><b>⚠ DEV/BETA CHANNEL:</b> pre-release firmware. A node flashed from this channel keeps receiving OTA updates from this channel unless changed with ota branch prod and ota update. The target must preserve its saved-settings format and required transports.</p>'
|
||||
cat firmware-notes.html
|
||||
} > /tmp/beta-notes.html
|
||||
gh_retry gh release edit "$RELEASE_TAG" --notes-file /tmp/beta-notes.html \
|
||||
|
||||
@@ -54,6 +54,10 @@ env:
|
||||
# same value the build was handed instead of a second hardcoded copy. Must match
|
||||
# build.sh's OTA_MANIFEST_BASE_URL default and the Pages path serving flasher/v.
|
||||
OTA_MANIFEST_BASE_URL: https://observer.gessaman.com/v
|
||||
# Both channel bases are baked into every build so `ota branch prod|beta` can
|
||||
# move a node between channels. Identical in both observer workflows.
|
||||
OTA_MANIFEST_BASE_STABLE_URL: https://observer.gessaman.com/v
|
||||
OTA_MANIFEST_BASE_DEV_URL: https://observer.gessaman.com/beta/v
|
||||
|
||||
jobs:
|
||||
|
||||
@@ -144,21 +148,9 @@ jobs:
|
||||
run: /usr/bin/env bash build.sh build-firmware ${{ matrix.shard.envs }}
|
||||
|
||||
- name: Verify production channel is baked in
|
||||
shell: bash
|
||||
run: |
|
||||
# Mirror of the beta workflow's guard, which production lacked. Fail fast
|
||||
# rather than publish firmware that would OTA itself onto the wrong
|
||||
# channel: the manifest base is a compile-time -D, so a build that lost
|
||||
# it (or picked up beta's) is invisible until a node tries `ota check`.
|
||||
BIN=$(find .pio/build -name firmware.elf | head -1)
|
||||
if [ -z "$BIN" ]; then echo "no ELF found to verify" >&2; exit 1; fi
|
||||
if ! strings "$BIN" | grep -qF "$OTA_MANIFEST_BASE_URL"; then
|
||||
echo "ERROR: production manifest base missing from $BIN" >&2; exit 1
|
||||
fi
|
||||
if strings "$BIN" | grep -qF 'https://observer.gessaman.com/beta/v'; then
|
||||
echo "ERROR: beta manifest base present in a production build" >&2; exit 1
|
||||
fi
|
||||
echo "OK: $BIN carries $OTA_MANIFEST_BASE_URL"
|
||||
# Fail fast rather than publish firmware that would OTA itself onto the
|
||||
# wrong channel: checks every .bin the shard publishes.
|
||||
run: python3 scripts/verify_ota_channel.py --expect prod --required-cap keymind1
|
||||
|
||||
- name: Upload Shard Artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
|
||||
@@ -160,10 +160,8 @@ jobs:
|
||||
- name: Build Shard ${{ matrix.shard.idx }}
|
||||
env:
|
||||
FIRMWARE_BUILD_NUMBER: ${{ needs.enumerate.outputs.build_number }}
|
||||
# Channel isolation for device pull-OTA: appended after the per-env
|
||||
# ini flags, so this definition of OTA_MANIFEST_BASE wins. build.sh
|
||||
# appends its own version/OTA_VARIANT flags to this variable.
|
||||
PLATFORMIO_BUILD_FLAGS: -D OTA_MANIFEST_BASE='"${{ env.OTA_MANIFEST_BASE_URL }}"'
|
||||
# build.sh uses the workflow's OTA_MANIFEST_BASE_URL for native OTA
|
||||
# and supplies the named production and beta channel bases.
|
||||
run: /usr/bin/env bash build.sh build-firmware ${{ matrix.shard.envs }}
|
||||
|
||||
- name: Upload Shard Artifact
|
||||
|
||||
@@ -8,6 +8,8 @@ on:
|
||||
push:
|
||||
tags:
|
||||
- 'repeater-*'
|
||||
# Cascade pages contain already-qualified local matrix artifacts.
|
||||
- '!repeater-room-*'
|
||||
|
||||
jobs:
|
||||
build-repeater-firmwares:
|
||||
|
||||
@@ -32,8 +32,11 @@ jobs:
|
||||
MKDOCS_EDIT_URI: edit/${{ github.ref_name }}/docs/
|
||||
run: |
|
||||
pip install mkdocs-material
|
||||
node test/test_firmware_picker.js
|
||||
node test/test_partition_catalog_viewer.js
|
||||
python scripts/sync_bootloader_manifest.py
|
||||
mkdocs build
|
||||
cmp firmware/esp32_partition_catalog.json site/_data/esp32_partition_catalog.json
|
||||
|
||||
- name: Deploy to GitHub Pages
|
||||
uses: peaceiris/actions-gh-pages@v4.1.0
|
||||
|
||||
@@ -10,6 +10,43 @@ on:
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
nrf52-companion-smoke:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Setup Build Environment
|
||||
uses: ./.github/actions/setup-build-environment
|
||||
- name: Qualify RAK4631 Full Companion with linked Meshtext support
|
||||
run: >-
|
||||
bash build.sh build-firmware RAK_4631_companion_radio_full
|
||||
--firmware-version v1.17.1.10-halo-keymind-cascade-dev
|
||||
--radio-preset usa-cascade-fixed --profile cascade --require-ota
|
||||
|
||||
- name: Verify linked Companion crypto, retained Reader and ARM stack use
|
||||
run: |
|
||||
python3 -m pip install unicorn==2.1.4
|
||||
python3 -B scripts/check_nrf52_companion_crypto.py .pio/build/RAK_4631_companion_radio_full/firmware.elf
|
||||
|
||||
nrf52-sensor-smoke:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Setup Build Environment
|
||||
uses: ./.github/actions/setup-build-environment
|
||||
- name: Verify nRF52 HEX packaging with installed build dependencies
|
||||
run: python3 -B test/test_nrf52_endf_hex.py -v
|
||||
- name: Compile GPS Sensor tracker on nRF52 boards
|
||||
run: pio run -e t1000e_sensor -e RAK_WisMesh_Tag_sensor
|
||||
|
||||
esp32-sensor-smoke:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Setup Build Environment
|
||||
uses: ./.github/actions/setup-build-environment
|
||||
- name: Compile GPS-capable Sensor tracker on ESP32-S3
|
||||
run: pio run -e heltec_v4_sensor
|
||||
|
||||
management-reports:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
@@ -25,7 +62,9 @@ jobs:
|
||||
- name: Verify protocol, encryption, scheduling and MQTT decoding
|
||||
run: |
|
||||
python3 -B test/test_management_report.py -v
|
||||
python3 -B test/test_management_cli_wrapper.py -v
|
||||
node test/test_management_decoder.js
|
||||
node scripts/test_telemetry_decoder.js
|
||||
|
||||
stm32-companion-smoke:
|
||||
runs-on: ubuntu-latest
|
||||
@@ -36,9 +75,20 @@ jobs:
|
||||
- name: Setup Build Environment
|
||||
uses: ./.github/actions/setup-build-environment
|
||||
|
||||
- name: Verify compact STM32 signature arithmetic
|
||||
run: |
|
||||
python3 -B test/test_ed25519_compact_scalar.py -v
|
||||
python3 -B test/test_ed25519_compact.py -v
|
||||
|
||||
- name: Compile ordinary STM32 USB Companion with pinned core and RadioLib
|
||||
run: pio run -e RAK_3x72_companion_radio_usb
|
||||
|
||||
- name: Qualify Wio-E5 Mini Companion at release version length
|
||||
run: >-
|
||||
bash build.sh build-firmware wio-e5-mini_companion_radio_usb
|
||||
--firmware-version v1.17.1.9-halo-keymind-cascade-dev
|
||||
--radio-preset usa-cascade-fixed --profile cascade --require-ota
|
||||
|
||||
bootloader-version:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
@@ -82,32 +132,164 @@ jobs:
|
||||
- name: Setup Build Environment
|
||||
uses: ./.github/actions/setup-build-environment
|
||||
|
||||
- name: Install signed-package verification dependency
|
||||
run: |
|
||||
python3 -m pip install cryptography
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y libssl-dev
|
||||
mkdir -p .pio/libdeps/room-mail-test
|
||||
pio pkg install --global --library 'rweather/Crypto@0.4.0' --storage-dir .pio/libdeps/room-mail-test
|
||||
|
||||
- name: Verify ArduinoJson pin
|
||||
run: |
|
||||
python3 -B scripts/check_arduinojson_pin.py --self-test
|
||||
python3 -B scripts/check_arduinojson_pin.py
|
||||
|
||||
- name: Verify build-size diagnostics and nRF52 optimizer recipes
|
||||
run: |
|
||||
bash test/test_build_size_detection.sh
|
||||
python3 -B test/test_nrf52_size_profile.py -v
|
||||
python3 -B test/test_rak_combined_build_config.py -v
|
||||
|
||||
- name: Run Unit Tests
|
||||
run: pio test -e native -e native_kiss_modem -vv
|
||||
run: pio test -e native -e native_kiss_modem -e native_ota_channel -vv
|
||||
|
||||
- name: Verify parser, delivery, storage, permissions, clock and radio stability
|
||||
run: |
|
||||
python3 -B test/test_protocol_parser_bounds.py -v
|
||||
python3 -B test/test_cli_error_bounds.py -v
|
||||
python3 -B test/test_ota_security.py -v
|
||||
python3 -B test/test_companion_offline_delivery.py -v
|
||||
python3 -B test/test_room_receive_admission.py -v
|
||||
python3 -B test/test_companion_lfs_traversal.py -v
|
||||
python3 -B test/test_room_post_permissions.py -v
|
||||
python3 -B test/test_room_login_authorization.py -v
|
||||
python3 -B test/test_room_login_integration.py -v
|
||||
python3 -B test/test_room_client_paths.py -v
|
||||
python3 -B test/test_client_acl_infrastructure.py -v
|
||||
python3 -B test/test_stored_client_reply_routes.py -v
|
||||
python3 -B test/test_room_catchup.py -v
|
||||
python3 -B test/test_room_catchup_transport.py -v
|
||||
python3 -B test/test_room_topic_store.py -v
|
||||
python3 -B test/test_room_topic_delivery.py -v
|
||||
python3 -B test/test_room_acl_persistence.py -v
|
||||
python3 -B test/test_room_access_policy.py -v
|
||||
python3 -B test/test_room_history_store.py -v
|
||||
python3 -B test/test_room_history_integration.py -v
|
||||
python3 -B test/test_room_board_store.py -v
|
||||
python3 -B test/test_room_board_protocol.py -v
|
||||
python3 -B test/test_room_mail_store.py -v
|
||||
python3 -B test/test_room_mail_protocol.py -v
|
||||
python3 -B test/test_room_web_mailbox.py -v
|
||||
python3 -B test/test_room_web_http.py -v
|
||||
python3 -B test/test_room_web_service.py -v
|
||||
python3 -B test/test_contact_clock_bootstrap.py -v
|
||||
python3 -B test/test_rtc_discovery.py -v
|
||||
python3 -B test/test_radio_interrupt_recovery.py -v
|
||||
python3 -B test/test_bridge_recovery.py -v
|
||||
|
||||
- name: Verify signed private fleet management
|
||||
run: |
|
||||
python3 -B test/test_fleet_command_crypto.py -v
|
||||
python3 -B test/test_companion_fleet_control.py -v
|
||||
python3 -B test/test_fleet_channel_runtime.py -v
|
||||
python3 -B test/test_fleet_role_wiring.py -v
|
||||
python3 -B test/test_fleet_location.py -v
|
||||
python3 -B test/test_fleet_radio_schedules.py -v
|
||||
python3 -B test/test_fleet_clock_control.py -v
|
||||
|
||||
- name: Verify OTA channel persistence and published image tags
|
||||
run: |
|
||||
python3 -B test/test_ota_channel_persistence.py -v
|
||||
python3 -B test/test_observer_ota_channel.py -v
|
||||
python3 -B test/test_observer_build_channels.py -v
|
||||
python3 -B scripts/verify_ota_channel.py --self-test
|
||||
|
||||
- name: Verify build output cleanup safety
|
||||
run: python3 -B test/test_build_output_safety.py -v
|
||||
|
||||
- name: Verify serial mOTA response deadlines and RX fairness
|
||||
run: |
|
||||
python3 -B test/test_mota_serial_deadline.py -v
|
||||
python3 -B test/test_mota_folder_deadline.py -v
|
||||
|
||||
- name: Verify filter playground modes and built-in defaults
|
||||
run: node scripts/test_filter_tool.js
|
||||
|
||||
- name: Verify bridge filter wiring and shorthand permissions
|
||||
run: python3 -B test/test_transport_filter_wiring.py -v
|
||||
run: |
|
||||
python3 -B test/test_transport_filter_wiring.py -v
|
||||
python3 -B test/test_rs232_runtime.py -v
|
||||
python3 -B test/test_rs232_mqtt_runtime.py -v
|
||||
python3 -B test/test_rs232_driver_lifecycle.py -v
|
||||
python3 -B test/test_rs232_heartbeat.py -v
|
||||
python3 -B test/test_rs232_heartbeat_owner.py -v
|
||||
python3 -B test/test_rs232_power_settings.py -v
|
||||
|
||||
- name: Verify saved repeat-off trace exception and relay guards
|
||||
run: |
|
||||
python3 -B test/test_repeater_trace_repeat.py -v
|
||||
python3 -B test/test_repeater_trace_forwarding.py -v
|
||||
|
||||
- name: Verify telemetry schedule input and persistence rollback
|
||||
run: python3 -B test/test_telemetry_tx_command.py -v
|
||||
|
||||
- name: Verify transactional telemetry preferences and legacy route recovery
|
||||
run: python3 -B test/test_telemetry_tx_persistence.py -v
|
||||
|
||||
- name: Verify MQTT and WiFi ownership, recovery and power policy
|
||||
run: |
|
||||
python3 -B test/test_mqtt_transport_results.py -v
|
||||
python3 -B test/test_mqtt_tls_security.py -v
|
||||
python3 -B test/test_mqtt_network_timeout.py -v
|
||||
python3 -B test/test_mqtt_disconnect.py -v
|
||||
python3 -B test/test_mqtt_connection_admission.py -v
|
||||
python3 -B test/test_mqtt_handshake_scheduler.py -v
|
||||
python3 -B test/test_mqtt_diagnostics.py -v
|
||||
python3 -B test/test_mqtt_publish_ack.py -v
|
||||
python3 -B test/test_wifi_network_power_policy.py -v
|
||||
python3 -B test/test_mqtt_wifi_network_power.py -v
|
||||
python3 -B test/test_companion_wifi_network_power.py -v
|
||||
python3 -B test/test_webconfig_network_power.py -v
|
||||
python3 -B test/test_companion_ap_ownership.py -v
|
||||
python3 -B test/test_heltec_v4_wifi_setup_page.py -v
|
||||
|
||||
- name: Verify DNS lease restoration and bounded diagnostics
|
||||
run: python3 -B test/test_network_dns.py -v
|
||||
|
||||
- name: Verify Sensor dog tracker ownership, routes, storage, GPS and sleep
|
||||
run: |
|
||||
python3 -B test/test_tracker_route_policy.py -v
|
||||
python3 -B test/test_sensor_tracker_store.py -v
|
||||
python3 -B test/test_tracker_owner_response.py -v
|
||||
python3 -B test/test_sensor_tracker_client.py -v
|
||||
python3 -B test/test_sensor_tracker_gps.py -v
|
||||
python3 -B test/test_sensor_tracker_sleep.py -v
|
||||
|
||||
- name: Verify Companion contact and channel TX routing
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y libssl-dev
|
||||
python3 -B test/test_companion_channel_alias.py -v
|
||||
python3 -B test/test_companion_tx_routing.py -v
|
||||
python3 -B test/test_companion_discovery.py -v
|
||||
python3 -B test/test_repeater_discovery.py -v
|
||||
python3 -B test/test_companion_discovery_correlation.py -v
|
||||
python3 -B test/test_pico_ble_queues.py -v
|
||||
python3 -B test/test_companion_preferences_transaction.py -v
|
||||
python3 -B test/test_companion_prefs_values.py -v
|
||||
python3 -B test/test_companion_prefs_image.py -v
|
||||
python3 -B test/test_companion_prefs_tail.py -v
|
||||
python3 -B test/test_companion_radio_settings_transaction.py -v
|
||||
python3 -B test/test_companion_prefs_transactions.py -v
|
||||
python3 -B test/test_companion_primary_radio_persistence.py -v
|
||||
python3 -B test/test_companion_response_bounds.py -v
|
||||
python3 -B test/test_companion_ack_delivery.py -v
|
||||
python3 -B test/test_companion_lost_reply.py -v
|
||||
python3 -B test/test_companion_delayed_reply_delivery.py -v
|
||||
python3 -B test/test_companion_hardware_serial.py -v
|
||||
python3 -B test/test_companion_hardware_serial_gps.py -v
|
||||
python3 -B test/test_companion_contact_lifetime.py -v
|
||||
python3 -B test/test_companion_identity_cli.py -v
|
||||
python3 -B test/test_companion_packet_stream.py -v
|
||||
python3 -B test/test_companion_uncached_storage.py -v
|
||||
python3 -B test/test_companion_ota_config.py -v
|
||||
python3 -B test/test_ota_identity_policy.py -v
|
||||
@@ -115,12 +297,28 @@ jobs:
|
||||
python3 -B test/test_common_radio_persistence.py -v
|
||||
python3 -B test/test_common_cli_save_results.py -v
|
||||
python3 -B test/test_common_prefs_commit.py -v
|
||||
python3 -B test/test_common_prefs_legacy_rxgain.py -v
|
||||
python3 -B test/test_flood_policy_capacity.py -v
|
||||
python3 -B test/test_flood_rule_layout.py -v
|
||||
python3 -B test/test_mqtt_prefs_commit.py -v
|
||||
python3 -B test/test_identity_and_settings_recovery.py -v
|
||||
python3 -B test/test_contact_cache.py -v
|
||||
|
||||
- name: Verify Meshtext chat codec, transport and Companion integration
|
||||
run: |
|
||||
python3 -B test/test_meshtext_chat.py -v
|
||||
python3 -B test/test_meshtext_config.py -v
|
||||
python3 -B test/test_meshtext_transport.py -v
|
||||
python3 -B test/test_meshtext_license.py -v
|
||||
python3 -B test/test_radio_profile_auto.py -v
|
||||
python3 -B test/test_companion_meshtext.py -v
|
||||
python3 -B test/test_companion_meshtext_storage.py -v
|
||||
python3 -B test/test_meshtext_auto_radio2_hil.py -v
|
||||
python3 -B test/test_meshtext_auto_radio2_hil_sdk.py -v
|
||||
|
||||
- name: Verify receive recovery and pinned radio status transport
|
||||
run: |
|
||||
python3 -B test/test_dispatcher_startup_watchdog.py -v
|
||||
python3 -B test/test_radio_receive_contract.py
|
||||
python3 - <<'PY'
|
||||
from pathlib import Path
|
||||
@@ -137,7 +335,24 @@ jobs:
|
||||
- name: Verify temporary-radio timing and exact reply delivery
|
||||
run: |
|
||||
python3 -B test/test_carrier_wave.py
|
||||
python3 -B test/test_morse_tx.py -v
|
||||
python3 -B test/test_morse_rx.py -v
|
||||
python3 -B test/test_morse_rx_timing.py -v
|
||||
python3 -B test/test_morse_rx_gap_bias.py -v
|
||||
python3 -B test/test_morse_rx_human.py -v
|
||||
python3 -B test/test_morse_hil_timing.py -v
|
||||
python3 -B test/test_morse_hil_production.py -v
|
||||
python3 -B test/test_morse_rx_squelch.py -v
|
||||
python3 -B test/test_morse_hil_squelch.py -v
|
||||
python3 -B test/test_morse_cli.py -v
|
||||
python3 -B test/test_morse_auth.py -v
|
||||
python3 -B test/test_morse_reply_handoff.py -v
|
||||
python3 -B test/test_morse_remote.py -v
|
||||
python3 -B test/test_morse_hil_safety.py -v
|
||||
python3 -B test/test_morse_loop_policy.py -v
|
||||
python3 -B test/test_morse_terminal_input.py -v
|
||||
python3 -B test/test_radio_profile_cli.py
|
||||
python3 -B test/test_radio_profile_spiffs_missing.py -v
|
||||
python3 -B test/test_radio_reply_mutation.py
|
||||
python3 -B test/test_radio_profile_scan.py
|
||||
python3 -B test/test_repeater_radio_timing_integration.py
|
||||
@@ -149,6 +364,9 @@ jobs:
|
||||
python3 -B test/test_ota_store_resume.py -v
|
||||
python3 -B test/test_ota_catalog_lifetime.py -v
|
||||
python3 -B test/test_ota_store_flash_nrf52_hybrid.py -v
|
||||
python3 -B test/test_tower_bootloader_caps.py -v
|
||||
python3 -B test/test_tower_auto_storage.py -v
|
||||
python3 -B test/test_tower_auto_integration.py -v
|
||||
python3 -B test/test_ota_dev_staging.py -v
|
||||
python3 -B test/test_ota_dev_reboot_guard.py -v
|
||||
|
||||
@@ -156,41 +374,141 @@ jobs:
|
||||
working-directory: test
|
||||
run: >-
|
||||
python3 -B -m unittest -v
|
||||
test_sx1262_profile_switch test_sx1262_batched_modulation
|
||||
test_sx1262_profile_switch test_sx1262_batched_modulation test_sx1262_protocol_batching
|
||||
test_radio_chirp_math
|
||||
|
||||
- name: Verify radio lab collectors and compiled experiment contracts
|
||||
run: |
|
||||
python3 -B -m unittest discover -s test -p 'test_profile_*.py' -v
|
||||
python3 -B -m unittest discover -s tools/hil -p 'test_*.py' -v
|
||||
python3 -B test/test_hil_esp32_self_serve_ota.py -v
|
||||
python3 -B test/test_hil_esp32_usb_host_reboot.py -v
|
||||
|
||||
- name: Verify ESP32 static DRAM budget
|
||||
run: |
|
||||
python3 -B test/test_esp32_dram.py
|
||||
python3 -B test/test_battery_charge_cli.py -v
|
||||
python3 -B test/test_tbeam_charge_target.py -v
|
||||
python3 -B test/test_elrs_power.py
|
||||
python3 -B test/test_ota_heap_context.py
|
||||
python3 -B test/test_ota_self_serve_policy.py -v
|
||||
python3 -B test/test_ota_self_metadata_cache.py -v
|
||||
python3 -B test/test_ota_nrf52_display_cache.py -v
|
||||
python3 -B test/test_ota_device_deflate_encoder.py -v
|
||||
python3 -B test/test_ota_device_deflate_role.py -v
|
||||
python3 -B test/test_client_acl_spiffs.py
|
||||
python3 -B test/test_client_acl_role.py -v
|
||||
python3 -B test/test_client_acl_metadata.py -v
|
||||
python3 -B test/test_client_acl_cli.py -v
|
||||
python3 -B test/test_client_acl_response.py -v
|
||||
|
||||
- name: Verify ESP32 USB sleep and G3 button wake
|
||||
run: python3 -B test/test_esp32_usb_sleep.py
|
||||
- name: Setup Node for official app compatibility tests
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '22'
|
||||
|
||||
- name: Cache pinned official app and browser protocol sources
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: ~/.cache/meshcore-tests
|
||||
key: official-clients-84bc39a950735aaffa93d912556852a4c22e235d0cc4e4d04790113207f13a68-aff42ad8ef2b332f24d644ff6a783f8272a88ff2
|
||||
|
||||
- name: Verify stock app radio, Access Control and room compatibility
|
||||
run: |
|
||||
node --version
|
||||
python3 -B test/test_official_app_compatibility.py -v
|
||||
python3 -B test/test_official_room_app_compatibility.py -v
|
||||
|
||||
- name: Verify ESP32 USB reboot sleep grace and G3 button wake
|
||||
run: |
|
||||
python3 -B test/test_usb_host_sleep_policy.py
|
||||
python3 -B test/test_esp32_usb_sleep.py
|
||||
python3 -B test/test_esp32_full_sleep.py
|
||||
python3 -B test/test_esp32_full_power_lifecycle.py
|
||||
|
||||
- name: Verify official web console framing and power-saving idle sessions
|
||||
run: |
|
||||
python3 -B test/test_cpp_source.py -v
|
||||
python3 -B test/test_official_web_console.py -v
|
||||
python3 -B test/test_hil_linux_web_serial.py -v
|
||||
python3 -B test/test_hil_slowfi_guard.py -v
|
||||
|
||||
- name: Verify official setup GUI initial time command deadline
|
||||
run: python3 -B test/test_official_config_gui.py -v
|
||||
|
||||
- name: Verify ESP32 boot filesystem inventory and recovery safety
|
||||
run: |
|
||||
python3 -B test/test_esp32_boot_file_inventory.py -v
|
||||
python3 -B test/test_esp32_boot_file_lifecycle.py -v
|
||||
|
||||
- name: Verify WiFi OTA startup and WebConfig handoff
|
||||
run: |
|
||||
python3 -B test/test_wifi_ota_start.py -v
|
||||
python3 -B test/test_webconfig_ap_start.py -v
|
||||
python3 -B test/test_webconfig_recovery_auth.py -v
|
||||
python3 -B test/test_esp32_wifi_scan_fix.py -v
|
||||
python3 -B test/test_esp32_wifi_scan_recipes.py -v
|
||||
|
||||
- name: Verify browser OTA completion and immediate HTTP listener restart
|
||||
run: python3 -B test/test_async_web_lifecycle.py -v
|
||||
|
||||
- name: Verify GPS status distinguishes user settings from receiver sleep
|
||||
run: python3 -B test/test_gps_status.py
|
||||
run: |
|
||||
python3 -B test/test_gps_status.py
|
||||
python3 -B test/test_gps_upstream_adaptations.py -v
|
||||
python3 -B test/test_esp32_gps_discovery.py -v
|
||||
python3 -B test/test_gps_sync_interval.py -v
|
||||
python3 -B test/test_gps_sync_preferences.py -v
|
||||
python3 -B test/test_rak_gps_power.py -v
|
||||
python3 -B test/test_peripheral_mode_prefs.py -v
|
||||
python3 -B test/test_t1000e_sensor_power.py -v
|
||||
python3 -B test/test_sensor_battery_alerts.py -v
|
||||
python3 -B test/test_sensor_subscriptions.py -v
|
||||
python3 -B test/test_sensor_reply_bounds.py -v
|
||||
|
||||
- name: Verify Companion ASCII defaults and USB session cleanup
|
||||
run: |
|
||||
python3 -B test/test_companion_usb_default.py
|
||||
python3 -B test/test_companion_usb_input.py
|
||||
python3 -B test/test_companion_ota_includes.py
|
||||
python3 -B test/test_companion_usb_logging_handoff.py
|
||||
python3 -B test/test_companion_usb_mota_exit.py
|
||||
python3 -B test/test_common_cli_logging_transaction.py
|
||||
python3 -B test/test_usb_diagnostic_logging.py
|
||||
python3 -B test/test_nrf52_usb_client.py
|
||||
python3 -B test/test_nrf52_usb_lifecycle.py
|
||||
python3 -B test/test_nrf52_usb_logging_contract.py
|
||||
python3 -B test/test_esp32_usb_serial_hygiene.py
|
||||
python3 -B test/test_esp32_tinyusb_nonblocking.py
|
||||
python3 -B test/test_esp32_usb_session_fix.py
|
||||
python3 -B test/test_hwcdc_tx_backport.py -v
|
||||
python3 -B test/test_hwcdc_startup_session.py -v
|
||||
python3 -B test/test_hwcdc_313_startup_session.py -v
|
||||
python3 -B test/test_hwcdc_3311_startup_session.py -v
|
||||
python3 -B test/test_esp32_hwcdc_recipes.py -v
|
||||
python3 -B test/test_hwcdc_role_transport.py -v
|
||||
python3 -B test/test_hwcdc_write_capacity.py -v
|
||||
python3 -B test/test_esp32_file_buffer_fix.py -v
|
||||
python3 -B test/test_companion_usb_logging_reply.py
|
||||
python3 -B test/test_esp32_tinyusb_cooperative_output.py
|
||||
python3 -B test/test_esp32_tinyusb_role_hygiene.py
|
||||
python3 -B test/test_stm32_float_conversion.py
|
||||
|
||||
- name: Verify Companion contact persistence and OTA sleep guards
|
||||
run: python3 -B test/test_companion_contact_sleep_contract.py -v
|
||||
run: |
|
||||
python3 -B test/test_companion_contact_sleep_contract.py -v
|
||||
python3 -B test/test_esp32_cooperative_contacts.py -v
|
||||
python3 -B test/test_esp32_contact_writer_batch.py -v
|
||||
python3 -B test/test_esp32_contact_crc_batch.py -v
|
||||
python3 -B test/test_esp32_contact_publication.py -v
|
||||
python3 -B test/test_esp32_advert_cache.py -v
|
||||
|
||||
- name: Verify Companion Bluetooth CLI connection protection
|
||||
run: python3 -B test/test_companion_bluetooth_control.py -v
|
||||
|
||||
- name: Verify Companion Bluetooth reboot persistence
|
||||
run: python3 -B test/test_companion_bluetooth_persistence.py -v
|
||||
|
||||
- name: Verify nRF52 SoftDevice LTO and persistent BLE subscriptions
|
||||
env:
|
||||
MESHCORE_REQUIRE_ARM_COMPILER: '1'
|
||||
@@ -206,9 +524,13 @@ jobs:
|
||||
run: |
|
||||
python3 -B test/test_wireless_control.py -v
|
||||
python3 -B test/test_serial_wifi_sessions.py -v
|
||||
python3 -B test/test_companion_wifi_listener.py -v
|
||||
python3 -B test/test_serial_ethernet_sessions.py -v
|
||||
python3 -B test/test_companion_wifi_session.py -v
|
||||
python3 -B test/test_espnow_radio_lifecycle.py -v
|
||||
python3 -B test/test_wifi_ap_lr_safety.py -v
|
||||
python3 -B test/test_espnow_runtime.py -v
|
||||
python3 -B test/test_wifi_ota_lifecycle.py -v
|
||||
|
||||
- name: Verify message reader buttons, touch targets, and footer layouts
|
||||
working-directory: test
|
||||
@@ -220,19 +542,39 @@ jobs:
|
||||
test_indicator_display_profile test_indicator_render_profile
|
||||
test_indicator_exclusive_transport test_companion_transport_selector
|
||||
|
||||
- name: Verify logging sleep guards and instructions
|
||||
- name: Verify independent USB packet/debug logging, recovery, and instructions
|
||||
run: |
|
||||
python3 -B test/test_common_usb_debug.py
|
||||
python3 -B test/test_companion_usb_logging.py
|
||||
python3 -B test/test_usb_logging_watchdog.py
|
||||
python3 -B test/test_usb_watchdog_event.py
|
||||
python3 -B test/test_usb_logging_client_activity.py
|
||||
python3 -B test/test_usb_logging_watchdog_runtime.py
|
||||
python3 -B test/test_usb_logging_watchdog_roles.py
|
||||
python3 -B test/test_nrf52_usb_console.py
|
||||
python3 -B test/test_esp32_tinyusb_nonblocking.py
|
||||
python3 -B test/test_esp32_tinyusb_role_hygiene.py
|
||||
python3 -B test/test_logging_sleep_contract.py
|
||||
python3 -B test/test_picker_controls_generator.py
|
||||
python3 -B test/test_firmware_picker_package.py
|
||||
node test/test_firmware_picker.js
|
||||
|
||||
- name: Verify Full logging compiler flags and packaged capabilities
|
||||
run: |
|
||||
python3 -B test/test_upstream_observer_recipes.py -v
|
||||
python3 -B test/test_station_g3_sensor_recipes.py -v
|
||||
python3 -B test/test_full_build_flag_dedup.py
|
||||
python3 -B test/test_full_logging_compile.py
|
||||
python3 -B test/test_full_logging_resume.py
|
||||
python3 -B test/test_xiao_canonical_logging.py -v
|
||||
python3 -B test/test_firmware_build_recipe.py -v
|
||||
python3 -B test/test_firmware_capabilities.py
|
||||
MESHCORE_TEST_REAL_PIO_PATHS=1 python3 -B test/test_esp32_partition_migration_build_dir.py -v
|
||||
python3 -B test/test_esp32_partition_migration_usb.py -v
|
||||
python3 -B test/test_usb_partition_bridge_set.py -v
|
||||
bash test/test_build_profiles.sh
|
||||
bash test/test_full_infrastructure_selection.sh
|
||||
bash test/test_nrf52_ota_profiles.sh
|
||||
bash test/test_xiao_qspi_combined.sh
|
||||
|
||||
- name: Verify nRF52 UF2-reset CLI coverage
|
||||
@@ -247,12 +589,18 @@ jobs:
|
||||
python3 -B test/test_replay_reset_integration.py -v
|
||||
|
||||
- name: Verify firmware memory budgets and nRF52 Bluetooth startup
|
||||
env:
|
||||
MESHCORE_REQUIRE_ARM_COMPILER: '1'
|
||||
run: |
|
||||
bash test/test_build_size_detection.sh
|
||||
python3 -B test/test_firmware_ram.py
|
||||
python3 -B test/test_recent_repeater_storage.py -v
|
||||
python3 -B test/test_ota_cold_buffers.py -v
|
||||
python3 -B test/test_neighbor_scope_storage.py
|
||||
python3 -B test/test_ed25519_compact.py
|
||||
python3 -B test/test_cascade_release_package.py
|
||||
python3 -B test/test_release_publication.py
|
||||
python3 -B test/test_local_release_full_selection.py
|
||||
python3 -B test/test_local_release_capacity_archive.py
|
||||
python3 -B test/test_t096_full_memory.py
|
||||
python3 -B test/test_nrf52_ble_startup.py
|
||||
python3 -B test/test_nrf52_ble_trace.py
|
||||
@@ -260,8 +608,44 @@ jobs:
|
||||
python3 -B test/test_nrf52_usb_power.py
|
||||
python3 -B test/test_shared_mota_queue.py
|
||||
|
||||
- name: Verify opt-in RAK4631 combined Ethernet and OTA modes
|
||||
run: |
|
||||
python3 -B test/test_rak_combined_ota.py -v
|
||||
python3 -B test/test_rak_ethernet_transmit.py -v
|
||||
python3 -B test/test_rak4631_ethernet_runtime.py -v
|
||||
|
||||
- name: Verify nRF52 infrastructure trims and unchanged Companion scope
|
||||
run: |
|
||||
python3 -B test/test_nrf52_flash_trim.py -v
|
||||
python3 -B test/test_nrf52_tft_font_trim.py -v
|
||||
python3 -B test/test_ota_target_names.py -v
|
||||
python3 -B test/test_ota_cli_listing.py -v
|
||||
|
||||
- name: Verify compact same-size fonts and lossless rendering
|
||||
run: |
|
||||
python3 -B test/test_compact_arial.py -v
|
||||
python3 -B test/test_compact_noto.py -v
|
||||
python3 -B test/test_nrf52_font_license.py -v
|
||||
python3 -B test/test_oled_font_experiments.py -v
|
||||
python3 -B test/test_font_family_trials.py -v
|
||||
|
||||
- name: Verify Companion Bluetooth settings and WebConfig
|
||||
run: |
|
||||
python3 -B test/test_gps_voice_buttons.py -v
|
||||
python3 -B test/test_gps_voice_generator.py -v
|
||||
python3 -B test/test_button_voice_actions.py -v
|
||||
python3 -B test/test_button_voice_generator.py -v
|
||||
python3 -B test/test_button_voice_preview.py -v
|
||||
python3 -B test/test_g726_voice.py -v
|
||||
MESH_G726_TEST_SANITIZERS=1 python3 -B test/test_g726_voice.py -v
|
||||
python3 -B test/test_g726_voice_generator.py -v
|
||||
python3 -B test/test_voice_prefixes.py -v
|
||||
MESH_VOICE_TEST_SANITIZERS=1 python3 -B test/test_voice_prefixes.py -v
|
||||
python3 -B test/test_nimble_companion.py
|
||||
python3 -B test/test_bluetooth_pairing_ui.py
|
||||
python3 -B test/test_companion_pairing_display.py
|
||||
python3 -B test/test_companion_identity_startup.py
|
||||
python3 -B test/test_startup_screen.py
|
||||
node test/test_notification_builder.js
|
||||
python3 -B test/test_notification_settings.py
|
||||
python3 -B test/test_display_power.py
|
||||
@@ -272,7 +656,14 @@ jobs:
|
||||
python3 -B test/test_webconfig_ui.py
|
||||
python3 -B test/test_webconfig_gzip.py
|
||||
python3 -B test/test_webconfig_secret_masking.py
|
||||
python3 -B test/test_webconfig_station_ownership.py -v
|
||||
python3 -B test/test_mqtt_canonical_wifi.py -v
|
||||
python3 -B test/test_mqtt_ap_ownership.py -v
|
||||
python3 -B test/test_sensor_webconfig_capabilities.py -v
|
||||
python3 -B test/test_webconfig_ui_runtime.py
|
||||
python3 -B test/test_webconfig_lost_reply.py -v
|
||||
MESHCORE_REQUIRE_ROOM_BROWSER=1 python3 -B test/test_room_web_ui.py -v
|
||||
MESHCORE_REQUIRE_ROOM_BROWSER=1 python3 -B test/test_room_mail_web_ui.py -v
|
||||
|
||||
- name: Upload Test Results
|
||||
# Upload test results even if the test step failed.
|
||||
@@ -281,3 +672,17 @@ jobs:
|
||||
with:
|
||||
name: test-results
|
||||
path: .pio/build/native/
|
||||
|
||||
hosted-doc-links:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-python@v6
|
||||
with:
|
||||
python-version: '3.13'
|
||||
- name: Install the documentation renderer
|
||||
run: python3 -m pip install mkdocs==1.6.1 mkdocs-material==9.7.7
|
||||
- name: Verify source links and published page routes
|
||||
run: python3 -B test/test_hosted_doc_links.py -v
|
||||
- name: Verify hosted-link visibility and navigation
|
||||
run: node test/test_hosted_doc_links.js
|
||||
@@ -639,6 +639,7 @@ These settings apply across all MQTT slots:
|
||||
- `get wifi.ssid` - Get WiFi SSID
|
||||
- `get wifi.pwd` - Get WiFi password
|
||||
- `get wifi.status` - Get WiFi connection status, IP, RSSI, and uptime
|
||||
- `get link.dns` - Show the live DNS resolver list, the selected medium's gateway, and each medium's leased DNS servers (`-` means empty; `none` means never leased). Long lease lists may be truncated to fit the CLI reply.
|
||||
- `get wifi.powersave` - Get WiFi power save mode (none/min/max)
|
||||
|
||||
#### Set Commands
|
||||
|
||||
+34
-1
@@ -137,6 +137,37 @@ acknowledged cleanly in about 11.7 seconds within its 21-second budget. The
|
||||
residual Section 1/Section 2 instance-pointer reads remain deferred, so those
|
||||
consumers can still touch live or torn-down bridge state as tracked above.
|
||||
|
||||
The 2026-10-10 connection-timeout fix restores the SDK network budget to
|
||||
10 seconds. The stop deadline now also allows the owner to finish one blocked
|
||||
publish and each client to finish a network operation: `5 s + 10 s +
|
||||
max(8 s, 10 s) * enabled_slots` (35 seconds for two slots). A clean
|
||||
acknowledgment still returns immediately. This deadline change is covered by
|
||||
synthetic tests; no new physical teardown measurement is claimed.
|
||||
|
||||
Connection admission reads SDK initialization and task-start state on the
|
||||
bridge owner task. `PsychicMqttClient::isInitialized()` reports whether its SDK
|
||||
handle still owns configuration, transport objects and MQTT buffers;
|
||||
`isStarted()` separately reports whether its SDK task is running. Callbacks
|
||||
change connection state but do not assign or clear the SDK handle.
|
||||
`MQTTConnectionAdmission::ClientMemoryState` uses those distinct lifetimes:
|
||||
uninitialized clients require 32 KiB free DMA heap and an 8 KiB block,
|
||||
initialized stopped clients require 24 KiB and an 8 KiB block, and started
|
||||
clients require 16 KiB and a 4 KiB block. The stopped-client allowance reserves
|
||||
8 KiB for recreating the explicit 6,144-byte SDK task stack plus TCB and
|
||||
allocator overhead while retaining the 16 KiB AES reserve. WiFi-loss shutdown
|
||||
still stops the SDK task and retains its handle; this admission distinction
|
||||
permits its restart without charging initialization buffers twice. A failed
|
||||
initialization that discards the SDK handle continues to use the full cold
|
||||
allowance. Internal-only TLS keeps its additional 60 KiB/17,408-byte thresholds.
|
||||
|
||||
All ESP32 clients use 896-byte MQTT buffers from their first initialization,
|
||||
including non-JWT slots on internal-only boards. The SDK does not resize those
|
||||
buffers when applying configuration, and the wrapper reassembly buffer also
|
||||
keeps its initial capacity. Uniform sizing therefore permits later JWT
|
||||
reconfiguration while retaining the client, its callbacks and the slot's token
|
||||
storage. Explicit reconfiguration and WiFi recovery keep the existing client
|
||||
lifetime; neither introduces deletion while Core 1 diagnostics may read it.
|
||||
|
||||
## Remaining target primitives
|
||||
|
||||
- **Task notifications or a command queue** for one-way lifecycle / reconfigure
|
||||
@@ -174,7 +205,9 @@ Phase 5 as a pure state machine plus a narrow injected `Ops` seam
|
||||
### Hardware characterization result
|
||||
|
||||
WSS teardown was measured as roughly 5-6 seconds per slot and is sequential.
|
||||
Production now sets the coordinator timeout to `5 s + 8 s * enabled_slots`.
|
||||
That characterization used the coordinator timeout `5 s + 8 s * enabled_slots`.
|
||||
The current deadline also includes the SDK network and blocked-publish budgets
|
||||
described above.
|
||||
Representative non-PSRAM and PSRAM start/stop matrices found no downward heap
|
||||
or largest-block trend; multi-day soak, task-stack high-water, and exhaustive
|
||||
callback ordering remain outside that run.
|
||||
|
||||
@@ -1,21 +1,30 @@
|
||||
## Halo Keymind Cascade Branch Changes over Stock
|
||||
|
||||
* **Two parallel LoRa radio settings on one device at the same time.** Alternate rx & tx between two frequencies and/or modulation settings. Choose transmission profiles per contact or channel, enable or isolate crossover traffic, schedule profiles, and use a temporary update profile alongside the normal network. Includes timing calibration and separate R2 screen pages. These are time-shared profiles on one transceiver.
|
||||
* **Firmware updates over LoRa.** Update repeaters/rooms/sensors over LoRa.
|
||||
* **Repeater forwarding policies.** Actions include dropping, rate limiting, assigning scopes, selecting retries, and controlling forwarding priority. Separate policies cover ordinary radio forwarding, bridges, and profile crossover.
|
||||
* **Flood retries that account for network topology.** Configure retry counts, path limits, packet-type limits, target prefixes, ignored repeaters, and channel-specific eligibility. Bridge buckets keep retrying until traffic is heard on the intended sides of a relay or the retry budget expires. Qualifying echoes cancel unnecessary attempts.
|
||||
* **Enhanced direct-message and direct-route retries.** Dynamic coding-rate, keep trying until echo hear.
|
||||
* **Enhanced Telemetry.** Single LoRa packet supports multiple days of temperature or battery voltage or GPS history.
|
||||
* **Unified Full Companion firmware.** Combines USB, BLE, WiFi, and Ethernet/serial transports in one image; with runtime controls and a LoRa firmware-serving mode. Most supported targets can operate multiple transports together; some boards have explicit transport restrictions.
|
||||
* **Operational reporting and alerts.** Adds optional management reports with firmware version, bootloader version, LoRa update capabilities, uptime, and historical voltage and temperature ranges.
|
||||
* **Set time using LoRa Network.** Automatically set the clock based off the LoRa network. Better than 2024.
|
||||
* **Companion Bluetooth privacy and recovery improvements.** Configurable mac address policies, paired-peer stealth behavior, and changes to reconnect, subscription, and startup handling.
|
||||
* **Single Key DMs.** Allow for you to send your advert in the initial DM. Makes DMing a bot/bbs/etc a lot easier if they're also running this firmware.
|
||||
* **agessaman Observer - ESP32 Integrated MQTT observation.** Send directly from the node to the observer api endpoint.
|
||||
* **IoTThinks PowerSaving - Broader power-saving controls.** Integrates device sleep, LoRa receive duty cycling, WiFi modem-sleep policies, and GPS acquisition/cache schedules across supported roles. These controls are independently configurable; actual savings depend on hardware and workload.
|
||||
* **[Two LoRa profiles on one device](https://mikecarper.github.io/MeshCore/radio_profiles/).** Time-share one transceiver between two frequencies and/or modulation settings. Choose transmission profiles per contact or channel, enable or isolate crossover traffic, schedule profiles, and use a temporary update profile alongside the normal network. Includes timing calibration and separate R2 screen pages.
|
||||
* **[Firmware updates over LoRa](https://mikecarper.github.io/MeshCore/lora_ota_automation/).** Update repeaters, room servers, and sensors over LoRa on supported firmware and storage layouts.
|
||||
* **[Repeater forwarding policies](https://mikecarper.github.io/MeshCore/flood_filtering/).** Actions include dropping, rate limiting, assigning scopes, selecting retries, and controlling forwarding priority. Separate policies cover ordinary radio forwarding, bridges, and profile crossover.
|
||||
* **[Private fleet management](https://mikecarper.github.io/MeshCore/fleet_management/).** Deploy filtering, secondary-radio changes, radio schedules, and clock updates to a whole fleet, existing configured or home regions, a GPS center and radius, one node, or a semicolon-separated target list. The publisher signs each complete command once, splitting longer requests across two packets; receivers hold its public verification key, with replay protection and acknowledgements before radio handoff.
|
||||
* **[Flood retries that account for network topology](https://mikecarper.github.io/MeshCore/halo_keymind_settings/#flood-and-advert-settings).** Configure retry counts, path limits, packet-type limits, target prefixes, ignored repeaters, and channel-specific eligibility. Bridge buckets keep retrying until traffic is heard on the intended sides of a relay or the retry budget expires. Qualifying echoes cancel unnecessary attempts.
|
||||
* **[Enhanced direct messages and direct-route retries](https://mikecarper.github.io/MeshCore/halo_keymind_settings/#direct-retry-settings).** Adapt the coding rate across bounded retry attempts and cancel unnecessary retries when a qualifying echo is heard. [Single Key DMs](https://mikecarper.github.io/MeshCore/companion_protocol/#private-text-when-only-the-sender-knows-the-recipient) let compatible Companions send private text when only the sender knows the recipient's key, using a signed, encrypted introduction. The recipient accepts the sender through the app or opts into automatic acceptance; compact STM32WL builds omit this feature. An optional [Companion lost-status responder](https://mikecarper.github.io/MeshCore/companion_lost_reply/) answers private "Am I lost?" questions with a saved Yes or No.
|
||||
* **[Enhanced telemetry history](https://mikecarper.github.io/MeshCore/telemetry_decoder/).** Send compact temperature and battery-voltage snapshots of up to 165 samples per packet, plus external-voltage history in chunks. Read retained GPS history through the CLI; GPS history is not included in the automatic telemetry packets. Decode snapshots and CLI history in the browser.
|
||||
* **[Room topics, mailboxes, history, and access controls](https://mikecarper.github.io/MeshCore/room_services/).** Save an announcement and optionally retain the newest 32 chat posts across reboots. Regular users manage personal mailboxes with public or selected-sender access, explicit receipts, and mailbox-only delivery for sleeping trackers. Room users can manage their own return routes and skip older unread chat by date or keep the newest N messages. Explicit ACL roles persist; full-key bans and per-user post/request limits apply to room sessions. ESP32 images with WebConfig provide local Wi-Fi chat, an information board, and an admin dashboard for mailboxes, routes, delivery, activity, catch-up and access. The existing app's chat and catch-up formats remain supported.
|
||||
* **[Full firmware profiles, Companion transports, and ESP32 bridges](https://mikecarper.github.io/MeshCore/full_profiles/).** Full combines the exact board and role's supported features with runtime controls: Companion transports and firmware serving, or qualified ESP32 infrastructure services and MQTT/ESP-NOW/RS-232 bridges. Includes capacity exceptions, matching application slots, exact-board/role partition migration packages, and nRF52 Full/Reduced supported-sensor packages with LoRa OTA. The picker links supported migration routes; USB layout changes use the matching merged image, since an app-only image cannot change the partition table.
|
||||
* **[Operational reporting and alerts](https://mikecarper.github.io/MeshCore/management_reports/).** Adds optional management reports with firmware version, bootloader version, LoRa update capabilities, uptime, and historical voltage and temperature ranges.
|
||||
* **[Set time using the LoRa network](https://mikecarper.github.io/MeshCore/cli_commands/#estimate-and-correct-infrastructure-node-time-after-startup).** Infrastructure nodes can estimate time from fresh mesh evidence and correct their clocks when a consensus is available. Successful manual, GPS, or NTP synchronization takes precedence until reboot.
|
||||
* **[Companion Bluetooth privacy and recovery improvements](https://mikecarper.github.io/MeshCore/companion_radio_full/#interfaces).** Configurable MAC address policies, paired-peer stealth behavior, and changes to reconnect, subscription, and startup handling on supported Bluetooth targets.
|
||||
* **[Programmable Companion radio notifications](https://mikecarper.github.io/MeshCore/notifications/).** Build per-contact, room, or channel rules for the radio's vibration, LED, melody, screen, and supported GPIO outputs. Select rules by client connection state and configure repeat/stop behavior. Available on ESP32, nRF52, and RP2040 Companions with the required outputs.
|
||||
* **[agessaman Observer - integrated ESP32 MQTT observation](https://mikecarper.github.io/MeshCore/WiFi/#mqtt-observer-setup).** Publish packets from the node over WiFi to a configured MQTT broker, without a separate USB host bridge.
|
||||
* **[IoTThinks PowerSaving - broader power-saving controls](https://mikecarper.github.io/MeshCore/role_feature_switches/#infrastructure-power-saving-and-bridges).** Integrates device sleep, LoRa receive duty cycling, WiFi modem-sleep policies, and GPS acquisition/cache schedules across supported roles. These controls are independently configurable; actual savings depend on hardware and workload.
|
||||
* **[LiFePO4 low-voltage cutoff and protection](https://mikecarper.github.io/MeshCore/cli_commands/#configure-nrf52-battery-protection).** On supported nRF52 power-management boards with a text CLI, the saved LiFePO4 profile sets a 2.9 V boot lock and 2.7 V running cutoff. Three valid low readings 30 seconds apart trigger protective shutdown; external power bypasses these checks. Use compatible charging hardware: selecting a battery profile does not change the charger.
|
||||
|
||||
Get the correct firmware file here for your hardware:
|
||||
**https://mikecarper.github.io/MeshCore/firmware_picker/**
|
||||
**[Firmware picker](https://mikecarper.github.io/MeshCore/firmware_picker/)**
|
||||
|
||||
Related source projects:
|
||||
|
||||
* [agessaman Observer firmware](https://github.com/agessaman/MeshCore/tree/observer-firmware-dev): embedded WiFi/MQTT observation.
|
||||
* [Cisien meshcoretomqtt](https://github.com/Cisien/meshcoretomqtt): a host-side Python USB/serial packet-log publisher.
|
||||
* [IoTThinks PowerSaving branch](https://github.com/IoTThinks/MeshCore/tree/PowerSaving-v17): power-saving integration.
|
||||
|
||||
---
|
||||
|
||||
@@ -65,7 +74,7 @@ For developers:
|
||||
|
||||
- Install [PlatformIO](https://docs.platformio.org) in [Visual Studio Code](https://code.visualstudio.com).
|
||||
- Clone and open the MeshCore repository in Visual Studio Code.
|
||||
- Install the pinned build-time compressors with `python -m pip install -r requirements-build.txt`.
|
||||
- Install the pinned build-time dependencies in PlatformIO's Python environment with `python -m pip install -r requirements-build.txt`.
|
||||
- See the example applications you can modify and run:
|
||||
- [Companion Radio](./examples/companion_radio) - For use with an external chat app, over BLE, USB or Wi-Fi.
|
||||
- [KISS Modem](./examples/kiss_modem) - Serial KISS protocol bridge for host applications. ([protocol docs](./docs/kiss_modem_protocol.md))
|
||||
|
||||
+49
-2
@@ -1,5 +1,51 @@
|
||||
# Releasing Firmware
|
||||
|
||||
For 1.17.1.9 and later, build the complete qualified local release from a
|
||||
clean, committed checkout. The builder runs one PlatformIO process at a time:
|
||||
|
||||
```bash
|
||||
python3 scripts/build_local_release.py \
|
||||
--firmware-version v1.17.1.9-halo-keymind-cascade-dev --pio-jobs 4 --resume
|
||||
```
|
||||
|
||||
Package its completed directory for stable publication:
|
||||
|
||||
```bash
|
||||
python3 scripts/package_cascade_release.py \
|
||||
--input .releases/v1.17.1.9-halo-keymind-cascade-dev-SOURCE8 \
|
||||
--output /path/to/publication-stage --local-release --stable \
|
||||
--version 1.17.1.9 --commit FULL_SOURCE_COMMIT
|
||||
```
|
||||
|
||||
The packager rechecks the local checksum inventory, qualified firmware,
|
||||
full/reduced nRF52 pairs, and every exact ESP32 migration package. It keeps
|
||||
migration-only application identities inside their migration ZIPs. It does
|
||||
not upload anything. Review `release-plan.json`, generate the picker controls
|
||||
as described in [the picker guide](docs/firmware_picker.md), and generate
|
||||
both Mesh America catalogs from the same staged asset manifests:
|
||||
|
||||
```bash
|
||||
python3 scripts/generate_mesh_america_release.py \
|
||||
--stage /path/to/publication-stage \
|
||||
--catalog mesh-america/keymind-cascade-v1.16.0-provider.json \
|
||||
--output /path/to/new-provider.json
|
||||
```
|
||||
|
||||
Both active provider URLs use the same binaries. Preserve their stable
|
||||
filenames when updating them. Create supporting releases first and publish
|
||||
the Companion family root last, marking only that root as GitHub Latest.
|
||||
Upload each group's complete asset inventory and verify the public asset
|
||||
names, sizes and checksums before switching the catalogs. Regenerate the
|
||||
downloadable picker from the same family and update its checksum on every
|
||||
page. Documentation-only follow-up commits do not move firmware tags.
|
||||
|
||||
Include the [1.17.1.9 release notes](docs/releases/1.17.1.9.md) and
|
||||
[1.17.1.8 WiFi upgrade guide](docs/upgrading_1.17.1.8_to_1.17.1.9.md) in the
|
||||
publication review. Replace the release notes' final package-validation gate
|
||||
with the completed inventory only after packaging passes. Keep the firmware
|
||||
source separate from a later documentation-only commit, and retain the
|
||||
measured hardware scope and old-updater completion limitation.
|
||||
|
||||
For the USA Cascade 1.17.1.5 matrix, use
|
||||
[the option 3 release instructions](docs/old-releases/1.17.1.5.md).
|
||||
Package the qualified outputs with `scripts/package_cascade_release.py`.
|
||||
@@ -16,12 +62,13 @@ and `room-server-*` tags (for example `repeater-v1.0.0`). They invoke the shared
|
||||
`firmware-builder.yml`, build their own target set, and request a draft release.
|
||||
They are separate from the qualified option 3 matrix and local release staging.
|
||||
|
||||
**The broad `repeater-*` trigger also matches `repeater-room-*` Cascade tags.**
|
||||
For 1.17.1.5 this workflow added its own artifacts and changed the title of the
|
||||
The older broad `repeater-*` trigger also matched `repeater-room-*` Cascade tags.
|
||||
For 1.17.1.5 that workflow added its own artifacts and changed the title of the
|
||||
already-published Repeater/Room Server page. Do not assume a published Cascade
|
||||
page is untouched while that workflow is running. Check workflow completion,
|
||||
release title/body, and the final asset inventory after publication. Preserve
|
||||
existing assets when making a documentation-only release edit.
|
||||
The current trigger explicitly excludes `repeater-room-*`.
|
||||
|
||||
Use role-specific introductions for Companion, Repeater/Room Server, Sensor,
|
||||
LoRa OTA Repeater, and expanded Full infrastructure pages. Link the exact
|
||||
|
||||
@@ -1,4 +1,17 @@
|
||||
#include <AsyncElegantOTA.h>
|
||||
#include "AsyncElegantOTA.h"
|
||||
#include <atomic>
|
||||
static std::atomic<bool> ota_uploads_enabled{false};
|
||||
static std::atomic<bool> ota_upload_busy{false};
|
||||
#if defined(ESP32)
|
||||
static std::atomic<bool> ota_reboot_pending{false};
|
||||
#endif
|
||||
// All upload callbacks run on AsyncTCP's task. Update is a global singleton;
|
||||
// retain one request as its owner until that request disconnects.
|
||||
static AsyncWebServerRequest* ota_upload_owner = nullptr;
|
||||
struct OtaUploadState {
|
||||
bool file_final;
|
||||
bool committed;
|
||||
};
|
||||
|
||||
AsyncElegantOtaClass AsyncElegantOTA;
|
||||
|
||||
@@ -8,6 +21,7 @@ void AsyncElegantOtaClass::setID(const char* id){
|
||||
|
||||
void AsyncElegantOtaClass::begin(AsyncWebServer *server, const char* username, const char* password){
|
||||
_server = server;
|
||||
setEnabled(true);
|
||||
|
||||
if(strlen(username) > 0){
|
||||
_authRequired = true;
|
||||
@@ -49,15 +63,28 @@ void AsyncElegantOtaClass::begin(AsyncWebServer *server, const char* username, c
|
||||
return request->requestAuthentication();
|
||||
}
|
||||
}
|
||||
// the request handler is triggered after the upload has finished...
|
||||
// create the response, add header, and send response
|
||||
AsyncWebServerResponse *response = request->beginResponse((Update.hasError())?500:200, "text/plain", (Update.hasError())?"FAIL":"OK");
|
||||
// send() queues a response until the request body has finished. Keep
|
||||
// an earlier upload error even before it has reached the wire.
|
||||
if (request->isSent() || request->getResponse()) return;
|
||||
auto* state = static_cast<OtaUploadState*>(request->_tempObject);
|
||||
// A file boundary can precede more multipart fields or another file.
|
||||
// Update.end() selects the next boot partition, so commit only after
|
||||
// the whole POST has completed without an earlier upload error.
|
||||
if (state && state->file_final && !state->committed
|
||||
&& ota_upload_owner == request && !Update.hasError()) {
|
||||
if (!Update.end(true)) {
|
||||
return request->send(400, "text/plain", "Could not end OTA");
|
||||
}
|
||||
state->committed = true;
|
||||
}
|
||||
const bool complete = state && state->committed && !Update.hasError();
|
||||
AsyncWebServerResponse *response = request->beginResponse(complete?200:500, "text/plain", complete?"OK":"FAIL");
|
||||
response->addHeader("Connection", "close");
|
||||
response->addHeader("Access-Control-Allow-Origin", "*");
|
||||
request->send(response);
|
||||
restart();
|
||||
}, [&](AsyncWebServerRequest *request, String filename, size_t index, uint8_t *data, size_t len, bool final) {
|
||||
//Upload handler chunks in data
|
||||
if (request->isSent() || request->getResponse()) return;
|
||||
if(_authRequired){
|
||||
if(!request->authenticate(_username.c_str(), _password.c_str())){
|
||||
return request->requestAuthentication();
|
||||
@@ -65,12 +92,26 @@ void AsyncElegantOtaClass::begin(AsyncWebServer *server, const char* username, c
|
||||
}
|
||||
|
||||
if (!index) {
|
||||
if(!request->hasParam("MD5", true)) {
|
||||
return request->send(400, "text/plain", "MD5 parameter missing");
|
||||
if (!ota_uploads_enabled.load()) {
|
||||
return request->send(503, "text/plain", "OTA stopped");
|
||||
}
|
||||
|
||||
if(!Update.setMD5(request->getParam("MD5", true)->value().c_str())) {
|
||||
return request->send(400, "text/plain", "MD5 parameter invalid");
|
||||
bool expected = false;
|
||||
if (request->_tempObject
|
||||
#if defined(ESP32)
|
||||
|| ota_reboot_pending.load()
|
||||
#endif
|
||||
|| !ota_upload_busy.compare_exchange_strong(expected, true)
|
||||
) {
|
||||
return request->send(409, "text/plain", "OTA upload already active");
|
||||
}
|
||||
// A stop can race the first check before ownership was reserved.
|
||||
if (!ota_uploads_enabled.load()) {
|
||||
ota_upload_busy.store(false);
|
||||
return request->send(503, "text/plain", "OTA stopped");
|
||||
}
|
||||
if(!request->hasParam("MD5", true)) {
|
||||
ota_upload_busy.store(false);
|
||||
return request->send(400, "text/plain", "MD5 parameter missing");
|
||||
}
|
||||
|
||||
#if defined(ESP8266)
|
||||
@@ -83,38 +124,100 @@ void AsyncElegantOtaClass::begin(AsyncWebServer *server, const char* username, c
|
||||
int cmd = (filename == "filesystem") ? U_SPIFFS : U_FLASH;
|
||||
if (!Update.begin(UPDATE_SIZE_UNKNOWN, cmd)) { // Start with max available size
|
||||
#endif
|
||||
Update.printError(Serial);
|
||||
ota_upload_busy.store(false);
|
||||
return request->send(400, "text/plain", "OTA could not begin");
|
||||
}
|
||||
// Arduino Update.begin() clears the expected digest. Set it only
|
||||
// after begin succeeds so end() actually verifies the uploaded body.
|
||||
if(!Update.setMD5(request->getParam("MD5", true)->value().c_str())) {
|
||||
Update.abort();
|
||||
ota_upload_busy.store(false);
|
||||
return request->send(400, "text/plain", "MD5 parameter invalid");
|
||||
}
|
||||
|
||||
// The HTTP listener defaults to a three-second receive timeout.
|
||||
// A valid OTA body can pause longer on a weak WiFi link; retain a
|
||||
// bounded timeout on this admitted upload without changing peers.
|
||||
request->client()->setRxTimeout(30);
|
||||
|
||||
// AsyncWebServer frees this per-request allocation on disconnect.
|
||||
// hasError() alone also reports success for a POST with no upload.
|
||||
request->_tempObject = calloc(1, sizeof(OtaUploadState));
|
||||
if (!request->_tempObject) {
|
||||
Update.abort();
|
||||
ota_upload_busy.store(false);
|
||||
return request->send(503, "text/plain", "OTA state allocation failed");
|
||||
}
|
||||
ota_upload_owner = request;
|
||||
request->onDisconnect([this, request]() {
|
||||
if (ota_upload_owner != request) return;
|
||||
ota_upload_owner = nullptr;
|
||||
if (static_cast<OtaUploadState*>(request->_tempObject)->committed) {
|
||||
// Let the response drain and the network callback return
|
||||
// before rebooting from the separate restart task.
|
||||
restart();
|
||||
} else {
|
||||
Update.abort(); // Release a disconnected, partial upload.
|
||||
}
|
||||
ota_upload_busy.store(false);
|
||||
});
|
||||
}
|
||||
|
||||
if (request->isSent() || ota_upload_owner != request) return;
|
||||
|
||||
// Write chunked data to the free sketch space
|
||||
if(len){
|
||||
if (Update.write(data, len) != len) {
|
||||
return request->send(400, "text/plain", "OTA could not begin");
|
||||
return request->send(400, "text/plain", "OTA flash write failed");
|
||||
}
|
||||
}
|
||||
|
||||
if (final) { // if the final flag is set then this is the last frame of data
|
||||
if (!Update.end(true)) { //true to set the size to the current progress
|
||||
Update.printError(Serial);
|
||||
return request->send(400, "text/plain", "Could not end OTA");
|
||||
}
|
||||
}else{
|
||||
return;
|
||||
if (final) {
|
||||
static_cast<OtaUploadState*>(request->_tempObject)->file_final = true;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
bool AsyncElegantOtaClass::setEnabled(bool enabled) {
|
||||
if (enabled) {
|
||||
ota_uploads_enabled.store(true);
|
||||
return true;
|
||||
}
|
||||
const bool was_enabled = ota_uploads_enabled.exchange(false);
|
||||
if (ota_upload_busy.load()
|
||||
#if defined(ESP32)
|
||||
|| ota_reboot_pending.load()
|
||||
#endif
|
||||
) {
|
||||
ota_uploads_enabled.store(was_enabled);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// deprecated, keeping for backward compatibility
|
||||
void AsyncElegantOtaClass::loop() {
|
||||
}
|
||||
|
||||
void AsyncElegantOtaClass::restart() {
|
||||
#if defined(ESP32)
|
||||
bool expected = false;
|
||||
if (!ota_reboot_pending.compare_exchange_strong(expected, true)) return;
|
||||
// Even disconnect handlers run on the shared AsyncTCP task. Return to it
|
||||
// immediately, then reboot from a separate task after final TCP cleanup.
|
||||
if (xTaskCreate([](void*) {
|
||||
vTaskDelay(pdMS_TO_TICKS(1000));
|
||||
ESP.restart();
|
||||
vTaskDelete(nullptr);
|
||||
}, "ota-reboot", 3072, nullptr, 1, nullptr) != pdPASS) {
|
||||
ota_reboot_pending.store(false);
|
||||
}
|
||||
#else
|
||||
yield();
|
||||
delay(1000);
|
||||
yield();
|
||||
ESP.restart();
|
||||
#endif
|
||||
}
|
||||
|
||||
String AsyncElegantOtaClass::getID(){
|
||||
|
||||
@@ -33,6 +33,10 @@ class AsyncElegantOtaClass{
|
||||
loop(),
|
||||
restart();
|
||||
|
||||
// Stop admission atomically with upload ownership. Existing GET
|
||||
// responses may drain while the containing listener is stopped.
|
||||
bool setEnabled(bool enabled);
|
||||
|
||||
private:
|
||||
AsyncWebServer *_server;
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
"core": "esp32",
|
||||
"extra_flags": [
|
||||
"-DBOARD_HAS_PSRAM",
|
||||
"-DARDUINO_USB_MODE=0",
|
||||
"-DARDUINO_USB_MODE=1",
|
||||
"-DARDUINO_USB_CDC_ON_BOOT=1",
|
||||
"-DARDUINO_RUNNING_CORE=1",
|
||||
"-DARDUINO_EVENT_RUNNING_CORE=1"
|
||||
@@ -34,8 +34,8 @@
|
||||
"flash_size": "16MB",
|
||||
"maximum_ram_size": 8388608,
|
||||
"maximum_size": 16777216,
|
||||
"use_1200bps_touch": true,
|
||||
"wait_for_upload_port": true,
|
||||
"use_1200bps_touch": false,
|
||||
"wait_for_upload_port": false,
|
||||
"require_upload_port": true,
|
||||
"speed": 921600
|
||||
},
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
"core": "esp32",
|
||||
"extra_flags": [
|
||||
"-DBOARD_HAS_PSRAM",
|
||||
"-DARDUINO_USB_MODE=0",
|
||||
"-DARDUINO_USB_MODE=1",
|
||||
"-DARDUINO_USB_CDC_ON_BOOT=1",
|
||||
"-DARDUINO_RUNNING_CORE=1",
|
||||
"-DARDUINO_EVENT_RUNNING_CORE=1"
|
||||
@@ -34,8 +34,8 @@
|
||||
"flash_size": "16MB",
|
||||
"maximum_ram_size": 8388608,
|
||||
"maximum_size": 16777216,
|
||||
"use_1200bps_touch": true,
|
||||
"wait_for_upload_port": true,
|
||||
"use_1200bps_touch": false,
|
||||
"wait_for_upload_port": false,
|
||||
"require_upload_port": true,
|
||||
"speed": 921600
|
||||
},
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
"core": "esp32",
|
||||
"extra_flags": [
|
||||
"-DBOARD_HAS_PSRAM",
|
||||
"-DARDUINO_USB_MODE=0",
|
||||
"-DARDUINO_USB_MODE=1",
|
||||
"-DARDUINO_USB_CDC_ON_BOOT=1",
|
||||
"-DARDUINO_RUNNING_CORE=1",
|
||||
"-DARDUINO_EVENT_RUNNING_CORE=1"
|
||||
@@ -34,8 +34,8 @@
|
||||
"flash_size": "16MB",
|
||||
"maximum_ram_size": 8388608,
|
||||
"maximum_size": 16777216,
|
||||
"use_1200bps_touch": true,
|
||||
"wait_for_upload_port": true,
|
||||
"use_1200bps_touch": false,
|
||||
"wait_for_upload_port": false,
|
||||
"require_upload_port": true,
|
||||
"speed": 921600
|
||||
},
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
"core": "esp32",
|
||||
"extra_flags": [
|
||||
"-DARDUINO_USB_CDC_ON_BOOT=1",
|
||||
"-DARDUINO_USB_MODE=0",
|
||||
"-DARDUINO_USB_MODE=1",
|
||||
"-DARDUINO_RUNNING_CORE=1",
|
||||
"-DARDUINO_EVENT_RUNNING_CORE=1"
|
||||
],
|
||||
@@ -30,8 +30,8 @@
|
||||
"flash_size": "8MB",
|
||||
"maximum_ram_size": 327680,
|
||||
"maximum_size": 8388608,
|
||||
"use_1200bps_touch": true,
|
||||
"wait_for_upload_port": true,
|
||||
"use_1200bps_touch": false,
|
||||
"wait_for_upload_port": false,
|
||||
"require_upload_port": true,
|
||||
"speed": 921600
|
||||
},
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
"core": "esp32",
|
||||
"extra_flags": [
|
||||
"-DARDUINO_USB_CDC_ON_BOOT=1",
|
||||
"-DARDUINO_USB_MODE=0",
|
||||
"-DARDUINO_USB_MODE=1",
|
||||
"-DARDUINO_RUNNING_CORE=1",
|
||||
"-DARDUINO_EVENT_RUNNING_CORE=1"
|
||||
],
|
||||
@@ -30,8 +30,8 @@
|
||||
"flash_size": "8MB",
|
||||
"maximum_ram_size": 327680,
|
||||
"maximum_size": 8388608,
|
||||
"use_1200bps_touch": true,
|
||||
"wait_for_upload_port": true,
|
||||
"use_1200bps_touch": false,
|
||||
"wait_for_upload_port": false,
|
||||
"require_upload_port": true,
|
||||
"speed": 921600
|
||||
},
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
"extra_flags": [
|
||||
"-DBOARD_HAS_PSRAM",
|
||||
"-DARDUINO_USB_CDC_ON_BOOT=1",
|
||||
"-DARDUINO_USB_MODE=0",
|
||||
"-DARDUINO_USB_MODE=1",
|
||||
"-DARDUINO_RUNNING_CORE=1",
|
||||
"-DARDUINO_EVENT_RUNNING_CORE=0"
|
||||
],
|
||||
@@ -33,8 +33,8 @@
|
||||
"flash_size": "16MB",
|
||||
"maximum_ram_size": 327680,
|
||||
"maximum_size": 16777216,
|
||||
"use_1200bps_touch": true,
|
||||
"wait_for_upload_port": true,
|
||||
"use_1200bps_touch": false,
|
||||
"wait_for_upload_port": false,
|
||||
"require_upload_port": true,
|
||||
"speed": 921600
|
||||
},
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
"extra_flags": [
|
||||
"-DBOARD_HAS_PSRAM",
|
||||
"-DARDUINO_USB_CDC_ON_BOOT=1",
|
||||
"-DARDUINO_USB_MODE=0",
|
||||
"-DARDUINO_USB_MODE=1",
|
||||
"-DARDUINO_RUNNING_CORE=1",
|
||||
"-DARDUINO_EVENT_RUNNING_CORE=0"
|
||||
],
|
||||
@@ -33,8 +33,8 @@
|
||||
"flash_size": "16MB",
|
||||
"maximum_ram_size": 327680,
|
||||
"maximum_size": 16777216,
|
||||
"use_1200bps_touch": true,
|
||||
"wait_for_upload_port": true,
|
||||
"use_1200bps_touch": false,
|
||||
"wait_for_upload_port": false,
|
||||
"require_upload_port": true,
|
||||
"speed": 921600
|
||||
},
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
"-DBOARD_HAS_PSRAM",
|
||||
"-DLILYGO_TBEAM_1W",
|
||||
"-DARDUINO_USB_CDC_ON_BOOT=1",
|
||||
"-DARDUINO_USB_MODE=0",
|
||||
"-DARDUINO_USB_MODE=1",
|
||||
"-DARDUINO_RUNNING_CORE=1",
|
||||
"-DARDUINO_EVENT_RUNNING_CORE=1"
|
||||
],
|
||||
@@ -40,6 +40,8 @@
|
||||
],
|
||||
"name": "LilyGo TBeam-1W",
|
||||
"upload": {
|
||||
"use_1200bps_touch": false,
|
||||
"wait_for_upload_port": false,
|
||||
"flash_size": "16MB",
|
||||
"maximum_ram_size": 327680,
|
||||
"maximum_size": 16777216,
|
||||
|
||||
+25
-3
@@ -1,10 +1,10 @@
|
||||
#!/usr/bin/env bash
|
||||
# ./build-iotthinks.sh
|
||||
export FIRMWARE_VERSION="PowerSaving17.1.2"
|
||||
export FIRMWARE_VERSION="PowerSaving17.1.5"
|
||||
|
||||
############# Repeaters #############
|
||||
# Commonly-used boards
|
||||
## ESP32 - 21 boards
|
||||
## ESP32 - 22 boards
|
||||
./build.sh build-firmware \
|
||||
Heltec_ct62_repeater \
|
||||
Heltec_E290_repeater \
|
||||
@@ -22,6 +22,7 @@ LilyGo_TDeck_repeater \
|
||||
Station_G2_repeater \
|
||||
T_Beam_S3_Supreme_SX1262_repeater \
|
||||
Tbeam_SX1262_repeater \
|
||||
ThinkNode_M2_Repeater \
|
||||
ThinkNode_M5_Repeater \
|
||||
Xiao_C3_repeater \
|
||||
Xiao_C6_repeater_ \
|
||||
@@ -91,6 +92,7 @@ Heltec_t096_companion_radio_ble_femoff \
|
||||
Heltec_t1_companion_radio_ble \
|
||||
Heltec_t114_companion_radio_ble \
|
||||
MKE_s3_companion_radio_ble \
|
||||
Mesh_pocket_companion_radio_ble \
|
||||
LilyGo_T-Echo_Card_companion_radio_ble \
|
||||
LilyGo_T-Echo_companion_radio_ble \
|
||||
LilyGo_T-Echo-Lite_companion_radio_ble \
|
||||
@@ -146,6 +148,9 @@ heltec_v4_companion_radio_usb_ps_femoff \
|
||||
heltec_v4_companion_radio_usb_ps_femon \
|
||||
heltec_tracker_v2_companion_radio_usb_femoff \
|
||||
heltec_tracker_v2_companion_radio_usb_femon \
|
||||
Heltec_v3_companion_radio_usb \
|
||||
Mesh_pocket_companion_radio_usb \
|
||||
ThinkNode_M2_companion_radio_usb \
|
||||
MKE_s3_companion_radio_usb \
|
||||
LilyGo_TBeam_1W_companion_radio_usb \
|
||||
LilyGo_TDeck_companion_radio_usb \
|
||||
@@ -156,11 +161,16 @@ Xiao_S3_WIO_companion_radio_usb
|
||||
|
||||
############# Sensors #############
|
||||
./build.sh build-firmware \
|
||||
Heltec_t096_sensor \
|
||||
Heltec_t114_sensor \
|
||||
t1000e_sensor
|
||||
|
||||
############# Bridge #############
|
||||
./build.sh build-firmware \
|
||||
Xiao_nrf52_repeater_bridge_rs232
|
||||
|
||||
############# Sample builds #############
|
||||
# 27 boards
|
||||
# 28 boards
|
||||
./build.sh build-firmware \
|
||||
Heltec_t096_companion_radio_ble_femon \
|
||||
Heltec_t096_companion_radio_ble_femoff \
|
||||
@@ -175,6 +185,7 @@ heltec_v4_3_companion_radio_ble_ps_femoff \
|
||||
heltec_v4_companion_radio_ble_ps_femon \
|
||||
heltec_v4_repeater \
|
||||
heltec_v4_r8_companion_radio_ble_ps \
|
||||
ProMicro_repeater \
|
||||
RAK_3401_companion_radio_ble \
|
||||
RAK_3401_repeater \
|
||||
RAK_4631_companion_radio_ble \
|
||||
@@ -190,6 +201,17 @@ Xiao_C6_repeater_ \
|
||||
Xiao_nrf52_companion_radio_ble \
|
||||
Xiao_nrf52_repeater
|
||||
|
||||
# Upstream application targets pending feedback. These are not evidence of
|
||||
# factory bootloader compatibility; OTAFIX qualification remains independent.
|
||||
./build.sh build-firmware \
|
||||
wio_wm1110_repeater \
|
||||
wio_wm1110_room_server \
|
||||
wio_wm1110_companion_radio_ble
|
||||
|
||||
./build.sh build-firmware \
|
||||
meshnology_w12_repeater \
|
||||
meshnology_w12_companion_radio_ble
|
||||
|
||||
# Heltec v4 USB power-saving variants
|
||||
./build.sh build-firmware \
|
||||
heltec_v4_companion_radio_ble_ps_femon \
|
||||
|
||||
+793
-152
File diff suppressed because it is too large.
Load diff
+82
-9
@@ -1,3 +1,7 @@
|
||||
<!-- meshcore-hosted-doc-link:start -->
|
||||
<p class="meshcore-hosted-doc-link"><a href="https://mikecarper.github.io/MeshCore/WiFi/">View this page on MeshCore Docs</a>.</p>
|
||||
<!-- meshcore-hosted-doc-link:end -->
|
||||
|
||||
# WiFi and MQTT by Firmware Type
|
||||
|
||||
MeshCore itself does not require WiFi or the internet. LoRa packet exchange,
|
||||
@@ -98,6 +102,32 @@ best effort rather than durable storage. Each enabled slot publishes
|
||||
independently, so one failed broker does not intentionally stop the other
|
||||
slots.
|
||||
|
||||
## Automatic network outage power saving
|
||||
|
||||
ESP32 MQTT firmware uses one shared outage timer for MQTT and NTP. If no broker
|
||||
demonstrates a working connection for five minutes, the MQTT worker stops its
|
||||
clients and NTP request before suspending background WiFi. Saved WiFi and broker
|
||||
settings remain enabled. Failed requests, a WiFi association, and NTP replies
|
||||
alone do not restart the outage timer or keep a failed MQTT uplink awake.
|
||||
|
||||
After 30 minutes, the firmware opens a recovery window lasting up to two
|
||||
minutes. A broker acknowledgment or a connection that stays up for 30 seconds
|
||||
returns the node to normal operation. Otherwise WiFi is suspended for another
|
||||
30 minutes. An explicit management request can ask for an earlier probe.
|
||||
Automatic NTP requests have a 15-second timeout; failed requests wait 30 minutes
|
||||
instead of repeatedly running a blocking server search. Successful NTP retains
|
||||
the daily refresh schedule.
|
||||
|
||||
Active browser administration, setup/OTA access points, connected Companion
|
||||
clients, and an attached WiFi OTA seeder protect their network access. An idle
|
||||
WebConfig listener does not prevent the cooldown. An enabled SNMP agent keeps a
|
||||
working LAN connection available. ESP-NOW keeps its radio and channel; only the
|
||||
failed infrastructure connection is suspended. LoRa continues operating, and
|
||||
when CPU power saving is enabled, its normal sleep eligibility returns after
|
||||
background WiFi shuts down. USB console and logging sleep rules still apply
|
||||
separately. This policy does not automatically turn off working services or
|
||||
expire an active setup access point.
|
||||
|
||||
See [MQTT_IMPLEMENTATION.md](https://github.com/mikecarper/MeshCore/blob/keymindCascade/MQTT_IMPLEMENTATION.md) for the complete preset
|
||||
list, custom broker configuration, topic formats, authentication, diagnostics,
|
||||
and memory limits.
|
||||
@@ -159,8 +189,35 @@ reboot
|
||||
```
|
||||
|
||||
The SSID and password values are the rest of the command line. Spaces are
|
||||
allowed and quotes must not be added. SSIDs may contain at most 31 characters
|
||||
and passwords at most 63. Leave the password value empty for an open network.
|
||||
allowed and quotes must not be added. SSIDs may contain at most 31 characters.
|
||||
ESP32 infrastructure MQTT builds with WebConfig share one canonical WiFi
|
||||
configuration between this CLI and the browser portal. Passwords accept
|
||||
0-63 characters or exactly 64 hexadecimal characters for a raw WPA PSK.
|
||||
Leave the password value empty for an open network. Reboot after changing
|
||||
credentials to apply them; an already-running MQTT worker keeps its current
|
||||
connection settings until it stops and restarts.
|
||||
|
||||
Older observer-only credentials remain usable until canonical WiFi settings
|
||||
are saved. The first CLI edit preserves the other legacy credential while
|
||||
saving the complete pair. Once canonical settings exist, empty, incomplete or
|
||||
unreadable canonical settings do not revive old observer credentials. Historical
|
||||
SSID-only canonical settings continue to represent an open network. Builds
|
||||
without this WebConfig integration retain their legacy 63-character password
|
||||
limit; this change does not add MQTT support to currently disabled Pico targets.
|
||||
|
||||
On these ESP32 MQTT/WebConfig builds, an empty CLI SSID clears the configured
|
||||
network while retaining saved broker settings:
|
||||
|
||||
```text
|
||||
set wifi.ssid
|
||||
reboot
|
||||
```
|
||||
|
||||
Use this to return the node to unconfigured provisioning. The WebConfig setup
|
||||
form still requires a nonempty SSID. After saving a new network, verify
|
||||
`get wifi.status`, `get mqtt.config.valid` and `get mqtt.running`; credentials
|
||||
alone do not prove a broker connection. A local `get wifi.pwd` can inspect the
|
||||
password, while an authenticated remote command returns only set/unset.
|
||||
|
||||
Useful checks are:
|
||||
|
||||
@@ -192,8 +249,9 @@ set mqtt.enabled off
|
||||
start webconfig ap
|
||||
```
|
||||
|
||||
After provisioning, use **Save & Reboot**, or stop the temporary portal and
|
||||
restart the bridge:
|
||||
After changing WiFi credentials, use **Save & Reboot** or `reboot`. For
|
||||
broker-only changes that keep the existing network, stop the temporary portal
|
||||
and restart the bridge:
|
||||
|
||||
```text
|
||||
stop webconfig
|
||||
@@ -307,6 +365,15 @@ modem sleep can miss ESP-NOW broadcasts; use `min` for coexistence. The primary
|
||||
mesh transport holds the driver's RF wake reference continuously, so `min`
|
||||
does not suspend its ESP-NOW receiver.
|
||||
|
||||
Espressif long-range WiFi (LR) trades speed for reach: its raw rates are
|
||||
250 or 500 kbit/s, with up to about 1 km in suitable line-of-sight conditions.
|
||||
LR traffic needs compatible Espressif hardware at both ends. An LR-enabled
|
||||
access point sends LR beacons that ordinary phones, routers and Raspberry Pis
|
||||
cannot receive. A mixed b/g/n+LR station can still join an ordinary router,
|
||||
using normal WiFi rates for that connection. MeshCore's setup and OTA access
|
||||
points therefore use ordinary b/g/n. See
|
||||
[Espressif's LR compatibility guide](https://docs.espressif.com/projects/esp-idf/en/v4.4.7/esp32s3/api-guides/wifi.html#long-range-lr).
|
||||
|
||||
ESP-NOW compatibility also depends on the bytes and PHY used above the common
|
||||
Espressif transport. The historical `*_repeater_bridge_espnow` firmware wraps
|
||||
each MeshCore packet with a magic value, checksum, and secret-based XOR, while
|
||||
@@ -423,6 +490,11 @@ direct browser uploader on HTTP port 80. A FULL role without WebConfig but with
|
||||
browser OTA support can use the `MeshCore-OTA` access point raised by
|
||||
`start ota`; its seeder address is `192.168.4.1:5001`.
|
||||
|
||||
For browser uploads from an existing 1.17.1.8 ESP32 node, see the
|
||||
[1.17.1.9 WiFi upgrade guide](upgrading_1.17.1.8_to_1.17.1.9.md).
|
||||
It covers the node AP, old-page completion behavior, and choosing an
|
||||
application-only image or the exact partition-migration package.
|
||||
|
||||
Only one external folder link can be active. A TCP client is rejected while
|
||||
`ota folder on` is using USB serial, and disconnecting `motatool` automatically
|
||||
removes the TCP folder. Port 5001 has no login layer, so expose it only on a
|
||||
@@ -675,11 +747,12 @@ Common causes are:
|
||||
For a WiFi companion, find its station IP in the router, connect the client to
|
||||
TCP port 5000, and use the open `MeshCore-Setup-XXXX` AP at
|
||||
`http://192.168.4.1/` if it cannot join the saved network. Current firmware
|
||||
normalizes both ESP32 WiFi interfaces to standard b/g/n before advertising the
|
||||
setup AP. Full Companion targets whose primary mesh radio is ESP-NOW use
|
||||
b/g/n+LR instead and keep the setup AP, infrastructure station, and mesh on the
|
||||
persisted `espnow.channel` (channel 1 by default). Their configured 2.4 GHz
|
||||
router and every other primary ESP-NOW node must use that same channel.
|
||||
uses standard b/g/n for the setup AP, including ESP-NOW-capable targets.
|
||||
A running LR mesh conflicts with a conventional access point; current firmware
|
||||
refuses that AP startup and defers LR startup while an AP is active. A primary
|
||||
ESP-NOW target keeps its infrastructure station and mesh on the persisted
|
||||
`espnow.channel` (channel 1 by default). Its configured 2.4 GHz router and every
|
||||
other primary ESP-NOW node must use that same channel.
|
||||
MQTT-capable Companions also accept `get mqtt.enabled`, `get mqtt.running`,
|
||||
and `get mqtt.status` in their USB/TCP and browser terminals. Extended slot
|
||||
and NTP diagnostics remain infrastructure features.
|
||||
+261
-275
@@ -1,9 +1,9 @@
|
||||
{
|
||||
"schemaVersion": 1,
|
||||
"repository": "mikecarper/Adafruit_nRF52_Bootloader_OTAFIX",
|
||||
"tag": "v0.11.0-OTAFIX2.4.10",
|
||||
"version": "2.4.10",
|
||||
"releaseUrl": "https://github.com/mikecarper/Adafruit_nRF52_Bootloader_OTAFIX/releases/tag/v0.11.0-OTAFIX2.4.10",
|
||||
"tag": "v0.11.0-OTAFIX2.4.11",
|
||||
"version": "2.4.11",
|
||||
"releaseUrl": "https://github.com/mikecarper/Adafruit_nRF52_Bootloader_OTAFIX/releases/tag/v0.11.0-OTAFIX2.4.11",
|
||||
"prerelease": false,
|
||||
"profiles": [
|
||||
{
|
||||
@@ -18,22 +18,22 @@
|
||||
"storage": "internal",
|
||||
"files": {
|
||||
"uf2": {
|
||||
"name": "update-gat562_bootloader-v0.11.0-OTAFIX2.4.10_mbr.uf2",
|
||||
"url": "https://github.com/mikecarper/Adafruit_nRF52_Bootloader_OTAFIX/releases/download/v0.11.0-OTAFIX2.4.10/update-gat562_bootloader-v0.11.0-OTAFIX2.4.10_mbr.uf2",
|
||||
"name": "update-gat562_bootloader-v0.11.0-OTAFIX2.4.11_mbr.uf2",
|
||||
"url": "https://github.com/mikecarper/Adafruit_nRF52_Bootloader_OTAFIX/releases/download/v0.11.0-OTAFIX2.4.11/update-gat562_bootloader-v0.11.0-OTAFIX2.4.11_mbr.uf2",
|
||||
"size": 84992,
|
||||
"sha256": "4bbe3a8fa3951a67fa8c1c6a910744af6370d9a666eb7b88b6b760c5139f6c32"
|
||||
"sha256": "2e3c5292abd9f87380e63926a01dc0cb437002d33acff8de2919d9cd79273927"
|
||||
},
|
||||
"zip": {
|
||||
"name": "gat562_bootloader-v0.11.0-OTAFIX2.4.10_s140_6.1.1.zip",
|
||||
"url": "https://github.com/mikecarper/Adafruit_nRF52_Bootloader_OTAFIX/releases/download/v0.11.0-OTAFIX2.4.10/gat562_bootloader-v0.11.0-OTAFIX2.4.10_s140_6.1.1.zip",
|
||||
"size": 192833,
|
||||
"sha256": "fe8346cd8d066caf4b4ee6a4c8605677e6457f022670d0fe270e021c98c5a1d4"
|
||||
"name": "gat562_bootloader-v0.11.0-OTAFIX2.4.11_s140_6.1.1.zip",
|
||||
"url": "https://github.com/mikecarper/Adafruit_nRF52_Bootloader_OTAFIX/releases/download/v0.11.0-OTAFIX2.4.11/gat562_bootloader-v0.11.0-OTAFIX2.4.11_s140_6.1.1.zip",
|
||||
"size": 192834,
|
||||
"sha256": "35bbb66831af4c1aaba1ae139f2c7d149a1b281c28aff3dac31a78f67ac026b0"
|
||||
},
|
||||
"hex": {
|
||||
"name": "gat562_bootloader-v0.11.0-OTAFIX2.4.10_s140_6.1.1.hex",
|
||||
"url": "https://github.com/mikecarper/Adafruit_nRF52_Bootloader_OTAFIX/releases/download/v0.11.0-OTAFIX2.4.10/gat562_bootloader-v0.11.0-OTAFIX2.4.10_s140_6.1.1.hex",
|
||||
"size": 530672,
|
||||
"sha256": "fa22eefe6c5a5eecf8c89abc2a195a3ccbe3bf28b7fb32d8b3c966489b92588b"
|
||||
"name": "gat562_bootloader-v0.11.0-OTAFIX2.4.11_s140_6.1.1.hex",
|
||||
"url": "https://github.com/mikecarper/Adafruit_nRF52_Bootloader_OTAFIX/releases/download/v0.11.0-OTAFIX2.4.11/gat562_bootloader-v0.11.0-OTAFIX2.4.11_s140_6.1.1.hex",
|
||||
"size": 530716,
|
||||
"sha256": "8df22a6951916f0eee1910cf8d449eb32fa712799ad12713045e06f93e355f82"
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -47,57 +47,28 @@
|
||||
"storage": "internal",
|
||||
"files": {
|
||||
"uf2": {
|
||||
"name": "update-heltec_mesh_pocket_bootloader-v0.11.0-OTAFIX2.4.10_mbr.uf2",
|
||||
"url": "https://github.com/mikecarper/Adafruit_nRF52_Bootloader_OTAFIX/releases/download/v0.11.0-OTAFIX2.4.10/update-heltec_mesh_pocket_bootloader-v0.11.0-OTAFIX2.4.10_mbr.uf2",
|
||||
"name": "update-heltec_mesh_pocket_bootloader-v0.11.0-OTAFIX2.4.11_mbr.uf2",
|
||||
"url": "https://github.com/mikecarper/Adafruit_nRF52_Bootloader_OTAFIX/releases/download/v0.11.0-OTAFIX2.4.11/update-heltec_mesh_pocket_bootloader-v0.11.0-OTAFIX2.4.11_mbr.uf2",
|
||||
"size": 84480,
|
||||
"sha256": "594506767173bb83ef773b69464fe2fd94ff71d7b4c1318c2fee2bdb04cd1dd2"
|
||||
"sha256": "3087bdd540db6a6399120a23012dc9d1e45ca9b72938abd49c389319f1a027db"
|
||||
},
|
||||
"zip": {
|
||||
"name": "heltec_mesh_pocket_bootloader-v0.11.0-OTAFIX2.4.10_s140_6.1.1.zip",
|
||||
"url": "https://github.com/mikecarper/Adafruit_nRF52_Bootloader_OTAFIX/releases/download/v0.11.0-OTAFIX2.4.10/heltec_mesh_pocket_bootloader-v0.11.0-OTAFIX2.4.10_s140_6.1.1.zip",
|
||||
"name": "heltec_mesh_pocket_bootloader-v0.11.0-OTAFIX2.4.11_s140_6.1.1.zip",
|
||||
"url": "https://github.com/mikecarper/Adafruit_nRF52_Bootloader_OTAFIX/releases/download/v0.11.0-OTAFIX2.4.11/heltec_mesh_pocket_bootloader-v0.11.0-OTAFIX2.4.11_s140_6.1.1.zip",
|
||||
"size": 192834,
|
||||
"sha256": "47bf7082e0a021c68c9c479d0effdc0f38fa150d9c49012922cad54c143240d1"
|
||||
"sha256": "7251ed15ce18dba03151b676d7401f50a043a61370e6eb7230daddfa9248dda4"
|
||||
},
|
||||
"hex": {
|
||||
"name": "heltec_mesh_pocket_bootloader-v0.11.0-OTAFIX2.4.10_s140_6.1.1.hex",
|
||||
"url": "https://github.com/mikecarper/Adafruit_nRF52_Bootloader_OTAFIX/releases/download/v0.11.0-OTAFIX2.4.10/heltec_mesh_pocket_bootloader-v0.11.0-OTAFIX2.4.10_s140_6.1.1.hex",
|
||||
"size": 530068,
|
||||
"sha256": "5bb3041b17d25bf45fffc51316fac350497102aaa6363a4f04dcde31d5ee8614"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "heltec_mesh_tower_v2",
|
||||
"label": "Heltec MeshTower V2 - internal storage",
|
||||
"meshcoreHardware": [
|
||||
"Heltec_tower_v2"
|
||||
],
|
||||
"deviceName": "TOWER_V2_OTA",
|
||||
"storage": "internal",
|
||||
"files": {
|
||||
"uf2": {
|
||||
"name": "update-heltec_mesh_tower_v2_bootloader-v0.11.0-OTAFIX2.4.10_mbr.uf2",
|
||||
"url": "https://github.com/mikecarper/Adafruit_nRF52_Bootloader_OTAFIX/releases/download/v0.11.0-OTAFIX2.4.10/update-heltec_mesh_tower_v2_bootloader-v0.11.0-OTAFIX2.4.10_mbr.uf2",
|
||||
"size": 84480,
|
||||
"sha256": "543766e38f347ce8c7bdeff6050fcd8d2c93e62c2a0510b318ac4e3c31f38552"
|
||||
},
|
||||
"zip": {
|
||||
"name": "heltec_mesh_tower_v2_bootloader-v0.11.0-OTAFIX2.4.10_s140_6.1.1.zip",
|
||||
"url": "https://github.com/mikecarper/Adafruit_nRF52_Bootloader_OTAFIX/releases/download/v0.11.0-OTAFIX2.4.10/heltec_mesh_tower_v2_bootloader-v0.11.0-OTAFIX2.4.10_s140_6.1.1.zip",
|
||||
"size": 192834,
|
||||
"sha256": "aa4558d5ad651bf820f73e8256d0130aa6d41bdb3a54b73297253c0a986573f0"
|
||||
},
|
||||
"hex": {
|
||||
"name": "heltec_mesh_tower_v2_bootloader-v0.11.0-OTAFIX2.4.10_s140_6.1.1.hex",
|
||||
"url": "https://github.com/mikecarper/Adafruit_nRF52_Bootloader_OTAFIX/releases/download/v0.11.0-OTAFIX2.4.10/heltec_mesh_tower_v2_bootloader-v0.11.0-OTAFIX2.4.10_s140_6.1.1.hex",
|
||||
"size": 530462,
|
||||
"sha256": "a1371c6dc1c22648dd8625c93f9789f65e5b0123b0cb8d4ca6bfd9c51bc51a4a"
|
||||
"name": "heltec_mesh_pocket_bootloader-v0.11.0-OTAFIX2.4.11_s140_6.1.1.hex",
|
||||
"url": "https://github.com/mikecarper/Adafruit_nRF52_Bootloader_OTAFIX/releases/download/v0.11.0-OTAFIX2.4.11/heltec_mesh_pocket_bootloader-v0.11.0-OTAFIX2.4.11_s140_6.1.1.hex",
|
||||
"size": 530112,
|
||||
"sha256": "f0b836e53af97d5445236ef89adb8443e5c8a4691e71488ff930cca2b3a57d90"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "heltec_mesh_tower_v2_sdcard",
|
||||
"label": "Heltec MeshTower V2 - microSD",
|
||||
"label": "Heltec MeshTower V2 - microSD + internal (SD primary)",
|
||||
"meshcoreHardware": [
|
||||
"Heltec_tower_v2_sdcard"
|
||||
],
|
||||
@@ -105,22 +76,22 @@
|
||||
"storage": "sd",
|
||||
"files": {
|
||||
"uf2": {
|
||||
"name": "update-heltec_mesh_tower_v2_sdcard_bootloader-v0.11.0-OTAFIX2.4.10_mbr.uf2",
|
||||
"url": "https://github.com/mikecarper/Adafruit_nRF52_Bootloader_OTAFIX/releases/download/v0.11.0-OTAFIX2.4.10/update-heltec_mesh_tower_v2_sdcard_bootloader-v0.11.0-OTAFIX2.4.10_mbr.uf2",
|
||||
"size": 86528,
|
||||
"sha256": "69ca460d976b778f3465be10ed11ed1abcd1fbe0bca8ce85b5fc2a5620f3e7a8"
|
||||
"name": "update-heltec_mesh_tower_v2_sdcard_bootloader-v0.11.0-OTAFIX2.4.11_mbr.uf2",
|
||||
"url": "https://github.com/mikecarper/Adafruit_nRF52_Bootloader_OTAFIX/releases/download/v0.11.0-OTAFIX2.4.11/update-heltec_mesh_tower_v2_sdcard_bootloader-v0.11.0-OTAFIX2.4.11_mbr.uf2",
|
||||
"size": 88064,
|
||||
"sha256": "26e8cc90bdf3a138bd8f5471f17c04cbeaddf5dfb01bf62601917f7d0c8fa825"
|
||||
},
|
||||
"zip": {
|
||||
"name": "heltec_mesh_tower_v2_sdcard_bootloader-v0.11.0-OTAFIX2.4.10_s140_6.1.1.zip",
|
||||
"url": "https://github.com/mikecarper/Adafruit_nRF52_Bootloader_OTAFIX/releases/download/v0.11.0-OTAFIX2.4.10/heltec_mesh_tower_v2_sdcard_bootloader-v0.11.0-OTAFIX2.4.10_s140_6.1.1.zip",
|
||||
"size": 192834,
|
||||
"sha256": "d967469e8c10885b00b235d3d2b451029699b2b41b6882d0011120667036ed8b"
|
||||
"name": "heltec_mesh_tower_v2_sdcard_bootloader-v0.11.0-OTAFIX2.4.11_s140_6.1.1.zip",
|
||||
"url": "https://github.com/mikecarper/Adafruit_nRF52_Bootloader_OTAFIX/releases/download/v0.11.0-OTAFIX2.4.11/heltec_mesh_tower_v2_sdcard_bootloader-v0.11.0-OTAFIX2.4.11_s140_6.1.1.zip",
|
||||
"size": 192833,
|
||||
"sha256": "ac5fdfa777ba583cc9e62e7cd1fef3cf0a3b97da5b221a332380b2209d31b79c"
|
||||
},
|
||||
"hex": {
|
||||
"name": "heltec_mesh_tower_v2_sdcard_bootloader-v0.11.0-OTAFIX2.4.10_s140_6.1.1.hex",
|
||||
"url": "https://github.com/mikecarper/Adafruit_nRF52_Bootloader_OTAFIX/releases/download/v0.11.0-OTAFIX2.4.10/heltec_mesh_tower_v2_sdcard_bootloader-v0.11.0-OTAFIX2.4.10_s140_6.1.1.hex",
|
||||
"size": 532874,
|
||||
"sha256": "5c491b436f34cb164c17195083309f375f84628ccb0c1f8ef166ce6d9ad5d2e1"
|
||||
"name": "heltec_mesh_tower_v2_sdcard_bootloader-v0.11.0-OTAFIX2.4.11_s140_6.1.1.hex",
|
||||
"url": "https://github.com/mikecarper/Adafruit_nRF52_Bootloader_OTAFIX/releases/download/v0.11.0-OTAFIX2.4.11/heltec_mesh_tower_v2_sdcard_bootloader-v0.11.0-OTAFIX2.4.11_s140_6.1.1.hex",
|
||||
"size": 535746,
|
||||
"sha256": "5980d8f96a3b9dbb55bbf488cbeacdcae1c6f59ff78c2766254c8b95c5e6beab"
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -134,22 +105,22 @@
|
||||
"storage": "internal",
|
||||
"files": {
|
||||
"uf2": {
|
||||
"name": "update-heltec_t096_bootloader-v0.11.0-OTAFIX2.4.10_mbr.uf2",
|
||||
"url": "https://github.com/mikecarper/Adafruit_nRF52_Bootloader_OTAFIX/releases/download/v0.11.0-OTAFIX2.4.10/update-heltec_t096_bootloader-v0.11.0-OTAFIX2.4.10_mbr.uf2",
|
||||
"name": "update-heltec_t096_bootloader-v0.11.0-OTAFIX2.4.11_mbr.uf2",
|
||||
"url": "https://github.com/mikecarper/Adafruit_nRF52_Bootloader_OTAFIX/releases/download/v0.11.0-OTAFIX2.4.11/update-heltec_t096_bootloader-v0.11.0-OTAFIX2.4.11_mbr.uf2",
|
||||
"size": 87552,
|
||||
"sha256": "d3f01cb890f21ca9f93fd2ce5d7dfc722dd0eb15069ba05b06bf1440b876f7b4"
|
||||
"sha256": "a7db3f390ab8934a33ccc1f60ac0acf1b5200f58fc6c037e233a178d5d416da8"
|
||||
},
|
||||
"zip": {
|
||||
"name": "heltec_t096_bootloader-v0.11.0-OTAFIX2.4.10_s140_6.1.1.zip",
|
||||
"url": "https://github.com/mikecarper/Adafruit_nRF52_Bootloader_OTAFIX/releases/download/v0.11.0-OTAFIX2.4.10/heltec_t096_bootloader-v0.11.0-OTAFIX2.4.10_s140_6.1.1.zip",
|
||||
"size": 192833,
|
||||
"sha256": "88780871f385463258e3b3447b41baf8158f651ae5554e952f958a9bb488f3b5"
|
||||
"name": "heltec_t096_bootloader-v0.11.0-OTAFIX2.4.11_s140_6.1.1.zip",
|
||||
"url": "https://github.com/mikecarper/Adafruit_nRF52_Bootloader_OTAFIX/releases/download/v0.11.0-OTAFIX2.4.11/heltec_t096_bootloader-v0.11.0-OTAFIX2.4.11_s140_6.1.1.zip",
|
||||
"size": 192834,
|
||||
"sha256": "4c2f1adfa28ad8a4a856c7c720ad1dbcb8cf13fcdc381152761bd72fadba227c"
|
||||
},
|
||||
"hex": {
|
||||
"name": "heltec_t096_bootloader-v0.11.0-OTAFIX2.4.10_s140_6.1.1.hex",
|
||||
"url": "https://github.com/mikecarper/Adafruit_nRF52_Bootloader_OTAFIX/releases/download/v0.11.0-OTAFIX2.4.10/heltec_t096_bootloader-v0.11.0-OTAFIX2.4.10_s140_6.1.1.hex",
|
||||
"size": 534426,
|
||||
"sha256": "7a793922c1ab66b6b93b308a57f400aa2820adfd9daf8b379093ba6f39dbd2ec"
|
||||
"name": "heltec_t096_bootloader-v0.11.0-OTAFIX2.4.11_s140_6.1.1.hex",
|
||||
"url": "https://github.com/mikecarper/Adafruit_nRF52_Bootloader_OTAFIX/releases/download/v0.11.0-OTAFIX2.4.11/heltec_t096_bootloader-v0.11.0-OTAFIX2.4.11_s140_6.1.1.hex",
|
||||
"size": 534470,
|
||||
"sha256": "878071c4e7bc16a9ec664773d34eadb7be6846e8791bcc87270ba5e41b463e2b"
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -163,22 +134,22 @@
|
||||
"storage": "internal",
|
||||
"files": {
|
||||
"uf2": {
|
||||
"name": "update-heltec_t1_bootloader-v0.11.0-OTAFIX2.4.10_mbr.uf2",
|
||||
"url": "https://github.com/mikecarper/Adafruit_nRF52_Bootloader_OTAFIX/releases/download/v0.11.0-OTAFIX2.4.10/update-heltec_t1_bootloader-v0.11.0-OTAFIX2.4.10_mbr.uf2",
|
||||
"name": "update-heltec_t1_bootloader-v0.11.0-OTAFIX2.4.11_mbr.uf2",
|
||||
"url": "https://github.com/mikecarper/Adafruit_nRF52_Bootloader_OTAFIX/releases/download/v0.11.0-OTAFIX2.4.11/update-heltec_t1_bootloader-v0.11.0-OTAFIX2.4.11_mbr.uf2",
|
||||
"size": 87552,
|
||||
"sha256": "c39f8878bbbdb8df323bb77526088a7ff711b5b532a8e965b22fa9cd502a97a1"
|
||||
"sha256": "3a053de7ba28e63756cd52d0e5632ce07bd244eeb271720f1178e71178680d77"
|
||||
},
|
||||
"zip": {
|
||||
"name": "heltec_t1_bootloader-v0.11.0-OTAFIX2.4.10_s140_6.1.1.zip",
|
||||
"url": "https://github.com/mikecarper/Adafruit_nRF52_Bootloader_OTAFIX/releases/download/v0.11.0-OTAFIX2.4.10/heltec_t1_bootloader-v0.11.0-OTAFIX2.4.10_s140_6.1.1.zip",
|
||||
"size": 192833,
|
||||
"sha256": "f3b876332448098cf68cb9c475729bd2f87e3cdfa71676bc362d7fd900abebbb"
|
||||
"name": "heltec_t1_bootloader-v0.11.0-OTAFIX2.4.11_s140_6.1.1.zip",
|
||||
"url": "https://github.com/mikecarper/Adafruit_nRF52_Bootloader_OTAFIX/releases/download/v0.11.0-OTAFIX2.4.11/heltec_t1_bootloader-v0.11.0-OTAFIX2.4.11_s140_6.1.1.zip",
|
||||
"size": 192834,
|
||||
"sha256": "b7144a4493a23902b07e31435f8c3758afe0b5d87f777236705865f51bb00ecb"
|
||||
},
|
||||
"hex": {
|
||||
"name": "heltec_t1_bootloader-v0.11.0-OTAFIX2.4.10_s140_6.1.1.hex",
|
||||
"url": "https://github.com/mikecarper/Adafruit_nRF52_Bootloader_OTAFIX/releases/download/v0.11.0-OTAFIX2.4.10/heltec_t1_bootloader-v0.11.0-OTAFIX2.4.10_s140_6.1.1.hex",
|
||||
"size": 534464,
|
||||
"sha256": "22c8701030be8ea3fa68612a99fe40b08e2ddae085768c87f82ad562dc2777ed"
|
||||
"name": "heltec_t1_bootloader-v0.11.0-OTAFIX2.4.11_s140_6.1.1.hex",
|
||||
"url": "https://github.com/mikecarper/Adafruit_nRF52_Bootloader_OTAFIX/releases/download/v0.11.0-OTAFIX2.4.11/heltec_t1_bootloader-v0.11.0-OTAFIX2.4.11_s140_6.1.1.hex",
|
||||
"size": 534508,
|
||||
"sha256": "8ba4ddc312f2246e5ace749e5734934ab5537c4ef60354a34658eebfa4d50449"
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -193,22 +164,22 @@
|
||||
"storage": "internal",
|
||||
"files": {
|
||||
"uf2": {
|
||||
"name": "update-heltec_t114_bootloader-v0.11.0-OTAFIX2.4.10_mbr.uf2",
|
||||
"url": "https://github.com/mikecarper/Adafruit_nRF52_Bootloader_OTAFIX/releases/download/v0.11.0-OTAFIX2.4.10/update-heltec_t114_bootloader-v0.11.0-OTAFIX2.4.10_mbr.uf2",
|
||||
"name": "update-heltec_t114_bootloader-v0.11.0-OTAFIX2.4.11_mbr.uf2",
|
||||
"url": "https://github.com/mikecarper/Adafruit_nRF52_Bootloader_OTAFIX/releases/download/v0.11.0-OTAFIX2.4.11/update-heltec_t114_bootloader-v0.11.0-OTAFIX2.4.11_mbr.uf2",
|
||||
"size": 88064,
|
||||
"sha256": "daf96a592012b2579876777265a891407631ef8a9cba059ebca1e78a7fe307d0"
|
||||
"sha256": "d8307134c4f63a9a91d86937abe25863722ff61f822524fc027847c6137282cc"
|
||||
},
|
||||
"zip": {
|
||||
"name": "heltec_t114_bootloader-v0.11.0-OTAFIX2.4.10_s140_6.1.1.zip",
|
||||
"url": "https://github.com/mikecarper/Adafruit_nRF52_Bootloader_OTAFIX/releases/download/v0.11.0-OTAFIX2.4.10/heltec_t114_bootloader-v0.11.0-OTAFIX2.4.10_s140_6.1.1.zip",
|
||||
"name": "heltec_t114_bootloader-v0.11.0-OTAFIX2.4.11_s140_6.1.1.zip",
|
||||
"url": "https://github.com/mikecarper/Adafruit_nRF52_Bootloader_OTAFIX/releases/download/v0.11.0-OTAFIX2.4.11/heltec_t114_bootloader-v0.11.0-OTAFIX2.4.11_s140_6.1.1.zip",
|
||||
"size": 192834,
|
||||
"sha256": "cf6b2e0770f9bba9fbaa8a5c967af325dc695479e2d3f6b79349b4dcf13545dc"
|
||||
"sha256": "a473faf32cf3e4dc13c5d8340e7ffebd04741be3e37f8c2cb98b2c176e01a777"
|
||||
},
|
||||
"hex": {
|
||||
"name": "heltec_t114_bootloader-v0.11.0-OTAFIX2.4.10_s140_6.1.1.hex",
|
||||
"url": "https://github.com/mikecarper/Adafruit_nRF52_Bootloader_OTAFIX/releases/download/v0.11.0-OTAFIX2.4.10/heltec_t114_bootloader-v0.11.0-OTAFIX2.4.10_s140_6.1.1.hex",
|
||||
"size": 535336,
|
||||
"sha256": "f2912d3a4cf6bd81226d17fc75324cd83d7b97a8e4808e7d5c191634865c553d"
|
||||
"name": "heltec_t114_bootloader-v0.11.0-OTAFIX2.4.11_s140_6.1.1.hex",
|
||||
"url": "https://github.com/mikecarper/Adafruit_nRF52_Bootloader_OTAFIX/releases/download/v0.11.0-OTAFIX2.4.11/heltec_t114_bootloader-v0.11.0-OTAFIX2.4.11_s140_6.1.1.hex",
|
||||
"size": 535380,
|
||||
"sha256": "5afa51d860b54dc820d94f67efb8f7f449b0a4f461bbc176ccbd27a9730bc4e9"
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -222,22 +193,22 @@
|
||||
"storage": "internal",
|
||||
"files": {
|
||||
"uf2": {
|
||||
"name": "update-keepteen_lt1_bootloader-v0.11.0-OTAFIX2.4.10_mbr.uf2",
|
||||
"url": "https://github.com/mikecarper/Adafruit_nRF52_Bootloader_OTAFIX/releases/download/v0.11.0-OTAFIX2.4.10/update-keepteen_lt1_bootloader-v0.11.0-OTAFIX2.4.10_mbr.uf2",
|
||||
"name": "update-keepteen_lt1_bootloader-v0.11.0-OTAFIX2.4.11_mbr.uf2",
|
||||
"url": "https://github.com/mikecarper/Adafruit_nRF52_Bootloader_OTAFIX/releases/download/v0.11.0-OTAFIX2.4.11/update-keepteen_lt1_bootloader-v0.11.0-OTAFIX2.4.11_mbr.uf2",
|
||||
"size": 84480,
|
||||
"sha256": "78460767341d867cd9a77845cad9de7d00e396ca5ab8340927a605c4fa899f2d"
|
||||
"sha256": "c2ad478f12cd3176f5ea4442726fdaebb6071218564a97b7c90cf52ed214f369"
|
||||
},
|
||||
"zip": {
|
||||
"name": "keepteen_lt1_bootloader-v0.11.0-OTAFIX2.4.10_s140_6.1.1.zip",
|
||||
"url": "https://github.com/mikecarper/Adafruit_nRF52_Bootloader_OTAFIX/releases/download/v0.11.0-OTAFIX2.4.10/keepteen_lt1_bootloader-v0.11.0-OTAFIX2.4.10_s140_6.1.1.zip",
|
||||
"name": "keepteen_lt1_bootloader-v0.11.0-OTAFIX2.4.11_s140_6.1.1.zip",
|
||||
"url": "https://github.com/mikecarper/Adafruit_nRF52_Bootloader_OTAFIX/releases/download/v0.11.0-OTAFIX2.4.11/keepteen_lt1_bootloader-v0.11.0-OTAFIX2.4.11_s140_6.1.1.zip",
|
||||
"size": 192834,
|
||||
"sha256": "425c26680603a2c9b6b7becb95412611a8449ea51cf427f0997335c233a4275f"
|
||||
"sha256": "f7a21a21afad7ecbf0e29f65e243b103f4b423fe575734fa4f00db56947f7c64"
|
||||
},
|
||||
"hex": {
|
||||
"name": "keepteen_lt1_bootloader-v0.11.0-OTAFIX2.4.10_s140_6.1.1.hex",
|
||||
"url": "https://github.com/mikecarper/Adafruit_nRF52_Bootloader_OTAFIX/releases/download/v0.11.0-OTAFIX2.4.10/keepteen_lt1_bootloader-v0.11.0-OTAFIX2.4.10_s140_6.1.1.hex",
|
||||
"size": 529990,
|
||||
"sha256": "9b1b4eed8033085e07bfb6c162fb0a46ee3aca157ec6d3fae162b548c9bc9764"
|
||||
"name": "keepteen_lt1_bootloader-v0.11.0-OTAFIX2.4.11_s140_6.1.1.hex",
|
||||
"url": "https://github.com/mikecarper/Adafruit_nRF52_Bootloader_OTAFIX/releases/download/v0.11.0-OTAFIX2.4.11/keepteen_lt1_bootloader-v0.11.0-OTAFIX2.4.11_s140_6.1.1.hex",
|
||||
"size": 530034,
|
||||
"sha256": "dbad71f1975f26c4f42e3057d2e6de82201fa9488e39556070ad68fa650204fe"
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -251,22 +222,22 @@
|
||||
"storage": "qspi",
|
||||
"files": {
|
||||
"uf2": {
|
||||
"name": "update-lilygo_techo_bootloader-v0.11.0-OTAFIX2.4.10_mbr.uf2",
|
||||
"url": "https://github.com/mikecarper/Adafruit_nRF52_Bootloader_OTAFIX/releases/download/v0.11.0-OTAFIX2.4.10/update-lilygo_techo_bootloader-v0.11.0-OTAFIX2.4.10_mbr.uf2",
|
||||
"name": "update-lilygo_techo_bootloader-v0.11.0-OTAFIX2.4.11_mbr.uf2",
|
||||
"url": "https://github.com/mikecarper/Adafruit_nRF52_Bootloader_OTAFIX/releases/download/v0.11.0-OTAFIX2.4.11/update-lilygo_techo_bootloader-v0.11.0-OTAFIX2.4.11_mbr.uf2",
|
||||
"size": 86528,
|
||||
"sha256": "506719ba59e5172beab8904044b6f8700442467d5a2df6889710de8800e77a30"
|
||||
"sha256": "9a3cd2a73f91f9f1ca01f44ba56c4b1ad2317511e766533ed61c361aca6e37fa"
|
||||
},
|
||||
"zip": {
|
||||
"name": "lilygo_techo_bootloader-v0.11.0-OTAFIX2.4.10_s140_6.1.1.zip",
|
||||
"url": "https://github.com/mikecarper/Adafruit_nRF52_Bootloader_OTAFIX/releases/download/v0.11.0-OTAFIX2.4.10/lilygo_techo_bootloader-v0.11.0-OTAFIX2.4.10_s140_6.1.1.zip",
|
||||
"size": 192833,
|
||||
"sha256": "cbcb872bb3525596e699d37a39bff85f2d02b178174c9812c461b21ba0753599"
|
||||
"name": "lilygo_techo_bootloader-v0.11.0-OTAFIX2.4.11_s140_6.1.1.zip",
|
||||
"url": "https://github.com/mikecarper/Adafruit_nRF52_Bootloader_OTAFIX/releases/download/v0.11.0-OTAFIX2.4.11/lilygo_techo_bootloader-v0.11.0-OTAFIX2.4.11_s140_6.1.1.zip",
|
||||
"size": 192834,
|
||||
"sha256": "a7cbdf2c911542222c0d12eb5fb2ab9bdf2263495b591d1a946860b324bf4731"
|
||||
},
|
||||
"hex": {
|
||||
"name": "lilygo_techo_bootloader-v0.11.0-OTAFIX2.4.10_s140_6.1.1.hex",
|
||||
"url": "https://github.com/mikecarper/Adafruit_nRF52_Bootloader_OTAFIX/releases/download/v0.11.0-OTAFIX2.4.10/lilygo_techo_bootloader-v0.11.0-OTAFIX2.4.10_s140_6.1.1.hex",
|
||||
"size": 532886,
|
||||
"sha256": "b7112639c09eae5dd2a015c6a5454d6d71bf0b677e9752ed23cc4dfc0e454cb7"
|
||||
"name": "lilygo_techo_bootloader-v0.11.0-OTAFIX2.4.11_s140_6.1.1.hex",
|
||||
"url": "https://github.com/mikecarper/Adafruit_nRF52_Bootloader_OTAFIX/releases/download/v0.11.0-OTAFIX2.4.11/lilygo_techo_bootloader-v0.11.0-OTAFIX2.4.11_s140_6.1.1.hex",
|
||||
"size": 532930,
|
||||
"sha256": "5c8f10b9b1217f36b8ff6f0dac957f108ca4125bc5e737dd827c08c01a5e556f"
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -281,22 +252,22 @@
|
||||
"storage": "qspi",
|
||||
"files": {
|
||||
"uf2": {
|
||||
"name": "update-lilygo_techo_lite_bootloader-v0.11.0-OTAFIX2.4.10_mbr.uf2",
|
||||
"url": "https://github.com/mikecarper/Adafruit_nRF52_Bootloader_OTAFIX/releases/download/v0.11.0-OTAFIX2.4.10/update-lilygo_techo_lite_bootloader-v0.11.0-OTAFIX2.4.10_mbr.uf2",
|
||||
"name": "update-lilygo_techo_lite_bootloader-v0.11.0-OTAFIX2.4.11_mbr.uf2",
|
||||
"url": "https://github.com/mikecarper/Adafruit_nRF52_Bootloader_OTAFIX/releases/download/v0.11.0-OTAFIX2.4.11/update-lilygo_techo_lite_bootloader-v0.11.0-OTAFIX2.4.11_mbr.uf2",
|
||||
"size": 87040,
|
||||
"sha256": "a3db93e42b1d59d5b89bab58ebee1207cc5634dd7d6543a885484fb46e509073"
|
||||
"sha256": "5ee193ed3ac342502efb03adc3aa214eb56198f22975fe5dfcf4d5743d4788d0"
|
||||
},
|
||||
"zip": {
|
||||
"name": "lilygo_techo_lite_bootloader-v0.11.0-OTAFIX2.4.10_s140_6.1.1.zip",
|
||||
"url": "https://github.com/mikecarper/Adafruit_nRF52_Bootloader_OTAFIX/releases/download/v0.11.0-OTAFIX2.4.10/lilygo_techo_lite_bootloader-v0.11.0-OTAFIX2.4.10_s140_6.1.1.zip",
|
||||
"name": "lilygo_techo_lite_bootloader-v0.11.0-OTAFIX2.4.11_s140_6.1.1.zip",
|
||||
"url": "https://github.com/mikecarper/Adafruit_nRF52_Bootloader_OTAFIX/releases/download/v0.11.0-OTAFIX2.4.11/lilygo_techo_lite_bootloader-v0.11.0-OTAFIX2.4.11_s140_6.1.1.zip",
|
||||
"size": 192834,
|
||||
"sha256": "adf1991f5377ed69905e16df138911096c31ecb3b4f4a6e9da417e5c91d3fb2d"
|
||||
"sha256": "2ed15158b2b015a445c3d91ad616c8550e3b6ed59de2ca54832de4cb8d5ce10d"
|
||||
},
|
||||
"hex": {
|
||||
"name": "lilygo_techo_lite_bootloader-v0.11.0-OTAFIX2.4.10_s140_6.1.1.hex",
|
||||
"url": "https://github.com/mikecarper/Adafruit_nRF52_Bootloader_OTAFIX/releases/download/v0.11.0-OTAFIX2.4.10/lilygo_techo_lite_bootloader-v0.11.0-OTAFIX2.4.10_s140_6.1.1.hex",
|
||||
"size": 533482,
|
||||
"sha256": "b750f8ad93f346a9d5a4ca701bbaa5697e63b08b028f43321a8c0ddd4530346b"
|
||||
"name": "lilygo_techo_lite_bootloader-v0.11.0-OTAFIX2.4.11_s140_6.1.1.hex",
|
||||
"url": "https://github.com/mikecarper/Adafruit_nRF52_Bootloader_OTAFIX/releases/download/v0.11.0-OTAFIX2.4.11/lilygo_techo_lite_bootloader-v0.11.0-OTAFIX2.4.11_s140_6.1.1.hex",
|
||||
"size": 533526,
|
||||
"sha256": "d3531513bc550431caf9893dd88a8324f83f401b7b1700bbee6cd6c8a2724983"
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -310,22 +281,22 @@
|
||||
"storage": "internal",
|
||||
"files": {
|
||||
"uf2": {
|
||||
"name": "update-minewsemi_mx25le01_bootloader-v0.11.0-OTAFIX2.4.10_mbr.uf2",
|
||||
"url": "https://github.com/mikecarper/Adafruit_nRF52_Bootloader_OTAFIX/releases/download/v0.11.0-OTAFIX2.4.10/update-minewsemi_mx25le01_bootloader-v0.11.0-OTAFIX2.4.10_mbr.uf2",
|
||||
"name": "update-minewsemi_mx25le01_bootloader-v0.11.0-OTAFIX2.4.11_mbr.uf2",
|
||||
"url": "https://github.com/mikecarper/Adafruit_nRF52_Bootloader_OTAFIX/releases/download/v0.11.0-OTAFIX2.4.11/update-minewsemi_mx25le01_bootloader-v0.11.0-OTAFIX2.4.11_mbr.uf2",
|
||||
"size": 84480,
|
||||
"sha256": "73784ca136fe9c0ebe22d4a0222dd18572bbd5740322ef133f6edbdd6b51353e"
|
||||
"sha256": "c5858e6ed516af22202a2c9b1c155764ae25adaf55a42941d9665df77d44fad7"
|
||||
},
|
||||
"zip": {
|
||||
"name": "minewsemi_mx25le01_bootloader-v0.11.0-OTAFIX2.4.10_s140_7.3.0.zip",
|
||||
"url": "https://github.com/mikecarper/Adafruit_nRF52_Bootloader_OTAFIX/releases/download/v0.11.0-OTAFIX2.4.10/minewsemi_mx25le01_bootloader-v0.11.0-OTAFIX2.4.10_s140_7.3.0.zip",
|
||||
"name": "minewsemi_mx25le01_bootloader-v0.11.0-OTAFIX2.4.11_s140_7.3.0.zip",
|
||||
"url": "https://github.com/mikecarper/Adafruit_nRF52_Bootloader_OTAFIX/releases/download/v0.11.0-OTAFIX2.4.11/minewsemi_mx25le01_bootloader-v0.11.0-OTAFIX2.4.11_s140_7.3.0.zip",
|
||||
"size": 194546,
|
||||
"sha256": "d1bc22c9822447f18b1590662ca95b612e3ccbf1353892a67441c87315ab457c"
|
||||
"sha256": "322b8c8e9caaca660bd2ed68f1d19f5cd38575ea7def6f262e652e2a872f7f2d"
|
||||
},
|
||||
"hex": {
|
||||
"name": "minewsemi_mx25le01_bootloader-v0.11.0-OTAFIX2.4.10_s140_7.3.0.hex",
|
||||
"url": "https://github.com/mikecarper/Adafruit_nRF52_Bootloader_OTAFIX/releases/download/v0.11.0-OTAFIX2.4.10/minewsemi_mx25le01_bootloader-v0.11.0-OTAFIX2.4.10_s140_7.3.0.hex",
|
||||
"size": 535040,
|
||||
"sha256": "c6229d65c7f7543bc48cb0a362a4799c7b0781684f9a63156686cf66aa085d79"
|
||||
"name": "minewsemi_mx25le01_bootloader-v0.11.0-OTAFIX2.4.11_s140_7.3.0.hex",
|
||||
"url": "https://github.com/mikecarper/Adafruit_nRF52_Bootloader_OTAFIX/releases/download/v0.11.0-OTAFIX2.4.11/minewsemi_mx25le01_bootloader-v0.11.0-OTAFIX2.4.11_s140_7.3.0.hex",
|
||||
"size": 535084,
|
||||
"sha256": "9de94798b5c25018ee9a5494b776bd5469f5c743d0fa9cbb82dfc7dda115fbbf"
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -339,22 +310,22 @@
|
||||
"storage": "qspi",
|
||||
"files": {
|
||||
"uf2": {
|
||||
"name": "update-pca10056_bootloader-v0.11.0-OTAFIX2.4.10_mbr.uf2",
|
||||
"url": "https://github.com/mikecarper/Adafruit_nRF52_Bootloader_OTAFIX/releases/download/v0.11.0-OTAFIX2.4.10/update-pca10056_bootloader-v0.11.0-OTAFIX2.4.10_mbr.uf2",
|
||||
"name": "update-pca10056_bootloader-v0.11.0-OTAFIX2.4.11_mbr.uf2",
|
||||
"url": "https://github.com/mikecarper/Adafruit_nRF52_Bootloader_OTAFIX/releases/download/v0.11.0-OTAFIX2.4.11/update-pca10056_bootloader-v0.11.0-OTAFIX2.4.11_mbr.uf2",
|
||||
"size": 86528,
|
||||
"sha256": "8f6b2f37b33e2b4c7a5b038e9deb04e088e1da4b716d7f6787f201acd9af8315"
|
||||
"sha256": "78ab8f3f8ea44d60c13d6fd34088451b1bd26745757deed44826c15c7ee70ea8"
|
||||
},
|
||||
"zip": {
|
||||
"name": "pca10056_bootloader-v0.11.0-OTAFIX2.4.10_s140_6.1.1.zip",
|
||||
"url": "https://github.com/mikecarper/Adafruit_nRF52_Bootloader_OTAFIX/releases/download/v0.11.0-OTAFIX2.4.10/pca10056_bootloader-v0.11.0-OTAFIX2.4.10_s140_6.1.1.zip",
|
||||
"name": "pca10056_bootloader-v0.11.0-OTAFIX2.4.11_s140_6.1.1.zip",
|
||||
"url": "https://github.com/mikecarper/Adafruit_nRF52_Bootloader_OTAFIX/releases/download/v0.11.0-OTAFIX2.4.11/pca10056_bootloader-v0.11.0-OTAFIX2.4.11_s140_6.1.1.zip",
|
||||
"size": 192834,
|
||||
"sha256": "76db09c07e9175204368a939eb71e920aae7168633dec4f122851c62027d83bc"
|
||||
"sha256": "d5d01a01f98850b3282cf7f443fa13e642a42617d7267cee22739880fbcf2ff9"
|
||||
},
|
||||
"hex": {
|
||||
"name": "pca10056_bootloader-v0.11.0-OTAFIX2.4.10_s140_6.1.1.hex",
|
||||
"url": "https://github.com/mikecarper/Adafruit_nRF52_Bootloader_OTAFIX/releases/download/v0.11.0-OTAFIX2.4.10/pca10056_bootloader-v0.11.0-OTAFIX2.4.10_s140_6.1.1.hex",
|
||||
"size": 532976,
|
||||
"sha256": "6e77e4fc3adf07e3ca455ce02ca021d8097261b43834844f6225d8f1c80616c8"
|
||||
"name": "pca10056_bootloader-v0.11.0-OTAFIX2.4.11_s140_6.1.1.hex",
|
||||
"url": "https://github.com/mikecarper/Adafruit_nRF52_Bootloader_OTAFIX/releases/download/v0.11.0-OTAFIX2.4.11/pca10056_bootloader-v0.11.0-OTAFIX2.4.11_s140_6.1.1.hex",
|
||||
"size": 533020,
|
||||
"sha256": "51ceaa88711ca24a8430bb6f35766df729245cbe2858336dad3082751c692fc2"
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -368,22 +339,22 @@
|
||||
"storage": "internal",
|
||||
"files": {
|
||||
"uf2": {
|
||||
"name": "update-promicro_nrf52840_bootloader-v0.11.0-OTAFIX2.4.10_mbr.uf2",
|
||||
"url": "https://github.com/mikecarper/Adafruit_nRF52_Bootloader_OTAFIX/releases/download/v0.11.0-OTAFIX2.4.10/update-promicro_nrf52840_bootloader-v0.11.0-OTAFIX2.4.10_mbr.uf2",
|
||||
"name": "update-promicro_nrf52840_bootloader-v0.11.0-OTAFIX2.4.11_mbr.uf2",
|
||||
"url": "https://github.com/mikecarper/Adafruit_nRF52_Bootloader_OTAFIX/releases/download/v0.11.0-OTAFIX2.4.11/update-promicro_nrf52840_bootloader-v0.11.0-OTAFIX2.4.11_mbr.uf2",
|
||||
"size": 84480,
|
||||
"sha256": "a445375e828a3724f3e9d7854175d5a1c120eef0613cc6fd7c0d4cfa03a5d04a"
|
||||
"sha256": "b849203d30a56b5243f8d92e308c6cd9a1eb3b35459072204f9f044d74dbad09"
|
||||
},
|
||||
"zip": {
|
||||
"name": "promicro_nrf52840_bootloader-v0.11.0-OTAFIX2.4.10_s140_6.1.1.zip",
|
||||
"url": "https://github.com/mikecarper/Adafruit_nRF52_Bootloader_OTAFIX/releases/download/v0.11.0-OTAFIX2.4.10/promicro_nrf52840_bootloader-v0.11.0-OTAFIX2.4.10_s140_6.1.1.zip",
|
||||
"name": "promicro_nrf52840_bootloader-v0.11.0-OTAFIX2.4.11_s140_6.1.1.zip",
|
||||
"url": "https://github.com/mikecarper/Adafruit_nRF52_Bootloader_OTAFIX/releases/download/v0.11.0-OTAFIX2.4.11/promicro_nrf52840_bootloader-v0.11.0-OTAFIX2.4.11_s140_6.1.1.zip",
|
||||
"size": 192834,
|
||||
"sha256": "10b32c1133be24a532f9678d7517c926ed474b751f99bc33d35a3a01c54d3aba"
|
||||
"sha256": "3b2ef682cc2641626b4db3d8bfe821cd86dd701f38c7c0c055864d78da0fbb88"
|
||||
},
|
||||
"hex": {
|
||||
"name": "promicro_nrf52840_bootloader-v0.11.0-OTAFIX2.4.10_s140_6.1.1.hex",
|
||||
"url": "https://github.com/mikecarper/Adafruit_nRF52_Bootloader_OTAFIX/releases/download/v0.11.0-OTAFIX2.4.10/promicro_nrf52840_bootloader-v0.11.0-OTAFIX2.4.10_s140_6.1.1.hex",
|
||||
"size": 530382,
|
||||
"sha256": "650ac8431867a21b31087ace9ef585e5d946952775dca63d256093a7804e4a0e"
|
||||
"name": "promicro_nrf52840_bootloader-v0.11.0-OTAFIX2.4.11_s140_6.1.1.hex",
|
||||
"url": "https://github.com/mikecarper/Adafruit_nRF52_Bootloader_OTAFIX/releases/download/v0.11.0-OTAFIX2.4.11/promicro_nrf52840_bootloader-v0.11.0-OTAFIX2.4.11_s140_6.1.1.hex",
|
||||
"size": 530426,
|
||||
"sha256": "bcfda096684f5da9f00c36a0baef81a0b4655424f594cc29953c27ca9051263f"
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -397,22 +368,22 @@
|
||||
"storage": "qspi",
|
||||
"files": {
|
||||
"uf2": {
|
||||
"name": "update-sensecap_solar_p1_bootloader-v0.11.0-OTAFIX2.4.10_mbr.uf2",
|
||||
"url": "https://github.com/mikecarper/Adafruit_nRF52_Bootloader_OTAFIX/releases/download/v0.11.0-OTAFIX2.4.10/update-sensecap_solar_p1_bootloader-v0.11.0-OTAFIX2.4.10_mbr.uf2",
|
||||
"name": "update-sensecap_solar_p1_bootloader-v0.11.0-OTAFIX2.4.11_mbr.uf2",
|
||||
"url": "https://github.com/mikecarper/Adafruit_nRF52_Bootloader_OTAFIX/releases/download/v0.11.0-OTAFIX2.4.11/update-sensecap_solar_p1_bootloader-v0.11.0-OTAFIX2.4.11_mbr.uf2",
|
||||
"size": 86016,
|
||||
"sha256": "35fea3ca9a5f440a99279861b17174f54cf14ab406c0b2eb6bbddc76af6f65e2"
|
||||
"sha256": "ca98f27f0289189d0b9b600c5a5a82acad14a56ed4ee4922c6c17fe1d669aadf"
|
||||
},
|
||||
"zip": {
|
||||
"name": "sensecap_solar_p1_bootloader-v0.11.0-OTAFIX2.4.10_s140_7.3.0.zip",
|
||||
"url": "https://github.com/mikecarper/Adafruit_nRF52_Bootloader_OTAFIX/releases/download/v0.11.0-OTAFIX2.4.10/sensecap_solar_p1_bootloader-v0.11.0-OTAFIX2.4.10_s140_7.3.0.zip",
|
||||
"name": "sensecap_solar_p1_bootloader-v0.11.0-OTAFIX2.4.11_s140_7.3.0.zip",
|
||||
"url": "https://github.com/mikecarper/Adafruit_nRF52_Bootloader_OTAFIX/releases/download/v0.11.0-OTAFIX2.4.11/sensecap_solar_p1_bootloader-v0.11.0-OTAFIX2.4.11_s140_7.3.0.zip",
|
||||
"size": 194546,
|
||||
"sha256": "60f74057e9ccd33cb90d59027d6e10231ea1b1c2f9244b1f319d10bb8c4a4a81"
|
||||
"sha256": "2ed90c5624a3cdb9852fb22da42f02c2d55d93e74e19978068570c1edce90ee8"
|
||||
},
|
||||
"hex": {
|
||||
"name": "sensecap_solar_p1_bootloader-v0.11.0-OTAFIX2.4.10_s140_7.3.0.hex",
|
||||
"url": "https://github.com/mikecarper/Adafruit_nRF52_Bootloader_OTAFIX/releases/download/v0.11.0-OTAFIX2.4.10/sensecap_solar_p1_bootloader-v0.11.0-OTAFIX2.4.10_s140_7.3.0.hex",
|
||||
"size": 537336,
|
||||
"sha256": "f8907bcfc2658e59d580dd1618d11d1f27b1f1887ba8617504d7d0d25347ba0f"
|
||||
"name": "sensecap_solar_p1_bootloader-v0.11.0-OTAFIX2.4.11_s140_7.3.0.hex",
|
||||
"url": "https://github.com/mikecarper/Adafruit_nRF52_Bootloader_OTAFIX/releases/download/v0.11.0-OTAFIX2.4.11/sensecap_solar_p1_bootloader-v0.11.0-OTAFIX2.4.11_s140_7.3.0.hex",
|
||||
"size": 537380,
|
||||
"sha256": "6a0ada53219f67dd1a99da32d1257acff5fa617db26a9c3931dfa69e64bfca96"
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -426,22 +397,22 @@
|
||||
"storage": "internal",
|
||||
"files": {
|
||||
"uf2": {
|
||||
"name": "update-t1000_e_bootloader-v0.11.0-OTAFIX2.4.10_mbr.uf2",
|
||||
"url": "https://github.com/mikecarper/Adafruit_nRF52_Bootloader_OTAFIX/releases/download/v0.11.0-OTAFIX2.4.10/update-t1000_e_bootloader-v0.11.0-OTAFIX2.4.10_mbr.uf2",
|
||||
"name": "update-t1000_e_bootloader-v0.11.0-OTAFIX2.4.11_mbr.uf2",
|
||||
"url": "https://github.com/mikecarper/Adafruit_nRF52_Bootloader_OTAFIX/releases/download/v0.11.0-OTAFIX2.4.11/update-t1000_e_bootloader-v0.11.0-OTAFIX2.4.11_mbr.uf2",
|
||||
"size": 84480,
|
||||
"sha256": "ea2fc2e1982a2e9365e42fea8471b2770f805d9d1fb37f4e2e49e43c3a83dce3"
|
||||
"sha256": "56a66098af73f8ef71d7709fb2e4803f0822b146310882cfccd9cd67dc7e6f8a"
|
||||
},
|
||||
"zip": {
|
||||
"name": "t1000_e_bootloader-v0.11.0-OTAFIX2.4.10_s140_7.3.0.zip",
|
||||
"url": "https://github.com/mikecarper/Adafruit_nRF52_Bootloader_OTAFIX/releases/download/v0.11.0-OTAFIX2.4.10/t1000_e_bootloader-v0.11.0-OTAFIX2.4.10_s140_7.3.0.zip",
|
||||
"name": "t1000_e_bootloader-v0.11.0-OTAFIX2.4.11_s140_7.3.0.zip",
|
||||
"url": "https://github.com/mikecarper/Adafruit_nRF52_Bootloader_OTAFIX/releases/download/v0.11.0-OTAFIX2.4.11/t1000_e_bootloader-v0.11.0-OTAFIX2.4.11_s140_7.3.0.zip",
|
||||
"size": 194546,
|
||||
"sha256": "b1743ea1e54a7c69fc04fe6dd1467b221bd82a3658fd3170a8dc9d6ffb0a7322"
|
||||
"sha256": "25e4f5af918ee1db913eb66f867f601578267ca5ebd8d4ebe7f6a43e193992e0"
|
||||
},
|
||||
"hex": {
|
||||
"name": "t1000_e_bootloader-v0.11.0-OTAFIX2.4.10_s140_7.3.0.hex",
|
||||
"url": "https://github.com/mikecarper/Adafruit_nRF52_Bootloader_OTAFIX/releases/download/v0.11.0-OTAFIX2.4.10/t1000_e_bootloader-v0.11.0-OTAFIX2.4.10_s140_7.3.0.hex",
|
||||
"size": 534892,
|
||||
"sha256": "863c2bdf6c01a709cb64808a51b1ff6710c35f40f10b86280ce07b763410a709"
|
||||
"name": "t1000_e_bootloader-v0.11.0-OTAFIX2.4.11_s140_7.3.0.hex",
|
||||
"url": "https://github.com/mikecarper/Adafruit_nRF52_Bootloader_OTAFIX/releases/download/v0.11.0-OTAFIX2.4.11/t1000_e_bootloader-v0.11.0-OTAFIX2.4.11_s140_7.3.0.hex",
|
||||
"size": 534936,
|
||||
"sha256": "9d75645dcf05a3f026ae2e798294dfbdb256288853fa18a8fc3d81a36df2079f"
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -455,22 +426,22 @@
|
||||
"storage": "qspi",
|
||||
"files": {
|
||||
"uf2": {
|
||||
"name": "update-thinknode_m1_bootloader-v0.11.0-OTAFIX2.4.10_mbr.uf2",
|
||||
"url": "https://github.com/mikecarper/Adafruit_nRF52_Bootloader_OTAFIX/releases/download/v0.11.0-OTAFIX2.4.10/update-thinknode_m1_bootloader-v0.11.0-OTAFIX2.4.10_mbr.uf2",
|
||||
"name": "update-thinknode_m1_bootloader-v0.11.0-OTAFIX2.4.11_mbr.uf2",
|
||||
"url": "https://github.com/mikecarper/Adafruit_nRF52_Bootloader_OTAFIX/releases/download/v0.11.0-OTAFIX2.4.11/update-thinknode_m1_bootloader-v0.11.0-OTAFIX2.4.11_mbr.uf2",
|
||||
"size": 86016,
|
||||
"sha256": "42e64392dacda730d83335335bb143af64fdb53abef7228c0fedbb4a12850a61"
|
||||
"sha256": "e9285e60f5789380f5aa7c61773e3d4d5dc61054688ec4e83830d3ea118f89d3"
|
||||
},
|
||||
"zip": {
|
||||
"name": "thinknode_m1_bootloader-v0.11.0-OTAFIX2.4.10_s140_6.1.1.zip",
|
||||
"url": "https://github.com/mikecarper/Adafruit_nRF52_Bootloader_OTAFIX/releases/download/v0.11.0-OTAFIX2.4.10/thinknode_m1_bootloader-v0.11.0-OTAFIX2.4.10_s140_6.1.1.zip",
|
||||
"name": "thinknode_m1_bootloader-v0.11.0-OTAFIX2.4.11_s140_6.1.1.zip",
|
||||
"url": "https://github.com/mikecarper/Adafruit_nRF52_Bootloader_OTAFIX/releases/download/v0.11.0-OTAFIX2.4.11/thinknode_m1_bootloader-v0.11.0-OTAFIX2.4.11_s140_6.1.1.zip",
|
||||
"size": 192833,
|
||||
"sha256": "0bf8e7acecf1019b90562c69e9a6e68ba4787652e3f3dd6060441f8a64c36c6c"
|
||||
"sha256": "3cac3e8f3b73a4e46e99fd11502954dd82e416274f413019d79265d8bca4e4b0"
|
||||
},
|
||||
"hex": {
|
||||
"name": "thinknode_m1_bootloader-v0.11.0-OTAFIX2.4.10_s140_6.1.1.hex",
|
||||
"url": "https://github.com/mikecarper/Adafruit_nRF52_Bootloader_OTAFIX/releases/download/v0.11.0-OTAFIX2.4.10/thinknode_m1_bootloader-v0.11.0-OTAFIX2.4.10_s140_6.1.1.hex",
|
||||
"size": 532614,
|
||||
"sha256": "74007bc4c78bc5a32246af1ace7083fc01c2a43dabd27906e8d8d3fd3fbe5226"
|
||||
"name": "thinknode_m1_bootloader-v0.11.0-OTAFIX2.4.11_s140_6.1.1.hex",
|
||||
"url": "https://github.com/mikecarper/Adafruit_nRF52_Bootloader_OTAFIX/releases/download/v0.11.0-OTAFIX2.4.11/thinknode_m1_bootloader-v0.11.0-OTAFIX2.4.11_s140_6.1.1.hex",
|
||||
"size": 532658,
|
||||
"sha256": "2c42f4334ac94e05cce770fe9570b9442f5d37c54fb3c42d3e9f3caf2d5f36d1"
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -484,22 +455,22 @@
|
||||
"storage": "internal",
|
||||
"files": {
|
||||
"uf2": {
|
||||
"name": "update-thinknode_m3_bootloader-v0.11.0-OTAFIX2.4.10_mbr.uf2",
|
||||
"url": "https://github.com/mikecarper/Adafruit_nRF52_Bootloader_OTAFIX/releases/download/v0.11.0-OTAFIX2.4.10/update-thinknode_m3_bootloader-v0.11.0-OTAFIX2.4.10_mbr.uf2",
|
||||
"name": "update-thinknode_m3_bootloader-v0.11.0-OTAFIX2.4.11_mbr.uf2",
|
||||
"url": "https://github.com/mikecarper/Adafruit_nRF52_Bootloader_OTAFIX/releases/download/v0.11.0-OTAFIX2.4.11/update-thinknode_m3_bootloader-v0.11.0-OTAFIX2.4.11_mbr.uf2",
|
||||
"size": 84480,
|
||||
"sha256": "c38ead189e8baf624ff9fddf31d2f16cd24f056a8ad2d66247e59828cefa1e8a"
|
||||
"sha256": "babdc56164774911c53c4c0384ff12a110b07a5afc4998ee2ada021808a15a28"
|
||||
},
|
||||
"zip": {
|
||||
"name": "thinknode_m3_bootloader-v0.11.0-OTAFIX2.4.10_s140_6.1.1.zip",
|
||||
"url": "https://github.com/mikecarper/Adafruit_nRF52_Bootloader_OTAFIX/releases/download/v0.11.0-OTAFIX2.4.10/thinknode_m3_bootloader-v0.11.0-OTAFIX2.4.10_s140_6.1.1.zip",
|
||||
"size": 192833,
|
||||
"sha256": "3a70cba6350a97d74d537799325a40612c17cc9901a2835ef80e2ccfb534bcfe"
|
||||
"name": "thinknode_m3_bootloader-v0.11.0-OTAFIX2.4.11_s140_6.1.1.zip",
|
||||
"url": "https://github.com/mikecarper/Adafruit_nRF52_Bootloader_OTAFIX/releases/download/v0.11.0-OTAFIX2.4.11/thinknode_m3_bootloader-v0.11.0-OTAFIX2.4.11_s140_6.1.1.zip",
|
||||
"size": 192834,
|
||||
"sha256": "d5997d7c3cd0917b5fc0302a50eb0543897b5009371e5400c728901002d8ef04"
|
||||
},
|
||||
"hex": {
|
||||
"name": "thinknode_m3_bootloader-v0.11.0-OTAFIX2.4.10_s140_6.1.1.hex",
|
||||
"url": "https://github.com/mikecarper/Adafruit_nRF52_Bootloader_OTAFIX/releases/download/v0.11.0-OTAFIX2.4.10/thinknode_m3_bootloader-v0.11.0-OTAFIX2.4.10_s140_6.1.1.hex",
|
||||
"size": 530096,
|
||||
"sha256": "77c76e4a61a06a547b0d49b40e29ea56263bb45d10091fc70d9c9472af48e3ad"
|
||||
"name": "thinknode_m3_bootloader-v0.11.0-OTAFIX2.4.11_s140_6.1.1.hex",
|
||||
"url": "https://github.com/mikecarper/Adafruit_nRF52_Bootloader_OTAFIX/releases/download/v0.11.0-OTAFIX2.4.11/thinknode_m3_bootloader-v0.11.0-OTAFIX2.4.11_s140_6.1.1.hex",
|
||||
"size": 530140,
|
||||
"sha256": "25fe01c8b9e2b73d9b8723e538dbeda48e136142884bb1769a527b4c183359db"
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -513,22 +484,22 @@
|
||||
"storage": "qspi",
|
||||
"files": {
|
||||
"uf2": {
|
||||
"name": "update-thinknode_m6_bootloader-v0.11.0-OTAFIX2.4.10_mbr.uf2",
|
||||
"url": "https://github.com/mikecarper/Adafruit_nRF52_Bootloader_OTAFIX/releases/download/v0.11.0-OTAFIX2.4.10/update-thinknode_m6_bootloader-v0.11.0-OTAFIX2.4.10_mbr.uf2",
|
||||
"name": "update-thinknode_m6_bootloader-v0.11.0-OTAFIX2.4.11_mbr.uf2",
|
||||
"url": "https://github.com/mikecarper/Adafruit_nRF52_Bootloader_OTAFIX/releases/download/v0.11.0-OTAFIX2.4.11/update-thinknode_m6_bootloader-v0.11.0-OTAFIX2.4.11_mbr.uf2",
|
||||
"size": 86016,
|
||||
"sha256": "a68f9dec813758bfcdb6584aed61919de77e6de4ed7ff7bb054afe5579d70891"
|
||||
"sha256": "c177e30bebd37a5a495780ebe472eaa49e50294f96cfb836568bce03dfc69d32"
|
||||
},
|
||||
"zip": {
|
||||
"name": "thinknode_m6_bootloader-v0.11.0-OTAFIX2.4.10_s140_6.1.1.zip",
|
||||
"url": "https://github.com/mikecarper/Adafruit_nRF52_Bootloader_OTAFIX/releases/download/v0.11.0-OTAFIX2.4.10/thinknode_m6_bootloader-v0.11.0-OTAFIX2.4.10_s140_6.1.1.zip",
|
||||
"name": "thinknode_m6_bootloader-v0.11.0-OTAFIX2.4.11_s140_6.1.1.zip",
|
||||
"url": "https://github.com/mikecarper/Adafruit_nRF52_Bootloader_OTAFIX/releases/download/v0.11.0-OTAFIX2.4.11/thinknode_m6_bootloader-v0.11.0-OTAFIX2.4.11_s140_6.1.1.zip",
|
||||
"size": 192834,
|
||||
"sha256": "a306f36d968b7cf92d3f849135049fb9dfbcd8bb650e5fe79e59b51c90e431b4"
|
||||
"sha256": "4615d333486d6638fe7b10f0ed82b2bb4ba25424bf11b1662a35f117d6f4060a"
|
||||
},
|
||||
"hex": {
|
||||
"name": "thinknode_m6_bootloader-v0.11.0-OTAFIX2.4.10_s140_6.1.1.hex",
|
||||
"url": "https://github.com/mikecarper/Adafruit_nRF52_Bootloader_OTAFIX/releases/download/v0.11.0-OTAFIX2.4.10/thinknode_m6_bootloader-v0.11.0-OTAFIX2.4.10_s140_6.1.1.hex",
|
||||
"size": 532702,
|
||||
"sha256": "a9d52a8215645d8431d9665a5abb419dc895157c3afc65564bc2aeaa22cb21f1"
|
||||
"name": "thinknode_m6_bootloader-v0.11.0-OTAFIX2.4.11_s140_6.1.1.hex",
|
||||
"url": "https://github.com/mikecarper/Adafruit_nRF52_Bootloader_OTAFIX/releases/download/v0.11.0-OTAFIX2.4.11/thinknode_m6_bootloader-v0.11.0-OTAFIX2.4.11_s140_6.1.1.hex",
|
||||
"size": 532746,
|
||||
"sha256": "5a6c937a222d2434a59b63eb7369a392f018404dbfc71913a27c48c64042fbec"
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -544,22 +515,22 @@
|
||||
"storage": "qspi",
|
||||
"files": {
|
||||
"uf2": {
|
||||
"name": "update-wio_tracker_l1_bootloader-v0.11.0-OTAFIX2.4.10_mbr.uf2",
|
||||
"url": "https://github.com/mikecarper/Adafruit_nRF52_Bootloader_OTAFIX/releases/download/v0.11.0-OTAFIX2.4.10/update-wio_tracker_l1_bootloader-v0.11.0-OTAFIX2.4.10_mbr.uf2",
|
||||
"name": "update-wio_tracker_l1_bootloader-v0.11.0-OTAFIX2.4.11_mbr.uf2",
|
||||
"url": "https://github.com/mikecarper/Adafruit_nRF52_Bootloader_OTAFIX/releases/download/v0.11.0-OTAFIX2.4.11/update-wio_tracker_l1_bootloader-v0.11.0-OTAFIX2.4.11_mbr.uf2",
|
||||
"size": 86016,
|
||||
"sha256": "84bc013be4242f77369ac9e87e8465716a9fc535f29b33a7b9e5fc0e25a62793"
|
||||
"sha256": "94270e321b602086b9b00a3a80d5f9cec07d16bd095efc252f2d6ddf4086adb9"
|
||||
},
|
||||
"zip": {
|
||||
"name": "wio_tracker_l1_bootloader-v0.11.0-OTAFIX2.4.10_s140_7.3.0.zip",
|
||||
"url": "https://github.com/mikecarper/Adafruit_nRF52_Bootloader_OTAFIX/releases/download/v0.11.0-OTAFIX2.4.10/wio_tracker_l1_bootloader-v0.11.0-OTAFIX2.4.10_s140_7.3.0.zip",
|
||||
"name": "wio_tracker_l1_bootloader-v0.11.0-OTAFIX2.4.11_s140_7.3.0.zip",
|
||||
"url": "https://github.com/mikecarper/Adafruit_nRF52_Bootloader_OTAFIX/releases/download/v0.11.0-OTAFIX2.4.11/wio_tracker_l1_bootloader-v0.11.0-OTAFIX2.4.11_s140_7.3.0.zip",
|
||||
"size": 194545,
|
||||
"sha256": "b40783b87f87de5b206c98fe970f0a94f8d55ba728c888333a323a6641aab63d"
|
||||
"sha256": "1d6436b2c4c434f5f5a0cb8646c97a3087afaf43cc79403da78ed85b36ea0313"
|
||||
},
|
||||
"hex": {
|
||||
"name": "wio_tracker_l1_bootloader-v0.11.0-OTAFIX2.4.10_s140_7.3.0.hex",
|
||||
"url": "https://github.com/mikecarper/Adafruit_nRF52_Bootloader_OTAFIX/releases/download/v0.11.0-OTAFIX2.4.10/wio_tracker_l1_bootloader-v0.11.0-OTAFIX2.4.10_s140_7.3.0.hex",
|
||||
"size": 537246,
|
||||
"sha256": "309a3f79d43bd638a31ea5bb54b8d4a9b3fb402e5c06ea017a02d69154cab39c"
|
||||
"name": "wio_tracker_l1_bootloader-v0.11.0-OTAFIX2.4.11_s140_7.3.0.hex",
|
||||
"url": "https://github.com/mikecarper/Adafruit_nRF52_Bootloader_OTAFIX/releases/download/v0.11.0-OTAFIX2.4.11/wio_tracker_l1_bootloader-v0.11.0-OTAFIX2.4.11_s140_7.3.0.hex",
|
||||
"size": 537290,
|
||||
"sha256": "4513b435762d69db75fc233a099d21a02efe6481d75f38dd37a64aa5da5116a6"
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -573,22 +544,22 @@
|
||||
"storage": "adaptive",
|
||||
"files": {
|
||||
"uf2": {
|
||||
"name": "update-wiscore_rak3401_auto_bootloader-v0.11.0-OTAFIX2.4.10_mbr.uf2",
|
||||
"url": "https://github.com/mikecarper/Adafruit_nRF52_Bootloader_OTAFIX/releases/download/v0.11.0-OTAFIX2.4.10/update-wiscore_rak3401_auto_bootloader-v0.11.0-OTAFIX2.4.10_mbr.uf2",
|
||||
"name": "update-wiscore_rak3401_auto_bootloader-v0.11.0-OTAFIX2.4.11_mbr.uf2",
|
||||
"url": "https://github.com/mikecarper/Adafruit_nRF52_Bootloader_OTAFIX/releases/download/v0.11.0-OTAFIX2.4.11/update-wiscore_rak3401_auto_bootloader-v0.11.0-OTAFIX2.4.11_mbr.uf2",
|
||||
"size": 88064,
|
||||
"sha256": "196c4720a4a4c04dcbce984b4fe9c995cdfac2551af9e5a2bb01b88a287a1bcc"
|
||||
"sha256": "e26087a540b235b5968d7e5fee7e9c8f6dcff8480ad7ea922792e88307335b67"
|
||||
},
|
||||
"zip": {
|
||||
"name": "wiscore_rak3401_auto_bootloader-v0.11.0-OTAFIX2.4.10_s140_6.1.1.zip",
|
||||
"url": "https://github.com/mikecarper/Adafruit_nRF52_Bootloader_OTAFIX/releases/download/v0.11.0-OTAFIX2.4.10/wiscore_rak3401_auto_bootloader-v0.11.0-OTAFIX2.4.10_s140_6.1.1.zip",
|
||||
"size": 192833,
|
||||
"sha256": "6fb37f8212d84263c1cdd8869cb844d8fbc5cbc470c864bf34221790ffc1b164"
|
||||
"name": "wiscore_rak3401_auto_bootloader-v0.11.0-OTAFIX2.4.11_s140_6.1.1.zip",
|
||||
"url": "https://github.com/mikecarper/Adafruit_nRF52_Bootloader_OTAFIX/releases/download/v0.11.0-OTAFIX2.4.11/wiscore_rak3401_auto_bootloader-v0.11.0-OTAFIX2.4.11_s140_6.1.1.zip",
|
||||
"size": 192834,
|
||||
"sha256": "4971a54970011c2df80ece58d917817de0d157e3fcb1a8490d1fa1f65e279db7"
|
||||
},
|
||||
"hex": {
|
||||
"name": "wiscore_rak3401_auto_bootloader-v0.11.0-OTAFIX2.4.10_s140_6.1.1.hex",
|
||||
"url": "https://github.com/mikecarper/Adafruit_nRF52_Bootloader_OTAFIX/releases/download/v0.11.0-OTAFIX2.4.10/wiscore_rak3401_auto_bootloader-v0.11.0-OTAFIX2.4.10_s140_6.1.1.hex",
|
||||
"size": 535390,
|
||||
"sha256": "f875900258e584dd9c577c05a21a2d65e1a511aa5e1b8e5680ad99d98fa910d7"
|
||||
"name": "wiscore_rak3401_auto_bootloader-v0.11.0-OTAFIX2.4.11_s140_6.1.1.hex",
|
||||
"url": "https://github.com/mikecarper/Adafruit_nRF52_Bootloader_OTAFIX/releases/download/v0.11.0-OTAFIX2.4.11/wiscore_rak3401_auto_bootloader-v0.11.0-OTAFIX2.4.11_s140_6.1.1.hex",
|
||||
"size": 535384,
|
||||
"sha256": "2d8a773b4f94a9e2e6fc13923101ca6e66384330489014cb5e4c3968dc941a21"
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -603,22 +574,22 @@
|
||||
"storage": "adaptive",
|
||||
"files": {
|
||||
"uf2": {
|
||||
"name": "update-wiscore_rak4631_auto_bootloader-v0.11.0-OTAFIX2.4.10_mbr.uf2",
|
||||
"url": "https://github.com/mikecarper/Adafruit_nRF52_Bootloader_OTAFIX/releases/download/v0.11.0-OTAFIX2.4.10/update-wiscore_rak4631_auto_bootloader-v0.11.0-OTAFIX2.4.10_mbr.uf2",
|
||||
"name": "update-wiscore_rak4631_auto_bootloader-v0.11.0-OTAFIX2.4.11_mbr.uf2",
|
||||
"url": "https://github.com/mikecarper/Adafruit_nRF52_Bootloader_OTAFIX/releases/download/v0.11.0-OTAFIX2.4.11/update-wiscore_rak4631_auto_bootloader-v0.11.0-OTAFIX2.4.11_mbr.uf2",
|
||||
"size": 88064,
|
||||
"sha256": "a628260984daaa7d29413bb59d23eccd104ad685e8fa6401d481dfac9fa2b00d"
|
||||
"sha256": "40bb22fcfb90aae335a8bb6bb6e876f3407d87407ead8ee3908bb0ba0b2541a7"
|
||||
},
|
||||
"zip": {
|
||||
"name": "wiscore_rak4631_auto_bootloader-v0.11.0-OTAFIX2.4.10_s140_6.1.1.zip",
|
||||
"url": "https://github.com/mikecarper/Adafruit_nRF52_Bootloader_OTAFIX/releases/download/v0.11.0-OTAFIX2.4.10/wiscore_rak4631_auto_bootloader-v0.11.0-OTAFIX2.4.10_s140_6.1.1.zip",
|
||||
"size": 192834,
|
||||
"sha256": "303d5d98d1e585798d11c6f37d74a8b33e9c99dd71a41b69d90b6efcd0d22565"
|
||||
"name": "wiscore_rak4631_auto_bootloader-v0.11.0-OTAFIX2.4.11_s140_6.1.1.zip",
|
||||
"url": "https://github.com/mikecarper/Adafruit_nRF52_Bootloader_OTAFIX/releases/download/v0.11.0-OTAFIX2.4.11/wiscore_rak4631_auto_bootloader-v0.11.0-OTAFIX2.4.11_s140_6.1.1.zip",
|
||||
"size": 192833,
|
||||
"sha256": "d3c12363a620083f18e828e724e0af67a2b141b21dc4768d0707cfaadc32b98d"
|
||||
},
|
||||
"hex": {
|
||||
"name": "wiscore_rak4631_auto_bootloader-v0.11.0-OTAFIX2.4.10_s140_6.1.1.hex",
|
||||
"url": "https://github.com/mikecarper/Adafruit_nRF52_Bootloader_OTAFIX/releases/download/v0.11.0-OTAFIX2.4.10/wiscore_rak4631_auto_bootloader-v0.11.0-OTAFIX2.4.10_s140_6.1.1.hex",
|
||||
"size": 535786,
|
||||
"sha256": "1d4bc0f07065976b6ddd01bf2eb27bb9804dac69cdaf418afc7445cf9507f33b"
|
||||
"name": "wiscore_rak4631_auto_bootloader-v0.11.0-OTAFIX2.4.11_s140_6.1.1.hex",
|
||||
"url": "https://github.com/mikecarper/Adafruit_nRF52_Bootloader_OTAFIX/releases/download/v0.11.0-OTAFIX2.4.11/wiscore_rak4631_auto_bootloader-v0.11.0-OTAFIX2.4.11_s140_6.1.1.hex",
|
||||
"size": 535780,
|
||||
"sha256": "963cb6e47179551ce975b54502fb8dbe62a9601ba7432cbf8b23cf4c86f2a6ef"
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -632,22 +603,22 @@
|
||||
"storage": "internal",
|
||||
"files": {
|
||||
"uf2": {
|
||||
"name": "update-wismesh_tag_bootloader-v0.11.0-OTAFIX2.4.10_mbr.uf2",
|
||||
"url": "https://github.com/mikecarper/Adafruit_nRF52_Bootloader_OTAFIX/releases/download/v0.11.0-OTAFIX2.4.10/update-wismesh_tag_bootloader-v0.11.0-OTAFIX2.4.10_mbr.uf2",
|
||||
"name": "update-wismesh_tag_bootloader-v0.11.0-OTAFIX2.4.11_mbr.uf2",
|
||||
"url": "https://github.com/mikecarper/Adafruit_nRF52_Bootloader_OTAFIX/releases/download/v0.11.0-OTAFIX2.4.11/update-wismesh_tag_bootloader-v0.11.0-OTAFIX2.4.11_mbr.uf2",
|
||||
"size": 84992,
|
||||
"sha256": "71116deddf4aa41d96408fbc4f3702a07fcabd913a6c0c61c624f26145185541"
|
||||
"sha256": "0d45f1d73fdcea41bf647347096b33919ae69ce957a06d6ff0e48be8767f1081"
|
||||
},
|
||||
"zip": {
|
||||
"name": "wismesh_tag_bootloader-v0.11.0-OTAFIX2.4.10_s140_6.1.1.zip",
|
||||
"url": "https://github.com/mikecarper/Adafruit_nRF52_Bootloader_OTAFIX/releases/download/v0.11.0-OTAFIX2.4.10/wismesh_tag_bootloader-v0.11.0-OTAFIX2.4.10_s140_6.1.1.zip",
|
||||
"name": "wismesh_tag_bootloader-v0.11.0-OTAFIX2.4.11_s140_6.1.1.zip",
|
||||
"url": "https://github.com/mikecarper/Adafruit_nRF52_Bootloader_OTAFIX/releases/download/v0.11.0-OTAFIX2.4.11/wismesh_tag_bootloader-v0.11.0-OTAFIX2.4.11_s140_6.1.1.zip",
|
||||
"size": 192834,
|
||||
"sha256": "23088b4263d1ea1e2e594094ee2345c26b3d37e89a302862bbb4b1848baa99c4"
|
||||
"sha256": "716520cce6e46b7b2d84272a8a5a29d1c38344dfef7455d974969b700527fda3"
|
||||
},
|
||||
"hex": {
|
||||
"name": "wismesh_tag_bootloader-v0.11.0-OTAFIX2.4.10_s140_6.1.1.hex",
|
||||
"url": "https://github.com/mikecarper/Adafruit_nRF52_Bootloader_OTAFIX/releases/download/v0.11.0-OTAFIX2.4.10/wismesh_tag_bootloader-v0.11.0-OTAFIX2.4.10_s140_6.1.1.hex",
|
||||
"size": 530818,
|
||||
"sha256": "e59dbc29d7ab3aab00f0838195f521b6f48339e12e4f64c78c44d6ee9d6a0340"
|
||||
"name": "wismesh_tag_bootloader-v0.11.0-OTAFIX2.4.11_s140_6.1.1.hex",
|
||||
"url": "https://github.com/mikecarper/Adafruit_nRF52_Bootloader_OTAFIX/releases/download/v0.11.0-OTAFIX2.4.11/wismesh_tag_bootloader-v0.11.0-OTAFIX2.4.11_s140_6.1.1.hex",
|
||||
"size": 530862,
|
||||
"sha256": "d7503e65e4f37d028c58650bd31236ca493cff5dae1700e00baa8bbe2dbdca2a"
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -671,22 +642,22 @@
|
||||
"storage": "qspi",
|
||||
"files": {
|
||||
"uf2": {
|
||||
"name": "update-xiao_nrf52840_ble_bootloader-v0.11.0-OTAFIX2.4.10_mbr.uf2",
|
||||
"url": "https://github.com/mikecarper/Adafruit_nRF52_Bootloader_OTAFIX/releases/download/v0.11.0-OTAFIX2.4.10/update-xiao_nrf52840_ble_bootloader-v0.11.0-OTAFIX2.4.10_mbr.uf2",
|
||||
"name": "update-xiao_nrf52840_ble_bootloader-v0.11.0-OTAFIX2.4.11_mbr.uf2",
|
||||
"url": "https://github.com/mikecarper/Adafruit_nRF52_Bootloader_OTAFIX/releases/download/v0.11.0-OTAFIX2.4.11/update-xiao_nrf52840_ble_bootloader-v0.11.0-OTAFIX2.4.11_mbr.uf2",
|
||||
"size": 86016,
|
||||
"sha256": "e78ac1a5f71ef5c3625c9742fd88ac2b1d807301c0adcca3db3b6afbafc92cb8"
|
||||
"sha256": "1c6f18a21e0a6992f0ec2cb8f48e79ada5ccd7111b5b53f73b679c4dad10a9f5"
|
||||
},
|
||||
"zip": {
|
||||
"name": "xiao_nrf52840_ble_bootloader-v0.11.0-OTAFIX2.4.10_s140_7.3.0.zip",
|
||||
"url": "https://github.com/mikecarper/Adafruit_nRF52_Bootloader_OTAFIX/releases/download/v0.11.0-OTAFIX2.4.10/xiao_nrf52840_ble_bootloader-v0.11.0-OTAFIX2.4.10_s140_7.3.0.zip",
|
||||
"name": "xiao_nrf52840_ble_bootloader-v0.11.0-OTAFIX2.4.11_s140_7.3.0.zip",
|
||||
"url": "https://github.com/mikecarper/Adafruit_nRF52_Bootloader_OTAFIX/releases/download/v0.11.0-OTAFIX2.4.11/xiao_nrf52840_ble_bootloader-v0.11.0-OTAFIX2.4.11_s140_7.3.0.zip",
|
||||
"size": 194546,
|
||||
"sha256": "1cc905af2ac0ed474df0ccde310faec7a10adc4c1b78c84c255de1bcee05bb78"
|
||||
"sha256": "f0bb4832a96a625d18edb272fdc6ae2bc82ca291731f3edcd178c1c22bbe322d"
|
||||
},
|
||||
"hex": {
|
||||
"name": "xiao_nrf52840_ble_bootloader-v0.11.0-OTAFIX2.4.10_s140_7.3.0.hex",
|
||||
"url": "https://github.com/mikecarper/Adafruit_nRF52_Bootloader_OTAFIX/releases/download/v0.11.0-OTAFIX2.4.10/xiao_nrf52840_ble_bootloader-v0.11.0-OTAFIX2.4.10_s140_7.3.0.hex",
|
||||
"size": 537284,
|
||||
"sha256": "39a857cd4a2cdd5adfc10d805a9a5b2aea5c30ec474a2b1ddeb8408e3dfb1ad0"
|
||||
"name": "xiao_nrf52840_ble_bootloader-v0.11.0-OTAFIX2.4.11_s140_7.3.0.hex",
|
||||
"url": "https://github.com/mikecarper/Adafruit_nRF52_Bootloader_OTAFIX/releases/download/v0.11.0-OTAFIX2.4.11/xiao_nrf52840_ble_bootloader-v0.11.0-OTAFIX2.4.11_s140_7.3.0.hex",
|
||||
"size": 537328,
|
||||
"sha256": "dbef733404fe15e06e0d6ffa006f34094a53a19fc62314cc28764afe3deb5154"
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -700,27 +671,35 @@
|
||||
"storage": "qspi",
|
||||
"files": {
|
||||
"uf2": {
|
||||
"name": "update-xiao_nrf52840_ble_sense_bootloader-v0.11.0-OTAFIX2.4.10_mbr.uf2",
|
||||
"url": "https://github.com/mikecarper/Adafruit_nRF52_Bootloader_OTAFIX/releases/download/v0.11.0-OTAFIX2.4.10/update-xiao_nrf52840_ble_sense_bootloader-v0.11.0-OTAFIX2.4.10_mbr.uf2",
|
||||
"name": "update-xiao_nrf52840_ble_sense_bootloader-v0.11.0-OTAFIX2.4.11_mbr.uf2",
|
||||
"url": "https://github.com/mikecarper/Adafruit_nRF52_Bootloader_OTAFIX/releases/download/v0.11.0-OTAFIX2.4.11/update-xiao_nrf52840_ble_sense_bootloader-v0.11.0-OTAFIX2.4.11_mbr.uf2",
|
||||
"size": 86016,
|
||||
"sha256": "78fffe89ca5d9591f5851cbf610ea246789ec3ccf736bd8d799774bc4a82f4d3"
|
||||
"sha256": "3a412b6eb12199b68cc3651082f0fb9f16ea1398c75537138d96068776e7084a"
|
||||
},
|
||||
"zip": {
|
||||
"name": "xiao_nrf52840_ble_sense_bootloader-v0.11.0-OTAFIX2.4.10_s140_7.3.0.zip",
|
||||
"url": "https://github.com/mikecarper/Adafruit_nRF52_Bootloader_OTAFIX/releases/download/v0.11.0-OTAFIX2.4.10/xiao_nrf52840_ble_sense_bootloader-v0.11.0-OTAFIX2.4.10_s140_7.3.0.zip",
|
||||
"name": "xiao_nrf52840_ble_sense_bootloader-v0.11.0-OTAFIX2.4.11_s140_7.3.0.zip",
|
||||
"url": "https://github.com/mikecarper/Adafruit_nRF52_Bootloader_OTAFIX/releases/download/v0.11.0-OTAFIX2.4.11/xiao_nrf52840_ble_sense_bootloader-v0.11.0-OTAFIX2.4.11_s140_7.3.0.zip",
|
||||
"size": 194546,
|
||||
"sha256": "d878c8b815bd8519e840b3221219c4bef8af8c964b039f89c79d71a6641e759b"
|
||||
"sha256": "954a5ee6b0d008e6918d91f0de5af7c80cf170c42ea9f8dc0957e9698a90f5c5"
|
||||
},
|
||||
"hex": {
|
||||
"name": "xiao_nrf52840_ble_sense_bootloader-v0.11.0-OTAFIX2.4.10_s140_7.3.0.hex",
|
||||
"url": "https://github.com/mikecarper/Adafruit_nRF52_Bootloader_OTAFIX/releases/download/v0.11.0-OTAFIX2.4.10/xiao_nrf52840_ble_sense_bootloader-v0.11.0-OTAFIX2.4.10_s140_7.3.0.hex",
|
||||
"size": 537318,
|
||||
"sha256": "cf43bffaa2ba751b0bd773bd50d2e9c8f9c99741de950e6166ea416f1585f5fb"
|
||||
"name": "xiao_nrf52840_ble_sense_bootloader-v0.11.0-OTAFIX2.4.11_s140_7.3.0.hex",
|
||||
"url": "https://github.com/mikecarper/Adafruit_nRF52_Bootloader_OTAFIX/releases/download/v0.11.0-OTAFIX2.4.11/xiao_nrf52840_ble_sense_bootloader-v0.11.0-OTAFIX2.4.11_s140_7.3.0.hex",
|
||||
"size": 537362,
|
||||
"sha256": "07ccd6e07d9eebb6ce077302a393df2edb59c3106b93202a627f755c3980d53d"
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"unavailableProfiles": [
|
||||
{
|
||||
"id": "heltec_mesh_tower_v2",
|
||||
"label": "Heltec MeshTower V2 - historical internal-only profile",
|
||||
"meshcoreHardware": [
|
||||
"Heltec_tower_v2"
|
||||
],
|
||||
"reason": "Retired from normal releases in favor of the combined SD/internal profile. Existing internal-only bootloaders require a one-time local installation of the normal combined image via USB/BLE DFU or SWD; do not use a cross-profile LoRa update."
|
||||
},
|
||||
{
|
||||
"id": "gat562_mesh_watch13",
|
||||
"label": "GAT562 Mesh Watch13",
|
||||
@@ -802,7 +781,7 @@
|
||||
"meshcoreHardware": [
|
||||
"ThinkNode_M8"
|
||||
],
|
||||
"reason": "ThinkNode M8 shares some flash wiring with M1 but has different buttons and peripheral controls. It needs an M8-specific profile."
|
||||
"reason": "A dedicated M8 bootloader is awaiting factory USB identification and hardware testing. No verified download is available."
|
||||
},
|
||||
{
|
||||
"id": "wio_wm1110",
|
||||
@@ -810,7 +789,14 @@
|
||||
"meshcoreHardware": [
|
||||
"wio_wm1110"
|
||||
],
|
||||
"reason": "Wio WM1110 is a separate tracker board with different LED and peripheral pins. Its XIAO build setting does not make it XIAO hardware."
|
||||
"reason": "This firmware targets the Wio WM1110 Dev Kit (SDK carrier), whose LEDs and controls differ from Wio Tracker 1110 and XIAO. Its bootloader identity and installation route still need qualification."
|
||||
}
|
||||
]
|
||||
],
|
||||
"profileSourceOverrides": {
|
||||
"heltec_mesh_tower_v2_sdcard": {
|
||||
"source_tag": "0.11.0-OTAFIX2.4.11",
|
||||
"source_commit": "39177b9bbaa894352141734350d6ef2fab18dc4f",
|
||||
"workflow_run": 37072081200
|
||||
}
|
||||
}
|
||||
}
|
||||
+5639
-4151
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,169 @@
|
||||
(function (global) {
|
||||
"use strict";
|
||||
|
||||
function validateCatalog(catalog) {
|
||||
if (!catalog || catalog.schema !== 1 || !Array.isArray(catalog.repositories) ||
|
||||
!Array.isArray(catalog.releases) || !Array.isArray(catalog.builds) ||
|
||||
!Array.isArray(catalog.unresolved) || !catalog.layouts ||
|
||||
typeof catalog.layouts !== "object" || Array.isArray(catalog.layouts)) {
|
||||
throw new Error("Unsupported partition catalog format");
|
||||
}
|
||||
return catalog;
|
||||
}
|
||||
|
||||
function snapshot(catalog) {
|
||||
return {
|
||||
schema: catalog.schema,
|
||||
generatedAt: catalog.generatedAt,
|
||||
repositories: catalog.repositories,
|
||||
counts: {
|
||||
releases: catalog.releases.length,
|
||||
builds: catalog.builds.length,
|
||||
layouts: Object.keys(catalog.layouts).length,
|
||||
unresolved: catalog.unresolved.length,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function jsonView(catalog, view) {
|
||||
validateCatalog(catalog);
|
||||
if (view === "all") return JSON.stringify(catalog, null, 2);
|
||||
const result = snapshot(catalog);
|
||||
if (view === "layouts") result.layouts = catalog.layouts;
|
||||
else if (view === "releases") result.releases = catalog.releases;
|
||||
else throw new Error("Unknown catalog view");
|
||||
return JSON.stringify(result, null, 2);
|
||||
}
|
||||
|
||||
function boardMinimums(catalog) {
|
||||
validateCatalog(catalog);
|
||||
const boards = new Map();
|
||||
catalog.builds.forEach(function (build) {
|
||||
const key = build.board.toLowerCase();
|
||||
if (!boards.has(key)) {
|
||||
boards.set(key, {
|
||||
board: build.board, smallestBytes: null,
|
||||
hasDualOta: false, hasNonDualOta: false, hasUnknownLayout: false,
|
||||
});
|
||||
}
|
||||
const row = boards.get(key);
|
||||
const layout = Object.prototype.hasOwnProperty.call(catalog.layouts, build.layout)
|
||||
? catalog.layouts[build.layout] : null;
|
||||
if (!layout || !Array.isArray(layout.partitions)) {
|
||||
row.hasUnknownLayout = true;
|
||||
return;
|
||||
}
|
||||
const apps = layout.partitions.filter(function (entry) {
|
||||
return Array.isArray(entry) && entry[0] === 0 &&
|
||||
Number.isSafeInteger(entry[3]) && entry[3] > 0;
|
||||
});
|
||||
if (!apps.length) {
|
||||
row.hasUnknownLayout = true;
|
||||
return;
|
||||
}
|
||||
const smallest = Math.min.apply(null, apps.map((entry) => entry[3]));
|
||||
row.smallestBytes = row.smallestBytes === null
|
||||
? smallest : Math.min(row.smallestBytes, smallest);
|
||||
if (layout.dualOta === true) row.hasDualOta = true;
|
||||
else if (layout.dualOta === false) row.hasNonDualOta = true;
|
||||
else row.hasUnknownLayout = true;
|
||||
});
|
||||
return Array.from(boards.values()).sort(function (left, right) {
|
||||
return left.board.localeCompare(right.board, "en", { sensitivity: "base" });
|
||||
});
|
||||
}
|
||||
|
||||
function formatSize(bytes) {
|
||||
return bytes === null ? "Unknown" : (bytes / 1048576).toLocaleString(
|
||||
"en-US", { maximumFractionDigits: 4 }
|
||||
) + " MiB";
|
||||
}
|
||||
|
||||
function otaLayouts(row) {
|
||||
if (row.hasUnknownLayout) return "Unknown layouts present";
|
||||
if (row.hasDualOta && row.hasNonDualOta) return "Mixed (some lack dual OTA)";
|
||||
return row.hasDualOta ? "Dual-slot OTA" : "No dual-slot OTA";
|
||||
}
|
||||
|
||||
async function initializeViewer() {
|
||||
const root = document.getElementById("partition-catalog");
|
||||
if (!root || root.dataset.initialized) return;
|
||||
root.dataset.initialized = "true";
|
||||
const selector = document.getElementById("partition-catalog-view");
|
||||
const status = document.getElementById("partition-catalog-status");
|
||||
const code = document.getElementById("partition-catalog-json");
|
||||
const link = document.getElementById("partition-catalog-open");
|
||||
const raw = document.getElementById("partition-catalog-raw");
|
||||
const search = document.getElementById("partition-catalog-search");
|
||||
const tbody = document.getElementById("partition-catalog-boards");
|
||||
const count = document.getElementById("partition-catalog-count");
|
||||
try {
|
||||
const response = await global.fetch(link.href, { cache: "no-cache" });
|
||||
if (!response.ok) throw new Error("HTTP " + response.status);
|
||||
const catalog = validateCatalog(await response.json());
|
||||
const render = function () {
|
||||
// Catalog text is data, never HTML. Only format the large builds array
|
||||
// when the reader explicitly requests the complete catalog.
|
||||
code.textContent = jsonView(catalog, selector.value);
|
||||
};
|
||||
raw.addEventListener("toggle", function () { if (raw.open) render(); });
|
||||
if (raw.open) render();
|
||||
selector.disabled = false;
|
||||
selector.addEventListener("change", render);
|
||||
const rows = boardMinimums(catalog).map(function (board) {
|
||||
const tr = document.createElement("tr");
|
||||
const name = document.createElement("th");
|
||||
name.scope = "row";
|
||||
name.textContent = board.board;
|
||||
const size = document.createElement("td");
|
||||
size.textContent = formatSize(board.smallestBytes);
|
||||
if (board.smallestBytes !== null) {
|
||||
const bytes = document.createElement("small");
|
||||
bytes.textContent = board.smallestBytes.toLocaleString("en-US") + " bytes";
|
||||
size.appendChild(bytes);
|
||||
}
|
||||
const layout = document.createElement("td");
|
||||
layout.textContent = otaLayouts(board);
|
||||
tr.appendChild(name);
|
||||
tr.appendChild(size);
|
||||
tr.appendChild(layout);
|
||||
tbody.appendChild(tr);
|
||||
return { name: board.board.toLowerCase(), element: tr };
|
||||
});
|
||||
const filter = function () {
|
||||
const query = search.value.trim().toLowerCase();
|
||||
let visible = 0;
|
||||
rows.forEach(function (row) {
|
||||
row.element.hidden = !row.name.includes(query);
|
||||
if (!row.element.hidden) visible++;
|
||||
});
|
||||
count.textContent = "Showing " + visible + " of " + rows.length + " boards.";
|
||||
};
|
||||
filter();
|
||||
search.disabled = false;
|
||||
search.addEventListener("input", filter);
|
||||
document.getElementById("partition-catalog-table").hidden = false;
|
||||
const counts = snapshot(catalog).counts;
|
||||
status.textContent = "Snapshot " + catalog.generatedAt + ": " +
|
||||
counts.builds.toLocaleString("en-US") + " builds, " +
|
||||
counts.releases.toLocaleString("en-US") + " releases, " +
|
||||
counts.layouts + " layouts, " + counts.unresolved + " unresolved assets.";
|
||||
} catch (error) {
|
||||
status.textContent = "Could not load the catalog: " + error.message +
|
||||
". Use Open JSON or Download JSON to access the file directly.";
|
||||
code.textContent = "Catalog preview unavailable.";
|
||||
}
|
||||
}
|
||||
|
||||
const api = Object.freeze({
|
||||
validateCatalog, jsonView, boardMinimums, formatSize, otaLayouts, initializeViewer,
|
||||
});
|
||||
if (typeof module === "object" && module.exports) module.exports = api;
|
||||
if (typeof document !== "undefined") {
|
||||
if (document.readyState === "loading") {
|
||||
document.addEventListener("DOMContentLoaded", initializeViewer, { once: true });
|
||||
} else {
|
||||
initializeViewer();
|
||||
}
|
||||
}
|
||||
})(typeof globalThis !== "undefined" ? globalThis : this);
|
||||
@@ -210,6 +210,7 @@
|
||||
hops: "all",
|
||||
channel: "#rgdata",
|
||||
incoming: "none",
|
||||
hashBytes: 0,
|
||||
pathKind: "none",
|
||||
pathPrefix: "",
|
||||
sender: "",
|
||||
@@ -255,6 +256,9 @@
|
||||
hops: normalizeHops(input.hops),
|
||||
channel: normalizeChannel(input.channel, true),
|
||||
incoming: normalizeIncoming(input.incoming),
|
||||
hashBytes: ["", "0", "any", "*"].includes(clean(input.hashBytes).toLowerCase())
|
||||
? 0 : clean(input.hashBytes) === "2+" ? "2+"
|
||||
: requiredInteger(input.hashBytes, 1, 3, "Path hash bytes"),
|
||||
pathKind: enumValue(input.pathKind, PATH_KINDS, "Path matcher", "none"),
|
||||
pathPrefix: "",
|
||||
sender: clean(input.sender),
|
||||
@@ -276,6 +280,11 @@
|
||||
|
||||
if (rule.pathKind === "prefix") rule.pathPrefix = normalizePathPrefix(input.pathPrefix, 3);
|
||||
if (rule.pathKind === "prefix" && !rule.pathPrefix) throw new FilterToolError("Prefix path matcher requires at least one pbyte ID.");
|
||||
if (rule.pathKind === "prefix" && rule.hashBytes
|
||||
&& (rule.hashBytes === "2+" ? rule.pathPrefix.split(",")[0].length / 2 < 2
|
||||
: rule.pathPrefix.split(",")[0].length / 2 !== rule.hashBytes)) {
|
||||
throw new FilterToolError("Path hash bytes conflicts with prefix width.");
|
||||
}
|
||||
if (rule.sender) {
|
||||
rule.sender = requiredText(rule.sender, "Sender", 31);
|
||||
if (rule.sender.includes(":")) throw new FilterToolError("Sender matcher cannot contain a colon.");
|
||||
@@ -356,6 +365,7 @@
|
||||
];
|
||||
if (rule.channel) matches.push(`channel=${quoteDsl(rule.channel)}`);
|
||||
if (rule.incoming !== "any") matches.push(`rx.scope=${quoteDsl(rule.incoming)}`);
|
||||
if (rule.hashBytes) matches.push(`hashbytes=${rule.hashBytes}`);
|
||||
if (rule.pathKind === "prefix") matches.push(`path=prefix:${rule.pathPrefix}`);
|
||||
else if (rule.pathKind !== "none") matches.push(`path=${rule.pathKind}`);
|
||||
if (rule.sender) matches.push(`sender=${quoteDsl(rule.sender)}`);
|
||||
@@ -462,6 +472,7 @@
|
||||
else if (name === "hops") rule.hops = value;
|
||||
else if (name === "channel") rule.channel = value;
|
||||
else if (name === "rx.scope") rule.incoming = value;
|
||||
else if (name === "hashbytes") rule.hashBytes = value;
|
||||
else if (name === "path") {
|
||||
if (value.toLowerCase().startsWith("prefix:")) {
|
||||
rule.pathKind = "prefix";
|
||||
@@ -568,6 +579,9 @@
|
||||
else if (rule.pathKind === "blacklist") conditions.push("a path matching the passive blacklist");
|
||||
else if (rule.pathKind.startsWith("bucket:")) conditions.push(`a path matching ${rule.pathKind}`);
|
||||
else if (rule.pathKind.startsWith("loop:")) conditions.push(`the ${rule.pathKind.slice(5)} own-ID loop threshold`);
|
||||
if (rule.hashBytes) conditions.push(rule.hashBytes === "2+"
|
||||
? "2- or 3-byte encoded path hashes, including zero hops"
|
||||
: `${rule.hashBytes}-byte encoded path hashes, including zero hops`);
|
||||
if (rule.sender) conditions.push(`decrypted sender "${rule.sender}"`);
|
||||
conditions.push(`traffic path ${rule.via}`);
|
||||
if (rule.tempRadio !== "any") conditions.push(`temporary radio ${rule.tempRadio}`);
|
||||
@@ -596,7 +610,9 @@
|
||||
if (rule.verdict === "drop" && rule.route === "flood" && rule.type === "any"
|
||||
&& rule.hops === "all" && !rule.channel && rule.incoming === "any"
|
||||
&& rule.pathKind === "none" && rule.tempRadio === "any") {
|
||||
warnings.push("This is a global drop rule across both flood route types.");
|
||||
warnings.push(rule.hashBytes
|
||||
? `This drops all ${rule.hashBytes === "2+" ? "2- or 3" : rule.hashBytes}-byte path-hash packets across both flood route types.`
|
||||
: "This is a global drop rule across both flood route types.");
|
||||
}
|
||||
if (limitsRemoteManagement(rule)) {
|
||||
const zeroHop = minimumHops === 0 && rule.pathKind === "none";
|
||||
@@ -622,6 +638,7 @@
|
||||
bytes += 3 + 3 + 3;
|
||||
if (rule.channel) bytes += 3 + (rule.channel.startsWith("hash:") ? 1 : rule.channel.length === 32 || rule.channel.length === 64 ? rule.channel.length / 2 : rule.channel.length);
|
||||
if (rule.incoming !== "any") bytes += 3 + rule.incoming.length;
|
||||
if (rule.hashBytes) bytes += 3;
|
||||
if (rule.pathKind === "prefix") bytes += 4 + rule.pathPrefix.replace(/,/g, "").length / 2;
|
||||
else if (rule.pathKind !== "none") bytes += 3;
|
||||
if (rule.sender) bytes += 3 + rule.sender.length;
|
||||
@@ -965,12 +982,20 @@
|
||||
const buckets = clean(input.buckets)
|
||||
? clean(input.buckets).split(",").map((value) => requiredInteger(value, 1, 6, "Path bucket"))
|
||||
: [];
|
||||
const path = normalizePacketPath(input.path);
|
||||
const inferredWidth = path ? path.split(",")[0].length / 2 : 1;
|
||||
const hashBytes = ["", "auto"].includes(clean(input.hashBytes).toLowerCase())
|
||||
? inferredWidth : requiredInteger(input.hashBytes, 1, 3, "Packet path hash bytes");
|
||||
if (path && hashBytes !== inferredWidth) {
|
||||
throw new FilterToolError("Packet path hash bytes conflicts with received path width.");
|
||||
}
|
||||
return {
|
||||
route: enumValue(input.route, ["unscoped_flood", "scoped_flood"], "Packet route", "unscoped_flood"),
|
||||
type,
|
||||
hops: requiredInteger(input.hops, 0, 63, "Packet hops"),
|
||||
channel,
|
||||
path: normalizePacketPath(input.path),
|
||||
path,
|
||||
hashBytes,
|
||||
scopeStatus,
|
||||
scopeName: clean(input.scopeName).replace(/^#/, ""),
|
||||
regionName: clean(input.regionName),
|
||||
@@ -1017,6 +1042,8 @@
|
||||
if (packet.hops < minimum || packet.hops > maximum) misses.push(`hop ${packet.hops} is outside ${rule.hops}`);
|
||||
if (rule.channel && rule.channel !== packet.channel) misses.push("channel differs or is unavailable");
|
||||
if (!incomingMatches(rule.incoming, packet)) misses.push(`original scope does not satisfy ${rule.incoming}`);
|
||||
if (rule.hashBytes && (rule.hashBytes === "2+" ? packet.hashBytes < 2
|
||||
: rule.hashBytes !== packet.hashBytes)) misses.push(`path hash width is ${packet.hashBytes} bytes`);
|
||||
if (rule.pathKind === "prefix") {
|
||||
const wanted = rule.pathPrefix.split(",");
|
||||
const actual = packet.path ? packet.path.split(",") : [];
|
||||
@@ -1139,6 +1166,12 @@
|
||||
}
|
||||
|
||||
const EXAMPLES = Object.freeze({
|
||||
hash_width: Object.freeze([
|
||||
{ ...documentedDropRule("drop-one-byte-paths", "any", "all"), hashBytes: 1 },
|
||||
]),
|
||||
hash_width_two_plus: Object.freeze([
|
||||
{ ...documentedDropRule("drop-two-plus-byte-paths", "any", "all"), hashBytes: "2+" },
|
||||
]),
|
||||
channel_scope: Object.freeze([
|
||||
{
|
||||
...defaultRule("rgdata-scope"),
|
||||
@@ -1312,6 +1345,7 @@
|
||||
hops: field("hops").value,
|
||||
channel: field("channel").value,
|
||||
incoming: field("incoming").value,
|
||||
hashBytes: field("hash-bytes").value,
|
||||
pathKind: field("path-kind").value,
|
||||
pathPrefix: field("path-prefix").value,
|
||||
sender: field("sender").value,
|
||||
@@ -1346,6 +1380,7 @@
|
||||
hops: rule.hops,
|
||||
channel: rule.channel,
|
||||
incoming: rule.incoming,
|
||||
"hash-bytes": rule.hashBytes,
|
||||
"path-kind": rule.pathKind,
|
||||
"path-prefix": rule.pathPrefix,
|
||||
sender: rule.sender,
|
||||
@@ -1575,6 +1610,7 @@
|
||||
hops: packetField("hops").value,
|
||||
channel: packetField("channel").value,
|
||||
path: packetField("path").value,
|
||||
hashBytes: packetField("hash-bytes").value,
|
||||
scopeStatus: packetField("scope-status").value,
|
||||
scopeName: packetField("scope-name").value,
|
||||
regionName: packetField("region-name").value,
|
||||
@@ -1593,6 +1629,7 @@
|
||||
packetField("type").value = decoded.type;
|
||||
packetField("hops").value = String(decoded.hops);
|
||||
packetField("path").value = decoded.pathIds.join(",");
|
||||
packetField("hash-bytes").value = String(decoded.pathHashBytes);
|
||||
packetField("channel").value = decoded.channelHash;
|
||||
packetField("scope-status").value = decoded.routeCode === 0 ? "unknown" : "none";
|
||||
packetField("scope-name").value = "";
|
||||
|
||||
@@ -23,6 +23,11 @@
|
||||
]);
|
||||
|
||||
const FACET_FIELDS = Object.freeze(FILTER_FIELDS.concat(["install"]));
|
||||
// Versioned picker selections stay self-contained as release catalogs change.
|
||||
const SHORT_SELECTION_FIELDS = Object.freeze({
|
||||
c: "chipFamily", g: "hardwareFamily", h: "hardware", r: "role",
|
||||
l: "logging", o: "ota", m: "mode", f: "feature", v: "variant", i: "install",
|
||||
});
|
||||
const PROFILE_FIELDS = Object.freeze(["ota", "feature", "variant"]);
|
||||
const CHOICE_FIELDS = Object.freeze([
|
||||
"chipFamily", "hardwareFamily", "hardware", "role", "logging",
|
||||
@@ -254,8 +259,13 @@
|
||||
const acceptedTags = new Set(RELEASE_PAGE_PREFIXES.map(function (prefix) {
|
||||
return prefix + familyTag;
|
||||
}));
|
||||
const chunkGroups = ["companion"].concat(RELEASE_PAGE_PREFIXES.filter(Boolean).map(function (prefix) {
|
||||
return prefix.slice(0, -1);
|
||||
}));
|
||||
const chunkTag = new RegExp("^(?:" + chunkGroups.map(escapeRegExp).join("|") +
|
||||
")-(?:[2-9]|[1-9][0-9]+)-" + escapeRegExp(familyTag) + "$");
|
||||
const familyReleases = publicReleases.filter(function (release) {
|
||||
return acceptedTags.has(release.tag_name);
|
||||
return acceptedTags.has(release.tag_name) || chunkTag.test(release.tag_name);
|
||||
}).sort(function (a, b) {
|
||||
return releaseDate(b).localeCompare(releaseDate(a));
|
||||
});
|
||||
@@ -317,9 +327,9 @@
|
||||
// Corrected assets can have a new source hash on the existing release
|
||||
// page. Keep the complete version/flavor boundary, allowing its commit
|
||||
// suffix to differ from the original tag's commit.
|
||||
const familyRoot = familyTag.replace(/-[0-9a-f]{7,8}$/i, "");
|
||||
const familyRoot = familyTag.replace(/-[0-9a-f]{7,40}$/i, "");
|
||||
const sourceSuffix = familyRoot === familyTag
|
||||
? "(?:-[0-9a-f]{7,8})?" : "-[0-9a-f]{7,8}(?:-[0-9a-f]{7,8})?";
|
||||
? "(?:-[0-9a-f]{7,40})?" : "-[0-9a-f]{7,40}(?:-[0-9a-f]{7,40})?";
|
||||
const versionPattern = new RegExp(
|
||||
"^(.*?)-(ota-)?" + escapeRegExp(familyRoot) + sourceSuffix + "$",
|
||||
"i"
|
||||
@@ -407,6 +417,12 @@
|
||||
|
||||
function variantForProfile(role, tail) {
|
||||
let value = String(tail);
|
||||
if (["repeater", "room", "sensor"].includes(role)) {
|
||||
// Publication suffixes distinguish downloads, not physical wiring.
|
||||
// Sensor support itself is read from qualified release controls below.
|
||||
value = value.replace(/-(?:full|reduced)-ota$/i, "")
|
||||
.replace(/-reduced$/i, "");
|
||||
}
|
||||
if (role === "companion") {
|
||||
value = value.replace(
|
||||
/^(?:full|wifi_mqtt|usb_ble|usb_wifi|ble|usb|wifi|serial|ethernet)(?=$|[_-])/i,
|
||||
@@ -468,9 +484,14 @@
|
||||
? "lora-source"
|
||||
: "";
|
||||
let variant = variantForProfile(parts.role, parts.tail);
|
||||
if (/^RAK_(?:3401|4631)_repeater_unified_lora_ota$/i.test(target)) {
|
||||
if (/^RAK_(?:3401|4631)_repeater_unified_lora_ota(?:-(?:full|reduced)(?:-ota)?)?$/i.test(target)) {
|
||||
variant = "default";
|
||||
}
|
||||
if (/^RAK_4631_repeater_ethernet(?:-(?:full|reduced)(?:-ota)?)?$/i.test(target)) {
|
||||
// The RAK13800 Ethernet image is a real hardware choice, separate from
|
||||
// adaptive storage. Label it consistently for both sensor policies.
|
||||
variant = "ethernet";
|
||||
}
|
||||
if (sourceHardware === "Station_G2_logging") {
|
||||
variant = variant === "default"
|
||||
? "rx-boosted"
|
||||
@@ -560,11 +581,13 @@
|
||||
heltec_t096_repeater_bridge_rs232_lora_ota_no_external_sensors:
|
||||
"heltec_t096_repeater_lora_ota_no_external_sensors",
|
||||
rak_4631_repeater_bridge_rs232_serial1_lora_ota_no_external_sensors:
|
||||
"rak_4631_repeater_lora_ota_no_external_sensors",
|
||||
["rak_4631_repeater_unified_lora_ota", "rak_4631_repeater_lora_ota_no_external_sensors"],
|
||||
rak_4631_repeater_bridge_rs232_serial2_lora_ota_no_external_sensors:
|
||||
"rak_4631_repeater_lora_ota_no_external_sensors",
|
||||
rak_4631_repeater_bridge_rs232_serial1: "rak_4631_repeater",
|
||||
rak_4631_repeater_bridge_rs232_serial2: "rak_4631_repeater",
|
||||
["rak_4631_repeater_unified_lora_ota", "rak_4631_repeater_lora_ota_no_external_sensors"],
|
||||
rak_4631_repeater_bridge_rs232_serial1:
|
||||
["rak_4631_repeater_unified_lora_ota", "rak_4631_repeater"],
|
||||
rak_4631_repeater_bridge_rs232_serial2:
|
||||
["rak_4631_repeater_unified_lora_ota", "rak_4631_repeater"],
|
||||
promicro_repeater_bridge_rs232_serial1: "promicro_repeater",
|
||||
heltec_t114_without_display_repeater_bridge_rs232:
|
||||
"heltec_t114_without_display_repeater",
|
||||
@@ -592,11 +615,19 @@
|
||||
combinedUsbBleKeys.has(key);
|
||||
const replacedTerminal = profile.role === "terminal" &&
|
||||
terminalReplacementKeys.has(key);
|
||||
const rs232Replacement = mergedRs232Targets[
|
||||
String(profile.target || "").toLowerCase()
|
||||
];
|
||||
const target = String(profile.target || "").toLowerCase();
|
||||
const sensorSuffix = (target.match(/-(?:full|reduced)$/) || [""])[0];
|
||||
const baseTarget = sensorSuffix ? target.slice(0, -sensorSuffix.length) : target;
|
||||
const rs232Replacement = mergedRs232Targets[baseTarget];
|
||||
// Recommendations prefer the combined UART2 image on RAK4631. Exact
|
||||
// Serial1 files remain in raw rows for existing GPS-free installations;
|
||||
// this preference does not migrate their wiring or OTA identity.
|
||||
// Preserve the exact sensor policy: a Full image does not replace a
|
||||
// missing Reduced image, or an older unsuffixed compatibility image.
|
||||
const replacedRs232 = Boolean(rs232Replacement &&
|
||||
targets.has(rs232Replacement));
|
||||
[].concat(rs232Replacement).some(function (replacement) {
|
||||
return targets.has(replacement + sensorSuffix);
|
||||
}));
|
||||
return !(replacedAttachedTransport && fullKeys.has(key)) &&
|
||||
!replacedByUsbBle && !replacedTerminal && !replacedRs232;
|
||||
});
|
||||
@@ -609,6 +640,11 @@
|
||||
|
||||
return (profiles || []).filter(function (profile) {
|
||||
const target = String(profile && profile.target || "");
|
||||
// TLora cannot fit UART, MQTT and ESP-NOW together. Its normal Full
|
||||
// UART image and Full MQTT observer are both intentional choices.
|
||||
if (/^LilyGo_TLora_V2_1_1_6_repeater_observer_mqtt_?-full-usb-wifi$/i.test(target)) {
|
||||
return true;
|
||||
}
|
||||
// Same-partition ESP32 migrations publish the ordinary target's Full
|
||||
// image with its established mOTA identity. The former observer-named
|
||||
// Full image remains in the release for deployed devices that still use
|
||||
@@ -628,8 +664,8 @@
|
||||
}));
|
||||
return (profiles || []).filter(function (profile) {
|
||||
const target = String(profile && profile.target || "").toLowerCase();
|
||||
const match = /^rak_(3401|4631)_repeater_(?:lora_ota_no_external_sensors|w25q16_lora_ota|rak15001_slot_c_lora_ota|rak13302_w25q16_lora_ota)$/.exec(target);
|
||||
return !match || !targets.has("rak_" + match[1] + "_repeater_unified_lora_ota");
|
||||
const match = /^rak_(3401|4631)_repeater_(?:lora_ota_no_external_sensors|w25q16_lora_ota|rak15001_slot_c_lora_ota|rak13302_w25q16_lora_ota)(-(?:full|reduced))?$/.exec(target);
|
||||
return !match || !targets.has("rak_" + match[1] + "_repeater_unified_lora_ota" + (match[2] || ""));
|
||||
});
|
||||
}
|
||||
|
||||
@@ -652,10 +688,7 @@
|
||||
function applyMergedRak4631RepeaterCapabilities(profiles) {
|
||||
return (profiles || []).map(function (profile) {
|
||||
const target = String(profile && profile.target || "").toLowerCase();
|
||||
if (
|
||||
target !== "rak_4631_repeater" &&
|
||||
target !== "rak_4631_repeater_lora_ota_no_external_sensors"
|
||||
) {
|
||||
if (!/^rak_4631_repeater(?:_lora_ota_no_external_sensors|_unified_lora_ota)?(?:-(?:full|reduced))?$/.test(target)) {
|
||||
return profile;
|
||||
}
|
||||
|
||||
@@ -718,6 +751,29 @@
|
||||
if (typeof controls.dedicatedUsbLogging === "boolean") {
|
||||
profile.dedicatedUsbLogging = controls.dedicatedUsbLogging;
|
||||
}
|
||||
const sensorSource = controls.sensorProfileSource;
|
||||
const sensorSourceMatches = typeof sensorSource === "string" && /^[0-9a-f]{40}$/i.test(sensorSource) &&
|
||||
profile.files.length > 0 && profile.files.every(function (file) {
|
||||
const suffix = file.name.replace(/\.(bin|hex|uf2|zip)$/i, "").match(/-([0-9a-f]{7,40})$/i);
|
||||
return suffix && sensorSource.toLowerCase().startsWith(suffix[1].toLowerCase());
|
||||
});
|
||||
if (controls.platform === "NRF52_PLATFORM" && ["repeater", "room", "sensor"].includes(profile.role) &&
|
||||
controls.otaRole === "lora-receiver" && Array.isArray(controls.updateMethods) &&
|
||||
controls.updateMethods.includes("lora") && sensorSourceMatches &&
|
||||
["full", "reduced"].includes(controls.sensorProfile)) {
|
||||
profile.sensorProfile = controls.sensorProfile;
|
||||
profile.feature = controls.sensorProfile === "full" ? "full" : "standard";
|
||||
if (controls.sensorProfile === "full") {
|
||||
// A full publication may retain an old reduced logical OTA identity.
|
||||
// That identity must not mislabel the actual qualified sensor policy.
|
||||
profile.variant = variantWithoutSensorMarker(profile.variant);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function variantWithoutSensorMarker(value) {
|
||||
return String(value || "default").replace(/(?:^|-)no-external-sensors(?=-|$)/g, "")
|
||||
.replace(/^-+|-+$/g, "").replace(/--+/g, "-") || "default";
|
||||
}
|
||||
|
||||
function hardwareFamilyFor(hardware, hardwareNames) {
|
||||
@@ -883,7 +939,7 @@
|
||||
}
|
||||
if (profile.controls.espnowBridge &&
|
||||
["repeater", "room"].includes(profile.role) && profile.mode === "standard") {
|
||||
profile.connectionModes = ["standard", "espnow"];
|
||||
profile.connectionModes = (profile.connectionModes || ["standard"]).concat("espnow");
|
||||
}
|
||||
}
|
||||
profile.hardwareFamily = hardwareFamilyFor(
|
||||
@@ -998,6 +1054,11 @@
|
||||
}
|
||||
|
||||
function firmwareProfileLabel(profile) {
|
||||
if (profile.sensorProfile) {
|
||||
const label = (profile.sensorProfile === "full" ? "Full supported sensors" : "Reduced sensors") + " + LoRa OTA";
|
||||
const variant = variantWithoutSensorMarker(profile.variant);
|
||||
return label + (variant !== "default" ? " - " + humanizeVariant(variant, profile.hardware) : "");
|
||||
}
|
||||
const parts = [];
|
||||
if (profile.feature === "full") parts.push("Full");
|
||||
else if (profile.feature === "standard" && profile.ota !== "lora-receiver") {
|
||||
@@ -1045,8 +1106,113 @@
|
||||
});
|
||||
}
|
||||
|
||||
// Count compiled builds, not files: merged and app-only downloads can belong
|
||||
// to the same build, but still require different installation instructions.
|
||||
function selectionProgress(profiles, filters) {
|
||||
const count = (profiles || []).filter(function (profile) {
|
||||
return profileMatchesFacets(profile, filters);
|
||||
}).length;
|
||||
return {
|
||||
count: count,
|
||||
state: count === 1 ? "single" : count ? "multiple" : "empty",
|
||||
title: count === 1 ? "1 firmware build left" : count ? count + " firmware builds remain"
|
||||
: "No matching firmware builds",
|
||||
note: count === 1
|
||||
? filters && filters.install ? "Confirm your board and install choice."
|
||||
: "Choose an install operation."
|
||||
: count ? "Choose options to narrow the results."
|
||||
: "Change or clear choices to see available builds.",
|
||||
};
|
||||
}
|
||||
|
||||
function choicesUseSameFirmware(profiles, filters, field, options) {
|
||||
// Never equate install operations, even when they belong to one build.
|
||||
if (!["hardware", "role", "logging", "mode", "firmwareProfile"].includes(field)) {
|
||||
return false;
|
||||
}
|
||||
const ignored = field === "firmwareProfile" ? PROFILE_FIELDS : field;
|
||||
const compatible = (profiles || []).filter(function (profile) {
|
||||
return profileMatchesFacets(profile, filters, ignored);
|
||||
});
|
||||
const choices = (options || []).filter(function (option) { return option.value; });
|
||||
if (!compatible.length || !choices.length) return false;
|
||||
// Ignore the current choice before comparing alternatives. Having one
|
||||
// result now does not imply that another button would keep the same build.
|
||||
return choices.every(function (option) {
|
||||
const choice = field === "firmwareProfile" ? firmwareProfileFilters(option.value)
|
||||
: { [field]: option.value };
|
||||
if (!choice) return false;
|
||||
const selection = Object.assign({}, filters, choice);
|
||||
return compatible.every(function (profile) {
|
||||
return profileMatchesFacets(profile, selection);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function selectionRequirements(profiles, filters) {
|
||||
const chosen = filters || {};
|
||||
const hiddenFields = [];
|
||||
let installKind = chosen.install || "";
|
||||
// Board and role are deliberate choices. Once known, do not ask for
|
||||
// settings that leave every compatible build unchanged.
|
||||
if (chosen.hardware && chosen.role) {
|
||||
const context = Object.assign({}, chosen);
|
||||
// Classify against board/role, not mutually narrowing settings. A real
|
||||
// alternative must stay visible so a selected filter can be cleared.
|
||||
["logging", "mode"].concat(PROFILE_FIELDS).forEach(function (field) {
|
||||
delete context[field];
|
||||
});
|
||||
const boardContext = Object.assign({}, context);
|
||||
delete boardContext.install;
|
||||
const boardProfiles = (profiles || []).filter(function (profile) {
|
||||
return profileMatchesFacets(profile, boardContext);
|
||||
});
|
||||
function supportsSelection(field) {
|
||||
const selection = field === "firmwareProfile"
|
||||
? PROFILE_FIELDS.reduce(function (parts, part) {
|
||||
if (chosen[part]) parts[part] = chosen[part];
|
||||
return parts;
|
||||
}, {}) : chosen[field] ? { [field]: chosen[field] } : {};
|
||||
return boardProfiles.some(function (profile) {
|
||||
return profileMatchesFacets(profile, selection);
|
||||
});
|
||||
}
|
||||
// Invalid old links need a visible Any control. Do not conceal their
|
||||
// requested value or replace it with another install format.
|
||||
if (!supportsSelection("install")) delete context.install;
|
||||
["logging", "firmwareProfile", "mode"].forEach(function (field) {
|
||||
const options = field === "firmwareProfile"
|
||||
? firmwareProfileChoices(profiles, context)
|
||||
: facetValues(profiles, context, field).map(function (value) {
|
||||
return { value: value };
|
||||
});
|
||||
if (supportsSelection(field) &&
|
||||
choicesUseSameFirmware(profiles, context, field, options)) {
|
||||
hiddenFields.push(field);
|
||||
}
|
||||
});
|
||||
const installs = facetValues(profiles, context, "install");
|
||||
if (installs.length === 1 && supportsSelection("install")) {
|
||||
hiddenFields.push("install");
|
||||
installKind = installs[0];
|
||||
}
|
||||
}
|
||||
return {
|
||||
hiddenFields: hiddenFields,
|
||||
installKind: installKind,
|
||||
missingFields: CHOICE_FIELDS.filter(function (field) {
|
||||
if (field === "chipFamily" || hiddenFields.includes(field)) return false;
|
||||
if (field === "firmwareProfile") return !PROFILE_FIELDS.every(function (part) {
|
||||
return chosen[part];
|
||||
});
|
||||
return !chosen[field];
|
||||
}),
|
||||
};
|
||||
}
|
||||
|
||||
function selectionUrl(url, filters, automaticChipFamily) {
|
||||
const result = new URL(url);
|
||||
result.searchParams.delete("s");
|
||||
FACET_FIELDS.forEach(function (field) {
|
||||
result.searchParams.delete(field);
|
||||
if (filters[field]) result.searchParams.set(field, filters[field]);
|
||||
@@ -1058,8 +1224,61 @@
|
||||
return result.href;
|
||||
}
|
||||
|
||||
function shortSelectionUrl(url, filters, automaticChipFamily) {
|
||||
const result = new URL(url);
|
||||
FACET_FIELDS.concat(["chipAuto", "s"]).forEach(function (field) {
|
||||
result.searchParams.delete(field);
|
||||
});
|
||||
const parts = ["1"];
|
||||
Object.keys(SHORT_SELECTION_FIELDS).forEach(function (key) {
|
||||
const field = SHORT_SELECTION_FIELDS[key];
|
||||
const value = filters[field];
|
||||
if (!value || (field === "chipFamily" && automaticChipFamily) ||
|
||||
(field === "hardwareFamily" && value === filters.hardware)) return;
|
||||
// Dots delimit fields; escape them even though encodeURIComponent does not.
|
||||
parts.push(key + encodeURIComponent(value).replace(/\./g, "%2E"));
|
||||
});
|
||||
if ((filters.hardwareFamily || filters.hardware) && !automaticChipFamily) {
|
||||
parts.push("a0");
|
||||
}
|
||||
if (parts.length > 1) result.searchParams.set("s", parts.join("."));
|
||||
return result.href;
|
||||
}
|
||||
|
||||
function shortSelectionParams(params) {
|
||||
const parts = params.get("s").split(".");
|
||||
const result = new URLSearchParams();
|
||||
if (params.getAll("s").length !== 1 || parts.shift() !== "1" || !parts.length) {
|
||||
throw new Error("unsupported or incomplete short picker link");
|
||||
}
|
||||
const seen = new Set();
|
||||
parts.forEach(function (part) {
|
||||
const key = part.charAt(0);
|
||||
if (seen.has(key) || (key !== "a" &&
|
||||
!Object.prototype.hasOwnProperty.call(SHORT_SELECTION_FIELDS, key))) {
|
||||
throw new Error("duplicate or unknown short picker field");
|
||||
}
|
||||
seen.add(key);
|
||||
const value = decodeURIComponent(part.slice(1));
|
||||
if (!value || (key === "a" && value !== "0" && value !== "1")) {
|
||||
throw new Error("invalid short picker field");
|
||||
}
|
||||
result.set(key === "a" ? "chipAuto" : SHORT_SELECTION_FIELDS[key], value);
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
function selectionFromUrl(url, profiles) {
|
||||
const params = new URL(url).searchParams;
|
||||
let params = new URL(url).searchParams;
|
||||
if (params.has("s")) {
|
||||
try {
|
||||
// The versioned selection is authoritative over any legacy fields.
|
||||
params = shortSelectionParams(params);
|
||||
} catch (error) {
|
||||
return { filters: {}, automaticChipFamily: false,
|
||||
unavailable: ["short link: " + error.message] };
|
||||
}
|
||||
}
|
||||
const requested = {};
|
||||
const filters = {};
|
||||
const unavailable = [];
|
||||
@@ -1342,7 +1561,12 @@
|
||||
"Requires the external W25Q16 storage board, its exact documented wiring, and the matching storage-aware OTAFIX bootloader."
|
||||
);
|
||||
}
|
||||
if (profile.variant.includes("no-external-sensors")) {
|
||||
if (profile.sensorProfile === "full") {
|
||||
extra.push("Includes the complete sensor drivers supported by this board's full recipe, with LoRa OTA retained. Attached sensor hardware and compatible wiring are still required.");
|
||||
} else if (profile.sensorProfile === "reduced") {
|
||||
extra.push("Omits selected optional environmental/ranging sensor drivers to reduce flash use, with LoRa OTA retained. Generic I2C and supported board peripherals remain available.");
|
||||
}
|
||||
if (!profile.sensorProfile && profile.variant.includes("no-external-sensors")) {
|
||||
if (/^RAK_(3401|4631)$/i.test(profile.hardware)) {
|
||||
extra.push("This profile uses internal storage for LoRa OTA; no external storage board is required.");
|
||||
}
|
||||
@@ -1424,6 +1648,7 @@
|
||||
}
|
||||
return {
|
||||
label: LOGGING_LABELS[mode], commands: commands,
|
||||
selection: { logging: mode },
|
||||
text: full && info && info.mqtt
|
||||
? (mode === "wifi" || mode === "both"
|
||||
? "In WebConfig, enable the desired MQTT broker presets/settings and save."
|
||||
@@ -1513,19 +1738,35 @@
|
||||
if (profile.role === "repeater") toggle("Repeat mesh traffic", "set repeat", "get repeat");
|
||||
if (info.rs232 && infrastructure) {
|
||||
const mode = chosen.mode;
|
||||
const uartNote = /^RAK_4631_repeater_unified_lora_ota-(?:full|reduced)$/i.test(profile.target) && profile.sensorProfile
|
||||
? "Unified RAK4631 uses UART2 for the RS-232 bridge; select it with set bridge.uart 2 while the bridge is stopped. GPS safety guards reserve UART1 even when no GPS was detected. Use UART2 for new installations. Legacy Serial1 downloads remain available by exact filename for existing GPS-free wiring."
|
||||
: /^RAK_4631_repeater_bridge_rs232_serial1(?:_lora_ota_no_external_sensors)?-(?:full|reduced)$/i.test(profile.target)
|
||||
? "This GPS-free Serial1 image uses UART1. Do not fit a powered UART GPS or header-connected W25 flash on the same UART1 pins. Use the unified UART2 image when those pins are occupied."
|
||||
: "Use the UART and pin map for this exact board. Canonical GPS-enabled RAK4631 uses UART2; select it with set bridge.uart 2 while the bridge is stopped. UART1 needs a compatible GPS-free image.";
|
||||
// MQTT images keep the historical bridge.enabled ESP-NOW alias.
|
||||
// Their independently saved UART uses the transport-specific controls.
|
||||
const uartSetting = info.mqtt ? "rs232.enabled" : "bridge.enabled";
|
||||
const uartRunning = info.mqtt ? "rs232.running" : "bridge.running";
|
||||
section("RS232 bridge" + (mode === "rs232" ? " - selected" : ""), [
|
||||
{ label: "On", commands: ["set bridge.enabled on", "get bridge.running"] },
|
||||
{ label: "Off", commands: ["set bridge.enabled off"] },
|
||||
{ label: "Set baud", commands: ["set bridge.enabled off", "set bridge.baud 115200", "set bridge.enabled on"] },
|
||||
{ label: "On", commands: ["set " + uartSetting + " on", "get " + uartRunning] },
|
||||
{ label: "Off", commands: ["set " + uartSetting + " off"] },
|
||||
{ label: "Set baud", commands: ["set " + uartSetting + " off", "set bridge.baud 115200", "set " + uartSetting + " on"] },
|
||||
].sort(function (a, b) {
|
||||
return mode === "standard" ? Number(b.label === "Off") - Number(a.label === "Off") : 0;
|
||||
}), "Use the UART and pin map for this exact board. Canonical GPS-enabled RAK4631 uses UART2; select it with set bridge.uart 2 while the bridge is stopped. UART1 needs a compatible GPS-free image.");
|
||||
}), uartNote);
|
||||
}
|
||||
if (info.espnowBridge && infrastructure) {
|
||||
toggle("ESP-NOW bridge", "set bridge.enabled", "get bridge.enabled",
|
||||
info.mqtt
|
||||
? "Independent of MQTT in this Full image. set espnow.enabled is an alias. get bridge.running (or get espnow.running) checks its live state."
|
||||
: "set bridge.enabled controls the ESP-NOW bridge; get bridge.running checks its live state.");
|
||||
// Combined images need the transport-specific switch: bridge.enabled
|
||||
// keeps its historical meaning for the primary transport. Older
|
||||
// dedicated ESP-NOW releases retain their original bridge.* commands.
|
||||
const independentEspnow = info.rs232 || info.mqtt;
|
||||
toggle("ESP-NOW bridge", independentEspnow ? "set espnow.enabled" : "set bridge.enabled",
|
||||
independentEspnow ? "get espnow.running" : "get bridge.running",
|
||||
info.rs232
|
||||
? "Independent of RS-232. Newly merged repeaters start with ESP-NOW off until enabled; saved settings survive reboot. get espnow.enabled checks the saved setting; get espnow.running checks its live state."
|
||||
: info.mqtt
|
||||
? "Independent of MQTT in this Full image. get espnow.enabled checks the saved setting; get espnow.running checks its live state."
|
||||
: "set bridge.enabled controls the ESP-NOW bridge; get bridge.running checks its live state.");
|
||||
section("ESP-NOW bridge framing", ["wrapped", "raw"].map(function (mode) {
|
||||
return { label: mode, commands: ["set bridge.format " + mode] };
|
||||
}), info.mqtt
|
||||
@@ -1552,7 +1793,40 @@
|
||||
return sections;
|
||||
}
|
||||
|
||||
function renderRuntimeDirections(card, profile, selection) {
|
||||
function runtimePanelDirections(profile, selection, runtimeFields) {
|
||||
const chosen = Object.assign({}, selection);
|
||||
const moved = runtimeFields || [];
|
||||
if (moved.includes("logging")) delete chosen.logging;
|
||||
if (moved.includes("mode") && !chosen.mode &&
|
||||
profileFieldValues(profile, "mode").includes("standard")) {
|
||||
chosen.mode = "standard";
|
||||
}
|
||||
const sections = runtimeDirections(profile, chosen);
|
||||
sections.forEach(function (item) {
|
||||
if (moved.includes("logging") && item.actions.some(function (action) {
|
||||
return action.selection && action.selection.logging;
|
||||
})) {
|
||||
const mode = selection && selection.logging || "none";
|
||||
const selected = item.actions.findIndex(function (action) {
|
||||
return action.selection && action.selection.logging === mode;
|
||||
});
|
||||
item.selectedIndex = selected < 0 ? 0 : selected;
|
||||
item.title = "Logging / MQTT output";
|
||||
}
|
||||
if (moved.includes("mode") && (/^RS232 bridge/.test(item.title) ||
|
||||
item.title === "ESP-NOW bridge")) {
|
||||
const on = /^RS232 bridge/.test(item.title)
|
||||
? chosen.mode === "rs232" : chosen.mode === "espnow";
|
||||
item.selectedIndex = item.actions.findIndex(function (action) {
|
||||
return action.label === (on ? "On" : "Off");
|
||||
});
|
||||
}
|
||||
});
|
||||
return sections;
|
||||
}
|
||||
|
||||
function renderRuntimeDirections(card, profile, selection, options) {
|
||||
const settings = options || {};
|
||||
const panel = createElement("section");
|
||||
panel.className = "firmware-picker-runtime";
|
||||
panel.appendChild(createElement("h4", "Restore your settings after flashing"));
|
||||
@@ -1572,7 +1846,7 @@
|
||||
: "Use board and ver to identify the node.")));
|
||||
}
|
||||
if (!profile.controls) panel.appendChild(createElement("p", "Additional hardware controls have not been verified for this exact release image. Use the role guide for those settings."));
|
||||
runtimeDirections(profile, selection).forEach(function (item, index) {
|
||||
runtimePanelDirections(profile, selection, settings.runtimeFields).forEach(function (item, index) {
|
||||
const details = createElement("details");
|
||||
details.open = index === 0;
|
||||
details.appendChild(createElement("summary", item.title));
|
||||
@@ -1582,13 +1856,13 @@
|
||||
fieldset.appendChild(createElement("legend", "Show steps for " + item.title));
|
||||
const choices = createElement("div");
|
||||
choices.className = "firmware-picker-radio-options";
|
||||
let actionIndex = item.selectedIndex >= 0 ? item.selectedIndex : 0;
|
||||
renderRadioChoices(choices, item.actions.map(function (action, i) {
|
||||
return { value: String(i), label: action.label };
|
||||
}), "0", "firmware-picker-runtime-" + (++runtimeGroupCount));
|
||||
}), String(actionIndex), "firmware-picker-runtime-" + (++runtimeGroupCount));
|
||||
fieldset.appendChild(choices);
|
||||
details.appendChild(fieldset);
|
||||
const output = createElement("div");
|
||||
let actionIndex = 0;
|
||||
function show() {
|
||||
output.replaceChildren();
|
||||
const action = item.actions[actionIndex];
|
||||
@@ -1614,6 +1888,11 @@
|
||||
choices.addEventListener("change", function (event) {
|
||||
actionIndex = Number(event.target.value) || 0;
|
||||
show();
|
||||
const action = item.actions[actionIndex];
|
||||
if (action.selection && settings.onSelectionChange &&
|
||||
(settings.runtimeFields || []).includes("logging")) {
|
||||
settings.onSelectionChange(action.selection);
|
||||
}
|
||||
});
|
||||
show();
|
||||
details.appendChild(output);
|
||||
@@ -1630,7 +1909,7 @@
|
||||
});
|
||||
}
|
||||
|
||||
function renderProfileCard(container, profile, asset, installKind, selection) {
|
||||
function renderProfileCard(container, profile, asset, installKind, selection, options) {
|
||||
const card = createElement("article");
|
||||
card.className = "firmware-picker-card";
|
||||
card.appendChild(createElement(
|
||||
@@ -1649,7 +1928,8 @@
|
||||
return labelFor("mode", mode);
|
||||
}).join(" / "),
|
||||
],
|
||||
["Logging", labelFor("logging", selection && selection.logging || profile.logging)],
|
||||
["Logging", labelFor("logging", options && (options.runtimeFields || []).includes("logging")
|
||||
? profile.logging : selection && selection.logging || profile.logging)],
|
||||
["Firmware profile", firmwareProfileLabel(profile)],
|
||||
["Install operation", labelFor("install", installKind)],
|
||||
["File", asset.name],
|
||||
@@ -1691,7 +1971,7 @@
|
||||
});
|
||||
steps.appendChild(list);
|
||||
card.appendChild(steps);
|
||||
renderRuntimeDirections(card, profile, selection);
|
||||
renderRuntimeDirections(card, profile, selection, options);
|
||||
container.appendChild(card);
|
||||
}
|
||||
|
||||
@@ -1712,6 +1992,8 @@
|
||||
const shareLink = root.querySelector('[data-role="share-link"]');
|
||||
const copyLinkButton = root.querySelector('[data-action="copy-link"]');
|
||||
const linkStatus = root.querySelector('[data-role="link-status"]');
|
||||
const feedback = root.querySelector('[data-role="filter-feedback"]');
|
||||
const resultLink = root.querySelector('[data-role="view-results"]');
|
||||
const controls = {};
|
||||
FACET_FIELDS.forEach(function (field) {
|
||||
controls[field] = form.querySelector('[data-field="' + field + '"]');
|
||||
@@ -1723,9 +2005,13 @@
|
||||
const filters = {};
|
||||
let automaticChipFamily = false;
|
||||
const groupPrefix = "firmware-picker-" + (++pickerInstanceCount);
|
||||
if (resultLink) {
|
||||
result.id = result.id || groupPrefix + "-results";
|
||||
resultLink.href = "#" + result.id;
|
||||
}
|
||||
|
||||
function updateSelectionUrl() {
|
||||
const url = selectionUrl(global.location.href, filters, automaticChipFamily);
|
||||
const url = shortSelectionUrl(global.location.href, filters, automaticChipFamily);
|
||||
if (url !== global.location.href) {
|
||||
try {
|
||||
global.history.replaceState(global.history.state, "", url);
|
||||
@@ -1735,9 +2021,11 @@
|
||||
}
|
||||
}
|
||||
if (shareLink) {
|
||||
const base = global.location.protocol === "file:"
|
||||
? root.getAttribute("data-share-url") : url;
|
||||
shareLink.href = selectionUrl(base || url, filters, automaticChipFamily);
|
||||
const base = new URL(global.location.protocol === "file:"
|
||||
? root.getAttribute("data-share-url") || url : url);
|
||||
base.pathname = base.pathname.replace(/\/firmware_picker\/$/, "/p/");
|
||||
if (global.location.protocol === "file:") base.hash = global.location.hash;
|
||||
shareLink.href = shortSelectionUrl(base.href, filters, automaticChipFamily);
|
||||
shareLink.hidden = false;
|
||||
}
|
||||
if (copyLinkButton) copyLinkButton.disabled = false;
|
||||
@@ -1760,13 +2048,13 @@
|
||||
if (copyLinkButton && shareLink) copyLinkButton.addEventListener("click", function () {
|
||||
const copy = global.navigator.clipboard && global.navigator.clipboard.writeText;
|
||||
if (!copy) {
|
||||
linkStatus.textContent = "Copy the address bar, or copy the 'Link to these settings' link.";
|
||||
linkStatus.textContent = "Copy the address bar, or copy the 'Short link to these settings' link.";
|
||||
return;
|
||||
}
|
||||
global.navigator.clipboard.writeText(shareLink.href).then(function () {
|
||||
linkStatus.textContent = "Link copied.";
|
||||
}).catch(function () {
|
||||
linkStatus.textContent = "Copy the address bar, or copy the 'Link to these settings' link.";
|
||||
linkStatus.textContent = "Copy the address bar, or copy the 'Short link to these settings' link.";
|
||||
});
|
||||
});
|
||||
global.addEventListener("popstate", restoreSelectionUrl);
|
||||
@@ -1816,6 +2104,35 @@
|
||||
renderRadioChoices(container, options, selected, groupPrefix + "-" + field, field);
|
||||
const fieldset = container.closest("fieldset");
|
||||
if (fieldset) fieldset.disabled = values.length === 0;
|
||||
setRefinementHint(container, field, options);
|
||||
}
|
||||
|
||||
function setRefinementHint(container, field, options) {
|
||||
const fieldset = container.closest("fieldset");
|
||||
if (!fieldset) return;
|
||||
let badge = fieldset.querySelector('[data-role="same-firmware"]');
|
||||
let hint = fieldset.querySelector('[data-role="refinement-hint"]');
|
||||
if (!badge) {
|
||||
badge = createElement("span", "Same firmware");
|
||||
badge.className = "firmware-picker-same-firmware";
|
||||
badge.dataset.role = "same-firmware";
|
||||
fieldset.querySelector("legend").appendChild(badge);
|
||||
hint = createElement("p");
|
||||
hint.className = "firmware-picker-control-help firmware-picker-refinement-hint";
|
||||
hint.dataset.role = "refinement-hint";
|
||||
hint.id = groupPrefix + "-" + field + "-hint";
|
||||
fieldset.insertBefore(hint, container);
|
||||
}
|
||||
const same = choicesUseSameFirmware(catalog.profiles, filters, field, options);
|
||||
badge.hidden = !same;
|
||||
hint.hidden = !same;
|
||||
hint.textContent = same ? "These choices do not narrow the firmware builds." +
|
||||
(["logging", "mode"].includes(field) ? " They can change setup instructions." : "") : "";
|
||||
const describedBy = (fieldset.getAttribute("aria-describedby") || "")
|
||||
.split(/\s+/).filter(function (id) { return id && id !== hint.id; });
|
||||
if (same) describedBy.push(hint.id);
|
||||
if (describedBy.length) fieldset.setAttribute("aria-describedby", describedBy.join(" "));
|
||||
else fieldset.removeAttribute("aria-describedby");
|
||||
}
|
||||
|
||||
function setControlOptions(field, values) {
|
||||
@@ -1892,6 +2209,12 @@
|
||||
renderRadioChoices(profileControl, [{ value: "", label: "Any" }].concat(profileChoices),
|
||||
firmwareProfileValue(filters), groupPrefix + "-firmwareProfile", "firmwareProfile");
|
||||
profileControl.closest("fieldset").disabled = profileChoices.length === 0;
|
||||
setRefinementHint(profileControl, "firmwareProfile", profileChoices);
|
||||
const requirements = selectionRequirements(catalog.profiles, filters);
|
||||
["logging", "firmwareProfile", "mode", "install"].forEach(function (field) {
|
||||
const control = controls[field];
|
||||
if (control) control.closest("fieldset").hidden = requirements.hiddenFields.includes(field);
|
||||
});
|
||||
const hardwareVariants = valuesForField("hardware");
|
||||
const hardwareVariantControl = root.querySelector(
|
||||
'[data-role="hardware-variant-control"]'
|
||||
@@ -1917,16 +2240,33 @@
|
||||
result.hidden = true;
|
||||
missing.hidden = true;
|
||||
resultList.replaceChildren();
|
||||
const missingFields = CHOICE_FIELDS.filter(function (field) {
|
||||
if (field === "firmwareProfile") return !PROFILE_FIELDS.every(function (part) {
|
||||
return filters[part];
|
||||
});
|
||||
return field !== "chipFamily" && !filters[field];
|
||||
});
|
||||
const requirements = selectionRequirements(catalog.profiles, filters);
|
||||
const missingFields = requirements.missingFields;
|
||||
const cardOptions = {
|
||||
runtimeFields: requirements.hiddenFields,
|
||||
onSelectionChange: function (selection) {
|
||||
Object.keys(selection).forEach(function (field) {
|
||||
filters[field] = selection[field];
|
||||
});
|
||||
updateSelectionUrl();
|
||||
},
|
||||
};
|
||||
const matches = matchingProfiles();
|
||||
if (feedback) {
|
||||
const progress = selectionProgress(catalog.profiles,
|
||||
Object.assign({}, filters, { install: requirements.installKind }));
|
||||
feedback.hidden = false;
|
||||
feedback.dataset.state = progress.state;
|
||||
feedback.querySelector('[data-role="filter-count"]').textContent = progress.title;
|
||||
feedback.querySelector('[data-role="filter-note"]').textContent = missingFields.length
|
||||
? progress.note : "Matching files and install instructions are shown below.";
|
||||
}
|
||||
if (resultLink) resultLink.hidden = true;
|
||||
|
||||
if (missingFields.length) {
|
||||
if (missingFields.length === CHOICE_FIELDS.length - 1 && !filters.chipFamily) {
|
||||
if (matches.length === 1) {
|
||||
status.textContent = "One firmware build remains. Confirm the hardware, role and any remaining install choice.";
|
||||
} else if (missingFields.length === CHOICE_FIELDS.length - 1 && !filters.chipFamily) {
|
||||
status.textContent = "Pick options in any order. " +
|
||||
matches.length + " compatible configurations are available.";
|
||||
} else {
|
||||
@@ -1936,7 +2276,7 @@
|
||||
(missingFields.length === 1 ? "" : "s") + " in any order.";
|
||||
}
|
||||
if (shouldShowCandidateResults(matches)) {
|
||||
const candidates = resolveProfileAssets(matches, filters.install);
|
||||
const candidates = resolveProfileAssets(matches, requirements.installKind);
|
||||
if (candidates.length) {
|
||||
status.textContent += " Possible files are shown below.";
|
||||
resultEyebrow.textContent = "Narrowed firmware candidates";
|
||||
@@ -1950,16 +2290,18 @@
|
||||
entry.profile,
|
||||
entry.asset,
|
||||
entry.installKind,
|
||||
filters
|
||||
filters,
|
||||
cardOptions
|
||||
);
|
||||
});
|
||||
result.hidden = false;
|
||||
if (resultLink) resultLink.hidden = false;
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
const installKind = filters.install;
|
||||
const installKind = requirements.installKind;
|
||||
const resolved = resolveProfileAssets(matches, installKind);
|
||||
if (!resolved.length) {
|
||||
missing.hidden = false;
|
||||
@@ -1981,10 +2323,12 @@
|
||||
entry.profile,
|
||||
entry.asset,
|
||||
entry.installKind,
|
||||
filters
|
||||
filters,
|
||||
cardOptions
|
||||
);
|
||||
});
|
||||
result.hidden = false;
|
||||
if (resultLink) resultLink.hidden = false;
|
||||
}
|
||||
|
||||
function renderBootloader() {
|
||||
@@ -2226,12 +2570,17 @@
|
||||
firmwareProfileFilters: firmwareProfileFilters,
|
||||
firmwareProfileLabel: firmwareProfileLabel,
|
||||
firmwareProfileChoices: firmwareProfileChoices,
|
||||
selectionProgress: selectionProgress,
|
||||
choicesUseSameFirmware: choicesUseSameFirmware,
|
||||
selectionRequirements: selectionRequirements,
|
||||
selectionUrl: selectionUrl,
|
||||
shortSelectionUrl: shortSelectionUrl,
|
||||
selectionFromUrl: selectionFromUrl,
|
||||
canonicalAsset: canonicalAsset,
|
||||
resolveProfileAssets: resolveProfileAssets,
|
||||
shouldShowCandidateResults: shouldShowCandidateResults,
|
||||
runtimeDirections: runtimeDirections,
|
||||
runtimePanelDirections: runtimePanelDirections,
|
||||
renderRuntimeDirections: renderRuntimeDirections,
|
||||
installSteps: installSteps,
|
||||
migrationReleaseUrl: migrationReleaseUrl,
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
/* Source links help GitHub readers; the hosted page is already their destination. */
|
||||
(function () {
|
||||
"use strict";
|
||||
|
||||
const hostedHostname = "mikecarper.github.io";
|
||||
const bannerSelector = ".meshcore-hosted-doc-link";
|
||||
|
||||
function hideHostedDocLinks(doc, hostname) {
|
||||
if (hostname !== hostedHostname) return 0;
|
||||
const banners = doc.querySelectorAll(bannerSelector);
|
||||
for (const banner of banners) banner.hidden = true;
|
||||
return banners.length;
|
||||
}
|
||||
|
||||
if (typeof module === "object" && module.exports) {
|
||||
module.exports = { hideHostedDocLinks };
|
||||
}
|
||||
if (typeof window === "undefined" || typeof document === "undefined") return;
|
||||
|
||||
const hide = function () {
|
||||
hideHostedDocLinks(document, window.location.hostname);
|
||||
};
|
||||
if (document.readyState === "loading") {
|
||||
document.addEventListener("DOMContentLoaded", hide, { once: true });
|
||||
} else {
|
||||
hide();
|
||||
}
|
||||
// Material can replace article content without loading the page again.
|
||||
if (typeof document$ !== "undefined" && document$ && typeof document$.subscribe === "function") {
|
||||
document$.subscribe(hide);
|
||||
}
|
||||
})();
|
||||
@@ -7,9 +7,11 @@
|
||||
// which Web Crypto exposes directly. AES-CBC with a zero IV supplies the
|
||||
// required AES block primitive; only its first ciphertext block is used.
|
||||
const HEADER = 83;
|
||||
const CURRENT_HEADER = 111;
|
||||
const TAG = 16;
|
||||
const ENTRY = 13;
|
||||
const PER_PAGE = 6;
|
||||
const CURRENT_PER_PAGE = 4;
|
||||
const MAX_KEYS = 36;
|
||||
const ZERO_BLOCK = new Uint8Array(16);
|
||||
const UTF8 = new TextEncoder();
|
||||
@@ -147,7 +149,7 @@
|
||||
for (let index = 0; index < 16; index += 1) d[index] ^= aadMac[index];
|
||||
|
||||
if (plaintext.length >= 16) {
|
||||
const adjusted = plaintext.slice();
|
||||
const adjusted = new Uint8Array(plaintext);
|
||||
const last = adjusted.length - 16;
|
||||
for (let index = 0; index < 16; index += 1) adjusted[last + index] ^= d[index];
|
||||
return cmac(encryptBlock, adjusted);
|
||||
@@ -163,7 +165,7 @@
|
||||
|
||||
async function ctrCrypt(keyBytes, tag, input) {
|
||||
const subtle = cryptoApi();
|
||||
const counter = tag.slice();
|
||||
const counter = new Uint8Array(tag);
|
||||
counter[8] &= 0x7f;
|
||||
counter[12] &= 0x7f;
|
||||
const key = await subtle.importKey("raw", keyBytes, { name: "AES-CTR" }, false, ["decrypt"]);
|
||||
@@ -214,22 +216,34 @@
|
||||
return (await hmacSha256(key, concatBytes(radio, normalized))).slice(0, 12);
|
||||
}
|
||||
|
||||
function layout(payload) {
|
||||
const magic = bytesToAscii(payload, 0, 4);
|
||||
if (magic === "MGR1") return { header: HEADER, perPage: PER_PAGE };
|
||||
if (magic === "MGR2") return { header: CURRENT_HEADER, perPage: CURRENT_PER_PAGE };
|
||||
return null;
|
||||
}
|
||||
|
||||
function canonicalLength(payload) {
|
||||
if (payload.length < HEADER + TAG || bytesToAscii(payload, 0, 4) !== "MGR1") {
|
||||
managementError("This data does not begin with an MGR1 management page.");
|
||||
const format = layout(payload);
|
||||
if (!format || payload.length < format.header + TAG) {
|
||||
managementError("This data does not begin with a complete MGR1/MGR2 management page.");
|
||||
}
|
||||
if (format.header === CURRENT_HEADER && ((uint16LE(payload, 83) & 0xf800) || (payload[85] & 0xc0))) {
|
||||
managementError("Reserved USB status bits must be zero.");
|
||||
}
|
||||
if (format.header === CURRENT_HEADER) usbWatchdogEvent(payload);
|
||||
const page = payload[78];
|
||||
const pages = payload[79];
|
||||
const total = payload[80];
|
||||
const first = payload[81];
|
||||
const count = payload[82];
|
||||
const expectedPages = total ? Math.ceil(total / PER_PAGE) : 1;
|
||||
const expectedPages = total ? Math.ceil(total / format.perPage) : 1;
|
||||
if (total > MAX_KEYS || pages !== expectedPages || page >= pages ||
|
||||
first !== page * PER_PAGE || first > total ||
|
||||
count !== Math.min(PER_PAGE, total - first)) {
|
||||
managementError("The MGR1 page index or ACL bounds are invalid.");
|
||||
first !== page * format.perPage || first > total ||
|
||||
count !== Math.min(format.perPage, total - first)) {
|
||||
managementError("The management page index or ACL bounds are invalid.");
|
||||
}
|
||||
return HEADER + count * ENTRY + TAG;
|
||||
return format.header + count * ENTRY + TAG;
|
||||
}
|
||||
|
||||
function paddedFloodLength(canonical) {
|
||||
@@ -256,13 +270,14 @@
|
||||
const hops = pathInfo & 0x3f;
|
||||
if (width > 3 || hops * width > 64) return null;
|
||||
const payloadOffset = pathOffset + 1 + width * hops;
|
||||
if (payloadOffset >= bytes.length || bytesToAscii(bytes, payloadOffset, 4) !== "MGR1") return null;
|
||||
if (payloadOffset >= bytes.length || !layout(bytes.slice(payloadOffset))) return null;
|
||||
const payload = bytes.slice(payloadOffset);
|
||||
const canonical = canonicalLength(payload);
|
||||
const padded = paddedFloodLength(canonical);
|
||||
if (payload.length !== canonical &&
|
||||
(payload.length !== padded || !payload.slice(canonical).every((value) => value === 0))) {
|
||||
managementError("MGR1 packet padding or length is invalid.");
|
||||
// Every GroupData route uses the group cipher-compatible padded length,
|
||||
// even though management pages protect their own ACL with AES-SIV.
|
||||
if (payload.length !== padded || !payload.slice(canonical).every((value) => value === 0)) {
|
||||
managementError("Management packet padding or length is invalid.");
|
||||
}
|
||||
return {
|
||||
payload: payload.slice(0, canonical),
|
||||
@@ -277,26 +292,35 @@
|
||||
}
|
||||
|
||||
function parseCanonical(bytes) {
|
||||
if (bytesToAscii(bytes, 0, 4) !== "MGR1") return null;
|
||||
if (!layout(bytes)) return null;
|
||||
const canonical = canonicalLength(bytes);
|
||||
const padded = paddedFloodLength(canonical);
|
||||
if (bytes.length !== canonical &&
|
||||
(bytes.length !== padded || !bytes.slice(canonical).every((value) => value === 0))) {
|
||||
managementError("MGR1 payload padding or length is invalid.");
|
||||
managementError("Management payload padding or length is invalid.");
|
||||
}
|
||||
return { payload: bytes.slice(0, canonical), envelope: null };
|
||||
}
|
||||
|
||||
function inputByteStreams(input) {
|
||||
if (typeof input !== "string" || input.trim() === "") {
|
||||
managementError("Paste one or more MGR1 payloads or GroupData packet hex values first.");
|
||||
managementError("Paste one or more MGR1/MGR2 payloads or GroupData packet hex values first.");
|
||||
}
|
||||
if (input.length > 32768) managementError("The pasted value is too large to be management-report data.");
|
||||
const candidates = [input, ...input.split(/\r?\n/)];
|
||||
// A wrapped single page is one candidate, not an invalid first-line
|
||||
// fragment. Conversely, joining complete page lines creates a bogus
|
||||
// oversized canonical page: let each line retain its own bounds.
|
||||
try {
|
||||
const whole = hexToBytes(input);
|
||||
if (parseCanonical(whole) || parsePacket(whole)) return [whole];
|
||||
} catch (_error) {
|
||||
// JSON/log wrappers and multiple complete page lines are handled below.
|
||||
}
|
||||
const candidates = input.split(/\r?\n/);
|
||||
const quotedRaw = /["'](?:raw|data)["']\s*:\s*["']([^"']+)["']/gi;
|
||||
let match;
|
||||
while ((match = quotedRaw.exec(input)) !== null) candidates.push(match[1]);
|
||||
const streams = input.match(/(?:0x)?[0-9a-f]{2}(?:(?:[\s:,_-]*)(?:0x)?[0-9a-f]{2}){15,}/gi);
|
||||
const streams = input.match(/(?:0x)?[0-9a-f]{2}(?:(?:[ \t:,_-]*)(?:0x)?[0-9a-f]{2}){15,}/gi);
|
||||
if (streams) candidates.push(...streams);
|
||||
|
||||
const unique = new Map();
|
||||
@@ -319,7 +343,7 @@
|
||||
try {
|
||||
parsed = parseCanonical(bytes) || parsePacket(bytes);
|
||||
} catch (error) {
|
||||
if (error instanceof ManagementDecodeError && bytesToAscii(bytes, 0, 4) !== "MGR1") {
|
||||
if (error instanceof ManagementDecodeError && !layout(bytes)) {
|
||||
// It may be an unrelated hex stream alongside a valid packet.
|
||||
continue;
|
||||
}
|
||||
@@ -333,7 +357,7 @@
|
||||
}
|
||||
}
|
||||
if (!found.length) {
|
||||
managementError("No complete MGR1 management page was found. Paste canonical MGR1 payload hex or an entire GroupData packet.");
|
||||
managementError("No complete management page was found. Paste canonical MGR1/MGR2 payload hex or an entire GroupData packet.");
|
||||
}
|
||||
return found;
|
||||
}
|
||||
@@ -366,6 +390,45 @@
|
||||
return ({ 1: "repeater", 2: "room server", 3: "sensor" })[value] || `unknown (${value})`;
|
||||
}
|
||||
|
||||
function usbStatus(payload) {
|
||||
if (bytesToAscii(payload, 0, 4) !== "MGR2") return null;
|
||||
const flags = uint16LE(payload, 83);
|
||||
const names = ["supported", "loggingEnabled", "watchdogEnabled", "hostConnected",
|
||||
"readerConnected", "stalled", "recovering", "recoveryDeferred", "persistenceReady", "watchdogAuto", "loggerActive"];
|
||||
const result = {};
|
||||
names.forEach((name, bit) => { result[name] = Boolean(flags & (1 << bit)); });
|
||||
result.stage = payload[85] & 3;
|
||||
result.backoffStep = (payload[85] >> 2) & 15;
|
||||
result.retrySeconds = uint32LE(payload, 86);
|
||||
result.inactiveSeconds = uint32LE(payload, 90);
|
||||
result.autoConnectedSeconds = uint32LE(payload, 94);
|
||||
return result;
|
||||
}
|
||||
|
||||
function usbWatchdogEvent(payload) {
|
||||
if (bytesToAscii(payload, 0, 4) !== "MGR2") return null;
|
||||
const code = payload[98];
|
||||
const sequence = uint32LE(payload, 107);
|
||||
if (!sequence) {
|
||||
if (!payload.slice(98, 111).every((value) => value === 0)) {
|
||||
managementError("Invalid empty USB watchdog event.");
|
||||
}
|
||||
return null;
|
||||
}
|
||||
const reasons = code & 15;
|
||||
const actionCode = (code >> 4) & 7;
|
||||
if (!reasons || actionCode < 1 || actionCode > 4) {
|
||||
managementError("Invalid USB watchdog event reason/action.");
|
||||
}
|
||||
const names = ["host-absent", "reader-absent", "tx-stalled", "client-inactive"];
|
||||
return {
|
||||
reasons, reasonNames: names.filter((_name, bit) => reasons & (1 << bit)),
|
||||
actionCode, action: [null, "soft-recovery", "reenumerate", "reboot-requested", "reboot-cancelled"][actionCode],
|
||||
persisted: Boolean(code & 128), epoch: uint32LE(payload, 99),
|
||||
uptimeSeconds: uint32LE(payload, 103), sequence,
|
||||
};
|
||||
}
|
||||
|
||||
function publicFields(payload, envelope) {
|
||||
const valid = uint16LE(payload, 76);
|
||||
const capabilities = payload[73];
|
||||
@@ -374,6 +437,7 @@
|
||||
const knownActive = featureNames(active & known);
|
||||
const unknownActive = featureNames(capabilities & ~known);
|
||||
return {
|
||||
protocol: bytesToAscii(payload, 0, 4),
|
||||
radioId: bytesToHex(payload.slice(4, 20)),
|
||||
sequence: uint32LE(payload, 20),
|
||||
timestamp: uint32LE(payload, 24),
|
||||
@@ -396,6 +460,8 @@
|
||||
acl: `${payload[80]} total; ${payload[82]} on this page`,
|
||||
partialSince: Boolean(valid & 32),
|
||||
mcuTemperature: Boolean(valid & 64),
|
||||
usbLogging: usbStatus(payload),
|
||||
usbWatchdogLast: usbWatchdogEvent(payload),
|
||||
envelope,
|
||||
};
|
||||
}
|
||||
@@ -417,9 +483,11 @@
|
||||
});
|
||||
const sorted = [...byPage.values()].sort((left, right) => left.payload[78] - right.payload[78]);
|
||||
const reference = sorted[0].payload;
|
||||
const header = layout(reference).header;
|
||||
if (sorted.some((page) => !equalBytes(page.payload.slice(0, 78), reference.slice(0, 78)) ||
|
||||
!equalBytes(page.payload.slice(HEADER, header), reference.slice(HEADER, header)) ||
|
||||
page.payload[79] !== reference[79] || page.payload[80] !== reference[80])) {
|
||||
managementError("MGR1 pages do not belong to the same snapshot.");
|
||||
managementError("Management pages do not belong to the same snapshot.");
|
||||
}
|
||||
return sorted;
|
||||
}
|
||||
@@ -427,9 +495,10 @@
|
||||
async function decryptPage(payload, password, candidate) {
|
||||
const root = await passwordKey(password);
|
||||
const key = await deriveKey(root, "MeshCore-MGR1-SIV", payload.slice(4, 20));
|
||||
const header = layout(payload).header;
|
||||
const privateLength = payload[82] * ENTRY;
|
||||
const plaintext = await openSiv(key, payload.slice(0, HEADER),
|
||||
payload.slice(HEADER, HEADER + privateLength), payload.slice(HEADER + privateLength));
|
||||
const plaintext = await openSiv(key, payload.slice(0, header),
|
||||
payload.slice(header, header + privateLength), payload.slice(header + privateLength));
|
||||
try {
|
||||
let wanted = null;
|
||||
if (candidate) wanted = await aclFingerprint(password, payload.slice(4, 20), candidate);
|
||||
@@ -470,6 +539,9 @@
|
||||
return model;
|
||||
}
|
||||
for (const page of pages) model.acl.push(...await decryptPage(page.payload, password, candidate));
|
||||
if (new Set(model.acl.map((entry) => entry.fingerprint)).size !== model.acl.length) {
|
||||
managementError("Authenticated ACL data contains a duplicate fingerprint.");
|
||||
}
|
||||
model.authenticated = true;
|
||||
if (!model.complete) {
|
||||
model.warnings.push(`Authenticated ${pages.length} of ${first.payload[79]} pages. Paste the remaining pages to view the complete ACL.`);
|
||||
@@ -508,6 +580,17 @@
|
||||
const warnings = root.querySelector("[data-role='warnings']");
|
||||
const warningList = root.querySelector("[data-role='warning-list']");
|
||||
const aclTable = root.querySelector("[data-role='acl-table']");
|
||||
let activeDecode = null;
|
||||
|
||||
function resetResults() {
|
||||
results.hidden = true;
|
||||
summary.replaceChildren();
|
||||
aclTable.replaceChildren();
|
||||
warningList.replaceChildren();
|
||||
warnings.hidden = true;
|
||||
status.textContent = "";
|
||||
status.classList.toggle("management-status-authenticated", false);
|
||||
}
|
||||
|
||||
function showError(value) {
|
||||
results.hidden = true;
|
||||
@@ -517,6 +600,7 @@
|
||||
|
||||
function render(model) {
|
||||
const values = [
|
||||
["Protocol", model.public.protocol],
|
||||
["Reporter", model.public.radioId], ["Sequence", model.public.sequence],
|
||||
["Report time", timestampText(model.public.timestamp)], ["Role", model.public.role],
|
||||
["Firmware", model.public.firmware], ["Bootloader", model.public.bootloader],
|
||||
@@ -528,6 +612,35 @@
|
||||
["Compiled capabilities", model.public.compiled], ["Known active capabilities", model.public.active],
|
||||
["Page", model.public.page], ["ACL", model.public.acl],
|
||||
];
|
||||
const usb = model.public.usbLogging;
|
||||
if (usb) {
|
||||
const stage = ["idle", "soft recovery wait", "re-enumeration wait", "reboot pending"][usb.stage];
|
||||
values.push(["USB logging", usb.supported ? (usb.loggingEnabled ? "enabled" : "disabled") : "unsupported"],
|
||||
["USB watchdog", usb.watchdogAuto ? "Auto (qualifying)" : usb.watchdogEnabled ? "On" : "Off"],
|
||||
["USB Auto qualification", `${usb.autoConnectedSeconds}s of 1209600s`],
|
||||
["USB host / reader", `${usb.hostConnected ? "host connected" : "no host"}; ${usb.readerConnected ? "reader connected" : "no reader"}`],
|
||||
["USB logging client", usb.loggerActive ? "recent USB stats polling (15-minute lease)" : "inactive (stats lease or healthy USB link missing)"],
|
||||
["USB recovery", `${usb.stalled ? "stalled; " : ""}${usb.recovering ? "recovering; " : ""}${usb.recoveryDeferred ? "deferred; " : ""}${stage}; backoff=${usb.backoffStep}`],
|
||||
["USB retry interval / inactive", `${usb.retrySeconds}s / ${usb.inactiveSeconds}s`],
|
||||
["USB persistence", usb.persistenceReady ? "ready" : "not ready"]);
|
||||
} else {
|
||||
values.push(["USB logging status", "unavailable in MGR1"]);
|
||||
}
|
||||
const event = model.public.usbWatchdogLast;
|
||||
if (event) {
|
||||
values.push(["Last USB watchdog event", `#${event.sequence}: ${event.action}`],
|
||||
["USB watchdog event reasons", event.reasonNames.join(", ")],
|
||||
["USB watchdog event time", `${timestampText(event.epoch)} (advisory RTC; raw epoch=${event.epoch})`],
|
||||
["USB watchdog event uptime", `${event.uptimeSeconds}s at the recorded boot`],
|
||||
["USB watchdog event persistence", event.persisted ? "saved" : "RAM-only"],
|
||||
["USB watchdog action meaning", event.actionCode === 3
|
||||
? "Reboot was requested; this does not prove a physical reset occurred."
|
||||
: event.actionCode === 4 ? "Reboot request was cancelled; no physical reset is claimed."
|
||||
: "Recovery attempt was recorded; this does not prove successful recovery."]);
|
||||
} else {
|
||||
values.push(["Last USB watchdog event", model.public.protocol === "MGR2"
|
||||
? "none recorded" : "unavailable in MGR1"]);
|
||||
}
|
||||
summary.replaceChildren();
|
||||
values.forEach(([term, description]) => {
|
||||
const item = document.createElement("div");
|
||||
@@ -540,7 +653,7 @@
|
||||
});
|
||||
status.textContent = model.authenticated
|
||||
? `Password-authenticated ${model.suppliedPages}/${model.pageCount} page${model.pageCount === 1 ? "" : "s"}.`
|
||||
: "Public-only decode — no authenticity claim without the management password.";
|
||||
: "Public-only decode - no authenticity claim without the management password.";
|
||||
status.classList.toggle("management-status-authenticated", model.authenticated);
|
||||
|
||||
warningList.replaceChildren();
|
||||
@@ -579,24 +692,36 @@
|
||||
}
|
||||
|
||||
async function decodeInput() {
|
||||
const request = {};
|
||||
activeDecode = request;
|
||||
resetResults();
|
||||
error.hidden = true;
|
||||
decode.disabled = true;
|
||||
try {
|
||||
render(await decodeManagement(input.value, password.value, candidate.value.trim()));
|
||||
const model = await decodeManagement(input.value, password.value, candidate.value.trim());
|
||||
if (activeDecode === request) render(model);
|
||||
} catch (failure) {
|
||||
showError(failure);
|
||||
if (activeDecode === request) showError(failure);
|
||||
} finally {
|
||||
decode.disabled = false;
|
||||
if (activeDecode === request) {
|
||||
activeDecode = null;
|
||||
decode.disabled = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
decode.addEventListener("click", decodeInput);
|
||||
clear.addEventListener("click", () => {
|
||||
// Web Crypto cannot be cancelled, but an older promise must never
|
||||
// redisplay decrypted ACL entries after Clear or a newer decode.
|
||||
activeDecode = null;
|
||||
input.value = "";
|
||||
password.value = "";
|
||||
candidate.value = "";
|
||||
results.hidden = true;
|
||||
resetResults();
|
||||
error.hidden = true;
|
||||
error.textContent = "";
|
||||
decode.disabled = false;
|
||||
input.focus();
|
||||
});
|
||||
input.addEventListener("keydown", (event) => {
|
||||
|
||||
@@ -5,7 +5,8 @@
|
||||
const EXAMPLES = {
|
||||
find: { label: "Find my node", kind: "all", id: "", when: "disconnected", vibration: "200,200,200", led: "100,100,100,700", sound: "find:d=8,o=6,b=180:c,e,g,p", screen: "on", gpio: "off", repeat: "forever", gap: "1000", stop: "connected" },
|
||||
food: { label: "Your food truck order is ready", kind: "channel", id: "3", when: "any", vibration: "50,300,40,20,500", led: "100,100,100,500", sound: "order:d=8,o=5,b=180:c,e,g,4c6", screen: "on", gpio: "off", repeat: "forever", gap: "2000", stop: "button" },
|
||||
vip: { label: "VIP chat", kind: "contact", id: "", when: "disconnected", vibration: "100,100,300", led: "50,300,40,20,500", sound: "vip:d=8,o=5,b=180:c,e,g,4g", screen: "on", gpio: "off", repeat: "2", gap: "500", stop: "button" }
|
||||
vip: { label: "VIP chat", kind: "contact", id: "", when: "disconnected", vibration: "100,100,300", led: "50,300,40,20,500", sound: "vip:d=8,o=5,b=180:c,e,g,4g", screen: "on", gpio: "off", repeat: "2", gap: "500", stop: "button" },
|
||||
channel9: { label: "Only channel 9", kind: "channel", id: "9", when: "any", vibration: "off", led: "off", sound: "ch9:d=8,o=5,b=180:c,e,g", screen: "off", gpio: "off", repeat: "1", gap: "500", stop: "button", quietOthers: "on" }
|
||||
};
|
||||
function pulse(text) {
|
||||
if (text === "off" || text === "inherit") return [];
|
||||
@@ -91,6 +92,14 @@
|
||||
if (config.repeat !== "forever" && (!/^\d+$/.test(config.repeat) || Number(config.repeat) < 1 || Number(config.repeat) > 65535)) throw new Error("Repeat must be 1-65535 or forever.");
|
||||
if (!/^\d+$/.test(config.gap) || Number(config.gap) < 1 || Number(config.gap) > 60000) throw new Error("Repeat gap must be 1-60000 ms.");
|
||||
if (!["button", "connected", "never"].includes(config.stop)) throw new Error("Unknown stop condition.");
|
||||
if (config.quietOthers === "on") {
|
||||
if (config.kind !== "channel") throw new Error("Silencing other message alerts needs a channel exception.");
|
||||
// This is a shared RULE, not master off: the selected channel can still
|
||||
// override it. Copy and USB Save must apply the same two-rule recipe.
|
||||
result.push("set notify.enabled on");
|
||||
for (const name of Object.keys(OUTPUTS)) result.push("set notify." + name + " all off");
|
||||
result.push("set notify.repeat all 1", "set notify.gap all 500", "set notify.stop all button");
|
||||
}
|
||||
for (const name of Object.keys(OUTPUTS)) {
|
||||
if (!["off", "inherit"].includes(config[name])) result.push("set notify." + name + " on");
|
||||
result.push("set notify." + name + " " + target + " " + config[name]);
|
||||
@@ -104,6 +113,7 @@
|
||||
return result;
|
||||
}
|
||||
function notificationText(config) {
|
||||
if (config.quietOthers === "on") throw new Error("Use CLI commands or USB Save for the silent default and channel exception; a notification DM cannot configure these rules.");
|
||||
const parts = [];
|
||||
for (const name of ["vibration", "sound", "led", "screen"]) if (config[name] !== "inherit") parts.push(name + "=" + config[name]);
|
||||
for (const name of ["repeat", "gap"]) parts.push(name + "=" + config[name]);
|
||||
@@ -185,6 +195,10 @@
|
||||
let client = null, supported = null, previewTimer = null, previewUrl = null, previewVersion = 0;
|
||||
const config = () => Object.fromEntries(new FormData(form));
|
||||
function render() {
|
||||
const quiet = form.elements.quietOthers;
|
||||
quiet.disabled = form.elements.kind.value !== "channel";
|
||||
if (quiet.disabled) quiet.checked = false;
|
||||
find("quiet-warning").hidden = !quiet.checked;
|
||||
try {
|
||||
output.textContent = commands(config()).join("\n"); error.textContent = "";
|
||||
try { find("dm").textContent = notificationText(config()); }
|
||||
@@ -193,8 +207,11 @@
|
||||
catch (e) { output.textContent = ""; find("dm").textContent = ""; error.textContent = e.message; }
|
||||
}
|
||||
function preset(name) {
|
||||
const values = { remote: "inherit", ...EXAMPLES[name] };
|
||||
for (const [key, value] of Object.entries(values)) if (form.elements[key]) form.elements[key].value = value;
|
||||
const values = { remote: "inherit", quietOthers: "off", ...EXAMPLES[name] };
|
||||
for (const [key, value] of Object.entries(values)) if (form.elements[key]) {
|
||||
if (key === "quietOthers") form.elements[key].checked = value === "on";
|
||||
else form.elements[key].value = value;
|
||||
}
|
||||
render();
|
||||
}
|
||||
function stopPreview(status = "Stopped") {
|
||||
@@ -296,6 +313,9 @@
|
||||
const c = config(), list = commands(c);
|
||||
if (!client || supported === null) throw new Error("Connect updated Companion firmware first.");
|
||||
for (const name of Object.keys(OUTPUTS)) if (!["off", "inherit"].includes(c[name]) && !(supported & OUTPUTS[name])) throw new Error(name + " is unavailable on this device. Choose off or inherit.");
|
||||
// Resolve/check numeric channel slots before changing the shared
|
||||
// default: a missing exception channel must not leave all alerts mute.
|
||||
if (c.quietOthers === "on") await run("get notify.sound " + selector(c));
|
||||
for (const text of list) await run(text);
|
||||
if (action === "apply-test") await run("notify.test " + selector(c));
|
||||
}
|
||||
|
||||
@@ -17,6 +17,59 @@
|
||||
const METERS_PER_DEGREE = 111320;
|
||||
const DEGREES_TO_RADIANS = Math.PI / 180;
|
||||
|
||||
// Keep the browser form out of GitHub's Markdown preview.
|
||||
const DECODER_MARKUP = `
|
||||
<div class="telemetry-examples" aria-label="Load an example reply">
|
||||
<strong>Try an analyzer example:</strong>
|
||||
<button type="button" data-telemetry-example="packetTemperature">Temperature packet</button>
|
||||
<button type="button" data-telemetry-example="packetVoltage">Voltage packet</button>
|
||||
<button type="button" data-telemetry-example="packetExternalVoltage">I2C voltage packet</button>
|
||||
<button type="button" data-telemetry-example="externalVoltage">I2C voltage CLI page</button>
|
||||
</div>
|
||||
|
||||
<label for="telemetry-reply-input">Raw packet or payload hex</label>
|
||||
<textarea
|
||||
id="telemetry-reply-input"
|
||||
data-role="input"
|
||||
spellcheck="false"
|
||||
autocomplete="off"
|
||||
placeholder="Paste hexadecimal Raw Data from the analyzer packet page"
|
||||
aria-describedby="telemetry-input-help"
|
||||
></textarea>
|
||||
<p class="telemetry-tool-help" id="telemetry-input-help">
|
||||
Spaces, line breaks, colons, dashes, a leading <code>0x</code>, and a quoted
|
||||
JSON field are accepted. CLI Base64 replies are also auto-detected. Paste
|
||||
multiple compatible packet or reply lines together to merge them by
|
||||
timestamp before downloading one CSV. Press Ctrl/Command+Enter to decode.
|
||||
</p>
|
||||
|
||||
<div class="telemetry-actions">
|
||||
<button class="telemetry-primary-action" type="button" data-role="decode">Decode telemetry</button>
|
||||
<button type="button" data-role="clear">Clear</button>
|
||||
<label class="telemetry-local-time">
|
||||
<input type="checkbox" data-role="local-time">
|
||||
Show browser-local time
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="telemetry-error" data-role="error" role="alert" aria-live="polite" hidden></div>
|
||||
|
||||
<section class="telemetry-results" data-role="results" aria-live="polite" hidden>
|
||||
<div class="telemetry-results-header">
|
||||
<h2 data-role="result-title">Decoded telemetry</h2>
|
||||
<button type="button" data-role="download">Download CSV</button>
|
||||
</div>
|
||||
<dl class="telemetry-summary" data-role="summary"></dl>
|
||||
<div class="telemetry-warnings" data-role="warnings" hidden>
|
||||
<strong>Decode notes</strong>
|
||||
<ul data-role="warning-list"></ul>
|
||||
</div>
|
||||
<div class="telemetry-table-wrap">
|
||||
<table class="telemetry-table" data-role="table"></table>
|
||||
</div>
|
||||
</section>
|
||||
`;
|
||||
|
||||
const EXAMPLES = Object.freeze({
|
||||
packetTemperature: Object.freeze({
|
||||
label: "Analyzer temperature packet",
|
||||
@@ -976,6 +1029,9 @@
|
||||
const root = document.querySelector("[data-telemetry-decoder]");
|
||||
if (!root) return;
|
||||
|
||||
// This is trusted, static markup; pasted telemetry is rendered as text below.
|
||||
root.innerHTML = DECODER_MARKUP;
|
||||
|
||||
const input = root.querySelector("[data-role='input']");
|
||||
const decodeButton = root.querySelector("[data-role='decode']");
|
||||
const clearButton = root.querySelector("[data-role='clear']");
|
||||
|
||||
@@ -0,0 +1,101 @@
|
||||
.partition-catalog {
|
||||
min-width: 0;
|
||||
margin: 1rem 0;
|
||||
}
|
||||
|
||||
.partition-catalog-controls {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
gap: 0.6rem 1rem;
|
||||
}
|
||||
|
||||
.partition-catalog-controls label {
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.partition-catalog-controls select,
|
||||
.partition-catalog-controls input {
|
||||
max-width: 100%;
|
||||
padding: 0.5rem;
|
||||
border: 1px solid var(--md-default-fg-color--light);
|
||||
border-radius: 0.25rem;
|
||||
background: var(--md-default-bg-color);
|
||||
color: var(--md-default-fg-color);
|
||||
font: inherit;
|
||||
}
|
||||
|
||||
.partition-catalog-controls select:focus-visible,
|
||||
.partition-catalog-controls input:focus-visible,
|
||||
.partition-catalog-table:focus-visible,
|
||||
.partition-catalog-json:focus-visible {
|
||||
outline: 2px solid var(--md-accent-fg-color);
|
||||
outline-offset: 2px;
|
||||
}
|
||||
|
||||
.partition-catalog-table {
|
||||
max-height: 32rem;
|
||||
overflow: auto;
|
||||
overscroll-behavior: contain;
|
||||
}
|
||||
|
||||
.partition-catalog [hidden] {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
.md-typeset .partition-catalog-table table {
|
||||
display: table;
|
||||
width: 100%;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.partition-catalog-table thead th {
|
||||
position: sticky;
|
||||
top: 0;
|
||||
background: var(--md-default-bg-color);
|
||||
}
|
||||
|
||||
.partition-catalog-table tbody th {
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
|
||||
.partition-catalog-table td small {
|
||||
display: block;
|
||||
color: var(--md-default-fg-color--light);
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.partition-catalog-raw {
|
||||
margin-top: 1rem;
|
||||
}
|
||||
|
||||
.partition-catalog-raw summary {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.partition-catalog-raw .partition-catalog-controls {
|
||||
margin-top: 1rem;
|
||||
}
|
||||
|
||||
@media (max-width: 600px) {
|
||||
.md-typeset .partition-catalog-table table th,
|
||||
.md-typeset .partition-catalog-table table td {
|
||||
padding: 0.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
.md-typeset .partition-catalog-json {
|
||||
max-height: 32rem;
|
||||
overflow: auto;
|
||||
overscroll-behavior: contain;
|
||||
border: 1px solid var(--md-default-fg-color--lightest);
|
||||
border-radius: 0.25rem;
|
||||
}
|
||||
|
||||
.md-typeset .partition-catalog-json > code {
|
||||
display: block;
|
||||
padding: 1rem;
|
||||
white-space: pre;
|
||||
overflow-wrap: normal;
|
||||
word-break: normal;
|
||||
}
|
||||
@@ -38,6 +38,46 @@
|
||||
margin: 1.2rem 0;
|
||||
}
|
||||
|
||||
.firmware-picker [hidden] {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
.firmware-picker-feedback {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 0.6rem;
|
||||
padding: 0.65rem 0.8rem;
|
||||
border: 1px solid var(--picker-border);
|
||||
border-left: 0.3rem solid var(--picker-accent);
|
||||
border-radius: 0.4rem;
|
||||
background: var(--md-default-bg-color, white);
|
||||
}
|
||||
|
||||
.firmware-picker-feedback[data-state="single"] {
|
||||
border-color: var(--picker-accent);
|
||||
}
|
||||
|
||||
.firmware-picker-feedback p {
|
||||
margin: 0.2rem 0 0;
|
||||
font-size: 0.75rem;
|
||||
line-height: 1.35;
|
||||
}
|
||||
|
||||
.firmware-picker-feedback a {
|
||||
flex-shrink: 0;
|
||||
padding: 0.55rem 0.65rem;
|
||||
border: 1px solid var(--picker-accent);
|
||||
border-radius: 0.35rem;
|
||||
font-size: 0.8rem;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.firmware-picker-feedback a:focus-visible {
|
||||
outline: 0.16rem solid var(--picker-accent);
|
||||
outline-offset: 0.12rem;
|
||||
}
|
||||
|
||||
.firmware-picker-order-note {
|
||||
margin: 1rem 0 0;
|
||||
color: var(--md-default-fg-color--light);
|
||||
@@ -76,10 +116,26 @@
|
||||
}
|
||||
|
||||
.firmware-picker-radio-control > legend {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
margin-bottom: 0.35rem;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.firmware-picker-same-firmware {
|
||||
padding: 0.15rem 0.45rem;
|
||||
border: 1px solid var(--picker-border);
|
||||
border-radius: 999px;
|
||||
font-size: 0.65rem;
|
||||
font-weight: 600;
|
||||
background: color-mix(in srgb, var(--picker-accent) 10%, transparent);
|
||||
}
|
||||
|
||||
.firmware-picker-refinement-hint {
|
||||
font-size: 0.75rem;
|
||||
}
|
||||
|
||||
.firmware-picker-select-control select,
|
||||
.firmware-asset-browser input {
|
||||
width: 100%;
|
||||
@@ -190,6 +246,7 @@
|
||||
|
||||
.firmware-picker-result {
|
||||
border-top: 0.3rem solid var(--picker-accent);
|
||||
scroll-margin-top: 4rem;
|
||||
}
|
||||
|
||||
.firmware-picker-eyebrow {
|
||||
@@ -309,6 +366,17 @@
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.firmware-picker-feedback {
|
||||
position: sticky;
|
||||
top: 0.5rem;
|
||||
z-index: 2;
|
||||
box-shadow: 0 0.2rem 0.5rem rgb(0 0 0 / 10%);
|
||||
}
|
||||
|
||||
.md-typeset .firmware-picker-feedback {
|
||||
top: calc(var(--md-header-height, 2.4rem) + 0.4rem);
|
||||
}
|
||||
|
||||
.firmware-picker-facts {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
@@ -7,6 +7,8 @@
|
||||
.notification-builder small { font-size: .7rem; font-weight: 400; color: var(--md-default-fg-color--light); }
|
||||
.notification-builder input, .notification-builder select { width: 100%; min-width: 0; border: 1px solid var(--md-default-fg-color--lighter); border-radius: 6px; padding: .6rem; background: var(--md-default-bg-color); color: var(--md-default-fg-color); font: inherit; font-weight: 400; }
|
||||
.notification-builder input:focus, .notification-builder select:focus { outline: 2px solid var(--md-primary-fg-color); outline-offset: 2px; }
|
||||
.notification-builder .notification-checkbox { flex-direction: row; align-items: center; margin-top: 1rem; }
|
||||
.notification-builder .notification-checkbox input { width: auto; flex: 0 0 auto; padding: 0; }
|
||||
.notification-builder button { border: 1px solid var(--md-primary-fg-color); border-radius: 6px; padding: .5rem .75rem; margin: .2rem; cursor: pointer; color: var(--md-primary-fg-color); font: inherit; }
|
||||
.notification-builder button:hover { background: var(--md-primary-fg-color); color: var(--md-primary-bg-color); }
|
||||
.notification-builder button:disabled { opacity: .5; cursor: wait; }
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
<!-- meshcore-hosted-doc-link:start -->
|
||||
<p class="meshcore-hosted-doc-link"><a href="https://mikecarper.github.io/MeshCore/bootloader_version_stock_audit/">View this page on MeshCore Docs</a>.</p>
|
||||
<!-- meshcore-hosted-doc-link:end -->
|
||||
|
||||
# Document moved
|
||||
|
||||
<script>window.location.replace(new URL("../test-results/bootloader_version_stock_audit/" + window.location.hash, window.location.href));</script>
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
<!-- meshcore-hosted-doc-link:start -->
|
||||
<p class="meshcore-hosted-doc-link"><a href="https://mikecarper.github.io/MeshCore/cad_scan_validation/">View this page on MeshCore Docs</a>.</p>
|
||||
<!-- meshcore-hosted-doc-link:end -->
|
||||
|
||||
# Document moved
|
||||
|
||||
<script>window.location.replace(new URL("../test-results/cad_scan_validation/" + window.location.hash, window.location.href));</script>
|
||||
|
||||
@@ -1,5 +1,187 @@
|
||||
<!-- meshcore-hosted-doc-link:start -->
|
||||
<p class="meshcore-hosted-doc-link"><a href="https://mikecarper.github.io/MeshCore/carrier_wave/">View this page on MeshCore Docs</a>.</p>
|
||||
<!-- meshcore-hosted-doc-link:end -->
|
||||
|
||||
# Carrier-wave RF checks
|
||||
|
||||
## TX-only Morse messages
|
||||
|
||||
Morse TX mode keys an unmodulated carrier into dots and dashes. It does not send
|
||||
LoRa packets or automatically retransmit channel chat or
|
||||
private messages. **The default is 902.100 MHz at 25 words per minute (WPM).**
|
||||
Nothing transmits until an explicit `morse send` command is accepted.
|
||||
|
||||
| Command | Action |
|
||||
| --- | --- |
|
||||
| `morse send TEST DE N0CALL` | Send the text once using the saved Morse frequency and speed. |
|
||||
| `morse stop` | Stop Morse immediately and restore packet reception. |
|
||||
| `get morse` | Show idle/active state, frequency, WPM and remaining time. |
|
||||
| `get morse.freq` | Show the Morse TX frequency in MHz. |
|
||||
| `set morse.freq 902.100` | Save the separate Morse TX frequency, rounded to 1 kHz. |
|
||||
| `get morse.wpm` | Show the saved sending speed. |
|
||||
| `set morse.wpm 12` | Slow transmission for easier hand decoding; supported range is 1–60 WPM. |
|
||||
|
||||
The default 25 WPM can be changed; for an easy-to-copy test, set 10–12 WPM.
|
||||
Standard timing uses one unit for a dot, three for a dash, one between elements,
|
||||
three between letters and seven between words, with a dot of `1200 / WPM`
|
||||
milliseconds. Letters are sent case-insensitively. Messages are bounded to
|
||||
80 ASCII characters, must contain supported Morse characters, and must fit
|
||||
within 180 seconds at the chosen speed. Supported punctuation is
|
||||
`. , ? ' ! / ( ) & : ; = + - _ " $ @`; tabs, newlines and non-ASCII text are
|
||||
rejected. Invalid messages are rejected before
|
||||
the transmitter is keyed. There is no automatic callsign insertion.
|
||||
|
||||
The Morse frequency is independent of `radio`, `tempradio`, `radio2` and
|
||||
`tempradio2`. Before sending, the firmware rejects a frequency that overlaps
|
||||
either packet-radio profile, including a guard outside its occupied bandwidth.
|
||||
It does **not** automatically find another channel or change your mesh settings.
|
||||
Choose another `morse.freq` if 902.100 MHz overlaps either profile. A frequency
|
||||
accepted as a saved value must still be supported by the actual radio at send
|
||||
time. Morse frequency/speed changes are rejected while Morse or CW is active.
|
||||
|
||||
There is one physical transceiver: packet reception and transmission pause
|
||||
throughout the message, including the spaces between dots and dashes. Normal
|
||||
receive/profile scanning is restored afterward. Use local USB/BLE for immediate
|
||||
status and stopping; a reply to a remote LoRa CLI command waits until the
|
||||
message finishes. Changing either packet profile during Morse can abort it to
|
||||
preserve the frequency-separation safety checks.
|
||||
|
||||
An unmodulated carrier has no selectable LoRa bandwidth. This uses native CW
|
||||
on SX1262/SX1268/LLCC68/STM32WLx and LR1110/LR1121/LR2021 radios, and
|
||||
zero-deviation FSK CW on SX1276-family radios with reset/recovery support, rather
|
||||
than pretending the narrowest receive filter sets the transmitted bandwidth.
|
||||
Morse sidebands depend on keying speed and transmitter edges. This is not a
|
||||
guarantee of a particular occupied RF bandwidth or freedom from interference.
|
||||
Unsupported radios reject the command without beginning transmission.
|
||||
The chips' configured TX ramps use 3400 microseconds on the SX126x-family and
|
||||
304 microseconds on the LR-family to reduce keying splatter. These are driver
|
||||
settings, not measured occupied-bandwidth results. Direct FEM gain changes are
|
||||
blocked while Morse owns the radio.
|
||||
|
||||
Frequency and speed are saved in a separate checked `/morse_prefs` file;
|
||||
identity, contacts and existing radio preferences are unchanged. An active
|
||||
message is never restored after reboot. TX uses the board's existing `set tx`
|
||||
power setting and hardware power limits.
|
||||
Unreadable or corrupt saved Morse settings without a usable backup block
|
||||
sending rather than silently
|
||||
transmitting on the default frequency. After storage recovers, verify and save
|
||||
`morse.freq` (and the desired WPM); a verified save makes sending available again.
|
||||
|
||||
Use an appropriate antenna or dummy load, lawful frequency/power and any
|
||||
required operator identification. The default frequency is a convenience, not
|
||||
permission to transmit or a claim that the channel is clear. Check the local
|
||||
band plan and listen first.
|
||||
|
||||
## Timed Morse reception and retained captures
|
||||
|
||||
Supported radios can temporarily listen for keyed carriers and decode the
|
||||
power envelope into text. This is a separate manual session, not another
|
||||
MeshCore packet channel or an authenticated sender. Nothing decoded is
|
||||
automatically transmitted, relayed or inserted into a channel/DM inbox.
|
||||
|
||||
| Command | Action |
|
||||
| --- | --- |
|
||||
| `morse listen 1` | Listen for one minute at the saved `morse.freq`, using `morse.wpm` as the timing hint. |
|
||||
| `morse listen 0.5` | Listen for 30 seconds. Fractions are allowed. |
|
||||
| `morse listen 5` | Listen for the maximum five minutes, then restore packet reception. |
|
||||
| `morse stop` | End TX or listening early. A received capture is retained. |
|
||||
| `get morse` | Show TX/RX state, actual RX filter bandwidth, remaining time and capture length/truncation. |
|
||||
| `morse stats` | Show retained receiver sample counts, noise/min/max/last power, timing unit, largest sample gap and read failures. |
|
||||
| `morse read` | Read the first retained capture page. |
|
||||
| `morse read 128` | Read beginning at character offset 128; use the offset returned by your previous page. |
|
||||
| `morse clear` | Clear the retained capture when no Morse session is active. |
|
||||
|
||||
Listening is bounded to **1–300 seconds** for every role. Keep the first
|
||||
0.5 seconds quiet while the receiver learns its noise floor; start the sending
|
||||
radio at least one second after listening begins. Start with matching WPM on
|
||||
the sender and receiver. Noise, fading, interference and timing mismatch can
|
||||
produce errors; decoded text has no sender identity or integrity check.
|
||||
|
||||
The receiver uses a continuous native FSK receive filter and live received
|
||||
signal strength (RSSI) to follow the carrier's on/off envelope. It does not
|
||||
transmit or require a packet preamble, sync word, CRC or direct-data pin:
|
||||
|
||||
| Radio family | Current Morse RX filter |
|
||||
| --- | ---: |
|
||||
| SX1276-family | 2.6 kHz |
|
||||
| SX1262/SX1268/STM32WLx, LLCC68 | 4.8 kHz |
|
||||
| LR1110/LR1121, LR2021 | 4.8 kHz |
|
||||
|
||||
Small-flash STM32 production builds disable Morse reception by default
|
||||
(`MESH_ENABLE_MORSE_RX=0`) because the receiver does not fit alongside their
|
||||
existing features. Morse TX, frequency/WPM settings and stopping remain
|
||||
available. Their listen/read/stats/clear commands return an unsupported-RX
|
||||
error. The STM32 filter above describes an explicitly enabled custom build,
|
||||
not RX support in those default production images.
|
||||
|
||||
`get morse` reports the actual configured bandwidth; this is not a measured
|
||||
selectivity or decoder-speed guarantee. Unsupported hardware rejects listening
|
||||
without changing the radio. A board also needs a proven complete LoRa-modem
|
||||
restoration path before native FSK RX is enabled. Narrow filters require
|
||||
sufficiently aligned oscillator frequencies. Passive listening may use either packet profile's
|
||||
frequency because it does not transmit; TX overlap protections still apply.
|
||||
FEM and saved Morse settings cannot be changed while a session owns the radio.
|
||||
|
||||
Morse sessions defer periodic sensor/GPS processing, screen redraws and
|
||||
background storage work so they do not interrupt envelope timing. Pending
|
||||
work and its deadlines are retained and resume when the session ends. Buttons,
|
||||
command connections, watchdogs and battery shutdown checks remain active.
|
||||
`morse stats` reports `gap` in milliseconds: the largest observed gap between
|
||||
receiver samples, including unavoidable safety work. Large gaps can limit
|
||||
reliable speed even when the RF signal is strong.
|
||||
|
||||
Repeater, Room, Sensor and Companion roles retain a bounded **4096-character
|
||||
RAM capture** after their normal packet receiver resumes. A successful new
|
||||
listening session replaces it; a failed start does not. `morse clear` or a
|
||||
reboot removes it. If the capture fills, listening stops early and
|
||||
`truncated=1`/capture-quota status is shown instead of continuing to discard
|
||||
incoming text. This does not change node keys, ACLs, stored radio
|
||||
settings or other files.
|
||||
|
||||
Capture replies fit one CLI response and begin with a page header such as
|
||||
`> 0000/0250 next=0125 truncated=0 ` followed by text. Send `morse read 125` to
|
||||
continue at the returned offset; `next=END` means the last page. Exact page
|
||||
length depends on the requesting transport's reply capacity, so follow `next`
|
||||
rather than assuming a fixed page size. Captures remain readable until you
|
||||
start another listening session or clear them.
|
||||
|
||||
A Full Companion's owned buffered USB ASCII terminal or browser terminal shows
|
||||
decoded progress as `[Morse RX] ...`, paused while USB input is being edited or
|
||||
an earlier reply is draining. Complete records use that terminal's existing
|
||||
ordered output queue. Raw UART/HWCDC/TCP terminals use `get morse`/`morse read`
|
||||
instead of unsolicited progress. Binary Companion USB/BLE frames never receive
|
||||
raw text.
|
||||
Companion apps and other clients can request the same capture through their
|
||||
CLI using `morse read`; it is not presented as an authenticated chat message.
|
||||
There is no automatic LCD/inbox injection.
|
||||
|
||||
One transceiver cannot listen for Morse and MeshCore LoRa simultaneously.
|
||||
During listening, normal packet reception/transmission is paused. An authorized
|
||||
remote `morse listen` first queues its reply on the existing packet-radio
|
||||
settings and starts only after the exact reply copies finish transmitting,
|
||||
with at least one successful transmission. It does not infer that the remote
|
||||
client received the reply. Failed queue admission/all-copy failure, permission
|
||||
revocation, changed Morse settings or a 30-second handoff deadline cancel the
|
||||
request without starting RX. A briefly busy radio can retry admission for up
|
||||
to 10 seconds after reply transmission, within that same handoff deadline.
|
||||
For remote captures, send `morse listen 1`, start the Morse sender after the
|
||||
queued reply and initial quiet learning interval, wait beyond the selected
|
||||
minute (plus any admission delay), then request
|
||||
`get morse` and `morse read` over LoRa. Use a local console to stop early.
|
||||
Hardware recovery failures hold exclusive ownership until the receiver can
|
||||
be safely restored; `get morse` reports recovery pending.
|
||||
|
||||
Lab images compiled with `MESH_MORSE_HIL=1` additionally provide
|
||||
`morse trace [offset]`. A page begins `> offset/length t=total n=next x=wrapped`
|
||||
and contains up to eight `elapsed_ms,rssi_dbm,flags;` records. `n=END` ends the
|
||||
retained window; `x=1` means earlier entries were overwritten. Offsets address
|
||||
the oldest of the latest 512 retained records; retrieve after stopping or
|
||||
check that `t` stays unchanged between pages. Flags are raw key 1, committed
|
||||
key 2, debounce candidate 4 and noise learning 8. This diagnostic command and
|
||||
its trace storage are absent from ordinary production builds.
|
||||
|
||||
## Continuous carrier checks
|
||||
|
||||
`cw` and `cw2` transmit a continuous, unmodulated carrier at the selected
|
||||
profile's frequency and the configured TX power. They are available in the
|
||||
shared ASCII CLI on SX1276-family, SX1262-family, and LR1110 builds, including repeaters,
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
<!-- meshcore-hosted-doc-link:start -->
|
||||
<p class="meshcore-hosted-doc-link"><a href="https://mikecarper.github.io/MeshCore/carrier_wave_validation/">View this page on MeshCore Docs</a>.</p>
|
||||
<!-- meshcore-hosted-doc-link:end -->
|
||||
|
||||
# Document moved
|
||||
|
||||
<script>window.location.replace(new URL("../test-results/carrier_wave_validation/" + window.location.hash, window.location.href));</script>
|
||||
|
||||
@@ -0,0 +1,80 @@
|
||||
<!-- meshcore-hosted-doc-link:start -->
|
||||
<p class="meshcore-hosted-doc-link"><a href="https://mikecarper.github.io/MeshCore/charge_voltage/">View this page on MeshCore Docs</a>.</p>
|
||||
<!-- meshcore-hosted-doc-link:end -->
|
||||
|
||||
# Battery charge voltage
|
||||
|
||||
The `charge.voltage` commands change the battery charger's constant-voltage
|
||||
target on supported boards. They are available through the shared text CLI in
|
||||
the companion, repeater, room-server, sensor, and secure-chat roles. They do not change the
|
||||
battery voltage display, low-voltage shutdown, battery chemistry, or charge
|
||||
current.
|
||||
|
||||
```text
|
||||
get charge.voltage
|
||||
get charge.voltage.options
|
||||
set charge.voltage 4.1
|
||||
```
|
||||
|
||||
Values are in volts. Query the options first: supported targets depend on the
|
||||
PMU fitted to the board. The firmware rejects values that the detected PMU
|
||||
cannot represent; it does not round them to a supported target.
|
||||
|
||||
On T-Beam boards, a successful change is saved in NVS and reapplied after a
|
||||
restart. The firmware reads the PMU setting back before reporting success.
|
||||
Boards without a saved setting retain their existing startup behavior: the full
|
||||
PMU driver sets 4.2 V, while the compact classic T-Beam observer driver leaves
|
||||
the charger setting untouched. A failed hardware write, readback, or preference
|
||||
save is reported as an error.
|
||||
|
||||
If the saved target cannot be read, is invalid, or cannot be restored, startup
|
||||
does not overwrite it with a default target. `get charge.voltage` reports the
|
||||
live charger voltage with `(boot restore failed)`. A successful saved change
|
||||
clears that warning. This cannot guarantee restoration while the PMU or storage
|
||||
is failing, but it avoids silently resetting a saved lower target to 4.2 V.
|
||||
|
||||
## Supported boards
|
||||
|
||||
| Board family | Detected PMU | Charge targets (V) |
|
||||
| --- | --- | --- |
|
||||
| LilyGo T-Beam SX1262 or SX1276 | AXP192 | 4.10, 4.15, 4.20, 4.36 |
|
||||
| LilyGo T-Beam SX1262 or SX1276 | AXP2101 | 4.00, 4.10, 4.20, 4.35, 4.40 |
|
||||
| LilyGo T-Beam S3 Supreme SX1262 | AXP2101 | 4.00, 4.10, 4.20, 4.35, 4.40 |
|
||||
|
||||
Targets above 4.2 V require batteries rated for the selected charge voltage.
|
||||
|
||||
The classic T-Beam driver detects the fitted AXP192 or AXP2101. Radio type does
|
||||
not determine which PMU is present. T-Beam 1W uses a different power design and
|
||||
does not support this setting.
|
||||
|
||||
Neither AXP192 nor AXP2101 supports a 3.65 V target. The lowest supported target
|
||||
is 4.10 V for AXP192 and 4.00 V for AXP2101. These PMUs cannot be configured as
|
||||
LiFePO4 chargers by changing this setting.
|
||||
|
||||
## Heltec Mesh Solar
|
||||
|
||||
Mesh Solar reports `charge.voltage` as unsupported because its CN3795 charger's
|
||||
constant-voltage target is set by hardware. Heltec supplies separate Li-ion
|
||||
(`I`) and LiFePO4 (`F`) hardware versions, with 4.2 V/cell and 3.6 V/cell charging
|
||||
respectively. Cell-count soldering and switches must also match the battery
|
||||
configuration. A software battery-type setting does not convert one hardware
|
||||
version into the other. See [Heltec's usage guide](https://wiki.heltec.org/docs/devices/open-source-hardware/nrf52840-series/mesh-solar/usage-guide)
|
||||
and [MeshSolar datasheet, sections 4.1 and 4.4](https://resource.heltec.cn/download/MeshSolar/datasheet/MeshSolar_V1.0.0.pdf).
|
||||
|
||||
The BQ4050 on this board is a battery gauge and protection controller. Its
|
||||
`ChargingVoltage()` is a requested voltage for a compatible smart charger. The
|
||||
pinned Mesh Solar library calls that parameter `eoc`, but writing it does not
|
||||
change the CN3795's voltage-feedback circuit. Valid charge termination also
|
||||
requires the charging current to taper, so lowering `eoc` alone does not create
|
||||
a reliable lower-voltage charge cutoff. Charge-overvoltage protection is a
|
||||
separate safeguard, not the charger's normal constant-voltage target. See
|
||||
[TI's BQ4050 technical reference manual, sections 4.5, 4.6, and 4.11](https://www.ti.com/lit/pdf/SLUUAQ3).
|
||||
|
||||
These commands do not write Mesh Solar's BQ4050 data flash or change its battery
|
||||
type, protection limits, cell count, or charge-FET configuration.
|
||||
|
||||
## Battery profiles
|
||||
|
||||
The nRF52 `battery.profile`, `battery.full`, and `battery.empty` settings control
|
||||
voltage-based reporting and protection. They do not change any charger target.
|
||||
See [Battery profile commands](cli_commands.md#configure-nrf52-battery-protection).
|
||||
+117
-24
@@ -1,3 +1,7 @@
|
||||
<!-- meshcore-hosted-doc-link:start -->
|
||||
<p class="meshcore-hosted-doc-link"><a href="https://mikecarper.github.io/MeshCore/cli_build_matrix/">View this page on MeshCore Docs</a>.</p>
|
||||
<!-- meshcore-hosted-doc-link:end -->
|
||||
|
||||
# CLI Availability by Firmware Build
|
||||
|
||||
MeshCore command availability is determined in three layers:
|
||||
@@ -28,8 +32,8 @@ and partition policy:
|
||||
|
||||
| Selection | Behavior |
|
||||
| --- | --- |
|
||||
| `auto` | For one explicit target, pass 1 builds the complete supported LoRa-OTA-capable recipe. ESP32 boards with a qualified expanded profile use it; other standalone repeaters and room servers attempt the complete recipe in their current application region. Other roles keep every capability declared by their resolved PlatformIO recipe, including LoRa OTA, and fail instead of silently removing one that does not fit. A measured repeater or room-server flash/partition overflow starts the standard `no_external_sensors` LoRa OTA pass. Internal-flash nRF52 repeaters and room servers publish that reduced pass even when the complete image fits, because the smaller running image leaves more room to stage a delta; matched QSPI/SD roles do not need the redundant artifact. Compiler errors and missing-capability checks never trigger or conceal a reduced build. Canonical bulk commands keep their established standard partition contract. |
|
||||
| `standard` | Immediately uses the deployed/portable partition contract and its documented reductions. This is useful when the operator already knows the expanded or complete image is unsuitable. |
|
||||
| `auto` | nRF52 repeater, room-server, and sensor builds publish the required pair: Full supported sensors + LoRa OTA and Reduced sensors + LoRa OTA. Both must pass flash, RAM, packaged-application OTA, and layout checks; a reduced image cannot substitute for a failing full image. The same policy applies to canonical bulk/release builds and external-QSPI/SD recipes. ESP32 boards with a qualified expanded profile use it; other standalone ESP32 repeaters and room servers attempt the complete recipe in their current application region, with a measured flash/partition-overflow fallback to their reduced LoRa OTA recipe. Other roles keep every capability declared by their resolved PlatformIO recipe and fail instead of silently removing one that does not fit. Compiler errors and missing-capability checks never trigger or conceal a reduced build. |
|
||||
| `standard` | Preserves the deployed/portable partition contract and documented reductions. nRF52 infrastructure still publishes both OTA sensor profiles in that same exact layout; this is not an override to remove OTA or skip the full profile. |
|
||||
| `full` | Requires a qualified ESP32 expanded-partition target (or an explicitly named Full Companion). Install a matching merged image when this changes the partition table. |
|
||||
|
||||
Successful builds also emit
|
||||
@@ -49,24 +53,33 @@ The direct observer and Full-matrix paths use the same `full-usb-wifi-ota`
|
||||
artifact and runtime output controls. The ordinary bulk release publishes one
|
||||
Full OTA identity per exact ESP32 board and role. Audited plain targets keep
|
||||
their identity while compiling the combined observer/ESP-NOW sources; G2 keeps
|
||||
its deployed observer identity. T-Beam SX1262/SX1276 and TLora V2 choose the
|
||||
observer in the ordinary release. Their plain Full recipes remain available
|
||||
its deployed observer identity. T-Beam SX1262/SX1276 choose the observer in
|
||||
the ordinary release. TLora V2 retains two Full images because UART, MQTT
|
||||
and ESP-NOW together exceed its internal RAM budget: the normal repeater
|
||||
provides UART plus ESP-NOW, and the observer provides MQTT plus ESP-NOW.
|
||||
T-Beam plain Full recipes remain available
|
||||
as explicit alternatives with different routing capacities. Build one with
|
||||
`bash build.sh build-firmware <target> --full-exact`, then deliberately install
|
||||
it over LoRa with `ota pull <MID8> flash` and `ota install` on compatible
|
||||
hardware and partition layouts.
|
||||
|
||||
Standard and reduced OTA images, including images that fit the 1.25 MiB
|
||||
portable application slot, remain available and remain in the standard matrix
|
||||
pass. Select `--build-profile standard` to retain the original target and
|
||||
partition contract. Moving from a plain target to its MQTT sibling changes the
|
||||
LoRa OTA target identity; a partition layout change needs its matching
|
||||
migration step.
|
||||
For the normal-role partition migrations, the matrix also publishes a
|
||||
canonical Full image under the normal target identity. Install its matching
|
||||
merged image over USB once; mOTA rejects the incompatible partition signature
|
||||
until that migration is complete, then later Full updates use the normal
|
||||
target ID. The portable image remains available while deployed nodes move.
|
||||
The ordinary ESP32 release publishes Full images. Standard and reduced OTA
|
||||
recipes, including those that fit a 1.25 MiB portable application slot, remain
|
||||
available through explicit builds for recovery and migration. Select
|
||||
`--build-profile standard` to retain an exact target's original partition
|
||||
contract. They are omitted from the ordinary release and interactive board
|
||||
menu. That menu also omits transport/default aliases covered by Full and
|
||||
development utilities; it retains distinct hardware, roles, storage, and
|
||||
measured capacity exceptions. Exact legacy names remain accepted by
|
||||
`build-firmware` and `build-matching-firmwares`.
|
||||
|
||||
For the normal-role partition migrations, the release publishes a canonical
|
||||
Full image under the normal target identity. Use its qualified wireless
|
||||
expansion package when one exists, or install its matching merged image over
|
||||
USB. mOTA rejects an incompatible partition signature until that migration
|
||||
is complete, then later Full updates use the normal target ID. Moving from a
|
||||
plain target to a differently named sibling can also change the LoRa OTA
|
||||
identity; partition expansion does not by itself authorize that transition.
|
||||
|
||||
Legacy supported ESP32 Wi-Fi repeaters can use the
|
||||
[Wi-Fi partition migration](esp32_wifi_partition_migration.md) bridge to
|
||||
@@ -104,15 +117,15 @@ getters; relaying a command over LoRa keeps its remote restrictions.
|
||||
|
||||
| Build/profile | Command availability |
|
||||
|---|---|
|
||||
| Standard non-MQTT repeater or room server | Keeps the normal role CLI and, where USB is a safe plaintext console, embeds debug/packet logging behind persistent `get/set usb.logging`. The explicitly selected portable policy can omit WebConfig and browser WiFi OTA, so those commands are unavailable and the omission is recorded in the capability manifest. |
|
||||
| Standard non-MQTT repeater or room server | Keeps the normal role CLI and, where USB is a safe plaintext console, embeds packet logging behind persistent `get/set usb.logging` and separately saved `get/set usb.debug` verbosity. USB debug defaults off and requires compiled diagnostic support. The explicitly selected portable policy can omit WebConfig and browser WiFi OTA, so those commands are unavailable and the omission is recorded in the capability manifest. |
|
||||
| Legacy standard logging | No longer emitted separately. Its behavior is compiled into the ordinary artifact. Size-constrained STM32 targets embed packet logging without verbose `MESH_DEBUG`. |
|
||||
| LoRa-OTA (`-ota-`) | LoRa OTA adds the `ota ...` commands; it does not otherwise reduce the role CLI. ESP32 `no_external_sensors` artifacts retain the compact browser WiFi uploader, the complete CLI, and up to 254 neighbors, subject to recorded internal-DRAM reductions. |
|
||||
| Internal-flash nRF52 repeater/room-server auto pair | `full-ota` retains the board's external-sensor drivers; `reduced-ota` omits the declared optional sensors to leave additional internal-flash staging room. RAK3401 and RAK4631 reduced builds retain INA219, INA226, INA260, and INA3221 I2C voltage/current monitors at a measured cost below 5 KiB. Both artifacts carry the same stable OTA target identity and are checked for `ota ...` and `retry.preset`; RAK artifacts also verify the retained monitor drivers. |
|
||||
| nRF52 repeater/room-server/sensor release pair | `full-ota` retains the complete supported sensor recipe; `reduced-ota` omits declared optional sensor drivers. Both include verified LoRa OTA, use the same logical target identity and exact storage/layout contract within the pair, and retain the role's normal commands. Repeaters/room servers also verify `retry.preset`. RAK3401 and RAK4631 reduced builds retain GPS where compatible and INA219/INA226/INA260/INA3221 I2C voltage/current monitors. External QSPI/SD targets also publish both choices. Companion/Terminal Chat and KISS roles do not enter this policy. |
|
||||
| ESP32 MQTT observer or ESP-NOW bridge | Always uses the expanded FULL partition profile. The build never substitutes a reduced CLI to fit the legacy application slot. |
|
||||
| FULL ESP32 USB + WiFi | Uses the matching MQTT target with packet logging on, verbose debug off, and the complete command surface supported by that role and hardware. `get/set logging.output off\|usb\|wifi\|both` selects and persists the active output paths. |
|
||||
| FULL ESP32 logging fallback | Uses the matching non-MQTT target only when no WiFi MQTT sibling exists, with debug and packet logging enabled and the complete command surface supported by that role and hardware. Its persistent USB gate also covers output-off operation, avoiding a second FULL ESP-NOW image. |
|
||||
| nRF52 dual-CDC Full Companion | Fresh installs expose only interface `00`; it starts as an ASCII terminal and automatically hands a complete `<` frame to framed Companion. The same interface also carries exclusive serial mOTA traffic. Enabling logging and rebooting adds interface `02` for plaintext logs. BLE and source-only LoRa OTA remain available. `get/set usb.logging` persistently controls whether the logging interface is present. |
|
||||
| ESP32 single-TTY Full Companion | Every ESP32 Full image starts with the ASCII terminal on its one USB TTY and automatically hands a complete `<` frame to framed Companion. On 1.17.1.5, run `set powersaving off` first. `set usb.logging on` switches that TTY to an input-capable plaintext logging terminal and makes framed Companion unavailable on USB; `set usb.logging off` stops logging but leaves the TTY in normal ASCII mode. The terminal stop token or a valid incoming framed probe then performs the ordinary switch to Binary Companion. A saved logging-on setting starts directly in that logging terminal and disables automatic frame detection. BLE, WiFi, and source-only LoRa OTA remain available. ESP32 Full uses Arduino-ESP32 2.x where supported; RC32 and ESP32-C6 keep their board-required Arduino 3.x platform but still expose only one TTY. |
|
||||
| FULL ESP32 USB + WiFi | Uses the matching MQTT target with packet logging, USB debug defaulting off, and the complete command surface supported by that role and hardware. Debug verbosity can be enabled only for diagnostics compiled into that image. `get/set logging.output off\|usb\|wifi\|both` selects and persists the active output paths; `get/set usb.debug` separately saves USB verbosity. |
|
||||
| FULL ESP32 logging fallback | Uses the matching non-MQTT target when no compatible MQTT sibling exists, preserving its hardware and role CLI. Full LoRa repeaters also include runtime ESP-NOW with `set espnow.enabled on\|off` and `get espnow.running`; newly combined modes default off. Dedicated images remain only for measured capacity exceptions. USB debug defaults off; `get/set usb.debug` selects verbose output independently of packet capture. |
|
||||
| nRF52 dual-CDC Full Companion | Fresh installs expose only interface `00`; it starts as an ASCII terminal and automatically hands a complete `<` frame to framed Companion. The same interface also carries exclusive serial mOTA traffic. `set usb.logging on reboot` adds output-only interface `02` for ASCII RAW/RX/TX records and optional debug. Alternatively, `set usb.logging stream reboot` keeps CLI replies and packet records on interface `00` for unmodified one-port serial bridges; Binary Companion and serial mOTA require logging off on that port. BLE and source-only LoRa OTA remain available. `get/set usb.logging` persistently controls whether the logging interface is present; `get/set usb.debug` changes verbosity without a reboot. Send CLI/control queries to interface `00`, not the log endpoint. |
|
||||
| ESP32 single-TTY Full Companion | Every ESP32 Full image starts with the ASCII terminal on its one USB TTY and automatically hands a complete `<` frame to framed Companion. On 1.17.1.5, run `set powersaving off` first. `set usb.logging on` switches that TTY to an input-capable plaintext RAW/RX/TX logging terminal and makes framed Companion unavailable on USB; `set usb.logging off` stops logging but leaves the TTY in normal ASCII mode. `get/set usb.debug` separately controls saved verbose diagnostics, defaulting off. The terminal stop token or a valid incoming framed probe then performs the ordinary switch to Binary Companion. A saved logging-on setting starts directly in that logging terminal and disables automatic frame detection. BLE, WiFi, and source-only LoRa OTA remain available. ESP32 Full uses Arduino-ESP32 2.x where supported; RC32 and ESP32-C6 keep their board-required Arduino 3.x platform but still expose only one TTY. |
|
||||
| `no_external_sensors` | Trims selected optional environmental/ranging drivers and their settings; it does not remove generic I2C, core repeater discovery, routing, or runtime RS-232 commands. RAK3401 and RAK4631 profiles retain the four common INA I2C voltage/current monitors. GPS-preserving RAK nRF52 OTA profiles retain their GPS commands and provider; RAK4631 defaults the bridge to UART 2 because RAK12501/L76K GPS uses UART 1. Legacy target suffixes remain stable for OTA identity compatibility. |
|
||||
|
||||
The four retained INA drivers are entries in the optional environmental-sensor
|
||||
@@ -129,12 +142,25 @@ both devices on one bus, leave RAK12500 at `0x42`, strap INA3221 A0 to SCL for
|
||||
`logging`, `OTA`, and `FULL` describe independent build features in historical
|
||||
filenames. Current standard artifacts use no `-logging-` infix because their
|
||||
USB logging is runtime controlled.
|
||||
USB packet capture and debug verbosity share the canonical artifact, not a new
|
||||
build/profile name. `usb.logging` remains the master; verbose diagnostics need
|
||||
both that switch and saved `usb.debug` on. Fresh and legacy preferences default
|
||||
debug to off. Setting debug on cannot restore code compiled out of an image.
|
||||
|
||||
## Canonical bulk-build policy
|
||||
|
||||
Bulk and release-matrix commands omit legacy names whose behavior is already
|
||||
available from a canonical image:
|
||||
|
||||
- Every canonical nRF52 repeater, room-server, and sensor recipe emits two
|
||||
separately named, qualified OTA artifacts: `full-ota` and `reduced-ota`.
|
||||
The capability sidecar records `sensor.profile.full` or
|
||||
`sensor.profile.reduced` so the picker can identify the actual profile.
|
||||
Boards without optional sensor drivers still receive the two named choices;
|
||||
the size saving may be small or zero. A missing or unqualified member blocks
|
||||
publication of an incomplete pair. This does not make an unsupported
|
||||
bootloader or a different physical storage layout compatible.
|
||||
|
||||
- Companion `_ps` names are replaced by the ordinary Companion image plus the
|
||||
persisted `set powersaving on|off` setting.
|
||||
- Companion `_femoff` names are replaced by the matching controllable-FEM
|
||||
@@ -172,15 +198,82 @@ available from a canonical image:
|
||||
releases. Wio-E5 remains separate because its normal image has only 916
|
||||
bytes free, while the combined image exceeds the fixed 240 KiB application
|
||||
partition by 2,192 bytes.
|
||||
`MKE_s3_repeater` includes the bridge on UART 2, with RX on GPIO16 and TX on
|
||||
GPIO17. Fresh installs leave it disabled at 115200 baud; upgrades from normal
|
||||
repeaters keep it disabled, while existing saved RS-232 settings are retained.
|
||||
Use `set bridge.baud 115200` and `set bridge.enabled on` to enable it, or
|
||||
`set bridge.enabled off` to stop it. `set bridge.uart 2` selects its fixed
|
||||
UART; other UART numbers are rejected. Successful changes take effect
|
||||
immediately and persist across reboots. The historical
|
||||
`MKE_s3_repeater_bridge_rs232` name remains directly buildable with its
|
||||
bridge-on default for compatibility but is omitted from bulk releases.
|
||||
The same MKE image includes ESP-NOW, controlled independently with
|
||||
`set espnow.enabled on|off` and `get espnow.running`. Both bridges can run
|
||||
together and share `bridge.source` and `bridge.delay`; `bridge.channel`,
|
||||
`bridge.secret`, and `bridge.format` apply only to ESP-NOW. Its first boot
|
||||
after an upgrade leaves ESP-NOW off, including upgrades from the dedicated
|
||||
ESP-NOW image: run `set espnow.enabled on` to enable it. Subsequent reboots
|
||||
retain the saved setting. The historical `MKE_s3_repeater_bridge_espnow`
|
||||
target remains directly buildable with its bridge-on default but is omitted
|
||||
from bulk releases. Browser OTA pauses ESP-NOW while leaving RS-232 running;
|
||||
`stop ota` restores ESP-NOW when its saved setting is on.
|
||||
- Full Heltec V3, Wireless Stick Lite V3 and RAK3112 MQTT repeaters retain
|
||||
RS-232 alongside MQTT and ESP-NOW. They use UART2 with RX on GPIO5 and
|
||||
TX on GPIO6. Use `set rs232.enabled on|off` and
|
||||
`get rs232.running` independently of `mqtt.enabled` and `espnow.enabled`.
|
||||
`bridge.baud` and `bridge.uart` configure the UART. Old MQTT preferences
|
||||
do not enable this newly added UART; its saved intent needs a validated
|
||||
UART profile marker. Historical `bridge.enabled` keeps its ESP-NOW meaning
|
||||
in MQTT images and its UART meaning in non-MQTT images. Full qualification
|
||||
verifies the linked UART driver even when compiling observer sources for a
|
||||
normal repeater, and resumed release selection rejects Full images without
|
||||
that proof.
|
||||
- LilyGo T-LoRa V2.1-1.6 retains a normal Full UART/ESP-NOW repeater and a
|
||||
separate Full MQTT/ESP-NOW observer. Real triple-transport qualification
|
||||
fell 6,496 bytes short of the required internal heap, including the UART
|
||||
driver allowance. The normal UART remains on RX GPIO34 and TX GPIO25;
|
||||
use `bridge.enabled` or `rs232.enabled` to control it. Both Full identities
|
||||
remain in bulk releases instead of reducing routing capacity or RAM margins.
|
||||
- MeshTower V2 repeaters use
|
||||
`Heltec_tower_v2_sdcard_repeater_lora_ota_no_external_sensors` as the one
|
||||
canonical target. Its SD OTA target `0A9DBBF0`, hardware ID
|
||||
`Heltec_tower_v2`, layout, and filenames stay unchanged, with separate
|
||||
required Full and Reduced sensor profiles. Both
|
||||
`Heltec_tower_v2_repeater` and
|
||||
`Heltec_tower_v2_repeater_lora_ota_no_external_sensors` resolve to this primary
|
||||
in `build-firmware` and matching builds. Old internal-only bootloader/OTA
|
||||
identities require one local USB/BLE DFU or SWD migration; the build aliases
|
||||
do not make the new image a same-target LoRa upgrade for those installed nodes.
|
||||
Existing SD users need no configuration change. The application selects SD
|
||||
first and falls back automatically for a missing or unmountable card only
|
||||
when the matching published combined OTAFIX profile declares exact
|
||||
privileged storage `09` and optional application storage `02` in its separate
|
||||
`MOTASTOR` record. Internal fallback accepts flash-staged signed application
|
||||
in-place deltas from trusted signers that fit its smaller workspace;
|
||||
SD-generated large deltas may be rejected. Full images, bootloader updates,
|
||||
and archive capture remain SD-only. Storage stays pinned through a transfer:
|
||||
use `ota cancel` before
|
||||
selecting a different backend. The application does not use an `APPHYBRID`
|
||||
retained-RAM staging arena. See [MeshTower SD OTA](ota_meshtower_v2_sdcard.md).
|
||||
|
||||
The old aliases still work with `build-firmware` and
|
||||
`build-matching-firmwares`. Dedicated LoRa OTA repeater images are not
|
||||
collapsed; they retain their exact storage, bootloader, role, and target
|
||||
identity contracts. Companion boards keep transport-specific canonical images
|
||||
only when no exact Full recipe has passed the combined flash/RAM qualification.
|
||||
collapsed outside the MeshTower consolidation above; they retain their exact
|
||||
storage, bootloader, role, and target identity contracts. Companion boards
|
||||
keep transport-specific canonical images only when no exact Full recipe has
|
||||
passed the combined flash/RAM qualification.
|
||||
ESP32 deliberately uses one TTY: Binary Companion and plaintext USB logging
|
||||
are mutually exclusive there. nRF52 retains its optional second CDC port.
|
||||
|
||||
The [OTAFIX 2.4.11 release](https://github.com/mikecarper/Adafruit_nRF52_Bootloader_OTAFIX/releases/tag/v0.11.0-OTAFIX2.4.11)
|
||||
is the current bootloader reference, not an all-board qualification claim.
|
||||
Consult its exact-board manifest and unavailable-profile reasons. Internal
|
||||
application storage accepts exact-base in-place deltas that fit the workspace;
|
||||
full application packages need the matching external NOR or microSD profile.
|
||||
The retired internal-only MeshTower V2 bootloader requires one local
|
||||
installation of the combined SD/internal profile. Its changed identity/layout
|
||||
must not be crossed by an ordinary LoRa application or bootloader update.
|
||||
|
||||
## Complete CLI policy
|
||||
|
||||
The compact ESP32 CLI has been removed. `retry.preset` is a checked invariant
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
<!-- meshcore-hosted-doc-link:start -->
|
||||
<p class="meshcore-hosted-doc-link"><a href="https://mikecarper.github.io/MeshCore/cli_command_availability/">View this page on MeshCore Docs</a>.</p>
|
||||
<!-- meshcore-hosted-doc-link:end -->
|
||||
|
||||
# CLI Command Availability Matrix
|
||||
|
||||
This page is the command-by-command companion to
|
||||
@@ -15,6 +19,15 @@ Infrastructure `get password` is available locally in every profile; LoRa
|
||||
requests cannot read it. `get acl` and stored packet `log` are infrastructure
|
||||
features, not Companion contact or live USB-logging commands.
|
||||
|
||||
Room users also have an owner-only command set described in
|
||||
[Personal mailboxes](room_services.md#personal-mailboxes): `mail settings`,
|
||||
`mail mode`, `mail allow`, `mail deny`, `mail delivery`, `mail check`,
|
||||
`mail inbox`/`mail list`, `mail read`, `mail ack`, and `mail delete`.
|
||||
Authenticated room writers can use `mail send`; readers cannot send.
|
||||
These work as encrypted CLI requests or ordinary direct `!mail ...` messages
|
||||
to the room. Regular users can also read `get room.board` and
|
||||
`get room.board.read`; board publishing and deletion remain administrative.
|
||||
|
||||
Build columns mean:
|
||||
|
||||
- **Standard** - the ordinary non-MQTT artifact, without an explicit `ota`
|
||||
@@ -38,6 +51,14 @@ Build columns mean:
|
||||
where there is no MQTT sibling. It has the complete parser and a saved
|
||||
USB-logging on/off gate. ESP-NOW bridges use this profile.
|
||||
|
||||
USB packet output and verbose diagnostics are separate saved controls in
|
||||
current source. Where `usb.logging` is available, `get/set usb.debug on|off`
|
||||
selects debug intent without changing packet capture or adding a build variant.
|
||||
Debug defaults off for fresh and older preference images; effective output
|
||||
requires `usb.logging` and `usb.debug` on plus compiled diagnostic support.
|
||||
See [live USB logging](cli_commands.md#control-live-usb-logging) for transport
|
||||
restrictions and Full Companion RAW output.
|
||||
|
||||
The firmware-configured INA3221 and RAK12500 addresses are both `0x42`; they
|
||||
cannot coexist on one bus at those addresses. Leave RAK12500 at `0x42`, strap
|
||||
INA3221 A0 to SCL for `0x43`, and use a build with
|
||||
@@ -62,6 +83,12 @@ fix, no WiFi connection, an inactive bridge, or an nRF52 bootloader without
|
||||
|
||||
## Companion framed CLI
|
||||
|
||||
GPS-equipped Sensor builds also provide the local-only
|
||||
[dog tracker settings](sensor_tracker.md): `get tracker`,
|
||||
`get/set tracker.owner`, `get/set tracker.mode`, `get/set tracker.interval`,
|
||||
`get/set tracker.lost.interval`, `get/set tracker.path`, and `tracker check`.
|
||||
They are excluded from compact STM32 defaults. LoRa CLI cannot change them.
|
||||
|
||||
Companion builds are not represented by the role/profile matrices below. Their
|
||||
local terminal and command `0x42` (`CMD_RUN_CLI_COMMAND`) expose these settings
|
||||
over the normal binary USB, BLE, or TCP connection:
|
||||
@@ -71,8 +98,10 @@ over the normal binary USB, BLE, or TCP connection:
|
||||
| `stats-core`, `stats-radio`, `stats-radio-diag`, `stats-packets` | Local terminal and binary command `0x42` |
|
||||
| `erase` | Local terminal and binary command `0x42`; reboot after erasing stored settings |
|
||||
| `set freq <MHz>` | Local terminal, binary command `0x42`, and authorized LoRa CLI; reboot to apply |
|
||||
| [`get public.key`](cli_commands.md#view-this-nodes-public-key) | Companion text terminal and binary command `0x42`; read-only, no private-key export feature required |
|
||||
| `get prv.key` | Local terminal and binary command `0x42`; requires private key export enabled |
|
||||
| `get password` | Reports that Companion has no admin password; infrastructure returns its password locally |
|
||||
| [`get/set lost.reply`](companion_lost_reply.md) | Local Companion CLI and framed command `0x42` when `MESH_ENABLE_LOST_REPLY` is enabled; saved `off`, `no` or `yes` private-message auto response |
|
||||
| `get wifi.pwd`, `get mqttN.password`, `get mqttN.token` | Local terminal and binary command `0x42`; corresponding WiFi/MQTT feature required |
|
||||
| [`board`](cli_commands.md#show-the-hardware-name) | Every Companion text terminal and command `0x42` |
|
||||
| [`version`](cli_commands.md#get-the-version) | Every Companion text terminal and protocol-v14 command `0x42`; returns the untruncated build identity |
|
||||
@@ -82,6 +111,7 @@ over the normal binary USB, BLE, or TCP connection:
|
||||
| [`get/set radio.fem.rxgain`](cli_commands.md#view-or-change-the-lora-fem-receive-path-gain-state-on-supported-boards) | Companion on a board with controllable LoRa FEM LNA |
|
||||
| [`get/set wifi.powersave`](cli_commands.md#browser-configuration-portal-esp32-repeater-and-room-server) | ESP32 WiFi Companion; active transport constraints still apply |
|
||||
| [`get/set bluetooth.name`](cli_commands.md#view-or-change-the-independent-bluetooth-name-companion) | Companion firmware |
|
||||
| [`set pin <0-999999>`](cli_commands.md#set-the-bluetooth-pairing-pin-companion) | Companion text terminal, command `0x42`, and authorized LoRa CLI; saved, reboot to apply; affects Bluetooth-capable builds |
|
||||
| [`get/set bluetooth.mac`](cli_commands.md#view-or-change-the-bluetooth-address-ble-companion) | Every Companion build with Bluetooth |
|
||||
| [`get/set display.touch`](cli_commands.md#show-touchscreen-touch-areas) | Touchscreen Companions using the shared UI; text terminal and framed CLI; off after reboot |
|
||||
| [`get/set display.inbox`](cli_commands.md#set-companion-inbox-behavior) | Shared Companion UI; `history` (default), `pending`, or `unread`; saved across reboot |
|
||||
@@ -124,6 +154,9 @@ should use command `0x42`. See [Companion radio binary protocol](companion_proto
|
||||
| Logging | [`log start`; `log stop`; `log erase`](cli_commands.md#logging) | Storage-backed roles retain data; other roles can return empty data | Yes | Yes | Yes |
|
||||
| Logging | [`log`](cli_commands.md#print-the-captured-log-to-the-serial-terminal) | Local connection | Local | Local | Local |
|
||||
| Logging | [`get/set usb.logging`; unified FULL `get/set logging.output`](cli_commands.md#control-live-usb-logging) | Ordinary safe-USB artifacts; CommonCLI USB gate is persistent; unified ESP32 FULL selects off/USB/WiFi/both; nRF52 Full Companion can add a reboot-controlled second CDC | Yes | Yes | No |
|
||||
| Logging | [`get/set usb.debug`](cli_commands.md#control-live-usb-logging) | Same USB-control availability; saved intent defaults off; actual diagnostics need compiled support and USB master on | Yes | Yes | No |
|
||||
| Logging | [`get/set usb.watchdog off\|on\|auto`](usb_logging_watchdog.md) | Current source; recovery requires native USB, live logging, and durable storage; Auto qualifies from USB stats polling | Yes | Yes | Feature |
|
||||
| Logging | [`get usb.watchdog.last`](usb_logging_watchdog.md#last-event-review) | Current source; latest USB watchdog event with advisory timestamp, uptime, action, reasons, and durability; native USB required | Yes | Yes | Feature |
|
||||
| Radio | [`get radio`; `set radio ...`](cli_commands.md#view-or-change-this-nodes-radio-parameters) | All text CLI roles | Yes | Yes | Yes |
|
||||
| Radio | [`get tx`; `set tx <dbm>`](cli_commands.md#view-or-change-this-nodes-transmit-power) | Board TX-power limits apply | Yes | Yes | Yes |
|
||||
| Radio | [`tempradio ...`; `normalradio`](cli_commands.md#change-the-radio-parameters-for-a-set-duration) | Full parser | Yes | Yes | Yes |
|
||||
@@ -153,6 +186,7 @@ should use command `0x42`. See [Companion radio binary protocol](companion_proto
|
||||
| System | [`get/set reboot.interval`](#reboot-interval) | Full parser | Yes | Yes | Yes |
|
||||
| Clock sync | [`get/set clock.sync.*`; `clock.sync.mesh now`](cli_commands.md#estimate-and-correct-infrastructure-node-time-after-startup) | Repeater, sensor, and room server; `clock.sync.internet` needs MQTT repeater | Yes | Yes | Yes |
|
||||
| Routing | [`get/set repeat`](cli_commands.md#view-or-change-this-nodes-repeat-flag) | Forwarding-capable role | Yes | Yes | Yes |
|
||||
| Routing | [`get/set repeat.trace`](cli_commands.md#allow-routed-traces-while-repeating-is-disabled-repeater-only) | Repeater only; saved repeat-off trace exception, default off | Yes | Yes | Yes |
|
||||
| Routing | [`get/set path.hash.mode`](cli_commands.md#view-or-change-this-nodes-advert-path-hash-size) | Role that supports path-hash selection | Yes | Yes | Yes |
|
||||
| Routing | [`get/set loop.detect`](cli_commands.md#view-or-change-this-nodes-loop-detection) | Repeater | Yes | Yes | Yes |
|
||||
| Routing | [`get/set txdelay`](cli_commands.md#view-or-change-the-retransmit-delay-factor-for-flood-traffic) | All text CLI roles, including bridges | Yes | Yes | Yes |
|
||||
@@ -175,11 +209,12 @@ should use command `0x42`. See [Companion radio binary protocol](companion_proto
|
||||
| Routing | [`get/set/del flood.channel.scope.require*`](cli_commands.md#require-valid-incoming-scopes-only-on-selected-channels) | Repeater | Yes | Yes | Yes |
|
||||
| Routing | [`get/set/del flood.rule*`; `get/set/del flood.filter*`; `get/set/del flood.filter.blacklist*`](cli_commands.md#change-persistent-flood-rules-in-the-field) | Repeater; `flood.rule`/`flood.filter` also on FULL ESP32 room server (no blacklist) | Yes | Yes | Yes |
|
||||
| Routing | [`get/set/del flood.moderation*`](cli_commands.md#moderate-flood-group-text-by-channel-sender-and-source-path) | Repeater | Yes | Yes | Yes |
|
||||
| Routing | [`get/set outpath`](halo_keymind_settings.md#direct-path-overrides) | Repeater remote-client context | Yes | Yes | Yes |
|
||||
| Routing | [`get/set altpath`](halo_keymind_settings.md#direct-path-overrides) | Repeater remote-client context | Yes | Yes | Yes |
|
||||
| Routing | [`get/set outpath`](halo_keymind_settings.md#direct-path-overrides) | Repeater or [room user](room_services.md#your-own-return-routes), own remote-client context | Yes | Yes | Yes |
|
||||
| Routing | [`get/set altpath`](halo_keymind_settings.md#direct-path-overrides) | Repeater or [room user](room_services.md#your-own-return-routes), own remote-client context | Yes | Yes | Yes |
|
||||
| ACL | [`setperm <pubkey> <permissions>`](cli_commands.md#add-update-or-remove-permissions-for-a-companion) | Repeater, room server, or sensor | Yes | Yes | Yes |
|
||||
| ACL | [`get acl [page]`](cli_commands.md#view-the-current-acl) | Repeater, room server, or sensor; admin over LoRa | Yes | Yes | Yes |
|
||||
| ACL | [`get/set allow.read.only`](cli_commands.md#view-or-change-this-room-servers-read-only-flag) | Room server | Yes | Yes | No |
|
||||
| Room | [`get/set topic`](cli_commands.md#view-or-change-the-room-topic) | Room server | Yes | Yes | No |
|
||||
| Regions | [`region load`](cli_commands.md#bulk-load-region-lists); [`region save`](cli_commands.md#save-any-changes-to-regions-made-since-reboot) | Role with region storage | Yes | Yes | Yes |
|
||||
| Regions | [`region allowf`](cli_commands.md#allow-a-region); [`region denyf`](cli_commands.md#block-a-region) | Role with region storage | Yes | Yes | Yes |
|
||||
| Regions | [`region get`](cli_commands.md#show-information-for-a-region); [`region list`](cli_commands.md#view-all-regions); [`region`](cli_commands.md#dump-all-defined-regions-and-flood-permissions) | Role with region storage | Yes | Yes | Yes |
|
||||
@@ -195,6 +230,7 @@ should use command `0x42`. See [Companion radio binary protocol](companion_proto
|
||||
| Retry | [`get/set/clear recent.repeater`; `get recent.repeaters`](cli_commands.md#view-seed-or-clear-the-recent-repeater-table) | Repeater | Yes | Yes | Yes |
|
||||
| GPS | [`get gps`; `set gps on/off`](cli_commands.md#view-or-change-gps-state) | Compiled GPS | Feature | Feature | Feature |
|
||||
| GPS | [`gps sync`](cli_commands.md#sync-this-nodes-clock-with-gps-time) | Compiled GPS | Feature | Feature | Feature |
|
||||
| GPS | [`get/set gps.sync.interval`](cli_commands.md#set-the-automatic-gps-clock-sync-interval) | Compiled GPS and provider; 1-336 hours | Feature | Feature | Feature |
|
||||
| GPS | [`gps setloc`](cli_commands.md#set-this-nodes-location-based-on-the-gps-coordinates) | Compiled GPS | Feature | Feature | Feature |
|
||||
| GPS | [`gps advert [none/share/prefs]`](cli_commands.md#view-or-change-the-gps-advert-policy) | Compiled GPS | Feature | Feature | Feature |
|
||||
| Sensors | [`get/set telemetry.access`](cli_commands.md#view-or-change-telemetry-access-mode) | Sensor-capable role | Feature | Feature | Limited |
|
||||
@@ -293,6 +329,9 @@ should use command `0x42`. See [Companion radio binary protocol](companion_proto
|
||||
| Logging | [`log start`; `log stop`; `log erase`](cli_commands.md#logging) | Storage-backed roles retain data | Yes | Yes | Yes | Yes | Yes |
|
||||
| Logging | [`log`](cli_commands.md#print-the-captured-log-to-the-serial-terminal) | Local connection | Local | Local | Local | Local | Local |
|
||||
| Logging | [`get/set usb.logging`; unified FULL `get/set logging.output`](cli_commands.md#control-live-usb-logging) | Ordinary safe-USB artifacts; CommonCLI USB gate is persistent; unified ESP32 FULL selects off/USB/WiFi/both; every ESP32 Full Companion uses an input-capable single-TTY logging terminal with framed USB Companion disabled while logging | Yes | Yes | No | No | Yes |
|
||||
| Logging | [`get/set usb.debug`](cli_commands.md#control-live-usb-logging) | Same USB-control availability; saved intent defaults off; actual diagnostics need compiled support and USB master on | Yes | Yes | No | No | Yes |
|
||||
| Logging | [`get/set usb.watchdog off\|on\|auto`](usb_logging_watchdog.md) | Current source; recovery requires native USB, live logging, and durable storage; Auto qualifies from USB stats polling | Yes | Yes | Feature | Yes | Yes |
|
||||
| Logging | [`get usb.watchdog.last`](usb_logging_watchdog.md#last-event-review) | Current source; latest USB watchdog event with advisory timestamp, uptime, action, reasons, and durability; native USB required | Yes | Yes | Feature | Yes | Yes |
|
||||
| Radio | [`get radio`; `set radio ...`](cli_commands.md#view-or-change-this-nodes-radio-parameters) | All text CLI roles | Yes | Yes | Yes | Yes | Yes |
|
||||
| Radio | [`get tx`; `set tx <dbm>`](cli_commands.md#view-or-change-this-nodes-transmit-power) | Board TX-power limits apply | Yes | Yes | Yes | Yes | Yes |
|
||||
| Radio | [`tempradio ...`; `normalradio`](cli_commands.md#change-the-radio-parameters-for-a-set-duration) | Full parser | Yes | Yes | Yes | Yes | Yes |
|
||||
@@ -323,6 +362,7 @@ should use command `0x42`. See [Companion radio binary protocol](companion_proto
|
||||
| System | [`get/set reboot.interval`](#reboot-interval) | Full parser | Yes | Yes | Yes | Yes | Yes |
|
||||
| Clock sync | [`get/set clock.sync.*`; `clock.sync.mesh now`](cli_commands.md#estimate-and-correct-infrastructure-node-time-after-startup) | Repeater, sensor, and room server; `clock.sync.internet` needs MQTT repeater | Yes | Yes | Yes | Yes | Yes |
|
||||
| Routing | [`get/set repeat`](cli_commands.md#view-or-change-this-nodes-repeat-flag) | Forwarding-capable role | Yes | Yes | Yes | Yes | Yes |
|
||||
| Routing | [`get/set repeat.trace`](cli_commands.md#allow-routed-traces-while-repeating-is-disabled-repeater-only) | Repeater only; saved repeat-off trace exception, default off | Yes | Yes | Yes | Yes | Yes |
|
||||
| Routing | [`get/set path.hash.mode`](cli_commands.md#view-or-change-this-nodes-advert-path-hash-size) | Full parser | Yes | Yes | Yes | Yes | Yes |
|
||||
| Routing | [`get/set loop.detect`](cli_commands.md#view-or-change-this-nodes-loop-detection) | Repeater, full common parser | Yes | Yes | Yes | Yes | Yes |
|
||||
| Routing | [`get/set txdelay`](cli_commands.md#view-or-change-the-retransmit-delay-factor-for-flood-traffic) | All text CLI roles, including bridges | Yes | Yes | Yes | Yes | Yes |
|
||||
@@ -345,11 +385,12 @@ should use command `0x42`. See [Companion radio binary protocol](companion_proto
|
||||
| Routing | [`get/set/del flood.channel.scope.require*`](cli_commands.md#require-valid-incoming-scopes-only-on-selected-channels) | Repeater role handler | Yes | Yes | Yes | Yes | Yes |
|
||||
| Routing | [`get/set/del flood.rule*`; `get/set/del flood.filter*`; `get/set/del flood.filter.blacklist*`](cli_commands.md#change-persistent-flood-rules-in-the-field) | Repeater role handler; `flood.rule`/`flood.filter` also on FULL ESP32 room server (no blacklist) | Yes | Yes | Yes | Yes | Yes |
|
||||
| Routing | [`get/set/del flood.moderation*`](cli_commands.md#moderate-flood-group-text-by-channel-sender-and-source-path) | Repeater role handler | Yes | Yes | Yes | Yes | Yes |
|
||||
| Routing | [`get/set outpath`](halo_keymind_settings.md#direct-path-overrides) | Repeater remote-client context | Yes | Yes | Yes | Yes | Yes |
|
||||
| Routing | [`get/set altpath`](halo_keymind_settings.md#direct-path-overrides) | Repeater remote-client context | Yes | Yes | Yes | Yes | Yes |
|
||||
| Routing | [`get/set outpath`](halo_keymind_settings.md#direct-path-overrides) | Repeater or [room user](room_services.md#your-own-return-routes), own remote-client context | Yes | Yes | Yes | Yes | Yes |
|
||||
| Routing | [`get/set altpath`](halo_keymind_settings.md#direct-path-overrides) | Repeater or [room user](room_services.md#your-own-return-routes), own remote-client context | Yes | Yes | Yes | Yes | Yes |
|
||||
| ACL | [`setperm <pubkey> <permissions>`](cli_commands.md#add-update-or-remove-permissions-for-a-companion) | Repeater, room server, or sensor | Yes | Yes | Yes | Yes | Yes |
|
||||
| ACL | [`get acl [page]`](cli_commands.md#view-the-current-acl) | Repeater, room server, or sensor; admin over LoRa | Yes | Yes | Yes | Yes | Yes |
|
||||
| ACL | [`get/set allow.read.only`](cli_commands.md#view-or-change-this-room-servers-read-only-flag) | Room server | Yes | Yes | No | Yes | Yes |
|
||||
| Room | [`get/set topic`](cli_commands.md#view-or-change-the-room-topic) | Room server | Yes | Yes | No | Yes | Yes |
|
||||
| Regions | [`region load`](cli_commands.md#bulk-load-region-lists); [`region save`](cli_commands.md#save-any-changes-to-regions-made-since-reboot) | Role with region storage | Yes | Yes | Yes | Yes | Yes |
|
||||
| Regions | [`region allowf`](cli_commands.md#allow-a-region); [`region denyf`](cli_commands.md#block-a-region) | Role with region storage | Yes | Yes | Yes | Yes | Yes |
|
||||
| Regions | [`region get`](cli_commands.md#show-information-for-a-region); [`region list`](cli_commands.md#view-all-regions); [`region`](cli_commands.md#dump-all-defined-regions-and-flood-permissions) | Role with region storage | Yes | Yes | Yes | Yes | Yes |
|
||||
@@ -365,6 +406,7 @@ should use command `0x42`. See [Companion radio binary protocol](companion_proto
|
||||
| Retry | [`get/set/clear recent.repeater`; `get recent.repeaters`](cli_commands.md#view-seed-or-clear-the-recent-repeater-table) | Repeater | Yes | Yes | Yes | Yes | Yes |
|
||||
| GPS | [`get gps`; `set gps on/off`](cli_commands.md#view-or-change-gps-state) | Compiled onboard GPS | Feature | Feature | Feature | Feature | Feature |
|
||||
| GPS | [`gps sync`](cli_commands.md#sync-this-nodes-clock-with-gps-time) | Compiled onboard GPS | Feature | Feature | Feature | Feature | Feature |
|
||||
| GPS | [`get/set gps.sync.interval`](cli_commands.md#set-the-automatic-gps-clock-sync-interval) | Compiled GPS and provider; 1-336 hours | Feature | Feature | Feature | Feature | Feature |
|
||||
| GPS | [`gps setloc`](cli_commands.md#set-this-nodes-location-based-on-the-gps-coordinates) | Compiled onboard GPS | Feature | Feature | Feature | Feature | Feature |
|
||||
| GPS | [`gps advert [none/share/prefs]`](cli_commands.md#view-or-change-the-gps-advert-policy) | Compiled onboard GPS | Feature | Feature | Feature | Feature | Feature |
|
||||
| Sensors | [`get/set telemetry.access`](cli_commands.md#view-or-change-telemetry-access-mode) | Sensor-capable full parser | Feature | Feature | Limited | Feature | Feature |
|
||||
|
||||
+593
-37
@@ -1,11 +1,40 @@
|
||||
<!-- meshcore-hosted-doc-link:start -->
|
||||
<p class="meshcore-hosted-doc-link"><a href="https://mikecarper.github.io/MeshCore/cli_commands/">View this page on MeshCore Docs</a>.</p>
|
||||
<!-- meshcore-hosted-doc-link:end -->
|
||||
|
||||
# CLI Commands
|
||||
|
||||
See [two LoRa profiles](radio_profiles.md) for `radio2`, `tempradio2`, scheduling,
|
||||
RX-only mode, cross-transmission policy, and optional radio preambles.
|
||||
|
||||
For supported Full Companions, see [Meshtext channel chat](radio_profiles.md#meshtext-channel-chat)
|
||||
for `mt-` channels and the explicit `send mt.nodeinfo <channel slot>` command.
|
||||
Chat remains plain text; NodeInfo is sent only when that command is requested.
|
||||
|
||||
See [carrier-wave RF checks](carrier_wave.md) for `cw on [seconds]`,
|
||||
`cw2 on [seconds]`, and stopping a carrier with `off`.
|
||||
|
||||
For keyed Morse TX, use `morse send TEST DE N0CALL` and `morse stop`.
|
||||
Defaults are **902.100 MHz and 25 WPM**; `set morse.freq <MHz>` and
|
||||
`set morse.wpm <1–60>` save separate frequency/speed settings.
|
||||
Small-flash STM32 production builds retain Morse TX but do not include Morse
|
||||
RX by default; listen/read/stats/clear return an unsupported-RX error there.
|
||||
`get morse` shows status. See [TX-only Morse messages](carrier_wave.md#tx-only-morse-messages)
|
||||
for supported characters, limits, overlap checks and the temporary pause in
|
||||
packet reception. Nothing transmits automatically.
|
||||
|
||||
For timed Morse RX, use `morse listen <minutes>` (fractions allowed, 1 second
|
||||
through 5 minutes). `morse stop` ends TX or RX. All roles retain the bounded
|
||||
capture in RAM for `morse read [offset]`; follow each page's `next` value until
|
||||
`next=END`. `morse clear` removes it when idle. A new successful session or
|
||||
reboot replaces/removes the capture. Buffered USB/browser Full Companion ASCII
|
||||
terminals show decoded progress; other terminals and binary clients request it
|
||||
through the CLI, without adding
|
||||
unidentified text to authenticated channel/chat messages. Normal LoRa is paused
|
||||
during listening, so a remote administrator must wait for the selected session
|
||||
to end before reading the capture over LoRa. See [timed reception](carrier_wave.md#timed-morse-reception-and-retained-captures)
|
||||
for arming time, real chip bandwidths and truncation behavior.
|
||||
|
||||
For copy/paste on/off recipes and the differences from Full Companion, see
|
||||
[feature switches by role](role_feature_switches.md). The
|
||||
[USB web console](https://flasher.meshcore.io/console) opens the default ASCII
|
||||
@@ -223,13 +252,18 @@ See the [OTA guide](ota_user_guide.md#adjust-lora-ota-speed).
|
||||
On nRF52, `start ota` invokes Bluetooth DFU with the matching bootloader and
|
||||
application DFU ZIP. The WiFi/AP instructions below apply to ESP32.
|
||||
|
||||
On ESP32, `start ota` serves the web upload page on the station IP when connected to WiFi;
|
||||
otherwise it raises the `MeshCore-OTA` access point. `start ota ap` always raises
|
||||
the access point, which is useful when the normal network uses client isolation.
|
||||
On ESP32, `start ota` enables WiFi as needed and serves the web upload page on
|
||||
the station IP when connected to WiFi; otherwise it raises the `MeshCore-OTA`
|
||||
access point. The reply gives the URL to open and tells you which network to
|
||||
use. `start ota ap` always selects the access point, including when OTA is
|
||||
already running on a LAN address. This is useful with client-isolated networks.
|
||||
|
||||
On an ESP32 build with WebConfig, the manual OTA uploader and WebConfig both use HTTP
|
||||
port 80 and cannot run together. Stop WebConfig before `start ota`, or stop OTA
|
||||
before `start webconfig`.
|
||||
Starting OTA automatically stops WebConfig and reports `WebConfig stopped` in
|
||||
the reply. The existing LAN connection stays up; a setup hotspot is replaced
|
||||
by `MeshCore-OTA`. WebConfig's delayed HTTP cleanup cannot stop OTA's WiFi.
|
||||
Use the USB/Bluetooth or LoRa command line to start the uploader; WebConfig's
|
||||
own terminal cannot deliver the handoff reply after shutting itself down.
|
||||
Stop OTA before starting WebConfig again.
|
||||
|
||||
FULL ESP32 builds also expose the `.mota` folder seeder on TCP port 5001
|
||||
whenever WiFi is usable. This listener is independent of port 80, so a host can
|
||||
@@ -279,6 +313,13 @@ them entirely. Sign in with the node's admin password. If the node has no saved
|
||||
WiFi SSID, the command starts the open `MeshCore-Setup-XXXX` captive AP at
|
||||
<http://192.168.4.1/> instead.
|
||||
|
||||
If a saved WiFi network is unavailable and the node falls back to its setup
|
||||
AP, sign in with the existing admin password before changing settings. Joining
|
||||
that AP does not grant permission to replace the password. First-time setup
|
||||
without a saved SSID keeps its AP provisioning flow. Images deliberately
|
||||
configured without an admin password, including passwordless Companions,
|
||||
retain their local no-password policy; use them only on a trusted network.
|
||||
|
||||
`start webconfig ap` forces captive-AP mode. It will not interrupt an active
|
||||
MQTT bridge, so run `set mqtt.enabled off` first. Use `stop webconfig` to
|
||||
close either mode for the current boot. (`stop webconfig` does not change a
|
||||
@@ -291,6 +332,15 @@ again without rebooting. Once an SSID is saved, the cutoff no longer applies
|
||||
and the selected WiFi/MQTT mode keeps reconnecting. Other setup sessions retain
|
||||
their profile's idle timeout.
|
||||
|
||||
Full Repeater and Room Server suspend unconfigured network bridges during
|
||||
automatic first-boot setup. Automatic ESP-NOW retries remain suspended after the AP
|
||||
closes, so they cannot keep an unused WiFi driver running. Saved bridge choices
|
||||
remain intact; explicitly enabling the bridge resumes it for this boot.
|
||||
Manual WebConfig/WiFi starts preserve an explicitly enabled ESP-NOW bridge,
|
||||
including when `set 2.4ghz on` restores the previously selected services.
|
||||
Stopping a browser OTA session restores only bridges that session actually
|
||||
paused, rather than starting services from saved intent alone.
|
||||
|
||||
Every ESP32 build with WebConfig supports the browser command terminal,
|
||||
including WiFi Companion and Full Companion. Companions use their complete
|
||||
USB/TCP terminal on the trusted LAN, including contact import, chat, recipient
|
||||
@@ -372,6 +422,35 @@ credentials alone do not turn it on. ESP32 simultaneous-transport builds apply
|
||||
the change from the main loop; exclusive-transport builds and Pico W apply it
|
||||
after reboot. This does not disable a primary ESP-NOW mesh radio or add Bluetooth
|
||||
to a WiFi-only build.
|
||||
|
||||
ESP32 WiFi Companions also accept `get wifi.tcp` over USB, BLE, or the browser
|
||||
CLI. This checks the app's TCP listener (normally port `5000`), not just the
|
||||
WiFi association. `listening` means the socket is open; `waiting-ip` means no
|
||||
usable station or setup-AP address yet; `retrying` means listener startup
|
||||
failed and will be retried automatically; `off` means no enabled listener
|
||||
request. `reg=1` confirms the interface is registered for Companion commands.
|
||||
`client=1` means a TCP client is connected. `acc`, `rx`, and `tx` count accepted
|
||||
clients, complete received frames, and complete transmitted frames since boot;
|
||||
the value after `/` is the last frame's hexadecimal opcode. `fail`, `err`, and
|
||||
`retry` show listener failures, the last listener-start socket error, and seconds until retry.
|
||||
`drop` gives the last client-disconnect reason. These counters contain no
|
||||
message contents or credentials. An open listener does not prove a router or
|
||||
guest-network firewall permits access from the phone.
|
||||
|
||||
Listener retries wait 1, 2, 4, 8, 16, then at most 30 seconds. They do not
|
||||
reconnect WiFi, interrupt BLE/WebConfig/OTA, replace a healthy app connection,
|
||||
or re-enable explicitly disabled WiFi. `get display.wifi` includes `tcp=1`
|
||||
only when the listener is actually open; `services=1` alone is not proof.
|
||||
|
||||
The ESP32 TCP transport services input even while radio logs or other outgoing
|
||||
frames are pending, so unsolicited traffic cannot starve the app handshake.
|
||||
Socket sends are non-blocking. A client that makes no outbound progress for
|
||||
10 seconds while data is pending is disconnected (`drop=tx-timeout`), without
|
||||
blocking the other transports. `drop=tx-error` identifies a fatal send error.
|
||||
Commands wait when all reply slots hold delivery-required data, rather than
|
||||
executing with nowhere to return their reply. A partly sent frame stays pinned
|
||||
until it finishes; input and output remain separate TCP streams.
|
||||
|
||||
See [local maintenance commands](terminal_chat_cli.md#local-maintenance-commands)
|
||||
for the complete access rules. WiFi-only Companions accept
|
||||
all three modes. A Full Companion that runs BLE and infrastructure WiFi
|
||||
@@ -428,6 +507,24 @@ The browser portal is not compiled into the two 4 MB
|
||||
retaining the two app slots required for LoRa OTA. Their normal CLI settings
|
||||
remain available.
|
||||
|
||||
### Switch the OTA release channel (observer builds)
|
||||
|
||||
- `ota branch` - shows the selected channel, the channel this build was made for, and the manifest base `ota check`/`ota update` will use.
|
||||
- `ota branch prod` (alias `stable`) / `ota branch beta` (alias `dev`) - pull future `ota update`s from that channel. The selection is saved; run `ota update` to switch.
|
||||
- `ota branch default` - follow the channel this build was made for.
|
||||
|
||||
The selector is saved only after the preferences transaction succeeds. A queued
|
||||
`ota update` keeps the channel it checked, even if `ota branch` is changed before
|
||||
the download starts. Switching channels offers the target image even when its
|
||||
commit matches the running image; build numbers are not comparable across channels.
|
||||
|
||||
Before an image is made bootable, its compatibility tag must confirm support for
|
||||
this fork's saved-settings format and required transports. Upstream observer
|
||||
images without the `keymind1` capability are refused, including images that use
|
||||
the different `/mqtt.json` format. A refusal leaves the current image running;
|
||||
select a compatible channel to continue OTA. Production and beta URLs are supplied
|
||||
by the build recipe, not accepted as arbitrary CLI URLs.
|
||||
|
||||
---
|
||||
|
||||
### Erase/Factory Reset
|
||||
@@ -462,7 +559,7 @@ command `0x42` where the role supports the command; unavailable over LoRa.
|
||||
### Remove a neighbor
|
||||
**Usage:**
|
||||
- `neighbor.remove <pubkey_prefix>`
|
||||
- `neighbor.remove` — remove all neighbors
|
||||
- `neighbor.remove` - remove all neighbors
|
||||
|
||||
**Parameters:**
|
||||
- `pubkey_prefix`: An even-length hexadecimal prefix (up to the full 64-character
|
||||
@@ -914,7 +1011,31 @@ All display-equipped Companion builds show the screen at boot in the default
|
||||
selected profile's timeout (15 seconds by default); button input restarts that
|
||||
timer. This also applies to previously saved button modes. Saved `off` and
|
||||
pairing-only modes still suppress the boot screen. Repeaters, room servers,
|
||||
and sensors do not gain this Companion-only boot wake.
|
||||
and sensors do not gain this Companion-only normal-UI boot wake.
|
||||
|
||||
On display-equipped Companions, repeaters, room servers, and sensors, a small
|
||||
`Starting...` screen appears as soon as the board, primary filesystem, and
|
||||
display are initialized, before radio initialization and serial-settle delays.
|
||||
If a new identity is needed, it changes to `Generating key` with an animated
|
||||
spinner during radio entropy collection. OLED/TFT updates are paced at 150 ms;
|
||||
e-paper uses a slower one-second cadence. Saved `off` and pairing-only modes
|
||||
still suppress this startup screen. The normal UI's boot timeout starts only
|
||||
when that UI is ready, so key generation does not consume it. Filesystem
|
||||
recovery and the display driver's own initialization can still delay the first
|
||||
visible frame; the firmware cannot draw before those operations finish.
|
||||
|
||||
Startup shows `Loading identity` while reading a saved identity. `Generating key`
|
||||
and its spinner are used only when creating a replacement/new identity;
|
||||
they end before flash persistence, which shows `Saving identity`. A normal
|
||||
boot with a valid saved identity never regenerates it. An erased filesystem
|
||||
does require a new identity unless one is restored.
|
||||
|
||||
On Full Companion screens, an unpaired Bluetooth PIN takes priority over a
|
||||
USB/TCP connected label. Button wake shows the PIN banner without requiring
|
||||
USB to be disconnected; a second navigation press can leave this preview.
|
||||
An actual phone pairing request wakes pairing-enabled display modes and keeps
|
||||
the banner visible until completion or its two-minute expiry. Bluetooth
|
||||
scanning alone is not a pairing request.
|
||||
|
||||
- `off`: stays dark, including boot and pairing.
|
||||
- `on`: stays on while the device is awake.
|
||||
@@ -1016,17 +1137,27 @@ flip state on display-enabled observer builds.
|
||||
|
||||
## Logging
|
||||
|
||||
Builds compiled with `MESH_PACKET_LOGGING` emit one `RAW:` line for every
|
||||
received radio frame. Serial output uses backpressure: if a connected host
|
||||
temporarily stops reading, packet processing waits for USB transmit space
|
||||
instead of silently omitting the record. A disconnected host cannot retain an
|
||||
unbounded capture, so logging deployments should keep the reader attached and
|
||||
draining the serial port.
|
||||
Non-compact builds compiled with `MESH_PACKET_LOGGING` emit a `RAW:` line
|
||||
for received radio frames that reach the raw capture hook while `usb.logging`
|
||||
is on, including Full Companion. Frames rejected by the early route policy
|
||||
are not exposed to raw capture.
|
||||
`RAW:` contains the complete received frame as uppercase hexadecimal, not only
|
||||
its decoded payload. Packet records do not require verbose USB debug output.
|
||||
Keep the host reader attached and draining the serial port: capture is best
|
||||
effort, not durable storage. Native-USB transports use bounded queues and drop
|
||||
records rather than block radio processing when the host stops reading;
|
||||
`DROP:<count>` markers report capture loss when output capacity returns.
|
||||
|
||||
Every valid received frame also emits the decoded RX summary, including signal,
|
||||
Successfully parsed received frames also emit the decoded RX summary, including signal,
|
||||
timing, hash, type, route, and payload information. Frames that cannot be
|
||||
decoded still emit their `RAW:` line. Transmitted packets emit the decoded TX
|
||||
summary.
|
||||
decoded, or cannot obtain a packet-pool slot, can still emit their `RAW:` line.
|
||||
Transmitted packets emit the decoded TX
|
||||
summary. RAW and RX summary are separate records, not an atomic pair; a queue
|
||||
drop or disconnect can leave a summary without its RAW bytes. USB log consumers
|
||||
must not associate a stale RAW record with the next unrelated summary.
|
||||
Size-constrained `MESH_PACKET_LOGGING_COMPACT` images instead use `R<hex>` and
|
||||
`T<hex>` records; that compact stream is not the full ASCII RAW/summary format
|
||||
expected by ordinary USB-to-MQTT parsers.
|
||||
|
||||
Ordinary non-OTA artifacts compile packet logging into the canonical image and
|
||||
control its live USB output at runtime; no separate `-logging-` artifact is
|
||||
@@ -1048,8 +1179,27 @@ set usb.logging on
|
||||
set usb.logging off
|
||||
set usb.logging on reboot
|
||||
set usb.logging off reboot
|
||||
get usb.debug
|
||||
set usb.debug on
|
||||
set usb.debug off
|
||||
```
|
||||
|
||||
Current source also provides `get usb.watchdog` and saved
|
||||
`set usb.watchdog off|on|auto`. Missing watchdog state defaults to Auto, which
|
||||
requires 14 continuous healthy logging-client days before saving On. Existing
|
||||
five-minute USB stats polls confirm recent logging-client activity; a plugged
|
||||
cable/open TTY alone does not. See the [USB logging watchdog](usb_logging_watchdog.md) for staged
|
||||
five-minute USB recovery, physical-fault-only MCU resets, persisted backoff,
|
||||
status fields, and safe shutdown deferrals. Older release binaries need an
|
||||
upgrade before these commands exist.
|
||||
|
||||
`get usb.watchdog.last` shows the latest USB watchdog event: recovery action,
|
||||
fault-reason bitmask, advisory node-clock epoch, uptime at the event, sequence,
|
||||
and whether the record passed durable readback. It survives reboot on durable
|
||||
storage. `reboot-requested` records intent, not proof that a physical reset
|
||||
occurred; a later safety veto records `reboot-cancelled`. See the watchdog
|
||||
documentation for storage and timestamp limitations.
|
||||
|
||||
**ESP32 1.17.1.5 USB logging procedure:** disable device sleep before enabling
|
||||
the log stream, using separate text commands:
|
||||
|
||||
@@ -1064,16 +1214,44 @@ saving. This works around the [released USB sleep bug](old-releases/1.17.1.5.md#
|
||||
nRF52 does not need this ESP32 workaround.
|
||||
|
||||
These commands are compiled into ordinary USB-loggable artifacts and every
|
||||
Full Companion. They control live USB debug and packet output. CommonCLI roles
|
||||
save the setting in `/com_prefs`, so it survives reboot; their first boot
|
||||
defaults to on. Full Companion and ordinary USB Companion start off on a fresh
|
||||
installation so diagnostics cannot corrupt framed traffic.
|
||||
Full Companion. `usb.logging` is the saved USB output master and packet-log
|
||||
gate. `usb.debug` is a separate saved preference for verbose diagnostics;
|
||||
effective USB debug output requires both switches to be on. Packet RAW and
|
||||
RX/TX summaries require only the master switch. `get usb.debug` reports saved
|
||||
intent even when the master is off. Turning `usb.logging` off does not erase
|
||||
that intent; turning it back on restores debug output if `usb.debug` is on.
|
||||
|
||||
CommonCLI roles save these settings in `/com_prefs`; their fresh USB logging
|
||||
default is on. Full Companion and ordinary USB Companion start with the
|
||||
master off on a fresh installation so diagnostics cannot corrupt framed
|
||||
traffic. Verbose USB debug defaults to off for every role, including when
|
||||
loading older preferences without a debug field. Updates retain an existing
|
||||
saved `usb.logging` choice. Older binaries without the new `usb.debug`
|
||||
command retain their previous behavior until upgraded.
|
||||
|
||||
For packet-only capture, use these separate text commands:
|
||||
|
||||
```text
|
||||
set usb.debug off
|
||||
set usb.logging on
|
||||
```
|
||||
|
||||
Use `set usb.debug on` temporarily when troubleshooting, then turn it off
|
||||
again to reduce USB traffic. This setting applies immediately and needs no
|
||||
USB-interface reboot. It does not select a firmware variant or enable code
|
||||
omitted from the image: verbose output still requires compiled `MESH_DEBUG`
|
||||
or other relevant diagnostic support. A packet-only build can retain the
|
||||
preference without gaining missing debug messages.
|
||||
|
||||
Debug off suppresses verbose diagnostic chatter, not functional CLI replies,
|
||||
USB port identity/drop markers, or retained operational warnings. Readers
|
||||
must still filter non-packet lines instead of treating every line as a packet.
|
||||
|
||||
On Full Companion these lines belong to its text terminal, not `meshcli`'s
|
||||
Binary `get/set` parameter namespace. Open interface `00`, send
|
||||
`+++MESHCORE-TERM-START`, and then issue the command. Running
|
||||
`meshcli ... get usb.logging` directly can instead return
|
||||
`Unknown var usb.logging` because that is a different protocol operation.
|
||||
`meshcli ... get usb.logging` or `get usb.debug` directly can instead return
|
||||
`Unknown var ...` because that is a different protocol operation.
|
||||
|
||||
nRF52 Full Companion defaults logging to off and enumerates only USB interface
|
||||
`00`, which carries Companion, terminal, and serial mOTA traffic. Enabling
|
||||
@@ -1083,6 +1261,8 @@ the optional `reboot` argument saves the choice and reports that a reboot is
|
||||
required when the USB interface count must change. The exact
|
||||
`set usb.logging on reboot` and `set usb.logging off reboot` forms save the
|
||||
choice, send their reply, and reboot one second later only when needed.
|
||||
Interface `02` is logging-only, not an input-capable CLI. Send logging/debug
|
||||
commands to interface `00`; USB debug uses the same log endpoint as packets.
|
||||
|
||||
On every ESP32 Full Companion, enter the USB text terminal and use
|
||||
`set usb.logging on` (preceded by `set powersaving off` on 1.17.1.5) to turn that TTY into a logging-repeater-style plaintext
|
||||
@@ -1099,6 +1279,20 @@ frames active on interface `00`; ESP32 resumes the ordinary ASCII/Binary
|
||||
switcher after the logging terminal turns logging off. This setting does not
|
||||
change the node-storage capture controlled by `log start` and `log stop`.
|
||||
|
||||
For a USB-to-MQTT bridge, enable packet logging but leave `usb.debug` off.
|
||||
Full Companion now supplies the ASCII RAW/RX/TX packet stream needed by such
|
||||
consumers; it does not provide durable or lossless capture. A compatible host
|
||||
must also support the role's identity/control commands and replies: packet
|
||||
records alone do not make an existing bridge compatible. Companion now answers
|
||||
`get public.key` with its node public key. ESP32 Full's logging TTY remains
|
||||
input-capable and accepts that query. nRF52 Full's dedicated logging CDC is
|
||||
not a command/reply console (its bounded stats-poll input only renews the
|
||||
watchdog client lease):
|
||||
a host integration must use interface `00` for commands and identity and
|
||||
interface `02` for logs.
|
||||
Existing single-port bridges that require CLI replies cannot simply point at
|
||||
the dedicated log endpoint. This change does not add a two-port bridge driver.
|
||||
|
||||
Companion, Repeater, Room Server, and Sensor builds with both MQTT and USB
|
||||
logging provide the same saved selector for both output paths:
|
||||
|
||||
@@ -1119,6 +1313,8 @@ in `both` mode; Full Companion starts with USB logging off. To change only
|
||||
MQTT, use `set mqtt.enabled on|off`; `get mqtt.enabled` checks the saved
|
||||
switch and `get mqtt.running` checks whether the MQTT service is running.
|
||||
Turning MQTT off preserves all broker slots and credentials.
|
||||
The output selector changes the USB master, not saved `usb.debug` intent;
|
||||
WiFi MQTT logging is independent of the USB debug preference.
|
||||
|
||||
On **1.17.1.5 ESP32**, run `set powersaving off` before selecting
|
||||
`set logging.output usb` or `set logging.output both`, since those modes
|
||||
@@ -1606,7 +1802,17 @@ set bluetooth on
|
||||
|
||||
`get ble` and `set ble ...` are aliases. This controls the running Bluetooth
|
||||
service on ESP32 and nRF52 Companions: `off` stops advertising and disconnects
|
||||
Bluetooth clients; `on` enables it again. The change lasts until reboot.
|
||||
Bluetooth clients; `on` enables it again. Both choices are saved and survive
|
||||
reboots. USB remains available when Bluetooth is off. Older saved preferences
|
||||
without this setting retain the previous default of Bluetooth on.
|
||||
|
||||
Successful local commands report `(saved)`. If saving fails, the command reports
|
||||
an error and leaves Bluetooth unchanged. When requested over Bluetooth, `off`
|
||||
first reports `(save pending)` so its reply can drain; the firmware rechecks the
|
||||
alternate connection and saves the preference before disconnecting. If that
|
||||
connection disappears or the save fails, shutdown is cancelled. `get bluetooth`
|
||||
reports the current runtime state. Broad `2.4ghz` controls still apply to the
|
||||
current boot only and do not change this saved Bluetooth preference.
|
||||
|
||||
Plain `off` requires another active management connection. A connected USB
|
||||
terminal or binary client, TCP client, or browser terminal can provide that
|
||||
@@ -1616,7 +1822,8 @@ bridge), issue the command through that USB client so the request itself proves
|
||||
it is active.
|
||||
|
||||
Append `force` to `off` to permit disconnecting the only active connection.
|
||||
Use USB, another management transport, or reboot to regain access afterward.
|
||||
Use USB or another management transport and `set bluetooth on` to regain
|
||||
access afterward. Reboot alone will not re-enable saved-off Bluetooth.
|
||||
`force` is accepted only as the final argument to `off`.
|
||||
|
||||
Commands sent through Bluetooth allow its reply to drain before shutdown
|
||||
@@ -1647,6 +1854,37 @@ the frame and reply format.
|
||||
|
||||
---
|
||||
|
||||
#### Set the Bluetooth pairing PIN (Companion)
|
||||
|
||||
**Usage:**
|
||||
|
||||
- `set pin <0-999999>`
|
||||
|
||||
Choose a six-digit code for a fixed Bluetooth pairing PIN. For example:
|
||||
|
||||
```text
|
||||
set pin 654321
|
||||
reboot
|
||||
```
|
||||
|
||||
The command saves the PIN across reboots and replies `> pin is now 654321`.
|
||||
Reboot to activate the new code; the current Bluetooth session keeps its
|
||||
existing PIN until then. A failed save reports an error and retains the
|
||||
previous saved setting.
|
||||
|
||||
`set pin 0` clears the saved override, rather than setting the pairing code to
|
||||
`000000` or disabling Bluetooth. After reboot, the build's default policy
|
||||
applies: normally `123456` without a display, or a generated PIN on eligible
|
||||
display-equipped builds.
|
||||
|
||||
This command is available in the Companion text terminal, binary command
|
||||
`0x42` (`CMD_RUN_CLI_COMMAND`), and authorized LoRa CLI; a Bluetooth-capable build
|
||||
is needed to use the PIN. Existing bonded clients may reconnect without being
|
||||
asked for a PIN.
|
||||
Forget the device on the phone and pair again if you need to enter the new code.
|
||||
|
||||
---
|
||||
|
||||
#### View or change the Bluetooth address (BLE Companion)
|
||||
|
||||
**Usage:**
|
||||
@@ -1860,7 +2098,7 @@ through binary command `0x42`; this command does not report the Bluetooth PIN.
|
||||
**Note:** On nRF52 boards with power management and a boot-voltage setting,
|
||||
this is a saved *relative* calibration: `1.000` keeps the board conversion,
|
||||
`1.050` raises all reported battery millivolts by 5%, and `0` resets to
|
||||
`1.000`. The allowed range is `0.500`–`1.500`. This affects boot protection,
|
||||
`1.000`. The allowed range is `0.500`-`1.500`. This affects boot protection,
|
||||
running cutoff, telemetry, battery alerts, and percentage. Calibrate against a
|
||||
voltmeter before enabling a low cutoff. Other boards retain their existing
|
||||
board-specific multiplier behavior, or return unsupported.
|
||||
@@ -2132,6 +2370,13 @@ get clock.sync.status
|
||||
#### View this node's public key
|
||||
**Usage:** `get public.key`
|
||||
|
||||
Returns this node's identity public key without changing it or exposing its
|
||||
private key. Companion supports the query through its text terminal and
|
||||
framed CLI command `0x42` (`CMD_RUN_CLI_COMMAND`); its reply is `> ` followed
|
||||
by exactly 64 uppercase hexadecimal characters. Private-key export support
|
||||
is not required. On nRF52 Full Companion, send the query to control interface
|
||||
`00`, not the output-only logging interface `02`.
|
||||
|
||||
---
|
||||
|
||||
#### View this node's firmware version
|
||||
@@ -2164,6 +2409,23 @@ are shared by Companion and infrastructure. The saved preference controls
|
||||
whether power saving is allowed; active USB, logging and network services may
|
||||
keep the hardware awake. Actual sleep depends on the board.
|
||||
|
||||
On ESP32 infrastructure boards using an external USB-to-UART chip, such as the
|
||||
Heltec V3's CP2102, the native USB host guard cannot detect an attached computer.
|
||||
Current source blocks MCU light sleep while the raw UART console driver is
|
||||
enabled, preserving the first web-console command after idle even with all
|
||||
bridges and USB logging stopped. This guard has no idle timeout and also covers
|
||||
a computer's first connection after boot. These builds enable that console at
|
||||
startup, so MCU light sleep is unavailable while it remains enabled, including
|
||||
on battery. `set powersaving on` still enables sensor/GPS power-saving policies;
|
||||
the CPU continues normal idle yielding while keeping the console available.
|
||||
A running MQTT, RS232 or ESP-NOW bridge keeps infrastructure awake even when
|
||||
`get powersaving` reports `on`; that readback alone does not prove MCU sleep.
|
||||
Negotiated RS232 standby can stop an unresponsive UART owner without changing
|
||||
its saved enabled flag; see `rs232.autosleep` below. Other sleep blockers still apply.
|
||||
Adding UART wake alone would still discard the characters that trigger wake,
|
||||
so it cannot preserve an unmodified web console's first command. See the
|
||||
[ESP-IDF UART wake documentation](https://docs.espressif.com/projects/esp-idf/en/v4.4.7/esp32s3/api-reference/system/sleep_modes.html#uart-wakeup-light-sleep-only).
|
||||
|
||||
For the **1.17.1.5 G3 USB-disconnect report**, use `set powersaving off` as the
|
||||
workaround. The released ESP32 sleep code can lose native USB after two
|
||||
minutes when the terminal is closed, even with a computer attached. The
|
||||
@@ -2176,6 +2438,12 @@ binaries.
|
||||
|
||||
Companion firmware defaults this setting to `on`. Full Companion accepts the command from its local USB terminal and exposes the same setting in WebConfig. On ESP32, it lowers the CPU clock to 80 MHz, enables idle yielding, and enables the configured GPS duty cycle. USB and each active wireless transport remain available; SenseCAP Indicator Full keeps only its selected BLE or infrastructure-WiFi secondary transport active. `set powersaving off` restores the board's normal CPU clock and disables the GPS duty cycle. This device setting is separate from LoRa RXPS (`radio.rxps`) and WiFi modem power save (`wifi.powersave`). Infrastructure uses the same commands; its hardware and active-service sleep guards determine when the node can sleep.
|
||||
|
||||
Native USB ESP32 Full Companions can also use short light-sleep intervals when
|
||||
the BLE/WiFi drivers, ESP-NOW, GPS UART, logging, and USB host sessions are
|
||||
inactive and no work is pending. Compiling those transports into Full does not
|
||||
itself prevent sleep. Turning off BLE advertising alone leaves its controller
|
||||
running and therefore retains idle behavior rather than manual light sleep.
|
||||
|
||||
---
|
||||
|
||||
#### View or set the reboot interval (Repeater and room server)
|
||||
@@ -2240,6 +2508,9 @@ The pin number is the Arduino pin number used by that target (the normal GPIO nu
|
||||
|
||||
**Notes:**
|
||||
- These commands require remote client context and update the caller's ACL entry.
|
||||
- Room Server readers and writers may manage their own `outpath` and `altpath`
|
||||
without administrator permission. See [room return routes](room_services.md#your-own-return-routes)
|
||||
for the command list, session persistence and alternate delivery behavior.
|
||||
- `get outpath path` reports the reciprocal `PAYLOAD_TYPE_PATH` received after
|
||||
the caller's latest flood login without changing the selected output route.
|
||||
Because that packet is asynchronous, an immediate query can report
|
||||
@@ -2265,7 +2536,48 @@ The pin number is the Arduino pin number used by that target (the normal GPIO nu
|
||||
**Parameters:**
|
||||
- `state`: `on`|`off`
|
||||
|
||||
**Default:** `flood.channel.data on`; `flood.channel.data.hops h=all`
|
||||
**Default:** `repeat on`.
|
||||
|
||||
`repeat off` disables relay forwarding, not locally generated traffic or
|
||||
configuration replies. Repeaters can separately opt in to the routed-trace
|
||||
exception below.
|
||||
|
||||
#### Allow routed traces while repeating is disabled (repeater only)
|
||||
|
||||
```text
|
||||
get repeat.trace
|
||||
set repeat.trace on
|
||||
set repeat.trace off
|
||||
```
|
||||
|
||||
Default: `off`, including upgrades from older preference files. The setting is
|
||||
saved and takes effect without reboot. Failed saves leave the active choice
|
||||
unchanged. Room Server, Sensor, and Companion do not gain this exception.
|
||||
|
||||
To keep ordinary forwarding disabled while allowing routed trace diagnostics:
|
||||
|
||||
```text
|
||||
set repeat off
|
||||
set repeat.trace on
|
||||
```
|
||||
|
||||
Only direct TRACE packets with a matching next hop may be relayed. Ordinary
|
||||
messages, adverts, group data, ACKs, and control packets remain subject to
|
||||
`repeat off`; flood traces remain invalid. Existing trace length, path-capacity,
|
||||
duplicate, radio-profile, and channel/duty-cycle checks still apply. This is
|
||||
a diagnostics opt-in, not authenticated-admin-only forwarding: trace packets
|
||||
retain the protocol's existing authentication behavior.
|
||||
|
||||
`get repeat` and the disabled status bit continue to report repeating off.
|
||||
With `repeat on`, traces use the ordinary forwarding policy regardless of
|
||||
`repeat.trace`. Changing the exception to off while repeating is off blocks
|
||||
subsequent relayed trace transmissions, including queued copies and retries;
|
||||
a trace already on air is allowed to finish. Locally initiated traffic and
|
||||
management replies are not cancelled.
|
||||
|
||||
The dynamic configuration field is `repeat.trace_off` (`0` or `1`). It is
|
||||
appended to `/com_prefs`; all field offsets in the published upstream layout
|
||||
stay unchanged.
|
||||
|
||||
---
|
||||
|
||||
@@ -2910,9 +3222,9 @@ worked moderation examples, see [Flood Filtering and Moderation](flood_filtering
|
||||
- `get flood.rule`
|
||||
- `get flood.rule.<n>`
|
||||
- `get fr[.<n>]` (numbered form returns a complete short setter for copying)
|
||||
- `set fr[.<n>] <type> [h=<range>] [m=r|b|c|bc] [c=<channel>] <d|s=<scope>|r=<region>|q=<N>|r|s> [...]`
|
||||
- `set fr[.<n>] <type> [h=<range>] [m=r|b|c|bc] [c=<channel>] [pb=any|1|2|2+|3] <d|s=<scope>|r=<region>|q=<N>|r|s> [...]`
|
||||
- `del fr.<n>` or `del fr all`
|
||||
- `set flood.rule[.<n>] type=<type> [mode=radio|bridge|cross|bridge,cross] [hops=<range>] [channel=<channel>] [prefix=<path-prefix>] [in=<input-scope>] <drop|scope=<name>|region=<name>|rate=<N>/min|retry|stop> [priority=<0-255>] [tx=slow] [suspend=tempradio]`
|
||||
- `set flood.rule[.<n>] type=<type> [mode=radio|bridge|cross|bridge,cross] [hops=<range>] [channel=<channel>] [hashbytes=any|1|2|2+|3] [prefix=<path-prefix>] [in=<input-scope>] <drop|scope=<name>|region=<name>|rate=<N>/min|retry|stop> [priority=<0-255>] [tx=slow] [suspend=tempradio]`
|
||||
- `del flood.rule.<n>`
|
||||
- `del flood.rule all`
|
||||
- `get flood.filter`
|
||||
@@ -2968,6 +3280,14 @@ compile this table.
|
||||
MAC/decrypt check with the Public channel key. That distinguishes an ordinary
|
||||
`0x11` collision, but it is channel authentication rather than sender
|
||||
authentication: the group MAC is two bytes and the Public key is shared.
|
||||
- `hashbytes=any|1|2|2+|3`: Optional encoded path-hash width, independent of hop
|
||||
count and hash value. Omitted, `any`, or `*` matches every supported width.
|
||||
Width-specific rules also match zero-hop packets. Four-byte hashes are
|
||||
reserved and unsupported. `2+` selects both 2- and 3-byte paths in one row,
|
||||
sharing any rate limit across both. A simultaneous `prefix=` must satisfy the
|
||||
selector; a 2- or 3-byte prefix narrows `2+` to its own exact width.
|
||||
Example: `set fr any pb=1 d` drops all 1-byte floods from forwarding, including
|
||||
relayed login/admin traffic, but not direct packets or local delivery.
|
||||
- `prefix=<ID[,ID...]>`: Optional ordered source-path prefix of one to three
|
||||
pbyte IDs. IDs must all be 2, 4, or 6 hex characters, matching a packet's
|
||||
1-, 2-, or 3-byte pbyte width. `path=<prefix>` is an alias.
|
||||
@@ -3217,6 +3537,7 @@ have the same behavior as their long forms.
|
||||
| `hops=...` | Positional range or `h=...`; `*` means `all` |
|
||||
| `mode=radio/bridge/cross/bridge,cross` | `m=r/b/c/bc` (choose one value) |
|
||||
| `channel=...`, `prefix=...` | `c=...`, `p=...` |
|
||||
| `hashbytes=any/1/2/2+/3` | `pb=*/1/2/2+/3` (choose one value) |
|
||||
| `path=blacklist` | `p=bl` (repeater only) |
|
||||
| `in=any/none/scoped/allowed/unknown` | `i=*/n/s/a/u` (choose one value) |
|
||||
| `in=scope:name`, `in=region:name` | `i=s:name`, `i=r:name` |
|
||||
@@ -3508,16 +3829,65 @@ setperm <PUBKEY_USER_1> 2
|
||||
```
|
||||
|
||||
With read-only mode on, a client without a valid room password can join as a
|
||||
read-only guest. Permission `2` gives the specified public key read-write
|
||||
read-only guest, including with a blank password. Empty configured admin and
|
||||
room passwords disable those password credentials; they never grant Admin or
|
||||
Read-Write access. Permission `2` gives the specified public key read-write
|
||||
access. Replace `<PUBKEY_USER_1>` with the user's full public key and repeat
|
||||
`setperm` for every approved writer.
|
||||
|
||||
An existing ACL identity keeps its assigned role when logging in with a blank,
|
||||
stale, or room password. A valid nonempty admin password can explicitly upgrade
|
||||
it to Admin. This prevents a guest-password login from demoting an admin, and
|
||||
prevents a room password from overriding an explicit read-only assignment.
|
||||
Only Read-Write (`2`) and Admin (`3`) clients can post.
|
||||
|
||||
This does not remove write access from clients that already know the room
|
||||
password. Keep that password private, or replace it, if write access should be
|
||||
limited to the explicitly permitted users.
|
||||
|
||||
---
|
||||
|
||||
#### View or change the room topic
|
||||
|
||||
**Availability:** Room Server firmware. Local consoles and authenticated remote
|
||||
admins can use these commands.
|
||||
|
||||
```text
|
||||
set topic Welcome to the Bellevue room. Please keep posts local.
|
||||
get topic
|
||||
set topic
|
||||
```
|
||||
|
||||
`set topic <text>` saves an announcement of up to 151 UTF-8 bytes. Individual
|
||||
command transports can impose a smaller input limit. `get topic` returns the
|
||||
current text prefixed with `> `. `set topic` with no text clears it.
|
||||
|
||||
The topic survives reboot and appears as a normal room post authored by the
|
||||
server, using the existing app message format. It does not require an app
|
||||
update or provide a separate pinned-header display. Older unread retained posts
|
||||
are delivered before the topic, so the companion's automatic catch-up cursor
|
||||
cannot jump past them. A topic ACK updates only topic delivery state, leaving
|
||||
the server's message-history cursor unchanged.
|
||||
|
||||
The server waits six seconds before delivering a new topic and attempts an
|
||||
unacknowledged announcement up to three times per client. It then continues
|
||||
ordinary post delivery; reconnecting or changing the topic renews that budget.
|
||||
Changing a topic while its predecessor is awaiting an ACK does not mark the
|
||||
replacement as received. Clearing the topic stops further announcement pushes;
|
||||
a packet already queued for transmission can still arrive.
|
||||
|
||||
Topic saves are verified before publication. A failed save reports an error
|
||||
and retains the previous live topic. Corrupt, unreadable, or unsupported stored
|
||||
topic files are preserved and block replacement until their storage issue is
|
||||
repaired. Ordinary room history uses the bounded RAM ring by default. Enable
|
||||
optional retention of the newest 32 posts with `set room.history on`.
|
||||
|
||||
See [Room history, moderation, and information board](room_services.md) for
|
||||
persistent history, saved ACL roles, full-key bans, per-user post/request
|
||||
limits, the ESP32 `/room` browser page, and versioned article downloads.
|
||||
|
||||
---
|
||||
|
||||
### Region Management (v1.10.+)
|
||||
|
||||
#### Bulk-load region lists
|
||||
@@ -4374,12 +4744,51 @@ clear recent.repeater
|
||||
|
||||
**Note:** These command names are shared with GPS-capable Companion builds.
|
||||
Companion reports `on` or `off`; infrastructure includes receiver details:
|
||||
- `off` when the GPS hardware is disabled
|
||||
- `on, {active|deactivated}, {fix|no fix}, {sat count} sats` when the GPS hardware is enabled
|
||||
- `off, {powered|unpowered}` when GPS is manually disabled
|
||||
- `on, {powered|unpowered}, {fix|no fix}, {sat count} sats` when GPS is enabled
|
||||
|
||||
`powered` describes the supply, not proof that the receiver is tracking or
|
||||
asleep. Shared-power hardware can remain powered while GPS processing is off.
|
||||
|
||||
On repeaters, `gps off` cancels any acquisition already in progress. A later
|
||||
authorized location-telemetry request may wake the receiver, but only until its
|
||||
first valid fix is cached; the receiver then powers down again.
|
||||
first valid fix is cached; GPS processing then stops again. Whether its supply
|
||||
can also turn off depends on the board's power policy below.
|
||||
|
||||
---
|
||||
|
||||
#### RAK4631 shared peripheral power
|
||||
|
||||
**Usage:**
|
||||
|
||||
```text
|
||||
get peripherals.mode
|
||||
set peripherals.mode gps-only
|
||||
set peripherals.mode shared
|
||||
reboot
|
||||
```
|
||||
|
||||
**Default:** `gps-only` on RAK4631. The saved mode takes effect only after
|
||||
reboot; setting it does not switch power immediately. `get peripherals.mode`
|
||||
reports the saved choice, which may differ from the current boot's mode.
|
||||
|
||||
- `gps-only`: when no protected power owner exists, GPS-off and GPS sleep can
|
||||
turn off the shared WisBlock `3V3_S` supply. Select this only when other
|
||||
attached peripherals do not need that supply.
|
||||
- `shared`: keeps `3V3_S` powered for other peripherals. Save this mode and
|
||||
reboot when using other WisBlock modules; firmware does not infer this choice
|
||||
from an I2C scan.
|
||||
|
||||
Ethernet/PoE builds and detected external NOR storage automatically keep the
|
||||
rail on, even in `gps-only` mode or when the network service is disabled.
|
||||
RAK3401 permanently protects the same rail because its radio frontend needs
|
||||
it; `peripherals.mode` is a RAK4631 setting, not an override of that protection.
|
||||
|
||||
On a powered shared rail, RAK12501/L76K cannot be independently switched off:
|
||||
GPS-off stops MCU parsing, not physical GNSS operation. Other receivers may use
|
||||
internal power-saving commands, but `shared` does not promise physical sleep.
|
||||
UART1 bridge ownership remains protected; changing this mode does not grant
|
||||
permission to connect two transmitters to the GPS UART.
|
||||
|
||||
---
|
||||
|
||||
@@ -4395,6 +4804,43 @@ On repeaters with device power saving enabled, the automatic GPS cycle wakes
|
||||
for up to 10 minutes and repeats seven days after GPS sleeps. A valid GPS time
|
||||
can correct the RTC either forward or backward. With device power saving off,
|
||||
an enabled GPS refreshes the RTC every 30 minutes.
|
||||
These are the legacy defaults; `gps.sync.interval` below overrides them.
|
||||
|
||||
---
|
||||
|
||||
#### Set the automatic GPS clock-sync interval
|
||||
|
||||
```text
|
||||
get gps.sync.interval
|
||||
set gps.sync.interval 1
|
||||
set gps.sync.interval 24
|
||||
set gps.sync.interval 336
|
||||
```
|
||||
|
||||
The interval is a whole number of **hours**, from **1 to 336** (two weeks).
|
||||
Larger numbers are capped, not rejected. For example:
|
||||
|
||||
```text
|
||||
set gps.sync.interval 999
|
||||
OK - GPS sync interval 336 hours (saved)
|
||||
```
|
||||
|
||||
Zero, negative numbers, fractional hours, and nonnumeric values are rejected.
|
||||
The setting is saved across reboots and supported by GPS-equipped repeater,
|
||||
room-server, sensor, and Companion builds. Until it is explicitly configured,
|
||||
the getter reports `default (board GPS sync policy)` and existing board timings
|
||||
are retained.
|
||||
|
||||
The selected interval controls periodic clock acquisitions both with GPS power
|
||||
saving and with a continuously powered receiver. Changing it reschedules a
|
||||
sleeping receiver without immediately powering it on. A GPS fix can take time;
|
||||
an unsuccessful acquisition retains the existing bounded awake window, then
|
||||
backs off before trying again. Device GPS/power switches still apply.
|
||||
|
||||
This does not change position-update `gps_interval`, location telemetry/cache
|
||||
timings, or mesh/NTP time synchronization. Location requests can still power GPS
|
||||
for a fix, but do not bypass the configured clock-sync interval. Explicit
|
||||
`gps sync` remains an immediate acquisition request where that command exists.
|
||||
|
||||
---
|
||||
|
||||
@@ -4487,6 +4933,81 @@ bridge actually started in this boot; they can differ after a hardware conflict,
|
||||
missing credentials, or a transient initialization failure. Normal merged
|
||||
repeater images default to `off`; dedicated bridge images may default to `on`.
|
||||
|
||||
Full MQTT repeaters with RS-232 expose three independent transports. Use
|
||||
`set rs232.enabled on|off` and `get rs232.enabled` for saved UART intent;
|
||||
`get rs232.running` reports the actual UART state. `mqtt.enabled` and
|
||||
`espnow.enabled` select the other two transports. In MQTT images the historical
|
||||
`bridge.enabled` alias controls ESP-NOW; in non-MQTT UART images it controls
|
||||
RS-232. `bridge.baud` and `bridge.uart` change only UART configuration, without
|
||||
restarting MQTT or ESP-NOW. The new UART mode starts off when upgrading old
|
||||
MQTT preferences and retains explicit settings across reboots.
|
||||
|
||||
---
|
||||
|
||||
#### RS232 heartbeat and automatic standby
|
||||
|
||||
Available on repeater builds with an RS232 bridge. This is a UART-local control
|
||||
exchange, not an advert or a LoRa packet. Room builds without RS232 are unaffected.
|
||||
|
||||
**Usage:**
|
||||
|
||||
- `get rs232.status`
|
||||
- `get rs232.heartbeat`
|
||||
- `set rs232.heartbeat on|off`
|
||||
- `get rs232.autosleep`
|
||||
- `set rs232.autosleep on|off|force`
|
||||
- `get rs232.idle`
|
||||
- `set rs232.idle <seconds>` (60 through 3600; default 300)
|
||||
|
||||
Heartbeat and negotiated automatic standby are **on by default**, including
|
||||
upgrades. An active UART pings every 30 seconds (52 bytes per ping).
|
||||
Stock bridges ignore that traffic and still receive the original data frames;
|
||||
they are not automatically put to sleep: standby is armed only after a fresh
|
||||
heartbeat reply proves support. Fresh, session/challenge-matched replies
|
||||
prove a bidirectional peer;
|
||||
ordinary valid incoming data can keep a legacy link awake but cannot prove
|
||||
heartbeat support. The status separates saved intent, actual UART state,
|
||||
standby/probe phase, and confirmed versus legacy/unknown peer liveness.
|
||||
|
||||
Enable on both updated peers:
|
||||
|
||||
```text
|
||||
set rs232.enabled on
|
||||
set rs232.heartbeat on
|
||||
set powersaving on
|
||||
set rs232.idle 300
|
||||
set rs232.autosleep on
|
||||
get rs232.status
|
||||
```
|
||||
|
||||
With heartbeat, autosleep, and global powersaving all on and the peer's heartbeat
|
||||
support confirmed, when no peer responds for the idle interval the UART is soft-stopped while
|
||||
`rs232.enabled` stays `on`. It wakes after 30 seconds for a 2-second probe;
|
||||
every eighth probe listens for 40 seconds. That longer window spans the other
|
||||
side's sleep interval, so two sleeping updated peers can reconnect without
|
||||
manual intervention (normally within about five minutes with working wiring
|
||||
and timely loop servicing). Healthy peers remain running; this is dead-link
|
||||
power saving, not an idle-data duty cycle.
|
||||
|
||||
Stock receivers ignore these control frames; they are deliberately invalid
|
||||
zero-length stock frames with an ASCII-only remainder. They do not allocate a
|
||||
mesh packet or enter LoRa forwarding. Stock peers do **not** answer heartbeats,
|
||||
so default negotiated standby never arms on a stock-only link. Valid legacy
|
||||
data after heartbeat replies cease also disarms negotiated standby, allowing
|
||||
a replacement stock peer to stay awake. Use `set rs232.autosleep force` only
|
||||
to sleep an absent/unconfirmed UART peer; this explicitly bypasses stock
|
||||
protection and can miss one-off stock packets.
|
||||
If a previously confirmed updated peer is silently replaced with stock while
|
||||
the local UART is already sleeping, its first packet can be missed before
|
||||
replacement is detected. Set autosleep off before that change, or restart the
|
||||
local bridge to clear the old negotiated session.
|
||||
While the UART is stopped it cannot receive a wake byte; mesh traffic
|
||||
during standby is not queued for later UART delivery. Turning autosleep or
|
||||
global powersaving off resumes the saved-enabled UART; `rs232.enabled off`
|
||||
remains an explicit off and never automatically wakes.
|
||||
`rs232.heartbeat off` stops initiating heartbeats and prevents standby. An
|
||||
updated active peer can still answer a valid incoming ping in passive mode.
|
||||
|
||||
---
|
||||
|
||||
#### Add a delay to packets routed through this bridge
|
||||
@@ -4527,6 +5048,7 @@ repeater images default to `off`; dedicated bridge images may default to `on`.
|
||||
- `set mqtt.enabled on`
|
||||
- `set mqtt.enabled off`
|
||||
- `get mqtt.running`
|
||||
- `get mqtt.stopping`
|
||||
- `get mqtt.status`
|
||||
|
||||
These commands are the same on MQTT-capable Companion, Repeater, Room Server,
|
||||
@@ -4536,6 +5058,21 @@ brokers to reconnect. `get mqtt.running` checks the service's runtime state;
|
||||
`get mqtt.status` reports individual broker connections. Enabling MQTT does
|
||||
not imply that a broker is connected.
|
||||
|
||||
Custom `mqtts://` and `wss://` brokers require an image with the embedded CA
|
||||
bundle. Without it, the slot stays disconnected and sends no credentials;
|
||||
use a build with the bundle or a supported preset with its pinned CA.
|
||||
Portable MQTT images retain those presets but do not include the general
|
||||
bundle for custom TLS brokers. Explicit `mqtt://` and `ws://` configurations
|
||||
remain available for trusted networks and use plaintext transport.
|
||||
|
||||
On ESP32 Repeater and Room Server images, `set mqtt.enabled off` acknowledges
|
||||
the stop request while the MQTT task disconnects and destroys its clients.
|
||||
`get mqtt.stopping` stays `on` until that cleanup completes; `mqtt.running`
|
||||
also stays `on` while those resources are still owned. Wait for
|
||||
`mqtt.stopping` to become `off` before enabling MQTT again. UART, ESP-NOW,
|
||||
and the radio continue running during this cleanup. OTA still waits for a
|
||||
completed MQTT shutdown before it can write firmware.
|
||||
|
||||
USB logging remains independent. On images with both outputs,
|
||||
`set logging.output off|usb|wifi|both` selects USB and MQTT together.
|
||||
|
||||
@@ -4650,7 +5187,9 @@ Requires WiFi connected and the MQTT bridge running.
|
||||
**Parameters:**
|
||||
- `rate`: Integer baud rate from `9600` through the board's compiled
|
||||
`BRIDGE_MAX_BAUD` (commonly `500000`); for example `115200`. Stop the bridge
|
||||
with `set bridge.enabled off` before changing it, then enable it again.
|
||||
with `set rs232.enabled off` in a combined MQTT image, or
|
||||
`set bridge.enabled off` in a non-MQTT UART image, before changing it.
|
||||
Enable the UART again with the matching switch.
|
||||
|
||||
**Default:** `115200`
|
||||
|
||||
@@ -4670,7 +5209,8 @@ Requires WiFi connected and the MQTT bridge running.
|
||||
|
||||
The setting is persistent and restarts an enabled bridge immediately. Normal
|
||||
repeater artifacts start with `bridge.enabled off`; configure the UART and baud
|
||||
rate before running `set bridge.enabled on`. On the canonical RAK4631 runtime
|
||||
rate before running `set bridge.enabled on`. Combined MQTT images use
|
||||
`rs232.enabled` for that independent UART setting. On the canonical RAK4631 runtime
|
||||
image, UART 1 is reserved even if the bounded boot probe hears no RAK12501.
|
||||
Silence cannot prove that a cold L76K is physically absent, and that module
|
||||
remains powered by the shared WB_IO2/3V3_S rail. Use UART 2. UART 1 requires an
|
||||
@@ -4817,6 +5357,22 @@ manufacturer images and the boards still requiring untouched factory dumps.
|
||||
|
||||
---
|
||||
|
||||
#### Configure battery charge voltage
|
||||
|
||||
T-Beam SX1262/SX1276 and T-Beam S3 Supreme support persistent charger-voltage
|
||||
selection through the fitted AXP192 or AXP2101 PMU:
|
||||
|
||||
```text
|
||||
get charge.voltage.options
|
||||
get charge.voltage
|
||||
set charge.voltage 4.1
|
||||
```
|
||||
|
||||
Values are exact supported targets in volts. Neither T-Beam PMU supports 3.65 V.
|
||||
Heltec Mesh Solar's CN3795 charge voltage is set by its Li-ion or LiFePO4 hardware
|
||||
version, so these commands report that limitation without changing BMS settings.
|
||||
See [charge-voltage behavior and supported targets](charge_voltage.md).
|
||||
|
||||
#### Configure nRF52 battery protection
|
||||
**Usage:** `get pwrmgt.bootlock`, `set pwrmgt.bootlock <2500-4200>`
|
||||
|
||||
@@ -4865,12 +5421,12 @@ get adc.multiplier
|
||||
set adc.multiplier 1.050
|
||||
```
|
||||
|
||||
The custom empty/full endpoints may be 2000–4200 mV and must be at least 100 mV
|
||||
The custom empty/full endpoints may be 2000-4200 mV and must be at least 100 mV
|
||||
apart. Setting either endpoint automatically selects `custom`; boot lock and
|
||||
cutoff remain separately adjustable. `get pwrmgt.bootlock` and
|
||||
`get pwrmgt.cutoff` show the values in force after selecting a profile.
|
||||
|
||||
For a RAK3401/RAK13302 LiFePO₄ cell:
|
||||
For a RAK3401/RAK13302 LiFePO4 cell:
|
||||
|
||||
```text
|
||||
set battery.profile lifepo4
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
<!-- meshcore-hosted-doc-link:start -->
|
||||
<p class="meshcore-hosted-doc-link"><a href="https://mikecarper.github.io/MeshCore/cli_settings_audit/">View this page on MeshCore Docs</a>.</p>
|
||||
<!-- meshcore-hosted-doc-link:end -->
|
||||
|
||||
# Document moved
|
||||
|
||||
<script>window.location.replace(new URL("../test-results/cli_settings_audit/" + window.location.hash, window.location.href));</script>
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
<!-- meshcore-hosted-doc-link:start -->
|
||||
<p class="meshcore-hosted-doc-link"><a href="https://mikecarper.github.io/MeshCore/companion_contact_cache/">View this page on MeshCore Docs</a>.</p>
|
||||
<!-- meshcore-hosted-doc-link:end -->
|
||||
|
||||
# Document moved
|
||||
|
||||
<script>window.location.replace(new URL("../research/companion_contact_cache/" + window.location.hash, window.location.href));</script>
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
<!-- meshcore-hosted-doc-link:start -->
|
||||
<p class="meshcore-hosted-doc-link"><a href="https://mikecarper.github.io/MeshCore/companion_contact_cache_results/">View this page on MeshCore Docs</a>.</p>
|
||||
<!-- meshcore-hosted-doc-link:end -->
|
||||
|
||||
# Document moved
|
||||
|
||||
<script>window.location.replace(new URL("../test-results/companion_contact_cache_results/" + window.location.hash, window.location.href));</script>
|
||||
|
||||
@@ -0,0 +1,87 @@
|
||||
<!-- meshcore-hosted-doc-link:start -->
|
||||
<p class="meshcore-hosted-doc-link"><a href="https://mikecarper.github.io/MeshCore/companion_lost_reply/">View this page on MeshCore Docs</a>.</p>
|
||||
<!-- meshcore-hosted-doc-link:end -->
|
||||
|
||||
# Companion lost-status auto response
|
||||
|
||||
Development Companion builds containing this feature can automatically answer a private `Am I lost?` message with `Yes`
|
||||
or `No`. You choose the saved answer. The default is `off`, and the firmware
|
||||
does not infer lost status from GPS, distance, or a missing phone connection.
|
||||
|
||||
This is included in nRF52, ESP32 and RP2040 Companion profiles, including
|
||||
T1000-E and RAK Tag. Compact STM32 Companion images omit it to preserve their
|
||||
existing application and filesystem boundaries. A custom build can explicitly
|
||||
enable `MESH_ENABLE_LOST_REPLY=1` when its flash budget permits; the normal
|
||||
flash and RAM checks still apply.
|
||||
|
||||
Configure it through a directly connected local Companion CLI:
|
||||
|
||||
~~~text
|
||||
get lost.reply
|
||||
set lost.reply no
|
||||
set lost.reply yes
|
||||
set lost.reply off
|
||||
~~~
|
||||
|
||||
The setting survives reboot and takes effect immediately. USB, BLE and other
|
||||
local Companion transports can use framed command `0x42`. Builds with a text
|
||||
terminal also accept these commands there. LoRa CLI cannot read or change this
|
||||
local setting. A failed save restores the previous answer and reports an error.
|
||||
Older saved preference images load the new setting as `off`.
|
||||
|
||||
ESP32 Companions with WebConfig also have a **Lost-status auto reply**
|
||||
selection on the radio configuration page. Choose Off, Reply No or Reply Yes
|
||||
and save. The control is shown only on Companion firmware with this capability.
|
||||
The existing mobile app protocol needs no new message type; a native app toggle
|
||||
is not added by this firmware change.
|
||||
|
||||
## Dog tracker example
|
||||
|
||||
The owner's Companion stores the tracker as a normal radio contact. Set the
|
||||
answer to `no` during normal operation. If the dog is lost, set it to `yes`.
|
||||
When the tracker sends `Am I lost?` privately to that reachable Companion, it
|
||||
receives the selected answer, even when the owner's phone is disconnected.
|
||||
The same configured answer applies to all eligible contacts on this Companion.
|
||||
|
||||
The opt-in [Sensor dog tracker](sensor_tracker.md) implements periodic fresh
|
||||
GPS acquisition, low power sleep, and owner check-ins. It uses a tagged encrypted
|
||||
request and response rather than parsing plain `Yes` or `No` text, so old answers
|
||||
cannot change a newer check-in. Store the Sensor as a persistent Sensor contact
|
||||
on the owner's Companion. The same saved `lost.reply` setting answers those
|
||||
checks; `off` returns Unknown. A fresh accepted GPS position updates the contact
|
||||
location through normal contact notifications.
|
||||
|
||||
Other clients can still use the plain text question. They must stay awake for
|
||||
the separate private answer; a delivery ACK is not that answer. Plain text
|
||||
answers have no request ID. The responding Companion must be powered on and
|
||||
reachable. [Personal room mailboxes](room_services.md#sleeping-tracker-example)
|
||||
provide store-and-forward mail; automatic mailbox polling is not yet implemented
|
||||
in the Sensor tracker.
|
||||
|
||||
## Matching and traffic limits
|
||||
|
||||
Only an authenticated ordinary private message from a known persistent radio
|
||||
contact is eligible. The contact must be an ordinary Companion/chat contact;
|
||||
room-server and repeater contacts are excluded. Temporary unknown senders,
|
||||
public channels, room chat posts and CLI messages do not trigger a response.
|
||||
|
||||
The exact phrase `am I lost` is matched without regard to ASCII letter case,
|
||||
with an optional final question mark and surrounding whitespace. Extra words
|
||||
or commands do not match. The reply is ordinary encrypted private text, `Yes`
|
||||
or `No`, and neither answer can trigger another automatic reply.
|
||||
|
||||
Automatic responses are limited to one per contact per minute and one globally
|
||||
per ten seconds. A bounded four-peer cache uses full public keys and the
|
||||
incoming request timestamp to suppress retries. Its state clears on reboot.
|
||||
The same retained request is not answered again; a fresh check uses a new
|
||||
timestamp. An old retry can receive another answer after a newer request
|
||||
replaces its saved timestamp or its peer slot is evicted. Queueing an answer
|
||||
records the request even if later delivery fails. An unanswered poll means
|
||||
unknown, and the tracker should trust replies only from its configured
|
||||
Companion's full public key.
|
||||
|
||||
The responder leaves packet capacity for normal receive/ACK work, uses a free
|
||||
ACK-tracking entry, and does not replace pending user messages or their
|
||||
delivery deadlines. Busy packet or ACK capacity, or a failed send, can prevent an answer;
|
||||
the tracker should retry later. A full offline app inbox does not disable the
|
||||
responder. Normal message reception and notification behavior remain in place.
|
||||
@@ -1,3 +1,7 @@
|
||||
<!-- meshcore-hosted-doc-link:start -->
|
||||
<p class="meshcore-hosted-doc-link"><a href="https://mikecarper.github.io/MeshCore/companion_offline_queue/">View this page on MeshCore Docs</a>.</p>
|
||||
<!-- meshcore-hosted-doc-link:end -->
|
||||
|
||||
# Companion Offline Message Queue
|
||||
|
||||
Companion firmware keeps received channel data, channel messages, and direct
|
||||
@@ -13,6 +17,7 @@ A reboot clears it.
|
||||
| ESP32 without PSRAM | 256 |
|
||||
| nRF52840 with at most 300 contact slots | 256 |
|
||||
| nRF52840 with more than 300 contacts and the contact cache (non-mOTA-shared queue) | 256 total: up to 15 held DMs use slots until accepted |
|
||||
| RAK WisMesh Tag ordinary BLE Companion | 240 total: up to 15 held DMs use slots until accepted |
|
||||
| nRF52840 with more than 300 contacts but no contact cache | 208 ordinary frames plus a separate 15-DM queue |
|
||||
| nRF52 Full Companion with the memory correction | 256 normally; 128 while mOTA owns shared storage |
|
||||
| RP2040 | 256 |
|
||||
@@ -56,6 +61,13 @@ Uncached high-contact profiles keep the conservative 208-frame default and
|
||||
separate held queue. An explicit target `OFFLINE_QUEUE_SIZE` still takes
|
||||
precedence.
|
||||
|
||||
The ordinary RAK WisMesh Tag BLE profile uses 240 frames to retain the runtime
|
||||
RAM reserve for Bluetooth, filesystem buffers, and the main task. It keeps
|
||||
350 contact slots and 40 group channels. All 240 slots are available when no
|
||||
DMs are held; 15 held DMs leave 225 ordinary slots. This saves 2,832 bytes
|
||||
compared with 256 frames. The Tag Full Companion retains 256 frames normally
|
||||
and 128 while mOTA borrows queue storage.
|
||||
|
||||
Each queue slot currently costs 177 bytes. A 256-frame queue reserves 45,312
|
||||
bytes, while a 512-frame queue reserves 90,624 bytes. There is no 256-frame
|
||||
protocol limit: the queue length and indexes can represent 512 or more. The
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
<!-- meshcore-hosted-doc-link:start -->
|
||||
<p class="meshcore-hosted-doc-link"><a href="https://mikecarper.github.io/MeshCore/companion_protocol/">View this page on MeshCore Docs</a>.</p>
|
||||
<!-- meshcore-hosted-doc-link:end -->
|
||||
|
||||
# Companion Protocol
|
||||
|
||||
- **Last Updated**: 2026-08-26
|
||||
@@ -310,6 +314,11 @@ WiFi store, and enablement is saved in node preferences. WiFi/BLE builds apply
|
||||
enablement from the main loop; exclusive WiFi/BLE builds apply it after reboot.
|
||||
`get wifi.status` reports the connection state and `get wifi.ip` reports the
|
||||
station address, the setup AP address, or `(not connected)`.
|
||||
`get wifi.tcp` reports the actual app listener, interface registration, client
|
||||
connection and completed frame counters. It is read-only and available through
|
||||
the same binary CLI command over BLE or USB when TCP is unreachable. Listener
|
||||
startup failures recover automatically without resetting a healthy client or
|
||||
the WiFi connection; see [the CLI diagnostics](cli_commands.md#browser-configuration-portal-esp32-repeater-and-room-server).
|
||||
`set freq <MHz>` works through command `0x42` and authorized LoRa CLI, like
|
||||
`set radio`; it saves the frequency and requires reboot to apply. See
|
||||
[local maintenance commands](terminal_chat_cli.md#local-maintenance-commands)
|
||||
@@ -541,6 +550,11 @@ Byte 1: Channel index (0 through max_channels - 1)
|
||||
|
||||
**Response**: `PACKET_CHANNEL_INFO` (0x12) with channel details
|
||||
|
||||
On builds with `MESH_ENABLE_MESHTEXT_CHAT=1`, a capable client can append a
|
||||
third request byte of `32` (`1F <index> 20`) to retrieve the complete 32-byte
|
||||
secret for an `mt-` channel. Ordinary two-byte requests, and requests for
|
||||
MeshCore channels, retain the original 16-byte secret response.
|
||||
|
||||
---
|
||||
|
||||
### 4. Set Channel
|
||||
@@ -579,7 +593,27 @@ Bytes 34-49: Secret (16 bytes)
|
||||
[16 bytes of secret]
|
||||
```
|
||||
|
||||
**Note**: The 32-byte secret variant is unsupported and returns `PACKET_ERROR`.
|
||||
**Meshtext-only extension**: With `MESH_ENABLE_MESHTEXT_CHAT=1`, a 66-byte
|
||||
`SET_CHANNEL` frame can supply 32 secret bytes at offsets 34–65 for an `mt-`
|
||||
channel. Non-MT channels and feature-disabled builds reject this variant with
|
||||
`PACKET_ERROR`; the standard 50-byte MeshCore command is unchanged. The MT
|
||||
backend uses AES-256 when the upper 16 bytes are nonzero, otherwise AES-128.
|
||||
|
||||
A legacy 50-byte SET of an existing MT channel preserves its upper 16 key
|
||||
bytes when the supplied lower half is unchanged. This permits an old client's
|
||||
GET/edit/SET round trip without silently truncating a provisioned AES-256 key.
|
||||
To explicitly replace or downgrade that key, send the full 32-byte variant
|
||||
(zero the upper half for AES-128). A client using only the legacy GET cannot
|
||||
export a complete AES-256 key. Stock app editors do not gain 256-bit
|
||||
provisioning or sharing from this firmware extension.
|
||||
|
||||
For local CLI provisioning without implementing the binary extension, use
|
||||
`set mt.channel` with a 32- or 64-digit hexadecimal PSK; see the
|
||||
[private-channel commands](radio_profiles.md#meshtext-channel-chat).
|
||||
|
||||
MT channel changes can also fail with a bad-state error for conflicting
|
||||
presets, an unsuitable manual radio2 profile, ambiguous region or a busy
|
||||
automatic owner. See [Meshtext channel chat](radio_profiles.md#meshtext-channel-chat).
|
||||
|
||||
**Response**: `PACKET_OK` (0x00) on success, `PACKET_ERROR` (0x01) on failure
|
||||
|
||||
@@ -1031,7 +1065,9 @@ Bytes 2-33: Channel Name (32 bytes, null-terminated)
|
||||
Bytes 34-49: Secret (16 bytes)
|
||||
```
|
||||
|
||||
**Note**: The device returns the 16-byte channel secret in this response.
|
||||
**Note**: The normal response contains the 16-byte channel secret. An opt-in
|
||||
32-byte MT GET described above extends the secret through byte 65; response
|
||||
code, index and name fields remain unchanged.
|
||||
|
||||
**PACKET_DEVICE_INFO** (0x0D):
|
||||
```
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
<!-- meshcore-hosted-doc-link:start -->
|
||||
<p class="meshcore-hosted-doc-link"><a href="https://mikecarper.github.io/MeshCore/companion_radio_full/">View this page on MeshCore Docs</a>.</p>
|
||||
<!-- meshcore-hosted-doc-link:end -->
|
||||
|
||||
# Full Companion
|
||||
|
||||
Configure message alerts with the [Companion notification builder](notifications.md).
|
||||
@@ -47,6 +51,86 @@ a busy radio rollback remains pending for recovery instead of reporting success.
|
||||
| LoRa self-update | No | No |
|
||||
| Optional self-update | WiFi on existing dual-app layouts; USB on single-app layouts | Bluetooth DFU with a compatible bootloader; USB always supported |
|
||||
|
||||
### Screenless nRF52 spoken button confirmations
|
||||
|
||||
Full Companion builds for T1000-E, RAK WisMesh Tag, MeshTracker X1,
|
||||
Muziworks R1 Neo and ThinkNode M3 use prerecorded spoken confirmations for
|
||||
their shared single-button controls. Other roles, legacy USB/BLE-only builds,
|
||||
screened boards and all Wio profiles retain their existing audio behavior.
|
||||
Set `MESH_GPS_VOICE=0` at build time to retain tones on an eligible Full build.
|
||||
|
||||
| User-button gesture | Action / spoken confirmation |
|
||||
| --- | --- |
|
||||
| One press | Acknowledge an active notification: "Notification cleared"; otherwise "Ready" |
|
||||
| Two presses | "Advert queued" or "Advert failed" |
|
||||
| Three presses | "Sound on" or "Sound off" after the preference is saved |
|
||||
| Four presses | "GPS on" or "GPS off" when the GPS setting is available |
|
||||
| Hold more than three seconds | "Shutting down"; shutdown remains nonblocking while audio drains |
|
||||
| Hold during the first eight seconds of UI startup | "USB setup" for terminal/rescue mode |
|
||||
|
||||
MeshTracker X1's three-press gesture instead cycles "Sound and vibration",
|
||||
"Sound only", "Vibration only", and "Silent". The new setting must save before
|
||||
either live alert output changes. A failed save restores both preferences and
|
||||
announces "Action failed". An explicit mute or alert-mode change permits one
|
||||
courtesy confirmation without unmuting ordinary notifications. The four
|
||||
additional mode clips are compiled only for `HAS_DRV2605` builds.
|
||||
Notification timers cannot cancel control speech, and the committed alert
|
||||
mode remains authoritative for vibration even if a notification-mask save fails.
|
||||
|
||||
Power-on and wake wiring remain board-specific. Speech is nonblocking and uses
|
||||
the existing nRF52 PWM player; each buzzer still needs a physical listening
|
||||
test for intelligibility. All spoken button, alert-mode and GPS recordings use
|
||||
Microsoft Zira Desktop with the selected compressed Companion voice profile.
|
||||
GPS on/off use three stored audio spans: one shared encoded GPS prefix and two
|
||||
phrase tails. Advert queued/failed also share an identical encoded prefix;
|
||||
Sound off/Sound only share one only on `HAS_DRV2605` boards. The tails are not
|
||||
independently decodable words. Playback treats each prefix and tail as one
|
||||
continuous compressed stream, without resetting its codec, bit position,
|
||||
interpolation or DMA at the seam. The approved full-phrase PCM and PWM output
|
||||
remain bit-for-bit identical, including pronunciation and pauses.
|
||||
|
||||
The IMA audio payload is 3,881 bytes smaller on ordinary voice boards, or
|
||||
4,971 bytes smaller on vibration-equipped boards, before the small segmented
|
||||
descriptor/decoder overhead. Tiny shared silence prefixes are not split.
|
||||
A representative Cortex-M4 GCC 14.2 size-optimized/LTO player-and-catalog
|
||||
build saves 3,732 or 4,792 bytes respectively after that overhead; final
|
||||
firmware savings can vary with toolchain and surrounding code.
|
||||
The existing generators still synthesize the two complete GPS phrases and
|
||||
button confirmations, then share byte-identical encoded prefixes; they do not
|
||||
try to identify word boundaries or splice independently spoken words. Windows
|
||||
Zira synthesis still requires Windows' local `System.Speech` tooling.
|
||||
R1 Neo's buzzer pin aliases its optional QSPI clock, so voice is
|
||||
not default-enabled when `QSPIFLASH` is selected. ThinkNode M4 is deliberately
|
||||
excluded: its former
|
||||
buzzer definitions incorrectly claimed I2C SDA and a battery-status LED, not
|
||||
verified audio hardware. Its Companion recipes no longer drive those pins as
|
||||
a buzzer.
|
||||
|
||||
The optional ESP32 RC32-without-display recipe is not part of this nRF52
|
||||
extension: its fitted audio hardware needs verification and its `ui-new`
|
||||
controls require an ESP32 playback backend, not the nRF52 PWM player.
|
||||
|
||||
#### Experimental G.726 speech
|
||||
|
||||
Ordinary builds still use the approved Zira IMA recordings. G.726 is a hardware
|
||||
test option only: set both `MESH_BUTTON_AUDIO_HIL=1` and
|
||||
`MESH_GPS_VOICE_G726_BITRATE=16` or `24`. Invalid bitrates or a missing test gate
|
||||
are rejected. The generated catalog selects exactly one bitrate, retains the
|
||||
original sample counts, and omits the X1-only mode prompts on other boards.
|
||||
|
||||
The decoder needs 52 bytes of state, no heap or full-clip buffer, and about
|
||||
1.2 KiB of Cortex-M4 code/tables. Its packing, resets and PWM lifecycle are
|
||||
checked against independently pinned FFmpeg output. At 24 kbps the audio
|
||||
payload is 25% smaller; at 16 kbps it is 50% smaller with more waveform error.
|
||||
Neither software comparisons nor successful builds qualify physical buzzer
|
||||
intelligibility or the refill deadline under Bluetooth/radio load.
|
||||
|
||||
`scripts/generate_g726_voice.py` regenerates both experimental catalogs from
|
||||
the approved mono 8 kHz Zira WAVs using FFmpeg's MSB-first `g726` encoder.
|
||||
Production IMA headers cannot be selected as its output. The decoder's Sun
|
||||
provenance, attributed FFmpeg MIX interpolation and LGPL license are retained
|
||||
in `src/helpers/ui/g726`; no claim of independent ITU conformance is made.
|
||||
|
||||
### nRF52 Bluetooth reconnect qualification
|
||||
|
||||
Full Companions persist the paired client's notification subscriptions (CCCDs)
|
||||
@@ -441,6 +525,26 @@ their views. Companion session state is device-wide, so use one active
|
||||
Companion application at a time. On nRF52, BLE remains available while USB is
|
||||
in terminal or mOTA mode.
|
||||
|
||||
The Companion TCP listener starts only after a usable WiFi station or setup-AP
|
||||
address exists. If opening the socket fails, it closes any failed-start socket
|
||||
and retries with a delay capped at 30 seconds, without restarting the WiFi
|
||||
connection or disturbing BLE, WebConfig, OTA, or a healthy accepted TCP client.
|
||||
Turning WiFi off or suspending it cancels the listener request; retry service
|
||||
cannot turn it back on. Use `get wifi.tcp` through BLE, USB, or the browser CLI
|
||||
to inspect listener state, registration, accepted clients, completed RX/TX
|
||||
frames, and errors without a PC port probe. `get display.wifi` also shows the
|
||||
actual `tcp` listener flag independently of the network-services flag. A
|
||||
listening socket alone cannot detect LAN firewall or guest-network isolation.
|
||||
|
||||
ESP32 TCP sends are non-blocking, and inbound commands are serviced alongside
|
||||
outgoing data rather than waiting for an empty transmit queue. Unsolicited
|
||||
radio logs therefore cannot keep the app handshake unread. A live client with
|
||||
pending output is disconnected after 10 seconds without write progress;
|
||||
other transports continue running. Required replies retain their queue slots,
|
||||
so a new command waits if no reply can be admitted. Partially sent frames
|
||||
remain pinned to preserve framing, while already queued replies keep the same
|
||||
reconnection rules below.
|
||||
|
||||
On WiFi TCP port 5000, reconnecting from the same IP retains queued outgoing
|
||||
frames. A different IP takes over immediately, but never receives the previous
|
||||
IP's replies: up to four queued frames are parked separately for 30 seconds
|
||||
@@ -478,13 +582,44 @@ or UART backpressure pauses the contact stream; a frame may drain through a
|
||||
smaller hardware FIFO in ordered chunks, but its remainder is retained and no
|
||||
later frame can interleave with it or cause it to be discarded.
|
||||
|
||||
Binary login, status, telemetry, path-discovery, binary-request, and trace
|
||||
results also have bounded producer retention when that transport queue is
|
||||
full. One request result and one trace result can wait independently, on their
|
||||
original captured interfaces. Their required `SENT` frames are retained and
|
||||
admitted before the final result. A first valid radio result gets up to ten
|
||||
seconds for whole-frame queue admission; a still-blocked `SENT` keeps its
|
||||
earlier ten-second deadline. Duplicate callbacks cannot replace the retained
|
||||
result or extend these deadlines. Disconnect, cancellation, or expiry drops
|
||||
an unadmitted result. Queue admission is not proof of physical host receipt.
|
||||
|
||||
Reflected request tags are matched against the complete peer public key.
|
||||
Recent request and trace history has eight slots each, reserved before radio
|
||||
transmission; live entries are never evicted to accept a newer request. A full
|
||||
history or a quarantined identifier returns `ERR_BAD_STATE`, so clients must
|
||||
back off. Retired reflected tags and trace tag/auth tuples are suppressed for
|
||||
at least ten seconds and through the original radio deadline plus ten seconds
|
||||
when longer. Login retries to the same peer have at least a thirty-second
|
||||
quarantine. Positively matched newer status/telemetry/binary replies still work
|
||||
after login, but otherwise unsolicited responses from that login peer can be
|
||||
suppressed during the quarantine.
|
||||
|
||||
These guards are finite and clear at reboot. Login replies carry a server
|
||||
timestamp, not an echoed request nonce; sufficiently late replies or a
|
||||
timestamp collision cannot always be distinguished without a protocol change.
|
||||
Trace tag/auth fields are correlation data, not cryptographic authentication.
|
||||
The guards do not authenticate a host process or retract frames already
|
||||
accepted by a transport. In particular, the same-IP TCP queue replay described
|
||||
above remains unchanged, including its lack of authentication.
|
||||
|
||||
When a BLE client requests pairing, a display-equipped build wakes the screen,
|
||||
switches to the first home page, and keeps the active six-digit PIN visible
|
||||
until Bluetooth connects or the two-minute pairing window expires. USB, WiFi,
|
||||
Ethernet, and hardware-serial connections do not suppress this screen. With no
|
||||
saved BLE PIN, display builds generate a new PIN at boot; builds without a
|
||||
physical display use `123456`. A PIN saved through the Companion protocol takes
|
||||
effect after reboot.
|
||||
physical display use `123456`. Use `set pin 654321` in the Companion CLI to save
|
||||
a fixed pairing PIN, then `reboot` to activate it. `set pin 0` restores the
|
||||
default/generated PIN policy. A PIN saved through the Companion protocol also
|
||||
takes effect after reboot. See the [PIN command reference](cli_commands.md#set-the-bluetooth-pairing-pin-companion).
|
||||
|
||||
The Bluetooth device name is independently configurable. In the text terminal,
|
||||
use `get bluetooth.name` and `set bluetooth.name <name>`; use
|
||||
@@ -613,7 +748,17 @@ that a reboot is required. Use `set usb.logging on reboot` to save it and have
|
||||
the node reboot automatically after the reply. The second interface appears
|
||||
after that reboot. Likewise, `set usb.logging off reboot` removes interface
|
||||
`02`. The optional `reboot` word is accepted only in these exact command forms
|
||||
and triggers a reboot only when the descriptor actually needs to change.
|
||||
and triggers a reboot only when the USB logging mode or descriptor needs to change.
|
||||
|
||||
For serial bridges that use one port for both commands and packets, including
|
||||
unmodified Cisien/meshcoretomqtt, use `set usb.logging stream reboot` instead.
|
||||
This saved packet-stream mode keeps only interface `00` and emits plaintext
|
||||
RAW/RX/TX records together with repeater-style ` -> ` CLI replies. Configure
|
||||
the bridge with the stable `*-if00` path. Binary Companion and serial mOTA are
|
||||
unavailable on that port while logging is enabled; `set usb.logging off` stops
|
||||
the stream and leaves the ASCII terminal active. BLE remains available.
|
||||
`get usb.logging` reports `stream` and the primary port after reboot. To return
|
||||
to separate logging, use `set usb.logging on reboot`.
|
||||
|
||||
These are Full Companion **text-terminal** commands. The superficially similar
|
||||
`meshcli ... get usb.logging` command uses the Binary Companion parameter
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
<!-- meshcore-hosted-doc-link:start -->
|
||||
<p class="meshcore-hosted-doc-link"><a href="https://mikecarper.github.io/MeshCore/companion_usb_ascii_validation/">View this page on MeshCore Docs</a>.</p>
|
||||
<!-- meshcore-hosted-doc-link:end -->
|
||||
|
||||
# Document moved
|
||||
|
||||
<script>window.location.replace(new URL("../test-results/companion_usb_ascii_validation/" + window.location.hash, window.location.href));</script>
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
<!-- meshcore-hosted-doc-link:start -->
|
||||
<p class="meshcore-hosted-doc-link"><a href="https://mikecarper.github.io/MeshCore/companion_usb_client_validation/">View this page on MeshCore Docs</a>.</p>
|
||||
<!-- meshcore-hosted-doc-link:end -->
|
||||
|
||||
# Document moved
|
||||
|
||||
<script>window.location.replace(new URL("../test-results/companion_usb_client_validation/" + window.location.hash, window.location.href));</script>
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
<!-- meshcore-hosted-doc-link:start -->
|
||||
<p class="meshcore-hosted-doc-link"><a href="https://mikecarper.github.io/MeshCore/docs/">View this page on MeshCore Docs</a>.</p>
|
||||
<!-- meshcore-hosted-doc-link:end -->
|
||||
|
||||
# Local Documentation
|
||||
|
||||
This document explains how to build and view the MeshCore documentation locally.
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
<!-- meshcore-hosted-doc-link:start -->
|
||||
<p class="meshcore-hosted-doc-link"><a href="https://mikecarper.github.io/MeshCore/double_frequency_v4_validation/">View this page on MeshCore Docs</a>.</p>
|
||||
<!-- meshcore-hosted-doc-link:end -->
|
||||
|
||||
# Document moved
|
||||
|
||||
<script>window.location.replace(new URL("../test-results/double_frequency_v4_validation/" + window.location.hash, window.location.href));</script>
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
<!-- meshcore-hosted-doc-link:start -->
|
||||
<p class="meshcore-hosted-doc-link"><a href="https://mikecarper.github.io/MeshCore/esp32_memory_budget/">View this page on MeshCore Docs</a>.</p>
|
||||
<!-- meshcore-hosted-doc-link:end -->
|
||||
|
||||
# Document moved
|
||||
|
||||
<script>window.location.replace(new URL("../research/esp32_memory_budget/" + window.location.hash, window.location.href));</script>
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
<!-- meshcore-hosted-doc-link:start -->
|
||||
<p class="meshcore-hosted-doc-link"><a href="https://mikecarper.github.io/MeshCore/esp32_partition_catalog/">View this page on MeshCore Docs</a>.</p>
|
||||
<!-- meshcore-hosted-doc-link:end -->
|
||||
|
||||
# ESP32 release partition lookup table
|
||||
|
||||
The canonical LUT is `firmware/esp32_partition_catalog.json` in this fork.
|
||||
@@ -5,6 +9,46 @@ The canonical LUT is `firmware/esp32_partition_catalog.json` in this fork.
|
||||
assets. MeshCore Open bundles an identical copy for offline phone updates.
|
||||
There is no need to run a command on the phone or download old firmware there.
|
||||
|
||||
## Smallest partition by board
|
||||
|
||||
The table shows each board's **smallest known application partition** across
|
||||
all cataloged releases, roles, and profiles. It does not count smaller NVS or
|
||||
filesystem partitions. Sizes use MiB (1,048,576 bytes), with exact bytes below.
|
||||
Mixed layouts means some releases have dual-slot OTA and others do not.
|
||||
These are historical factory-image minimums, not measurements of your device;
|
||||
use `get storage.layout` to check its actual installed layout.
|
||||
|
||||
<div class="partition-catalog" id="partition-catalog">
|
||||
<div class="partition-catalog-controls">
|
||||
<label for="partition-catalog-search">Board</label>
|
||||
<input id="partition-catalog-search" type="search" placeholder="Search board name" autocomplete="off" disabled>
|
||||
<a id="partition-catalog-open" href="../_data/esp32_partition_catalog.json">Open JSON</a>
|
||||
<a href="../_data/esp32_partition_catalog.json" download="esp32_partition_catalog.json">Download JSON</a>
|
||||
</div>
|
||||
<p id="partition-catalog-status" role="status" aria-live="polite">Loading catalog JSON...</p>
|
||||
<p id="partition-catalog-count" role="status" aria-live="polite"></p>
|
||||
<div class="partition-catalog-table" id="partition-catalog-table" tabindex="0" aria-label="Smallest application partition by board" hidden>
|
||||
<table>
|
||||
<thead><tr><th scope="col">Board</th><th scope="col">Smallest app partition</th><th scope="col">OTA layouts</th></tr></thead>
|
||||
<tbody id="partition-catalog-boards"></tbody>
|
||||
</table>
|
||||
</div>
|
||||
<details class="partition-catalog-raw" id="partition-catalog-raw">
|
||||
<summary>Show JSON details</summary>
|
||||
<div class="partition-catalog-controls">
|
||||
<label for="partition-catalog-view">JSON view</label>
|
||||
<select id="partition-catalog-view" disabled>
|
||||
<option value="layouts">Snapshot and partition layouts</option>
|
||||
<option value="releases">Release inventory</option>
|
||||
<option value="all">Complete catalog (large)</option>
|
||||
</select>
|
||||
</div>
|
||||
<pre class="partition-catalog-json" tabindex="0" aria-label="Partition catalog JSON"><code id="partition-catalog-json">Expand this section to view the JSON.</code></pre>
|
||||
</details>
|
||||
</div>
|
||||
|
||||
With JavaScript disabled, use [the complete JSON file](_data/esp32_partition_catalog.json).
|
||||
|
||||
## Coverage
|
||||
|
||||
The initial snapshot enumerates every published release (including prereleases,
|
||||
|
||||
@@ -1,11 +1,30 @@
|
||||
# ESP32 legacy-partition migration over Wi-Fi or LoRa
|
||||
<!-- meshcore-hosted-doc-link:start -->
|
||||
<p class="meshcore-hosted-doc-link"><a href="https://mikecarper.github.io/MeshCore/esp32_wifi_partition_migration/">View this page on MeshCore Docs</a>.</p>
|
||||
<!-- meshcore-hosted-doc-link:end -->
|
||||
|
||||
# ESP32 legacy-partition migration over USB, Wi-Fi or LoRa
|
||||
|
||||
The partition-migration bridge lets a supported ESP32 node move from a small
|
||||
dual-OTA layout to a verified larger dual-OTA layout without cable flashing or
|
||||
a full-chip erase. It fits a legacy 1.25 MiB slot. The Wi-Fi version exposes an
|
||||
updater for the full image after migration. The LoRa version retains the old
|
||||
OTA-capable repeater in the other expanded slot and returns to it after
|
||||
restoring identity, so it can fetch the full image over LoRa.
|
||||
updater for the Full image after migration. The LoRa version preserves a
|
||||
verified old OTA receiver so it can fetch the Full image into the expanded
|
||||
inactive slot. On 8/16 MiB flash the bridge restores identity before handing
|
||||
off; the 4 MiB route keeps identity staged until the Full image's first boot.
|
||||
|
||||
The distinct USB version performs verified identity/configuration restoration
|
||||
before the host writes the final app. It keeps application USB CDC disabled
|
||||
during flash operations and prevents Arduino startup from automatically
|
||||
erasing damaged/full NVS. S3/C3 return to ROM download automatically once
|
||||
restoration settles; original ESP32 reports matching-MAC settled UART status
|
||||
before the host uses DTR/RTS. Neither route needs physical BOOT access, but
|
||||
classic boards require their working USB-UART automatic-reset circuit.
|
||||
|
||||
The host must verify the same physical USB device and original chip MAC, the
|
||||
exact target table, CRC-valid NVS restored/no-pending markers, and current OTA
|
||||
metadata before flashing the unchanged final app. USB reappearance, a timer,
|
||||
or settled status alone is not recovery proof. An interrupted migration retains
|
||||
its pending host record and device-side restoration markers for a safe retry.
|
||||
|
||||
It has explicit, byte-for-byte generated target plans for the 4 MiB MeshCore
|
||||
dual-OTA Full table and Arduino ESP32 `default_8MB.csv` and
|
||||
@@ -19,6 +38,37 @@ table-prefix test; the bridge core is otherwise shared.
|
||||
| 8 MiB | `default_8MB.csv` | 0x330000 / 0x330000 | Seeed XIAO ESP32-S3, Heltec V3 |
|
||||
| 16 MiB | `default_16MB.csv` | 0x640000 / 0x640000 | Heltec V4 / V4.3, Station G2 |
|
||||
|
||||
These are the supported migration plans. The 8/16 MiB tables retain more
|
||||
filesystem storage and a coredump partition; their application slots are not
|
||||
the absolute maximum permitted by the physical flash capacity.
|
||||
|
||||
## Ordinary Full releases and installation
|
||||
|
||||
Ordinary ESP32 node releases use Full for each exact board and firmware role.
|
||||
The existing RAM and application-size guards still apply: a failed Full
|
||||
qualification fails that release target rather than publishing a smaller
|
||||
ordinary image. Direct `--standard` builds and reduced identities remain
|
||||
compatibility/development tools outside the ordinary release selection.
|
||||
|
||||
An app-only update cannot change the installed partition table. If a device
|
||||
already has the required layout, use the matching Full application through a
|
||||
supported updater. If its layout needs expansion, use a verified migration
|
||||
ZIP for its exact entry in `--list-boards` and an updater supported by the old
|
||||
image. Otherwise, install the exact board/role's Full `-merged.bin` through
|
||||
USB/serial using that board's flashing procedure; the merged image contains
|
||||
the bootloader, partition table, and application. Never upload a merged image
|
||||
through an app-only browser or LoRa updater.
|
||||
|
||||
The new Full defaults outside the migration catalog, including ESP32-C6
|
||||
boards and boards introduced with `min_spiffs.csv`, have no reviewed wireless
|
||||
expansion package in this recipe. If their Full app fits the unchanged live
|
||||
layout and update identity, an ordinary app update can suffice. A table change
|
||||
requires USB/serial until a matching migration package is reviewed. A Full
|
||||
image fitting physical flash does not by itself prove it can be installed
|
||||
through an old updater. Full Companion images with a single app slot likewise
|
||||
need USB/serial for local firmware replacement; their host-backed LoRa sending
|
||||
role does not provide a second local update slot.
|
||||
|
||||
## Repeatable ESP32 build recipe
|
||||
|
||||
From a clean commit, run the shell menu in WSL/Linux:
|
||||
@@ -69,28 +119,68 @@ ZIPs appear under `.releases/esp32-expanded-<commit>/packages/`. The recipe
|
||||
requires a clean checkout so each ZIP identifies the firmware commit it was
|
||||
built from. It creates files only; it does not flash a device or erase flash.
|
||||
|
||||
## Independent USB firmware set
|
||||
|
||||
The USB intermediate firmware has six distinct chip/flash-capacity builds:
|
||||
|
||||
| Chip | Flash capacities | USB completion |
|
||||
| --- | --- | --- |
|
||||
| Original ESP32 | 4, 8 MiB | Settled UART status, then host DTR/RTS |
|
||||
| ESP32-C3 | 4 MiB | Automatic ROM return |
|
||||
| ESP32-S3 | 4, 8, 16 MiB | Automatic ROM return |
|
||||
|
||||
To build only this complete set, without rebuilding or relabeling any Full
|
||||
firmware release, run serially in WSL/Linux:
|
||||
|
||||
```bash
|
||||
python3 -B scripts/build_esp32_partition_migration.py --usb-only
|
||||
```
|
||||
|
||||
This creates `meshcore-usb-partition-bridges.zip` under
|
||||
`.releases/esp32-usb-bridges-<commit>/`. Its manifest records each binary's chip,
|
||||
capacity, bytes and SHA-256, plus the utility source commit, dirty-tree state
|
||||
and runtime/configuration source hashes. All six are mandatory, and both the
|
||||
USB migration and NVS no-autoerase guard markers are verified. Existing Wi-Fi
|
||||
and LoRa artifacts remain unchanged. Exact new board/role migration packages
|
||||
also include their matching `usb-bridge.bin`.
|
||||
|
||||
A host can pair this standalone set with an older, exact Full migration ZIP;
|
||||
the Full image retains its original release/source identity. Meshfirmware
|
||||
downloads a versioned, checksum-pinned published USB set when a qualified
|
||||
older package has no USB bridge. It does not use an untested `latest` bridge,
|
||||
and an ordinary update that fits does not download migration utilities.
|
||||
For an offline or explicitly selected set, use `MESHFIRMWARE_USB_MIGRATION_SET`,
|
||||
or place the ZIP beside the flasher at `tools/meshcore-usb-partition-bridges.zip`.
|
||||
The original Full
|
||||
package must still match the selected final image and prove the target layout.
|
||||
No app-only upload or manual partition-table write substitutes for that proof.
|
||||
|
||||
The catalog covers canonical ESP32 repeater, room-server, and sensor roles on
|
||||
4, 8, and 16 MiB chip-family plans, including historical 1.25 MiB candidates
|
||||
now built with expanded tables. This includes ESP32-S3, original ESP32, and
|
||||
ESP32-C3 boards with an evidenced old default layout. `--list-boards` is the
|
||||
authoritative list of exact targets; specialty ESP-NOW/observer aliases are
|
||||
separate identities and are not packaged under their old IDs. With Wi-Fi OTA
|
||||
they can migrate to the
|
||||
same physical board's canonical role image; their feature settings may need
|
||||
to be recreated. The LoRa route still requires an exact old target ID. The
|
||||
they can migrate to the same physical board's canonical role image. The Wi-Fi
|
||||
bridge verifies and restores its supported saved settings; other settings may
|
||||
need to be recreated. The LoRa route still requires an exact old target ID. The
|
||||
catalog omits boards with no evidenced legacy default-layout build, such as
|
||||
boards introduced with `min_spiffs.csv` or another non-1.25 MiB layout. Some
|
||||
listed boards use larger slots today but had historical default-layout builds.
|
||||
A listed package is usable only when the installed old image has a working
|
||||
Wi-Fi updater and the bridge verifies its live source table. Menu inclusion
|
||||
does **not** override either check. The only LoRa
|
||||
bridge packages currently available are the exact-target Heltec V4 and XIAO
|
||||
S3 WIO repeater packages. On 4 MiB flash the expanded app1 overlaps old app1,
|
||||
so the currently safe LoRa receiver-copy method cannot be offered there. A
|
||||
4 MiB Full build offers 1,984 KiB slots, not 4 MiB per slot. A Wi-Fi-only ZIP
|
||||
can omit `full-application.mota` when its application exceeds the current
|
||||
2 KiB x 1024-block LoRa serving limit; `full-application.bin` remains the
|
||||
verified Wi-Fi update image.
|
||||
A listed package is usable only when the installed old image supports the
|
||||
selected updater and the bridge verifies its live source table. Menu
|
||||
inclusion does **not** override either check.
|
||||
|
||||
Every listed board/role has Wi-Fi and LoRa bridge recipes. Heltec V4 and XIAO
|
||||
S3 WIO repeaters retain their board-specific bridge names; the other entries
|
||||
use matching original-ESP32, ESP32-S3, or ESP32-C3 chip-family bridges. LoRa
|
||||
still requires a verified same-target old receiver, and 4 MiB boards require
|
||||
the slot-B-only sequence below. A 4 MiB Full build offers 1,984 KiB per slot.
|
||||
The packager verifies both the Full application's slot fit and its mOTA
|
||||
staging overhead. It supports at most 4096 blocks of 2 KiB for the Full
|
||||
transfer, and records the seeder proof-scratch requirement in the manifest.
|
||||
Older 4 KiB-scratch seeders are limited to 1024 blocks. A package fails if
|
||||
its required Full LoRa transfer cannot fit the supported staging/block limits.
|
||||
|
||||
Other ESP32 boards need a reviewed source updater, chip/flash-size bridge,
|
||||
verified Full image, and board entry before they can be added. The LoRa route
|
||||
@@ -102,15 +192,23 @@ Matching flash capacity alone is not enough to claim a package is safe.
|
||||
Before it touches the partition table, the bridge reads the historical
|
||||
`/identity/_main.id` SPIFFS file. It requires all 96 bytes (32-byte public key
|
||||
followed by its 64-byte private key), stages them in an NVS namespace that is
|
||||
unchanged by both target layouts, then changes the table. On the first boot
|
||||
with the expanded layout it writes the staged value into SPIFFS, reads it back
|
||||
byte-for-byte, and only then clears the NVS staging record and offers the final
|
||||
uploader.
|
||||
unchanged by the source and target layouts, then changes the table. The Wi-Fi
|
||||
bridge and 8/16 MiB LoRa bridge restore the staged value into expanded SPIFFS,
|
||||
read it back byte-for-byte, and only then clear the NVS staging record. On the
|
||||
4 MiB LoRa route, the Full application's first boot performs this restoration.
|
||||
|
||||
The private identity is the guaranteed preservation boundary. The old SPIFFS
|
||||
image is deliberately not raw-copied into an expanded partition: SPIFFS is not
|
||||
safe to resize that way. Other SPIFFS-backed settings can be recreated after
|
||||
the full image boots.
|
||||
The Wi-Fi bridge also stages and verifies its supported saved configuration:
|
||||
node name and preferences, radio profiles, ACL and login replay state, region
|
||||
keys, and selected network/OTA, filtering, display, and telemetry settings.
|
||||
It refuses migration if the saved files cannot fit or verify in NVS. The older
|
||||
LoRa handoff route preserves only the private identity, so saved settings may
|
||||
reset. The two board-specific Partition Expander routes preserve their
|
||||
supported configuration and automatically fetch the exact-target Full image;
|
||||
follow the package README for those routes.
|
||||
|
||||
The old SPIFFS image is deliberately not raw-copied into an expanded partition:
|
||||
SPIFFS is not safe to resize that way. Files outside the supported preservation
|
||||
set, including logs and temporary radio sessions, are not preserved.
|
||||
|
||||
Migration does erase the 4 KiB partition-table sector and the destination app
|
||||
sectors. Expanded SPIFFS can be reformatted. It never issues a full-chip erase.
|
||||
@@ -129,8 +227,9 @@ This means the source app-slot and SPIFFS sizes can vary; the bridge validates
|
||||
the live geometry instead of carrying board-name rules. It also ensures it is
|
||||
running from one source OTA slot. If it arrived in B, it CRC-copies itself to
|
||||
the future A address, selects A in OTA metadata, writes the table, and
|
||||
restarts. It therefore works whether the browser OTA updater placed the bridge
|
||||
in legacy A or B.
|
||||
restarts. The Wi-Fi route therefore works whether the browser OTA updater
|
||||
placed the bridge in legacy A or B. Single-app source layouts cannot use this
|
||||
route.
|
||||
|
||||
Keep power stable while the bridge is replacing the 4 KiB table sector. A loss
|
||||
of power before that point leaves the old table and source intact. After that
|
||||
@@ -154,26 +253,43 @@ bootloader and table offsets for cable flashing, not browser OTA.
|
||||
`xiao_s3_partition_legacy_seed` is a disposable-hardware test image only. It
|
||||
models the legacy table and an identity file; it is not a deployable repeater.
|
||||
|
||||
## LoRa-only migration of a repeater
|
||||
## LoRa-only migration of a listed role
|
||||
|
||||
The exact old repeater must already support MeshCore LoRa mOTA and have a valid
|
||||
The exact old board/role must already support MeshCore LoRa mOTA and have a valid
|
||||
EndF image identity. The LoRa bridge checks the old app's target ID and body
|
||||
hash before any partition-table write; an unsupported or damaged receiver is
|
||||
refused. It works from either legacy A or B. It copies legacy B to the future
|
||||
B address, boots the bridge once to restore the private key in expanded
|
||||
SPIFFS, then boots the preserved old repeater. That repeater can receive the
|
||||
full image into its now-expanded inactive slot.
|
||||
refused. Keep a capable seeder on the old receiver's compiled default radio
|
||||
profile before starting: this route may reset saved radio settings. Verify
|
||||
that the seeder lists the Full image and has the manifest's required proof
|
||||
scratch before installing the first-stage bridge.
|
||||
|
||||
On 8/16 MiB flash the bridge can run from either legacy A or B. It copies
|
||||
legacy B to the future B address, boots the bridge once to restore the private
|
||||
key in expanded SPIFFS, then boots the preserved old receiver. That receiver
|
||||
can fetch the Full image into its now-expanded inactive slot.
|
||||
|
||||
On 4 MiB flash the expanded app1 overlaps old app1, so that copy cannot be
|
||||
used. A verified old same-target receiver must remain in old A and the bridge
|
||||
must be installed in old B; a bridge in A refuses migration. If the old node
|
||||
is running B, first install and boot a working same-target old receiver in A,
|
||||
then install the bridge in B. After expansion the old receiver in A may use a
|
||||
temporary identity. The original key stays staged in NVS until the Full image
|
||||
restores it on first boot. Complete the second update before treating that
|
||||
device as the migrated node.
|
||||
|
||||
The board-specific LoRa bridges are `heltec_v4_partition_migrator_lora_repeater`
|
||||
and `xiao_s3_partition_migrator_lora_repeater`. Build the final repeater with
|
||||
and `xiao_s3_partition_migrator_lora_repeater`; the other listed roles use their
|
||||
configured chip-family LoRa bridges. Build the final application with
|
||||
`bash build.sh build-firmware <target> --full-exact` so its mOTA target ID
|
||||
still matches the old repeater. `scripts/package_esp32_partition_migration.py`
|
||||
still matches the old role. The repeatable recipe already uses this selector.
|
||||
`scripts/package_esp32_partition_migration.py`
|
||||
checks the images, partition tables, hashes and mOTA containers and creates
|
||||
separate Wi-Fi/LoRa migration ZIPs for those two boards. The ZIP README gives
|
||||
one Wi-Fi/LoRa migration ZIP for each selected exact board/role. The ZIP README gives
|
||||
the exact `ota ls`, `ota pull <id> flash`, `ota install` sequence.
|
||||
|
||||
A stock repeater without LoRa mOTA cannot use the LoRa-only route. The
|
||||
Wi-Fi bridge remains the route for that device.
|
||||
An old image without LoRa mOTA cannot use the LoRa-only route. Use its working
|
||||
Wi-Fi updater with a listed package, or USB/serial if no supported updater or
|
||||
package is available.
|
||||
|
||||
## Heltec V4 / V4.3
|
||||
|
||||
|
||||
+31
-14
@@ -1,3 +1,7 @@
|
||||
<!-- meshcore-hosted-doc-link:start -->
|
||||
<p class="meshcore-hosted-doc-link"><a href="https://mikecarper.github.io/MeshCore/espnow_bridge_setup/">View this page on MeshCore Docs</a>.</p>
|
||||
<!-- meshcore-hosted-doc-link:end -->
|
||||
|
||||
# ESP-NOW bridge: simple setup
|
||||
|
||||
Use two ESP32 LoRa boards to join two MeshCore networks with different radio
|
||||
@@ -11,10 +15,13 @@ LoRa network A <--> Repeater A <-- ESP-NOW --> Repeater B <--> LoRa network B
|
||||
|
||||
## 1. Choose the firmware
|
||||
|
||||
Install the exact board's **Full ESP-NOW bridge repeater** image on each board.
|
||||
The target name ends in `_repeater_bridge_espnow`; a standard Heltec V4 uses
|
||||
`heltec_v4_repeater_bridge_espnow`. Full/MQTT and Full Companion images are
|
||||
different choices. See the [firmware picker guide](firmware_picker.md).
|
||||
Install the exact board's **Full repeater** image on each board. ESP-NOW is
|
||||
included as a runtime transport on supported ESP32 Full repeaters. Newly
|
||||
combined ordinary repeaters leave it off until explicitly enabled; that
|
||||
choice is saved across reboot. Boards with measured capacity exceptions keep
|
||||
separate `_repeater_bridge_espnow` images. Historical dedicated target names
|
||||
remain available for direct builds with their original bridge-on defaults.
|
||||
Full Companion is a different role. See the [firmware picker guide](firmware_picker.md).
|
||||
|
||||
Open each board's [USB console](https://flasher.meshcore.io/console) at
|
||||
115200 baud and run:
|
||||
@@ -23,8 +30,11 @@ Open each board's [USB console](https://flasher.meshcore.io/console) at
|
||||
get bridge.type
|
||||
```
|
||||
|
||||
The answer must be `espnow`. `mqtt`, `rs232`, or `none` means that image does
|
||||
not provide the ESP-NOW bridge described here.
|
||||
The answer can be `espnow`, `mqtt+espnow`, `rs232+espnow`, or
|
||||
`mqtt+rs232+espnow`, depending on the image. Confirm the
|
||||
independent ESP-NOW control is present with `get espnow.enabled` and
|
||||
`get espnow.running`. The `espnow` commands select this transport without
|
||||
changing another bridge in a combined image.
|
||||
|
||||
## 2. Set each board's LoRa network
|
||||
|
||||
@@ -33,7 +43,7 @@ the radio settings used by the network on that board's side. Use `Bridge-A`
|
||||
for the first board and `Bridge-B` for the second, or choose your own names.
|
||||
|
||||
```text
|
||||
set bridge.enabled off
|
||||
set espnow.enabled off
|
||||
set name Bridge-A
|
||||
set radio <freq>,<bw>,<sf>,<cr>
|
||||
reboot
|
||||
@@ -60,14 +70,14 @@ case-sensitive secret on each board, using 1-15 characters. Channel `6` is
|
||||
an example; both boards must use the same channel.
|
||||
|
||||
```text
|
||||
set bridge.enabled off
|
||||
set espnow.enabled off
|
||||
set bridge.channel 6
|
||||
set bridge.format wrapped
|
||||
set bridge.secret YourSharedKey
|
||||
set bridge.source tx
|
||||
set bridge.delay 500
|
||||
set repeat on
|
||||
set bridge.enabled on
|
||||
set espnow.enabled on
|
||||
```
|
||||
|
||||
These settings are saved across reboot. Bridge changes apply immediately;
|
||||
@@ -78,15 +88,16 @@ Stopping it first lets you finish configuring both sides before connecting them.
|
||||
|
||||
| Option | Choices and purpose |
|
||||
| --- | --- |
|
||||
| `bridge.enabled` | `on` starts the bridge; `off` stops it and keeps its saved settings. |
|
||||
| `espnow.enabled` | `on` starts ESP-NOW; `off` stops it and keeps its saved settings. Independent of MQTT or RS-232. |
|
||||
| `rs232.enabled` | On combined UART images, starts or stops RS-232 independently of MQTT and ESP-NOW. Use `get rs232.running` to check the live UART. |
|
||||
| `bridge.channel` | The 2.4 GHz channel, 1-13. Match it on both boards. This is separate from the LoRa frequency in `set radio`. |
|
||||
| `bridge.format` | `wrapped` is the default for this two-repeater setup and uses the shared secret. `raw` connects to primary-ESP-NOW nodes such as `Generic_ESPNOW` or `SenseCapIndicator-ESPNow`; it ignores the secret. Both peers must use compatible formats. |
|
||||
| `bridge.secret` | A matching, case-sensitive value of 1-15 characters for `wrapped` mode. It separates bridge groups; it is not strong encryption. MeshCore's own message encryption remains in place. |
|
||||
| `bridge.source` | `tx` crosses packets this repeater transmits on LoRa, after its forwarding decisions; use this for the setup above. `rx` crosses received packets before those decisions and can pass traffic the local repeater would not forward. |
|
||||
| `bridge.delay` | Wait before processing a packet received from the other bridge, in milliseconds. Range: 0-10000. Start with the default `500`. |
|
||||
| `repeat` | `on` enables normal LoRa forwarding; `off` disables forwarding. Keep it `on` on both bridge repeaters. Normal routing, scope, and packet-filter rules still apply. |
|
||||
| `bridge.type` | Read-only firmware capability. It must report `espnow` for this guide. |
|
||||
| `bridge.running` | Read-only runtime state. `on` means the local bridge started; it does not prove the other board is reachable. |
|
||||
| `bridge.type` | Read-only firmware capability, including combined transports. |
|
||||
| `espnow.running` | Read-only ESP-NOW runtime state. `on` means the local bridge started; it does not prove the other board is reachable. |
|
||||
|
||||
This fork uses **`set bridge.source tx`** or **`rx`**. Some upstream guides
|
||||
use `logTx` and `logRx`; those are not the command values used here.
|
||||
@@ -99,8 +110,8 @@ LoRa repeater, see [raw ESP-NOW compatibility](WiFi.md#wifi-companion-setup).
|
||||
Run on both boards:
|
||||
|
||||
```text
|
||||
get bridge.enabled
|
||||
get bridge.running
|
||||
get espnow.enabled
|
||||
get espnow.running
|
||||
get bridge.channel
|
||||
get bridge.format
|
||||
get bridge.secret
|
||||
@@ -127,6 +138,12 @@ If it does not work:
|
||||
and check the networks' forwarding and scope rules.
|
||||
|
||||
A running bridge keeps the device awake, even with device power saving enabled.
|
||||
ESP-NOW shares the WiFi radio: use the connected WiFi network's channel when
|
||||
WiFi is active. Browser OTA pauses ESP-NOW on ordinary non-MQTT repeaters,
|
||||
reports that pause, and resumes saved-enabled ESP-NOW when OTA is stopped.
|
||||
A failed OTA start restores the bridge. The RS-232 transport remains available
|
||||
on combined boards while ESP-NOW is paused.
|
||||
|
||||
For every command's full details, see the [bridge CLI reference](cli_commands.md#bridge-when-bridge-support-is-compiled-in).
|
||||
|
||||
For a broader comparison with a Linux-based bridge, see the
|
||||
|
||||
+7
-3
@@ -1,3 +1,7 @@
|
||||
<!-- meshcore-hosted-doc-link:start -->
|
||||
<p class="meshcore-hosted-doc-link"><a href="https://mikecarper.github.io/MeshCore/faq/">View this page on MeshCore Docs</a>.</p>
|
||||
<!-- meshcore-hosted-doc-link:end -->
|
||||
|
||||
# Frequently Asked Questions
|
||||
|
||||
For this fork's **1.17.1.5 USA Cascade** release, start with
|
||||
@@ -872,13 +876,13 @@ After this bootloader is flashed onto the device, you can trigger an over-the-ai
|
||||
filename).
|
||||
2. From the MeshCore app, log in remotely to the repeater you want to update with admin privileges.
|
||||
3. Go to the Command Line tab, type `start ota` and hit enter.
|
||||
4. You should see `OK` to confirm the repeater device is now in OTA mode.
|
||||
4. The reply confirms WiFi is on and gives the OTA upload URL and network to use.
|
||||
5. If the ESP32 is not already joined to Wi-Fi, `start ota` starts an open hotspot named `MeshCore-OTA` at `192.168.4.1`. If it is already joined, the command reports and uses its router-assigned address instead.
|
||||
6. From your phone or computer, connect to the `MeshCore-OTA` hotspot when one was started.
|
||||
7. Open the URL reported by `start ota` and upload the non-merged bin from the flasher. When the device started `MeshCore-OTA`, the URL is <http://192.168.4.1/update>.
|
||||
|
||||
On an MQTT observer, stop WebConfig before running `start ota`; both servers use
|
||||
HTTP port 80. Likewise, stop the OTA uploader before running `start webconfig`.
|
||||
`start ota` automatically stops WebConfig and reports the change; both servers
|
||||
use HTTP port 80. Stop the OTA uploader before running `start webconfig` again.
|
||||
|
||||
|
||||
### 7.3. Q: Is there a way to lower the chance of a failed OTA device firmware update (DFU)?
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
<!-- meshcore-hosted-doc-link:start -->
|
||||
<p class="meshcore-hosted-doc-link"><a href="https://mikecarper.github.io/MeshCore/filter_tool/">View this page on MeshCore Docs</a>.</p>
|
||||
<!-- meshcore-hosted-doc-link:end -->
|
||||
|
||||
# Filter policy playground
|
||||
|
||||
Build a forwarding policy, see its readable definition, and simulate how a
|
||||
@@ -24,6 +28,20 @@ Start with an example or build a rule, then test the draft against packet facts
|
||||
in the simulator below. The examples draw from
|
||||
[Flood Filtering and Moderation](flood_filtering.md).
|
||||
|
||||
To match every 1-byte path, select **Path hash width → 1 byte**, **type → ANY**,
|
||||
all hops, no channel/input-scope restriction, and no prefix condition (or use
|
||||
the 1-byte example below). This also matches zero-hop packets. The firmware command is
|
||||
`set fr any pb=1 d` (or `set flood.rule type=any hashbytes=1 drop`); it adds a
|
||||
forwarding-drop rule, including for relayed login/admin floods. Direct routes
|
||||
and local delivery remain unaffected. Width-only rules require updated
|
||||
firmware. The simulator's policy definitions remain design-preview text, not
|
||||
installable commands.
|
||||
|
||||
Select **2+ bytes (2 or 3)** to match both larger widths in one rule. The
|
||||
firmware command is `set fr any pb=2+ d`. A rate-limit action on this rule uses
|
||||
one allowance shared by both widths, rather than a separate limit for each.
|
||||
Four-byte paths remain unsupported.
|
||||
|
||||
<div class="filter-tool" data-filter-tool>
|
||||
<div class="filter-management-safety" role="note">
|
||||
<strong>Remote login and direct routes</strong>
|
||||
@@ -68,6 +86,7 @@ in the simulator below. The examples draw from
|
||||
<tr><td><code>channel=</code></td><td><code>*</code> means no channel condition; a name or key authenticates one group channel; <code>hash:XX</code> is an unauthenticated one-byte fallback</td></tr>
|
||||
<tr><td><code>rx.scope=</code></td><td>Original incoming transport scope</td></tr>
|
||||
<tr><td><code>path=</code></td><td>Path prefix, blacklist, bucket, or loop match</td></tr>
|
||||
<tr><td><code>hashbytes=</code></td><td>Encoded path-hash width: 1, 2, 3, or 2+ (2 and 3 bytes), including zero-hop packets</td></tr>
|
||||
<tr><td><code>tempradio=</code></td><td>Temporary-radio state</td></tr>
|
||||
<tr><td><code>do drop</code></td><td>Do not retransmit</td></tr>
|
||||
<tr><td><code>do scope=</code></td><td>Set the outgoing transport scope</td></tr>
|
||||
@@ -82,6 +101,14 @@ in the simulator below. The examples draw from
|
||||
</p>
|
||||
</div>
|
||||
<div class="filter-example-grid">
|
||||
<button type="button" data-example="hash_width">
|
||||
<span>Drop all floods with 1-byte path hashes (including zero hops)</span>
|
||||
<code>when type=any hops=all hashbytes=1 do drop</code>
|
||||
</button>
|
||||
<button type="button" data-example="hash_width_two_plus">
|
||||
<span>Drop all floods with 2- or 3-byte path hashes (including zero hops)</span>
|
||||
<code>when type=any hops=all hashbytes=2+ do drop</code>
|
||||
</button>
|
||||
<button type="button" data-example="channel_scope">
|
||||
<span>Set #BlackHole86 scope on all #rgdata group traffic</span>
|
||||
<code>when type=class:group hops=all channel=#rgdata do scope=#BlackHole86 timing=fast</code>
|
||||
@@ -185,6 +212,16 @@ in the simulator below. The examples draw from
|
||||
<option value="region:west"></option>
|
||||
</datalist>
|
||||
</label>
|
||||
<label>
|
||||
Path hash width
|
||||
<select data-field="hash-bytes">
|
||||
<option value="0">Any width</option>
|
||||
<option value="1">1 byte</option>
|
||||
<option value="2">2 bytes</option>
|
||||
<option value="2+">2+ bytes (2 or 3)</option>
|
||||
<option value="3">3 bytes</option>
|
||||
</select>
|
||||
</label>
|
||||
<label>
|
||||
Path matcher
|
||||
<select data-field="path-kind">
|
||||
@@ -497,6 +534,15 @@ in the simulator below. The examples draw from
|
||||
Channel
|
||||
<input data-packet="channel" value="#rgdata" placeholder="#name, public, hash:A7, key, or blank">
|
||||
</label>
|
||||
<label>
|
||||
Encoded path hash width
|
||||
<select data-packet="hash-bytes">
|
||||
<option value="auto">Infer from path (empty defaults to 1 byte)</option>
|
||||
<option value="1">1 byte</option>
|
||||
<option value="2">2 bytes</option>
|
||||
<option value="3">3 bytes</option>
|
||||
</select>
|
||||
</label>
|
||||
<label>
|
||||
Received pbyte path
|
||||
<input data-packet="path" value="860C,12A4" placeholder="same-width IDs">
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
<!-- meshcore-hosted-doc-link:start -->
|
||||
<p class="meshcore-hosted-doc-link"><a href="https://mikecarper.github.io/MeshCore/firmware_memory_budget/">View this page on MeshCore Docs</a>.</p>
|
||||
<!-- meshcore-hosted-doc-link:end -->
|
||||
|
||||
# Document moved
|
||||
|
||||
<script>window.location.replace(new URL("../research/firmware_memory_budget/" + window.location.hash, window.location.href));</script>
|
||||
|
||||
+126
-45
@@ -1,3 +1,7 @@
|
||||
<!-- meshcore-hosted-doc-link:start -->
|
||||
<p class="meshcore-hosted-doc-link"><a href="https://mikecarper.github.io/MeshCore/firmware_picker/">View this page on MeshCore Docs</a>.</p>
|
||||
<!-- meshcore-hosted-doc-link:end -->
|
||||
|
||||
# Firmware picker
|
||||
|
||||
Each result now includes **Restore your settings after flashing**. Its commands
|
||||
@@ -8,27 +12,46 @@ MQTT and GPS show their app/WebConfig steps instead. See
|
||||
The [USB web console](https://flasher.meshcore.io/console) works with the
|
||||
default ASCII terminal on USB Companion and infrastructure roles.
|
||||
|
||||
Pick the choices in any order. Every selection narrows all the other controls
|
||||
to firmware combinations that were actually built in the current release set.
|
||||
Pick the choices in any order. The controls stay limited to compatible
|
||||
firmware combinations that were actually built in the current release set.
|
||||
The result count stays visible while you scroll through the filters on a phone.
|
||||
Once the exact board and role are selected, the picker skips logging,
|
||||
connection, and profile choices that all select the same firmware. Logging
|
||||
settings move into **Restore your settings after flashing** beneath the files.
|
||||
Choices that select different images remain visible.
|
||||
The optional chip-family filter (ESP32, nRF52, RP2040, or STM32) can narrow the
|
||||
hardware list first. You can skip it: picking hardware fills it in automatically.
|
||||
Chip family and hardware are the only dropdowns. All remaining choices use
|
||||
buttons. **Firmware profile** combines OTA support, Full/standard, and sensor
|
||||
or storage differences in one choice.
|
||||
or storage differences when a choice between different images is needed.
|
||||
|
||||
RAK3401 and RAK4631 repeaters use one unified image per sensor policy for
|
||||
supported internal and external OTA storage. **Full** and **Reduced** choose
|
||||
optional sensor drivers; they do not select a storage module. The unified
|
||||
RAK4631 image also includes RS-232 on UART2, which is recommended for new
|
||||
installations. The unified image reserves UART1 for potentially powered GPS
|
||||
hardware; header-connected W25 flash also occupies UART1 pins. Legacy GPS-free
|
||||
Serial1 downloads remain available through exact-filename search for existing
|
||||
wiring, but are omitted from normal choices when the matching unified sensor
|
||||
profile is available. Ethernet remains a separate RAK4631 hardware image.
|
||||
LoRa OTA still requires the matching
|
||||
OTAFIX bootloader and a package compatible with the installed target.
|
||||
|
||||
The picker reads public release metadata from GitHub. It does not upload device
|
||||
information. Hardware names, target names, and download links come directly
|
||||
from the published firmware assets.
|
||||
|
||||
<div class="firmware-picker" data-firmware-picker data-release-repo="mikecarper/MeshCore" data-controls-url="../_data/firmware_controls.json?v=1.17.1.8-runtime-1" data-bootloaders-url="../_data/bootloader_manifest.json?v=20260929-2" data-share-url="https://mikecarper.github.io/MeshCore/firmware_picker/">
|
||||
<div class="firmware-picker" data-firmware-picker data-release-repo="mikecarper/MeshCore" data-controls-url="../_data/firmware_controls.json?v=1.17.1.9-88c85108" data-bootloaders-url="../_data/bootloader_manifest.json?v=20261002-1" data-share-url="https://mikecarper.github.io/MeshCore/p/">
|
||||
<div class="firmware-picker-intro" role="note">
|
||||
<strong>Current release set</strong>
|
||||
<p data-role="release-set">Loading release information...</p>
|
||||
<p>
|
||||
For a new installation, choose the exact board and role, prefer a
|
||||
<strong>FULL / complete profile</strong> when it is available, and select
|
||||
<strong>Full install / layout migration (merged .bin)</strong>. Narrower profiles
|
||||
remain available when their reduced transport or feature set is intentional.
|
||||
For a new installation, choose the exact board and role, and select
|
||||
<strong>Full install / layout migration (merged .bin)</strong>. Current ESP32
|
||||
releases combine the supported features in Full; use runtime settings to
|
||||
choose the active transports. Where separate profiles remain, prefer
|
||||
<strong>FULL / complete</strong> when it meets your update and hardware
|
||||
requirements. Other chip families retain their qualified profiles.
|
||||
</p>
|
||||
<p>
|
||||
Upgrading an existing ESP32 infrastructure node from a smaller partition
|
||||
@@ -47,10 +70,18 @@ from the published firmware assets.
|
||||
<p class="firmware-picker-order-note">
|
||||
Pick in any order. Use <strong>Any</strong> to clear one choice, or clear
|
||||
everything with the button below. The address bar updates with your choices;
|
||||
copy its URL to reopen or share the same selection.
|
||||
use <strong>Copy link to settings</strong> for a shorter link to the same selection.
|
||||
</p>
|
||||
|
||||
<form class="firmware-picker-form" data-role="form">
|
||||
<div class="firmware-picker-feedback firmware-picker-wide" data-role="filter-feedback" hidden>
|
||||
<div aria-live="polite" aria-atomic="true">
|
||||
<strong data-role="filter-count"></strong>
|
||||
<p data-role="filter-note"></p>
|
||||
</div>
|
||||
<a data-role="view-results" hidden>View files</a>
|
||||
</div>
|
||||
|
||||
<details class="firmware-picker-chip-family firmware-picker-wide">
|
||||
<summary data-role="chip-family-summary">Optional: chip family</summary>
|
||||
<p id="firmware-picker-chip-help">Skip this if you know your board. Picking hardware selects its chip family automatically. Choose Any to clear this filter.</p>
|
||||
@@ -115,7 +146,7 @@ from the published firmware assets.
|
||||
<div class="firmware-picker-form-actions firmware-picker-wide">
|
||||
<button type="reset" data-action="clear" disabled>Clear all choices</button>
|
||||
<button type="button" data-action="copy-link" disabled>Copy link to settings</button>
|
||||
<a data-role="share-link" hidden>Link to these settings</a>
|
||||
<a data-role="share-link" hidden>Short link to these settings</a>
|
||||
<p data-role="link-status" aria-live="polite"></p>
|
||||
</div>
|
||||
</form>
|
||||
@@ -168,9 +199,11 @@ from the published firmware assets.
|
||||
| USB logging / USB-connected MQTT | Node remains attached to a computer over a data-capable USB cable |
|
||||
| Wi-Fi MQTT observer | Firmware connects directly to MQTT over Wi-Fi; this is not USB logging |
|
||||
| USB logging + Wi-Fi MQTT | Unified FULL image sends to both paths; avoid two publishers aimed at the same broker unless messages are deduplicated |
|
||||
| No logging | Normal standalone operation without the dedicated logging/MQTT profile |
|
||||
| No logging | Normal standalone operation with external logging output disabled |
|
||||
| Receives LoRa OTA | Repeater, room server, or sensor image that can stage an exact matching update received over LoRa; any sensor/storage tradeoff appears on the same Firmware profile button |
|
||||
| Receives LoRa OTA - Reduced optional sensors | Compact OTA image that omits selected optional environmental/ranging drivers while retaining generic I2C and supported board peripherals |
|
||||
| Reduced sensors + LoRa OTA | Qualified nRF52 repeater, room server, or sensor build with the reduced optional sensor recipe and LoRa OTA retained |
|
||||
| Full supported sensors + LoRa OTA | Qualified nRF52 build with all sensor drivers supported by that board's full recipe and LoRa OTA retained; does not add support for every possible attached sensor |
|
||||
| LoRa OTA source only | Full Companion serving a host-supplied update to another node without self-installing it |
|
||||
|
||||
Connection and bridge choices depend on the selected role. Companion firmware
|
||||
@@ -179,19 +212,28 @@ Ethernet transports.
|
||||
Normal repeater firmware includes runtime-controlled RS-232 support where the
|
||||
board has room; use `set bridge.enabled on` after configuring `bridge.uart` and
|
||||
`bridge.baud`. The Wio-E5 remains the capacity exception and offers a separate
|
||||
RS-232 image. Choose Wi-Fi MQTT under **Logging / MQTT**; it is an output mode,
|
||||
not a second connection choice. Repeaters may still offer separate ESP-NOW or
|
||||
Ethernet bridge firmware because those paths use different compiled drivers.
|
||||
RS-232 image. Choose Wi-Fi MQTT in the result's saved logging settings when
|
||||
it shares the Full image; separate MQTT images retain a **Logging / MQTT**
|
||||
filter. Full ESP32 LoRa repeaters include runtime
|
||||
ESP-NOW when capacity permits; dedicated ESP-NOW images remain for measured
|
||||
capacity exceptions. Ethernet still needs its exact hardware recipe.
|
||||
|
||||
For commands and option explanations, follow the
|
||||
[ESP-NOW bridge setup guide](espnow_bridge_setup.md).
|
||||
|
||||
An ESP-NOW bridge target keeps LoRa as its primary mesh radio. Expanded ESP32
|
||||
Full repeater and room-server images combine ESP-NOW with WiFi MQTT in the
|
||||
same firmware. MQTT and ESP-NOW are independent runtime transports: use
|
||||
`set mqtt.enabled on|off` for MQTT and `set bridge.enabled on|off` (or
|
||||
`set espnow.enabled on|off`) for ESP-NOW, so either or both can run. Both use
|
||||
one 2.4 GHz radio, so when both are enabled the ESP-NOW bridge channel must
|
||||
Full LoRa repeaters include ESP-NOW, including boards without an MQTT
|
||||
recipe. Existing MQTT-backed Full repeater and room-server images combine
|
||||
both transports. Use `set espnow.enabled on|off` for ESP-NOW and, where
|
||||
supported, `set mqtt.enabled on|off` for MQTT. Newly combined ordinary
|
||||
repeaters start with ESP-NOW off until explicitly enabled, including the first
|
||||
upgrade from unmarked legacy preferences. Later reboots preserve that setting.
|
||||
On combined RS-232 boards without MQTT, `bridge.enabled` controls the UART.
|
||||
Full Heltec V3, WSL3 and RAK3112 MQTT images also retain RS-232;
|
||||
use `set rs232.enabled on|off` and `get rs232.running` for their independent
|
||||
UART. T-LoRa V2.1 keeps two Full choices due to its internal RAM limit:
|
||||
the normal repeater has UART plus ESP-NOW, and the observer has MQTT plus
|
||||
ESP-NOW. MQTT and ESP-NOW use one 2.4 GHz radio, so when both are enabled the ESP-NOW bridge channel must
|
||||
match the connected WiFi access point's fixed channel. ESP-NOW-only mode does
|
||||
not require WiFi credentials. Its runtime
|
||||
`bridge.format` setting chooses the peer protocol: `wrapped` (the
|
||||
@@ -201,48 +243,59 @@ backward-compatible bridge-to-bridge default using `bridge.secret`) or `raw`
|
||||
firmware choice, not two board images. Match `bridge.channel` to the primary
|
||||
nodes' `espnow.channel` before selecting `set bridge.format raw`.
|
||||
For Heltec V4 specifically, `companion_radio_full` is still a LoRa-primary
|
||||
Companion; choose the existing `heltec_v4_repeater_bridge_espnow` firmware to
|
||||
make that board the LoRa/ESP-NOW gateway. Use its exact merged artifact when
|
||||
Companion; choose the combined `heltec_v4_repeater` Full firmware and enable
|
||||
ESP-NOW to make that board the LoRa/ESP-NOW gateway. Use its exact merged artifact when
|
||||
changing roles or partition layouts.
|
||||
|
||||
## Share a selection
|
||||
|
||||
Select any combination of choices, then copy the address bar or use **Copy link
|
||||
to settings**. Opening the link restores those choices after the release
|
||||
Select any combination of choices, then use **Copy link to settings** for a
|
||||
short URL under `https://mikecarper.github.io/MeshCore/p/`. Opening the link
|
||||
restores those choices after the release
|
||||
catalog loads. Partial selections work too, so you can share a board and role
|
||||
while leaving other choices open. **Clear all choices** removes the picker
|
||||
parameters from the URL. Changes update the current browser-history entry
|
||||
without reloading the page or adding a Back-button entry for every click.
|
||||
|
||||
For example, [RAK3401 repeater with internal storage](?hardware=RAK_3401&role=repeater&ota=lora-receiver&variant=no-external-sensors)
|
||||
preselects that board, role, OTA capability, and storage profile.
|
||||
For example, [RAK3401 unified Full repeater](https://mikecarper.github.io/MeshCore/p/?s=1.hRAK_3401.rrepeater.olora-receiver.ffull.vdefault)
|
||||
preselects that board, role, OTA capability, and Full sensor policy. Supported
|
||||
internal or external storage is detected by the unified image.
|
||||
|
||||
The query parameters are `chipFamily`, `hardwareFamily`, `hardware`, `role`,
|
||||
Short links encode only the selected choices in a versioned `s` parameter.
|
||||
Hardware identifiers stay readable, and the link omits redundant hardware-family
|
||||
and automatically selected chip-family values. It keeps profile and install
|
||||
choices explicitly rather than relying on the current catalog's defaults.
|
||||
The address bar also uses the compact selection format; copying it works too.
|
||||
|
||||
Existing long links still work. Their query parameters are `chipFamily`, `hardwareFamily`, `hardware`, `role`,
|
||||
`logging`, `ota`, `mode`, `feature`, `variant`, and `install`. Values use the
|
||||
picker's internal identifiers rather than the displayed labels. One Firmware
|
||||
profile button sets `ota`, `feature`, and `variant` together. Existing links
|
||||
with only some of those choices still work and show their partial selection.
|
||||
The generated
|
||||
link also records `chipAuto` so automatic chip-family selection or an explicit
|
||||
The selection also records when chip-family filtering was chosen manually so
|
||||
automatic chip-family selection or an explicit
|
||||
**Any** choice behaves the same after reopening. Existing section anchors and
|
||||
unrelated query parameters are preserved.
|
||||
|
||||
Links use the current release catalog. If a linked choice is no longer available
|
||||
or conflicts with another choice, the picker identifies it and asks you to
|
||||
review the remaining selections. The downloadable HTML's **Copy link to
|
||||
settings** button creates a public website link that other people can open.
|
||||
settings** button creates a short public website link that other people can open.
|
||||
|
||||
## FULL versus standard
|
||||
|
||||
For a new installation, use the FULL / complete profile when it exists and the
|
||||
board has enough flash. FULL profiles keep the complete supported feature set
|
||||
and CLI. Standard profiles remain useful for boards without a FULL build, for
|
||||
an intentionally narrower transport, or when retaining an existing compatible
|
||||
partition layout.
|
||||
Current ESP32 ordinary releases use one Full / complete image per exact board
|
||||
and role. Full keeps the supported feature set and CLI; runtime switches select
|
||||
USB, Bluetooth, Wi-Fi, logging, and other qualified features. Separate portable,
|
||||
minimal, and single-transport ESP32 images are installation/recovery recipes,
|
||||
not additional ordinary release choices. The documented T-LoRa UART/MQTT RAM
|
||||
split remains two Full images. Other chip families retain their qualified
|
||||
capacity and sensor-profile choices.
|
||||
|
||||
Changing between standard and FULL ESP32 layouts requires a partition migration.
|
||||
Moving an older ESP32 installation to an expanded Full layout requires a
|
||||
partition migration.
|
||||
Use the exact-board merged image over USB, or a supported exact board and role
|
||||
staged migration ZIP from the [utility release](https://github.com/mikecarper/MeshCore/releases/tag/utility-v1.17.1.8-halo-keymind-cascade-dev-9053038f).
|
||||
staged migration ZIP from the [utility release](https://github.com/mikecarper/MeshCore/releases/tag/utility-v1.17.1.9-halo-keymind-cascade-dev-88c85108).
|
||||
Read that ZIP's README before choosing the Wi-Fi or LoRa route. Do not send a
|
||||
loose app-only Full `.bin` directly to an older layout: the running application
|
||||
cannot move its own active and inactive partitions. Run
|
||||
@@ -253,17 +306,26 @@ guess cannot confirm the installed layout. See the
|
||||
that command.
|
||||
|
||||
Current `full-usb-wifi` profiles use one binary for no external output, USB
|
||||
packet logging/USB-connected MQTT, direct WiFi MQTT, or both. The picker shows
|
||||
that same exact binary for each compatible logging choice; select the saved
|
||||
packet logging/USB-connected MQTT, direct WiFi MQTT, or both. The picker skips
|
||||
the logging filter when these modes use the same file; select the saved
|
||||
runtime mode with `set logging.output off|usb|wifi|both`. A FULL logging-fallback
|
||||
profile is listed only when no WiFi MQTT sibling exists; it appears for both
|
||||
the no-output and USB choices because `set usb.logging off|on` is persistent.
|
||||
profile is listed only when no WiFi MQTT sibling exists; it supports both
|
||||
the no-output and USB settings because `set usb.logging off|on` is persistent.
|
||||
On a fresh unified FULL install with no saved SSID, the setup AP and WiFi radio
|
||||
remain available for 30 minutes per boot, then turn off automatically until the
|
||||
next reboot or power cycle. An explicit administrator `start webconfig` remains
|
||||
available as an override. A saved SSID switches to the normal indefinite
|
||||
reconnect behavior instead.
|
||||
|
||||
Fresh ESP32 Full infrastructure defaults to USB logging off. Existing saved
|
||||
logging and device power-saving settings survive an update. Automatic
|
||||
unconfigured boot setup also pauses network-bridge retries until an explicit
|
||||
bridge start, allowing its radio to turn off when the AP closes. Manual
|
||||
WebConfig/WiFi starts preserve an explicitly enabled ESP-NOW bridge, including
|
||||
when the master radio switch restores the selected services. Compact OTA
|
||||
images scan before raising a new AP and verify live driver readiness before
|
||||
reporting the update URL.
|
||||
|
||||
Full Companion profiles use one binary for USB, BLE, ordinary Wi-Fi on ESP32,
|
||||
source-only LoRa OTA, Terminal Chat, optional USB packet logging, and any
|
||||
board-qualified serial or Ethernet Companion transport. Bulk builds therefore
|
||||
@@ -396,8 +458,13 @@ is available. ESP32 and other chip families do not show this section. The web
|
||||
picker checks the latest stable OTAFIX release; the downloadable picker
|
||||
includes the bootloader release named in its saved catalog. If a board has no
|
||||
verified mapping or a file is missing, the picker explains that instead of
|
||||
offering another board's bootloader. MeshTower internal and microSD remain
|
||||
separate; RAK3401 and RAK4631 each use their adaptive storage bootloader.
|
||||
offering another board's bootloader. OTAFIX 2.4.11 publishes a combined
|
||||
MeshTower V2 SD/internal image, mapped only to `Heltec_tower_v2_sdcard`.
|
||||
The historical `Heltec_tower_v2` internal-only profile is explicitly retired:
|
||||
move to the normal combined image with a one-time local USB/BLE DFU or SWD
|
||||
installation, not a cross-profile LoRa bootloader update. The picker does not
|
||||
silently substitute its bootloader. RAK3401 and RAK4631 each use their
|
||||
board-specific adaptive storage bootloader.
|
||||
Ikoka Stick, Nano and Handheld plus SolarXiao use the XIAO bootloader;
|
||||
Wio Tracker L1 1W and E-Ink use the L1 bootloader. See the
|
||||
[board coverage audit](https://github.com/mikecarper/Adafruit_nRF52_Bootloader_OTAFIX/blob/feature/ota-delta-apply/docs/meshcore-hardware-coverage.md)
|
||||
@@ -420,6 +487,20 @@ buttons. They separate revisions, display type, expansion kit, radio/PA layout,
|
||||
pin map, and other physical differences without crowding the Hardware dropdown.
|
||||
**Firmware profile** buttons combine OTA support, Full/standard, and choices
|
||||
that need a different image or wiring, such as serial port or external storage.
|
||||
The picker skips this step when all available profile choices lead to the
|
||||
same firmware file.
|
||||
The 1.17.1.9 nRF52 repeater, room-server and sensor releases publish two separately
|
||||
qualified choices: **Reduced sensors + LoRa OTA** and
|
||||
**Full supported sensors + LoRa OTA**, with `-reduced-ota` and `-full-ota` artifact suffixes. Each keeps
|
||||
its exact board/role OTA identity and storage layout; the suffix is not a new
|
||||
radio or partition identity. Labels come from the release's qualified
|
||||
`sensor.profile.reduced` / `sensor.profile.full` metadata and verified LoRa
|
||||
receiver, not from the filename alone. Both profiles remain separate in local
|
||||
and published releases, including boards whose optional sensor set is small
|
||||
enough that the images have little or no size difference. Packaging requires
|
||||
both profiles with matching packaged OTA identity and flash geometry, even
|
||||
when a matrix is resumed or partial publication is requested. A failed build
|
||||
is reported, never presented as a supported download. Companion is unchanged.
|
||||
The legacy `no_external_sensors` profile is labeled **Receives LoRa OTA -
|
||||
Reduced optional sensors** on the same button. On RAK3401 and RAK4631 this
|
||||
compact profile uses internal storage without an external storage board. The
|
||||
@@ -455,7 +536,7 @@ retains its optional dedicated interface `02`.
|
||||
Exact filename search finds compatibility images included in the selected
|
||||
release family. For older releases, open their GitHub release pages.
|
||||
|
||||
The 1.17.1.8 public matrix keeps one ESP32 Full image per board and role.
|
||||
The current public matrix keeps one ESP32 Full image per board and role.
|
||||
Some richer images, including Station G2 Full, retain an observer-named OTA
|
||||
identity. Exact older identities needed by a partition migration are packaged
|
||||
inside that board and role's migration ZIP instead of adding a second ordinary
|
||||
@@ -463,8 +544,8 @@ Full download. A deliberate target-ID override does not make a different
|
||||
partition layout or physical board compatible.
|
||||
|
||||
Some normal ESP32 roles begin in the legacy 1.25 MiB dual-OTA layout but have
|
||||
a Full image with larger slots. The 1.17.1.8 utility release contains 94 exact
|
||||
board and role migration ZIPs. Each package checks its source layout and target
|
||||
a Full image with larger slots. The utility release lists the exact
|
||||
board and role migration ZIPs available for that release. Each package checks its source layout and target
|
||||
identity, contains the bridge and Full application files for its supported
|
||||
routes, and documents what saved data it preserves. Use the package's own Full
|
||||
application after the bridge; do not substitute a loose Full image with a
|
||||
|
||||
@@ -0,0 +1,396 @@
|
||||
---
|
||||
layout: default
|
||||
title: Private fleet management
|
||||
---
|
||||
<!-- meshcore-hosted-doc-link:start -->
|
||||
<p class="meshcore-hosted-doc-link"><a href="https://mikecarper.github.io/MeshCore/fleet_management/">View this page on MeshCore Docs</a>.</p>
|
||||
<!-- meshcore-hosted-doc-link:end -->
|
||||
|
||||
|
||||
# Private fleet management
|
||||
|
||||
Fleet control lets one publisher send filtering, radio schedules, secondary-radio
|
||||
changes, and clock commands to
|
||||
all enrolled infrastructure nodes, regions, a geographic radius, one node, or a list of targets over a
|
||||
private LoRa channel.
|
||||
It is disabled until configured. It is supported on repeaters and room servers;
|
||||
fixed-size STM32 images omit it because of their flash limit.
|
||||
Both the publishing Companion and receivers need firmware built with fleet-control
|
||||
support; the existing app can use its local Command Line screen.
|
||||
|
||||
## Read-only receivers
|
||||
|
||||
Commands use two separate credentials:
|
||||
|
||||
- A random **128-bit channel key** encrypts the traffic. Receivers need this key
|
||||
to read the private channel.
|
||||
- The publisher's **Ed25519 public key** verifies commands. Only the publisher
|
||||
holds the corresponding private key and can authorize changes.
|
||||
|
||||
Giving a repeater the channel key and publisher public key does **not** give it
|
||||
the publisher's signing key. An ordinary channel message, a copied display name,
|
||||
or possession of the channel key cannot authorize a fleet command. Each command
|
||||
signature also binds the channel, target, sequence, expiry, and exact command.
|
||||
|
||||
This makes management commands read-only for receivers. It does not turn ordinary
|
||||
MeshCore chat into a signed, read-only announcement channel: stock apps do not
|
||||
verify signatures on ordinary channel text. Fleet acknowledgements are convenient
|
||||
channel text, not authenticated inventory evidence. Use existing individually
|
||||
authenticated administration for sensitive confirmation.
|
||||
|
||||
## Enroll nodes
|
||||
|
||||
Use an existing authenticated admin connection or the local console. Generate a
|
||||
random private channel key; for example, `openssl rand -hex 16`. Do not use Public
|
||||
or a predictable hashtag key. Add that same key as a private channel on the
|
||||
publishing Companion, and note its channel index.
|
||||
|
||||
Get the publishing Companion's full public identity key from its device information
|
||||
or existing identity command. On every managed repeater or room server, run:
|
||||
|
||||
```text
|
||||
set fleet.controller <publisher-public-key-64-hex>
|
||||
set fleet.channel <private-channel-key-32-hex>
|
||||
get fleet.channel
|
||||
get fleet.controller
|
||||
get fleet.stats
|
||||
```
|
||||
|
||||
`get fleet.channel` reports enrollment, storage health, a channel hash, and the
|
||||
last accepted sequence. It does not reveal the channel key. `set fleet.channel
|
||||
off` disables reception; `set fleet.controller off` revokes the publisher. Key
|
||||
changes use the existing individually authenticated admin path, never fleet
|
||||
commands.
|
||||
`get fleet.stats` includes `parts`, the number of fragments currently buffered
|
||||
across the two bounded, incomplete two-packet command slots.
|
||||
|
||||
Nodes need a clock set to current UTC. Commands expire after ten minutes and may
|
||||
be at most one minute ahead of a receiver's clock. A bad clock fails closed;
|
||||
correct it through the normal administration path.
|
||||
|
||||
## Send commands from the app
|
||||
|
||||
On the publishing Companion, use the existing local Command Line screen. The
|
||||
Companion signs with its own identity. No publisher private key is copied to a
|
||||
repeater, and no app protocol change is required.
|
||||
|
||||
Examples use private channel index `3`:
|
||||
|
||||
```text
|
||||
fleet send 3 get radio2
|
||||
fleet send 3 set tempradio2 910.5,500,8,5,rxtx,120
|
||||
fleet send 3 set radio2.cross auto
|
||||
fleet send 3 set flood.rule.4 type=grp_txt hops=6+ drop
|
||||
fleet send 3 del flood.rule.4
|
||||
```
|
||||
|
||||
Targets are optional: omitting the target sends to **all enrolled nodes** on the
|
||||
private fleet channel. Explicit `all` still works, for example
|
||||
`fleet send 3 all get radio2`.
|
||||
|
||||
To address particular nodes, insert **8 or 12 hexadecimal characters from the
|
||||
start of a node's public key**, or its **complete 64-character public key**, before
|
||||
the command. Separate multiple targets with semicolons, without spaces. Lengths can
|
||||
be mixed:
|
||||
|
||||
```text
|
||||
fleet send 3 1257aee5 get radio2
|
||||
fleet send 3 1257aee5a754;9abc0123 get radio2
|
||||
fleet send 3 1257aee5;9abc01234567 set radio2.cross auto
|
||||
```
|
||||
|
||||
These are example public-key prefixes; substitute your nodes' actual keys.
|
||||
**A prefix addresses every enrolled node whose public key starts with it.**
|
||||
Use all 64 characters when you need one exact identity, especially in a large
|
||||
fleet. Names and short LoRa path IDs are not accepted as target identities.
|
||||
`all` must appear alone. A node that matches several entries still executes the
|
||||
command only once, and the signature protects the entire list.
|
||||
|
||||
### Target existing regions
|
||||
|
||||
Fleet commands read each receiver's **existing region map**. Two selectors are
|
||||
available, and they can be mixed with public-key targets:
|
||||
|
||||
| Selector | Which receivers execute |
|
||||
| --- | --- |
|
||||
| `sea` or `region:sea` | Receivers with an exact `sea` entry in their configured region list |
|
||||
| `home:sea` | Receivers whose home region is `sea`, or whose home has `sea` as a parent or ancestor |
|
||||
|
||||
```text
|
||||
fleet send 3 sea get radio2
|
||||
fleet send 3 sea;pdx clock sync
|
||||
fleet send 3 region:sea set radio2.cross auto
|
||||
fleet send 3 home:sea get tempradio2
|
||||
fleet send 3 home:sea;home:pdx clock sync
|
||||
fleet send 3 sea;home:pdx;1257aee5 get radio2
|
||||
```
|
||||
|
||||
The semicolon-separated list is an **OR**: a receiver that matches any entry executes
|
||||
once, even if it matches several entries. Names match exactly and are case
|
||||
sensitive; `sea` does not match `seattle`. One leading `#` is an alias, as in the
|
||||
existing region commands. `$` remains significant for private region names.
|
||||
Names may contain up to 30 printable ASCII bytes using region-name characters.
|
||||
Use `region:` or `home:` explicitly for names with 8 or 12 characters, names
|
||||
made entirely of hexadecimal characters, or reserved command words such as
|
||||
`get`, `set`, `del`, `time` and `clock`. Bare `all` still means the entire fleet;
|
||||
`region:all` selects a region actually named `all`.
|
||||
|
||||
Inspect the existing configuration with `region` and `region home`. To choose a
|
||||
home from regions already defined on that receiver, use:
|
||||
|
||||
```text
|
||||
region home sea
|
||||
region save
|
||||
```
|
||||
|
||||
List matching includes a configured region even when forwarding for it is
|
||||
denied. Home matching follows the selected home's ancestry; a wildcard or unset
|
||||
home matches no named home target. The default transmit scope and the incoming
|
||||
packet's scope do not determine target membership. Region selection is checked
|
||||
against the live map when the complete command is received.
|
||||
|
||||
Targets select which nodes execute. Delivery still follows the Companion's
|
||||
channel scope and the intervening forwarding rules. Unknown names match no
|
||||
receivers and never become a whole-fleet request.
|
||||
|
||||
### Target a GPS center and radius
|
||||
|
||||
Use `gps:latitude,longitude:radius-km` to select receivers within a circle. For
|
||||
example, this selects nodes within **25 km** of the given Seattle coordinates:
|
||||
|
||||
```text
|
||||
fleet send 3 gps:47.6062,-122.3321:25 get radio2
|
||||
fleet send 3 gps:47.6062,-122.3321:25 set radio2.cross auto
|
||||
fleet send 3 sea;home:pdx;gps:47.6062,-122.3321:25;1257aee5 clock sync
|
||||
```
|
||||
|
||||
GPS, region, home and key selectors can share the same semicolon-separated list.
|
||||
They use the same OR rule: any matching entry selects the node, and the command
|
||||
executes once. The center, radius and complete target list are signed.
|
||||
The comma is only for the coordinate pair; `;` is the only target-list separator.
|
||||
|
||||
Coordinates use ordinary decimal degrees, with latitude from -90 to 90 and
|
||||
longitude from -180 to 180, and at most six decimal places. Radius is in
|
||||
kilometers, from **0.001 to 20050**, with at most three decimal places. For
|
||||
example, `0.5` means 500 meters. Scientific notation, excess decimal places,
|
||||
missing fields and out-of-range values are rejected without sending. Distance
|
||||
uses the great-circle calculation, including across the date line and near the
|
||||
poles; a node on the radius boundary is included.
|
||||
|
||||
Each receiver prefers its configured `lat` and `lon`. A stationary radio can
|
||||
have a fixed position even without GPS hardware; configure it through its
|
||||
normal administration connection:
|
||||
|
||||
```text
|
||||
set lat 47.6062
|
||||
set lon -122.3321
|
||||
get lat
|
||||
get lon
|
||||
```
|
||||
|
||||
When both configured coordinates are zero, the receiver can use an existing
|
||||
valid GPS cache no older than 12 hours. Matching never wakes GPS, takes a UART
|
||||
or requests telemetry. A missing, expired, invalid or default `(0,0)` position
|
||||
matches no GPS selector; it can still match another entry in the list. Invalid
|
||||
nonzero configured coordinates are rejected rather than falling back to GPS.
|
||||
For a stationary radio that lets GPS sleep for longer than 12 hours, configure
|
||||
its fixed coordinates.
|
||||
|
||||
Private fleet matching uses this position even when location is hidden from
|
||||
public adverts. It reads the position when the complete command arrives,
|
||||
including after the second fragment of a two-packet command, so a moving node
|
||||
is selected using its position at completion.
|
||||
|
||||
On air, full keys use a 128-bit SHA-256 digest; prefixes use their original
|
||||
public-key bytes. Whole-fleet commands use the compact FMC2 format with no target
|
||||
records, whether the CLI target is omitted or explicitly `all`. This reduces the
|
||||
header from 29 to 14 bytes. A single complete key uses the smaller fixed-target
|
||||
FMC1 format. Region, home and GPS selectors use separate signed record types in FMC2.
|
||||
Separate fleets use separate channel keys.
|
||||
|
||||
Only local commands on the publishing Companion can originate fleet sends. A
|
||||
remote CLI command cannot turn that Companion into a multicast signing proxy.
|
||||
An `OK` means the Companion queued the command, not that every receiver applied
|
||||
it. Wait for acknowledgements and confirm the result on important nodes.
|
||||
|
||||
Use at most one new command per second. The publishing Companion remembers its
|
||||
last queued sequence for the current boot; its clock supplies the next sequence.
|
||||
Each receiving node reserves the Unix-second sequence in durable storage
|
||||
**before** dispatch. That receiver never executes the same sequence twice,
|
||||
including after reboot or a lost reply. Retry an uncertain command with a fresh
|
||||
sequence; the operation should be chosen to be idempotent.
|
||||
|
||||
## Supported controls
|
||||
|
||||
The Companion automatically uses **one or two LoRa packets**, depending on the
|
||||
signed command's size. Whole-fleet commands fit **87 ASCII bytes** in one packet
|
||||
or **230 bytes** across two. Targets consume some of that allowance:
|
||||
|
||||
| Targets | One packet: command bytes | Two packets: command bytes |
|
||||
| --- | ---: | ---: |
|
||||
| Omitted or `all` | 87 | 230 |
|
||||
| One 8-character public-key prefix | 82 | 225 |
|
||||
| One 12-character public-key prefix | 80 | 223 |
|
||||
| One complete 64-character public key | 72 | 215 |
|
||||
| One three-character region or home name, such as `sea` | 82 | 225 |
|
||||
| Two three-character region or home names, such as `sea;pdx` | 77 | 220 |
|
||||
| One GPS center and radius | 74 | 217 |
|
||||
| Two 8-character public-key prefixes | 77 | 220 |
|
||||
| Two complete public keys | 53 | 196 |
|
||||
|
||||
Each prefix in a list uses 5 or 7 bytes; each complete key in a list uses 17.
|
||||
Each region or home selector uses two bytes plus its canonical name length.
|
||||
Each GPS selector uses 13 bytes: its type, latitude and longitude in millionths
|
||||
of a degree, and radius in meters.
|
||||
A single complete key uses the smaller fixed-target format with a 29-byte header,
|
||||
so its limits come from packet space. Lists are capped at 17 entries and 86 bytes
|
||||
of encoded target records; for example, at most six GPS selectors fit. Requests
|
||||
exceeding their target-specific budget are rejected before signing or sending,
|
||||
with no partial target list.
|
||||
|
||||
The existing app/console input limit also applies. The app's normal 176-byte
|
||||
command frame fits **161 command bytes** after `fleet send 3 `; a correlated
|
||||
request uses three more bytes, and explicit targets reduce the space further.
|
||||
The shorter USB rescue console has an 80-byte input buffer. The 230-byte limit
|
||||
is the fleet protocol's capacity, rather than a promise that every console can
|
||||
enter that much text. Oversized or invalid lists are rejected as a whole,
|
||||
without sending or truncating any targets.
|
||||
|
||||
### Two-packet delivery
|
||||
|
||||
Longer commands keep **one 64-byte signature over the entire command**, including
|
||||
its channel, targets, sequence and expiry. The signed envelope is split into two
|
||||
`FMP1` fragments. Each fragment has an 11-byte collection header and up to 154
|
||||
envelope bytes, within the existing 165-byte group-data limit. Together they can
|
||||
carry a 308-byte signed envelope. No third packet is used.
|
||||
|
||||
The Companion reserves both packets before sending. If either cannot be queued,
|
||||
it cancels the queued part and any radio copy, reports an error, and leaves the
|
||||
sequence available for a retry. Successful queueing still does not guarantee
|
||||
delivery over LoRa.
|
||||
|
||||
A receiver accepts either arrival order and identical duplicates. It executes
|
||||
and acknowledges **only after both parts arrive and the complete signature,
|
||||
target, clock and replay checks pass**. Missing or altered parts cannot execute
|
||||
a partial command. Two bounded assemblies are kept for up to five minutes
|
||||
from their respective first parts; duplicates do not extend those deadlines.
|
||||
Unverified fragment sequences cannot grant priority over another transfer. A
|
||||
third transfer replaces the oldest collection. A complete, valid signed command
|
||||
clears the partial collections, and durable replay checks remain authoritative.
|
||||
Reboot or changing enrollment discards the partial transfers.
|
||||
|
||||
| Control | Fleet commands |
|
||||
| --- | --- |
|
||||
| Saved second profile | `get radio2`, `set radio2 <tuple|off>` |
|
||||
| Temporary second profile | `get tempradio2`, `set tempradio2 <tuple|off>` |
|
||||
| Scheduled primary profile | `get/set/del radioat`, `get/set/del tempradioat` |
|
||||
| Scheduled second profile | `get/set/del radioat2`, `get/set/del tempradioat2` |
|
||||
| UTC clock | `clock`, `clock sync`, `time <epoch-seconds>` |
|
||||
| Profile diagnostics | `get radio2.status`, `get radio2.timing` |
|
||||
| Crossover | `get radio2.cross`, `set radio2.cross <auto|on|off>` |
|
||||
| Filter rules | `get/set/del flood.filter[.n]`, `flood.rule[.n]` |
|
||||
| Path blacklist | `get/set/del flood.filter.blacklist[.n]` |
|
||||
| Channel moderation | `get/set/del flood.moderation[.n]` |
|
||||
| Scope policy | `get/set/del flood.channel.scope[.n]`, `flood.channel.scope.require[.n]` |
|
||||
| Group-data forwarding | `get/set flood.channel.data`, `flood.channel.data.hops` |
|
||||
| Flood hop limits | `get/set flood.max`, `flood.max.unscoped`, `flood.max.advert` |
|
||||
|
||||
Each role still checks whether it supports the requested setting. Immediate
|
||||
primary radio changes, identity export, passwords, ACL edits, filesystem operations, firmware
|
||||
installation, reboot, and unrestricted CLI are outside fleet capability.
|
||||
|
||||
## Schedule radio changes and set clocks
|
||||
|
||||
Use the normal schedule syntax, with UTC epoch seconds or `+N` whole minutes.
|
||||
Primary schedules are supported by repeaters; a role without a primary scheduler
|
||||
replies that the command is unsupported. Second-profile schedules use `rx` or
|
||||
`rxtx` after the coding rate:
|
||||
|
||||
```text
|
||||
fleet send 3 clock
|
||||
fleet send 3 clock sync
|
||||
fleet send 3 set radioat 910.5,500,8,5,+5
|
||||
fleet send 3 set tempradioat 910.5,500,8,5,+5,+15
|
||||
fleet send 3 set radioat2 910.5,500,8,5,rxtx,+5
|
||||
fleet send 3 set tempradioat2 910.5,500,8,5,rxtx,+5,+15
|
||||
fleet send 3 1257aee5 get tempradioat2 1
|
||||
fleet send 3 1257aee5 del tempradioat2 all
|
||||
```
|
||||
|
||||
Both relative endpoints use the receiver's clock at command acceptance. For a
|
||||
coordinated fleet switch, use the same absolute UTC start/end timestamps on all
|
||||
nodes. An optional final preamble value or `auto` uses the existing radio syntax.
|
||||
Schedule capacity, radio-range validation, and overlap checks remain unchanged;
|
||||
pending schedules still do not survive reboot.
|
||||
|
||||
`clock sync` advances a receiver to the publishing Companion's signed timestamp
|
||||
plus one second. `time <epoch-seconds>` sets an explicit UTC timestamp. Both use
|
||||
the existing forward-only clock rule. Initial setup or a clock far out of sync
|
||||
still requires local or individually authenticated clock correction: fleet clock
|
||||
commands retain the signature, expiry, replay, and maximum-clock-lead checks.
|
||||
|
||||
Radio mutations and schedule edits keep the old state until their exact acknowledgement
|
||||
packets drain, with at least one successful physical transmission. Queue failure,
|
||||
failed transmissions, or an acknowledgement timeout cancels the staged mutation.
|
||||
Temporary-radio leases and scheduled windows retain their original monotonic end time; waiting for an
|
||||
acknowledgement does not lengthen them.
|
||||
|
||||
Exact single-node acknowledgements use 0.5-1.5 seconds of random delay; prefix,
|
||||
multiple-target, region, GPS and whole-fleet replies use 0.5-10 seconds, including
|
||||
radio mutations. Queuing, airtime and forwarding can add delivery time.
|
||||
This reduces simultaneous responses but is not a reliable
|
||||
delivery protocol for an arbitrarily large fleet. Use individual requests for
|
||||
confirmation and divide very large fleets into separately keyed channels.
|
||||
|
||||
## Filtering and recovery
|
||||
|
||||
Local fleet reception happens before a node's forwarding decision, so that node
|
||||
can still process an authenticated command when a filter or `repeat off` blocks
|
||||
forwarding. **Intermediate nodes can still block delivery to downstream nodes.**
|
||||
Management traffic has no automatic exemption from forwarding filters. Preserve
|
||||
an appropriate group-data/channel forwarding rule and an existing direct-admin
|
||||
recovery path before changing network-wide filters. Primary-radio schedules can
|
||||
move nodes off the normal network; use a temporary window or a planned recovery
|
||||
path for experiments. A forwarding policy can also partition the fleet.
|
||||
|
||||
The receiver is allocated lazily when fleet settings are present or queried, with
|
||||
one bounded mailbox and two 308-byte fragment assemblies, plus bounded metadata.
|
||||
Cryptographic work and storage writes occur after the receive stack unwinds.
|
||||
Signature-validation attempts are bounded after private-channel MAC verification,
|
||||
complete framing, replay, target, time and command checks. Keyless matching-hash
|
||||
junk, already-used requests, other nodes' requests and incomplete fragments do not
|
||||
spend that signature budget. A corrupt or unreadable fleet store denies commands
|
||||
rather than discarding its replay state.
|
||||
|
||||
## Ideas for packet-abuse containment
|
||||
|
||||
A packet-count chart can show a burst, but cannot establish intent, an authenticated
|
||||
source, or the airtime consumed. A large Trace spike deserves investigation;
|
||||
MeshCore already rejects flood-form Trace and Control packets. Valid direct Trace
|
||||
requests need different treatment from flood forwarding rules.
|
||||
|
||||
Useful next additions, rather than features already implemented here:
|
||||
|
||||
- **Temporary containment with automatic rollback:** deploy a drop/rate policy
|
||||
for a bounded period and restore the previous policy unless confirmed.
|
||||
- **Direct-request rate limits:** separately bound Trace, anonymous requests,
|
||||
discovery, login, and expensive response generation without breaking legitimate
|
||||
management, ACKs, or ongoing OTA exchanges.
|
||||
- **Policy preview and revisions:** validate complete rule bundles, detect board
|
||||
limitations, apply atomically, and report the active revision before rollout.
|
||||
- **Better evidence:** report per-type receive/drop counts, airtime, queue pressure,
|
||||
invalid signatures, duplicate rate, and radio recoveries. Truncated path IDs
|
||||
remain observations, not authenticated attacker identities.
|
||||
- **Authenticated inventory:** stagger signed status replies with firmware version,
|
||||
uptime/reset reason, battery, memory/storage headroom, and radio errors.
|
||||
- **Maintenance windows:** coordinated secondary-profile experiments and OTA
|
||||
readiness checks, keeping existing firmware-signing policy intact.
|
||||
- **Per-controller roles and revocation:** multiple public verification keys with
|
||||
separate filter-only/radio permissions and independent replay state.
|
||||
|
||||
Start with observation, then a narrow temporary rule, then verify neighboring
|
||||
nodes still communicate. Software filtering can reduce forwarding and response
|
||||
amplification; it cannot recover airtime already occupied by an RF transmitter.
|
||||
|
||||
See [flood filtering](flood_filtering.md), [radio profiles](radio_profiles.md), and
|
||||
[ACL administration](cli_commands.md#acl).
|
||||
+33
-1
@@ -1,3 +1,7 @@
|
||||
<!-- meshcore-hosted-doc-link:start -->
|
||||
<p class="meshcore-hosted-doc-link"><a href="https://mikecarper.github.io/MeshCore/flood_filtering/">View this page on MeshCore Docs</a>.</p>
|
||||
<!-- meshcore-hosted-doc-link:end -->
|
||||
|
||||
# Flood Filtering and Moderation
|
||||
|
||||
This guide explains the Keymind forwarding filters. Repeaters expose the full
|
||||
@@ -445,7 +449,7 @@ The extended form is:
|
||||
|
||||
```text
|
||||
set flood.rule[.<slot>] type=<type> [mode=radio|bridge|cross|bridge,cross]
|
||||
[hops=<range>] [channel=<channel>]
|
||||
[hops=<range>] [channel=<channel>] [hashbytes=any|1|2|2+|3]
|
||||
[prefix=<ID[,ID...]>] [in=<input-scope>] <action> [rate=<N>/min]
|
||||
[priority=<0-255>] [stop] [tx=fast|slow] [suspend=tempradio]
|
||||
get flood.rule
|
||||
@@ -495,6 +499,14 @@ Match fields:
|
||||
collision, while `public` verifies and decrypts with the Public key. This is
|
||||
channel authentication, not sender authentication: the group MAC is two
|
||||
bytes and the Public key is shared.
|
||||
- `hashbytes=any|1|2|2+|3` matches the encoded width of each path hash, independent
|
||||
of its value or the hop count. Omitted or `any` means any width; `*` is an
|
||||
alias for `any`. It includes zero-hop packets carrying the selected width.
|
||||
`2+` matches the supported 2- and 3-byte widths in one rule; a rate limit is
|
||||
shared across both widths. Four-byte path hashes are reserved and unsupported.
|
||||
Combine this with other match fields as needed; a `prefix=` must satisfy the
|
||||
width selector. With `2+`, a 2- or 3-byte prefix narrows the rule to that exact
|
||||
width, and saved output shows the prefix. The compact alias is `pb=`.
|
||||
- `prefix=` is a source-path prefix containing one to three comma-separated
|
||||
pbyte IDs. Every ID must use the packet's pbyte width: 2, 4, or 6 hex
|
||||
characters for 1-, 2-, or 3-byte paths. Order matters and matching begins at
|
||||
@@ -684,6 +696,7 @@ set fr.3 any c=#wardriving m=bc d
|
||||
| `mode=radio`, `bridge`, `cross`, `bridge,cross` | `m=r`, `m=b`, `m=c`, `m=bc` | Select the admission path. Omitted means radio; `cb`, `b,c`, and `c,b` also mean both transport paths. |
|
||||
| `channel=...` | `c=...` | Same authenticated name/key or explicit `hash:XX` matcher. |
|
||||
| `prefix=...` | `p=...` | Ordered path prefix. |
|
||||
| `hashbytes=any`, `1`, `2`, `2+`, or `3` | `pb=*`, `1`, `2`, `2+`, or `3` | Encoded path-hash width, even with zero hops; `2+` matches 2 and 3. |
|
||||
| `path=blacklist` | `p=bl` | Shared unordered blacklist; repeater only. |
|
||||
| `in=any`, `none`, `scoped`, `allowed`, `unknown` | `i=*`, `i=n`, `i=s`, `i=a`, `i=u` | Incoming scope condition. |
|
||||
| `in=scope:name`, `in=region:name` | `i=s:name`, `i=r:name` | Match a specific incoming scope or region. |
|
||||
@@ -698,6 +711,25 @@ The `=` distinguishes `s=name` (scope) from `s` (stop), and `r=name`
|
||||
(region) from `r` (retry). Short forms keep the same action restrictions;
|
||||
for example, `m=bc r` is rejected because transport rules cannot enable retries.
|
||||
|
||||
### Filter every 1-byte flood path
|
||||
|
||||
```text
|
||||
set flood.rule type=any hashbytes=1 drop
|
||||
```
|
||||
|
||||
The equivalent short command is `set fr any pb=1 d`. This adds a rule in a free
|
||||
slot without replacing the default rules. It stops this node forwarding all
|
||||
1-byte flood packets, including advertisements and relayed login/admin traffic;
|
||||
direct routes and local delivery are unchanged. Use a narrower `type=` or
|
||||
`channel=` if those floods must remain reachable. Use `hashbytes=2` or `3` for
|
||||
an exact width, or `set fr any pb=2+ d` to drop both larger supported widths with
|
||||
one rule. Use `get fr.<slot>` to copy the saved command.
|
||||
|
||||
Width-only rules require updated firmware. Existing rules remain unchanged and
|
||||
the table uses no additional RAM or storage. Before downgrading to firmware
|
||||
without this selector, remove width-only rules: its older reader considers them
|
||||
invalid and can reset the rule table to defaults.
|
||||
|
||||
```text
|
||||
get fr.3
|
||||
```
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
<!-- meshcore-hosted-doc-link:start -->
|
||||
<p class="meshcore-hosted-doc-link"><a href="https://mikecarper.github.io/MeshCore/four_fixed_tx_dwell_validation/">View this page on MeshCore Docs</a>.</p>
|
||||
<!-- meshcore-hosted-doc-link:end -->
|
||||
|
||||
# Document moved
|
||||
|
||||
<script>window.location.replace(new URL("../test-results/four_fixed_tx_dwell_validation/" + window.location.hash, window.location.href));</script>
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
<!-- meshcore-hosted-doc-link:start -->
|
||||
<p class="meshcore-hosted-doc-link"><a href="https://mikecarper.github.io/MeshCore/four_fixed_tx_single_pass_validation/">View this page on MeshCore Docs</a>.</p>
|
||||
<!-- meshcore-hosted-doc-link:end -->
|
||||
|
||||
# Document moved
|
||||
|
||||
<script>window.location.replace(new URL("../test-results/four_fixed_tx_single_pass_validation/" + window.location.hash, window.location.href));</script>
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
<!-- meshcore-hosted-doc-link:start -->
|
||||
<p class="meshcore-hosted-doc-link"><a href="https://mikecarper.github.io/MeshCore/full_companion_features/">View this page on MeshCore Docs</a>.</p>
|
||||
<!-- meshcore-hosted-doc-link:end -->
|
||||
|
||||
# Full Companion: turn features on and off
|
||||
|
||||
For side-by-side Companion and infrastructure commands, see
|
||||
@@ -51,6 +55,7 @@ editor does not necessarily forward them.
|
||||
| External FEM TX gain | `set radio.fem.txgain on` | `set radio.fem.txgain off` | Immediately; saved; controllable PA only |
|
||||
| ESP32 USB packet/debug logging | For 1.17.1.5: `set powersaving off`, then `set usb.logging on` | `set usb.logging off` | Immediately; saved; logging owns the USB terminal |
|
||||
| nRF52 separate USB logging port | `set usb.logging on reboot` | `set usb.logging off reboot` | Saves and reboots to add/remove the second USB port |
|
||||
| nRF52 one-port packet stream | `set usb.logging stream reboot` | `set usb.logging off` | Saved; CLI and packets share the primary port for stock serial bridges |
|
||||
| ESP32 browser settings | `set webui on` | `set webui off` | Saved; starts/stops WebConfig |
|
||||
| ESP32 temporary setup portal | `start webconfig ap` | `stop webconfig` | This session; opens a setup network/QR where available |
|
||||
| ESP32 WiFi firmware uploader | `start ota` or `start ota ap` | `stop ota` | This session; only usable with two application slots |
|
||||
@@ -100,9 +105,16 @@ WiFi modem sleep is independent of device power saving. Use
|
||||
state with `get wifi.powersave`. `max` is available only where the firmware's
|
||||
radio coexistence policy permits it.
|
||||
|
||||
Most Full Companions provide BLE alongside their other transports. There is
|
||||
no universal `set bluetooth off` command. SenseCAP Indicator selects one
|
||||
secondary wireless transport per boot:
|
||||
Most Full Companions provide BLE alongside their other transports. On
|
||||
Bluetooth-capable ESP32 and nRF52 Companions, `get bluetooth` reports the
|
||||
running state and `set bluetooth on|off` saves the service preference.
|
||||
Plain `off` requires another active management connection;
|
||||
`set bluetooth off force` explicitly permits disconnecting the only client.
|
||||
Reboot does not undo a saved-off preference. See
|
||||
[Bluetooth controls](cli_commands.md#turn-bluetooth-on-or-off-companion) for
|
||||
reply draining, connection checks, and unavailable boot modes.
|
||||
|
||||
SenseCAP Indicator selects one secondary wireless transport per boot:
|
||||
|
||||
```text
|
||||
set companion.transport wifi
|
||||
@@ -118,6 +130,12 @@ The Companion app's `gps=1` / `gps=0` custom setting controls the same GPS.
|
||||
Only boards with a compiled GPS provider expose this setting. Sharing location
|
||||
with contacts is a separate setting.
|
||||
|
||||
Use `set gps.sync.interval <hours>` to save an automatic clock-sync interval of
|
||||
1-336 hours (two weeks); `get gps.sync.interval` reports it. Larger inputs are
|
||||
capped at 336 and the reply shows the applied value. Existing GPS timings are
|
||||
unchanged until configured. This controls clock synchronization, not the
|
||||
position-update `gps_interval` or location-sharing policy.
|
||||
|
||||
When the exact Full image includes MQTT, use WebConfig's MQTT cards or the
|
||||
same CLI settings used by infrastructure, such as `set mqtt1.preset custom`
|
||||
and `set mqtt1.server broker.example.com`. `set mqtt.enabled on|off` controls
|
||||
@@ -181,8 +199,9 @@ For ESP32 with two application slots, `start ota` returns the WiFi uploader
|
||||
URL, normally `http://DEVICE_IP:8080/update`. `start ota ap` explicitly opens
|
||||
`MeshCore-OTA`; join it and use the returned URL. Upload the exact board's
|
||||
application `.bin`. The device reboots when the upload succeeds. Use
|
||||
`stop ota` to close an unused uploader. Port 8080 keeps WebConfig on port 80
|
||||
available. Use a trusted local network or a temporary setup network.
|
||||
`stop ota` to close an unused uploader. Starting OTA enables WiFi as needed,
|
||||
stops WebConfig automatically, and reports the network and URL to use. Use a
|
||||
trusted local network or a temporary setup network.
|
||||
|
||||
The 4 MB Full layouts and T-Beam 1W Full use a single application slot and
|
||||
require USB; the command reports that limitation. The artifact's
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
<!-- meshcore-hosted-doc-link:start -->
|
||||
<p class="meshcore-hosted-doc-link"><a href="https://mikecarper.github.io/MeshCore/full_companion_usb_switcher/">View this page on MeshCore Docs</a>.</p>
|
||||
<!-- meshcore-hosted-doc-link:end -->
|
||||
|
||||
# Companion USB CLI and binary switcher
|
||||
|
||||
USB Companion uses one primary USB serial interface for two incompatible wire
|
||||
@@ -82,8 +86,9 @@ switched; they remain binary.
|
||||
1. Companion initializes the normal USB Binary Companion interface, then
|
||||
gives its primary stream to the ASCII terminal before normal loop service
|
||||
begins.
|
||||
2. While the prompt has no buffered input, the terminal peeks at the next byte.
|
||||
It does not remove that byte.
|
||||
2. While the prompt has no buffered input, the terminal skips blank CR/LF
|
||||
delimiters and peeks at the next byte without removing it. A queued binary
|
||||
frame after a blank line is detected before any ASCII banner is emitted.
|
||||
3. If the byte is `<`, the terminal temporarily releases the stream and enables
|
||||
the existing `ArduinoSerialInterface` frame parser.
|
||||
4. The parser consumes the original `<`, the two-byte length, and the payload.
|
||||
@@ -94,11 +99,21 @@ switched; they remain binary.
|
||||
6. If no complete frame arrives within one second, the parser state is reset
|
||||
and the ASCII terminal prints a new banner and prompt.
|
||||
|
||||
The switcher checks framing, not client identity. Any syntactically complete
|
||||
Binary Companion frame confirms binary mode; it does not require the first
|
||||
The switcher checks framing, not client identity. A complete Binary Companion
|
||||
frame with 1-176 payload bytes confirms binary mode; it does not require the first
|
||||
command to be `CMD_APP_START` or `CMD_DEVICE_QUERY`. Normal command validation
|
||||
still occurs after the frame parser returns the payload.
|
||||
|
||||
Oversized frames are consumed and discarded, never truncated into executable
|
||||
commands or counted as proof of a Binary client. ASCII, framed Binary, and mOTA
|
||||
input handlers process at most the initial queued-byte count, so a continuously
|
||||
sending host cannot keep the radio loop trapped in one input call.
|
||||
|
||||
ASCII and mOTA reject an entire line containing an embedded NUL or exceeding its
|
||||
line buffer, discarding through the next CR/LF. A valid-looking suffix is not
|
||||
executed. Binary payloads may still contain NUL bytes. The immediate manual
|
||||
terminal-stop token remains an exception to ordinary line-oriented handling.
|
||||
|
||||
The empty-prompt requirement prevents a literal `<` in the middle of a command
|
||||
from silently changing modes. A literal `<` typed as the first character does
|
||||
start a probe, but the terminal returns after the one-second timeout if no
|
||||
@@ -173,6 +188,36 @@ base where supported; RC32 and ESP32-C6 keep their board-required Arduino 3.x
|
||||
platform but follow the same one-TTY policy. No ESP32 Full build enumerates a
|
||||
second CDC interface.
|
||||
|
||||
`usb.logging` is the saved master for USB packet output. `usb.debug` separately
|
||||
saves verbose diagnostic intent and defaults off, including when older
|
||||
preferences do not contain the new field. RAW/RX/TX packet records need only
|
||||
the master; diagnostics need both switches and compiled debug support. Turning
|
||||
the master off preserves debug intent. `get usb.debug` reports that saved
|
||||
choice, not whether USB currently owns a plaintext logging stream.
|
||||
|
||||
Full Companion now emits complete received frames as uppercase ASCII `RAW:`
|
||||
hex, separately from decoded RX summaries and TX summaries. Leave debug off
|
||||
for USB-to-MQTT capture. These are bounded best-effort records; drops and host
|
||||
disconnects can separate a RAW/summary pair. The dedicated nRF52 log CDC cannot
|
||||
answer CLI/identity queries: programs expecting one bidirectional CLI/log port
|
||||
need separate control-port support, not just a change to the serial pathname.
|
||||
Companion supports `get public.key` in the primary terminal or framed CLI;
|
||||
the reply is `> ` followed by 64 uppercase hex characters. This query does not
|
||||
make the dedicated log CDC input-capable or establish full bridge compatibility.
|
||||
Send `get/set usb.debug` to the primary ASCII terminal; changing verbosity
|
||||
needs no USB-interface reboot and does not relax Binary/mOTA ownership rules.
|
||||
|
||||
Current source adds `get usb.watchdog` and saved `set usb.watchdog off|on|auto`, defaulting to
|
||||
Auto until 14 continuous healthy logging-client days qualify it for On.
|
||||
Existing USB stats polls renew a 15-minute client lease; a cable or DTR alone
|
||||
does not count. A lost confirmed client can trigger USB-only recovery after
|
||||
lease expiry plus five minutes. MCU resets additionally require On and a
|
||||
full physical-fault interval, a verified tier save, and clear protocol/update
|
||||
ownership. Active Binary USB and mOTA sessions veto recovery. On dual CDC,
|
||||
only logging CDC1 accepts the bounded stats markers; primary CDC0 stats do not
|
||||
renew that lease. CDC1 still provides no CLI/statistics replies.
|
||||
See [USB logging watchdog](usb_logging_watchdog.md) for status and limitations.
|
||||
|
||||
With logging enabled on a shared port, the stop token is rejected with
|
||||
`ERROR: set usb.logging off before Binary mode`. Turn logging off first; a
|
||||
successful binary-mode acknowledgement must not immediately revert to logging.
|
||||
|
||||
@@ -0,0 +1,179 @@
|
||||
<!-- meshcore-hosted-doc-link:start -->
|
||||
<p class="meshcore-hosted-doc-link"><a href="https://mikecarper.github.io/MeshCore/full_profiles/">View this page on MeshCore Docs</a>.</p>
|
||||
<!-- meshcore-hosted-doc-link:end -->
|
||||
|
||||
# Full firmware profiles
|
||||
|
||||
Full combines the features supported by an exact board and firmware role in
|
||||
one image. Use runtime settings to choose the services you need instead of
|
||||
flashing a separate image for each transport. Hardware, memory, and the
|
||||
installed flash layout still determine which combinations can run.
|
||||
|
||||
**Full is not a runtime role switch.** Choose Companion, Repeater, Room Server,
|
||||
or Sensor in the [firmware picker](firmware_picker.md). Changing roles requires
|
||||
the matching firmware; enabling a bridge does not turn a Companion into a
|
||||
repeater.
|
||||
|
||||
## Choose the board, role, and profile
|
||||
|
||||
| Choice | What Full combines | Important limits |
|
||||
| --- | --- | --- |
|
||||
| Full Companion | The board's supported USB, Bluetooth, ESP32 WiFi, and qualified serial/Ethernet Companion transports, plus terminal chat and mOTA serving | The primary radio and board interfaces remain those of the selected target. Full Companion serves updates to other nodes; it does not install LoRa mOTA on itself. |
|
||||
| ESP32 Full Repeater or Room Server | Standalone operation, supported USB/WiFi services, and the qualified MQTT, ESP-NOW, or RS-232 combination | Not every image contains all three bridges. Read its capabilities and check the compiled controls. |
|
||||
| ESP32 Full Sensor | Sensor operation, supported CLI/logging, and qualified WiFi firmware-update and TCP 5001 seeding services | Current Sensor profiles do not gain the repeater's MQTT/ESP-NOW/RS-232 bridges. |
|
||||
| nRF52 Full supported sensors or Reduced sensors | Two separately qualified Repeater, Room Server, or Sensor recipes with LoRa OTA | These are sensor-driver choices, not ESP32 network features. Full Companion remains a separate profile. |
|
||||
|
||||
Select the exact hardware variant and storage choice. Read the selected
|
||||
image's capability manifest for update methods and requirements; a similar
|
||||
filename is not evidence that another board's image will work.
|
||||
|
||||
## Full Companion: choose the active transports
|
||||
|
||||
Close any application using the USB port, then open the
|
||||
[USB web console](https://flasher.meshcore.io/console) at 115200 baud. A fresh
|
||||
Full Companion starts in the text terminal. If an app already selected Binary
|
||||
Companion mode, send this line and wait for the terminal banner:
|
||||
|
||||
```text
|
||||
+++MESHCORE-TERM-START
|
||||
```
|
||||
|
||||
Run `help`, `board`, and `version` to inspect the device. The same image can
|
||||
serve an app through its compiled transports or provide
|
||||
[terminal chat](terminal_chat_cli.md). USB remains the normal installation
|
||||
and recovery path.
|
||||
|
||||
On a Bluetooth-capable Companion, `get bluetooth` shows the running state;
|
||||
`set bluetooth on` enables the saved Bluetooth preference. On an ESP32
|
||||
profile with WiFi, `start webconfig ap` opens a temporary setup portal where
|
||||
you can save network credentials. MQTT-capable Companions also expose broker
|
||||
settings there; MQTT is not included on every Companion board.
|
||||
|
||||
Most Full Companions can use multiple transports together. **SenseCAP
|
||||
Indicator** selects Bluetooth or infrastructure WiFi per boot using
|
||||
`set companion.transport ble` or `set companion.transport wifi`, followed
|
||||
by `reboot`; USB remains available. Generic ESP-NOW and Indicator ESP-NOW
|
||||
profiles use ESP-NOW as their primary mesh radio. Full does not convert those
|
||||
profiles into LoRa radios.
|
||||
|
||||
USB logging shares the ESP32 Companion's single USB port. Use
|
||||
`set usb.logging off`, then `+++MESHCORE-TERM-STOP` and close the terminal
|
||||
before reconnecting a Binary Companion app. nRF52 Full Companion can instead
|
||||
add a separate logging port with `set usb.logging on reboot`; use its primary
|
||||
port for Companion commands and mOTA serving. A one-port host bridge can use
|
||||
`set usb.logging stream reboot`, which occupies the primary port until logging
|
||||
is disabled. See the [USB mode guide](full_companion_usb_switcher.md).
|
||||
|
||||
## ESP32 infrastructure: enable the compiled services
|
||||
|
||||
Full Repeater and Room Server images retain their normal role while their
|
||||
supported bridges run. A LoRa/ESP-NOW bridge keeps LoRa as its primary radio
|
||||
and exchanges packets with its ESP-NOW peers over 2.4 GHz. MQTT publishes
|
||||
observed traffic to brokers; it does not subscribe to broker messages or
|
||||
inject them into LoRa. RS-232 uses the board's configured UART and pins.
|
||||
|
||||
Check `get bridge.type` on bridge-capable images. Depending on the recipe,
|
||||
it can report combinations such as `mqtt+espnow`, `rs232+espnow`, or
|
||||
`mqtt+rs232+espnow`. Use only the controls compiled into your image:
|
||||
|
||||
| Service | Saved enable/disable control | Check actual operation |
|
||||
| --- | --- | --- |
|
||||
| MQTT | `set mqtt.enabled on` / `off` | `get mqtt.running`, `get mqtt.status` |
|
||||
| ESP-NOW | `set espnow.enabled on` / `off` | `get espnow.running` |
|
||||
| RS-232 | `set rs232.enabled on` / `off` | `get rs232.running` |
|
||||
| USB packet logs | `set usb.logging on` / `off` | `get usb.logging` |
|
||||
|
||||
An enabled preference is not proof that a service started. Missing
|
||||
credentials, a channel mismatch, or a UART conflict can prevent operation.
|
||||
`bridge.enabled` is a historical alias with different meanings between
|
||||
recipes; use the explicit transport controls on combined images.
|
||||
|
||||
For an MQTT-capable image, first follow its role's
|
||||
[WiFi/MQTT setup](WiFi.md#mqtt-observer-setup) and verify the actual connection
|
||||
with `get wifi.status`. Once connected, this example selects a custom broker:
|
||||
|
||||
```text
|
||||
set mqtt.iata SEA
|
||||
set mqtt1.preset custom
|
||||
set mqtt1.server broker.example.com
|
||||
set mqtt1.port 1883
|
||||
set mqtt.enabled on
|
||||
get mqtt.status
|
||||
```
|
||||
|
||||
Replace the sample broker value. Add the broker's username and
|
||||
password if needed. Use the [WiFi/MQTT guide](WiFi.md#how-the-mqtt-bridge-works)
|
||||
for TLS, publication settings, broker-slot limits, and reconnect behavior.
|
||||
Where MQTT and USB logging coexist, `set logging.output off|usb|wifi|both`
|
||||
selects those two outputs together; it does not change LoRa repeating.
|
||||
|
||||
For ESP-NOW, configure both peers with the same channel and compatible
|
||||
`bridge.format`. `wrapped` uses the shared `bridge.secret` for bridge peers;
|
||||
`raw` connects to primary-ESP-NOW MeshCore nodes. When MQTT WiFi and ESP-NOW
|
||||
run together, the ESP-NOW channel must match the access point's fixed WiFi
|
||||
channel. ESP-NOW alone needs no WiFi credentials. Follow the
|
||||
[two-board setup](espnow_bridge_setup.md) for the complete commands.
|
||||
|
||||
For RS-232, stop the UART bridge before changing its configuration:
|
||||
|
||||
```text
|
||||
set rs232.enabled off
|
||||
get bridge.uart
|
||||
set bridge.baud 115200
|
||||
set rs232.enabled on
|
||||
get rs232.running
|
||||
```
|
||||
|
||||
Use the UART and pin assignment supported by your board. GPS can reserve or
|
||||
share a UART; enabling the bridge may be rejected or pause a yieldable GPS
|
||||
provider. Check the board's recipe before changing `bridge.uart`.
|
||||
|
||||
### Capacity and role exceptions
|
||||
|
||||
MKE S3 combines UART and ESP-NOW in its normal Full repeater. Full Heltec V3,
|
||||
WSL3, and RAK3112 MQTT repeaters also retain independent RS-232 alongside
|
||||
MQTT and ESP-NOW. **T-LoRa V2.1** retains two Full repeater choices because
|
||||
of internal RAM limits: UART plus ESP-NOW, or MQTT plus ESP-NOW. Other
|
||||
measured capacity exceptions retain dedicated images. Non-ESP32 boards do
|
||||
not inherit these ESP32 combinations; Wio-E5, for example, keeps a separate
|
||||
RS-232 image.
|
||||
|
||||
## nRF52: Full and Reduced supported sensors
|
||||
|
||||
Qualified nRF52 Repeater, Room Server, and Sensor builds offer **Full
|
||||
supported sensors + LoRa OTA** and **Reduced sensors + LoRa OTA**. Full
|
||||
includes the drivers supported by that board's full recipe; it does not add
|
||||
support for every possible attached sensor. Reduced omits selected optional
|
||||
environmental/ranging drivers while retaining generic I2C and supported board
|
||||
peripherals. Reduced RAK3401/RAK4631 recipes also retain their supported
|
||||
INA219/INA226/INA260/INA3221 voltage/current drivers.
|
||||
|
||||
Both profiles retain their exact board/role OTA identity and storage layout.
|
||||
Choose the matching storage variant and meet its staging/bootloader
|
||||
requirements. These profiles are LoRa OTA receivers; nRF52 Full Companion
|
||||
is an update source and does not acquire WiFi or on-device MQTT. An attached
|
||||
Pi can publish its USB packets using a separate host bridge. See
|
||||
[sensor/storage choices](firmware_picker.md#hardware-and-variant-names).
|
||||
|
||||
## Install or update the matching layout
|
||||
|
||||
Expanded ESP32 Full images need application slots large enough for the image.
|
||||
Use `get storage.layout` on firmware that provides it to inspect the installed
|
||||
partition table. Firmware version alone does not establish the layout; use
|
||||
the [partition lookup guide](esp32_partition_catalog.md) for older firmware.
|
||||
|
||||
For a compatible layout, use the exact board/role application-only image
|
||||
through a supported updater. To change an ESP32 layout, use the matching
|
||||
merged image over USB or an exact board/role staged migration ZIP whose README
|
||||
supports your installed source layout and update route. An app-only image
|
||||
cannot change the partition table, and a merged image is not a browser-OTA
|
||||
application. The picker links the matching migration package where available.
|
||||
|
||||
ESP32 Full Companion WiFi self-update requires two application slots; the
|
||||
single-slot profiles require USB. Infrastructure WiFi/LoRa self-update also
|
||||
depends on the exact image's declared capabilities and installed layout.
|
||||
nRF52 updates use the matching application UF2/DFU package or a qualified
|
||||
LoRa route; they do not use the ESP32 browser procedure. See the
|
||||
[ESP32 WiFi update and partition guide](esp32_wifi_partition_migration.md)
|
||||
for supported staged routes and the [WiFi guide](WiFi.md) for role-specific
|
||||
network setup.
|
||||
@@ -1,3 +1,7 @@
|
||||
<!-- meshcore-hosted-doc-link:start -->
|
||||
<p class="meshcore-hosted-doc-link"><a href="https://mikecarper.github.io/MeshCore/full_usb_logging_validation/">View this page on MeshCore Docs</a>.</p>
|
||||
<!-- meshcore-hosted-doc-link:end -->
|
||||
|
||||
# Document moved
|
||||
|
||||
<script>window.location.replace(new URL("../test-results/full_usb_logging_validation/" + window.location.hash, window.location.href));</script>
|
||||
|
||||
+40
-2
@@ -1,3 +1,7 @@
|
||||
<!-- meshcore-hosted-doc-link:start -->
|
||||
<p class="meshcore-hosted-doc-link"><a href="https://mikecarper.github.io/MeshCore/gps_tracking/">View this page on MeshCore Docs</a>.</p>
|
||||
<!-- meshcore-hosted-doc-link:end -->
|
||||
|
||||
# GPS Tracking
|
||||
|
||||
This document describes how GPS telemetry works for companion/client nodes and
|
||||
@@ -46,10 +50,11 @@ GPS on, and manual GPS-on still keeps the cache updated.
|
||||
|
||||
Repeaters use a shorter on-demand policy when GPS is manually off. An authorized
|
||||
location-telemetry request wakes the receiver only when its cached position is
|
||||
stale. The first valid fix is cached and the receiver immediately powers down;
|
||||
stale. The first valid fix is cached and GPS processing immediately stops;
|
||||
the next request can return that fix without another wake. `gps off` also
|
||||
cancels an acquisition already in progress. An acquisition that never obtains a
|
||||
fix retains the normal 15-minute safety timeout.
|
||||
fix retains the normal 15-minute safety timeout. Physical supply switching
|
||||
depends on the board and its shared peripheral policy.
|
||||
|
||||
When GPS and device power saving are both enabled, a repeater wakes GPS for up
|
||||
to 10 minutes to obtain a valid time and then sleeps it for seven days. A valid
|
||||
@@ -57,6 +62,39 @@ GPS time may correct the RTC either forward or backward. With device power
|
||||
saving off, an enabled GPS remains active and refreshes the RTC every 30
|
||||
minutes.
|
||||
|
||||
## RAK WisBlock Power Supply
|
||||
|
||||
RAK4631 defaults to saved peripheral mode `gps-only`. With no protected power
|
||||
owner, stopping or sleeping GPS can turn off the shared `3V3_S` rail. If other
|
||||
attached modules need that rail, explicitly select shared mode:
|
||||
|
||||
```text
|
||||
get peripherals.mode
|
||||
set peripherals.mode shared
|
||||
reboot
|
||||
```
|
||||
|
||||
Use `set peripherals.mode gps-only` and reboot to return to the default.
|
||||
Changes are saved for the next boot, not applied live; the query reports the
|
||||
saved choice. Other peripheral requirements are not guessed from an I2C scan.
|
||||
Boot and discovery keep the rail on until the power policy is established.
|
||||
|
||||
This saved setting is available in Companion and Common CLI roles (repeater,
|
||||
room server, and sensor). Raw KISS and legacy Terminal Chat do not configure
|
||||
it and retain the powered rail. Shared mode controls the supply; it does not
|
||||
add Ethernet support to a non-Ethernet firmware target.
|
||||
|
||||
Ethernet/PoE builds and detected external NOR storage automatically retain
|
||||
power regardless of this setting. RAK3401 always retains the rail for its radio
|
||||
frontend and does not offer this override.
|
||||
|
||||
In shared mode, GPS-off does not mean the supply is off. In particular, the
|
||||
[RAK12501/L76K module](https://docs.rakwireless.com/product-categories/wisblock/rak12501/datasheet/)
|
||||
has no independent power switch: stopping MCU parsing leaves the receiver
|
||||
powered. Internal sleep support on other GNSS modules is receiver-dependent.
|
||||
This setting does not remove the UART1 bridge conflict or authorize another
|
||||
transmitter on the GPS wiring.
|
||||
|
||||
## Companion/Client Nodes
|
||||
|
||||
Companion/client telemetry uses the existing companion telemetry permission
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
<!-- meshcore-hosted-doc-link:start -->
|
||||
<p class="meshcore-hosted-doc-link"><a href="https://mikecarper.github.io/MeshCore/halo_keymind_settings/">View this page on MeshCore Docs</a>.</p>
|
||||
<!-- meshcore-hosted-doc-link:end -->
|
||||
|
||||
# Halo and Keymind Branch Settings
|
||||
|
||||
This file covers only CLI settings and helper commands added by the Halo or
|
||||
@@ -199,6 +203,10 @@ rows; an unpaged local serial query prints the full table.
|
||||
`outpath` and `altpath` apply to the current remote client ACL entry. They need
|
||||
remote client context, so they are not useful from the local serial CLI.
|
||||
|
||||
Room Server also supports these commands for readers and writers, restricted
|
||||
to the caller's own routes. See [room return routes](room_services.md#your-own-return-routes)
|
||||
for session persistence, alternate chat delivery and client requirements.
|
||||
|
||||
Set paths with comma-separated hop hashes. Each hop must be `2`, `4`, or `6`
|
||||
hex characters, and all hops in one path must use the same width. Hex input is
|
||||
case-insensitive. Replies use uppercase hex and retain the commas, so the value
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
<!-- meshcore-hosted-doc-link:start -->
|
||||
<p class="meshcore-hosted-doc-link"><a href="https://mikecarper.github.io/MeshCore/hardware_validation_bluetooth_stealth_2026-09-07/">View this page on MeshCore Docs</a>.</p>
|
||||
<!-- meshcore-hosted-doc-link:end -->
|
||||
|
||||
# Report moved
|
||||
|
||||
<script>window.location.replace(new URL("../test-results/hardware_validation_bluetooth_stealth_2026-09-07/" + window.location.hash, window.location.href));</script>
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
<!-- meshcore-hosted-doc-link:start -->
|
||||
<p class="meshcore-hosted-doc-link"><a href="https://mikecarper.github.io/MeshCore/hardware_validation_checklist/">View this page on MeshCore Docs</a>.</p>
|
||||
<!-- meshcore-hosted-doc-link:end -->
|
||||
|
||||
# Document moved
|
||||
|
||||
<script>window.location.replace(new URL("../test-results/hardware_validation_checklist/" + window.location.hash, window.location.href));</script>
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
<!-- meshcore-hosted-doc-link:start -->
|
||||
<p class="meshcore-hosted-doc-link"><a href="https://mikecarper.github.io/MeshCore/heltec_v4_wifi_partition_migration/">View this page on MeshCore Docs</a>.</p>
|
||||
<!-- meshcore-hosted-doc-link:end -->
|
||||
|
||||
# Heltec V4 legacy-partition migration over Wi-Fi
|
||||
|
||||
Heltec V4/V4.3 OLED repeaters with their normal 16 MiB flash use the shared
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
<!-- meshcore-hosted-doc-link:start -->
|
||||
<p class="meshcore-hosted-doc-link"><a href="https://mikecarper.github.io/MeshCore/home_text_spacing/">View this page on MeshCore Docs</a>.</p>
|
||||
<!-- meshcore-hosted-doc-link:end -->
|
||||
|
||||
# Document moved
|
||||
|
||||
<script>window.location.replace(new URL("../test-results/home_text_spacing/" + window.location.hash, window.location.href));</script>
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
<!-- meshcore-hosted-doc-link:start -->
|
||||
<p class="meshcore-hosted-doc-link"><a href="https://mikecarper.github.io/MeshCore/host_cli_service/">View this page on MeshCore Docs</a>.</p>
|
||||
<!-- meshcore-hosted-doc-link:end -->
|
||||
|
||||
# LoRa CLI Host Service
|
||||
|
||||
The repeater `host <text>` command lets an authenticated LoRa administrator
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
<!-- meshcore-hosted-doc-link:start -->
|
||||
<p class="meshcore-hosted-doc-link"><a href="https://mikecarper.github.io/MeshCore/">View this page on MeshCore Docs</a>.</p>
|
||||
<!-- meshcore-hosted-doc-link:end -->
|
||||
|
||||
# MeshCore documentation
|
||||
|
||||
Choose firmware with the [firmware picker](firmware_picker.md). For help with a device already in service, start with the [FAQ](faq.md).
|
||||
@@ -12,6 +16,8 @@ Choose firmware with the [firmware picker](firmware_picker.md). For help with a
|
||||
- [Feature switches by role](role_feature_switches.md) and [Full Companion features](full_companion_features.md)
|
||||
- [Firmware update guide](ota_user_guide.md), [easy LoRa OTA](ota_easy.md), and [scripted LoRa OTA](lora_ota_automation.md)
|
||||
- [CLI commands](cli_commands.md), [availability by build](cli_build_matrix.md), and [radio profiles](radio_profiles.md)
|
||||
- [Private fleet management](fleet_management.md): signed filtering and secondary-radio commands for a fleet or one node
|
||||
- [Room history, moderation, and information board](room_services.md): optional persistent chat and local Wi-Fi room access
|
||||
- [Companion protocol](companion_protocol.md), [packet format](packet_format.md), and [QR codes](qr_codes.md)
|
||||
|
||||
The navigation groups the remaining guides under Documentation, including board-specific updates, networking, power, and protocol references.
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
<!-- meshcore-hosted-doc-link:start -->
|
||||
<p class="meshcore-hosted-doc-link"><a href="https://mikecarper.github.io/MeshCore/kiss_modem_protocol/">View this page on MeshCore Docs</a>.</p>
|
||||
<!-- meshcore-hosted-doc-link:end -->
|
||||
|
||||
# MeshCore KISS Modem Protocol
|
||||
|
||||
Standard KISS TNC firmware for MeshCore LoRa radios. Compatible with any KISS client (Direwolf, APRSdroid, YAAC, etc.) for sending and receiving raw packets. MeshCore-specific extensions (cryptography, radio configuration, telemetry) are available through the standard SetHardware (0x06) command.
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
<!-- meshcore-hosted-doc-link:start -->
|
||||
<p class="meshcore-hosted-doc-link"><a href="https://mikecarper.github.io/MeshCore/lora_ota_automation/">View this page on MeshCore Docs</a>.</p>
|
||||
<!-- meshcore-hosted-doc-link:end -->
|
||||
|
||||
# Scripted LoRa OTA from start to finish
|
||||
|
||||
For **1.17.1.5**, use the exact board/storage profile from
|
||||
@@ -774,6 +778,20 @@ verbose device log contains its `COUNT` acknowledgement; an immediate device
|
||||
`ERR` or a missing acknowledgement fails during startup instead of surfacing
|
||||
later as a catalog timeout.
|
||||
|
||||
Newer `motatool` builds prepare raw-DEFLATE blocks with Zopfli before opening
|
||||
the source link. This host-only work has a separate 30-minute preparation
|
||||
limit (`--seeder-prepare-wait SECONDS`) and reports progress while waiting.
|
||||
The five-second device acknowledgement timer (`--seeder-start-wait SECONDS`)
|
||||
starts after the final `compressed N/N blocks` message, not when compression
|
||||
starts. Neither compression progress nor a host "serving" message proves
|
||||
device readiness: the runner still requires the device's `COUNT` reply.
|
||||
Older motatool builds without a preparation phase keep the original device
|
||||
startup timeout. Process exits and device errors still fail immediately.
|
||||
Preparation happens after TempRadio is armed, so leave enough lease time for
|
||||
both compression and transfer, especially on a slow host. The suggested
|
||||
TempRadio budget includes the preparation limit; it remains a warning, not a
|
||||
hard requirement to select that entire worst-case budget.
|
||||
|
||||
```bash
|
||||
./tools/lora_ota/lora_ota.sh ./release.mota "Remote Target" \
|
||||
--controller-serial /dev/ttyACM0 \
|
||||
@@ -905,7 +923,8 @@ the destination.
|
||||
target, far-to-near relays, and source. A separate controller is moved and
|
||||
read back through Binary; a shared Full Companion instead schedules its
|
||||
bounded local override while Binary remains the transport. The runner
|
||||
rejects a TempRadio window that cannot cover setup, seeder startup,
|
||||
rejects TempRadio windows under 20 minutes and warns when the selected
|
||||
window does not cover worst-case setup, seeder preparation and startup,
|
||||
discovery, the transfer timeout, final polling, and install checks.
|
||||
7. Start `motatool serve`, discover the exact eight-hex manifest ID, request
|
||||
`ota pull <id> flash`, and poll until that same ID reports ready. A seeder
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
<!-- meshcore-hosted-doc-link:start -->
|
||||
<p class="meshcore-hosted-doc-link"><a href="https://mikecarper.github.io/MeshCore/management_decoder/">View this page on MeshCore Docs</a>.</p>
|
||||
<!-- meshcore-hosted-doc-link:end -->
|
||||
|
||||
# Management report decoder
|
||||
|
||||
Paste MGR1 management-report payloads or complete GroupData packets from a
|
||||
Paste MGR1 or MGR2 management-report payloads or complete GroupData packets from a
|
||||
packet analyzer to read a radio's public management information. Everything,
|
||||
including password authentication and ACL decryption, happens locally in this
|
||||
browser. The report, password, and candidate public key are never uploaded.
|
||||
@@ -13,13 +17,13 @@ by someone who does not know that password.
|
||||
## Decode a management report
|
||||
|
||||
<div class="management-tool" data-management-decoder>
|
||||
<label for="management-packet-input">MGR1 payload or complete GroupData packet hex</label>
|
||||
<label for="management-packet-input">MGR1/MGR2 payload or complete GroupData packet hex</label>
|
||||
<textarea
|
||||
id="management-packet-input"
|
||||
data-role="input"
|
||||
spellcheck="false"
|
||||
autocomplete="off"
|
||||
placeholder="Paste analyzer Raw Data, canonical MGR1 payload hex, or one MQTT raw value per line"
|
||||
placeholder="Paste analyzer Raw Data, canonical MGR1/MGR2 payload hex, or one MQTT raw value per line"
|
||||
aria-describedby="management-packet-help"
|
||||
></textarea>
|
||||
<p class="management-help" id="management-packet-help">
|
||||
@@ -31,7 +35,7 @@ by someone who does not know that password.
|
||||
<label for="management-password-input">Management password <span>(optional for public fields; required for ACLs)</span></label>
|
||||
<input id="management-password-input" data-role="password" type="password" autocomplete="new-password">
|
||||
<p class="management-help">
|
||||
The password remains in this page only. The decoder derives the MGR1 AES-SIV key in
|
||||
The password remains in this page only. The decoder derives the management AES-SIV key in
|
||||
your browser and does not send it anywhere.
|
||||
</p>
|
||||
|
||||
@@ -67,9 +71,9 @@ by someone who does not know that password.
|
||||
## What the decoder accepts
|
||||
|
||||
- Complete `PAYLOAD_TYPE_GRP_DATA` (`0x06`) analyzer/MQTT packet hex. It checks
|
||||
the MeshCore route header, encoded path length, MGR1 page bounds, and required
|
||||
the MeshCore route header, encoded path length, version-specific page bounds, and required
|
||||
zero padding.
|
||||
- A canonical MGR1 payload beginning with `4D475231` (`MGR1`).
|
||||
- A canonical payload beginning with `4D475231` (`MGR1`) or `4D475232` (`MGR2`).
|
||||
- One raw packet or canonical payload per line; duplicate observations of an
|
||||
identical page are deduplicated.
|
||||
|
||||
@@ -78,5 +82,36 @@ the report-specific fingerprints and the administrator and/or OTA-signer flags.
|
||||
It cannot turn a fingerprint back into a full key. Use the optional candidate
|
||||
field to test a specific full public key.
|
||||
|
||||
MGR2 also shows USB logging, host/reader connection, logger-client activity, stalled/recovering/deferred
|
||||
state, recovery stage, saved backoff, retry interval/inactive seconds, Auto qualification
|
||||
seconds, and persistence
|
||||
readiness. Unsupported observation is shown explicitly; legacy MGR1 has no
|
||||
USB block and shows it as unavailable. These values are the report-start
|
||||
snapshot, not proof that a Pi or host application is healthy. Older decoder
|
||||
copies require an update for MGR2.
|
||||
|
||||
MGR2 also shows the latest USB watchdog action, reason mask and names,
|
||||
advisory node-clock timestamp, recorded-boot uptime seconds, event sequence,
|
||||
and whether the record was durably saved. Software recovery and re-enumeration
|
||||
mean attempted actions, not successful repairs. A reboot request is not proof
|
||||
that a reboot completed; a later cancellation can replace it. The node clock
|
||||
can be wrong, even when it resembles a valid date. Event uptime and sequence
|
||||
remain useful for ordering. MGR1 does not carry this event field and shows it
|
||||
as unavailable. All 36 ACL entries still fit, using up to nine MGR2 pages.
|
||||
|
||||
The earlier USB-only MGR2 development layout was never released. This decoder
|
||||
accepts the finalized MGR2 format with its 111-byte public header and latest
|
||||
watchdog event.
|
||||
|
||||
Reports contain a frozen latest-event snapshot, not immediate watchdog alerts
|
||||
or a complete event history. See [USB logging watchdog](usb_logging_watchdog.md)
|
||||
for local review and persistence limitations.
|
||||
|
||||
Host/reader connection only describes the USB link or open reader. Logger-client
|
||||
activity means recent USB stats polling within a 15-minute lease, renewed by
|
||||
the existing five-minute polls without a new registration or heartbeat command.
|
||||
The flag also requires logging enabled and a connected, unstalled USB link.
|
||||
It does not identify the client or prove MQTT delivery, disk writes, or Pi health.
|
||||
|
||||
For the report schedule, public-field layout, cryptographic design, and the
|
||||
offline Python capture tool, see [Management reports](management_reports.md).
|
||||
+124
-23
@@ -1,4 +1,8 @@
|
||||
# Management reports (MGR1)
|
||||
<!-- meshcore-hosted-doc-link:start -->
|
||||
<p class="meshcore-hosted-doc-link"><a href="https://mikecarper.github.io/MeshCore/management_reports/">View this page on MeshCore Docs</a>.</p>
|
||||
<!-- meshcore-hosted-doc-link:end -->
|
||||
|
||||
# Management reports (MGR1/MGR2)
|
||||
|
||||
Available on repeater (including observer), room-server and sensor firmware.
|
||||
Companions/terminal-chat nodes and KISS modems do not originate these reports.
|
||||
@@ -6,6 +10,12 @@ Off by default. A management password and explicit enable are both required.
|
||||
No radio settings, existing preferences layout, or OTA authorization policy is
|
||||
changed by enabling reporting. This protocol does **not** authorize updates.
|
||||
|
||||
Current firmware emits `MGR2`, including a public USB logging/recovery snapshot
|
||||
and its latest watchdog event. Updated decoders still accept released `MGR1`;
|
||||
older decoder copies need an update to read MGR2. The earlier USB-only MGR2
|
||||
development layout was never released and was replaced before release.
|
||||
Reporting USB status does not enable logging or change retry policy.
|
||||
|
||||
## CLI
|
||||
|
||||
Run through the existing local CLI or an authenticated administrator session:
|
||||
@@ -26,13 +36,17 @@ Use a long randomly generated password. There is no password getter. Firmware
|
||||
stores the derived 32-byte key, not the plaintext password. That stored key is
|
||||
equivalent authority to decrypt reports and must also be protected. Passwords
|
||||
entered into terminal programs may still be recorded by those programs.
|
||||
The recognized firmware password command wipes its value from the input buffer
|
||||
on success and error, including unavailable management or allocation failure.
|
||||
|
||||
`data.tx` is the single shared route for management reports, telemetry history,
|
||||
and future scheduled data producers. Its fresh-install defaults are `path=direct`
|
||||
(zero hops) and `region=auto`; configuring it never enables a producer. Paths
|
||||
use `1:`, `2:` or `3:` followed by complete hop hashes without separators, or
|
||||
the comma-separated form accepted by `set outpath`. `none` removes the path.
|
||||
the comma-separated form accepted by `set outpath`. Empty hops, including a
|
||||
trailing comma, are rejected. `none` removes the path.
|
||||
`get/set mgmt.path` remain compatibility aliases for `get/set data.tx path`.
|
||||
Alias expansion that exceeds the command buffer is rejected, not truncated.
|
||||
|
||||
`region=auto` uses the radio's configured default region when it is usable;
|
||||
otherwise it resolves the unique deepest flood-enabled entry in the region
|
||||
@@ -48,14 +62,19 @@ identifies the reporter.
|
||||
|
||||
Direct and flood schedules are independently configurable and may each be
|
||||
turned off. Fresh settings are `direct=5d` and `flood=21d`; global reporting is
|
||||
still off until `mgmt.enabled on`. Direct accepts 5–90 days and requires the
|
||||
shared path. Flood accepts 21–90 days and requires a resolvable shared region.
|
||||
still off until `mgmt.enabled on`. Direct accepts 5-90 days and requires the
|
||||
shared path. Flood accepts 21-90 days and requires a resolvable shared region.
|
||||
At least one route must remain active while reporting is enabled. The legacy
|
||||
`set mgmt.interval N` shorthand enables both, setting direct to `N` and flood to
|
||||
`max(21,N)`. No transmission is sent merely by configuring a password. Initial
|
||||
reporting waits for the configured schedules. Reports have deterministic
|
||||
reporting waits for the configured schedules. Invalid settings leave saved
|
||||
configuration, countdowns, and any report pages already in progress unchanged.
|
||||
Day counts must be unsigned decimal integers; signs and trailing text are rejected.
|
||||
Reports have deterministic
|
||||
per-radio/per-sequence jitter of up to an hour; pages are spaced at least a
|
||||
minute apart.
|
||||
minute apart. Watchdog events appear in the next scheduled snapshot, not as
|
||||
immediate radio alerts. Multiple events between snapshots can replace the
|
||||
latest-event record; this is not a complete off-node event history.
|
||||
|
||||
The radio cannot know that an observer uploaded a packet to MQTT, so the flood
|
||||
schedule is deliberately independent of direct transmission. A region-scoped
|
||||
@@ -76,6 +95,12 @@ writes stop reporting; `get mgmt` shows `FAULT(no TX)` until storage is repaired
|
||||
and the radio restarted. `/management` and the shared `/data_tx` are versioned,
|
||||
CRC-protected, and replaced transactionally.
|
||||
|
||||
On upgrade, an existing saved `data.tx` route takes precedence over the legacy
|
||||
management-only path. Otherwise that path must be saved successfully to
|
||||
`/data_tx` before reporting resumes. A failed migration leaves the old
|
||||
`/management` file intact for retry after storage is repaired and the radio
|
||||
restarted.
|
||||
|
||||
Weekly history is collected once a minute while enabled. Hour-bucket extrema
|
||||
cover 7 days to 7 days + 1 hour (conservative boundary bucket). The first report
|
||||
after enable/reboot is marked partial where appropriate. Since-report extrema
|
||||
@@ -90,8 +115,8 @@ is bounded to 1.5 KiB, plus a small configuration object and temporary stack use
|
||||
## Routing and MQTT
|
||||
|
||||
Both direct/path and flood reports use **`PAYLOAD_TYPE_GRP_DATA` (`0x06`)**.
|
||||
The route bits independently select direct or flood. `MGR1` is an application
|
||||
extension with **literally plaintext public fields**, not a call to the ordinary
|
||||
The route bits independently select direct or flood. `MGR1`/`MGR2` are application
|
||||
extensions with **literally plaintext public fields**, not a call to the ordinary
|
||||
encrypted `createGroupDatagram()` builder. A fixed public marker is not an owner
|
||||
or password-derived channel ID. There is no outer channel encryption.
|
||||
|
||||
@@ -108,7 +133,7 @@ not flood-route it.
|
||||
|
||||
The observer's existing MQTT `PACKET` JSON supplies the complete frame in `raw`.
|
||||
No broker configuration or password changes are necessary to capture a report.
|
||||
The decoder understands all four route forms, 1–3-byte hashes, scope transport
|
||||
The decoder understands all four route forms, 1-3-byte hashes, scope transport
|
||||
codes, and duplicate copies heard by several uplinks:
|
||||
|
||||
```
|
||||
@@ -118,19 +143,22 @@ python tools/management/report.py --mqtt capture.jsonl
|
||||
|
||||
The password is prompted, not supplied as a process argument. Input may be JSONL
|
||||
or a JSON array of MQTT messages. Alternatively omit `--mqtt` for a JSON array
|
||||
of canonical payload hex strings. `--match-admin FULL_PUBLIC_KEY` in canonical
|
||||
payload mode matches a known administrator against the encrypted fingerprints.
|
||||
of canonical payload hex strings. `--match-admin FULL_PUBLIC_KEY` matches a known
|
||||
administrator against the encrypted fingerprints in either input mode. MQTT
|
||||
captures match each radio's fingerprints separately. Invalid files or keys are
|
||||
rejected before the password prompt; an unavailable password input gives a
|
||||
command-line error instead of a traceback.
|
||||
This is an offline capture decoder, not a broker subscriber or downlink service.
|
||||
|
||||
For a browser-local decoder that also decrypts and authenticates ACL entries,
|
||||
use the [Management report decoder](management_decoder.md). It does not upload
|
||||
the captured packet or password.
|
||||
|
||||
## Canonical payload (little endian)
|
||||
## Canonical MGR2 payload (little endian)
|
||||
|
||||
| Offset | Bytes | Field |
|
||||
|---:|---:|---|
|
||||
| 0 | 4 | `MGR1` |
|
||||
| 0 | 4 | `MGR2` |
|
||||
| 4 | 16 | First 16 bytes of reporter public key |
|
||||
| 20 | 4 | Persisted report sequence (never wraps; exhaustion stops TX) |
|
||||
| 24 | 4 | RTC Unix timestamp; advisory, may be wrong |
|
||||
@@ -142,7 +170,7 @@ the captured packet or password.
|
||||
| 52 | 4 | Staging capacity; planning still checks actual package geometry |
|
||||
| 56 | 4 | OTA capability bits |
|
||||
| 60 | 2 | Uptime hours, saturated at 65535 |
|
||||
| 62 | 4 | Weekly minimum mV, minimum °C, maximum °C |
|
||||
| 62 | 4 | Weekly minimum mV, minimum C, maximum C |
|
||||
| 66 | 4 | Since-report extrema in the same format |
|
||||
| 70 | 1 | History coverage hours, capped at 168 |
|
||||
| 71 | 1 | Interval days for this report's direct or flood schedule |
|
||||
@@ -152,16 +180,87 @@ the captured packet or password.
|
||||
| 75 | 1 | Mask of status bits whose state is known |
|
||||
| 76 | 2 | Validity/partial-history flags |
|
||||
| 78 | 1 | Zero-based page index |
|
||||
| 79 | 1 | Total pages (1–6) |
|
||||
| 80 | 1 | Unique ACL count (0–36) |
|
||||
| 79 | 1 | Total pages (1-9) |
|
||||
| 80 | 1 | Unique ACL count (0-36) |
|
||||
| 81 | 1 | First ACL index in this page |
|
||||
| 82 | 1 | ACL entries on this page (0–6) |
|
||||
| 83 | 13 × count | AES-SIV encrypted ACL entries |
|
||||
| 82 | 1 | ACL entries on this page (0-4) |
|
||||
| 83 | 2 | USB status flags |
|
||||
| 85 | 1 | Packed USB recovery stage and retry backoff step |
|
||||
| 86 | 4 | USB full-reboot retry interval seconds |
|
||||
| 90 | 4 | USB inactive seconds |
|
||||
| 94 | 4 | USB continuous Auto qualification seconds |
|
||||
| 98 | 1 | Latest watchdog reason/action/durability code |
|
||||
| 99 | 4 | Latest watchdog event RTC Unix epoch; advisory, zero if unavailable |
|
||||
| 103 | 4 | Uptime seconds at that event's boot |
|
||||
| 107 | 4 | Latest watchdog event sequence, saturated at 4294967295 |
|
||||
| 111 | 13 * count | AES-SIV encrypted ACL entries |
|
||||
| after ACL | 16 | Full AES-SIV authentication tag |
|
||||
| after tag | 0–15 | Zero padding to group-compatible length; not part of canonical payload |
|
||||
| after tag | 0-15 | Zero padding to group-compatible length; not part of canonical payload |
|
||||
|
||||
MGR2 retains all 36 ACL entries, using four per page and up to nine pages. The
|
||||
largest canonical page is 179 bytes and already group-compatible;
|
||||
the radio payload budget is unchanged. The USB snapshot is frozen with the
|
||||
other public fields at report start and repeated on each page. It is not a
|
||||
live host-health monitor.
|
||||
|
||||
USB flag bits 0-10 are, in order: supported, logging enabled, watchdog enabled,
|
||||
host connected, reader connected, stalled, recovering, recovery deferred,
|
||||
persistence ready, watchdog Auto, and logger active. Bits 11-15 must be zero.
|
||||
Byte 85 bits 0-1 hold stage: 0 idle, 1 soft-recovery wait, 2 re-enumeration
|
||||
wait, and 3 reboot pending. Bits 2-5 hold the saved backoff step; bits 6-7
|
||||
must be zero. Backoff is not a count of successful recoveries. The three
|
||||
timers are unsigned seconds; local recovery/reboot counters are not sent.
|
||||
The retry field is the saved backoff interval, not a countdown to an action;
|
||||
inactive age is reported separately. USB-only cleanup can precede that interval.
|
||||
Unsupported USB observation/recovery is explicitly represented by a false
|
||||
supported bit, not omitted or mistaken for a connected healthy reader.
|
||||
USB status describes the radio's observed transport and policy, not proof
|
||||
that the host process, MQTT uplink, SSH service, or Pi is healthy.
|
||||
Host/reader connection alone does not mean a logging client is running.
|
||||
The logger-active bit means recent USB stats polling within a 15-minute lease
|
||||
while logging is enabled and the observed USB host/reader link is not stalled,
|
||||
separate from USB attachment or an open reader. The existing five-minute stats
|
||||
polls renew this lease; no new client-registration or heartbeat command is
|
||||
required. This does not identify the client, prove logs reached MQTT or disk,
|
||||
or show that the Pi remains otherwise healthy.
|
||||
|
||||
Missing watchdog state on new or upgraded installations defaults to Auto.
|
||||
Auto promotes to saved On after 14 continuous healthy logging-USB days with
|
||||
renewed stats polling; its qualification age resets on reboot, detach, stall,
|
||||
stats-lease expiry, or USB logging off.
|
||||
Explicit On and Off remain available. Nonlogging/no-reader nodes remain
|
||||
Auto without watchdog board reboots. The Auto flag and age report this
|
||||
qualification separately from an enabled On policy.
|
||||
Use the node's `get usb.watchdog` and `set usb.watchdog off|on|auto` CLI to
|
||||
inspect or change policy; receiving a management snapshot changes nothing.
|
||||
|
||||
The 13-byte latest-event block is all zero when no event is available. Otherwise
|
||||
byte 98 bits 0-3 are the reason mask: host absent, reader absent, TX stalled,
|
||||
and confirmed stats client inactive. Bits 4-6 hold the action: 1 software
|
||||
recovery attempted, 2 re-enumeration attempted, 3 reboot requested, or 4 reboot
|
||||
cancelled after a final health/ownership check. Bit 7 means the event was saved
|
||||
and verified on durable storage. Sequence is nonzero for a present event.
|
||||
Physical faults may combine reason bits; stats-only expiry cannot authorize a
|
||||
board reboot. Epoch zero is allowed when the clock source is unavailable.
|
||||
|
||||
Event time is the node's advisory clock, not verified UTC. Default clocks can
|
||||
start at a plausible fixed date. Clock corrections do not rewrite old events;
|
||||
recorded uptime and sequence help review them independently. A saved reboot
|
||||
request is intent, not proof of a completed reset. The latest event survives
|
||||
normal reboot and mode changes, but is not a log of hardware hangs or sudden
|
||||
power failures. See [USB logging watchdog](usb_logging_watchdog.md) for storage
|
||||
failure, cancellation, and review limitations.
|
||||
|
||||
Legacy MGR1 retains the same offsets through byte 82, followed immediately
|
||||
by encrypted ACL entries at byte 83 and the authentication tag. It carries
|
||||
six entries per page and up to six pages, with no USB block. Its absent USB
|
||||
status is decoded as unavailable, not as all switches off.
|
||||
|
||||
Each private entry is a per-radio 12-byte keyed fingerprint followed by flags:
|
||||
bit 0 administrator, bit 1 trusted OTA signer. Duplicate entries combine flags.
|
||||
bit 0 administrator, bit 1 trusted OTA signer. The firmware combines permissions
|
||||
for the same fingerprint before paging, so every transmitted entry is unique.
|
||||
Decoders reject authenticated duplicate fingerprints within or across pages;
|
||||
repeated identical packet copies from multiple uplinks are still deduplicated.
|
||||
An oversized ACL fails closed rather than silently truncating. The reported
|
||||
allowlist includes all current full administrators and all four possible trusted
|
||||
OTA signing keys; region/filter managers and ordinary clients are excluded.
|
||||
@@ -175,9 +274,9 @@ means compiled support; active means an established usable apply/store path.
|
||||
Validity bits: 0 firmware version, 1 bootloader version, 2 EndF/base identity,
|
||||
3 staging capacity, 4 partial week, 5 partial since-report period, 6 MCU temperature.
|
||||
Voltage is unsigned millivolts, zero missing. Temperatures: zero missing,
|
||||
1–251 represent −50…200 °C, 252 below range, 253 above range, 254–255 reserved.
|
||||
1-251 represent -50..200 C, 252 below range, 253 above range, 254-255 reserved.
|
||||
OTA bits: 0 protocol compiled, 1 transfer DEFLATE, 2 2-KiB app transfer blocks;
|
||||
bits 8–23 are apply codec bits (full/sequential/in-place). Transfer DEFLATE is
|
||||
bits 8-23 are apply codec bits (full/sequential/in-place). Transfer DEFLATE is
|
||||
not compressed bootloader apply. There is **no manifest ID**. Exact old binaries
|
||||
are still needed on the computer to generate a differential update; a hash alone
|
||||
cannot reconstruct them. Unknown metadata must not be treated as OTA readiness.
|
||||
@@ -193,7 +292,9 @@ needs a candidate administrator's full key to identify a fingerprint.
|
||||
|
||||
Encryption is [RFC 5297 AES-SIV-CMAC-256](https://www.rfc-editor.org/rfc/rfc5297),
|
||||
using the existing rweather AES primitive. The single associated-data string is
|
||||
the complete 83-byte clear header. Ciphertext is exactly the ACL byte length.
|
||||
the complete clear header: 111 bytes for MGR2, including USB status and its event,
|
||||
or 83 bytes for MGR1. Both retain the existing derivation domains; the authenticated
|
||||
magic and layout distinguish their wire versions. Ciphertext is exactly the ACL byte length.
|
||||
Full 16-byte tag, no truncation. This deterministic misuse-resistant mode avoids
|
||||
reliance on the firmware's noncryptographic general-purpose RNG or RTC nonces.
|
||||
An identical restored snapshot may repeat ciphertext, but does not expose XORs
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
<!-- meshcore-hosted-doc-link:start -->
|
||||
<p class="meshcore-hosted-doc-link"><a href="https://mikecarper.github.io/MeshCore/mbedtls-tls-footprint/">View this page on MeshCore Docs</a>.</p>
|
||||
<!-- meshcore-hosted-doc-link:end -->
|
||||
|
||||
# Document moved
|
||||
|
||||
<script>window.location.replace(new URL("../test-results/mbedtls-tls-footprint/" + window.location.hash, window.location.href));</script>
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
<!-- meshcore-hosted-doc-link:start -->
|
||||
<p class="meshcore-hosted-doc-link"><a href="https://mikecarper.github.io/MeshCore/mixed_scan_validation/">View this page on MeshCore Docs</a>.</p>
|
||||
<!-- meshcore-hosted-doc-link:end -->
|
||||
|
||||
# Document moved
|
||||
|
||||
<script>window.location.replace(new URL("../test-results/mixed_scan_validation/" + window.location.hash, window.location.href));</script>
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
<!-- meshcore-hosted-doc-link:start -->
|
||||
<p class="meshcore-hosted-doc-link"><a href="https://mikecarper.github.io/MeshCore/mixed_sf_bw_validation/">View this page on MeshCore Docs</a>.</p>
|
||||
<!-- meshcore-hosted-doc-link:end -->
|
||||
|
||||
# Document moved
|
||||
|
||||
<script>window.location.replace(new URL("../test-results/mixed_sf_bw_validation/" + window.location.hash, window.location.href));</script>
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
<!-- meshcore-hosted-doc-link:start -->
|
||||
<p class="meshcore-hosted-doc-link"><a href="https://mikecarper.github.io/MeshCore/nimble_companion_trial/">View this page on MeshCore Docs</a>.</p>
|
||||
<!-- meshcore-hosted-doc-link:end -->
|
||||
|
||||
# Document moved
|
||||
|
||||
<script>window.location.replace(new URL("../test-results/nimble_companion_trial/" + window.location.hash, window.location.href));</script>
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
<!-- meshcore-hosted-doc-link:start -->
|
||||
<p class="meshcore-hosted-doc-link"><a href="https://mikecarper.github.io/MeshCore/nimble_companion_trial_results/">View this page on MeshCore Docs</a>.</p>
|
||||
<!-- meshcore-hosted-doc-link:end -->
|
||||
|
||||
# Document moved
|
||||
|
||||
<script>window.location.replace(new URL("../test-results/nimble_companion_trial_results/" + window.location.hash, window.location.href));</script>
|
||||
|
||||
+70
-1
@@ -1,3 +1,7 @@
|
||||
<!-- meshcore-hosted-doc-link:start -->
|
||||
<p class="meshcore-hosted-doc-link"><a href="https://mikecarper.github.io/MeshCore/notifications/">View this page on MeshCore Docs</a>.</p>
|
||||
<!-- meshcore-hosted-doc-link:end -->
|
||||
|
||||
# Companion notification builder
|
||||
|
||||
Make a rule, preview it here, then copy the CLI commands or apply and test it
|
||||
@@ -13,6 +17,7 @@ stop policy. USB testing uses Binary Companion mode at 115200 baud.
|
||||
<button type="button" data-example="find">Find my node</button>
|
||||
<button type="button" data-example="food">Food order ready</button>
|
||||
<button type="button" data-example="vip">VIP chat</button>
|
||||
<button type="button" data-example="channel9">Only channel 9</button>
|
||||
</div>
|
||||
<form data-role="form" onsubmit="return false">
|
||||
<section class="notification-card">
|
||||
@@ -22,6 +27,12 @@ stop policy. USB testing uses Binary Companion mode at 115200 baud.
|
||||
<label>Contact/room key or channel slot<input name="id" autocomplete="off" placeholder="Full contact/room public key, or channel slot such as 3"></label>
|
||||
<label>Companion client status<select name="when"><option value="any">Connected or disconnected</option><option value="connected">Connected</option><option value="disconnected">Disconnected</option></select></label>
|
||||
</div>
|
||||
<label class="notification-checkbox"><input type="checkbox" name="quietOthers" value="on">Silence other message alerts (channel exception)</label>
|
||||
<p data-role="quiet-warning" hidden>This saves a silent default plus the selected channel's rule.
|
||||
It also silences ordinary DMs and room-message alerts. Existing specific/state rules
|
||||
and authorized <code>!notify</code> strings can still alert; review the
|
||||
<a href="#only-channel-9-alerts-all-other-channels-silent">channel 9 example and caveats</a>.
|
||||
Copy commands and USB Save include both rules; preview only plays the exception.</p>
|
||||
<p>Connected means an active phone/app, USB, WiFi, Ethernet or serial
|
||||
companion client. It selects a message rule; connecting or disconnecting
|
||||
alone does not create an alert. Contact matching uses the full public key.
|
||||
@@ -106,7 +117,9 @@ stop policy. USB testing uses Binary Companion mode at 115200 baud.
|
||||
<p>Save writes the generated commands in order and stops at the first device
|
||||
error; earlier successful settings remain saved. A test with a connection
|
||||
suffix selects that profile even over USB. Stop-on-connect still watches
|
||||
real subsequent connections. USB testing requires Chrome or Edge.</p>
|
||||
real subsequent connections. The silence-other-alerts option saves both
|
||||
the silent default and the selected channel rule. Deleting the selected
|
||||
rule does not delete that default. USB testing requires Chrome or Edge.</p>
|
||||
<pre data-role="device-log" class="notification-log" aria-live="polite"></pre>
|
||||
</section>
|
||||
</div>
|
||||
@@ -240,3 +253,59 @@ notification strings. Receiver master switches apply even while connected.
|
||||
and vibration. Add a connected profile with sound/vibration off if the phone
|
||||
should handle notifications while attached. Permission to send notification
|
||||
strings is separate from a VIP's ordinary message rule.
|
||||
|
||||
### Only channel 9 alerts; all other channels silent
|
||||
|
||||
This example makes message alerts silent by default, then plays one short
|
||||
melody for channel 9. It works both with and without a connected companion
|
||||
client and requires a device with a buzzer. Messages still arrive normally;
|
||||
this changes the node's alerts, not the phone app's notification settings.
|
||||
Use the **Only channel 9** button above to load this setup into the builder.
|
||||
You can change the channel slot or alert pattern before copying or saving.
|
||||
Uncheck **Silence other message alerts** to generate only the selected rule;
|
||||
this does not remove a silent default already saved on the node.
|
||||
|
||||
`channel:9` means the firmware's **zero-based slot 9** (the tenth slot), not
|
||||
a channel named `ch9`. Configure that slot before running these commands.
|
||||
The rule follows its saved channel key; recreate it if you replace the channel
|
||||
in that slot.
|
||||
|
||||
Start with no conflicting custom rules. Use `get notify.rules`, then
|
||||
`get notify.rules <rule-slot>` to inspect existing selectors. Remove only
|
||||
unwanted rules with `notify.delete <selector>`: existing state-specific and
|
||||
contact/channel rules can override the silent default below.
|
||||
|
||||
```text
|
||||
set notify.enabled on
|
||||
set notify.vibration all off
|
||||
set notify.sound all off
|
||||
set notify.led all off
|
||||
set notify.screen all off
|
||||
set notify.gpio all off
|
||||
set notify.repeat all 1
|
||||
set notify.gap all 500
|
||||
set notify.stop all button
|
||||
set notify.sound on
|
||||
set notify.sound channel:9 ch9:d=8,o=5,b=180:c,e,g
|
||||
```
|
||||
|
||||
Keep the sound **master** on: `set notify.sound all off` silences the default
|
||||
rule, while `set notify.sound off` would also block the channel 9 exception.
|
||||
The other outputs remain silent, including screen wake-ups for messages.
|
||||
To add vibration for channel 9 on a device with a vibration motor:
|
||||
|
||||
```text
|
||||
set notify.vibration on
|
||||
set notify.vibration channel:9 100,100,300
|
||||
```
|
||||
|
||||
Test the saved exception with `notify.test channel:9`; stop it with
|
||||
`notify.stop`. Send a message on another configured channel to verify that
|
||||
it stays quiet.
|
||||
|
||||
**Scope:** there is no channels-only wildcard: `all` also silences ordinary
|
||||
DM and room-message alerts unless a more specific rule enables them.
|
||||
Previously authorized contact/room `!notify` strings can still request alerts.
|
||||
If those should stay quiet too, revoke their permission with
|
||||
`set notify.remote contact:PUBLIC_KEY off` or
|
||||
`set notify.remote room:ROOM_PUBLIC_KEY off`, using the full sender/server key.
|
||||
@@ -1,3 +1,7 @@
|
||||
<!-- meshcore-hosted-doc-link:start -->
|
||||
<p class="meshcore-hosted-doc-link"><a href="https://mikecarper.github.io/MeshCore/nrf52-usb-ready-fix/">View this page on MeshCore Docs</a>.</p>
|
||||
<!-- meshcore-hosted-doc-link:end -->
|
||||
|
||||
# Document moved
|
||||
|
||||
<script>window.location.replace(new URL("../test-results/nrf52-usb-ready-fix/" + window.location.hash, window.location.href));</script>
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
<!-- meshcore-hosted-doc-link:start -->
|
||||
<p class="meshcore-hosted-doc-link"><a href="https://mikecarper.github.io/MeshCore/nrf52_bluetooth_debug/">View this page on MeshCore Docs</a>.</p>
|
||||
<!-- meshcore-hosted-doc-link:end -->
|
||||
|
||||
# Document moved
|
||||
|
||||
<script>window.location.replace(new URL("../research/nrf52_bluetooth_debug/" + window.location.hash, window.location.href));</script>
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
<!-- meshcore-hosted-doc-link:start -->
|
||||
<p class="meshcore-hosted-doc-link"><a href="https://mikecarper.github.io/MeshCore/nrf52_companion_storage_recovery/">View this page on MeshCore Docs</a>.</p>
|
||||
<!-- meshcore-hosted-doc-link:end -->
|
||||
|
||||
# Document moved
|
||||
|
||||
<script>window.location.replace(new URL("../test-results/nrf52_companion_storage_recovery/" + window.location.hash, window.location.href));</script>
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
<!-- meshcore-hosted-doc-link:start -->
|
||||
<p class="meshcore-hosted-doc-link"><a href="https://mikecarper.github.io/MeshCore/nrf52_power_management/">View this page on MeshCore Docs</a>.</p>
|
||||
<!-- meshcore-hosted-doc-link:end -->
|
||||
|
||||
# nRF52 Power Management
|
||||
|
||||
## Overview
|
||||
@@ -221,6 +225,10 @@ DEBUG: PWRMGT: LPCOMP wake configured (AIN7, ref=3/8 VDD)
|
||||
|
||||
## Further power-management work (Planned)
|
||||
|
||||
For the opt-in Sensor check-in workflow using System ON idle sleep and bounded
|
||||
GPS acquisition, see [Sensor dog tracker](sensor_tracker.md). It is separate
|
||||
from the battery-protection SYSTEMOFF shutdown described here.
|
||||
|
||||
- A voltage state machine (Normal -> Warning -> Critical -> Shutdown)
|
||||
- Configurable wake comparator thresholds
|
||||
- Load shedding callbacks for power reduction
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
<!-- meshcore-hosted-doc-link:start -->
|
||||
<p class="meshcore-hosted-doc-link"><a href="https://mikecarper.github.io/MeshCore/number_allocations/">View this page on MeshCore Docs</a>.</p>
|
||||
<!-- meshcore-hosted-doc-link:end -->
|
||||
|
||||
# Number Allocations
|
||||
|
||||
This document lists unique numbers/identifiers used in various MeshCore protocol payloads.
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
<!-- meshcore-hosted-doc-link:start -->
|
||||
<p class="meshcore-hosted-doc-link"><a href="https://mikecarper.github.io/MeshCore/old-releases/1.17.1.5-doc-audit/">View this page on MeshCore Docs</a>.</p>
|
||||
<!-- meshcore-hosted-doc-link:end -->
|
||||
|
||||
# Markdown audit — 1.17.1.5 USA Cascade
|
||||
|
||||
Audited on 2026-09-05 against firmware source `26303793`, the resolved build
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
<!-- meshcore-hosted-doc-link:start -->
|
||||
<p class="meshcore-hosted-doc-link"><a href="https://mikecarper.github.io/MeshCore/old-releases/1.17.1.5/">View this page on MeshCore Docs</a>.</p>
|
||||
<!-- meshcore-hosted-doc-link:end -->
|
||||
|
||||
# MeshCore 1.17.1.5 Dev - USA Cascade
|
||||
|
||||
Use [feature switches by role](../role_feature_switches.md) for exact on/off
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
<!-- meshcore-hosted-doc-link:start -->
|
||||
<p class="meshcore-hosted-doc-link"><a href="https://mikecarper.github.io/MeshCore/old-releases/1.17.1.6/">View this page on MeshCore Docs</a>.</p>
|
||||
<!-- meshcore-hosted-doc-link:end -->
|
||||
|
||||
# MeshCore 1.17.1.6 Dev - USA Cascade
|
||||
|
||||
This release builds the current `keymindCascade` source after repairing the
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
<!-- meshcore-hosted-doc-link:start -->
|
||||
<p class="meshcore-hosted-doc-link"><a href="https://mikecarper.github.io/MeshCore/old-releases/1.17.1.7/">View this page on MeshCore Docs</a>.</p>
|
||||
<!-- meshcore-hosted-doc-link:end -->
|
||||
|
||||
# MeshCore 1.17.1.7 USA Cascade
|
||||
|
||||
This development release uses 910.525 MHz, 62.5 kHz bandwidth, SF7, and CR5 with Cascade defaults. Saved radio settings take precedence. The firmware source is `2d03e098cf75d58adcf35fe3d4dbbeaf0af114e1`.
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
<!-- meshcore-hosted-doc-link:start -->
|
||||
<p class="meshcore-hosted-doc-link"><a href="https://mikecarper.github.io/MeshCore/old-releases/">View this page on MeshCore Docs</a>.</p>
|
||||
<!-- meshcore-hosted-doc-link:end -->
|
||||
|
||||
# Old Releases
|
||||
|
||||
Historical release notes and their supporting files are kept here for reference. Use the [current 1.17.1.8 release notes](../releases/1.17.1.8.md) for current firmware.
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
<!-- meshcore-hosted-doc-link:start -->
|
||||
<p class="meshcore-hosted-doc-link"><a href="https://mikecarper.github.io/MeshCore/ota_easy/">View this page on MeshCore Docs</a>.</p>
|
||||
<!-- meshcore-hosted-doc-link:end -->
|
||||
|
||||
# Easy firmware updates over LoRa
|
||||
|
||||
For **1.17.1.5**, use the exact board/storage profile from
|
||||
|
||||
Loaded 100 of 1255 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user