Compare commits

..
Author SHA1 Message Date
richandClaude Opus 5.5 0030aec8dd Count only firmware-code commits in the -hz.N build identifier
Build and deploy Docs site to GitHub Pages / github-pages (push) Canceled after 0s
Run Unit Tests / nrf52-companion-smoke (push) Canceled after 0s
Run Unit Tests / nrf52-sensor-smoke (push) Canceled after 0s
Run Unit Tests / esp32-sensor-smoke (push) Canceled after 0s
Run Unit Tests / management-reports (push) Canceled after 0s
Run Unit Tests / stm32-companion-smoke (push) Canceled after 0s
Run Unit Tests / bootloader-version (push) Canceled after 0s
Run Unit Tests / test (push) Canceled after 0s
Run Unit Tests / hosted-doc-links (push) Canceled after 0s
Merge commits and commits that touch only the build-setup files
(local_build.sh, local_version.py, platformio.local.ini) no longer bump
the identifier, and setup-file edits no longer mark a build .dirty, so
a branch that only carries settings builds as the plain fork version.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01C6JeNqcQRUMo2vt6vFBmhk
2026-10-13 21:40:55 -04:00
richandClaude Opus 5.5 675e40575d Merge upstream keymindCascade (WisBlock GPS power policy, Companion WiFi TCP fixes)
Build and deploy Docs site to GitHub Pages / github-pages (push) Canceled after 0s
Run Unit Tests / nrf52-companion-smoke (push) Canceled after 0s
Run Unit Tests / nrf52-sensor-smoke (push) Canceled after 0s
Run Unit Tests / esp32-sensor-smoke (push) Canceled after 0s
Run Unit Tests / management-reports (push) Canceled after 0s
Run Unit Tests / stm32-companion-smoke (push) Canceled after 0s
Run Unit Tests / bootloader-version (push) Canceled after 0s
Run Unit Tests / test (push) Canceled after 0s
Run Unit Tests / hosted-doc-links (push) Canceled after 0s
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01C6JeNqcQRUMo2vt6vFBmhk
2026-10-13 21:33:28 -04:00
richandClaude Opus 5.5 f88c6c64e5 Add Hazelwood local build wrapper and Heltec no-MQTT companion env
Run Unit Tests / nrf52-companion-smoke (push) Canceled after 0s
Run Unit Tests / nrf52-sensor-smoke (push) Canceled after 0s
Run Unit Tests / esp32-sensor-smoke (push) Canceled after 0s
Run Unit Tests / management-reports (push) Canceled after 0s
Run Unit Tests / stm32-companion-smoke (push) Canceled after 0s
Run Unit Tests / bootloader-version (push) Canceled after 0s
Run Unit Tests / test (push) Canceled after 0s
Run Unit Tests / hosted-doc-links (push) Canceled after 0s
Build and deploy Docs site to GitHub Pages / github-pages (push) Canceled after 0s
local_build.sh wraps build.sh with the Hazelwood defaults (919.5 MHz /
BW500 / SF10 / CR5, BLE PIN 123456, target radio preset, default settings
profile), builds into out/<timestamp> so build.sh cannot wipe other
files, and sets up the macOS GNU tool PATH.

local_version.py labels firmware "hazelwood_<role> v<fork version>
(core v<upstream>)" (companions version-first for the app's 19-character
field). The version is the highest vX.Y.Z.W tag reachable from HEAD;
commits not in upstream/keymindCascade add -hz.N and uncommitted edits
add .dirty.

platformio.local.ini adds Heltec_v3_companion_radio_full_nomqtt: the V3
Full Companion without the MQTT bridge, which otherwise fails the
runtime RAM check (198 KB needed, 181 KB available), with the 100-contact
limit build.sh applies only to the exact stock env name.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01C6JeNqcQRUMo2vt6vFBmhk
2026-10-13 21:31:31 -04:00
mikecarper eaea32fcc6 Add actual Companion device-query handshake regression harness 2026-10-13 17:05:37 -07:00
mikecarper 0e477426f5 Prevent WiFi handshake starvation and blocking Companion TCP sends 2026-10-13 17:03:10 -07:00
mikecarper c1683e333f Scope Companion public-key CLI assertions to the identity branch 2026-10-13 16:29:28 -07:00
mikecarper 6693dba8cf Recover Companion WiFi TCP listener failures and expose diagnostics 2026-10-13 16:24:34 -07:00
mikecarper a2f51253d8 Fix WisBlock GPS power policy and preserve shared peripherals 2026-10-13 09:55:32 -07:00
mikecarper af08c02a00 Correct Morse human spacing and harden speed qualification
Run Unit Tests / nrf52-companion-smoke (push) Canceled after 0s
Run Unit Tests / nrf52-sensor-smoke (push) Canceled after 0s
Run Unit Tests / esp32-sensor-smoke (push) Canceled after 0s
Run Unit Tests / management-reports (push) Canceled after 0s
Run Unit Tests / stm32-companion-smoke (push) Canceled after 0s
Run Unit Tests / bootloader-version (push) Canceled after 0s
Run Unit Tests / test (push) Canceled after 0s
Run Unit Tests / hosted-doc-links (push) Canceled after 0s
Build and deploy Docs site to GitHub Pages / github-pages (push) Canceled after 0s
2026-10-13 05:25:07 -07:00
mikecarper 378925638b Improve Morse timing adaptation and repair CI regression coverage 2026-10-13 03:02:52 -07:00
mikecarper 3963048226 Coordinate Reader output with Morse and enforce HIL flash safety
Preserve Reader streaming during Morse timing and block synchronous inflate/progress interleaving. Fail closed on uncertain port ownership and enforce application-only DFU packages before querying USB. Cover the guards in CI and clarify experimental benchmark scope.
2026-10-13 01:51:27 -07:00
mikecarper 0be4c7488c Add bounded Morse receive and negotiated RS232 autosleep
Add native narrow-band CW capture, ACK-first remote sessions, retained LoRa-readable pages, checked restoration and transport-safe progress. Add stock-compatible negotiated heartbeat/autosleep and recovery. Include host regressions and identity-bound HIL runners; keep unverified faster tuning HIL-only and document outstanding hardware validation.
2026-10-13 01:24:39 -07:00
mikecarper 8c698dc2d1 Reduce nRF52 Companion flash and stream Reader chapters
Use a 960-byte constant-time Ed25519 base table, compact hash/scalar code and unused-library trims while retaining Reader, BLE P-256 and the 8 KiB stack. RAK4631 Full saves 57,496 flash bytes and 3,152 static RAM bytes versus preview3.

Support complete chapters and verse ranges with bounded app continuation replies and cooperative terminal output. Use nonblocking TCP writes under reader backpressure. Add scalar/signature comparisons and a linked ARM crypto/stack CI gate.

Validated clean RAK4631, T1000-E, T096 and V4 Full builds, host regressions, and physical RAK chapter/reconnect and signing comparisons. Physical signing averages 35.9 ms versus 14.4 ms with the original table.
2026-10-13 01:02:30 -07:00
mikecarper 9ce2a076eb Include and qualify Meshtext in RAK4631 Full Companion
Port Meshtext settings to verified atomic nRF52 storage and use the existing radio RNG and hardware entropy. Keep the ExtraFS boundary, GPS, contacts, channel count and 8 KiB stack unchanged; omit unused library data to fit the image. Require linked codec and Companion transport evidence and build the real RAK Full profile in CI. Add configuration and storage-failure regression coverage.
2026-10-12 22:39:00 -07:00
mikecarper a31b19ed8a Shorten firmware picker selection links and keep original download names
Replace the download-file shortcut approach with compact self-contained picker URLs under /p/. Preserve long-link compatibility, selected install formats, and full published firmware filenames. Cover selection round trips and the redirect in the picker tests.
2026-10-12 22:02:20 -07:00
mikecarper 3e0ce84486 Publish immutable short firmware download links
Mirror pinned GitHub release bytes at shorter version-specific Pages URLs. Verify size, SHA-256 and firmware format before publishing, and preserve deployed alias identities across later site builds.

Add RAK4631 Full Companion 1.17.1.10 preview2 UF2 and native DFU ZIP aliases, a short-download directory, documentation and CI regressions. Existing flashing tools use the ZIP URL without losing board-family checks.

Validation: 16 short-link and Pages guard regressions, hosted-doc routing tests, firmware picker tests, full MkDocs build and exact UF2/ZIP byte checks.
2026-10-12 21:47:08 -07:00
mikecarper 015a3e8e05 Repair WiFi power-policy CI fixtures and run ownership checks earlier 2026-10-12 15:55:04 -07:00
mikecarper 6b3f7df4d0 Suspend failed ESP32 MQTT and NTP uplinks for power saving
Share one bounded WiFi outage and recovery schedule across MQTT, automatic NTP, Companion station ownership and WebConfig. Preserve enabled settings and foreground access while allowing normal sleep after confirmed physical shutdown.

Honor failed SDK client stops before handing off WiFi, protect clean teardown and reconfiguration, and retry transient ESP-NOW channel restoration without repeated disconnects. Add production-method, SDK failure and real sleep regressions to CI.

Validation: native MQTT/NTP/clock/reconnect suites; production power and wrapper failure tests; browser, room HTTP, OTA and combined bridge regressions; Full Heltec V4 repeater, room and companion builds with capability, flash and linked-memory checks.
2026-10-12 14:57:01 -07:00
mikecarper f62cc7b527 Check all application USB startup flags on migration bridges 2026-10-11 15:14:28 -07:00
mikecarper a52059e603 Verify USB partition bridge transports in CI 2026-10-11 15:10:33 -07:00
mikecarper 3d3d5827ba Document automatic pinned USB migration utility downloads 2026-10-11 13:29:08 -07:00
mikecarper 331ba22a90 Merge remote-tracking branch 'origin/keymindCascade' into keymindCascade 2026-10-11 13:28:23 -07:00
mikecarper cbafd44047 Add verified USB-only ESP32 partition migration bridges 2026-10-11 13:28:23 -07:00
mikecarper 926238123d Repair CI dependencies and fit STM32 Companion firmware 2026-10-11 09:13:33 -07:00
mikecarper f304b8ff77 Add Meshtext Companion channel chat and automatic radio2 setup 2026-10-11 02:08:02 -07:00
mikecarper 41f48344bd Add opt-in low power Sensor dog tracking
Acquire fresh GPS and validated UTC, check an authenticated owner Companion, and apply saved lost-status cadence with bounded awake windows. Persist request tags, routes, and six-hour flood reservations; preserve the four-day cutoff across sleep and restart.

Keep Dispatcher recovery active after a failed radio wake, honor denied flood retries after lost TxDone, and qualify GPS ownership and clock corrections. Add executable regression tests and nRF52/ESP32 Sensor build jobs.

Validation: six firmware builds, 1829 native test cases, sanitized tracker/storage/GPS/sleep/recovery tests, and strict documentation build. Bluetooth Find My advertising and automatic Room mailbox polling remain separate work.
2026-10-10 20:08:58 -07:00
mikecarper 32a994e838 Repair room mailbox adapters in sensor reply bounds tests 2026-10-10 19:48:17 -07:00
mikecarper 3e8c637866 Add Companion lost-status replies and repair room mailbox test adapters
Save an opt-in off/no/yes answer to private Am I lost questions from known
chat contacts. Keep reply traffic and ACK state bounded, preserve pending
user delivery, and expose the setting through local CLI and ESP32 WebConfig.

Retain compact STM32 application/filesystem boundaries, qualify the Tag BLE
queue allowance, and exercise saved-image compatibility and browser controls.
Fix stale room mailbox protocol dependencies in the ACL/route test fixtures.

Validation: ten Companion builds, sanitized production-path regressions,
preference fault/compatibility tests, browser tests, and 86 capacity/build
recipe checks passed. No live radio was flashed for this feature.
2026-10-10 19:24:42 -07:00
mikecarper 33204b768a Add owner-controlled personal room mailboxes and WiFi management 2026-10-10 18:26:13 -07:00
mikecarper a43f48f63c Correct LoRa room board index completion documentation 2026-10-10 17:56:01 -07:00
mikecarper f7b8777e9a Clarify MQTT restoration evidence 2026-10-10 15:52:36 -07:00
mikecarper 584fc9a4d7 Record shared ESP32 MQTT regression and hardware validation 2026-10-10 15:52:04 -07:00
mikecarper d11bf3615d Count runtime WiFi memory in every ESP32 MQTT profile 2026-10-10 15:46:53 -07:00
mikecarper 937f8610c7 Fix MQTT recovery admission and retained client buffer sizing 2026-10-10 15:29:50 -07:00
mikecarper 5a031f7674 Reserve SDK client startup memory before MQTT TLS admission 2026-10-10 15:09:19 -07:00
mikecarper ef80f29337 Expose broker publish acknowledgments for MQTT validation 2026-10-10 15:01:14 -07:00
mikecarper 7acc637468 Preserve MQTT TLS connection time and internal DMA headroom 2026-10-10 14:43:57 -07:00
mikecarper 9a176964dd Keep room ACL test harnesses aligned with reply routing 2026-10-10 11:59:59 -07:00
mikecarper 4606f3da60 Add room catch-up and Wi-Fi administration 2026-10-10 11:51:00 -07:00
mikecarper 0eb1d03a16 Let room users manage their own return routes 2026-10-10 11:27:39 -07:00
mikecarper 5cb8367d12 Update Tower OTA fixture for manifest admission callback 2026-10-10 10:39:40 -07:00
mikecarper 04f98ea44c Reduce neighbor discovery RAM without changing published results 2026-10-10 01:30:09 -07:00
mikecarper 894d033503 Harden recovery authentication and signed OTA admission 2026-10-10 01:25:08 -07:00
mikecarper d031609f26 Improve room retention, moderation and local browser access
Add opt-in bounded chat history, persist explicit room ACL roles, enforce full-key bans and per-user quotas, and provide versioned information-board reads plus an ESP32 local room page. Preserve stored evidence on faults and withhold room delivery acknowledgments when a Companion cannot retain the message.

Exercise production handlers and filesystem fault recovery, pinned official-app JavaScript, real Chromium interactions, and representative ESP32, nRF52, RP2040 and STM32 builds. Add regression suites and bounded browser-workspace RAM reservations to CI.
2026-10-09 23:44:00 -07:00
mikecarper a2a63ea5cc ci: run room app checks after Node and cache setup 2026-10-09 22:32:42 -07:00
mikecarper 6e4b501dd6 fix(room): preserve ACL roles and persist room topics
Allow unknown blank-password guests only when read-only access is enabled,
disable empty configured credentials, and preserve explicit ACL roles on
reconnect unless a valid admin password selects Admin.

Save room topics through verified, recoverable filesystem transactions. Deliver
topics after older unread posts with independent ACK state and bounded retries,
including clock correction and long-uptime timer handling.

Exercise production handlers and pinned unmodified app JavaScript in CI.
Document topic commands and login policy. Native tests (1829), fault-injection
and compatibility suites, Pico W, V4 Full, G2 Full, and RAK4631 Full/Reduced
build qualification passed. Hardware behavior remains untested for this change.
2026-10-09 22:31:35 -07:00
mikecarper 7bdd9adec1 test: verify inherited nRF52 optimizer before firmware suites 2026-10-09 20:50:13 -07:00
mikecarper 6b7812bfc2 test: update bridge lifecycle stand-ins for recovery state 2026-10-09 20:07:06 -07:00
mikecarper ddf625e9d0 Fix packet, delivery, clock and radio stability regressions
Reject truncated anonymous reply paths and advert metadata before copying,
while preserving timestamp-only empty-password logins. Limit room posting to
read-write/admin ACL roles, retain inbox frames until the requesting transport
accepts them, and lock raw LittleFS traversal against concurrent writes.

Separate ISR event generations from main-loop radio state. Confirm RX/TX IRQs,
detach concrete wrapper callbacks safely, and discard inconsistent LR2021 FIFO
snapshots before rearming. Preserve bridge RX frames through packet-pool
exhaustion and bound ESP-NOW retries and missing-callback recovery. Screen RTC
register layouts without losing DS1307 startup, and ignore poisoned future
contact timestamps during clock bootstrap.

Keep the nRF52 loop stack at 8 KiB while reducing the OTA listing call chain
from 7,832 to 6,056 static bytes. Retain all target names through lossless Full
Companion compression and the canonical size optimizer. Preserve constrained
STM32 filesystems and bind corrected ESP32 UART/OLED budgets to source size
assertions. Document the separately measured preexisting W12 package limits.

Adapted for this branch from meshcore-dev/MeshCore PRs #3521, #3340, #3362,
#2062, #3503, #3038, #3534, #3544 and #3512.

Validation: 1,829 native tests; sanitized parser, inbox, LittleFS, ACL, clock,
RTC, IRQ/FIFO and bridge fault tests; 332 additional Companion cases; real
RAK4631 Full and both repeater profiles, G2 Full/standard, V3 Full and both
constrained STM32 release recipes. Radio-family compile coverage continues
separately; W12 full/portable package guards remain enforced.
2026-10-09 19:56:08 -07:00
mikecarper f79a316da8 Cap fleet reply jitter at ten seconds 2026-10-09 02:02:27 -07:00
mikecarper 466eb008c7 Target signed fleet commands by region and GPS radius 2026-10-09 01:32:12 -07:00
mikecarper fdad79eaf7 Support atomic two-packet signed fleet commands
Automatically split longer signed envelopes into two bounded group-data packets, reserve both before queueing, and roll back exact packet copies and retries on admission failure. Collect fragments in either order with a fixed deadline and verify the complete command before durable replay reservation and dispatch.

Raise whole-fleet protocol capacity to 230 bytes, preserve target-specific budgets and radio acknowledgement barriers, and reject rule-token and USB rescue-line truncation. Document existing app input limits and add real-signature, sanitizer, queue failure, timeout, replay, and compact-board regressions.
2026-10-09 00:46:50 -07:00
mikecarper d74a0f8b44 Default fleet commands to compact whole-fleet targets 2026-10-08 23:57:50 -07:00
mikecarper 89f26fe1c9 Support signed fleet radio schedules and clock updates 2026-10-08 23:27:38 -07:00
mikecarper b0c02b1e47 Support public-key prefixes and lists in signed fleet commands 2026-10-08 17:32:05 -07:00
mikecarper d83fa7cf8e Add signed private fleet controls for filtering and secondary radio
Enroll a private channel and publisher public key on repeaters and room servers. Local Companion fleet sends sign targeted or whole-fleet commands, with expiry, durable replay protection, and a restricted command allowlist. Preserve exact transmitted-reply barriers for secondary-radio changes, bound validation and replies, and qualify crypto, persistence, transport scope, role permissions, and compact-profile exclusions with regression tests.
2026-10-08 16:45:19 -07:00
mikecarper a14bbdc58f Fix conditional C++ extraction in web console tests
Preserve mutually exclusive preprocessor branches when extracting production C++ for the browser and HWCDC fixtures. Ignore braces in comments, literals and macro definitions, reject mismatched branch joins, and cover the actual repeater command pump with regression tests. Firmware behavior is unchanged.

Validated 93 extractor, official web console, setup GUI and HWCDC checks plus downstream workflow and real PlatformIO metadata checks.
2026-10-08 15:10:51 -07:00
mikecarper 7a463937ca Reduce shared RAM usage without lowering firmware capacities
Specialize repeater ACL session state, compact recent and flood records, stream Merkle proofs, and release cold OTA buffers safely. Preserve saved formats, stack sizes, table capacities, and OTA source limits. Account for lazy buffers when active and add compatibility, ARM alignment, allocation failure, and queued-response tests.

Validated six representative board/role builds, 1736 native test cases, sanitizer fixtures, and strict documentation. Document the preexisting Wio-E5 Mini flash overflow separately.
2026-10-08 14:41:16 -07:00
mikecarper df88a6ef95 Add opt-in combined RAK4631 Ethernet test image
Keep Full sensors, UART2, adaptive OTA storage and the 8 KiB loop stack. Arbitrate shared SPI and OTA workspace ownership, move network waits into a worker, and use bounded queued TCP operations. Add reproducible test configs, RAM checks and regression coverage. Validated the Full build, UF2 layout and 29 sanitizer runtime scenarios.
2026-10-08 13:53:31 -07:00
mikecarper 57036b1d66 fix(picker): prefer RAK4631 UART2 for new installations 2026-10-08 12:46:08 -07:00
mikecarper 396474bf9c fix(picker): preserve GPS-free RAK UART1 firmware 2026-10-08 01:54:17 -07:00
mikecarper c07d16e398 fix(picker): recommend unified RAK repeater profiles 2026-10-08 01:34:07 -07:00
mikecarper 4bbd613417 Publish 1.17.1.9 USA Cascade picker and Mesh America catalogs
Point both stable providers and runtime controls to the validated v1.17.1.9-halo-keymind-cascade-dev-88c85108 family. Record completed package validation while retaining hardware coverage limitations. Give all documentation source pages their exact hosted links and mount the telemetry form only in the browser. Verify current combined Full and nRF52 Full/Reduced catalog fixtures.
2026-10-07 13:56:17 -07:00
mikecarper d2b6244ae0 Document 1.17.1.9 profiles and WiFi upgrade limits 2026-10-07 07:37:32 -07:00
726 changed files with 100172 additions and 21908 deletions

No files matched your search

+200 -13
View File
@@ -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:
@@ -27,6 +64,7 @@ jobs:
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
@@ -37,6 +75,11 @@ 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
@@ -90,16 +133,71 @@ jobs:
uses: ./.github/actions/setup-build-environment
- name: Install signed-package verification dependency
run: python3 -m pip install cryptography
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 -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
@@ -111,7 +209,9 @@ jobs:
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
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
@@ -122,6 +222,9 @@ jobs:
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: |
@@ -134,16 +237,38 @@ jobs:
- name: Verify transactional telemetry preferences and legacy route recovery
run: python3 -B test/test_telemetry_tx_persistence.py -v
- name: Verify MQTT start results and connection-attempt accounting
run: python3 -B test/test_mqtt_transport_results.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
@@ -158,6 +283,7 @@ jobs:
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
@@ -173,10 +299,23 @@ jobs:
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
@@ -196,6 +335,22 @@ 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
@@ -219,7 +374,7 @@ 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
@@ -242,11 +397,10 @@ jobs:
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
python3 -B test/test_client_acl_infrastructure.py -v
python3 -B test/test_stored_client_reply_routes.py -v
- name: Setup Node for official app compatibility tests
uses: actions/setup-node@v4
@@ -259,10 +413,11 @@ jobs:
path: ~/.cache/meshcore-tests
key: official-clients-84bc39a950735aaffa93d912556852a4c22e235d0cc4e4d04790113207f13a68-aff42ad8ef2b332f24d644ff6a783f8272a88ff2
- name: Verify stock app radio and Access Control compatibility
- 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: |
@@ -273,6 +428,7 @@ jobs:
- 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
@@ -289,6 +445,7 @@ jobs:
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
@@ -302,6 +459,8 @@ jobs:
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
@@ -365,6 +524,7 @@ 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
@@ -410,6 +570,8 @@ jobs:
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
@@ -427,9 +589,12 @@ 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
@@ -443,11 +608,18 @@ 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: |
@@ -487,10 +659,11 @@ jobs:
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_companion_ap_ownership.py -v
python3 -B test/test_heltec_v4_wifi_setup_page.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.
@@ -499,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
+34 -1
View File
@@ -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.
+4 -2
View File
@@ -3,9 +3,11 @@
* **[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.
* **[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.
@@ -72,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))
+7
View File
@@ -39,6 +39,13 @@ 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`.
+18 -12
View File
@@ -3835,18 +3835,13 @@ apply_nrf52_size_profile() {
return 0
fi
# The Adafruit nRF52 platform defaults release builds to -Ofast. Once the
# runtime software Ed25519 fallback is linked alongside CC310, that setting
# fully expands repeated Curve25519 arithmetic and wastes tens of kilobytes.
# Keep hardware crypto, RNG mixing, the software fallback, and board features;
# select the size optimizer for repeaters, constrained self-updating images
# and Full Companion source images, especially their diagnostic profile.
# Full-sensor repeaters also need this after adding the second radio profile;
# changing optimization preserves their sensors and protocol features.
# The legacy RAK4631 Ethernet Companion also needs -Os to fit its declared
# application limit; keep its sensors, transports, OTA and layout unchanged.
# The framework defaults to -Ofast, which expands repeated Curve25519
# arithmetic when software Ed25519 is linked alongside CC310. Inherit the
# canonical nrf52_base -Oz optimizer rather than appending a weaker -Os
# override for repeaters, Full Companion, Ethernet, and constrained OTA
# recipes. Keep hardware crypto, software fallback, sensors, transports,
# OTA, the 8 KiB loop stack, and each board's application/storage layout.
append_platformio_build_unflags "-Ofast"
export PLATFORMIO_BUILD_FLAGS="${PLATFORMIO_BUILD_FLAGS} -Os"
# These color-display repeaters reserve a 64 KiB reset-retained OTA arena
# plus a 25 KiB framebuffer. With the 8 KiB loop stack, the complete sensor
@@ -4180,6 +4175,9 @@ apply_companion_radio_full_profile() {
# Apply the proven bootstrap to every nRF52 Full Companion rather than
# making a board-specific Bluetooth reliability promise.
export PLATFORMIO_BUILD_FLAGS="${PLATFORMIO_BUILD_FLAGS} -DOTA_FOLDER_SERIAL=1 -DCOMPANION_FEATURE_USB_MOTA_SOURCE=1 -DCOMPANION_FEATURE_BLE_MOTA_SOURCE=1 -DMESH_DEBUG=1 -DMESH_PACKET_LOGGING=1 -DCFG_DEBUG=1 -DRECOVERABLE_EXTERNAL_RADIO=1"
# Lossless name compression retains every seeder target and its wire ID
# while recovering flash headroom; reuse the existing bounded OTA decoder.
export PLATFORMIO_BUILD_FLAGS="${PLATFORMIO_BUILD_FLAGS} -DOTA_TARGET_NAME_FRONT_CODED=1"
if [ "$env_name" = "t1000e_companion_radio_full" ]; then
# The T1000-E's primary CDC transport is the only USB interface. Its
# `usb.logging on` selects an input-capable ASCII logging terminal on
@@ -4194,7 +4192,7 @@ apply_companion_radio_full_profile() {
# Every nRF52 Full Companion lends the upper half of its offline queue
# to the cold mOTA context. Keep 256 slots for everyday use and reserve
# runtime space for Bluetooth, displays, tasks and filesystem buffers.
append_platformio_build_unflags "-DOFFLINE_QUEUE_SIZE=512 -DOFFLINE_QUEUE_SIZE=128 -DOFFLINE_QUEUE_SIZE=16"
append_platformio_build_unflags "-DOFFLINE_QUEUE_SIZE=512 -DOFFLINE_QUEUE_SIZE=240 -DOFFLINE_QUEUE_SIZE=128 -DOFFLINE_QUEUE_SIZE=16"
export PLATFORMIO_BUILD_FLAGS="${PLATFORMIO_BUILD_FLAGS} -DOFFLINE_QUEUE_SIZE=256 -DOTA_SHARED_COMPANION_QUEUE=1"
case "$env_name" in
Heltec_t096_companion_radio_full_*)
@@ -4475,6 +4473,11 @@ write_build_capability_manifest() {
|| is_xiao_qspi_canonical_repeater_build "$env_name"; then
checker_args+=(--require-ota)
fi
if [ "$env_name" = "RAK_4631_companion_radio_full" ]; then
# This profile is offered for MeshTalk testing. Check executable codec and
# Companion transport definitions, not a channel name or CLI help string.
checker_args+=(--require-meshtext)
fi
if [ "$env_platform" = "ESP32_PLATFORM" ]; then
checker_args+=(--firmware-bin "${build_output_dir}/firmware.bin"
--partitions "${build_output_dir}/partitions.bin")
@@ -4745,6 +4748,9 @@ collect_build_artifacts() {
;;
esac
python3 scripts/package_meshtext_license.py collect "$build_output_dir" \
--stem "${OUTPUT_DIR}/${firmware_filename}" || return $?
python3 scripts/firmware_memory_manifest.py package "$build_output_dir" \
--stem "${OUTPUT_DIR}/${firmware_filename}" || return $?
}
+82 -9
View File
@@ -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.
File diff suppressed because it is too large. Load diff
+101 -22
View File
@@ -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",
@@ -482,6 +487,11 @@
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"
@@ -571,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",
@@ -603,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;
});
@@ -644,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] || ""));
});
}
@@ -668,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;
}
@@ -1195,6 +1212,7 @@
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]);
@@ -1206,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 = [];
@@ -1667,6 +1738,11 @@
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";
@@ -1677,7 +1753,7 @@
{ 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) {
// Combined images need the transport-specific switch: bridge.enabled
@@ -1935,7 +2011,7 @@
}
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);
@@ -1945,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;
@@ -1970,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);
@@ -2496,6 +2574,7 @@
choicesUseSameFirmware: choicesUseSameFirmware,
selectionRequirements: selectionRequirements,
selectionUrl: selectionUrl,
shortSelectionUrl: shortSelectionUrl,
selectionFromUrl: selectionFromUrl,
canonicalAsset: canonicalAsset,
resolveProfileAssets: resolveProfileAssets,
+32
View File
@@ -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);
}
})();
+56
View File
@@ -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']");
+4
View File
@@ -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>
+4
View File
@@ -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>
+182
View File
@@ -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,
+4
View File
@@ -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>
+4
View File
@@ -1,3 +1,7 @@
<!-- 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
+4
View File
@@ -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:
+26 -4
View File
@@ -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`
@@ -70,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:
@@ -82,6 +101,7 @@ over the normal binary USB, BLE, or TCP connection:
| [`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 |
@@ -189,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 |
@@ -364,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 |
+235 -4
View File
@@ -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
@@ -284,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
@@ -386,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
@@ -2355,6 +2420,8 @@ 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).
@@ -2441,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
@@ -3759,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
@@ -4625,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.
---
@@ -4786,6 +4944,72 @@ 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
**Usage:**
- `get bridge.delay`
@@ -4834,6 +5058,13 @@ 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`
+4
View File
@@ -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>
+4
View File
@@ -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>
+4
View File
@@ -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>
+87
View File
@@ -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.
+12
View File
@@ -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
+38 -2
View File
@@ -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):
```
+24
View File
@@ -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).
@@ -521,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
+4
View File
@@ -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>
+4
View File
@@ -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>
+4
View File
@@ -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.
+4
View File
@@ -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>
+4
View File
@@ -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>
+4
View File
@@ -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.
+55 -1
View File
@@ -1,4 +1,8 @@
# 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
@@ -8,6 +12,20 @@ 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
`default_16MB.csv`. It does not guess a table for other flash sizes. Adding a
@@ -101,6 +119,42 @@ 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
+4
View File
@@ -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
+4
View File
@@ -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
+4
View File
@@ -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
+4
View File
@@ -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>
+40 -16
View File
@@ -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
@@ -21,11 +25,23 @@ 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 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=20261002-1" 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>
@@ -54,7 +70,7 @@ 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">
@@ -130,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>
@@ -233,30 +249,38 @@ 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
@@ -271,7 +295,7 @@ capacity and sensor-profile choices.
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
@@ -465,7 +489,7 @@ pin map, and other physical differences without crowding the Hardware dropdown.
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.
Future nRF52 repeater, room-server and sensor releases publish two separately
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
@@ -512,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
@@ -520,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
+396
View File
@@ -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).
+4
View File
@@ -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
+4
View File
@@ -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>
+4
View File
@@ -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
+4
View File
@@ -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
+4
View File
@@ -1,3 +1,7 @@
<!-- 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
+4
View File
@@ -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
View File
@@ -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
+8
View File
@@ -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>
+4
View File
@@ -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
+4
View File
@@ -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>
+4
View File
@@ -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
+6
View File
@@ -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.
+4
View File
@@ -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.
+4
View File
@@ -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
+4
View File
@@ -1,3 +1,7 @@
<!-- 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 or MGR2 management-report payloads or complete GroupData packets from a
+4
View File
@@ -1,3 +1,7 @@
<!-- 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.
+4
View File
@@ -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>
+4
View File
@@ -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>
+4
View File
@@ -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>
+4
View File
@@ -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>
+4
View File
@@ -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>
+4
View File
@@ -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
+4
View File
@@ -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>
+4
View File
@@ -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>
+4
View File
@@ -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>
+8
View File
@@ -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
+4
View File
@@ -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.
+4
View File
@@ -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
+4
View File
@@ -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
+4
View File
@@ -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
+4
View File
@@ -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`.
+4
View File
@@ -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.
+4
View File
@@ -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
+4
View File
@@ -1,3 +1,7 @@
<!-- meshcore-hosted-doc-link:start -->
<p class="meshcore-hosted-doc-link"><a href="https://mikecarper.github.io/MeshCore/ota_meshtower_v2_sdcard/">View this page on MeshCore Docs</a>.</p>
<!-- meshcore-hosted-doc-link:end -->
# MeshTower V2 microSD LoRa OTA
`Heltec_tower_v2_sdcard_repeater_lora_ota_no_external_sensors` is the one
+4
View File
@@ -1,3 +1,7 @@
<!-- meshcore-hosted-doc-link:start -->
<p class="meshcore-hosted-doc-link"><a href="https://mikecarper.github.io/MeshCore/ota_nrf52_bootloader_update/">View this page on MeshCore Docs</a>.</p>
<!-- meshcore-hosted-doc-link:end -->
# nRF52 bootloader updates over LoRa
For **1.17.1.5**, use the exact board/storage profile from
+4
View File
@@ -1,3 +1,7 @@
<!-- meshcore-hosted-doc-link:start -->
<p class="meshcore-hosted-doc-link"><a href="https://mikecarper.github.io/MeshCore/ota_nrf52_qspi/">View this page on MeshCore Docs</a>.</p>
<!-- meshcore-hosted-doc-link:end -->
# nRF52 repeater LoRa OTA with external QSPI
For **1.17.1.5**, use the exact board/storage profile from
+4
View File
@@ -1,3 +1,7 @@
<!-- meshcore-hosted-doc-link:start -->
<p class="meshcore-hosted-doc-link"><a href="https://mikecarper.github.io/MeshCore/ota_protocol/">View this page on MeshCore Docs</a>.</p>
<!-- meshcore-hosted-doc-link:end -->
# MeshCore OTA - `.mota` container & LoRa protocol
This is the **single source of truth** for MeshCore's over-the-air firmware update system ("mOTA"). It is
+31 -6
View File
@@ -1,3 +1,7 @@
<!-- meshcore-hosted-doc-link:start -->
<p class="meshcore-hosted-doc-link"><a href="https://mikecarper.github.io/MeshCore/ota_user_guide/">View this page on MeshCore Docs</a>.</p>
<!-- meshcore-hosted-doc-link:end -->
# Updating your node over the air (OTA) - user guide
For **1.17.1.5**, use the exact board/storage profile from
@@ -420,6 +424,10 @@ and packet loss; `3` does not guarantee three times the throughput.
The factor covers OTA requests and responses (catalog, manifest, leaves, DATA,
and proofs), OTA relay delays, discovery jitter, and automatic OTA adverts.
Sources queue manifest and leaf replies and admit their fragments one at a
time through the paced transmit queue. Repeated requests merge pending
fragments; a full response queue leaves the request available for another
source or a later retry. This preserves packet space for other mesh traffic.
Below `1`, the transmit queue also spaces out **every OTA packet**, including
both profile copies, while letting ordinary messages run. Recovery waits grow
for slower transfers; faster settings retain the existing retry allowance so
@@ -465,7 +473,7 @@ automation (e.g. for a remote node you can't easily reach), you can opt in. Thes
```
ota config autofetch any # auto-DOWNLOAD any compatible update for this node (still won't install)
ota config autofetch signed # auto-download only signed updates
ota config autofetch signed # auto-download verified updates from allowlisted signers
ota config autofetch off # back to manual (default)
ota config autoinstall trusted # auto-INSTALL only a trusted signed version newer than the running EndF
@@ -480,9 +488,18 @@ ota config hops 0 # only exchange OTA with directly-connected node
ota config # show the current settings
```
These policies also govern automatic adoption of an interrupted staged download after reboot. `off` leaves
it untouched, `signed` requires the stored manifest's signed flag, and automatic resume requires the stored
target to match this node and its version to be newer than the running valid EndF. Reissuing an explicit
With `autofetch signed`, add the author's public key using `ota key add` before
expecting an automatic download. The node verifies the manifest's signature
against that allowlist before planning, erasing, or writing staging storage.
`autofetch any` remains the deliberate option for automatically downloading
unsigned compatible packages; it does not authorize installation.
These policies also govern automatic adoption of an interrupted staged
download after reboot. `off` leaves it untouched; `signed` requires the same
allowlisted signature verification. Automatic resume also requires the stored
target to match this node and its version to be newer than the running valid
EndF. The signature check applies when an automatic download resumes after
discovering a source or reconnecting its storage. Reissuing an explicit
`ota pull <MID8>` remains the deliberate override for an older or unsigned partial.
For bring-up/debugging, `ota dev resume <MID8>` performs the same explicit MID-bound re-adoption without
starting a new network fetch. After reboot it requires the MID; bare `ota dev resume` is accepted only while
@@ -492,6 +509,13 @@ guessing which is newest. Specify the intended MID to select its unique checkpoi
rejected. Temporary storage read failures no longer consume a download slot indefinitely: host storage
pauses for reconnection, while non-reconnectable storage reports a storage error and can be retried.
A failed automatic download starts a cooldown before other automatic candidates
are considered: one minute initially, doubling after further failures up to
15 minutes. Changing the offered image or source does not skip this wait.
The timer uses elapsed uptime, and a completed download clears it. Manual
`ota pull`, explicit resume, and archive captures keep their existing operator
controls and are not restricted by this automatic-download cooldown.
Recommended for most people: leave both **off** and update by hand. Use `autoinstall trusted` only once
you've added the signer's key (next section) and you trust them to push updates unattended. Automatic
admission and final apply both reject zero, equal, or older signed versions; a dishonest catalog version
@@ -509,8 +533,9 @@ for the required `/mota/<manifest-id>.mota` filenames and the complete TempRadio
## Optional: only trust updates from specific people
If you'll use auto-install, tell your node which signing keys to trust. The firmware author shares their
**public** key (a hex string); you add it:
If you'll use signed auto-download or auto-install, tell your node which
signing keys to trust. The firmware author shares their **public** key (a hex
string); you add it:
```
ota key add <public-key-hex> # trust this signer
+22
View File
@@ -0,0 +1,22 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>MeshCore firmware picker</title>
<link rel="canonical" href="../firmware_picker/">
</head>
<body>
<p>Opening the firmware picker. <a id="picker-link" href="../firmware_picker/">Continue to the picker</a>.</p>
<script>
(function () {
"use strict";
const target = new URL("../firmware_picker/", window.location.href);
target.search = window.location.search;
target.hash = window.location.hash;
document.getElementById("picker-link").href = target.href;
window.location.replace(target.href);
})();
</script>
</body>
</html>
+4
View File
@@ -1,3 +1,7 @@
<!-- meshcore-hosted-doc-link:start -->
<p class="meshcore-hosted-doc-link"><a href="https://mikecarper.github.io/MeshCore/packet_format/">View this page on MeshCore Docs</a>.</p>
<!-- meshcore-hosted-doc-link:end -->
# Packet Format
This document describes the MeshCore packet format.
+4
View File
@@ -1,3 +1,7 @@
<!-- meshcore-hosted-doc-link:start -->
<p class="meshcore-hosted-doc-link"><a href="https://mikecarper.github.io/MeshCore/pair_4p6_300us_validation/">View this page on MeshCore Docs</a>.</p>
<!-- meshcore-hosted-doc-link:end -->
# Document moved
<script>window.location.replace(new URL("../test-results/pair_4p6_300us_validation/" + window.location.hash, window.location.href));</script>
+4
View File
@@ -1,3 +1,7 @@
<!-- meshcore-hosted-doc-link:start -->
<p class="meshcore-hosted-doc-link"><a href="https://mikecarper.github.io/MeshCore/pair_preamble32_validation/">View this page on MeshCore Docs</a>.</p>
<!-- meshcore-hosted-doc-link:end -->
# Document moved
<script>window.location.replace(new URL("../test-results/pair_preamble32_validation/" + window.location.hash, window.location.href));</script>
+5
View File
@@ -1,3 +1,7 @@
<!-- meshcore-hosted-doc-link:start -->
<p class="meshcore-hosted-doc-link"><a href="https://mikecarper.github.io/MeshCore/payloads/">View this page on MeshCore Docs</a>.</p>
<!-- meshcore-hosted-doc-link:end -->
# Payload Format
Inside each [MeshCore Packet](./packet_format.md) is a payload, identified by the payload type in the packet header. The types of payloads are:
@@ -112,6 +116,7 @@ For the common chat/server helpers in `BaseChatMesh`, the current request type v
| `0x06` | get neighbors | node neighbors query |
| `0x08` | subscribe | subscribe to telemetry push |
| `0x09` | ubsubscribe | unsubscribe from telemetry push |
| `0x0C` | tracker status | encrypted Sensor dog tracker check to a configured owner's Companion; [wire format](sensor_tracker.md#encrypted-check-format) |
#### Get stats
+4
View File
@@ -1,3 +1,7 @@
<!-- meshcore-hosted-doc-link:start -->
<p class="meshcore-hosted-doc-link"><a href="https://mikecarper.github.io/MeshCore/pr7_review_validation/">View this page on MeshCore Docs</a>.</p>
<!-- meshcore-hosted-doc-link:end -->
# Document moved
<script>window.location.replace(new URL("../test-results/pr7_review_validation/" + window.location.hash, window.location.href));</script>
+4
View File
@@ -1,3 +1,7 @@
<!-- meshcore-hosted-doc-link:start -->
<p class="meshcore-hosted-doc-link"><a href="https://mikecarper.github.io/MeshCore/preamble_detection_investigation/">View this page on MeshCore Docs</a>.</p>
<!-- meshcore-hosted-doc-link:end -->
# Document moved
<script>window.location.replace(new URL("../test-results/preamble_detection_investigation/" + window.location.hash, window.location.href));</script>
+4
View File
@@ -1,3 +1,7 @@
<!-- meshcore-hosted-doc-link:start -->
<p class="meshcore-hosted-doc-link"><a href="https://mikecarper.github.io/MeshCore/preset_test/">View this page on MeshCore Docs</a>.</p>
<!-- meshcore-hosted-doc-link:end -->
# <span data-role="preset-test-page-title">Temporary radio test builder</span>
<span data-role="preset-test-page-summary">Choose a window and radio tuple to
+4
View File
@@ -1,3 +1,7 @@
<!-- meshcore-hosted-doc-link:start -->
<p class="meshcore-hosted-doc-link"><a href="https://mikecarper.github.io/MeshCore/qr_codes/">View this page on MeshCore Docs</a>.</p>
<!-- meshcore-hosted-doc-link:end -->
# QR Codes
This document provides an overview of QR Code formats that can be used for sharing MeshCore channels and contacts. The formats described below are supported by the MeshCore mobile app.
+4
View File
@@ -1,3 +1,7 @@
<!-- meshcore-hosted-doc-link:start -->
<p class="meshcore-hosted-doc-link"><a href="https://mikecarper.github.io/MeshCore/radio_chirp_math_validation/">View this page on MeshCore Docs</a>.</p>
<!-- meshcore-hosted-doc-link:end -->
# Document moved
<script>window.location.replace(new URL("../test-results/radio_chirp_math_validation/" + window.location.hash, window.location.href));</script>
+4
View File
@@ -1,3 +1,7 @@
<!-- meshcore-hosted-doc-link:start -->
<p class="meshcore-hosted-doc-link"><a href="https://mikecarper.github.io/MeshCore/radio_dwell_policy_validation/">View this page on MeshCore Docs</a>.</p>
<!-- meshcore-hosted-doc-link:end -->
# Document moved
<script>window.location.replace(new URL("../test-results/radio_dwell_policy_validation/" + window.location.hash, window.location.href));</script>
+4
View File
@@ -1,3 +1,7 @@
<!-- meshcore-hosted-doc-link:start -->
<p class="meshcore-hosted-doc-link"><a href="https://mikecarper.github.io/MeshCore/radio_profile_switch_validation/">View this page on MeshCore Docs</a>.</p>
<!-- meshcore-hosted-doc-link:end -->
# Document moved
<script>window.location.replace(new URL("../test-results/radio_profile_switch_validation/" + window.location.hash, window.location.href));</script>
+273
View File
@@ -1,3 +1,7 @@
<!-- meshcore-hosted-doc-link:start -->
<p class="meshcore-hosted-doc-link"><a href="https://mikecarper.github.io/MeshCore/radio_profiles/">View this page on MeshCore Docs</a>.</p>
<!-- meshcore-hosted-doc-link:end -->
# Two LoRa profiles on one radio
`radio2` lets one LoRa transceiver alternate between two frequencies or modulation
@@ -237,6 +241,272 @@ its choice. They use reserved bytes in existing saved records, so record
sizes and the phone protocol are unchanged. Older firmware ignores these
bytes and may reset the choices when it saves the records.
## Companion channel-name routing aliases
Companions recognize the case-sensitive prefixes `r2-` and `mt-`, immediately
after the optional leading `#`. Keep the prefix in the app's displayed name.
For MeshCore aliases, the firmware strips exactly one prefix when deriving an
automatic hashtag key:
```text
#emergency -> hashtag key for #emergency
#r2-emergency -> the same key for #emergency
#mt-emergency -> the same key for #emergency only in MeshCore fallback mode
```
Only the app's automatic, name-derived hashtag key is normalized; a supplied
private key is not replaced. Non-hashtag names such as `r2-Private` also retain
their supplied key. Empty or nested prefixes (`r2-`, `#r2-mt-emergency`) are
rejected. MeshCore hashtag names remain case-sensitive.
When radio2 is configured (`rx` or `rxtx`), `r2-` binds the channel to radio2 for
both send and receive, including emergency retransmissions. It overrides global
crossing and cannot be changed with `set tx.channel`; rename the channel to
remove the binding. RX-only radio2 cannot send. If radio2 is off/unconfigured,
ignore the routing prefix and use ordinary MeshCore routing instead. The key
stays the same as radio2 is enabled, disabled, or a temporary window expires.
If both the ordinary and `r2-` slots have the same key, a packet received on
radio2 selects the `r2-` slot; a packet received on radio1 selects the ordinary
slot. Global packet deduplication still delivers each packet once, not once in
each slot. Receiving a radio2-only channel on the wrong profile does not mark
that authenticated packet seen, so it can still arrive later on radio2.
The saved channel record retains the original displayed name and app-supplied
key for editing and sharing. Runtime copies resolve the actual wire key.
Repeater/room/sensor hashtag behavior is unchanged; those nodes can join the
canonical MeshCore channel (`#emergency`). Older Companion firmware does not
recognize these aliases.
### Meshtext channel chat
`MESH_ENABLE_MESHTEXT_CHAT` enables the text-only Meshtext backend in the
Companion. It defaults on for ESP32-S3 **Full Companion** builds using
SX1262 LoRa and for the RAK4631 **Full Companion** starting with 1.17.1.10
preview3. An ESP-NOW primary and other builds default off. RAK4631 1.17.1.10
preview2 and earlier builds did not include this backend. This is not
a replacement for stock peer firmware or a cross-protocol bridge.
In an enabled build, an `mt-` name always requests Meshtext, even if radio2 is
off or setup is blocked. It never falls back to sending MeshCore frames. In a
feature-disabled build, `mt-` remains an ordinary MeshCore naming alias while
radio2 is off; with radio2 configured, MT channel changes/sends are unsupported.
The `r2-` MeshCore behavior above is unchanged in either build.
With the region configured, join `#mt-longfast`, for example, through the app's
normal hashtag-channel editor. A recognized preset name selects the stock
peer channel name (`LongFast`) and, **only for an automatically
name-derived hashtag key**, the expanded public `AQ==` key. This public key is
not a secret. An explicitly
supplied private AES-128 or AES-256 key stays private and unchanged. A custom
name such as `mt-Private` uses the exact tail (`Private`) as its wire name and
the supplied key; custom wire names are limited to 11 bytes and need a manual
radio2 profile or another MT channel with a recognized preset. Preset names
are ASCII case-insensitive; the `mt-` marker is still case-sensitive.
Recognized aliases and modulation are:
| Alias tail | Stock wire name | BW (kHz) | SF | CR |
| --- | --- | ---: | ---: | ---: |
| `shortturbo` | `ShortTurbo` | 500 | 7 | 5 |
| `shortfast` | `ShortFast` | 250 | 7 | 5 |
| `shortslow` | `ShortSlow` | 250 | 8 | 5 |
| `mediumturbo` | `MediumTurbo` | 500 | 9 | 5 |
| `mediumfast` | `MediumFast` | 250 | 9 | 5 |
| `mediumslow` | `MediumSlow` | 250 | 10 | 5 |
| `longturbo` | `LongTurbo` | 500 | 11 | 8 |
| `longfast` | `LongFast` | 250 | 11 | 5 |
| `longmod` or `longmoderate` | `LongMod` | 125 | 11 | 8 |
| `longslow` | `LongSlow` | 125 | 12 | 8 |
The selected region must have room for the bandwidth; for example, 500 kHz
presets are unavailable in the supported EU868 plan. Use these CLI commands:
```text
get mt.status
get mt.region
get mt.slot
get mt.hops
get mt.node
get mt.shortname
set mt.node !12345678
set mt.node default
set mt.region auto
set mt.region us
set mt.slot 0
set mt.hops 7
set mt.shortname HALO
set mt.shortname default
send mt.nodeinfo 0
get radio2
set radio2 auto
```
`mt.region` accepts `auto`, `us`, `eu868`, `eu433`, or `anz`. `auto` infers US
only from a primary frequency at least 902 MHz and below 915 MHz, EU868 from
869.4–869.65 MHz, and EU433 from 433–434 MHz. The US/ANZ overlap at 915–928 MHz
is ambiguous: explicitly select `us` or `anz`. `mt.slot` is a 1-based frequency
slot, or `0` for the stock preset-name-derived slot; unavailable slots are
blocked. Check `get mt.status` after configuration; a successful settings save
does not mean the MT backend is active.
Outgoing MT messages default to a hop limit of **7**. `set mt.hops <0..7>`
changes this budget, and `get mt.hops` or `get mt.status` shows it. `0` sends
only the initial over-air hop with no forwarding budget; larger values permit
protocol-compatible peers to forward within that budget. The Companion itself still
does not relay MT packets.
`get mt.node` shows the effective Meshtext sender node ID. By default it is
SHA-derived from the MeshCore public identity and remains stable for that
identity. `set mt.node !12345678` sets a saved override; exactly eight hexadecimal
digits are required, with or without the leading `!`. Input is hexadecimal,
not decimal, and is never silently truncated. IDs `00000000` through `00000003`
and `ffffffff` are reserved and rejected. `set mt.node default` clears the
override and restores the identity-derived ID.
The example ID is illustrative: choose an ID unique among your protocol-compatible
peers. Collisions can merge different senders into one identity and interfere
with packet deduplication. Changing the MT ID does not alter the MeshCore
private key, reset the outgoing packet-ID sequence or send automatic traffic.
Settings changes are refused while an MT transmission is
pending. Neither node IDs nor text labels are authenticated.
`set mt.shortname <label>` saves a Meshtext-only NodeInfo name of 1–4 Unicode
scalar characters, with valid UTF-8 and at most 16 encoded bytes. Combining
characters count separately. `get mt.shortname` shows the saved/effective
name. Stock NodeInfo accepts only four UTF-8 **bytes**, so the effective wire
name is the longest complete UTF-8 prefix that fits. The saved value is not
changed. `set mt.shortname default` clears the override and uses the last four
hexadecimal digits of the effective MT node ID. This does not change the
MeshCore node name, add a label to chat text or transmit anything.
`send mt.nodeinfo <channel slot>` explicitly queues **one** standard
`NODEINFO_APP` broadcast on the selected configured MT channel. The slot is
the Companion channel index, not `mt.slot` (the frequency slot). The packet
contains the actual sender ID, the global MeshCore node name as its long name
(UTF-8-safe truncated to 24 bytes), the effective short name, `CLIENT_MUTE`
(no relaying), and the no-DM flag. It omits MAC, public keys and unsupported
metadata. The usual radio2 TX-mode, pending-TX, airtime and profile guards
apply. The reply says **queued**, not delivered or acknowledged.
There are no automatic NodeInfo advertisements: not at boot, on a timer, when
names change, when chat is sent, or in response to received packets. Received
NodeInfo is still ignored; this backend does not maintain a node database.
Region, frequency slot, hop limit, shortname and node-ID override are saved
atomically together; changing one preserves the others. Current 40-byte `MT03`
settings include both overrides. New firmware accepts valid legacy
`MT01`/`MT02` settings and defaults the shortname to the sender-ID suffix and
the node ID to the MeshCore identity; `MT01` also defaults the hop limit to 7.
Older MT firmware cannot read `MT03`, so downgrading does not preserve access
to these settings. Invalid or unsupported stored settings block MT operation
rather than silently selecting another network.
Private channels can be provisioned directly through the Companion CLI:
```text
set mt.preset <longfast|shortturbo|...>
set mt.channel <slot> <mt-name> <32/64 hex digits|AQ==>
get mt.channel <slot>
delete mt.channel <slot>
```
`set mt.preset` accepts the recognized presets above and explicitly saves a
manual `rxtx` radio2 RF tuple using the current region and frequency slot. It
is not an automatic owner: deleting the channel does not remove that saved RF
configuration. Set region/slot first; changing them later does not overwrite
the manual frequency, so rerun `set mt.preset` if you intend to retune it.
`set mt.channel` accepts a 32-digit hexadecimal AES-128 PSK, a 64-digit
hexadecimal AES-256 PSK, or the literal `AQ==` for the public key. It does not
accept arbitrary Base64 private keys. The stored format interprets an all-zero
upper 16-byte half as AES-128, including in a 64-digit input; use a nonzero
upper half for AES-256. Slot numbers are zero-based and build-specific, as in
the app. `get mt.channel` reports name, wire name and AES size, never the PSK.
The firmware console masks key input echo; keep your client-side logs and
stored keys private as well. SET and DELETE save transactionally and restore
the previous channel if storage fails. DELETE applies only to an MT channel,
not a MeshCore slot.
For a private channel named `Team` on a protocol-compatible peer:
```text
set mt.region us
set mt.preset longfast
set mt.channel 1 mt-Team <your-32-or-64-hex-digit-key>
get mt.channel 1
get mt.node
set mt.shortname HALO
get mt.status
send mt.nodeinfo 1
channel 1 Hello
```
Replace the key placeholder with your actual hexadecimal PSK and send after
`get mt.status` reports active. The peer must use the same preset, frequency,
PSK and exact case-sensitive wire name `Team`; `mt-` is only the local marker.
Alternatively, `set mt.channel 1 mt-longfast <your-key>` preserves a private
PSK but uses wire name `LongFast` and can acquire automatic RF without
`set mt.preset`, provided radio2 automatic ownership is permitted. This route
is available for both 128-bit and 256-bit private keys even when the stock app
cannot provision or export the latter.
When radio2 was never manually configured, a recognized preset can acquire an
automatic `rxtx` profile. Its RF tuple is RAM-only and stays available for RX
while the required channels exist. Removing the last MT channel, losing its
last recognized-preset dependency, or detecting a conflict releases only the
automatic profile. Explicit manual RF settings are retained: named aliases
must match their BW/SF/CR and fit the selected region, but do not overwrite
their frequency. The runtime MT overlay uses its required 16-symbol preamble;
the saved manual preamble is retained.
`set radio2 off` is an explicit manual veto, including after reboot. Older
saved off profiles are conservatively treated as manual. Local
`set radio2 auto` clears the saved manual RF/off choice and permits automatic
ownership after two seconds; it rejects pending changes, leases and schedules
rather than cancelling them. `get radio2` distinguishes `auto-owned`, `off (manual)`
and `off (automatic allowed)`. Temporary profiles, scheduled changes and reply
commit barriers take precedence and can suspend MT operation. Automatic
cleanup does not undo later manual edits.
All MT channels share this one secondary modem profile. Different recognized
presets, or mixing any MT and `r2-` channels, are rejected instead of selecting
whichever channel was edited last. Loaded conflicts also block MT traffic.
Neither `radio2.cross on` nor MeshCore channel routes cross MT frames between
protocols.
The app uses its existing channel index/text commands and receive events.
Outgoing MT chat is plain text exactly as entered, with the sender node ID in
the standard radio header. It contains no custom name envelope or NodeInfo.
Incoming text uses the exact sender-ID prefix `!12345678: ` before the received
payload when passed to the MeshCore app. Changing the shortname only changes
future manually requested NodeInfo, not chat or the packet's sender ID;
`set mt.node` changes the actual Meshtext sender ID. Neither setting change
emits advertisements. A successful
send response means **queued**, not acknowledged by a remote node. This backend
does not relay MT packets or provide ACKs, administrative commands, direct
messages, automatic node-info adverts or a node database. MeshCore emergency forwarding
does not apply to MT traffic. Interoperability is with stock peers' PSK channel
text using AES-CTR, not AES-CCM. AES-CTR channel traffic is not authenticated;
successful decoding is not proof of sender identity.
The codec supports AES-128 and AES-256 and up to 228 UTF-8 bytes of wire text,
without an added label/node-ID prefix. The standard app channel editor
still provisions 16-byte keys. An MT-only 32-byte binary SET/opt-in GET
extension is available for capable clients, with protection against legacy
16-byte edits truncating an existing 256-bit key; see
[Companion protocol](companion_protocol.md#4-set-channel). This does not give
stock apps seamless 256-bit provisioning or sharing. Current V3 Companion
receive events carry at most 165 text bytes **including the node-ID prefix**;
longer incoming MT text is UTF-8-safe truncated for the app.
Radio1 and radio2 still time-share **one physical radio**. The device cannot
receive either network while tuned to the other or transmitting; long MT
airtime, particularly LongFast, can leave substantial MeshCore receive gaps.
The compatible protocol's fixed short preamble also does not provide the scan-aware long
preamble described below. Do not treat this as continuous coverage of both
networks or guaranteed message delivery.
## Choose whether traffic crosses between profiles
```text
@@ -253,6 +523,9 @@ set radio2.cross off
| `radio` | `tempradio2` | Isolated | Cross | Isolated |
| `tempradio` | `radio2` | Isolated | Cross | Isolated |
This table describes MeshCore profiles. A Meshtext secondary profile is
always isolated, including with `radio2.cross on`.
These are the global rules used by Companion targets set to `auto`; an explicit
contact/channel choice above overrides crossing for its own outbound packets.
Ordinary crossing respects `rx`. The infrastructure-only `tx.reply ... force`
+4
View File
@@ -1,3 +1,7 @@
<!-- meshcore-hosted-doc-link:start -->
<p class="meshcore-hosted-doc-link"><a href="https://mikecarper.github.io/MeshCore/radio_profiles_validation/">View this page on MeshCore Docs</a>.</p>
<!-- meshcore-hosted-doc-link:end -->
# Document moved
<script>window.location.replace(new URL("../test-results/radio_profiles_validation/" + window.location.hash, window.location.href));</script>
+4
View File
@@ -1,3 +1,7 @@
<!-- meshcore-hosted-doc-link:start -->
<p class="meshcore-hosted-doc-link"><a href="https://mikecarper.github.io/MeshCore/radio_receive_calibration/">View this page on MeshCore Docs</a>.</p>
<!-- meshcore-hosted-doc-link:end -->
# Document moved
<script>window.location.replace(new URL("../test-results/radio_receive_calibration/" + window.location.hash, window.location.href));</script>
+4
View File
@@ -1,3 +1,7 @@
<!-- meshcore-hosted-doc-link:start -->
<p class="meshcore-hosted-doc-link"><a href="https://mikecarper.github.io/MeshCore/rak3401_1.17.1.7_legacy24/">View this page on MeshCore Docs</a>.</p>
<!-- meshcore-hosted-doc-link:end -->
# Document moved
<script>window.location.replace(new URL("../research/rak3401_1.17.1.7_legacy24/" + window.location.hash, window.location.href));</script>
+4
View File
@@ -1,3 +1,7 @@
<!-- meshcore-hosted-doc-link:start -->
<p class="meshcore-hosted-doc-link"><a href="https://mikecarper.github.io/MeshCore/rak3401_mota_chain/">View this page on MeshCore Docs</a>.</p>
<!-- meshcore-hosted-doc-link:end -->
# Document moved
<script>window.location.replace(new URL("../research/rak3401_mota_chain/" + window.location.hash, window.location.href));</script>
+4
View File
@@ -1,3 +1,7 @@
<!-- meshcore-hosted-doc-link:start -->
<p class="meshcore-hosted-doc-link"><a href="https://mikecarper.github.io/MeshCore/releases/1.17.1.5-doc-audit/">View this page on MeshCore Docs</a>.</p>
<!-- meshcore-hosted-doc-link:end -->
# Release notes moved
<script>window.location.replace(new URL("../../old-releases/1.17.1.5-doc-audit/" + window.location.hash, window.location.href));</script>
+4
View File
@@ -1,3 +1,7 @@
<!-- meshcore-hosted-doc-link:start -->
<p class="meshcore-hosted-doc-link"><a href="https://mikecarper.github.io/MeshCore/releases/1.17.1.5/">View this page on MeshCore Docs</a>.</p>
<!-- meshcore-hosted-doc-link:end -->
# Release notes moved
<script>window.location.replace(new URL("../../old-releases/1.17.1.5/" + window.location.hash, window.location.href));</script>
+4
View File
@@ -1,3 +1,7 @@
<!-- meshcore-hosted-doc-link:start -->
<p class="meshcore-hosted-doc-link"><a href="https://mikecarper.github.io/MeshCore/releases/1.17.1.6/">View this page on MeshCore Docs</a>.</p>
<!-- meshcore-hosted-doc-link:end -->
# Release notes moved
<script>window.location.replace(new URL("../../old-releases/1.17.1.6/" + window.location.hash, window.location.href));</script>
+4
View File
@@ -1,3 +1,7 @@
<!-- meshcore-hosted-doc-link:start -->
<p class="meshcore-hosted-doc-link"><a href="https://mikecarper.github.io/MeshCore/releases/1.17.1.7/">View this page on MeshCore Docs</a>.</p>
<!-- meshcore-hosted-doc-link:end -->
# Release notes moved
<script>window.location.replace(new URL("../../old-releases/1.17.1.7/" + window.location.hash, window.location.href));</script>
Loaded 100 of 726 files, more files were not shown because too many files have changed in this diff. Show more