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
mikecarper 88c8510800 Preserve enabled RX gain from compact 1.14.1 preferences 2026-10-07 05:22:54 -07:00
mikecarper 8c4e09275e fix(esp32): defer station maintenance while an AP owns WiFi
Starting or restarting MQTT during browser OTA could remove the active AP by forcing STA mode. Defer shared MQTT startup and recovery, and Companion WiFi startup, pending credential reloads and saved-SSID retries, until the live SDK AP releases the driver. Preserve the setup owner's deliberate credential handoff and cooperative stop acknowledgement.

Validated 91 source host tests, including 20 new AP ownership cases and nine reverted controls under ASan/UBSan. Hardware and firmware build qualification remain separate; the Arduino mode snapshot is not atomic.
2026-10-07 02:44:18 -07:00
mikecarper bd4cbf281f Keep ESP32 access points compatible with LR transports
Protect standard access points from the shared ESP-IDF LR flag, defer incompatible raw ESP-NOW starts, and select RAM storage before LR writes. Track successful LR owners so setup and OTA access points reject a conflicting running transport before changing WiFi.

Run actual production policies, transport lifecycle methods, and WebConfig teardown branches against a shared-LR SDK model with runtime negative controls; include the regression in unit CI.
2026-10-07 02:07:06 -07:00
mikecarper 4c5f58f4b5 Keep WiFi test recovery retrying until the original network returns 2026-10-06 21:11:38 -07:00
mikecarper 3ab690b508 Test MQTT power saving through canonical credential snapshots 2026-10-06 20:22:18 -07:00
mikecarper af56405d5e Prepare Linux native Web Serial tests after pyserial 2026-10-06 20:20:31 -07:00
mikecarper 80dba6298d Test shared WiFi reader silence on fresh ESP32 NVS 2026-10-06 19:55:17 -07:00
mikecarper 69869ef398 Consolidate branch feature summaries 2026-10-06 19:43:01 -07:00
mikecarper 1e421ff4fc Fix MQTT startup after ESP32 WebConfig provisioning 2026-10-06 19:28:10 -07:00
mikecarper 745aeb1758 Combine Full profiles into one linked overview 2026-10-06 19:04:59 -07:00
mikecarper aebab7d46b Clarify WiFi setup before configuring Full MQTT brokers 2026-10-06 19:02:59 -07:00
mikecarper 87d51d3d64 Explain Full firmware profiles in one guide 2026-10-06 18:57:34 -07:00
mikecarper c9ca39b5dd Link Cascade features and document Full profiles and LiFePO4 protection 2026-10-06 18:44:42 -07:00
mikecarper c5fe8a0edd Commit WiFi OTA only after the complete upload request 2026-10-06 17:14:48 -07:00
mikecarper 70bf4bdfeb Preserve multipart OTA MD5 after Update initialization
Arduino-ESP32 2.0.17 UpdateClass::begin() clears _target_md5 before
initializing the digest builder (Updater.cpp:123-126). The installed pinned
framework package is 3.20017.241212+sha.dcc1105b. Setting the multipart
digest before begin therefore skipped the final comparison in end(),
which only compares a nonempty _target_md5 (Updater.cpp:303-308).

Set the supplied MD5 after Update.begin succeeds. If the digest is rejected,
abort the begun update, release upload admission, and return HTTP 400 before
assigning an owner or extending its receive timeout.

Model the pinned begin-clears-digest behavior in the host fixture using
independent digest vectors. Verify malformed and mismatched digests, actual
byte corruption, disconnect cleanup, and later valid admission. Negative
controls restore the original ordering and remove abort/busy cleanup.

Validation: python3 -B test/test_async_web_lifecycle.py -v passed 4/4,
including C++ ASan/UBSan execution and negative controls. No PlatformIO or
hardware validation was run in this worktree.
2026-10-06 14:42:20 -07:00
mikecarper be2ab346b0 Terminate DNS diagnostics after partial address conversion 2026-10-06 13:18:38 -07:00
mikecarper 3ede9b2155 Regenerate WebConfig assets for DNS diagnostics 2026-10-06 13:12:08 -07:00
mikecarper 5d5941fc4d Run DNS restoration and diagnostics regressions in CI 2026-10-06 13:11:15 -07:00
mikecarper 52c70994d8 Preserve valid DNS leases and cover bounded diagnostics 2026-10-06 13:11:15 -07:00
mikecarper 0724e2efa3 Test repeater hidden discovery and observer compatibility 2026-10-06 13:02:20 -07:00
mikecarper 7d0984cc90 Merge observer DNS diagnostics and complete resolver restoration 2026-10-06 12:58:28 -07:00
mikecarper 771e86741c Merge IoT power updates while retaining configured role defaults
Keep the current runtime RXPS preference layout, configurable role defaults, board FEM policy, and persisted radio overrides. Power saving and Companion Balanced RXPS already match the incoming policy; the retired fixed Companion helper has been replaced by runtime controls.
2026-10-06 12:57:30 -07:00
mikecarper 3bb7c7bdd8 Merge upstream dev while preserving local GPS and USB recovery 2026-10-06 12:56:19 -07:00
mikecarper 825eb592f7 test: extract USB sleep helper for official web console 2026-10-06 12:26:36 -07:00
mikecarper 1ecceeb686 test: verify canonical Full matrix grouping 2026-10-06 12:07:53 -07:00
mikecarper 6978b354cc Merge remote-tracking branch 'origin/keymindCascade' into keymindCascade 2026-10-06 11:59:54 -07:00
mikecarper 20a75af1fa Record ESP32 power and AP regression qualification 2026-10-06 11:59:32 -07:00
mikecarper 9c54621b63 Merge remote-tracking branch 'origin/keymindCascade' into keymindCascade 2026-10-06 11:58:31 -07:00
mikecarper 99ec3a9bbf test: qualify Full fixtures for local release staging 2026-10-06 11:57:21 -07:00
mikecarper f8e04f090b test: resolve inherited C6L USB console flags 2026-10-06 11:51:39 -07:00
mikecarper 219e936ef8 Preserve requested ESP-NOW across manual setup and master restore 2026-10-06 11:46:10 -07:00
mikecarper 97f16dedd0 Fix ESP32 Full idle radio teardown and compact AP startup 2026-10-06 11:46:10 -07:00
mikecarper 5a7c07a6ce Use real Mesh Solar hardware in resume regression fixture 2026-10-06 11:30:49 -07:00
mikecarper d6ec787ba7 Honor explicit resume for ordinary firmware builds 2026-10-06 11:29:52 -07:00
mikecarper 77f0842830 Keep T-Beam 1W outside programmable charger support 2026-10-06 11:23:03 -07:00
mikecarper 1adf0c6d01 Scope charger controls to T-Beam and Mesh Solar flash budgets 2026-10-06 11:22:03 -07:00
mikecarper 79167af8e6 Update GPS request fixture for bounded LPP replies 2026-10-06 11:21:56 -07:00
mikecarper a5f4158b36 Clarify charger battery ratings and keep host checks portable 2026-10-06 11:05:05 -07:00
mikecarper ad2bc929d6 Cover every T-Beam charger boot read failure 2026-10-06 10:57:33 -07:00
mikecarper f5cda7337c Merge branch 'codex/charge-voltage-controls' into keymindCascade 2026-10-06 10:56:32 -07:00
mikecarper c97dbd791a Add persistent T-Beam charge voltage controls and report Mesh Solar limits 2026-10-06 10:56:32 -07:00
mikecarper 9202fc2a96 Simplify firmware selection and canonical build menus 2026-10-06 10:46:11 -07:00
mikecarper 08bf174ea3 Merge branch 'keymindCascade' of github.com:mikecarper/MeshCore into keymindCascade 2026-10-06 10:23:28 -07:00
mikecarper 19379eb9b8 Use C6L native USB and recover diagnostics-only Companion sessions 2026-10-06 10:14:59 -07:00
mikecarper db42a998f8 Publish Full-only ESP32 node releases and separate migration tools 2026-10-06 10:14:54 -07:00
mikecarper 5990b4c5fd Keep telemetry permission fixtures aligned with production prefs 2026-10-06 10:11:41 -07:00
mikecarper 7581fa3abd Cover WiFi scan fixes across every ESP32 recipe 2026-10-06 09:58:40 -07:00
mikecarper df5e591319 Bound telemetry replies and preserve Sensor send retries 2026-10-06 09:56:00 -07:00
mikecarper 2ad479ce0e Merge branch 'keymindCascade' of github.com:mikecarper/MeshCore into keymindCascade 2026-10-06 09:43:16 -07:00
mikecarper 2ee62b9814 Fix V4 setup AP startup and pre-AP network scans 2026-10-06 09:40:28 -07:00
mikecarper 24a223314c Identify failed resume qualification gates safely 2026-10-06 09:29:48 -07:00
mikecarper ba6e48b5ce Fix Room and Sensor requests and add G3 Sensor builds
Bound ACL replies for encrypted routes, authorize Sensor ACL access using the actual Admin role, and preserve request retries after failed queue admission. Validate stored paths for replies, while keeping Room keep-alives direct-only.

Add both G3 RF-slot Sensor profiles, suppress battery alarms on unavailable measurements, and remove the unsupported Sensor WebConfig capability. Add executable regression coverage and CI checks.
2026-10-06 09:20:05 -07:00
mikecarper 3baa169fc6 Preserve qualified release artifacts across resumed builds 2026-10-06 09:19:02 -07:00
Kevin Le 78e134b2ba Only kept enabled, level and preamble for RXPS defaults 2026-10-06 20:33:33 +07:00
mikecarper 8d31b3932d Record user-confirmed V4 button screen wake 2026-10-06 00:12:21 -07:00
mikecarper 3a7d273d49 Record updated V4 build and native console qualification 2026-10-06 00:06:30 -07:00
mikecarper ebf2f0823e Wake Heltec V4 buttons from power-saving sleep 2026-10-05 23:51:26 -07:00
mikecarper c88885a315 Record qualified G2 OTA transitions and power-saving idle checks 2026-10-05 22:30:30 -07:00
mikecarper 95913c5301 Retry saved WebConfig WiFi when the MQTT worker is absent 2026-10-05 22:10:36 -07:00
mikecarper d2983762bc Keep admitted WiFi OTA uploads alive through receive gaps 2026-10-05 21:45:06 -07:00
mikecarper e971beabc5 Avoid reset pulses when serial test collectors reopen UART bridges 2026-10-05 20:44:17 -07:00
mikecarper 757f15fcb6 Preserve raw UART console input while power saving is enabled 2026-10-05 20:11:10 -07:00
mikecarper 8fe83ebc29 Document raw-UART console limits during power saving 2026-10-05 19:47:00 -07:00
mikecarper 77f8416bb6 Verify asynchronous MQTT controls in wireless regressions 2026-10-05 19:33:49 -07:00
mikecarper 8842b86a46 Keep bridge admission fixtures aligned with cooperative shutdown 2026-10-05 19:18:03 -07:00
mikecarper 1d0ae06800 Keep the console responsive during MQTT shutdown 2026-10-05 19:05:58 -07:00
mikecarper 933bdf7cbe Resolve HIL USB device links before setting modem signals 2026-10-05 18:46:33 -07:00
mikecarper 1db09f41ac Add channel-nine-only notification builder preset 2026-10-05 18:41:27 -07:00
mikecarper 46e6bb7b71 Document channel-nine-only notification example 2026-10-05 18:33:56 -07:00
mikecarper 8af0511f54 Preserve separate TLora Full transports within RAM budget 2026-10-05 18:33:34 -07:00
mikecarper 51d74a200e Retain independent RS232 in Full MQTT repeaters 2026-10-05 18:21:42 -07:00
mikecarper 28023c7302 Install crypto dependency for OTA unit tests 2026-10-05 17:54:31 -07:00
mikecarper bec874f917 Keep independent bridge choices in firmware picker 2026-10-05 17:54:31 -07:00
mikecarper 5f10e7d601 Include runtime ESP-NOW in Full ESP32 repeaters 2026-10-05 17:33:02 -07:00
mikecarper 7ef67c49f5 Combine MKE S3 repeater bridge modes at runtime 2026-10-05 17:12:10 -07:00
mikecarper 30fe0d617b Unify Tower repeater builds on SD-primary OTA storage
Keep the deployed SD target and layout, select qualified internal delta storage when no card is usable, and retain exact SD bootloader-update gates. Add storage, capability, transfer, recipe, and release-selection regression coverage.

Also preserve mandatory runtime packet logging in canonical XIAO images when verbose build debugging is disabled, fixing the four stopped release-matrix qualification failures.
2026-10-05 16:38:30 -07:00
mikecarper 7d3ee7200f Keep nRF52 GPS discovery from blocking USB setup 2026-10-05 15:01:40 -07:00
mikecarper 853bf2e1d9 Align host fixtures with ESP32 metadata and OTA ownership 2026-10-05 15:01:40 -07:00
mikecarper b4a77c6996 Fix startup trace dependency in shared CLI test harness 2026-10-05 14:48:00 -07:00
mikecarper 5f20fee396 docs: clarify radio watchdog flag in final USB validation 2026-10-05 14:29:46 -07:00
mikecarper e3cb2c2bba docs: record native ESP32 GUI startup and idle qualification 2026-10-05 14:27:55 -07:00
mikecarper 631f2ad973 Check boot filesystem lifetime and ordinary ESP32 build inclusion 2026-10-05 14:12:59 -07:00
mikecarper 0b9a80d7ed Avoid repeated missing SPIFFS scans during ESP32 startup 2026-10-05 14:12:44 -07:00
mikecarper 49f698b452 Run stock setup GUI and ESP32 startup recovery checks in CI 2026-10-05 14:09:01 -07:00
mikecarper b259b10553 test: cover stock config GUI startup handshake deadline 2026-10-05 14:08:24 -07:00
mikecarper a8b39daafb Treat absent ESP32 radio profile files as missing 2026-10-05 13:53:59 -07:00
mikecarper c8c5f87ab9 Keep ESP32 USB startup responsive during optional GPS discovery 2026-10-05 13:31:28 -07:00
mikecarper 163364e528 Guard startup resets in the pinned Arduino 3.1.3 HWCDC driver 2026-10-05 13:08:55 -07:00
mikecarper a4e3a530cb Capture HWCDC startup resets in the pinned Arduino 3.3.11 driver 2026-10-05 12:59:37 -07:00
mikecarper 68a27a2ebb Preserve the first HWCDC console session after flashing 2026-10-05 12:42:58 -07:00
mikecarper af759be715 Record physical browser OTA qualification and remaining console limits 2026-10-05 12:25:30 -07:00
mikecarper 7821329851 Keep OTA headers aligned with the checked-out vendored source 2026-10-05 11:56:00 -07:00
mikecarper e95cf6d618 Complete browser OTA responses before reboot and allow HTTP restarts 2026-10-05 11:52:24 -07:00
mikecarper 8fbf948d68 Restore ordinary WiFi protocols for browser OTA hotspots 2026-10-05 11:23:01 -07:00
mikecarper 64a09cbada Require physical web console and browser OTA release checks 2026-10-05 11:19:14 -07:00
mikecarper 0529a461f2 Keep automatic ESP-NOW retries paused during browser OTA 2026-10-05 11:06:30 -07:00
mikecarper a294f01a5d Restore configured ESP-NOW bridge after browser OTA stops 2026-10-05 10:47:37 -07:00
mikecarper bd7ce50e7b Preserve healthy OTA access points and drain stopping uploaders safely 2026-10-05 10:44:50 -07:00
mikecarper ec83da079d Merge branch 'keymindCascade' into fix/web-console-1.17.1.9 2026-10-05 10:40:13 -07:00
mikecarper c38ba5f8d0 Make ESP32 OTA startup handle WiFi and WebConfig automatically
Report the upload address and required network, honor AP switches on an existing uploader, and keep delayed WebConfig cleanup from disabling OTA WiFi. Preserve actionable startup errors and account for OTA in wireless state controls.

Exercise the production OTA lifecycle and stock website console, including power-saving idle sessions, in host regression tests.
2026-10-05 10:31:43 -07:00
mikecarper 2c806f19f9 Add WiFi OTA lifecycle regression coverage 2026-10-05 09:47:11 -07:00
mikecarper efed592fdb Qualify ESP-NOW RAM and Wio Mini release capacity 2026-10-05 07:18:22 -07:00
mikecarper 64885dab28 Preserve download-only Mesh America hardware cards 2026-10-05 05:34:08 -07:00
mikecarper 74a2bb0723 Support pinned Arduino 3 filesystem buffer checks 2026-10-05 05:33:09 -07:00
mikecarper 19d02c0dfa Preserve flood policies across qualified RAM capacity changes 2026-10-05 03:33:44 -07:00
mikecarper 982f96ca96 Avoid Companion request tag collisions after login 2026-10-05 01:47:05 -07:00
mikecarper cc4bcb9cad Qualify 1.17.1.9 memory limits and exact release asset routing 2026-10-05 01:14:02 -07:00
mikecarper 835c7a8e6e Migrate ESP32 native USB roles to bounded hardware CDC
Switch eight S3 board defaults and 78 remaining TinyUSB recipes to hardware Serial/JTAG while preserving UART and other platforms. Restore retained S3 PHY ownership after a TinyUSB OTA restart and prepare KISS queues before enabling the driver.

Add real recipe resolution, Companion/KISS transport sanitizer coverage, and RF lifecycle reset regressions to CI. Native tests pass 1765/1765; SDK, transport, recipe and USB watchdog checks pass. Board and role cross-compilation and physical OTA qualification continue.
2026-10-04 23:36:54 -07:00
mikecarper 4939de4c61 Keep hardware CDC packet log lines whole under congestion 2026-10-04 21:40:43 -07:00
mikecarper 672e0ae777 Test hardware CDC capacity and stalled-host writes in CI 2026-10-04 21:22:51 -07:00
mikecarper a3d8045199 Use bounded hardware CDC for the Full G2 repeater console 2026-10-04 21:11:18 -07:00
agessaman a24dade1c0 fix(network): snapshot and restore every lwIP DNS slot
DHCP can fill all DNS_MAX_SERVERS (3) slots, but only two were saved and restored, so a third server from one medium survived a switch to the other. link.dns now drops trailing empty slots to keep remote replies under 160 bytes.
2026-10-04 20:45:07 -07:00
agessaman 274997e9ae feat(cli): add get link.dns for resolver, gateway and lease DNS
Shows lwIP's live global DNS server list next to the DNS each medium's DHCP lease installed, so a node that is linked but failing to resolve can be diagnosed remotely.
2026-10-04 19:55:54 -07:00
mikecarper 841571a7d8 Cache verified nRF52 metadata for OTA status displays 2026-10-04 17:34:16 -07:00
mikecarper a14a0ad81d Reduce ESP ACL save metadata opens while preserving verified recovery 2026-10-04 15:57:10 -07:00
mikecarper 7a6c7c2b40 Update remote CLI identity fixture for ACL reply admission 2026-10-04 15:24:29 -07:00
mikecarper e1b439ab82 Fit padded ACL replies through the Companion frame envelope 2026-10-04 14:57:15 -07:00
mikecarper c747c15ffc Test radio and Access Control against pinned official app code 2026-10-04 14:47:10 -07:00
mikecarper b9ba35f9d7 Update USB lifecycle fixtures for stream mode and host deadlines 2026-10-04 14:39:38 -07:00
mikecarper 0cc561b0d8 Include runtime USB endpoint selection in nRF52 fixture 2026-10-04 14:37:06 -07:00
mikecarper d46fb879f1 Keep Companion replies alive for the official app timeout window 2026-10-04 14:34:28 -07:00
mikecarper e4420519a4 Fit Access Control replies to encrypted packet route budgets 2026-10-04 14:24:03 -07:00
mikecarper 9268c57f8c Support stock serial MQTT bridges on Full Companion 2026-10-04 14:14:53 -07:00
mikecarper ec8f82e918 Fix USB regression extraction across nested SDK guards
Inspect the complete exclusive TX kick and wrapper functions instead of stopping at the first nested endif. Retain the existing transport guards and verify the pinned SDK pending/kick hooks.

Validation: all 82 tests across the 19-script USB workflow step passed; independent review passed. Production firmware is unchanged.
2026-10-04 13:06:55 -07:00
mikecarper 6516c719e1 Fix watchdog test timing and run hardware regression fixtures in CI
Arm the test watchdog through initial loop service and assert both recovery boundaries. Update cooperative contact negative controls for the current background APIs and run the existing USB, contact persistence, and startup regressions in Actions.

Validation: 1762 native tests, 48 added-workflow fixture tests, and 18 related persistence/logging checks passed locally.
2026-10-04 12:53:37 -07:00
mikecarper 9c43efb0bc Coalesce ESP background contact I/O and defer backup cleanup 2026-10-04 11:50:12 -07:00
mikecarper 8bc93822ef Clarify ESP contact CRC stack fixture ABI scope 2026-10-04 10:43:46 -07:00
mikecarper 15bdb7fc4d Batch ESP background contact CRC checks without growing scratch storage 2026-10-04 09:27:54 -07:00
mikecarper 55e4f47338 Service ESP received advert cache writes between loop passes 2026-10-04 08:20:28 -07:00
mikecarper 62d76b7bab Reduce ESP contact save backend work with bounded buffering 2026-10-04 08:20:28 -07:00
mikecarper 85ff9e321e Reject unhonored ESP filesystem buffer sizes before I/O 2026-10-04 08:20:16 -07:00
Kevin Le b1119e30ea Set RXPS defaults (Balanced profile) for fresh Installs 2026-10-04 21:51:51 +07:00
Kevin Le fd142084d3 Fixed typo in debug print in applyCompanionRxPowerSaving() 2026-10-04 21:49:51 +07:00
mikecarper c1a80e95c7 Preserve ESP native USB bytes across short FIFO writes
Backport the pinned Arduino 2.0.17 HWCDC suffix and interrupt handling into a build-local source copy. Preserve unsent bytes, serialize ISR and producer rearm, and include FIFO, suffix and active writers in transport cleanup proofs without changing the loop stack allocation.

Validate zero and short writes, producer races, quarantine and stale cleanup with sanitizer-backed native tests and negative controls. Keep other frameworks and USB modes on their existing drivers; update the watchdog file mock for the existing buffer API.
2026-10-04 06:49:31 -07:00
mikecarper 0a3187ac30 Stage ESP contact transaction startup between loop passes
Heltec V4 timing measured 2.19 seconds for recovery checks, temporary-file cleanup, open and buffer configuration combined in one lazy-save pass. Defer those startup operations across guarded companion loops while explicit durability callers still initialize synchronously.

Track ownership only after attempting to create the temporary file, preserve unowned stale files on early cancellation, and reject premature writes or commits. Cover startup faults, interruption, mutation, cache pressure and synchronous-construction negative controls with native sanitizer tests.
2026-10-04 05:27:09 -07:00
mikecarper 7e1750ceac Bound ESP contact transaction file buffers between loop passes
Arduino defaults to 4 KiB stdio buffering, so small record writes and CRC
reads could still trigger large SPIFFS batches. Set the newly opened ESP
writer buffer to one 152-byte contact record and verifier buffer to 64
bytes before I/O. Fail closed if either setup fails; keep other platforms
unchanged.

Model backend buffering separately from logical File operations. Validate
mutation cancellation, durability, setup failures, and negative controls
which restore the hidden default writer or reader batching. Update API
seams in existing uncached, radio and telemetry fixtures.
2026-10-04 04:32:37 -07:00
Kevin Le 9414de8593 Set defaults to powersaving_enabled=1 and radio_fem_rxgain=0 for fresh installs 2026-10-04 18:29:05 +07:00
mikecarper 4f114e914b Keep ESP contact saves responsive on PSRAM boards
Extend the cooperative contact transaction to uncached ESP32 storage,
including the PSRAM policy used by the XIAO and V4. Preserve owned contact
copies, the 152-byte disk format, revision cancellation and synchronous
durability operations. Keep contact capacities unchanged.

Avoid SDK error logs when optional blob or transaction cleanup files are
absent, while preserving metadata and deletion failure handling.

Validate cached ESP/nRF and uncached PSRAM paths under ASan/UBSan,
including bounded writes, mutation cancellation, faults, recovery and
reboot readback. Update the existing uncached production fixture.
2026-10-04 04:01:40 -07:00
mikecarper a71b99fd62 Service cached contact persistence between companion loop passes 2026-10-04 03:39:27 -07:00
mikecarper b9b7a7b7e1 Share production USB input limits with the handoff fixture 2026-10-04 03:03:24 -07:00
mikecarper 1e03eb3056 Retain oversized native USB commands until they can be rejected 2026-10-04 02:53:04 -07:00
mikecarper 67408ffd13 Support reader commands through local Bluetooth and USB CLI 2026-10-04 02:16:04 -07:00
mikecarper 638e8cf3b7 Keep BLE contact sync from blocking other companion transports 2026-10-04 02:11:18 -07:00
mikecarper c316e9fb3c Reduce notification settings stack usage without shared scratch storage 2026-10-04 02:07:26 -07:00
mikecarper b128ac9a70 Start radio liveness watchdog when loop service begins 2026-10-04 02:05:32 -07:00
mikecarper 0d5a6e6fc8 Reduce SHA-512 stack usage with a rolling message schedule 2026-10-04 02:04:16 -07:00
mikecarper 057f1824a0 Fix hardware test serial controls and offline DM queue matching 2026-10-04 01:55:10 -07:00
mikecarper 8586ef5f51 Fix nRF52 loop stack allocation under LTO 2026-10-04 01:54:01 -07:00
mikecarper 63f4642252 Merge observer OTA channel switching into keymindCascade 2026-10-04 00:26:46 -07:00
Liam Cottle 3e3150c83f Merge pull request #3538 from meshcore-dev/hidden-repeater
Hidden repeater
2026-10-04 20:10:43 +13:00
mikecarper 72eb37c229 Merge latest PowerSaving-v17 BOOT button support 2026-10-03 23:56:38 -07:00
mikecarper 30de48d27e Document Companion Bluetooth pairing PIN CLI 2026-10-03 23:44:23 -07:00
mikecarper c3d3c4e294 Retain and isolate delayed Companion results under backpressure 2026-10-03 23:22:34 -07:00
mikecarper 46df653dbc Share bounded STM32 Companion preference wire metadata 2026-10-03 23:16:37 -07:00
mikecarper 38563a9828 Update preference snapshot CI contract for bounded value copies 2026-10-03 22:44:03 -07:00
mikecarper 54e5b0b996 Bound STM32 Companion preference image writes 2026-10-03 22:41:45 -07:00
mikecarper 59e761262d Copy only persisted Companion preference values during loading 2026-10-03 22:16:31 -07:00
mikecarper 2817e180ec Separate GPS UART ownership and collapse shared Companion ports 2026-10-03 21:42:54 -07:00
mikecarper 9693c87002 Ignore inactive discovery tags without clearing other requests 2026-10-03 21:28:40 -07:00
mikecarper 161600b4b7 Inspect received ACK route before clearing its header 2026-10-03 21:28:24 -07:00
mikecarper 399464ea37 Use bounded frame transmission for Companion hardware UART 2026-10-03 21:23:14 -07:00
mikecarper ea938e8e84 Preserve ACK ownership across contact relocation and retirement 2026-10-03 21:10:49 -07:00
Scott Powell f19733e478 * don't respond to DISCOVER requests if node has adverts turned off. (isHiddenNode()) 2026-10-04 15:02:00 +11:00
mikecarper d29cba046b Align OTA profile prefix regression with frozen source identity 2026-10-03 20:51:14 -07:00
mikecarper e368b52f7d Bind resumed firmware packages to their effective build recipe 2026-10-03 20:48:49 -07:00
mikecarper eab6676316 Use resolved PlatformIO directories for migration packaging 2026-10-03 20:35:27 -07:00
mikecarper 7945d5bf5a Retain Companion ACK notifications during host backpressure 2026-10-03 20:23:50 -07:00
mikecarper 326f8cdf78 Default optional debug flags safely in strict shells 2026-10-03 19:55:57 -07:00
mikecarper eee4fa9e98 Treat public identity as a read-only CLI preference 2026-10-03 19:52:37 -07:00
mikecarper 72898f7dd5 Update USB startup fixture for retained mOTA responses 2026-10-03 19:45:06 -07:00
mikecarper 4fca38363d Update CLI identity regression for captured reply routing 2026-10-03 19:39:58 -07:00
mikecarper a02554dd69 Protect build cleanup from unsafe output paths 2026-10-03 19:31:10 -07:00
mikecarper f0828e025a Share lossless voice prefixes across companion announcements 2026-10-03 19:11:15 -07:00
mikecarper d4f88680e7 Run USB and persistence regression suites in CI 2026-10-03 19:01:37 -07:00
mikecarper 4837265648 Preserve USB mOTA ownership across deadlines and logging handoffs 2026-10-03 19:01:22 -07:00
mikecarper ecdaacdd58 Reject oversized USB frames and bound serial receive work 2026-10-03 18:55:23 -07:00
mikecarper d9088f9e86 Preserve telemetry preferences and routes across storage failures 2026-10-03 18:34:12 -07:00
mikecarper 1d05ef9e86 Reduce radio preamble validation overhead with equivalent math 2026-10-03 18:31:46 -07:00
mikecarper 81676be25c Validate management CLI captures and authenticated ACL snapshots 2026-10-03 18:29:12 -07:00
mikecarper 071ea533b0 Fix management browser authentication and stale decode results 2026-10-03 18:27:49 -07:00
mikecarper 581339efe7 Harden management report persistence and CLI input handling 2026-10-03 18:26:26 -07:00
mikecarper 06fc2d58d1 Fix radio profile reconfiguration transaction results 2026-10-03 18:25:22 -07:00
mikecarper 18f7edcf82 Keep USB watchdog snapshots coherent and fail-safe 2026-10-03 18:22:32 -07:00
Adam Gessaman a928498c46 Merge pull request #63 from agessaman/merge/ota-branch-into-observer-firmware-dev
feat(ota): ota branch channel switch (dev)
2026-10-03 16:41:52 -07:00
mikecarper 5ee6059564 Finalize USB watchdog management format as MGR2 2026-10-03 16:34:37 -07:00
mikecarper eeb25a8b5f Merge USB session safety and preserve GPS preference compatibility 2026-10-03 16:12:32 -07:00
agessaman 45d8f15098 Merge branch 'feat/ota-branch-switch' into merge/ota-branch-into-observer-firmware-dev 2026-10-03 15:52:39 -07:00
agessaman 8ec31dda71 fix(ota): skip the bare compat search literal when scanning an image
Every image also holds "ota-compat:" with no value (the search string);
finding it first made every channel switch look untagged.
2026-10-03 15:52:21 -07:00
mikecarper 7803d4fb07 Add saved repeater trace exception while repeating is disabled 2026-10-03 15:48:44 -07:00
agessaman a0856bed72 feat(ota): dev stores /mqtt.json, so its state generation is 2
Builds that only read /mqtt_prefs (gen 1) are refused as channel-switch
targets until they can read /mqtt.json.
2026-10-03 15:47:01 -07:00
agessaman baf01a5c16 Merge branch 'feat/ota-branch-switch' into merge/ota-branch-into-observer-firmware-dev
# Conflicts:
#	examples/simple_repeater/MyMesh.cpp
2026-10-03 15:47:01 -07:00
agessaman ed8af1b7ca fix(ota): refuse a channel switch to a build that cannot carry this node
A switch can land on an older build: e.g. prod cannot read dev's /mqtt.json,
and prod's M7 image has no Ethernet. Each image carries an ota-compat tag
(state generation + transports). A switch is flashed without rebooting, the
new image's tag is read (bounded by its length), and boot is pointed back at
the running image unless the target keeps this node's state and transports.
CI now requires the tag in every published binary.
2026-10-03 15:46:46 -07:00
mikecarper 65512661c3 Fix USB logging session safety and add hourly GPS sync interval 2026-10-03 15:43:35 -07:00
mikecarper 9b82c74275 Merge origin/keymindCascade USB reconnect fixes 2026-10-03 15:37:49 -07:00
agessaman f652e7d185 fix(ota): flash the channel ota update checked, not a later ota branch
The deferred flash re-read the channel preference ~2.5 s after ota update
acknowledged its target; capture it when the update is scheduled.
2026-10-03 15:37:49 -07:00
agessaman a1943a8eac fix(ota): always offer another channel's image, even from the same commit
Prod and beta are often built from one commit (e.g. after a dev->prod merge),
so a hash match does not mean the images are the same: the native base differs.
After the flash the target channel is native, so this cannot loop.
2026-10-03 15:37:49 -07:00
mikecarper 3add4695cc Harden USB logging and persist watchdog events in management reports 2026-10-03 15:29:19 -07:00
agessaman ee88192acb Merge branch 'feat/ota-branch-switch' into merge/ota-branch-into-observer-firmware-dev
# Conflicts:
#	.github/workflows/build-observer-firmwares.yml
#	examples/simple_repeater/MyMesh.cpp
#	examples/simple_room_server/MyMesh.cpp
#	src/helpers/ESP32Board.cpp
#	src/helpers/ESP32Board.h
#	test/test_config_serializer/test_config_serializer.cpp
2026-10-03 15:24:33 -07:00
agessaman 65d80bc908 ci(ota): verify each published binary's native channel by tag
Every observer build now carries both channel URLs, so checking for a URL's
presence or absence can no longer tell prod from beta (and the old checks would
reject every build). Read the ota-base-* tags from every .bin in out/ instead
of a single ELF, with URLs shared by build.sh and both workflows.
2026-10-03 15:24:11 -07:00
agessaman 9c65e198f1 feat(ota): prod/beta channel names for ota branch, with stable/dev aliases
Report the selected channel and the build's own channel, tag each baked-in
base for CI, and list ota branch in the CLI docs and portal autocomplete.
2026-10-03 15:19:06 -07:00
agessaman 4afb3f7e3f build(ota): point the stable/dev channel bases at observer.gessaman.com 2026-10-03 15:17:46 -07:00
agessaman 2633b4267c fix(ota): compare by commit hash when targeting another channel
Build counters are per channel, so a cross-channel build number is not
comparable: a switch to a channel with a lower counter reported up to date
and never happened. Off the native channel, update unless the hash matches.
2026-10-03 15:17:46 -07:00
Elektr0Vodka 763ba835c1 build(ota): bake stable + dev manifest bases into observer builds
(cherry picked from commit 29011959170db757ec5364de88f6beeaae82b995)
2026-10-03 15:17:36 -07:00
Elektr0Vodka ea541e182a feat(ota): add 'ota branch [stable|dev|default]' command
(cherry picked from commit 240804999edb284fd276489e6898ccad8a19a8e7)
2026-10-03 15:17:36 -07:00
Elektr0Vodka 942a19eb63 feat(ota): pass runtime manifest base through otaFromManifest
(cherry picked from commit 0bee84c0546780b2e7147148c32087e92d894ac0)
2026-10-03 15:17:36 -07:00
Elektr0Vodka 3910489414 feat(ota): persist ota_channel selector in NodePrefs
(cherry picked from commit db1db5b57ecce408c7441cb6aa70f29608b09603)
2026-10-03 15:17:36 -07:00
Elektr0Vodka f04d163bea feat(ota): channel-base resolver + arg parser with native tests
(cherry picked from commit 52a4ccc39093891b64e3752316fe7c191fd9b9ca)
2026-10-03 15:17:36 -07:00
mikecarper b4c8c0ab23 Fix USB logging ownership, persistence, and reconnect safety 2026-10-03 14:45:57 -07:00
Kevin Le fc182b7769 Support button press on BOOT for MKE-S3 2026-10-03 17:36:11 +07:00
mikecarper bd55369aa9 Improve firmware tooling docs and OTA seeder startup checks 2026-10-03 03:12:24 -07:00
mikecarper 4086222d85 Persist Companion Bluetooth on-off preference across reboots 2026-10-03 03:09:20 -07:00
mikecarper c917158167 Add Zira spoken button confirmations and opt-in G726 speech tests 2026-10-03 03:03:02 -07:00
mikecarper f0199b3111 Add compressed self-serve OTA and resilient native USB sleep
Use a bounded uzlib-derived block encoder with the existing strict tinf decoder, qualify infrastructure roles, budget temporary RAM, and cache running-image metadata. Add self-serve CLI and policy, USB host-loss sleep grace, hardware test harnesses, regression tests, and CI coverage.
2026-10-03 01:14:23 -07:00
mikecarper 43f474a806 Integrate upstream fixes and paired nRF52 sensor OTA release profiles 2026-10-02 17:52:12 -07:00
mikecarper 42d6bfd560 Reduce nRF52 infrastructure flash usage without trimming sensors
Keep Companion and Terminal Chat profiles unchanged; retain full-sensor and reduced-sensor OTA builds. Add compact crypto and OTA names, qualified display asset compression, font license packaging, and scope regression tests.
2026-10-02 15:52:23 -07:00
mikecarper d4d318814a Record production Bluetooth bootloader and following application test 2026-10-02 14:08:32 -07:00
mikecarper 488d51979c Update button and reader fixtures for pairing wake API 2026-10-02 13:09:11 -07:00
mikecarper a0105731f5 Fix Companion pairing PIN display and truthful identity startup 2026-10-02 12:42:26 -07:00
mikecarper 7324bf06ef Support 2+ path-hash width filters 2026-10-02 10:21:17 -07:00
mikecarper d084f5b837 Add encoded path-hash width selectors to forwarding rules 2026-10-02 10:07:48 -07:00
mikecarper 946354760b Record published recovery correction and final node restoration 2026-10-02 04:14:38 -07:00
Kevin Le eec86a994c Patched typo for interval check in GPS synctime 2026-10-02 17:51:51 +07:00
mikecarper 9b17596049 Record corrected 2.4.11 Bluetooth recovery phone tests 2026-10-02 03:47:21 -07:00
IoTThinks dbff241118 Merge pull request #20 from kng/sensor_gps_interval
Add GPS read interval to sensor
2026-10-02 17:30:35 +07:00
Daniel Ekman 88a1cbfa04 Add GPS read interval to sensor
Signed-off-by: Daniel Ekman <knegge@gmail.com>
2026-10-02 12:23:52 +02:00
Kevin Le 890e593d0f All board builds for PowerSaving17.1.5 2026-10-02 17:14:43 +07:00
Huw Duddy fad0ffb7f1 Merge pull request #3533 from oltaco/fix/bad-comment-format
Fix bad comment format
2026-10-02 07:46:36 +10:00
taco 0e1ba7102d tidy: remove old commented Bluefruit.autoConnLed(false) 2026-10-02 07:40:42 +10:00
taco 35e10a9c75 fix bad comment format 2026-10-02 07:35:34 +10:00
Huw Duddy ea0213754f Merge pull request #2838 from benskigomez/fix/gps-cold-start-detection
GPS: poll up to 5s for cold-start detection instead of a single 1s check
2026-10-01 23:52:16 +10:00
Huw Duddy f35216799a Merge pull request #3128 from bplein/feature/ble_led_disable
Allow for build-time macro to disable the bluetooth LED
2026-10-01 23:51:04 +10:00
Huw Duddy 1af2760c88 Merge pull request #3531 from meshcore-dev/revert-3411-patch-3
Revert "Fix Heltec Mesh Node T1 GNSS pins"
2026-10-01 22:19:32 +10:00
Huw Duddy a706d1d943 Revert "Fix Heltec Mesh Node T1 GNSS pins" 2026-10-01 22:17:44 +10:00
Huw Duddy 914143cb7e Merge pull request #3411 from Avamander/patch-3
Fix Heltec Mesh Node T1 GNSS pins
2026-10-01 20:31:38 +10:00
Huw Duddy ce266c00e5 Merge pull request #2065 from robekl/fix-t1000e-sensor-power-pin
Fix T1000-E sensor power pin mapping
2026-10-01 20:04:07 +10:00
Huw Duddy 2ce37ec8cc Merge branch 'dev' into fix-t1000e-sensor-power-pin 2026-10-01 19:57:50 +10:00
Adam Gessaman 1e23d7c1e4 Merge branch 'feat/mqtt-connection-health' into observer-firmware-dev 2026-09-26 14:19:06 -07:00
agessaman 7d0a602b20 fix(mqtt): exclude unready slots from slot health and keep cf monotonic
A slot waiting on a token/IATA/credential stays enabled but never attempts a
connection, so it held mqtt_slots_up below mqtt_slots_total (and down= at 1)
permanently -- a standing false alert on a node that is merely unconfigured.
slots_total and down= now count only ready slots, matching the CLI's "wait".

mqtt_connect_failures summed only enabled slots, so disabling a failing slot
made it drop. It now sums every slot, monotonic within a boot.

cf: moves after the error tail in mqttN.diag so a clamped 160-byte reply
loses the count rather than the refusal/TLS detail. A failed start now clears
the attempt flag instead of restoring a possibly stale one. Outage comments
corrected: the clock starts at a slot's first DISCONNECTED, not first connect.
2026-09-26 13:54:43 -07:00
agessaman f37445f8a7 fix(mqtt): count only failed starts, not rejected reconnects, in cf
reconnect() on a started client returns ESP_FAIL while an attempt is already
in progress. On a Heltec V3, a live reconfigure hit exactly that and the
attempt then connected, yet cf read 1. Count start_failures only when
connect() on a stopped client fails, and restore the attempt flag to its
prior value on any failed call so the in-progress attempt is still judged by
its own outcome.
2026-09-26 12:48:44 -07:00
agessaman 6390763c71 fix(mqtt): count failed starts and ignore bounced sessions in cf
connect()/reconnect() returning an error started no attempt, so no event
arrived and the failure went uncounted -- the low-heap case the counter exists
for. Counted now in a bridge-task-only start_failures, summed into cf: and
mqtt_connect_failures.

A renewal or corrected-clock bounce whose softDisconnect() times out gets its
old session's DISCONNECTED after the new attempt raised the flag. That session
is still marked connected until the handler runs, so the gate now also
requires !connected. An ignored late CONNECTED clears the flag too.

cf: no longer depends on dc being non-zero. Comments corrected for the
PSRAM-fallback stack cost and heap_largest's sampling point; the worst-case
test now models 6 slots.
2026-09-26 12:25:53 -07:00
agessaman 73098a44b6 fix(mqtt): count connect failures from a flag set before the attempt
The Starting-state gate raced the event task: client_state becomes Starting
only after connect()/reconnect() returns, so a fast failure could deliver
DISCONNECTED first and go uncounted, while a deliberate stop of an attempt in
flight was counted as a failure. A per-slot flag is now raised before the call
and cleared by onConnect, a failed start, or stopSlotClient().

Also stops zero-valued connect_failures/slots_breaker from creating an empty
stats object.
2026-09-26 11:53:54 -07:00
agessaman 7004b81c0c feat(mqtt): count connect failures and report tripped breakers
Two gaps remained after the first connection-health commit.

A slot retrying into a heap that cannot satisfy its TLS allocation moved no
counter at all. disconnect_count rose, but it also rises for dropped live
sessions, so it could not separate "actively failing to connect" from "was
connected, dropped once". Adds a per-slot connect_failures, incremented in
onDisconnect when the event arrives while the client is Starting -- meaning
it ended an attempt that never reached onConnect. Late events from a stopped
or quarantined client, and soft-disconnects of a live session, are in other
states and are not miscounted.

A slot parked by the circuit breaker also looked identical to one still
retrying, despite being materially worse: the breaker only probes every 30
minutes, so recovery is far slower and an operator may need to intervene.

Surfaces both:
- `mqttN.diag` gains `cf:<n>` beside the existing `dc:<n>`.
- the status payload gains `mqtt_connect_failures` (summed across slots) and
  `mqtt_slots_breaker` (count of tripped slots), each emitted only when
  non-zero so a healthy board's payload does not grow.

The counter is what distinguishes a retrying slot from a quiet one: while a
slot is down its publish counters cannot move by definition, so this is the
only value that shows the device is still trying.

Both suggested by adversarial review.
2026-09-26 11:10:13 -07:00
agessaman 383339eccc fix(mqtt): size the status buffer for real field maxima
The worst-case test added with the connection-health fields did not model
production maxima -- it used a 28-byte model and a 24-byte firmware string,
while MQTTBridge permits _origin[32], _device_id[65], _board_model[64],
_firmware_version[64] and derives a 63-byte client version. isValidName()
rejects [ ] / \ : , ? * but permits '"', so a 31-character node name of
quotes is legal and every quote escapes to two bytes.

Measured at those real maxima:

  without connection-health fields   762 bytes
  with connection-health fields      851 bytes
  buffer                             768 bytes

So the 768-byte buffer already had only six bytes of headroom before this
work, and the four new fields overflow it. Overflow is silent:
serializeComplete() returns 0 and clears the buffer, and the bridge
publishes only a positive length, so a device would simply stop publishing
status with no error anywhere -- the same blindness the connection-health
fields exist to remove.

Raises STATUS_JSON_BUFFER_SIZE to 1024 and corrects the test to use the
actual maxima. Status serializes into the shared 2048-byte publish scratch
buffer, so this costs no extra RAM; only the PSRAM-allocation-failed stack
fallback grows by 256 bytes. A static_assert keeps the status ceiling within
the shared buffer.

Found by adversarial review of the preceding commit.
2026-09-26 11:09:21 -07:00
agessaman f7ed2f176d feat(mqtt): report slot connection health so an outage is visible
A slot that cannot establish TLS reported nothing. The per-slot counters
track publish successes and failures, and a slot that never connects never
attempts a publish -- so its ok count freezes and its error count stays at
zero for the entire outage. Observed on hardware: a slot sat dead for over
five hours while `sN=` showed 0 errors and the published status payload
carried no indication at all.

Two surfaces gain the missing signal:

- `get mqtt.stats` gains `down=<n>` (omitted when zero, like `filt=`) and
  marks each disconnected slot with a trailing `!`. The marker goes after
  the counts so existing "s(\d)=(\d+)/(\d+)" parsers keep matching.
- The published status payload gains `mqtt_slots_up`, `mqtt_slots_total`,
  `heap_largest`, and `mqtt_outage_secs`. These ride on every healthy slot's
  status message, because the slot that is down cannot report itself.

`mqtt_slots_up < mqtt_slots_total` is the condition to alert on.
`mqtt_outage_secs` is detail: it is present only when the outage has a known
start, so its absence does not imply health -- a slot that has never
connected since boot has no outage start time.

`heap_largest` is included because free heap alone does not predict a failed
connection. A slot needs one contiguous 16,384-byte block for its TLS record
buffers; measured immediately before an attempt this value predicted the
outcome in every observed case, while total free heap was 85 KB throughout a
multi-hour outage. A low value with every slot up is normal rather than a
fault -- established sessions already hold their buffers.

Publish-error semantics are unchanged: connect failures are not folded into
them.

Adds host coverage for the new fields, for the outage key's presence rule,
and for worst-case payload size. That last one matters because
serializeComplete() returns 0 and clears the buffer on overflow and the
bridge publishes only a positive length, so exceeding the 768-byte status
buffer would silently stop status publication -- the same blindness this
change exists to remove.
2026-09-26 11:05:40 -07:00
agessaman 43b55de26f Merge branch 'fix/mqtt-omit-unknown-snr-rssi' into observer-firmware-dev 2026-09-26 10:52:15 -07:00
agessamanandACETyr a91009e3dd fix(mqtt): omit SNR/RSSI instead of publishing placeholder values
The reconstruction-less fallback path in buildPacketJSON (queued packet
with no captured raw frame that cannot be re-serialized) published
fabricated SNR 12.5 / RSSI -65 that consumers could not distinguish
from real radio readings. It now passes NAN / MQTT_RSSI_UNKNOWN, and
buildPacketMessage omits the SNR/RSSI keys entirely when no measurement
exists, mirroring the existing score omission.

Ports the fix from ACETyr's PR #13 (0a037040) on mqtt-bridge-implementation.

Co-Authored-By: ACETyr <35745290+ACETyr@users.noreply.github.com>
2026-09-26 10:46:11 -07:00
agessaman 91d9db9c7f Merge branch 'feat/observer-more-esp32-boards' into observer-firmware-dev 2026-09-26 10:08:18 -07:00
agessaman 33f1b1096d feat(observer): add dev-preview observer envs for 11 more ESP32-S3 boards
Repeater + room-server observer builds for Heltec RC32 (with and without
display), Wireless Tracker v1.0, Wireless Paper, Vision Master E213/E290,
Meshnology W12, LilyGo T-ETH Elite (WiFi only), T3S3 SX1276, Xiao S3 and
Ebyte EoRa-S3. The 4 MB boards (T3S3 SX1276, EoRa-S3) use min_spiffs and
omit SNMP; non-PSRAM boards opt into MQTT_NEIGHBORS_WITHOUT_PSRAM.
2026-09-26 10:03:48 -07:00
agessaman 6baffa753c Merge upstream/dev into observer-firmware-dev
Picks up 35 upstream commits through 988d017a: board loop() hook,
sensor SUBSCRIBE/telemetry push, companion MyMesh::Listener UI refactor,
agc.reset.interval widened to uint16, LR1121 DC-DC, ThinkNode M4 and
Muzi Base boards, M7 companion WiFi.
2026-09-26 09:32:09 -07:00
Avamander 61fcc49c8a Fix Heltec Mesh Node T1 GNSS pins 2026-09-14 17:53:50 +03:00
Bill Plein fe72d0321a Allow for build-time macro to disable the bluetooth LED
Several variants use one or another method to disable the bluetooth LED that is grabbed by the Adafruit Bluefruit library by default. This allows for single build_flags macro to control the behavior (default behavior if not defined, not auto-controlled by Bluefruit if defined). This would open up for future cases where this could be controlled via a CLI command or by an application.

Does not change behavior if the flag is not defined.
2026-08-07 17:51:01 -05:00
Jody Bentley 27feb73d4e GPS: poll up to 5s for cold-start detection instead of a single 1s check
initBasicGPS() waited a fixed 1 second and then decided the GPS was
present only if NMEA data had already arrived. Cold-start GPS modules
often need 2-5 seconds before they emit their first sentence, so on a
cold boot detection frequently failed. A failed detection sets
gps_detected = false, which hides all GPS CLI settings and disables GPS
time sync until the next reboot - so the GPS appears permanently dead
even though the hardware is fine.

Poll for up to ~5 seconds (in 250ms steps) and return as soon as any
data appears. Fast modules are detected almost immediately and add no
boot delay; slow cold-start modules now get enough time. The
ENV_SKIP_GPS_DETECT bypass is unchanged.

Fixes #2258.
2026-06-26 10:44:18 -04:00
Robert Ekl e525200ce1 Fix T1000-E sensor power pin mapping 2026-03-17 00:50:19 -05:00
1255 changed files with 217207 additions and 25512 deletions

No files matched your search

+3
View File
@@ -14,3 +14,6 @@ tools/hil/preamble_stationary*.json -text whitespace=cr-at-eol
tools/hil/sf6_125*.json -text whitespace=cr-at-eol
tools/hil/sf8_125*.json -text whitespace=cr-at-eol
tools/hil/sf5_250*.json -text whitespace=cr-at-eol
# Preserve the pinned upstream FS/newlib fixtures byte-for-byte.
test/fixtures/esp32_file_buffer/* -text whitespace=-blank-at-eol,-blank-at-eof
@@ -67,6 +67,10 @@ env:
# The channel itself. Firmware fetches <OTA_MANIFEST_BASE>/<OTA_VARIANT>.json,
# so this URL is what keeps beta nodes on beta.
OTA_MANIFEST_BASE_URL: https://observer.gessaman.com/beta/v
# Both channel bases are baked into every build so `ota branch prod|beta` can
# move a node between channels. Identical in both observer workflows.
OTA_MANIFEST_BASE_STABLE_URL: https://observer.gessaman.com/v
OTA_MANIFEST_BASE_DEV_URL: https://observer.gessaman.com/beta/v
# Marks the embedded version, e.g. v1.16.0.3-observer-beta-dev-abc1234, so `ver`
# (and the MQTT firmware_version / SNMP) identify BOTH the channel and its
# provenance: this channel is built from the upstream-dev-merged line, so "dev"
@@ -168,20 +172,9 @@ jobs:
run: /usr/bin/env bash build.sh build-firmware ${{ matrix.shard.envs }}
- name: Verify beta channel is baked in
shell: bash
run: |
# Fail fast rather than publish firmware that would OTA itself onto the
# production channel. Checks one built binary actually carries the beta
# manifest URL and does NOT carry the production one.
BIN=$(find .pio/build -name firmware.elf | head -1)
if [ -z "$BIN" ]; then echo "no ELF found to verify" >&2; exit 1; fi
if ! strings "$BIN" | grep -qF "$OTA_MANIFEST_BASE_URL"; then
echo "ERROR: beta manifest base missing from $BIN" >&2; exit 1
fi
if strings "$BIN" | grep -qE 'https://observer\.gessaman\.com/v"?$'; then
echo "ERROR: production manifest base present in a beta build" >&2; exit 1
fi
echo "OK: $BIN carries $OTA_MANIFEST_BASE_URL"
# Fail fast rather than publish firmware that would OTA itself onto the
# production channel: checks every .bin the shard publishes.
run: python3 scripts/verify_ota_channel.py --expect beta --required-cap keymind1
- name: Upload Shard Artifact
uses: actions/upload-artifact@v4
@@ -258,7 +251,7 @@ jobs:
# this channel's dropdown changelog. Non-fatal -- stale notes beat a
# red build whose binaries are already live.
{
printf '%s' '<p><b>&#9888; DEV/BETA CHANNEL:</b> pre-release firmware. A node flashed from this channel keeps receiving OTA updates from this channel until re-flashed by cable.</p>'
printf '%s' '<p><b>&#9888; DEV/BETA CHANNEL:</b> pre-release firmware. A node flashed from this channel keeps receiving OTA updates from this channel unless changed with ota branch prod and ota update. The target must preserve its saved-settings format and required transports.</p>'
cat firmware-notes.html
} > /tmp/beta-notes.html
gh_retry gh release edit "$RELEASE_TAG" --notes-file /tmp/beta-notes.html \
+7 -15
View File
@@ -54,6 +54,10 @@ env:
# same value the build was handed instead of a second hardcoded copy. Must match
# build.sh's OTA_MANIFEST_BASE_URL default and the Pages path serving flasher/v.
OTA_MANIFEST_BASE_URL: https://observer.gessaman.com/v
# Both channel bases are baked into every build so `ota branch prod|beta` can
# move a node between channels. Identical in both observer workflows.
OTA_MANIFEST_BASE_STABLE_URL: https://observer.gessaman.com/v
OTA_MANIFEST_BASE_DEV_URL: https://observer.gessaman.com/beta/v
jobs:
@@ -144,21 +148,9 @@ jobs:
run: /usr/bin/env bash build.sh build-firmware ${{ matrix.shard.envs }}
- name: Verify production channel is baked in
shell: bash
run: |
# Mirror of the beta workflow's guard, which production lacked. Fail fast
# rather than publish firmware that would OTA itself onto the wrong
# channel: the manifest base is a compile-time -D, so a build that lost
# it (or picked up beta's) is invisible until a node tries `ota check`.
BIN=$(find .pio/build -name firmware.elf | head -1)
if [ -z "$BIN" ]; then echo "no ELF found to verify" >&2; exit 1; fi
if ! strings "$BIN" | grep -qF "$OTA_MANIFEST_BASE_URL"; then
echo "ERROR: production manifest base missing from $BIN" >&2; exit 1
fi
if strings "$BIN" | grep -qF 'https://observer.gessaman.com/beta/v'; then
echo "ERROR: beta manifest base present in a production build" >&2; exit 1
fi
echo "OK: $BIN carries $OTA_MANIFEST_BASE_URL"
# Fail fast rather than publish firmware that would OTA itself onto the
# wrong channel: checks every .bin the shard publishes.
run: python3 scripts/verify_ota_channel.py --expect prod --required-cap keymind1
- name: Upload Shard Artifact
uses: actions/upload-artifact@v4
@@ -160,10 +160,8 @@ jobs:
- name: Build Shard ${{ matrix.shard.idx }}
env:
FIRMWARE_BUILD_NUMBER: ${{ needs.enumerate.outputs.build_number }}
# Channel isolation for device pull-OTA: appended after the per-env
# ini flags, so this definition of OTA_MANIFEST_BASE wins. build.sh
# appends its own version/OTA_VARIANT flags to this variable.
PLATFORMIO_BUILD_FLAGS: -D OTA_MANIFEST_BASE='"${{ env.OTA_MANIFEST_BASE_URL }}"'
# build.sh uses the workflow's OTA_MANIFEST_BASE_URL for native OTA
# and supplies the named production and beta channel bases.
run: /usr/bin/env bash build.sh build-firmware ${{ matrix.shard.envs }}
- name: Upload Shard Artifact
@@ -8,6 +8,8 @@ on:
push:
tags:
- 'repeater-*'
# Cascade pages contain already-qualified local matrix artifacts.
- '!repeater-room-*'
jobs:
build-repeater-firmwares:
+3
View File
@@ -32,8 +32,11 @@ jobs:
MKDOCS_EDIT_URI: edit/${{ github.ref_name }}/docs/
run: |
pip install mkdocs-material
node test/test_firmware_picker.js
node test/test_partition_catalog_viewer.js
python scripts/sync_bootloader_manifest.py
mkdocs build
cmp firmware/esp32_partition_catalog.json site/_data/esp32_partition_catalog.json
- name: Deploy to GitHub Pages
uses: peaceiris/actions-gh-pages@v4.1.0
+416 -11
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:
@@ -25,7 +62,9 @@ jobs:
- name: Verify protocol, encryption, scheduling and MQTT decoding
run: |
python3 -B test/test_management_report.py -v
python3 -B test/test_management_cli_wrapper.py -v
node test/test_management_decoder.js
node scripts/test_telemetry_decoder.js
stm32-companion-smoke:
runs-on: ubuntu-latest
@@ -36,9 +75,20 @@ jobs:
- name: Setup Build Environment
uses: ./.github/actions/setup-build-environment
- name: Verify compact STM32 signature arithmetic
run: |
python3 -B test/test_ed25519_compact_scalar.py -v
python3 -B test/test_ed25519_compact.py -v
- name: Compile ordinary STM32 USB Companion with pinned core and RadioLib
run: pio run -e RAK_3x72_companion_radio_usb
- name: Qualify Wio-E5 Mini Companion at release version length
run: >-
bash build.sh build-firmware wio-e5-mini_companion_radio_usb
--firmware-version v1.17.1.9-halo-keymind-cascade-dev
--radio-preset usa-cascade-fixed --profile cascade --require-ota
bootloader-version:
runs-on: ubuntu-latest
steps:
@@ -82,32 +132,164 @@ jobs:
- name: Setup Build Environment
uses: ./.github/actions/setup-build-environment
- name: Install signed-package verification dependency
run: |
python3 -m pip install cryptography
sudo apt-get update
sudo apt-get install -y libssl-dev
mkdir -p .pio/libdeps/room-mail-test
pio pkg install --global --library 'rweather/Crypto@0.4.0' --storage-dir .pio/libdeps/room-mail-test
- name: Verify ArduinoJson pin
run: |
python3 -B scripts/check_arduinojson_pin.py --self-test
python3 -B scripts/check_arduinojson_pin.py
- name: Verify build-size diagnostics and nRF52 optimizer recipes
run: |
bash test/test_build_size_detection.sh
python3 -B test/test_nrf52_size_profile.py -v
python3 -B test/test_rak_combined_build_config.py -v
- name: Run Unit Tests
run: pio test -e native -e native_kiss_modem -vv
run: pio test -e native -e native_kiss_modem -e native_ota_channel -vv
- name: Verify parser, delivery, storage, permissions, clock and radio stability
run: |
python3 -B test/test_protocol_parser_bounds.py -v
python3 -B test/test_cli_error_bounds.py -v
python3 -B test/test_ota_security.py -v
python3 -B test/test_companion_offline_delivery.py -v
python3 -B test/test_room_receive_admission.py -v
python3 -B test/test_companion_lfs_traversal.py -v
python3 -B test/test_room_post_permissions.py -v
python3 -B test/test_room_login_authorization.py -v
python3 -B test/test_room_login_integration.py -v
python3 -B test/test_room_client_paths.py -v
python3 -B test/test_client_acl_infrastructure.py -v
python3 -B test/test_stored_client_reply_routes.py -v
python3 -B test/test_room_catchup.py -v
python3 -B test/test_room_catchup_transport.py -v
python3 -B test/test_room_topic_store.py -v
python3 -B test/test_room_topic_delivery.py -v
python3 -B test/test_room_acl_persistence.py -v
python3 -B test/test_room_access_policy.py -v
python3 -B test/test_room_history_store.py -v
python3 -B test/test_room_history_integration.py -v
python3 -B test/test_room_board_store.py -v
python3 -B test/test_room_board_protocol.py -v
python3 -B test/test_room_mail_store.py -v
python3 -B test/test_room_mail_protocol.py -v
python3 -B test/test_room_web_mailbox.py -v
python3 -B test/test_room_web_http.py -v
python3 -B test/test_room_web_service.py -v
python3 -B test/test_contact_clock_bootstrap.py -v
python3 -B test/test_rtc_discovery.py -v
python3 -B test/test_radio_interrupt_recovery.py -v
python3 -B test/test_bridge_recovery.py -v
- name: Verify signed private fleet management
run: |
python3 -B test/test_fleet_command_crypto.py -v
python3 -B test/test_companion_fleet_control.py -v
python3 -B test/test_fleet_channel_runtime.py -v
python3 -B test/test_fleet_role_wiring.py -v
python3 -B test/test_fleet_location.py -v
python3 -B test/test_fleet_radio_schedules.py -v
python3 -B test/test_fleet_clock_control.py -v
- name: Verify OTA channel persistence and published image tags
run: |
python3 -B test/test_ota_channel_persistence.py -v
python3 -B test/test_observer_ota_channel.py -v
python3 -B test/test_observer_build_channels.py -v
python3 -B scripts/verify_ota_channel.py --self-test
- name: Verify build output cleanup safety
run: python3 -B test/test_build_output_safety.py -v
- name: Verify serial mOTA response deadlines and RX fairness
run: |
python3 -B test/test_mota_serial_deadline.py -v
python3 -B test/test_mota_folder_deadline.py -v
- name: Verify filter playground modes and built-in defaults
run: node scripts/test_filter_tool.js
- name: Verify bridge filter wiring and shorthand permissions
run: python3 -B test/test_transport_filter_wiring.py -v
run: |
python3 -B test/test_transport_filter_wiring.py -v
python3 -B test/test_rs232_runtime.py -v
python3 -B test/test_rs232_mqtt_runtime.py -v
python3 -B test/test_rs232_driver_lifecycle.py -v
python3 -B test/test_rs232_heartbeat.py -v
python3 -B test/test_rs232_heartbeat_owner.py -v
python3 -B test/test_rs232_power_settings.py -v
- name: Verify saved repeat-off trace exception and relay guards
run: |
python3 -B test/test_repeater_trace_repeat.py -v
python3 -B test/test_repeater_trace_forwarding.py -v
- name: Verify telemetry schedule input and persistence rollback
run: python3 -B test/test_telemetry_tx_command.py -v
- name: Verify transactional telemetry preferences and legacy route recovery
run: python3 -B test/test_telemetry_tx_persistence.py -v
- name: Verify MQTT and WiFi ownership, recovery and power policy
run: |
python3 -B test/test_mqtt_transport_results.py -v
python3 -B test/test_mqtt_tls_security.py -v
python3 -B test/test_mqtt_network_timeout.py -v
python3 -B test/test_mqtt_disconnect.py -v
python3 -B test/test_mqtt_connection_admission.py -v
python3 -B test/test_mqtt_handshake_scheduler.py -v
python3 -B test/test_mqtt_diagnostics.py -v
python3 -B test/test_mqtt_publish_ack.py -v
python3 -B test/test_wifi_network_power_policy.py -v
python3 -B test/test_mqtt_wifi_network_power.py -v
python3 -B test/test_companion_wifi_network_power.py -v
python3 -B test/test_webconfig_network_power.py -v
python3 -B test/test_companion_ap_ownership.py -v
python3 -B test/test_heltec_v4_wifi_setup_page.py -v
- name: Verify DNS lease restoration and bounded diagnostics
run: python3 -B test/test_network_dns.py -v
- name: Verify Sensor dog tracker ownership, routes, storage, GPS and sleep
run: |
python3 -B test/test_tracker_route_policy.py -v
python3 -B test/test_sensor_tracker_store.py -v
python3 -B test/test_tracker_owner_response.py -v
python3 -B test/test_sensor_tracker_client.py -v
python3 -B test/test_sensor_tracker_gps.py -v
python3 -B test/test_sensor_tracker_sleep.py -v
- name: Verify Companion contact and channel TX routing
run: |
sudo apt-get update
sudo apt-get install -y libssl-dev
python3 -B test/test_companion_channel_alias.py -v
python3 -B test/test_companion_tx_routing.py -v
python3 -B test/test_companion_discovery.py -v
python3 -B test/test_repeater_discovery.py -v
python3 -B test/test_companion_discovery_correlation.py -v
python3 -B test/test_pico_ble_queues.py -v
python3 -B test/test_companion_preferences_transaction.py -v
python3 -B test/test_companion_prefs_values.py -v
python3 -B test/test_companion_prefs_image.py -v
python3 -B test/test_companion_prefs_tail.py -v
python3 -B test/test_companion_radio_settings_transaction.py -v
python3 -B test/test_companion_prefs_transactions.py -v
python3 -B test/test_companion_primary_radio_persistence.py -v
python3 -B test/test_companion_response_bounds.py -v
python3 -B test/test_companion_ack_delivery.py -v
python3 -B test/test_companion_lost_reply.py -v
python3 -B test/test_companion_delayed_reply_delivery.py -v
python3 -B test/test_companion_hardware_serial.py -v
python3 -B test/test_companion_hardware_serial_gps.py -v
python3 -B test/test_companion_contact_lifetime.py -v
python3 -B test/test_companion_identity_cli.py -v
python3 -B test/test_companion_packet_stream.py -v
python3 -B test/test_companion_uncached_storage.py -v
python3 -B test/test_companion_ota_config.py -v
python3 -B test/test_ota_identity_policy.py -v
@@ -115,12 +297,28 @@ jobs:
python3 -B test/test_common_radio_persistence.py -v
python3 -B test/test_common_cli_save_results.py -v
python3 -B test/test_common_prefs_commit.py -v
python3 -B test/test_common_prefs_legacy_rxgain.py -v
python3 -B test/test_flood_policy_capacity.py -v
python3 -B test/test_flood_rule_layout.py -v
python3 -B test/test_mqtt_prefs_commit.py -v
python3 -B test/test_identity_and_settings_recovery.py -v
python3 -B test/test_contact_cache.py -v
- name: Verify Meshtext chat codec, transport and Companion integration
run: |
python3 -B test/test_meshtext_chat.py -v
python3 -B test/test_meshtext_config.py -v
python3 -B test/test_meshtext_transport.py -v
python3 -B test/test_meshtext_license.py -v
python3 -B test/test_radio_profile_auto.py -v
python3 -B test/test_companion_meshtext.py -v
python3 -B test/test_companion_meshtext_storage.py -v
python3 -B test/test_meshtext_auto_radio2_hil.py -v
python3 -B test/test_meshtext_auto_radio2_hil_sdk.py -v
- name: Verify receive recovery and pinned radio status transport
run: |
python3 -B test/test_dispatcher_startup_watchdog.py -v
python3 -B test/test_radio_receive_contract.py
python3 - <<'PY'
from pathlib import Path
@@ -137,7 +335,24 @@ jobs:
- name: Verify temporary-radio timing and exact reply delivery
run: |
python3 -B test/test_carrier_wave.py
python3 -B test/test_morse_tx.py -v
python3 -B test/test_morse_rx.py -v
python3 -B test/test_morse_rx_timing.py -v
python3 -B test/test_morse_rx_gap_bias.py -v
python3 -B test/test_morse_rx_human.py -v
python3 -B test/test_morse_hil_timing.py -v
python3 -B test/test_morse_hil_production.py -v
python3 -B test/test_morse_rx_squelch.py -v
python3 -B test/test_morse_hil_squelch.py -v
python3 -B test/test_morse_cli.py -v
python3 -B test/test_morse_auth.py -v
python3 -B test/test_morse_reply_handoff.py -v
python3 -B test/test_morse_remote.py -v
python3 -B test/test_morse_hil_safety.py -v
python3 -B test/test_morse_loop_policy.py -v
python3 -B test/test_morse_terminal_input.py -v
python3 -B test/test_radio_profile_cli.py
python3 -B test/test_radio_profile_spiffs_missing.py -v
python3 -B test/test_radio_reply_mutation.py
python3 -B test/test_radio_profile_scan.py
python3 -B test/test_repeater_radio_timing_integration.py
@@ -149,6 +364,9 @@ jobs:
python3 -B test/test_ota_store_resume.py -v
python3 -B test/test_ota_catalog_lifetime.py -v
python3 -B test/test_ota_store_flash_nrf52_hybrid.py -v
python3 -B test/test_tower_bootloader_caps.py -v
python3 -B test/test_tower_auto_storage.py -v
python3 -B test/test_tower_auto_integration.py -v
python3 -B test/test_ota_dev_staging.py -v
python3 -B test/test_ota_dev_reboot_guard.py -v
@@ -156,41 +374,141 @@ jobs:
working-directory: test
run: >-
python3 -B -m unittest -v
test_sx1262_profile_switch test_sx1262_batched_modulation
test_sx1262_profile_switch test_sx1262_batched_modulation test_sx1262_protocol_batching
test_radio_chirp_math
- name: Verify radio lab collectors and compiled experiment contracts
run: |
python3 -B -m unittest discover -s test -p 'test_profile_*.py' -v
python3 -B -m unittest discover -s tools/hil -p 'test_*.py' -v
python3 -B test/test_hil_esp32_self_serve_ota.py -v
python3 -B test/test_hil_esp32_usb_host_reboot.py -v
- name: Verify ESP32 static DRAM budget
run: |
python3 -B test/test_esp32_dram.py
python3 -B test/test_battery_charge_cli.py -v
python3 -B test/test_tbeam_charge_target.py -v
python3 -B test/test_elrs_power.py
python3 -B test/test_ota_heap_context.py
python3 -B test/test_ota_self_serve_policy.py -v
python3 -B test/test_ota_self_metadata_cache.py -v
python3 -B test/test_ota_nrf52_display_cache.py -v
python3 -B test/test_ota_device_deflate_encoder.py -v
python3 -B test/test_ota_device_deflate_role.py -v
python3 -B test/test_client_acl_spiffs.py
python3 -B test/test_client_acl_role.py -v
python3 -B test/test_client_acl_metadata.py -v
python3 -B test/test_client_acl_cli.py -v
python3 -B test/test_client_acl_response.py -v
- name: Verify ESP32 USB sleep and G3 button wake
run: python3 -B test/test_esp32_usb_sleep.py
- name: Setup Node for official app compatibility tests
uses: actions/setup-node@v4
with:
node-version: '22'
- name: Cache pinned official app and browser protocol sources
uses: actions/cache@v4
with:
path: ~/.cache/meshcore-tests
key: official-clients-84bc39a950735aaffa93d912556852a4c22e235d0cc4e4d04790113207f13a68-aff42ad8ef2b332f24d644ff6a783f8272a88ff2
- name: Verify stock app radio, Access Control and room compatibility
run: |
node --version
python3 -B test/test_official_app_compatibility.py -v
python3 -B test/test_official_room_app_compatibility.py -v
- name: Verify ESP32 USB reboot sleep grace and G3 button wake
run: |
python3 -B test/test_usb_host_sleep_policy.py
python3 -B test/test_esp32_usb_sleep.py
python3 -B test/test_esp32_full_sleep.py
python3 -B test/test_esp32_full_power_lifecycle.py
- name: Verify official web console framing and power-saving idle sessions
run: |
python3 -B test/test_cpp_source.py -v
python3 -B test/test_official_web_console.py -v
python3 -B test/test_hil_linux_web_serial.py -v
python3 -B test/test_hil_slowfi_guard.py -v
- name: Verify official setup GUI initial time command deadline
run: python3 -B test/test_official_config_gui.py -v
- name: Verify ESP32 boot filesystem inventory and recovery safety
run: |
python3 -B test/test_esp32_boot_file_inventory.py -v
python3 -B test/test_esp32_boot_file_lifecycle.py -v
- name: Verify WiFi OTA startup and WebConfig handoff
run: |
python3 -B test/test_wifi_ota_start.py -v
python3 -B test/test_webconfig_ap_start.py -v
python3 -B test/test_webconfig_recovery_auth.py -v
python3 -B test/test_esp32_wifi_scan_fix.py -v
python3 -B test/test_esp32_wifi_scan_recipes.py -v
- name: Verify browser OTA completion and immediate HTTP listener restart
run: python3 -B test/test_async_web_lifecycle.py -v
- name: Verify GPS status distinguishes user settings from receiver sleep
run: python3 -B test/test_gps_status.py
run: |
python3 -B test/test_gps_status.py
python3 -B test/test_gps_upstream_adaptations.py -v
python3 -B test/test_esp32_gps_discovery.py -v
python3 -B test/test_gps_sync_interval.py -v
python3 -B test/test_gps_sync_preferences.py -v
python3 -B test/test_rak_gps_power.py -v
python3 -B test/test_peripheral_mode_prefs.py -v
python3 -B test/test_t1000e_sensor_power.py -v
python3 -B test/test_sensor_battery_alerts.py -v
python3 -B test/test_sensor_subscriptions.py -v
python3 -B test/test_sensor_reply_bounds.py -v
- name: Verify Companion ASCII defaults and USB session cleanup
run: |
python3 -B test/test_companion_usb_default.py
python3 -B test/test_companion_usb_input.py
python3 -B test/test_companion_ota_includes.py
python3 -B test/test_companion_usb_logging_handoff.py
python3 -B test/test_companion_usb_mota_exit.py
python3 -B test/test_common_cli_logging_transaction.py
python3 -B test/test_usb_diagnostic_logging.py
python3 -B test/test_nrf52_usb_client.py
python3 -B test/test_nrf52_usb_lifecycle.py
python3 -B test/test_nrf52_usb_logging_contract.py
python3 -B test/test_esp32_usb_serial_hygiene.py
python3 -B test/test_esp32_tinyusb_nonblocking.py
python3 -B test/test_esp32_usb_session_fix.py
python3 -B test/test_hwcdc_tx_backport.py -v
python3 -B test/test_hwcdc_startup_session.py -v
python3 -B test/test_hwcdc_313_startup_session.py -v
python3 -B test/test_hwcdc_3311_startup_session.py -v
python3 -B test/test_esp32_hwcdc_recipes.py -v
python3 -B test/test_hwcdc_role_transport.py -v
python3 -B test/test_hwcdc_write_capacity.py -v
python3 -B test/test_esp32_file_buffer_fix.py -v
python3 -B test/test_companion_usb_logging_reply.py
python3 -B test/test_esp32_tinyusb_cooperative_output.py
python3 -B test/test_esp32_tinyusb_role_hygiene.py
python3 -B test/test_stm32_float_conversion.py
- name: Verify Companion contact persistence and OTA sleep guards
run: python3 -B test/test_companion_contact_sleep_contract.py -v
run: |
python3 -B test/test_companion_contact_sleep_contract.py -v
python3 -B test/test_esp32_cooperative_contacts.py -v
python3 -B test/test_esp32_contact_writer_batch.py -v
python3 -B test/test_esp32_contact_crc_batch.py -v
python3 -B test/test_esp32_contact_publication.py -v
python3 -B test/test_esp32_advert_cache.py -v
- name: Verify Companion Bluetooth CLI connection protection
run: python3 -B test/test_companion_bluetooth_control.py -v
- name: Verify Companion Bluetooth reboot persistence
run: python3 -B test/test_companion_bluetooth_persistence.py -v
- name: Verify nRF52 SoftDevice LTO and persistent BLE subscriptions
env:
MESHCORE_REQUIRE_ARM_COMPILER: '1'
@@ -206,9 +524,13 @@ jobs:
run: |
python3 -B test/test_wireless_control.py -v
python3 -B test/test_serial_wifi_sessions.py -v
python3 -B test/test_companion_wifi_listener.py -v
python3 -B test/test_serial_ethernet_sessions.py -v
python3 -B test/test_companion_wifi_session.py -v
python3 -B test/test_espnow_radio_lifecycle.py -v
python3 -B test/test_wifi_ap_lr_safety.py -v
python3 -B test/test_espnow_runtime.py -v
python3 -B test/test_wifi_ota_lifecycle.py -v
- name: Verify message reader buttons, touch targets, and footer layouts
working-directory: test
@@ -220,19 +542,39 @@ jobs:
test_indicator_display_profile test_indicator_render_profile
test_indicator_exclusive_transport test_companion_transport_selector
- name: Verify logging sleep guards and instructions
- name: Verify independent USB packet/debug logging, recovery, and instructions
run: |
python3 -B test/test_common_usb_debug.py
python3 -B test/test_companion_usb_logging.py
python3 -B test/test_usb_logging_watchdog.py
python3 -B test/test_usb_watchdog_event.py
python3 -B test/test_usb_logging_client_activity.py
python3 -B test/test_usb_logging_watchdog_runtime.py
python3 -B test/test_usb_logging_watchdog_roles.py
python3 -B test/test_nrf52_usb_console.py
python3 -B test/test_esp32_tinyusb_nonblocking.py
python3 -B test/test_esp32_tinyusb_role_hygiene.py
python3 -B test/test_logging_sleep_contract.py
python3 -B test/test_picker_controls_generator.py
python3 -B test/test_firmware_picker_package.py
node test/test_firmware_picker.js
- name: Verify Full logging compiler flags and packaged capabilities
run: |
python3 -B test/test_upstream_observer_recipes.py -v
python3 -B test/test_station_g3_sensor_recipes.py -v
python3 -B test/test_full_build_flag_dedup.py
python3 -B test/test_full_logging_compile.py
python3 -B test/test_full_logging_resume.py
python3 -B test/test_xiao_canonical_logging.py -v
python3 -B test/test_firmware_build_recipe.py -v
python3 -B test/test_firmware_capabilities.py
MESHCORE_TEST_REAL_PIO_PATHS=1 python3 -B test/test_esp32_partition_migration_build_dir.py -v
python3 -B test/test_esp32_partition_migration_usb.py -v
python3 -B test/test_usb_partition_bridge_set.py -v
bash test/test_build_profiles.sh
bash test/test_full_infrastructure_selection.sh
bash test/test_nrf52_ota_profiles.sh
bash test/test_xiao_qspi_combined.sh
- name: Verify nRF52 UF2-reset CLI coverage
@@ -247,12 +589,18 @@ jobs:
python3 -B test/test_replay_reset_integration.py -v
- name: Verify firmware memory budgets and nRF52 Bluetooth startup
env:
MESHCORE_REQUIRE_ARM_COMPILER: '1'
run: |
bash test/test_build_size_detection.sh
python3 -B test/test_firmware_ram.py
python3 -B test/test_recent_repeater_storage.py -v
python3 -B test/test_ota_cold_buffers.py -v
python3 -B test/test_neighbor_scope_storage.py
python3 -B test/test_ed25519_compact.py
python3 -B test/test_cascade_release_package.py
python3 -B test/test_release_publication.py
python3 -B test/test_local_release_full_selection.py
python3 -B test/test_local_release_capacity_archive.py
python3 -B test/test_t096_full_memory.py
python3 -B test/test_nrf52_ble_startup.py
python3 -B test/test_nrf52_ble_trace.py
@@ -260,8 +608,44 @@ jobs:
python3 -B test/test_nrf52_usb_power.py
python3 -B test/test_shared_mota_queue.py
- name: Verify opt-in RAK4631 combined Ethernet and OTA modes
run: |
python3 -B test/test_rak_combined_ota.py -v
python3 -B test/test_rak_ethernet_transmit.py -v
python3 -B test/test_rak4631_ethernet_runtime.py -v
- name: Verify nRF52 infrastructure trims and unchanged Companion scope
run: |
python3 -B test/test_nrf52_flash_trim.py -v
python3 -B test/test_nrf52_tft_font_trim.py -v
python3 -B test/test_ota_target_names.py -v
python3 -B test/test_ota_cli_listing.py -v
- name: Verify compact same-size fonts and lossless rendering
run: |
python3 -B test/test_compact_arial.py -v
python3 -B test/test_compact_noto.py -v
python3 -B test/test_nrf52_font_license.py -v
python3 -B test/test_oled_font_experiments.py -v
python3 -B test/test_font_family_trials.py -v
- name: Verify Companion Bluetooth settings and WebConfig
run: |
python3 -B test/test_gps_voice_buttons.py -v
python3 -B test/test_gps_voice_generator.py -v
python3 -B test/test_button_voice_actions.py -v
python3 -B test/test_button_voice_generator.py -v
python3 -B test/test_button_voice_preview.py -v
python3 -B test/test_g726_voice.py -v
MESH_G726_TEST_SANITIZERS=1 python3 -B test/test_g726_voice.py -v
python3 -B test/test_g726_voice_generator.py -v
python3 -B test/test_voice_prefixes.py -v
MESH_VOICE_TEST_SANITIZERS=1 python3 -B test/test_voice_prefixes.py -v
python3 -B test/test_nimble_companion.py
python3 -B test/test_bluetooth_pairing_ui.py
python3 -B test/test_companion_pairing_display.py
python3 -B test/test_companion_identity_startup.py
python3 -B test/test_startup_screen.py
node test/test_notification_builder.js
python3 -B test/test_notification_settings.py
python3 -B test/test_display_power.py
@@ -272,7 +656,14 @@ jobs:
python3 -B test/test_webconfig_ui.py
python3 -B test/test_webconfig_gzip.py
python3 -B test/test_webconfig_secret_masking.py
python3 -B test/test_webconfig_station_ownership.py -v
python3 -B test/test_mqtt_canonical_wifi.py -v
python3 -B test/test_mqtt_ap_ownership.py -v
python3 -B test/test_sensor_webconfig_capabilities.py -v
python3 -B test/test_webconfig_ui_runtime.py
python3 -B test/test_webconfig_lost_reply.py -v
MESHCORE_REQUIRE_ROOM_BROWSER=1 python3 -B test/test_room_web_ui.py -v
MESHCORE_REQUIRE_ROOM_BROWSER=1 python3 -B test/test_room_mail_web_ui.py -v
- name: Upload Test Results
# Upload test results even if the test step failed.
@@ -281,3 +672,17 @@ jobs:
with:
name: test-results
path: .pio/build/native/
hosted-doc-links:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v6
with:
python-version: '3.13'
- name: Install the documentation renderer
run: python3 -m pip install mkdocs==1.6.1 mkdocs-material==9.7.7
- name: Verify source links and published page routes
run: python3 -B test/test_hosted_doc_links.py -v
- name: Verify hosted-link visibility and navigation
run: node test/test_hosted_doc_links.js
+1
View File
@@ -639,6 +639,7 @@ These settings apply across all MQTT slots:
- `get wifi.ssid` - Get WiFi SSID
- `get wifi.pwd` - Get WiFi password
- `get wifi.status` - Get WiFi connection status, IP, RSSI, and uptime
- `get link.dns` - Show the live DNS resolver list, the selected medium's gateway, and each medium's leased DNS servers (`-` means empty; `none` means never leased). Long lease lists may be truncated to fit the CLI reply.
- `get wifi.powersave` - Get WiFi power save mode (none/min/max)
#### Set Commands
+34 -1
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.
+24 -15
View File
@@ -1,21 +1,30 @@
## Halo Keymind Cascade Branch Changes over Stock
* **Two parallel LoRa radio settings on one device at the same time.** Alternate rx & tx between two frequencies and/or modulation settings. Choose transmission profiles per contact or channel, enable or isolate crossover traffic, schedule profiles, and use a temporary update profile alongside the normal network. Includes timing calibration and separate R2 screen pages. These are time-shared profiles on one transceiver.
* **Firmware updates over LoRa.** Update repeaters/rooms/sensors over LoRa.
* **Repeater forwarding policies.** Actions include dropping, rate limiting, assigning scopes, selecting retries, and controlling forwarding priority. Separate policies cover ordinary radio forwarding, bridges, and profile crossover.
* **Flood retries that account for network topology.** Configure retry counts, path limits, packet-type limits, target prefixes, ignored repeaters, and channel-specific eligibility. Bridge buckets keep retrying until traffic is heard on the intended sides of a relay or the retry budget expires. Qualifying echoes cancel unnecessary attempts.
* **Enhanced direct-message and direct-route retries.** Dynamic coding-rate, keep trying until echo hear.
* **Enhanced Telemetry.** Single LoRa packet supports multiple days of temperature or battery voltage or GPS history.
* **Unified Full Companion firmware.** Combines USB, BLE, WiFi, and Ethernet/serial transports in one image; with runtime controls and a LoRa firmware-serving mode. Most supported targets can operate multiple transports together; some boards have explicit transport restrictions.
* **Operational reporting and alerts.** Adds optional management reports with firmware version, bootloader version, LoRa update capabilities, uptime, and historical voltage and temperature ranges.
* **Set time using LoRa Network.** Automatically set the clock based off the LoRa network. Better than 2024.
* **Companion Bluetooth privacy and recovery improvements.** Configurable mac address policies, paired-peer stealth behavior, and changes to reconnect, subscription, and startup handling.
* **Single Key DMs.** Allow for you to send your advert in the initial DM. Makes DMing a bot/bbs/etc a lot easier if they're also running this firmware.
* **agessaman Observer - ESP32 Integrated MQTT observation.** Send directly from the node to the observer api endpoint.
* **IoTThinks PowerSaving - Broader power-saving controls.** Integrates device sleep, LoRa receive duty cycling, WiFi modem-sleep policies, and GPS acquisition/cache schedules across supported roles. These controls are independently configurable; actual savings depend on hardware and workload.
* **[Two LoRa profiles on one device](https://mikecarper.github.io/MeshCore/radio_profiles/).** Time-share one transceiver between two frequencies and/or modulation settings. Choose transmission profiles per contact or channel, enable or isolate crossover traffic, schedule profiles, and use a temporary update profile alongside the normal network. Includes timing calibration and separate R2 screen pages.
* **[Firmware updates over LoRa](https://mikecarper.github.io/MeshCore/lora_ota_automation/).** Update repeaters, room servers, and sensors over LoRa on supported firmware and storage layouts.
* **[Repeater forwarding policies](https://mikecarper.github.io/MeshCore/flood_filtering/).** Actions include dropping, rate limiting, assigning scopes, selecting retries, and controlling forwarding priority. Separate policies cover ordinary radio forwarding, bridges, and profile crossover.
* **[Private fleet management](https://mikecarper.github.io/MeshCore/fleet_management/).** Deploy filtering, secondary-radio changes, radio schedules, and clock updates to a whole fleet, existing configured or home regions, a GPS center and radius, one node, or a semicolon-separated target list. The publisher signs each complete command once, splitting longer requests across two packets; receivers hold its public verification key, with replay protection and acknowledgements before radio handoff.
* **[Flood retries that account for network topology](https://mikecarper.github.io/MeshCore/halo_keymind_settings/#flood-and-advert-settings).** Configure retry counts, path limits, packet-type limits, target prefixes, ignored repeaters, and channel-specific eligibility. Bridge buckets keep retrying until traffic is heard on the intended sides of a relay or the retry budget expires. Qualifying echoes cancel unnecessary attempts.
* **[Enhanced direct messages and direct-route retries](https://mikecarper.github.io/MeshCore/halo_keymind_settings/#direct-retry-settings).** Adapt the coding rate across bounded retry attempts and cancel unnecessary retries when a qualifying echo is heard. [Single Key DMs](https://mikecarper.github.io/MeshCore/companion_protocol/#private-text-when-only-the-sender-knows-the-recipient) let compatible Companions send private text when only the sender knows the recipient's key, using a signed, encrypted introduction. The recipient accepts the sender through the app or opts into automatic acceptance; compact STM32WL builds omit this feature. An optional [Companion lost-status responder](https://mikecarper.github.io/MeshCore/companion_lost_reply/) answers private "Am I lost?" questions with a saved Yes or No.
* **[Enhanced telemetry history](https://mikecarper.github.io/MeshCore/telemetry_decoder/).** Send compact temperature and battery-voltage snapshots of up to 165 samples per packet, plus external-voltage history in chunks. Read retained GPS history through the CLI; GPS history is not included in the automatic telemetry packets. Decode snapshots and CLI history in the browser.
* **[Room topics, mailboxes, history, and access controls](https://mikecarper.github.io/MeshCore/room_services/).** Save an announcement and optionally retain the newest 32 chat posts across reboots. Regular users manage personal mailboxes with public or selected-sender access, explicit receipts, and mailbox-only delivery for sleeping trackers. Room users can manage their own return routes and skip older unread chat by date or keep the newest N messages. Explicit ACL roles persist; full-key bans and per-user post/request limits apply to room sessions. ESP32 images with WebConfig provide local Wi-Fi chat, an information board, and an admin dashboard for mailboxes, routes, delivery, activity, catch-up and access. The existing app's chat and catch-up formats remain supported.
* **[Full firmware profiles, Companion transports, and ESP32 bridges](https://mikecarper.github.io/MeshCore/full_profiles/).** Full combines the exact board and role's supported features with runtime controls: Companion transports and firmware serving, or qualified ESP32 infrastructure services and MQTT/ESP-NOW/RS-232 bridges. Includes capacity exceptions, matching application slots, exact-board/role partition migration packages, and nRF52 Full/Reduced supported-sensor packages with LoRa OTA. The picker links supported migration routes; USB layout changes use the matching merged image, since an app-only image cannot change the partition table.
* **[Operational reporting and alerts](https://mikecarper.github.io/MeshCore/management_reports/).** Adds optional management reports with firmware version, bootloader version, LoRa update capabilities, uptime, and historical voltage and temperature ranges.
* **[Set time using the LoRa network](https://mikecarper.github.io/MeshCore/cli_commands/#estimate-and-correct-infrastructure-node-time-after-startup).** Infrastructure nodes can estimate time from fresh mesh evidence and correct their clocks when a consensus is available. Successful manual, GPS, or NTP synchronization takes precedence until reboot.
* **[Companion Bluetooth privacy and recovery improvements](https://mikecarper.github.io/MeshCore/companion_radio_full/#interfaces).** Configurable MAC address policies, paired-peer stealth behavior, and changes to reconnect, subscription, and startup handling on supported Bluetooth targets.
* **[Programmable Companion radio notifications](https://mikecarper.github.io/MeshCore/notifications/).** Build per-contact, room, or channel rules for the radio's vibration, LED, melody, screen, and supported GPIO outputs. Select rules by client connection state and configure repeat/stop behavior. Available on ESP32, nRF52, and RP2040 Companions with the required outputs.
* **[agessaman Observer - integrated ESP32 MQTT observation](https://mikecarper.github.io/MeshCore/WiFi/#mqtt-observer-setup).** Publish packets from the node over WiFi to a configured MQTT broker, without a separate USB host bridge.
* **[IoTThinks PowerSaving - broader power-saving controls](https://mikecarper.github.io/MeshCore/role_feature_switches/#infrastructure-power-saving-and-bridges).** Integrates device sleep, LoRa receive duty cycling, WiFi modem-sleep policies, and GPS acquisition/cache schedules across supported roles. These controls are independently configurable; actual savings depend on hardware and workload.
* **[LiFePO4 low-voltage cutoff and protection](https://mikecarper.github.io/MeshCore/cli_commands/#configure-nrf52-battery-protection).** On supported nRF52 power-management boards with a text CLI, the saved LiFePO4 profile sets a 2.9 V boot lock and 2.7 V running cutoff. Three valid low readings 30 seconds apart trigger protective shutdown; external power bypasses these checks. Use compatible charging hardware: selecting a battery profile does not change the charger.
Get the correct firmware file here for your hardware:
**https://mikecarper.github.io/MeshCore/firmware_picker/**
**[Firmware picker](https://mikecarper.github.io/MeshCore/firmware_picker/)**
Related source projects:
* [agessaman Observer firmware](https://github.com/agessaman/MeshCore/tree/observer-firmware-dev): embedded WiFi/MQTT observation.
* [Cisien meshcoretomqtt](https://github.com/Cisien/meshcoretomqtt): a host-side Python USB/serial packet-log publisher.
* [IoTThinks PowerSaving branch](https://github.com/IoTThinks/MeshCore/tree/PowerSaving-v17): power-saving integration.
---
@@ -65,7 +74,7 @@ For developers:
- Install [PlatformIO](https://docs.platformio.org) in [Visual Studio Code](https://code.visualstudio.com).
- Clone and open the MeshCore repository in Visual Studio Code.
- Install the pinned build-time compressors with `python -m pip install -r requirements-build.txt`.
- Install the pinned build-time dependencies in PlatformIO's Python environment with `python -m pip install -r requirements-build.txt`.
- See the example applications you can modify and run:
- [Companion Radio](./examples/companion_radio) - For use with an external chat app, over BLE, USB or Wi-Fi.
- [KISS Modem](./examples/kiss_modem) - Serial KISS protocol bridge for host applications. ([protocol docs](./docs/kiss_modem_protocol.md))
+49 -2
View File
@@ -1,5 +1,51 @@
# Releasing Firmware
For 1.17.1.9 and later, build the complete qualified local release from a
clean, committed checkout. The builder runs one PlatformIO process at a time:
```bash
python3 scripts/build_local_release.py \
--firmware-version v1.17.1.9-halo-keymind-cascade-dev --pio-jobs 4 --resume
```
Package its completed directory for stable publication:
```bash
python3 scripts/package_cascade_release.py \
--input .releases/v1.17.1.9-halo-keymind-cascade-dev-SOURCE8 \
--output /path/to/publication-stage --local-release --stable \
--version 1.17.1.9 --commit FULL_SOURCE_COMMIT
```
The packager rechecks the local checksum inventory, qualified firmware,
full/reduced nRF52 pairs, and every exact ESP32 migration package. It keeps
migration-only application identities inside their migration ZIPs. It does
not upload anything. Review `release-plan.json`, generate the picker controls
as described in [the picker guide](docs/firmware_picker.md), and generate
both Mesh America catalogs from the same staged asset manifests:
```bash
python3 scripts/generate_mesh_america_release.py \
--stage /path/to/publication-stage \
--catalog mesh-america/keymind-cascade-v1.16.0-provider.json \
--output /path/to/new-provider.json
```
Both active provider URLs use the same binaries. Preserve their stable
filenames when updating them. Create supporting releases first and publish
the Companion family root last, marking only that root as GitHub Latest.
Upload each group's complete asset inventory and verify the public asset
names, sizes and checksums before switching the catalogs. Regenerate the
downloadable picker from the same family and update its checksum on every
page. Documentation-only follow-up commits do not move firmware tags.
Include the [1.17.1.9 release notes](docs/releases/1.17.1.9.md) and
[1.17.1.8 WiFi upgrade guide](docs/upgrading_1.17.1.8_to_1.17.1.9.md) in the
publication review. Replace the release notes' final package-validation gate
with the completed inventory only after packaging passes. Keep the firmware
source separate from a later documentation-only commit, and retain the
measured hardware scope and old-updater completion limitation.
For the USA Cascade 1.17.1.5 matrix, use
[the option 3 release instructions](docs/old-releases/1.17.1.5.md).
Package the qualified outputs with `scripts/package_cascade_release.py`.
@@ -16,12 +62,13 @@ and `room-server-*` tags (for example `repeater-v1.0.0`). They invoke the shared
`firmware-builder.yml`, build their own target set, and request a draft release.
They are separate from the qualified option 3 matrix and local release staging.
**The broad `repeater-*` trigger also matches `repeater-room-*` Cascade tags.**
For 1.17.1.5 this workflow added its own artifacts and changed the title of the
The older broad `repeater-*` trigger also matched `repeater-room-*` Cascade tags.
For 1.17.1.5 that workflow added its own artifacts and changed the title of the
already-published Repeater/Room Server page. Do not assume a published Cascade
page is untouched while that workflow is running. Check workflow completion,
release title/body, and the final asset inventory after publication. Preserve
existing assets when making a documentation-only release edit.
The current trigger explicitly excludes `repeater-room-*`.
Use role-specific introductions for Companion, Repeater/Room Server, Sensor,
LoRa OTA Repeater, and expanded Full infrastructure pages. Link the exact
@@ -1,4 +1,17 @@
#include <AsyncElegantOTA.h>
#include "AsyncElegantOTA.h"
#include <atomic>
static std::atomic<bool> ota_uploads_enabled{false};
static std::atomic<bool> ota_upload_busy{false};
#if defined(ESP32)
static std::atomic<bool> ota_reboot_pending{false};
#endif
// All upload callbacks run on AsyncTCP's task. Update is a global singleton;
// retain one request as its owner until that request disconnects.
static AsyncWebServerRequest* ota_upload_owner = nullptr;
struct OtaUploadState {
bool file_final;
bool committed;
};
AsyncElegantOtaClass AsyncElegantOTA;
@@ -8,6 +21,7 @@ void AsyncElegantOtaClass::setID(const char* id){
void AsyncElegantOtaClass::begin(AsyncWebServer *server, const char* username, const char* password){
_server = server;
setEnabled(true);
if(strlen(username) > 0){
_authRequired = true;
@@ -49,15 +63,28 @@ void AsyncElegantOtaClass::begin(AsyncWebServer *server, const char* username, c
return request->requestAuthentication();
}
}
// the request handler is triggered after the upload has finished...
// create the response, add header, and send response
AsyncWebServerResponse *response = request->beginResponse((Update.hasError())?500:200, "text/plain", (Update.hasError())?"FAIL":"OK");
// send() queues a response until the request body has finished. Keep
// an earlier upload error even before it has reached the wire.
if (request->isSent() || request->getResponse()) return;
auto* state = static_cast<OtaUploadState*>(request->_tempObject);
// A file boundary can precede more multipart fields or another file.
// Update.end() selects the next boot partition, so commit only after
// the whole POST has completed without an earlier upload error.
if (state && state->file_final && !state->committed
&& ota_upload_owner == request && !Update.hasError()) {
if (!Update.end(true)) {
return request->send(400, "text/plain", "Could not end OTA");
}
state->committed = true;
}
const bool complete = state && state->committed && !Update.hasError();
AsyncWebServerResponse *response = request->beginResponse(complete?200:500, "text/plain", complete?"OK":"FAIL");
response->addHeader("Connection", "close");
response->addHeader("Access-Control-Allow-Origin", "*");
request->send(response);
restart();
}, [&](AsyncWebServerRequest *request, String filename, size_t index, uint8_t *data, size_t len, bool final) {
//Upload handler chunks in data
if (request->isSent() || request->getResponse()) return;
if(_authRequired){
if(!request->authenticate(_username.c_str(), _password.c_str())){
return request->requestAuthentication();
@@ -65,12 +92,26 @@ void AsyncElegantOtaClass::begin(AsyncWebServer *server, const char* username, c
}
if (!index) {
if(!request->hasParam("MD5", true)) {
return request->send(400, "text/plain", "MD5 parameter missing");
if (!ota_uploads_enabled.load()) {
return request->send(503, "text/plain", "OTA stopped");
}
if(!Update.setMD5(request->getParam("MD5", true)->value().c_str())) {
return request->send(400, "text/plain", "MD5 parameter invalid");
bool expected = false;
if (request->_tempObject
#if defined(ESP32)
|| ota_reboot_pending.load()
#endif
|| !ota_upload_busy.compare_exchange_strong(expected, true)
) {
return request->send(409, "text/plain", "OTA upload already active");
}
// A stop can race the first check before ownership was reserved.
if (!ota_uploads_enabled.load()) {
ota_upload_busy.store(false);
return request->send(503, "text/plain", "OTA stopped");
}
if(!request->hasParam("MD5", true)) {
ota_upload_busy.store(false);
return request->send(400, "text/plain", "MD5 parameter missing");
}
#if defined(ESP8266)
@@ -83,38 +124,100 @@ void AsyncElegantOtaClass::begin(AsyncWebServer *server, const char* username, c
int cmd = (filename == "filesystem") ? U_SPIFFS : U_FLASH;
if (!Update.begin(UPDATE_SIZE_UNKNOWN, cmd)) { // Start with max available size
#endif
Update.printError(Serial);
ota_upload_busy.store(false);
return request->send(400, "text/plain", "OTA could not begin");
}
// Arduino Update.begin() clears the expected digest. Set it only
// after begin succeeds so end() actually verifies the uploaded body.
if(!Update.setMD5(request->getParam("MD5", true)->value().c_str())) {
Update.abort();
ota_upload_busy.store(false);
return request->send(400, "text/plain", "MD5 parameter invalid");
}
// The HTTP listener defaults to a three-second receive timeout.
// A valid OTA body can pause longer on a weak WiFi link; retain a
// bounded timeout on this admitted upload without changing peers.
request->client()->setRxTimeout(30);
// AsyncWebServer frees this per-request allocation on disconnect.
// hasError() alone also reports success for a POST with no upload.
request->_tempObject = calloc(1, sizeof(OtaUploadState));
if (!request->_tempObject) {
Update.abort();
ota_upload_busy.store(false);
return request->send(503, "text/plain", "OTA state allocation failed");
}
ota_upload_owner = request;
request->onDisconnect([this, request]() {
if (ota_upload_owner != request) return;
ota_upload_owner = nullptr;
if (static_cast<OtaUploadState*>(request->_tempObject)->committed) {
// Let the response drain and the network callback return
// before rebooting from the separate restart task.
restart();
} else {
Update.abort(); // Release a disconnected, partial upload.
}
ota_upload_busy.store(false);
});
}
if (request->isSent() || ota_upload_owner != request) return;
// Write chunked data to the free sketch space
if(len){
if (Update.write(data, len) != len) {
return request->send(400, "text/plain", "OTA could not begin");
return request->send(400, "text/plain", "OTA flash write failed");
}
}
if (final) { // if the final flag is set then this is the last frame of data
if (!Update.end(true)) { //true to set the size to the current progress
Update.printError(Serial);
return request->send(400, "text/plain", "Could not end OTA");
}
}else{
return;
if (final) {
static_cast<OtaUploadState*>(request->_tempObject)->file_final = true;
}
});
}
bool AsyncElegantOtaClass::setEnabled(bool enabled) {
if (enabled) {
ota_uploads_enabled.store(true);
return true;
}
const bool was_enabled = ota_uploads_enabled.exchange(false);
if (ota_upload_busy.load()
#if defined(ESP32)
|| ota_reboot_pending.load()
#endif
) {
ota_uploads_enabled.store(was_enabled);
return false;
}
return true;
}
// deprecated, keeping for backward compatibility
void AsyncElegantOtaClass::loop() {
}
void AsyncElegantOtaClass::restart() {
#if defined(ESP32)
bool expected = false;
if (!ota_reboot_pending.compare_exchange_strong(expected, true)) return;
// Even disconnect handlers run on the shared AsyncTCP task. Return to it
// immediately, then reboot from a separate task after final TCP cleanup.
if (xTaskCreate([](void*) {
vTaskDelay(pdMS_TO_TICKS(1000));
ESP.restart();
vTaskDelete(nullptr);
}, "ota-reboot", 3072, nullptr, 1, nullptr) != pdPASS) {
ota_reboot_pending.store(false);
}
#else
yield();
delay(1000);
yield();
ESP.restart();
#endif
}
String AsyncElegantOtaClass::getID(){
@@ -33,6 +33,10 @@ class AsyncElegantOtaClass{
loop(),
restart();
// Stop admission atomically with upload ownership. Existing GET
// responses may drain while the containing listener is stopped.
bool setEnabled(bool enabled);
private:
AsyncWebServer *_server;
+3 -3
View File
@@ -8,7 +8,7 @@
"core": "esp32",
"extra_flags": [
"-DBOARD_HAS_PSRAM",
"-DARDUINO_USB_MODE=0",
"-DARDUINO_USB_MODE=1",
"-DARDUINO_USB_CDC_ON_BOOT=1",
"-DARDUINO_RUNNING_CORE=1",
"-DARDUINO_EVENT_RUNNING_CORE=1"
@@ -34,8 +34,8 @@
"flash_size": "16MB",
"maximum_ram_size": 8388608,
"maximum_size": 16777216,
"use_1200bps_touch": true,
"wait_for_upload_port": true,
"use_1200bps_touch": false,
"wait_for_upload_port": false,
"require_upload_port": true,
"speed": 921600
},
+3 -3
View File
@@ -8,7 +8,7 @@
"core": "esp32",
"extra_flags": [
"-DBOARD_HAS_PSRAM",
"-DARDUINO_USB_MODE=0",
"-DARDUINO_USB_MODE=1",
"-DARDUINO_USB_CDC_ON_BOOT=1",
"-DARDUINO_RUNNING_CORE=1",
"-DARDUINO_EVENT_RUNNING_CORE=1"
@@ -34,8 +34,8 @@
"flash_size": "16MB",
"maximum_ram_size": 8388608,
"maximum_size": 16777216,
"use_1200bps_touch": true,
"wait_for_upload_port": true,
"use_1200bps_touch": false,
"wait_for_upload_port": false,
"require_upload_port": true,
"speed": 921600
},
+3 -3
View File
@@ -8,7 +8,7 @@
"core": "esp32",
"extra_flags": [
"-DBOARD_HAS_PSRAM",
"-DARDUINO_USB_MODE=0",
"-DARDUINO_USB_MODE=1",
"-DARDUINO_USB_CDC_ON_BOOT=1",
"-DARDUINO_RUNNING_CORE=1",
"-DARDUINO_EVENT_RUNNING_CORE=1"
@@ -34,8 +34,8 @@
"flash_size": "16MB",
"maximum_ram_size": 8388608,
"maximum_size": 16777216,
"use_1200bps_touch": true,
"wait_for_upload_port": true,
"use_1200bps_touch": false,
"wait_for_upload_port": false,
"require_upload_port": true,
"speed": 921600
},
+3 -3
View File
@@ -7,7 +7,7 @@
"core": "esp32",
"extra_flags": [
"-DARDUINO_USB_CDC_ON_BOOT=1",
"-DARDUINO_USB_MODE=0",
"-DARDUINO_USB_MODE=1",
"-DARDUINO_RUNNING_CORE=1",
"-DARDUINO_EVENT_RUNNING_CORE=1"
],
@@ -30,8 +30,8 @@
"flash_size": "8MB",
"maximum_ram_size": 327680,
"maximum_size": 8388608,
"use_1200bps_touch": true,
"wait_for_upload_port": true,
"use_1200bps_touch": false,
"wait_for_upload_port": false,
"require_upload_port": true,
"speed": 921600
},
+3 -3
View File
@@ -7,7 +7,7 @@
"core": "esp32",
"extra_flags": [
"-DARDUINO_USB_CDC_ON_BOOT=1",
"-DARDUINO_USB_MODE=0",
"-DARDUINO_USB_MODE=1",
"-DARDUINO_RUNNING_CORE=1",
"-DARDUINO_EVENT_RUNNING_CORE=1"
],
@@ -30,8 +30,8 @@
"flash_size": "8MB",
"maximum_ram_size": 327680,
"maximum_size": 8388608,
"use_1200bps_touch": true,
"wait_for_upload_port": true,
"use_1200bps_touch": false,
"wait_for_upload_port": false,
"require_upload_port": true,
"speed": 921600
},
+3 -3
View File
@@ -8,7 +8,7 @@
"extra_flags": [
"-DBOARD_HAS_PSRAM",
"-DARDUINO_USB_CDC_ON_BOOT=1",
"-DARDUINO_USB_MODE=0",
"-DARDUINO_USB_MODE=1",
"-DARDUINO_RUNNING_CORE=1",
"-DARDUINO_EVENT_RUNNING_CORE=0"
],
@@ -33,8 +33,8 @@
"flash_size": "16MB",
"maximum_ram_size": 327680,
"maximum_size": 16777216,
"use_1200bps_touch": true,
"wait_for_upload_port": true,
"use_1200bps_touch": false,
"wait_for_upload_port": false,
"require_upload_port": true,
"speed": 921600
},
+3 -3
View File
@@ -8,7 +8,7 @@
"extra_flags": [
"-DBOARD_HAS_PSRAM",
"-DARDUINO_USB_CDC_ON_BOOT=1",
"-DARDUINO_USB_MODE=0",
"-DARDUINO_USB_MODE=1",
"-DARDUINO_RUNNING_CORE=1",
"-DARDUINO_EVENT_RUNNING_CORE=0"
],
@@ -33,8 +33,8 @@
"flash_size": "16MB",
"maximum_ram_size": 327680,
"maximum_size": 16777216,
"use_1200bps_touch": true,
"wait_for_upload_port": true,
"use_1200bps_touch": false,
"wait_for_upload_port": false,
"require_upload_port": true,
"speed": 921600
},
+3 -1
View File
@@ -10,7 +10,7 @@
"-DBOARD_HAS_PSRAM",
"-DLILYGO_TBEAM_1W",
"-DARDUINO_USB_CDC_ON_BOOT=1",
"-DARDUINO_USB_MODE=0",
"-DARDUINO_USB_MODE=1",
"-DARDUINO_RUNNING_CORE=1",
"-DARDUINO_EVENT_RUNNING_CORE=1"
],
@@ -40,6 +40,8 @@
],
"name": "LilyGo TBeam-1W",
"upload": {
"use_1200bps_touch": false,
"wait_for_upload_port": false,
"flash_size": "16MB",
"maximum_ram_size": 327680,
"maximum_size": 16777216,
+25 -3
View File
@@ -1,10 +1,10 @@
#!/usr/bin/env bash
# ./build-iotthinks.sh
export FIRMWARE_VERSION="PowerSaving17.1.2"
export FIRMWARE_VERSION="PowerSaving17.1.5"
############# Repeaters #############
# Commonly-used boards
## ESP32 - 21 boards
## ESP32 - 22 boards
./build.sh build-firmware \
Heltec_ct62_repeater \
Heltec_E290_repeater \
@@ -22,6 +22,7 @@ LilyGo_TDeck_repeater \
Station_G2_repeater \
T_Beam_S3_Supreme_SX1262_repeater \
Tbeam_SX1262_repeater \
ThinkNode_M2_Repeater \
ThinkNode_M5_Repeater \
Xiao_C3_repeater \
Xiao_C6_repeater_ \
@@ -91,6 +92,7 @@ Heltec_t096_companion_radio_ble_femoff \
Heltec_t1_companion_radio_ble \
Heltec_t114_companion_radio_ble \
MKE_s3_companion_radio_ble \
Mesh_pocket_companion_radio_ble \
LilyGo_T-Echo_Card_companion_radio_ble \
LilyGo_T-Echo_companion_radio_ble \
LilyGo_T-Echo-Lite_companion_radio_ble \
@@ -146,6 +148,9 @@ heltec_v4_companion_radio_usb_ps_femoff \
heltec_v4_companion_radio_usb_ps_femon \
heltec_tracker_v2_companion_radio_usb_femoff \
heltec_tracker_v2_companion_radio_usb_femon \
Heltec_v3_companion_radio_usb \
Mesh_pocket_companion_radio_usb \
ThinkNode_M2_companion_radio_usb \
MKE_s3_companion_radio_usb \
LilyGo_TBeam_1W_companion_radio_usb \
LilyGo_TDeck_companion_radio_usb \
@@ -156,11 +161,16 @@ Xiao_S3_WIO_companion_radio_usb
############# Sensors #############
./build.sh build-firmware \
Heltec_t096_sensor \
Heltec_t114_sensor \
t1000e_sensor
############# Bridge #############
./build.sh build-firmware \
Xiao_nrf52_repeater_bridge_rs232
############# Sample builds #############
# 27 boards
# 28 boards
./build.sh build-firmware \
Heltec_t096_companion_radio_ble_femon \
Heltec_t096_companion_radio_ble_femoff \
@@ -175,6 +185,7 @@ heltec_v4_3_companion_radio_ble_ps_femoff \
heltec_v4_companion_radio_ble_ps_femon \
heltec_v4_repeater \
heltec_v4_r8_companion_radio_ble_ps \
ProMicro_repeater \
RAK_3401_companion_radio_ble \
RAK_3401_repeater \
RAK_4631_companion_radio_ble \
@@ -190,6 +201,17 @@ Xiao_C6_repeater_ \
Xiao_nrf52_companion_radio_ble \
Xiao_nrf52_repeater
# Upstream application targets pending feedback. These are not evidence of
# factory bootloader compatibility; OTAFIX qualification remains independent.
./build.sh build-firmware \
wio_wm1110_repeater \
wio_wm1110_room_server \
wio_wm1110_companion_radio_ble
./build.sh build-firmware \
meshnology_w12_repeater \
meshnology_w12_companion_radio_ble
# Heltec v4 USB power-saving variants
./build.sh build-firmware \
heltec_v4_companion_radio_ble_ps_femon \
+793 -152
View File
File diff suppressed because it is too large. Load diff
+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.
+261 -275
View File
@@ -1,9 +1,9 @@
{
"schemaVersion": 1,
"repository": "mikecarper/Adafruit_nRF52_Bootloader_OTAFIX",
"tag": "v0.11.0-OTAFIX2.4.10",
"version": "2.4.10",
"releaseUrl": "https://github.com/mikecarper/Adafruit_nRF52_Bootloader_OTAFIX/releases/tag/v0.11.0-OTAFIX2.4.10",
"tag": "v0.11.0-OTAFIX2.4.11",
"version": "2.4.11",
"releaseUrl": "https://github.com/mikecarper/Adafruit_nRF52_Bootloader_OTAFIX/releases/tag/v0.11.0-OTAFIX2.4.11",
"prerelease": false,
"profiles": [
{
@@ -18,22 +18,22 @@
"storage": "internal",
"files": {
"uf2": {
"name": "update-gat562_bootloader-v0.11.0-OTAFIX2.4.10_mbr.uf2",
"url": "https://github.com/mikecarper/Adafruit_nRF52_Bootloader_OTAFIX/releases/download/v0.11.0-OTAFIX2.4.10/update-gat562_bootloader-v0.11.0-OTAFIX2.4.10_mbr.uf2",
"name": "update-gat562_bootloader-v0.11.0-OTAFIX2.4.11_mbr.uf2",
"url": "https://github.com/mikecarper/Adafruit_nRF52_Bootloader_OTAFIX/releases/download/v0.11.0-OTAFIX2.4.11/update-gat562_bootloader-v0.11.0-OTAFIX2.4.11_mbr.uf2",
"size": 84992,
"sha256": "4bbe3a8fa3951a67fa8c1c6a910744af6370d9a666eb7b88b6b760c5139f6c32"
"sha256": "2e3c5292abd9f87380e63926a01dc0cb437002d33acff8de2919d9cd79273927"
},
"zip": {
"name": "gat562_bootloader-v0.11.0-OTAFIX2.4.10_s140_6.1.1.zip",
"url": "https://github.com/mikecarper/Adafruit_nRF52_Bootloader_OTAFIX/releases/download/v0.11.0-OTAFIX2.4.10/gat562_bootloader-v0.11.0-OTAFIX2.4.10_s140_6.1.1.zip",
"size": 192833,
"sha256": "fe8346cd8d066caf4b4ee6a4c8605677e6457f022670d0fe270e021c98c5a1d4"
"name": "gat562_bootloader-v0.11.0-OTAFIX2.4.11_s140_6.1.1.zip",
"url": "https://github.com/mikecarper/Adafruit_nRF52_Bootloader_OTAFIX/releases/download/v0.11.0-OTAFIX2.4.11/gat562_bootloader-v0.11.0-OTAFIX2.4.11_s140_6.1.1.zip",
"size": 192834,
"sha256": "35bbb66831af4c1aaba1ae139f2c7d149a1b281c28aff3dac31a78f67ac026b0"
},
"hex": {
"name": "gat562_bootloader-v0.11.0-OTAFIX2.4.10_s140_6.1.1.hex",
"url": "https://github.com/mikecarper/Adafruit_nRF52_Bootloader_OTAFIX/releases/download/v0.11.0-OTAFIX2.4.10/gat562_bootloader-v0.11.0-OTAFIX2.4.10_s140_6.1.1.hex",
"size": 530672,
"sha256": "fa22eefe6c5a5eecf8c89abc2a195a3ccbe3bf28b7fb32d8b3c966489b92588b"
"name": "gat562_bootloader-v0.11.0-OTAFIX2.4.11_s140_6.1.1.hex",
"url": "https://github.com/mikecarper/Adafruit_nRF52_Bootloader_OTAFIX/releases/download/v0.11.0-OTAFIX2.4.11/gat562_bootloader-v0.11.0-OTAFIX2.4.11_s140_6.1.1.hex",
"size": 530716,
"sha256": "8df22a6951916f0eee1910cf8d449eb32fa712799ad12713045e06f93e355f82"
}
}
},
@@ -47,57 +47,28 @@
"storage": "internal",
"files": {
"uf2": {
"name": "update-heltec_mesh_pocket_bootloader-v0.11.0-OTAFIX2.4.10_mbr.uf2",
"url": "https://github.com/mikecarper/Adafruit_nRF52_Bootloader_OTAFIX/releases/download/v0.11.0-OTAFIX2.4.10/update-heltec_mesh_pocket_bootloader-v0.11.0-OTAFIX2.4.10_mbr.uf2",
"name": "update-heltec_mesh_pocket_bootloader-v0.11.0-OTAFIX2.4.11_mbr.uf2",
"url": "https://github.com/mikecarper/Adafruit_nRF52_Bootloader_OTAFIX/releases/download/v0.11.0-OTAFIX2.4.11/update-heltec_mesh_pocket_bootloader-v0.11.0-OTAFIX2.4.11_mbr.uf2",
"size": 84480,
"sha256": "594506767173bb83ef773b69464fe2fd94ff71d7b4c1318c2fee2bdb04cd1dd2"
"sha256": "3087bdd540db6a6399120a23012dc9d1e45ca9b72938abd49c389319f1a027db"
},
"zip": {
"name": "heltec_mesh_pocket_bootloader-v0.11.0-OTAFIX2.4.10_s140_6.1.1.zip",
"url": "https://github.com/mikecarper/Adafruit_nRF52_Bootloader_OTAFIX/releases/download/v0.11.0-OTAFIX2.4.10/heltec_mesh_pocket_bootloader-v0.11.0-OTAFIX2.4.10_s140_6.1.1.zip",
"name": "heltec_mesh_pocket_bootloader-v0.11.0-OTAFIX2.4.11_s140_6.1.1.zip",
"url": "https://github.com/mikecarper/Adafruit_nRF52_Bootloader_OTAFIX/releases/download/v0.11.0-OTAFIX2.4.11/heltec_mesh_pocket_bootloader-v0.11.0-OTAFIX2.4.11_s140_6.1.1.zip",
"size": 192834,
"sha256": "47bf7082e0a021c68c9c479d0effdc0f38fa150d9c49012922cad54c143240d1"
"sha256": "7251ed15ce18dba03151b676d7401f50a043a61370e6eb7230daddfa9248dda4"
},
"hex": {
"name": "heltec_mesh_pocket_bootloader-v0.11.0-OTAFIX2.4.10_s140_6.1.1.hex",
"url": "https://github.com/mikecarper/Adafruit_nRF52_Bootloader_OTAFIX/releases/download/v0.11.0-OTAFIX2.4.10/heltec_mesh_pocket_bootloader-v0.11.0-OTAFIX2.4.10_s140_6.1.1.hex",
"size": 530068,
"sha256": "5bb3041b17d25bf45fffc51316fac350497102aaa6363a4f04dcde31d5ee8614"
}
}
},
{
"id": "heltec_mesh_tower_v2",
"label": "Heltec MeshTower V2 - internal storage",
"meshcoreHardware": [
"Heltec_tower_v2"
],
"deviceName": "TOWER_V2_OTA",
"storage": "internal",
"files": {
"uf2": {
"name": "update-heltec_mesh_tower_v2_bootloader-v0.11.0-OTAFIX2.4.10_mbr.uf2",
"url": "https://github.com/mikecarper/Adafruit_nRF52_Bootloader_OTAFIX/releases/download/v0.11.0-OTAFIX2.4.10/update-heltec_mesh_tower_v2_bootloader-v0.11.0-OTAFIX2.4.10_mbr.uf2",
"size": 84480,
"sha256": "543766e38f347ce8c7bdeff6050fcd8d2c93e62c2a0510b318ac4e3c31f38552"
},
"zip": {
"name": "heltec_mesh_tower_v2_bootloader-v0.11.0-OTAFIX2.4.10_s140_6.1.1.zip",
"url": "https://github.com/mikecarper/Adafruit_nRF52_Bootloader_OTAFIX/releases/download/v0.11.0-OTAFIX2.4.10/heltec_mesh_tower_v2_bootloader-v0.11.0-OTAFIX2.4.10_s140_6.1.1.zip",
"size": 192834,
"sha256": "aa4558d5ad651bf820f73e8256d0130aa6d41bdb3a54b73297253c0a986573f0"
},
"hex": {
"name": "heltec_mesh_tower_v2_bootloader-v0.11.0-OTAFIX2.4.10_s140_6.1.1.hex",
"url": "https://github.com/mikecarper/Adafruit_nRF52_Bootloader_OTAFIX/releases/download/v0.11.0-OTAFIX2.4.10/heltec_mesh_tower_v2_bootloader-v0.11.0-OTAFIX2.4.10_s140_6.1.1.hex",
"size": 530462,
"sha256": "a1371c6dc1c22648dd8625c93f9789f65e5b0123b0cb8d4ca6bfd9c51bc51a4a"
"name": "heltec_mesh_pocket_bootloader-v0.11.0-OTAFIX2.4.11_s140_6.1.1.hex",
"url": "https://github.com/mikecarper/Adafruit_nRF52_Bootloader_OTAFIX/releases/download/v0.11.0-OTAFIX2.4.11/heltec_mesh_pocket_bootloader-v0.11.0-OTAFIX2.4.11_s140_6.1.1.hex",
"size": 530112,
"sha256": "f0b836e53af97d5445236ef89adb8443e5c8a4691e71488ff930cca2b3a57d90"
}
}
},
{
"id": "heltec_mesh_tower_v2_sdcard",
"label": "Heltec MeshTower V2 - microSD",
"label": "Heltec MeshTower V2 - microSD + internal (SD primary)",
"meshcoreHardware": [
"Heltec_tower_v2_sdcard"
],
@@ -105,22 +76,22 @@
"storage": "sd",
"files": {
"uf2": {
"name": "update-heltec_mesh_tower_v2_sdcard_bootloader-v0.11.0-OTAFIX2.4.10_mbr.uf2",
"url": "https://github.com/mikecarper/Adafruit_nRF52_Bootloader_OTAFIX/releases/download/v0.11.0-OTAFIX2.4.10/update-heltec_mesh_tower_v2_sdcard_bootloader-v0.11.0-OTAFIX2.4.10_mbr.uf2",
"size": 86528,
"sha256": "69ca460d976b778f3465be10ed11ed1abcd1fbe0bca8ce85b5fc2a5620f3e7a8"
"name": "update-heltec_mesh_tower_v2_sdcard_bootloader-v0.11.0-OTAFIX2.4.11_mbr.uf2",
"url": "https://github.com/mikecarper/Adafruit_nRF52_Bootloader_OTAFIX/releases/download/v0.11.0-OTAFIX2.4.11/update-heltec_mesh_tower_v2_sdcard_bootloader-v0.11.0-OTAFIX2.4.11_mbr.uf2",
"size": 88064,
"sha256": "26e8cc90bdf3a138bd8f5471f17c04cbeaddf5dfb01bf62601917f7d0c8fa825"
},
"zip": {
"name": "heltec_mesh_tower_v2_sdcard_bootloader-v0.11.0-OTAFIX2.4.10_s140_6.1.1.zip",
"url": "https://github.com/mikecarper/Adafruit_nRF52_Bootloader_OTAFIX/releases/download/v0.11.0-OTAFIX2.4.10/heltec_mesh_tower_v2_sdcard_bootloader-v0.11.0-OTAFIX2.4.10_s140_6.1.1.zip",
"size": 192834,
"sha256": "d967469e8c10885b00b235d3d2b451029699b2b41b6882d0011120667036ed8b"
"name": "heltec_mesh_tower_v2_sdcard_bootloader-v0.11.0-OTAFIX2.4.11_s140_6.1.1.zip",
"url": "https://github.com/mikecarper/Adafruit_nRF52_Bootloader_OTAFIX/releases/download/v0.11.0-OTAFIX2.4.11/heltec_mesh_tower_v2_sdcard_bootloader-v0.11.0-OTAFIX2.4.11_s140_6.1.1.zip",
"size": 192833,
"sha256": "ac5fdfa777ba583cc9e62e7cd1fef3cf0a3b97da5b221a332380b2209d31b79c"
},
"hex": {
"name": "heltec_mesh_tower_v2_sdcard_bootloader-v0.11.0-OTAFIX2.4.10_s140_6.1.1.hex",
"url": "https://github.com/mikecarper/Adafruit_nRF52_Bootloader_OTAFIX/releases/download/v0.11.0-OTAFIX2.4.10/heltec_mesh_tower_v2_sdcard_bootloader-v0.11.0-OTAFIX2.4.10_s140_6.1.1.hex",
"size": 532874,
"sha256": "5c491b436f34cb164c17195083309f375f84628ccb0c1f8ef166ce6d9ad5d2e1"
"name": "heltec_mesh_tower_v2_sdcard_bootloader-v0.11.0-OTAFIX2.4.11_s140_6.1.1.hex",
"url": "https://github.com/mikecarper/Adafruit_nRF52_Bootloader_OTAFIX/releases/download/v0.11.0-OTAFIX2.4.11/heltec_mesh_tower_v2_sdcard_bootloader-v0.11.0-OTAFIX2.4.11_s140_6.1.1.hex",
"size": 535746,
"sha256": "5980d8f96a3b9dbb55bbf488cbeacdcae1c6f59ff78c2766254c8b95c5e6beab"
}
}
},
@@ -134,22 +105,22 @@
"storage": "internal",
"files": {
"uf2": {
"name": "update-heltec_t096_bootloader-v0.11.0-OTAFIX2.4.10_mbr.uf2",
"url": "https://github.com/mikecarper/Adafruit_nRF52_Bootloader_OTAFIX/releases/download/v0.11.0-OTAFIX2.4.10/update-heltec_t096_bootloader-v0.11.0-OTAFIX2.4.10_mbr.uf2",
"name": "update-heltec_t096_bootloader-v0.11.0-OTAFIX2.4.11_mbr.uf2",
"url": "https://github.com/mikecarper/Adafruit_nRF52_Bootloader_OTAFIX/releases/download/v0.11.0-OTAFIX2.4.11/update-heltec_t096_bootloader-v0.11.0-OTAFIX2.4.11_mbr.uf2",
"size": 87552,
"sha256": "d3f01cb890f21ca9f93fd2ce5d7dfc722dd0eb15069ba05b06bf1440b876f7b4"
"sha256": "a7db3f390ab8934a33ccc1f60ac0acf1b5200f58fc6c037e233a178d5d416da8"
},
"zip": {
"name": "heltec_t096_bootloader-v0.11.0-OTAFIX2.4.10_s140_6.1.1.zip",
"url": "https://github.com/mikecarper/Adafruit_nRF52_Bootloader_OTAFIX/releases/download/v0.11.0-OTAFIX2.4.10/heltec_t096_bootloader-v0.11.0-OTAFIX2.4.10_s140_6.1.1.zip",
"size": 192833,
"sha256": "88780871f385463258e3b3447b41baf8158f651ae5554e952f958a9bb488f3b5"
"name": "heltec_t096_bootloader-v0.11.0-OTAFIX2.4.11_s140_6.1.1.zip",
"url": "https://github.com/mikecarper/Adafruit_nRF52_Bootloader_OTAFIX/releases/download/v0.11.0-OTAFIX2.4.11/heltec_t096_bootloader-v0.11.0-OTAFIX2.4.11_s140_6.1.1.zip",
"size": 192834,
"sha256": "4c2f1adfa28ad8a4a856c7c720ad1dbcb8cf13fcdc381152761bd72fadba227c"
},
"hex": {
"name": "heltec_t096_bootloader-v0.11.0-OTAFIX2.4.10_s140_6.1.1.hex",
"url": "https://github.com/mikecarper/Adafruit_nRF52_Bootloader_OTAFIX/releases/download/v0.11.0-OTAFIX2.4.10/heltec_t096_bootloader-v0.11.0-OTAFIX2.4.10_s140_6.1.1.hex",
"size": 534426,
"sha256": "7a793922c1ab66b6b93b308a57f400aa2820adfd9daf8b379093ba6f39dbd2ec"
"name": "heltec_t096_bootloader-v0.11.0-OTAFIX2.4.11_s140_6.1.1.hex",
"url": "https://github.com/mikecarper/Adafruit_nRF52_Bootloader_OTAFIX/releases/download/v0.11.0-OTAFIX2.4.11/heltec_t096_bootloader-v0.11.0-OTAFIX2.4.11_s140_6.1.1.hex",
"size": 534470,
"sha256": "878071c4e7bc16a9ec664773d34eadb7be6846e8791bcc87270ba5e41b463e2b"
}
}
},
@@ -163,22 +134,22 @@
"storage": "internal",
"files": {
"uf2": {
"name": "update-heltec_t1_bootloader-v0.11.0-OTAFIX2.4.10_mbr.uf2",
"url": "https://github.com/mikecarper/Adafruit_nRF52_Bootloader_OTAFIX/releases/download/v0.11.0-OTAFIX2.4.10/update-heltec_t1_bootloader-v0.11.0-OTAFIX2.4.10_mbr.uf2",
"name": "update-heltec_t1_bootloader-v0.11.0-OTAFIX2.4.11_mbr.uf2",
"url": "https://github.com/mikecarper/Adafruit_nRF52_Bootloader_OTAFIX/releases/download/v0.11.0-OTAFIX2.4.11/update-heltec_t1_bootloader-v0.11.0-OTAFIX2.4.11_mbr.uf2",
"size": 87552,
"sha256": "c39f8878bbbdb8df323bb77526088a7ff711b5b532a8e965b22fa9cd502a97a1"
"sha256": "3a053de7ba28e63756cd52d0e5632ce07bd244eeb271720f1178e71178680d77"
},
"zip": {
"name": "heltec_t1_bootloader-v0.11.0-OTAFIX2.4.10_s140_6.1.1.zip",
"url": "https://github.com/mikecarper/Adafruit_nRF52_Bootloader_OTAFIX/releases/download/v0.11.0-OTAFIX2.4.10/heltec_t1_bootloader-v0.11.0-OTAFIX2.4.10_s140_6.1.1.zip",
"size": 192833,
"sha256": "f3b876332448098cf68cb9c475729bd2f87e3cdfa71676bc362d7fd900abebbb"
"name": "heltec_t1_bootloader-v0.11.0-OTAFIX2.4.11_s140_6.1.1.zip",
"url": "https://github.com/mikecarper/Adafruit_nRF52_Bootloader_OTAFIX/releases/download/v0.11.0-OTAFIX2.4.11/heltec_t1_bootloader-v0.11.0-OTAFIX2.4.11_s140_6.1.1.zip",
"size": 192834,
"sha256": "b7144a4493a23902b07e31435f8c3758afe0b5d87f777236705865f51bb00ecb"
},
"hex": {
"name": "heltec_t1_bootloader-v0.11.0-OTAFIX2.4.10_s140_6.1.1.hex",
"url": "https://github.com/mikecarper/Adafruit_nRF52_Bootloader_OTAFIX/releases/download/v0.11.0-OTAFIX2.4.10/heltec_t1_bootloader-v0.11.0-OTAFIX2.4.10_s140_6.1.1.hex",
"size": 534464,
"sha256": "22c8701030be8ea3fa68612a99fe40b08e2ddae085768c87f82ad562dc2777ed"
"name": "heltec_t1_bootloader-v0.11.0-OTAFIX2.4.11_s140_6.1.1.hex",
"url": "https://github.com/mikecarper/Adafruit_nRF52_Bootloader_OTAFIX/releases/download/v0.11.0-OTAFIX2.4.11/heltec_t1_bootloader-v0.11.0-OTAFIX2.4.11_s140_6.1.1.hex",
"size": 534508,
"sha256": "8ba4ddc312f2246e5ace749e5734934ab5537c4ef60354a34658eebfa4d50449"
}
}
},
@@ -193,22 +164,22 @@
"storage": "internal",
"files": {
"uf2": {
"name": "update-heltec_t114_bootloader-v0.11.0-OTAFIX2.4.10_mbr.uf2",
"url": "https://github.com/mikecarper/Adafruit_nRF52_Bootloader_OTAFIX/releases/download/v0.11.0-OTAFIX2.4.10/update-heltec_t114_bootloader-v0.11.0-OTAFIX2.4.10_mbr.uf2",
"name": "update-heltec_t114_bootloader-v0.11.0-OTAFIX2.4.11_mbr.uf2",
"url": "https://github.com/mikecarper/Adafruit_nRF52_Bootloader_OTAFIX/releases/download/v0.11.0-OTAFIX2.4.11/update-heltec_t114_bootloader-v0.11.0-OTAFIX2.4.11_mbr.uf2",
"size": 88064,
"sha256": "daf96a592012b2579876777265a891407631ef8a9cba059ebca1e78a7fe307d0"
"sha256": "d8307134c4f63a9a91d86937abe25863722ff61f822524fc027847c6137282cc"
},
"zip": {
"name": "heltec_t114_bootloader-v0.11.0-OTAFIX2.4.10_s140_6.1.1.zip",
"url": "https://github.com/mikecarper/Adafruit_nRF52_Bootloader_OTAFIX/releases/download/v0.11.0-OTAFIX2.4.10/heltec_t114_bootloader-v0.11.0-OTAFIX2.4.10_s140_6.1.1.zip",
"name": "heltec_t114_bootloader-v0.11.0-OTAFIX2.4.11_s140_6.1.1.zip",
"url": "https://github.com/mikecarper/Adafruit_nRF52_Bootloader_OTAFIX/releases/download/v0.11.0-OTAFIX2.4.11/heltec_t114_bootloader-v0.11.0-OTAFIX2.4.11_s140_6.1.1.zip",
"size": 192834,
"sha256": "cf6b2e0770f9bba9fbaa8a5c967af325dc695479e2d3f6b79349b4dcf13545dc"
"sha256": "a473faf32cf3e4dc13c5d8340e7ffebd04741be3e37f8c2cb98b2c176e01a777"
},
"hex": {
"name": "heltec_t114_bootloader-v0.11.0-OTAFIX2.4.10_s140_6.1.1.hex",
"url": "https://github.com/mikecarper/Adafruit_nRF52_Bootloader_OTAFIX/releases/download/v0.11.0-OTAFIX2.4.10/heltec_t114_bootloader-v0.11.0-OTAFIX2.4.10_s140_6.1.1.hex",
"size": 535336,
"sha256": "f2912d3a4cf6bd81226d17fc75324cd83d7b97a8e4808e7d5c191634865c553d"
"name": "heltec_t114_bootloader-v0.11.0-OTAFIX2.4.11_s140_6.1.1.hex",
"url": "https://github.com/mikecarper/Adafruit_nRF52_Bootloader_OTAFIX/releases/download/v0.11.0-OTAFIX2.4.11/heltec_t114_bootloader-v0.11.0-OTAFIX2.4.11_s140_6.1.1.hex",
"size": 535380,
"sha256": "5afa51d860b54dc820d94f67efb8f7f449b0a4f461bbc176ccbd27a9730bc4e9"
}
}
},
@@ -222,22 +193,22 @@
"storage": "internal",
"files": {
"uf2": {
"name": "update-keepteen_lt1_bootloader-v0.11.0-OTAFIX2.4.10_mbr.uf2",
"url": "https://github.com/mikecarper/Adafruit_nRF52_Bootloader_OTAFIX/releases/download/v0.11.0-OTAFIX2.4.10/update-keepteen_lt1_bootloader-v0.11.0-OTAFIX2.4.10_mbr.uf2",
"name": "update-keepteen_lt1_bootloader-v0.11.0-OTAFIX2.4.11_mbr.uf2",
"url": "https://github.com/mikecarper/Adafruit_nRF52_Bootloader_OTAFIX/releases/download/v0.11.0-OTAFIX2.4.11/update-keepteen_lt1_bootloader-v0.11.0-OTAFIX2.4.11_mbr.uf2",
"size": 84480,
"sha256": "78460767341d867cd9a77845cad9de7d00e396ca5ab8340927a605c4fa899f2d"
"sha256": "c2ad478f12cd3176f5ea4442726fdaebb6071218564a97b7c90cf52ed214f369"
},
"zip": {
"name": "keepteen_lt1_bootloader-v0.11.0-OTAFIX2.4.10_s140_6.1.1.zip",
"url": "https://github.com/mikecarper/Adafruit_nRF52_Bootloader_OTAFIX/releases/download/v0.11.0-OTAFIX2.4.10/keepteen_lt1_bootloader-v0.11.0-OTAFIX2.4.10_s140_6.1.1.zip",
"name": "keepteen_lt1_bootloader-v0.11.0-OTAFIX2.4.11_s140_6.1.1.zip",
"url": "https://github.com/mikecarper/Adafruit_nRF52_Bootloader_OTAFIX/releases/download/v0.11.0-OTAFIX2.4.11/keepteen_lt1_bootloader-v0.11.0-OTAFIX2.4.11_s140_6.1.1.zip",
"size": 192834,
"sha256": "425c26680603a2c9b6b7becb95412611a8449ea51cf427f0997335c233a4275f"
"sha256": "f7a21a21afad7ecbf0e29f65e243b103f4b423fe575734fa4f00db56947f7c64"
},
"hex": {
"name": "keepteen_lt1_bootloader-v0.11.0-OTAFIX2.4.10_s140_6.1.1.hex",
"url": "https://github.com/mikecarper/Adafruit_nRF52_Bootloader_OTAFIX/releases/download/v0.11.0-OTAFIX2.4.10/keepteen_lt1_bootloader-v0.11.0-OTAFIX2.4.10_s140_6.1.1.hex",
"size": 529990,
"sha256": "9b1b4eed8033085e07bfb6c162fb0a46ee3aca157ec6d3fae162b548c9bc9764"
"name": "keepteen_lt1_bootloader-v0.11.0-OTAFIX2.4.11_s140_6.1.1.hex",
"url": "https://github.com/mikecarper/Adafruit_nRF52_Bootloader_OTAFIX/releases/download/v0.11.0-OTAFIX2.4.11/keepteen_lt1_bootloader-v0.11.0-OTAFIX2.4.11_s140_6.1.1.hex",
"size": 530034,
"sha256": "dbad71f1975f26c4f42e3057d2e6de82201fa9488e39556070ad68fa650204fe"
}
}
},
@@ -251,22 +222,22 @@
"storage": "qspi",
"files": {
"uf2": {
"name": "update-lilygo_techo_bootloader-v0.11.0-OTAFIX2.4.10_mbr.uf2",
"url": "https://github.com/mikecarper/Adafruit_nRF52_Bootloader_OTAFIX/releases/download/v0.11.0-OTAFIX2.4.10/update-lilygo_techo_bootloader-v0.11.0-OTAFIX2.4.10_mbr.uf2",
"name": "update-lilygo_techo_bootloader-v0.11.0-OTAFIX2.4.11_mbr.uf2",
"url": "https://github.com/mikecarper/Adafruit_nRF52_Bootloader_OTAFIX/releases/download/v0.11.0-OTAFIX2.4.11/update-lilygo_techo_bootloader-v0.11.0-OTAFIX2.4.11_mbr.uf2",
"size": 86528,
"sha256": "506719ba59e5172beab8904044b6f8700442467d5a2df6889710de8800e77a30"
"sha256": "9a3cd2a73f91f9f1ca01f44ba56c4b1ad2317511e766533ed61c361aca6e37fa"
},
"zip": {
"name": "lilygo_techo_bootloader-v0.11.0-OTAFIX2.4.10_s140_6.1.1.zip",
"url": "https://github.com/mikecarper/Adafruit_nRF52_Bootloader_OTAFIX/releases/download/v0.11.0-OTAFIX2.4.10/lilygo_techo_bootloader-v0.11.0-OTAFIX2.4.10_s140_6.1.1.zip",
"size": 192833,
"sha256": "cbcb872bb3525596e699d37a39bff85f2d02b178174c9812c461b21ba0753599"
"name": "lilygo_techo_bootloader-v0.11.0-OTAFIX2.4.11_s140_6.1.1.zip",
"url": "https://github.com/mikecarper/Adafruit_nRF52_Bootloader_OTAFIX/releases/download/v0.11.0-OTAFIX2.4.11/lilygo_techo_bootloader-v0.11.0-OTAFIX2.4.11_s140_6.1.1.zip",
"size": 192834,
"sha256": "a7cbdf2c911542222c0d12eb5fb2ab9bdf2263495b591d1a946860b324bf4731"
},
"hex": {
"name": "lilygo_techo_bootloader-v0.11.0-OTAFIX2.4.10_s140_6.1.1.hex",
"url": "https://github.com/mikecarper/Adafruit_nRF52_Bootloader_OTAFIX/releases/download/v0.11.0-OTAFIX2.4.10/lilygo_techo_bootloader-v0.11.0-OTAFIX2.4.10_s140_6.1.1.hex",
"size": 532886,
"sha256": "b7112639c09eae5dd2a015c6a5454d6d71bf0b677e9752ed23cc4dfc0e454cb7"
"name": "lilygo_techo_bootloader-v0.11.0-OTAFIX2.4.11_s140_6.1.1.hex",
"url": "https://github.com/mikecarper/Adafruit_nRF52_Bootloader_OTAFIX/releases/download/v0.11.0-OTAFIX2.4.11/lilygo_techo_bootloader-v0.11.0-OTAFIX2.4.11_s140_6.1.1.hex",
"size": 532930,
"sha256": "5c8f10b9b1217f36b8ff6f0dac957f108ca4125bc5e737dd827c08c01a5e556f"
}
}
},
@@ -281,22 +252,22 @@
"storage": "qspi",
"files": {
"uf2": {
"name": "update-lilygo_techo_lite_bootloader-v0.11.0-OTAFIX2.4.10_mbr.uf2",
"url": "https://github.com/mikecarper/Adafruit_nRF52_Bootloader_OTAFIX/releases/download/v0.11.0-OTAFIX2.4.10/update-lilygo_techo_lite_bootloader-v0.11.0-OTAFIX2.4.10_mbr.uf2",
"name": "update-lilygo_techo_lite_bootloader-v0.11.0-OTAFIX2.4.11_mbr.uf2",
"url": "https://github.com/mikecarper/Adafruit_nRF52_Bootloader_OTAFIX/releases/download/v0.11.0-OTAFIX2.4.11/update-lilygo_techo_lite_bootloader-v0.11.0-OTAFIX2.4.11_mbr.uf2",
"size": 87040,
"sha256": "a3db93e42b1d59d5b89bab58ebee1207cc5634dd7d6543a885484fb46e509073"
"sha256": "5ee193ed3ac342502efb03adc3aa214eb56198f22975fe5dfcf4d5743d4788d0"
},
"zip": {
"name": "lilygo_techo_lite_bootloader-v0.11.0-OTAFIX2.4.10_s140_6.1.1.zip",
"url": "https://github.com/mikecarper/Adafruit_nRF52_Bootloader_OTAFIX/releases/download/v0.11.0-OTAFIX2.4.10/lilygo_techo_lite_bootloader-v0.11.0-OTAFIX2.4.10_s140_6.1.1.zip",
"name": "lilygo_techo_lite_bootloader-v0.11.0-OTAFIX2.4.11_s140_6.1.1.zip",
"url": "https://github.com/mikecarper/Adafruit_nRF52_Bootloader_OTAFIX/releases/download/v0.11.0-OTAFIX2.4.11/lilygo_techo_lite_bootloader-v0.11.0-OTAFIX2.4.11_s140_6.1.1.zip",
"size": 192834,
"sha256": "adf1991f5377ed69905e16df138911096c31ecb3b4f4a6e9da417e5c91d3fb2d"
"sha256": "2ed15158b2b015a445c3d91ad616c8550e3b6ed59de2ca54832de4cb8d5ce10d"
},
"hex": {
"name": "lilygo_techo_lite_bootloader-v0.11.0-OTAFIX2.4.10_s140_6.1.1.hex",
"url": "https://github.com/mikecarper/Adafruit_nRF52_Bootloader_OTAFIX/releases/download/v0.11.0-OTAFIX2.4.10/lilygo_techo_lite_bootloader-v0.11.0-OTAFIX2.4.10_s140_6.1.1.hex",
"size": 533482,
"sha256": "b750f8ad93f346a9d5a4ca701bbaa5697e63b08b028f43321a8c0ddd4530346b"
"name": "lilygo_techo_lite_bootloader-v0.11.0-OTAFIX2.4.11_s140_6.1.1.hex",
"url": "https://github.com/mikecarper/Adafruit_nRF52_Bootloader_OTAFIX/releases/download/v0.11.0-OTAFIX2.4.11/lilygo_techo_lite_bootloader-v0.11.0-OTAFIX2.4.11_s140_6.1.1.hex",
"size": 533526,
"sha256": "d3531513bc550431caf9893dd88a8324f83f401b7b1700bbee6cd6c8a2724983"
}
}
},
@@ -310,22 +281,22 @@
"storage": "internal",
"files": {
"uf2": {
"name": "update-minewsemi_mx25le01_bootloader-v0.11.0-OTAFIX2.4.10_mbr.uf2",
"url": "https://github.com/mikecarper/Adafruit_nRF52_Bootloader_OTAFIX/releases/download/v0.11.0-OTAFIX2.4.10/update-minewsemi_mx25le01_bootloader-v0.11.0-OTAFIX2.4.10_mbr.uf2",
"name": "update-minewsemi_mx25le01_bootloader-v0.11.0-OTAFIX2.4.11_mbr.uf2",
"url": "https://github.com/mikecarper/Adafruit_nRF52_Bootloader_OTAFIX/releases/download/v0.11.0-OTAFIX2.4.11/update-minewsemi_mx25le01_bootloader-v0.11.0-OTAFIX2.4.11_mbr.uf2",
"size": 84480,
"sha256": "73784ca136fe9c0ebe22d4a0222dd18572bbd5740322ef133f6edbdd6b51353e"
"sha256": "c5858e6ed516af22202a2c9b1c155764ae25adaf55a42941d9665df77d44fad7"
},
"zip": {
"name": "minewsemi_mx25le01_bootloader-v0.11.0-OTAFIX2.4.10_s140_7.3.0.zip",
"url": "https://github.com/mikecarper/Adafruit_nRF52_Bootloader_OTAFIX/releases/download/v0.11.0-OTAFIX2.4.10/minewsemi_mx25le01_bootloader-v0.11.0-OTAFIX2.4.10_s140_7.3.0.zip",
"name": "minewsemi_mx25le01_bootloader-v0.11.0-OTAFIX2.4.11_s140_7.3.0.zip",
"url": "https://github.com/mikecarper/Adafruit_nRF52_Bootloader_OTAFIX/releases/download/v0.11.0-OTAFIX2.4.11/minewsemi_mx25le01_bootloader-v0.11.0-OTAFIX2.4.11_s140_7.3.0.zip",
"size": 194546,
"sha256": "d1bc22c9822447f18b1590662ca95b612e3ccbf1353892a67441c87315ab457c"
"sha256": "322b8c8e9caaca660bd2ed68f1d19f5cd38575ea7def6f262e652e2a872f7f2d"
},
"hex": {
"name": "minewsemi_mx25le01_bootloader-v0.11.0-OTAFIX2.4.10_s140_7.3.0.hex",
"url": "https://github.com/mikecarper/Adafruit_nRF52_Bootloader_OTAFIX/releases/download/v0.11.0-OTAFIX2.4.10/minewsemi_mx25le01_bootloader-v0.11.0-OTAFIX2.4.10_s140_7.3.0.hex",
"size": 535040,
"sha256": "c6229d65c7f7543bc48cb0a362a4799c7b0781684f9a63156686cf66aa085d79"
"name": "minewsemi_mx25le01_bootloader-v0.11.0-OTAFIX2.4.11_s140_7.3.0.hex",
"url": "https://github.com/mikecarper/Adafruit_nRF52_Bootloader_OTAFIX/releases/download/v0.11.0-OTAFIX2.4.11/minewsemi_mx25le01_bootloader-v0.11.0-OTAFIX2.4.11_s140_7.3.0.hex",
"size": 535084,
"sha256": "9de94798b5c25018ee9a5494b776bd5469f5c743d0fa9cbb82dfc7dda115fbbf"
}
}
},
@@ -339,22 +310,22 @@
"storage": "qspi",
"files": {
"uf2": {
"name": "update-pca10056_bootloader-v0.11.0-OTAFIX2.4.10_mbr.uf2",
"url": "https://github.com/mikecarper/Adafruit_nRF52_Bootloader_OTAFIX/releases/download/v0.11.0-OTAFIX2.4.10/update-pca10056_bootloader-v0.11.0-OTAFIX2.4.10_mbr.uf2",
"name": "update-pca10056_bootloader-v0.11.0-OTAFIX2.4.11_mbr.uf2",
"url": "https://github.com/mikecarper/Adafruit_nRF52_Bootloader_OTAFIX/releases/download/v0.11.0-OTAFIX2.4.11/update-pca10056_bootloader-v0.11.0-OTAFIX2.4.11_mbr.uf2",
"size": 86528,
"sha256": "8f6b2f37b33e2b4c7a5b038e9deb04e088e1da4b716d7f6787f201acd9af8315"
"sha256": "78ab8f3f8ea44d60c13d6fd34088451b1bd26745757deed44826c15c7ee70ea8"
},
"zip": {
"name": "pca10056_bootloader-v0.11.0-OTAFIX2.4.10_s140_6.1.1.zip",
"url": "https://github.com/mikecarper/Adafruit_nRF52_Bootloader_OTAFIX/releases/download/v0.11.0-OTAFIX2.4.10/pca10056_bootloader-v0.11.0-OTAFIX2.4.10_s140_6.1.1.zip",
"name": "pca10056_bootloader-v0.11.0-OTAFIX2.4.11_s140_6.1.1.zip",
"url": "https://github.com/mikecarper/Adafruit_nRF52_Bootloader_OTAFIX/releases/download/v0.11.0-OTAFIX2.4.11/pca10056_bootloader-v0.11.0-OTAFIX2.4.11_s140_6.1.1.zip",
"size": 192834,
"sha256": "76db09c07e9175204368a939eb71e920aae7168633dec4f122851c62027d83bc"
"sha256": "d5d01a01f98850b3282cf7f443fa13e642a42617d7267cee22739880fbcf2ff9"
},
"hex": {
"name": "pca10056_bootloader-v0.11.0-OTAFIX2.4.10_s140_6.1.1.hex",
"url": "https://github.com/mikecarper/Adafruit_nRF52_Bootloader_OTAFIX/releases/download/v0.11.0-OTAFIX2.4.10/pca10056_bootloader-v0.11.0-OTAFIX2.4.10_s140_6.1.1.hex",
"size": 532976,
"sha256": "6e77e4fc3adf07e3ca455ce02ca021d8097261b43834844f6225d8f1c80616c8"
"name": "pca10056_bootloader-v0.11.0-OTAFIX2.4.11_s140_6.1.1.hex",
"url": "https://github.com/mikecarper/Adafruit_nRF52_Bootloader_OTAFIX/releases/download/v0.11.0-OTAFIX2.4.11/pca10056_bootloader-v0.11.0-OTAFIX2.4.11_s140_6.1.1.hex",
"size": 533020,
"sha256": "51ceaa88711ca24a8430bb6f35766df729245cbe2858336dad3082751c692fc2"
}
}
},
@@ -368,22 +339,22 @@
"storage": "internal",
"files": {
"uf2": {
"name": "update-promicro_nrf52840_bootloader-v0.11.0-OTAFIX2.4.10_mbr.uf2",
"url": "https://github.com/mikecarper/Adafruit_nRF52_Bootloader_OTAFIX/releases/download/v0.11.0-OTAFIX2.4.10/update-promicro_nrf52840_bootloader-v0.11.0-OTAFIX2.4.10_mbr.uf2",
"name": "update-promicro_nrf52840_bootloader-v0.11.0-OTAFIX2.4.11_mbr.uf2",
"url": "https://github.com/mikecarper/Adafruit_nRF52_Bootloader_OTAFIX/releases/download/v0.11.0-OTAFIX2.4.11/update-promicro_nrf52840_bootloader-v0.11.0-OTAFIX2.4.11_mbr.uf2",
"size": 84480,
"sha256": "a445375e828a3724f3e9d7854175d5a1c120eef0613cc6fd7c0d4cfa03a5d04a"
"sha256": "b849203d30a56b5243f8d92e308c6cd9a1eb3b35459072204f9f044d74dbad09"
},
"zip": {
"name": "promicro_nrf52840_bootloader-v0.11.0-OTAFIX2.4.10_s140_6.1.1.zip",
"url": "https://github.com/mikecarper/Adafruit_nRF52_Bootloader_OTAFIX/releases/download/v0.11.0-OTAFIX2.4.10/promicro_nrf52840_bootloader-v0.11.0-OTAFIX2.4.10_s140_6.1.1.zip",
"name": "promicro_nrf52840_bootloader-v0.11.0-OTAFIX2.4.11_s140_6.1.1.zip",
"url": "https://github.com/mikecarper/Adafruit_nRF52_Bootloader_OTAFIX/releases/download/v0.11.0-OTAFIX2.4.11/promicro_nrf52840_bootloader-v0.11.0-OTAFIX2.4.11_s140_6.1.1.zip",
"size": 192834,
"sha256": "10b32c1133be24a532f9678d7517c926ed474b751f99bc33d35a3a01c54d3aba"
"sha256": "3b2ef682cc2641626b4db3d8bfe821cd86dd701f38c7c0c055864d78da0fbb88"
},
"hex": {
"name": "promicro_nrf52840_bootloader-v0.11.0-OTAFIX2.4.10_s140_6.1.1.hex",
"url": "https://github.com/mikecarper/Adafruit_nRF52_Bootloader_OTAFIX/releases/download/v0.11.0-OTAFIX2.4.10/promicro_nrf52840_bootloader-v0.11.0-OTAFIX2.4.10_s140_6.1.1.hex",
"size": 530382,
"sha256": "650ac8431867a21b31087ace9ef585e5d946952775dca63d256093a7804e4a0e"
"name": "promicro_nrf52840_bootloader-v0.11.0-OTAFIX2.4.11_s140_6.1.1.hex",
"url": "https://github.com/mikecarper/Adafruit_nRF52_Bootloader_OTAFIX/releases/download/v0.11.0-OTAFIX2.4.11/promicro_nrf52840_bootloader-v0.11.0-OTAFIX2.4.11_s140_6.1.1.hex",
"size": 530426,
"sha256": "bcfda096684f5da9f00c36a0baef81a0b4655424f594cc29953c27ca9051263f"
}
}
},
@@ -397,22 +368,22 @@
"storage": "qspi",
"files": {
"uf2": {
"name": "update-sensecap_solar_p1_bootloader-v0.11.0-OTAFIX2.4.10_mbr.uf2",
"url": "https://github.com/mikecarper/Adafruit_nRF52_Bootloader_OTAFIX/releases/download/v0.11.0-OTAFIX2.4.10/update-sensecap_solar_p1_bootloader-v0.11.0-OTAFIX2.4.10_mbr.uf2",
"name": "update-sensecap_solar_p1_bootloader-v0.11.0-OTAFIX2.4.11_mbr.uf2",
"url": "https://github.com/mikecarper/Adafruit_nRF52_Bootloader_OTAFIX/releases/download/v0.11.0-OTAFIX2.4.11/update-sensecap_solar_p1_bootloader-v0.11.0-OTAFIX2.4.11_mbr.uf2",
"size": 86016,
"sha256": "35fea3ca9a5f440a99279861b17174f54cf14ab406c0b2eb6bbddc76af6f65e2"
"sha256": "ca98f27f0289189d0b9b600c5a5a82acad14a56ed4ee4922c6c17fe1d669aadf"
},
"zip": {
"name": "sensecap_solar_p1_bootloader-v0.11.0-OTAFIX2.4.10_s140_7.3.0.zip",
"url": "https://github.com/mikecarper/Adafruit_nRF52_Bootloader_OTAFIX/releases/download/v0.11.0-OTAFIX2.4.10/sensecap_solar_p1_bootloader-v0.11.0-OTAFIX2.4.10_s140_7.3.0.zip",
"name": "sensecap_solar_p1_bootloader-v0.11.0-OTAFIX2.4.11_s140_7.3.0.zip",
"url": "https://github.com/mikecarper/Adafruit_nRF52_Bootloader_OTAFIX/releases/download/v0.11.0-OTAFIX2.4.11/sensecap_solar_p1_bootloader-v0.11.0-OTAFIX2.4.11_s140_7.3.0.zip",
"size": 194546,
"sha256": "60f74057e9ccd33cb90d59027d6e10231ea1b1c2f9244b1f319d10bb8c4a4a81"
"sha256": "2ed90c5624a3cdb9852fb22da42f02c2d55d93e74e19978068570c1edce90ee8"
},
"hex": {
"name": "sensecap_solar_p1_bootloader-v0.11.0-OTAFIX2.4.10_s140_7.3.0.hex",
"url": "https://github.com/mikecarper/Adafruit_nRF52_Bootloader_OTAFIX/releases/download/v0.11.0-OTAFIX2.4.10/sensecap_solar_p1_bootloader-v0.11.0-OTAFIX2.4.10_s140_7.3.0.hex",
"size": 537336,
"sha256": "f8907bcfc2658e59d580dd1618d11d1f27b1f1887ba8617504d7d0d25347ba0f"
"name": "sensecap_solar_p1_bootloader-v0.11.0-OTAFIX2.4.11_s140_7.3.0.hex",
"url": "https://github.com/mikecarper/Adafruit_nRF52_Bootloader_OTAFIX/releases/download/v0.11.0-OTAFIX2.4.11/sensecap_solar_p1_bootloader-v0.11.0-OTAFIX2.4.11_s140_7.3.0.hex",
"size": 537380,
"sha256": "6a0ada53219f67dd1a99da32d1257acff5fa617db26a9c3931dfa69e64bfca96"
}
}
},
@@ -426,22 +397,22 @@
"storage": "internal",
"files": {
"uf2": {
"name": "update-t1000_e_bootloader-v0.11.0-OTAFIX2.4.10_mbr.uf2",
"url": "https://github.com/mikecarper/Adafruit_nRF52_Bootloader_OTAFIX/releases/download/v0.11.0-OTAFIX2.4.10/update-t1000_e_bootloader-v0.11.0-OTAFIX2.4.10_mbr.uf2",
"name": "update-t1000_e_bootloader-v0.11.0-OTAFIX2.4.11_mbr.uf2",
"url": "https://github.com/mikecarper/Adafruit_nRF52_Bootloader_OTAFIX/releases/download/v0.11.0-OTAFIX2.4.11/update-t1000_e_bootloader-v0.11.0-OTAFIX2.4.11_mbr.uf2",
"size": 84480,
"sha256": "ea2fc2e1982a2e9365e42fea8471b2770f805d9d1fb37f4e2e49e43c3a83dce3"
"sha256": "56a66098af73f8ef71d7709fb2e4803f0822b146310882cfccd9cd67dc7e6f8a"
},
"zip": {
"name": "t1000_e_bootloader-v0.11.0-OTAFIX2.4.10_s140_7.3.0.zip",
"url": "https://github.com/mikecarper/Adafruit_nRF52_Bootloader_OTAFIX/releases/download/v0.11.0-OTAFIX2.4.10/t1000_e_bootloader-v0.11.0-OTAFIX2.4.10_s140_7.3.0.zip",
"name": "t1000_e_bootloader-v0.11.0-OTAFIX2.4.11_s140_7.3.0.zip",
"url": "https://github.com/mikecarper/Adafruit_nRF52_Bootloader_OTAFIX/releases/download/v0.11.0-OTAFIX2.4.11/t1000_e_bootloader-v0.11.0-OTAFIX2.4.11_s140_7.3.0.zip",
"size": 194546,
"sha256": "b1743ea1e54a7c69fc04fe6dd1467b221bd82a3658fd3170a8dc9d6ffb0a7322"
"sha256": "25e4f5af918ee1db913eb66f867f601578267ca5ebd8d4ebe7f6a43e193992e0"
},
"hex": {
"name": "t1000_e_bootloader-v0.11.0-OTAFIX2.4.10_s140_7.3.0.hex",
"url": "https://github.com/mikecarper/Adafruit_nRF52_Bootloader_OTAFIX/releases/download/v0.11.0-OTAFIX2.4.10/t1000_e_bootloader-v0.11.0-OTAFIX2.4.10_s140_7.3.0.hex",
"size": 534892,
"sha256": "863c2bdf6c01a709cb64808a51b1ff6710c35f40f10b86280ce07b763410a709"
"name": "t1000_e_bootloader-v0.11.0-OTAFIX2.4.11_s140_7.3.0.hex",
"url": "https://github.com/mikecarper/Adafruit_nRF52_Bootloader_OTAFIX/releases/download/v0.11.0-OTAFIX2.4.11/t1000_e_bootloader-v0.11.0-OTAFIX2.4.11_s140_7.3.0.hex",
"size": 534936,
"sha256": "9d75645dcf05a3f026ae2e798294dfbdb256288853fa18a8fc3d81a36df2079f"
}
}
},
@@ -455,22 +426,22 @@
"storage": "qspi",
"files": {
"uf2": {
"name": "update-thinknode_m1_bootloader-v0.11.0-OTAFIX2.4.10_mbr.uf2",
"url": "https://github.com/mikecarper/Adafruit_nRF52_Bootloader_OTAFIX/releases/download/v0.11.0-OTAFIX2.4.10/update-thinknode_m1_bootloader-v0.11.0-OTAFIX2.4.10_mbr.uf2",
"name": "update-thinknode_m1_bootloader-v0.11.0-OTAFIX2.4.11_mbr.uf2",
"url": "https://github.com/mikecarper/Adafruit_nRF52_Bootloader_OTAFIX/releases/download/v0.11.0-OTAFIX2.4.11/update-thinknode_m1_bootloader-v0.11.0-OTAFIX2.4.11_mbr.uf2",
"size": 86016,
"sha256": "42e64392dacda730d83335335bb143af64fdb53abef7228c0fedbb4a12850a61"
"sha256": "e9285e60f5789380f5aa7c61773e3d4d5dc61054688ec4e83830d3ea118f89d3"
},
"zip": {
"name": "thinknode_m1_bootloader-v0.11.0-OTAFIX2.4.10_s140_6.1.1.zip",
"url": "https://github.com/mikecarper/Adafruit_nRF52_Bootloader_OTAFIX/releases/download/v0.11.0-OTAFIX2.4.10/thinknode_m1_bootloader-v0.11.0-OTAFIX2.4.10_s140_6.1.1.zip",
"name": "thinknode_m1_bootloader-v0.11.0-OTAFIX2.4.11_s140_6.1.1.zip",
"url": "https://github.com/mikecarper/Adafruit_nRF52_Bootloader_OTAFIX/releases/download/v0.11.0-OTAFIX2.4.11/thinknode_m1_bootloader-v0.11.0-OTAFIX2.4.11_s140_6.1.1.zip",
"size": 192833,
"sha256": "0bf8e7acecf1019b90562c69e9a6e68ba4787652e3f3dd6060441f8a64c36c6c"
"sha256": "3cac3e8f3b73a4e46e99fd11502954dd82e416274f413019d79265d8bca4e4b0"
},
"hex": {
"name": "thinknode_m1_bootloader-v0.11.0-OTAFIX2.4.10_s140_6.1.1.hex",
"url": "https://github.com/mikecarper/Adafruit_nRF52_Bootloader_OTAFIX/releases/download/v0.11.0-OTAFIX2.4.10/thinknode_m1_bootloader-v0.11.0-OTAFIX2.4.10_s140_6.1.1.hex",
"size": 532614,
"sha256": "74007bc4c78bc5a32246af1ace7083fc01c2a43dabd27906e8d8d3fd3fbe5226"
"name": "thinknode_m1_bootloader-v0.11.0-OTAFIX2.4.11_s140_6.1.1.hex",
"url": "https://github.com/mikecarper/Adafruit_nRF52_Bootloader_OTAFIX/releases/download/v0.11.0-OTAFIX2.4.11/thinknode_m1_bootloader-v0.11.0-OTAFIX2.4.11_s140_6.1.1.hex",
"size": 532658,
"sha256": "2c42f4334ac94e05cce770fe9570b9442f5d37c54fb3c42d3e9f3caf2d5f36d1"
}
}
},
@@ -484,22 +455,22 @@
"storage": "internal",
"files": {
"uf2": {
"name": "update-thinknode_m3_bootloader-v0.11.0-OTAFIX2.4.10_mbr.uf2",
"url": "https://github.com/mikecarper/Adafruit_nRF52_Bootloader_OTAFIX/releases/download/v0.11.0-OTAFIX2.4.10/update-thinknode_m3_bootloader-v0.11.0-OTAFIX2.4.10_mbr.uf2",
"name": "update-thinknode_m3_bootloader-v0.11.0-OTAFIX2.4.11_mbr.uf2",
"url": "https://github.com/mikecarper/Adafruit_nRF52_Bootloader_OTAFIX/releases/download/v0.11.0-OTAFIX2.4.11/update-thinknode_m3_bootloader-v0.11.0-OTAFIX2.4.11_mbr.uf2",
"size": 84480,
"sha256": "c38ead189e8baf624ff9fddf31d2f16cd24f056a8ad2d66247e59828cefa1e8a"
"sha256": "babdc56164774911c53c4c0384ff12a110b07a5afc4998ee2ada021808a15a28"
},
"zip": {
"name": "thinknode_m3_bootloader-v0.11.0-OTAFIX2.4.10_s140_6.1.1.zip",
"url": "https://github.com/mikecarper/Adafruit_nRF52_Bootloader_OTAFIX/releases/download/v0.11.0-OTAFIX2.4.10/thinknode_m3_bootloader-v0.11.0-OTAFIX2.4.10_s140_6.1.1.zip",
"size": 192833,
"sha256": "3a70cba6350a97d74d537799325a40612c17cc9901a2835ef80e2ccfb534bcfe"
"name": "thinknode_m3_bootloader-v0.11.0-OTAFIX2.4.11_s140_6.1.1.zip",
"url": "https://github.com/mikecarper/Adafruit_nRF52_Bootloader_OTAFIX/releases/download/v0.11.0-OTAFIX2.4.11/thinknode_m3_bootloader-v0.11.0-OTAFIX2.4.11_s140_6.1.1.zip",
"size": 192834,
"sha256": "d5997d7c3cd0917b5fc0302a50eb0543897b5009371e5400c728901002d8ef04"
},
"hex": {
"name": "thinknode_m3_bootloader-v0.11.0-OTAFIX2.4.10_s140_6.1.1.hex",
"url": "https://github.com/mikecarper/Adafruit_nRF52_Bootloader_OTAFIX/releases/download/v0.11.0-OTAFIX2.4.10/thinknode_m3_bootloader-v0.11.0-OTAFIX2.4.10_s140_6.1.1.hex",
"size": 530096,
"sha256": "77c76e4a61a06a547b0d49b40e29ea56263bb45d10091fc70d9c9472af48e3ad"
"name": "thinknode_m3_bootloader-v0.11.0-OTAFIX2.4.11_s140_6.1.1.hex",
"url": "https://github.com/mikecarper/Adafruit_nRF52_Bootloader_OTAFIX/releases/download/v0.11.0-OTAFIX2.4.11/thinknode_m3_bootloader-v0.11.0-OTAFIX2.4.11_s140_6.1.1.hex",
"size": 530140,
"sha256": "25fe01c8b9e2b73d9b8723e538dbeda48e136142884bb1769a527b4c183359db"
}
}
},
@@ -513,22 +484,22 @@
"storage": "qspi",
"files": {
"uf2": {
"name": "update-thinknode_m6_bootloader-v0.11.0-OTAFIX2.4.10_mbr.uf2",
"url": "https://github.com/mikecarper/Adafruit_nRF52_Bootloader_OTAFIX/releases/download/v0.11.0-OTAFIX2.4.10/update-thinknode_m6_bootloader-v0.11.0-OTAFIX2.4.10_mbr.uf2",
"name": "update-thinknode_m6_bootloader-v0.11.0-OTAFIX2.4.11_mbr.uf2",
"url": "https://github.com/mikecarper/Adafruit_nRF52_Bootloader_OTAFIX/releases/download/v0.11.0-OTAFIX2.4.11/update-thinknode_m6_bootloader-v0.11.0-OTAFIX2.4.11_mbr.uf2",
"size": 86016,
"sha256": "a68f9dec813758bfcdb6584aed61919de77e6de4ed7ff7bb054afe5579d70891"
"sha256": "c177e30bebd37a5a495780ebe472eaa49e50294f96cfb836568bce03dfc69d32"
},
"zip": {
"name": "thinknode_m6_bootloader-v0.11.0-OTAFIX2.4.10_s140_6.1.1.zip",
"url": "https://github.com/mikecarper/Adafruit_nRF52_Bootloader_OTAFIX/releases/download/v0.11.0-OTAFIX2.4.10/thinknode_m6_bootloader-v0.11.0-OTAFIX2.4.10_s140_6.1.1.zip",
"name": "thinknode_m6_bootloader-v0.11.0-OTAFIX2.4.11_s140_6.1.1.zip",
"url": "https://github.com/mikecarper/Adafruit_nRF52_Bootloader_OTAFIX/releases/download/v0.11.0-OTAFIX2.4.11/thinknode_m6_bootloader-v0.11.0-OTAFIX2.4.11_s140_6.1.1.zip",
"size": 192834,
"sha256": "a306f36d968b7cf92d3f849135049fb9dfbcd8bb650e5fe79e59b51c90e431b4"
"sha256": "4615d333486d6638fe7b10f0ed82b2bb4ba25424bf11b1662a35f117d6f4060a"
},
"hex": {
"name": "thinknode_m6_bootloader-v0.11.0-OTAFIX2.4.10_s140_6.1.1.hex",
"url": "https://github.com/mikecarper/Adafruit_nRF52_Bootloader_OTAFIX/releases/download/v0.11.0-OTAFIX2.4.10/thinknode_m6_bootloader-v0.11.0-OTAFIX2.4.10_s140_6.1.1.hex",
"size": 532702,
"sha256": "a9d52a8215645d8431d9665a5abb419dc895157c3afc65564bc2aeaa22cb21f1"
"name": "thinknode_m6_bootloader-v0.11.0-OTAFIX2.4.11_s140_6.1.1.hex",
"url": "https://github.com/mikecarper/Adafruit_nRF52_Bootloader_OTAFIX/releases/download/v0.11.0-OTAFIX2.4.11/thinknode_m6_bootloader-v0.11.0-OTAFIX2.4.11_s140_6.1.1.hex",
"size": 532746,
"sha256": "5a6c937a222d2434a59b63eb7369a392f018404dbfc71913a27c48c64042fbec"
}
}
},
@@ -544,22 +515,22 @@
"storage": "qspi",
"files": {
"uf2": {
"name": "update-wio_tracker_l1_bootloader-v0.11.0-OTAFIX2.4.10_mbr.uf2",
"url": "https://github.com/mikecarper/Adafruit_nRF52_Bootloader_OTAFIX/releases/download/v0.11.0-OTAFIX2.4.10/update-wio_tracker_l1_bootloader-v0.11.0-OTAFIX2.4.10_mbr.uf2",
"name": "update-wio_tracker_l1_bootloader-v0.11.0-OTAFIX2.4.11_mbr.uf2",
"url": "https://github.com/mikecarper/Adafruit_nRF52_Bootloader_OTAFIX/releases/download/v0.11.0-OTAFIX2.4.11/update-wio_tracker_l1_bootloader-v0.11.0-OTAFIX2.4.11_mbr.uf2",
"size": 86016,
"sha256": "84bc013be4242f77369ac9e87e8465716a9fc535f29b33a7b9e5fc0e25a62793"
"sha256": "94270e321b602086b9b00a3a80d5f9cec07d16bd095efc252f2d6ddf4086adb9"
},
"zip": {
"name": "wio_tracker_l1_bootloader-v0.11.0-OTAFIX2.4.10_s140_7.3.0.zip",
"url": "https://github.com/mikecarper/Adafruit_nRF52_Bootloader_OTAFIX/releases/download/v0.11.0-OTAFIX2.4.10/wio_tracker_l1_bootloader-v0.11.0-OTAFIX2.4.10_s140_7.3.0.zip",
"name": "wio_tracker_l1_bootloader-v0.11.0-OTAFIX2.4.11_s140_7.3.0.zip",
"url": "https://github.com/mikecarper/Adafruit_nRF52_Bootloader_OTAFIX/releases/download/v0.11.0-OTAFIX2.4.11/wio_tracker_l1_bootloader-v0.11.0-OTAFIX2.4.11_s140_7.3.0.zip",
"size": 194545,
"sha256": "b40783b87f87de5b206c98fe970f0a94f8d55ba728c888333a323a6641aab63d"
"sha256": "1d6436b2c4c434f5f5a0cb8646c97a3087afaf43cc79403da78ed85b36ea0313"
},
"hex": {
"name": "wio_tracker_l1_bootloader-v0.11.0-OTAFIX2.4.10_s140_7.3.0.hex",
"url": "https://github.com/mikecarper/Adafruit_nRF52_Bootloader_OTAFIX/releases/download/v0.11.0-OTAFIX2.4.10/wio_tracker_l1_bootloader-v0.11.0-OTAFIX2.4.10_s140_7.3.0.hex",
"size": 537246,
"sha256": "309a3f79d43bd638a31ea5bb54b8d4a9b3fb402e5c06ea017a02d69154cab39c"
"name": "wio_tracker_l1_bootloader-v0.11.0-OTAFIX2.4.11_s140_7.3.0.hex",
"url": "https://github.com/mikecarper/Adafruit_nRF52_Bootloader_OTAFIX/releases/download/v0.11.0-OTAFIX2.4.11/wio_tracker_l1_bootloader-v0.11.0-OTAFIX2.4.11_s140_7.3.0.hex",
"size": 537290,
"sha256": "4513b435762d69db75fc233a099d21a02efe6481d75f38dd37a64aa5da5116a6"
}
}
},
@@ -573,22 +544,22 @@
"storage": "adaptive",
"files": {
"uf2": {
"name": "update-wiscore_rak3401_auto_bootloader-v0.11.0-OTAFIX2.4.10_mbr.uf2",
"url": "https://github.com/mikecarper/Adafruit_nRF52_Bootloader_OTAFIX/releases/download/v0.11.0-OTAFIX2.4.10/update-wiscore_rak3401_auto_bootloader-v0.11.0-OTAFIX2.4.10_mbr.uf2",
"name": "update-wiscore_rak3401_auto_bootloader-v0.11.0-OTAFIX2.4.11_mbr.uf2",
"url": "https://github.com/mikecarper/Adafruit_nRF52_Bootloader_OTAFIX/releases/download/v0.11.0-OTAFIX2.4.11/update-wiscore_rak3401_auto_bootloader-v0.11.0-OTAFIX2.4.11_mbr.uf2",
"size": 88064,
"sha256": "196c4720a4a4c04dcbce984b4fe9c995cdfac2551af9e5a2bb01b88a287a1bcc"
"sha256": "e26087a540b235b5968d7e5fee7e9c8f6dcff8480ad7ea922792e88307335b67"
},
"zip": {
"name": "wiscore_rak3401_auto_bootloader-v0.11.0-OTAFIX2.4.10_s140_6.1.1.zip",
"url": "https://github.com/mikecarper/Adafruit_nRF52_Bootloader_OTAFIX/releases/download/v0.11.0-OTAFIX2.4.10/wiscore_rak3401_auto_bootloader-v0.11.0-OTAFIX2.4.10_s140_6.1.1.zip",
"size": 192833,
"sha256": "6fb37f8212d84263c1cdd8869cb844d8fbc5cbc470c864bf34221790ffc1b164"
"name": "wiscore_rak3401_auto_bootloader-v0.11.0-OTAFIX2.4.11_s140_6.1.1.zip",
"url": "https://github.com/mikecarper/Adafruit_nRF52_Bootloader_OTAFIX/releases/download/v0.11.0-OTAFIX2.4.11/wiscore_rak3401_auto_bootloader-v0.11.0-OTAFIX2.4.11_s140_6.1.1.zip",
"size": 192834,
"sha256": "4971a54970011c2df80ece58d917817de0d157e3fcb1a8490d1fa1f65e279db7"
},
"hex": {
"name": "wiscore_rak3401_auto_bootloader-v0.11.0-OTAFIX2.4.10_s140_6.1.1.hex",
"url": "https://github.com/mikecarper/Adafruit_nRF52_Bootloader_OTAFIX/releases/download/v0.11.0-OTAFIX2.4.10/wiscore_rak3401_auto_bootloader-v0.11.0-OTAFIX2.4.10_s140_6.1.1.hex",
"size": 535390,
"sha256": "f875900258e584dd9c577c05a21a2d65e1a511aa5e1b8e5680ad99d98fa910d7"
"name": "wiscore_rak3401_auto_bootloader-v0.11.0-OTAFIX2.4.11_s140_6.1.1.hex",
"url": "https://github.com/mikecarper/Adafruit_nRF52_Bootloader_OTAFIX/releases/download/v0.11.0-OTAFIX2.4.11/wiscore_rak3401_auto_bootloader-v0.11.0-OTAFIX2.4.11_s140_6.1.1.hex",
"size": 535384,
"sha256": "2d8a773b4f94a9e2e6fc13923101ca6e66384330489014cb5e4c3968dc941a21"
}
}
},
@@ -603,22 +574,22 @@
"storage": "adaptive",
"files": {
"uf2": {
"name": "update-wiscore_rak4631_auto_bootloader-v0.11.0-OTAFIX2.4.10_mbr.uf2",
"url": "https://github.com/mikecarper/Adafruit_nRF52_Bootloader_OTAFIX/releases/download/v0.11.0-OTAFIX2.4.10/update-wiscore_rak4631_auto_bootloader-v0.11.0-OTAFIX2.4.10_mbr.uf2",
"name": "update-wiscore_rak4631_auto_bootloader-v0.11.0-OTAFIX2.4.11_mbr.uf2",
"url": "https://github.com/mikecarper/Adafruit_nRF52_Bootloader_OTAFIX/releases/download/v0.11.0-OTAFIX2.4.11/update-wiscore_rak4631_auto_bootloader-v0.11.0-OTAFIX2.4.11_mbr.uf2",
"size": 88064,
"sha256": "a628260984daaa7d29413bb59d23eccd104ad685e8fa6401d481dfac9fa2b00d"
"sha256": "40bb22fcfb90aae335a8bb6bb6e876f3407d87407ead8ee3908bb0ba0b2541a7"
},
"zip": {
"name": "wiscore_rak4631_auto_bootloader-v0.11.0-OTAFIX2.4.10_s140_6.1.1.zip",
"url": "https://github.com/mikecarper/Adafruit_nRF52_Bootloader_OTAFIX/releases/download/v0.11.0-OTAFIX2.4.10/wiscore_rak4631_auto_bootloader-v0.11.0-OTAFIX2.4.10_s140_6.1.1.zip",
"size": 192834,
"sha256": "303d5d98d1e585798d11c6f37d74a8b33e9c99dd71a41b69d90b6efcd0d22565"
"name": "wiscore_rak4631_auto_bootloader-v0.11.0-OTAFIX2.4.11_s140_6.1.1.zip",
"url": "https://github.com/mikecarper/Adafruit_nRF52_Bootloader_OTAFIX/releases/download/v0.11.0-OTAFIX2.4.11/wiscore_rak4631_auto_bootloader-v0.11.0-OTAFIX2.4.11_s140_6.1.1.zip",
"size": 192833,
"sha256": "d3c12363a620083f18e828e724e0af67a2b141b21dc4768d0707cfaadc32b98d"
},
"hex": {
"name": "wiscore_rak4631_auto_bootloader-v0.11.0-OTAFIX2.4.10_s140_6.1.1.hex",
"url": "https://github.com/mikecarper/Adafruit_nRF52_Bootloader_OTAFIX/releases/download/v0.11.0-OTAFIX2.4.10/wiscore_rak4631_auto_bootloader-v0.11.0-OTAFIX2.4.10_s140_6.1.1.hex",
"size": 535786,
"sha256": "1d4bc0f07065976b6ddd01bf2eb27bb9804dac69cdaf418afc7445cf9507f33b"
"name": "wiscore_rak4631_auto_bootloader-v0.11.0-OTAFIX2.4.11_s140_6.1.1.hex",
"url": "https://github.com/mikecarper/Adafruit_nRF52_Bootloader_OTAFIX/releases/download/v0.11.0-OTAFIX2.4.11/wiscore_rak4631_auto_bootloader-v0.11.0-OTAFIX2.4.11_s140_6.1.1.hex",
"size": 535780,
"sha256": "963cb6e47179551ce975b54502fb8dbe62a9601ba7432cbf8b23cf4c86f2a6ef"
}
}
},
@@ -632,22 +603,22 @@
"storage": "internal",
"files": {
"uf2": {
"name": "update-wismesh_tag_bootloader-v0.11.0-OTAFIX2.4.10_mbr.uf2",
"url": "https://github.com/mikecarper/Adafruit_nRF52_Bootloader_OTAFIX/releases/download/v0.11.0-OTAFIX2.4.10/update-wismesh_tag_bootloader-v0.11.0-OTAFIX2.4.10_mbr.uf2",
"name": "update-wismesh_tag_bootloader-v0.11.0-OTAFIX2.4.11_mbr.uf2",
"url": "https://github.com/mikecarper/Adafruit_nRF52_Bootloader_OTAFIX/releases/download/v0.11.0-OTAFIX2.4.11/update-wismesh_tag_bootloader-v0.11.0-OTAFIX2.4.11_mbr.uf2",
"size": 84992,
"sha256": "71116deddf4aa41d96408fbc4f3702a07fcabd913a6c0c61c624f26145185541"
"sha256": "0d45f1d73fdcea41bf647347096b33919ae69ce957a06d6ff0e48be8767f1081"
},
"zip": {
"name": "wismesh_tag_bootloader-v0.11.0-OTAFIX2.4.10_s140_6.1.1.zip",
"url": "https://github.com/mikecarper/Adafruit_nRF52_Bootloader_OTAFIX/releases/download/v0.11.0-OTAFIX2.4.10/wismesh_tag_bootloader-v0.11.0-OTAFIX2.4.10_s140_6.1.1.zip",
"name": "wismesh_tag_bootloader-v0.11.0-OTAFIX2.4.11_s140_6.1.1.zip",
"url": "https://github.com/mikecarper/Adafruit_nRF52_Bootloader_OTAFIX/releases/download/v0.11.0-OTAFIX2.4.11/wismesh_tag_bootloader-v0.11.0-OTAFIX2.4.11_s140_6.1.1.zip",
"size": 192834,
"sha256": "23088b4263d1ea1e2e594094ee2345c26b3d37e89a302862bbb4b1848baa99c4"
"sha256": "716520cce6e46b7b2d84272a8a5a29d1c38344dfef7455d974969b700527fda3"
},
"hex": {
"name": "wismesh_tag_bootloader-v0.11.0-OTAFIX2.4.10_s140_6.1.1.hex",
"url": "https://github.com/mikecarper/Adafruit_nRF52_Bootloader_OTAFIX/releases/download/v0.11.0-OTAFIX2.4.10/wismesh_tag_bootloader-v0.11.0-OTAFIX2.4.10_s140_6.1.1.hex",
"size": 530818,
"sha256": "e59dbc29d7ab3aab00f0838195f521b6f48339e12e4f64c78c44d6ee9d6a0340"
"name": "wismesh_tag_bootloader-v0.11.0-OTAFIX2.4.11_s140_6.1.1.hex",
"url": "https://github.com/mikecarper/Adafruit_nRF52_Bootloader_OTAFIX/releases/download/v0.11.0-OTAFIX2.4.11/wismesh_tag_bootloader-v0.11.0-OTAFIX2.4.11_s140_6.1.1.hex",
"size": 530862,
"sha256": "d7503e65e4f37d028c58650bd31236ca493cff5dae1700e00baa8bbe2dbdca2a"
}
}
},
@@ -671,22 +642,22 @@
"storage": "qspi",
"files": {
"uf2": {
"name": "update-xiao_nrf52840_ble_bootloader-v0.11.0-OTAFIX2.4.10_mbr.uf2",
"url": "https://github.com/mikecarper/Adafruit_nRF52_Bootloader_OTAFIX/releases/download/v0.11.0-OTAFIX2.4.10/update-xiao_nrf52840_ble_bootloader-v0.11.0-OTAFIX2.4.10_mbr.uf2",
"name": "update-xiao_nrf52840_ble_bootloader-v0.11.0-OTAFIX2.4.11_mbr.uf2",
"url": "https://github.com/mikecarper/Adafruit_nRF52_Bootloader_OTAFIX/releases/download/v0.11.0-OTAFIX2.4.11/update-xiao_nrf52840_ble_bootloader-v0.11.0-OTAFIX2.4.11_mbr.uf2",
"size": 86016,
"sha256": "e78ac1a5f71ef5c3625c9742fd88ac2b1d807301c0adcca3db3b6afbafc92cb8"
"sha256": "1c6f18a21e0a6992f0ec2cb8f48e79ada5ccd7111b5b53f73b679c4dad10a9f5"
},
"zip": {
"name": "xiao_nrf52840_ble_bootloader-v0.11.0-OTAFIX2.4.10_s140_7.3.0.zip",
"url": "https://github.com/mikecarper/Adafruit_nRF52_Bootloader_OTAFIX/releases/download/v0.11.0-OTAFIX2.4.10/xiao_nrf52840_ble_bootloader-v0.11.0-OTAFIX2.4.10_s140_7.3.0.zip",
"name": "xiao_nrf52840_ble_bootloader-v0.11.0-OTAFIX2.4.11_s140_7.3.0.zip",
"url": "https://github.com/mikecarper/Adafruit_nRF52_Bootloader_OTAFIX/releases/download/v0.11.0-OTAFIX2.4.11/xiao_nrf52840_ble_bootloader-v0.11.0-OTAFIX2.4.11_s140_7.3.0.zip",
"size": 194546,
"sha256": "1cc905af2ac0ed474df0ccde310faec7a10adc4c1b78c84c255de1bcee05bb78"
"sha256": "f0bb4832a96a625d18edb272fdc6ae2bc82ca291731f3edcd178c1c22bbe322d"
},
"hex": {
"name": "xiao_nrf52840_ble_bootloader-v0.11.0-OTAFIX2.4.10_s140_7.3.0.hex",
"url": "https://github.com/mikecarper/Adafruit_nRF52_Bootloader_OTAFIX/releases/download/v0.11.0-OTAFIX2.4.10/xiao_nrf52840_ble_bootloader-v0.11.0-OTAFIX2.4.10_s140_7.3.0.hex",
"size": 537284,
"sha256": "39a857cd4a2cdd5adfc10d805a9a5b2aea5c30ec474a2b1ddeb8408e3dfb1ad0"
"name": "xiao_nrf52840_ble_bootloader-v0.11.0-OTAFIX2.4.11_s140_7.3.0.hex",
"url": "https://github.com/mikecarper/Adafruit_nRF52_Bootloader_OTAFIX/releases/download/v0.11.0-OTAFIX2.4.11/xiao_nrf52840_ble_bootloader-v0.11.0-OTAFIX2.4.11_s140_7.3.0.hex",
"size": 537328,
"sha256": "dbef733404fe15e06e0d6ffa006f34094a53a19fc62314cc28764afe3deb5154"
}
}
},
@@ -700,27 +671,35 @@
"storage": "qspi",
"files": {
"uf2": {
"name": "update-xiao_nrf52840_ble_sense_bootloader-v0.11.0-OTAFIX2.4.10_mbr.uf2",
"url": "https://github.com/mikecarper/Adafruit_nRF52_Bootloader_OTAFIX/releases/download/v0.11.0-OTAFIX2.4.10/update-xiao_nrf52840_ble_sense_bootloader-v0.11.0-OTAFIX2.4.10_mbr.uf2",
"name": "update-xiao_nrf52840_ble_sense_bootloader-v0.11.0-OTAFIX2.4.11_mbr.uf2",
"url": "https://github.com/mikecarper/Adafruit_nRF52_Bootloader_OTAFIX/releases/download/v0.11.0-OTAFIX2.4.11/update-xiao_nrf52840_ble_sense_bootloader-v0.11.0-OTAFIX2.4.11_mbr.uf2",
"size": 86016,
"sha256": "78fffe89ca5d9591f5851cbf610ea246789ec3ccf736bd8d799774bc4a82f4d3"
"sha256": "3a412b6eb12199b68cc3651082f0fb9f16ea1398c75537138d96068776e7084a"
},
"zip": {
"name": "xiao_nrf52840_ble_sense_bootloader-v0.11.0-OTAFIX2.4.10_s140_7.3.0.zip",
"url": "https://github.com/mikecarper/Adafruit_nRF52_Bootloader_OTAFIX/releases/download/v0.11.0-OTAFIX2.4.10/xiao_nrf52840_ble_sense_bootloader-v0.11.0-OTAFIX2.4.10_s140_7.3.0.zip",
"name": "xiao_nrf52840_ble_sense_bootloader-v0.11.0-OTAFIX2.4.11_s140_7.3.0.zip",
"url": "https://github.com/mikecarper/Adafruit_nRF52_Bootloader_OTAFIX/releases/download/v0.11.0-OTAFIX2.4.11/xiao_nrf52840_ble_sense_bootloader-v0.11.0-OTAFIX2.4.11_s140_7.3.0.zip",
"size": 194546,
"sha256": "d878c8b815bd8519e840b3221219c4bef8af8c964b039f89c79d71a6641e759b"
"sha256": "954a5ee6b0d008e6918d91f0de5af7c80cf170c42ea9f8dc0957e9698a90f5c5"
},
"hex": {
"name": "xiao_nrf52840_ble_sense_bootloader-v0.11.0-OTAFIX2.4.10_s140_7.3.0.hex",
"url": "https://github.com/mikecarper/Adafruit_nRF52_Bootloader_OTAFIX/releases/download/v0.11.0-OTAFIX2.4.10/xiao_nrf52840_ble_sense_bootloader-v0.11.0-OTAFIX2.4.10_s140_7.3.0.hex",
"size": 537318,
"sha256": "cf43bffaa2ba751b0bd773bd50d2e9c8f9c99741de950e6166ea416f1585f5fb"
"name": "xiao_nrf52840_ble_sense_bootloader-v0.11.0-OTAFIX2.4.11_s140_7.3.0.hex",
"url": "https://github.com/mikecarper/Adafruit_nRF52_Bootloader_OTAFIX/releases/download/v0.11.0-OTAFIX2.4.11/xiao_nrf52840_ble_sense_bootloader-v0.11.0-OTAFIX2.4.11_s140_7.3.0.hex",
"size": 537362,
"sha256": "07ccd6e07d9eebb6ce077302a393df2edb59c3106b93202a627f755c3980d53d"
}
}
}
],
"unavailableProfiles": [
{
"id": "heltec_mesh_tower_v2",
"label": "Heltec MeshTower V2 - historical internal-only profile",
"meshcoreHardware": [
"Heltec_tower_v2"
],
"reason": "Retired from normal releases in favor of the combined SD/internal profile. Existing internal-only bootloaders require a one-time local installation of the normal combined image via USB/BLE DFU or SWD; do not use a cross-profile LoRa update."
},
{
"id": "gat562_mesh_watch13",
"label": "GAT562 Mesh Watch13",
@@ -802,7 +781,7 @@
"meshcoreHardware": [
"ThinkNode_M8"
],
"reason": "ThinkNode M8 shares some flash wiring with M1 but has different buttons and peripheral controls. It needs an M8-specific profile."
"reason": "A dedicated M8 bootloader is awaiting factory USB identification and hardware testing. No verified download is available."
},
{
"id": "wio_wm1110",
@@ -810,7 +789,14 @@
"meshcoreHardware": [
"wio_wm1110"
],
"reason": "Wio WM1110 is a separate tracker board with different LED and peripheral pins. Its XIAO build setting does not make it XIAO hardware."
"reason": "This firmware targets the Wio WM1110 Dev Kit (SDK carrier), whose LEDs and controls differ from Wio Tracker 1110 and XIAO. Its bootloader identity and installation route still need qualification."
}
]
],
"profileSourceOverrides": {
"heltec_mesh_tower_v2_sdcard": {
"source_tag": "0.11.0-OTAFIX2.4.11",
"source_commit": "39177b9bbaa894352141734350d6ef2fab18dc4f",
"workflow_run": 37072081200
}
}
}
File diff suppressed because it is too large. Load diff
+169
View File
@@ -0,0 +1,169 @@
(function (global) {
"use strict";
function validateCatalog(catalog) {
if (!catalog || catalog.schema !== 1 || !Array.isArray(catalog.repositories) ||
!Array.isArray(catalog.releases) || !Array.isArray(catalog.builds) ||
!Array.isArray(catalog.unresolved) || !catalog.layouts ||
typeof catalog.layouts !== "object" || Array.isArray(catalog.layouts)) {
throw new Error("Unsupported partition catalog format");
}
return catalog;
}
function snapshot(catalog) {
return {
schema: catalog.schema,
generatedAt: catalog.generatedAt,
repositories: catalog.repositories,
counts: {
releases: catalog.releases.length,
builds: catalog.builds.length,
layouts: Object.keys(catalog.layouts).length,
unresolved: catalog.unresolved.length,
},
};
}
function jsonView(catalog, view) {
validateCatalog(catalog);
if (view === "all") return JSON.stringify(catalog, null, 2);
const result = snapshot(catalog);
if (view === "layouts") result.layouts = catalog.layouts;
else if (view === "releases") result.releases = catalog.releases;
else throw new Error("Unknown catalog view");
return JSON.stringify(result, null, 2);
}
function boardMinimums(catalog) {
validateCatalog(catalog);
const boards = new Map();
catalog.builds.forEach(function (build) {
const key = build.board.toLowerCase();
if (!boards.has(key)) {
boards.set(key, {
board: build.board, smallestBytes: null,
hasDualOta: false, hasNonDualOta: false, hasUnknownLayout: false,
});
}
const row = boards.get(key);
const layout = Object.prototype.hasOwnProperty.call(catalog.layouts, build.layout)
? catalog.layouts[build.layout] : null;
if (!layout || !Array.isArray(layout.partitions)) {
row.hasUnknownLayout = true;
return;
}
const apps = layout.partitions.filter(function (entry) {
return Array.isArray(entry) && entry[0] === 0 &&
Number.isSafeInteger(entry[3]) && entry[3] > 0;
});
if (!apps.length) {
row.hasUnknownLayout = true;
return;
}
const smallest = Math.min.apply(null, apps.map((entry) => entry[3]));
row.smallestBytes = row.smallestBytes === null
? smallest : Math.min(row.smallestBytes, smallest);
if (layout.dualOta === true) row.hasDualOta = true;
else if (layout.dualOta === false) row.hasNonDualOta = true;
else row.hasUnknownLayout = true;
});
return Array.from(boards.values()).sort(function (left, right) {
return left.board.localeCompare(right.board, "en", { sensitivity: "base" });
});
}
function formatSize(bytes) {
return bytes === null ? "Unknown" : (bytes / 1048576).toLocaleString(
"en-US", { maximumFractionDigits: 4 }
) + " MiB";
}
function otaLayouts(row) {
if (row.hasUnknownLayout) return "Unknown layouts present";
if (row.hasDualOta && row.hasNonDualOta) return "Mixed (some lack dual OTA)";
return row.hasDualOta ? "Dual-slot OTA" : "No dual-slot OTA";
}
async function initializeViewer() {
const root = document.getElementById("partition-catalog");
if (!root || root.dataset.initialized) return;
root.dataset.initialized = "true";
const selector = document.getElementById("partition-catalog-view");
const status = document.getElementById("partition-catalog-status");
const code = document.getElementById("partition-catalog-json");
const link = document.getElementById("partition-catalog-open");
const raw = document.getElementById("partition-catalog-raw");
const search = document.getElementById("partition-catalog-search");
const tbody = document.getElementById("partition-catalog-boards");
const count = document.getElementById("partition-catalog-count");
try {
const response = await global.fetch(link.href, { cache: "no-cache" });
if (!response.ok) throw new Error("HTTP " + response.status);
const catalog = validateCatalog(await response.json());
const render = function () {
// Catalog text is data, never HTML. Only format the large builds array
// when the reader explicitly requests the complete catalog.
code.textContent = jsonView(catalog, selector.value);
};
raw.addEventListener("toggle", function () { if (raw.open) render(); });
if (raw.open) render();
selector.disabled = false;
selector.addEventListener("change", render);
const rows = boardMinimums(catalog).map(function (board) {
const tr = document.createElement("tr");
const name = document.createElement("th");
name.scope = "row";
name.textContent = board.board;
const size = document.createElement("td");
size.textContent = formatSize(board.smallestBytes);
if (board.smallestBytes !== null) {
const bytes = document.createElement("small");
bytes.textContent = board.smallestBytes.toLocaleString("en-US") + " bytes";
size.appendChild(bytes);
}
const layout = document.createElement("td");
layout.textContent = otaLayouts(board);
tr.appendChild(name);
tr.appendChild(size);
tr.appendChild(layout);
tbody.appendChild(tr);
return { name: board.board.toLowerCase(), element: tr };
});
const filter = function () {
const query = search.value.trim().toLowerCase();
let visible = 0;
rows.forEach(function (row) {
row.element.hidden = !row.name.includes(query);
if (!row.element.hidden) visible++;
});
count.textContent = "Showing " + visible + " of " + rows.length + " boards.";
};
filter();
search.disabled = false;
search.addEventListener("input", filter);
document.getElementById("partition-catalog-table").hidden = false;
const counts = snapshot(catalog).counts;
status.textContent = "Snapshot " + catalog.generatedAt + ": " +
counts.builds.toLocaleString("en-US") + " builds, " +
counts.releases.toLocaleString("en-US") + " releases, " +
counts.layouts + " layouts, " + counts.unresolved + " unresolved assets.";
} catch (error) {
status.textContent = "Could not load the catalog: " + error.message +
". Use Open JSON or Download JSON to access the file directly.";
code.textContent = "Catalog preview unavailable.";
}
}
const api = Object.freeze({
validateCatalog, jsonView, boardMinimums, formatSize, otaLayouts, initializeViewer,
});
if (typeof module === "object" && module.exports) module.exports = api;
if (typeof document !== "undefined") {
if (document.readyState === "loading") {
document.addEventListener("DOMContentLoaded", initializeViewer, { once: true });
} else {
initializeViewer();
}
}
})(typeof globalThis !== "undefined" ? globalThis : this);
+39 -2
View File
@@ -210,6 +210,7 @@
hops: "all",
channel: "#rgdata",
incoming: "none",
hashBytes: 0,
pathKind: "none",
pathPrefix: "",
sender: "",
@@ -255,6 +256,9 @@
hops: normalizeHops(input.hops),
channel: normalizeChannel(input.channel, true),
incoming: normalizeIncoming(input.incoming),
hashBytes: ["", "0", "any", "*"].includes(clean(input.hashBytes).toLowerCase())
? 0 : clean(input.hashBytes) === "2+" ? "2+"
: requiredInteger(input.hashBytes, 1, 3, "Path hash bytes"),
pathKind: enumValue(input.pathKind, PATH_KINDS, "Path matcher", "none"),
pathPrefix: "",
sender: clean(input.sender),
@@ -276,6 +280,11 @@
if (rule.pathKind === "prefix") rule.pathPrefix = normalizePathPrefix(input.pathPrefix, 3);
if (rule.pathKind === "prefix" && !rule.pathPrefix) throw new FilterToolError("Prefix path matcher requires at least one pbyte ID.");
if (rule.pathKind === "prefix" && rule.hashBytes
&& (rule.hashBytes === "2+" ? rule.pathPrefix.split(",")[0].length / 2 < 2
: rule.pathPrefix.split(",")[0].length / 2 !== rule.hashBytes)) {
throw new FilterToolError("Path hash bytes conflicts with prefix width.");
}
if (rule.sender) {
rule.sender = requiredText(rule.sender, "Sender", 31);
if (rule.sender.includes(":")) throw new FilterToolError("Sender matcher cannot contain a colon.");
@@ -356,6 +365,7 @@
];
if (rule.channel) matches.push(`channel=${quoteDsl(rule.channel)}`);
if (rule.incoming !== "any") matches.push(`rx.scope=${quoteDsl(rule.incoming)}`);
if (rule.hashBytes) matches.push(`hashbytes=${rule.hashBytes}`);
if (rule.pathKind === "prefix") matches.push(`path=prefix:${rule.pathPrefix}`);
else if (rule.pathKind !== "none") matches.push(`path=${rule.pathKind}`);
if (rule.sender) matches.push(`sender=${quoteDsl(rule.sender)}`);
@@ -462,6 +472,7 @@
else if (name === "hops") rule.hops = value;
else if (name === "channel") rule.channel = value;
else if (name === "rx.scope") rule.incoming = value;
else if (name === "hashbytes") rule.hashBytes = value;
else if (name === "path") {
if (value.toLowerCase().startsWith("prefix:")) {
rule.pathKind = "prefix";
@@ -568,6 +579,9 @@
else if (rule.pathKind === "blacklist") conditions.push("a path matching the passive blacklist");
else if (rule.pathKind.startsWith("bucket:")) conditions.push(`a path matching ${rule.pathKind}`);
else if (rule.pathKind.startsWith("loop:")) conditions.push(`the ${rule.pathKind.slice(5)} own-ID loop threshold`);
if (rule.hashBytes) conditions.push(rule.hashBytes === "2+"
? "2- or 3-byte encoded path hashes, including zero hops"
: `${rule.hashBytes}-byte encoded path hashes, including zero hops`);
if (rule.sender) conditions.push(`decrypted sender "${rule.sender}"`);
conditions.push(`traffic path ${rule.via}`);
if (rule.tempRadio !== "any") conditions.push(`temporary radio ${rule.tempRadio}`);
@@ -596,7 +610,9 @@
if (rule.verdict === "drop" && rule.route === "flood" && rule.type === "any"
&& rule.hops === "all" && !rule.channel && rule.incoming === "any"
&& rule.pathKind === "none" && rule.tempRadio === "any") {
warnings.push("This is a global drop rule across both flood route types.");
warnings.push(rule.hashBytes
? `This drops all ${rule.hashBytes === "2+" ? "2- or 3" : rule.hashBytes}-byte path-hash packets across both flood route types.`
: "This is a global drop rule across both flood route types.");
}
if (limitsRemoteManagement(rule)) {
const zeroHop = minimumHops === 0 && rule.pathKind === "none";
@@ -622,6 +638,7 @@
bytes += 3 + 3 + 3;
if (rule.channel) bytes += 3 + (rule.channel.startsWith("hash:") ? 1 : rule.channel.length === 32 || rule.channel.length === 64 ? rule.channel.length / 2 : rule.channel.length);
if (rule.incoming !== "any") bytes += 3 + rule.incoming.length;
if (rule.hashBytes) bytes += 3;
if (rule.pathKind === "prefix") bytes += 4 + rule.pathPrefix.replace(/,/g, "").length / 2;
else if (rule.pathKind !== "none") bytes += 3;
if (rule.sender) bytes += 3 + rule.sender.length;
@@ -965,12 +982,20 @@
const buckets = clean(input.buckets)
? clean(input.buckets).split(",").map((value) => requiredInteger(value, 1, 6, "Path bucket"))
: [];
const path = normalizePacketPath(input.path);
const inferredWidth = path ? path.split(",")[0].length / 2 : 1;
const hashBytes = ["", "auto"].includes(clean(input.hashBytes).toLowerCase())
? inferredWidth : requiredInteger(input.hashBytes, 1, 3, "Packet path hash bytes");
if (path && hashBytes !== inferredWidth) {
throw new FilterToolError("Packet path hash bytes conflicts with received path width.");
}
return {
route: enumValue(input.route, ["unscoped_flood", "scoped_flood"], "Packet route", "unscoped_flood"),
type,
hops: requiredInteger(input.hops, 0, 63, "Packet hops"),
channel,
path: normalizePacketPath(input.path),
path,
hashBytes,
scopeStatus,
scopeName: clean(input.scopeName).replace(/^#/, ""),
regionName: clean(input.regionName),
@@ -1017,6 +1042,8 @@
if (packet.hops < minimum || packet.hops > maximum) misses.push(`hop ${packet.hops} is outside ${rule.hops}`);
if (rule.channel && rule.channel !== packet.channel) misses.push("channel differs or is unavailable");
if (!incomingMatches(rule.incoming, packet)) misses.push(`original scope does not satisfy ${rule.incoming}`);
if (rule.hashBytes && (rule.hashBytes === "2+" ? packet.hashBytes < 2
: rule.hashBytes !== packet.hashBytes)) misses.push(`path hash width is ${packet.hashBytes} bytes`);
if (rule.pathKind === "prefix") {
const wanted = rule.pathPrefix.split(",");
const actual = packet.path ? packet.path.split(",") : [];
@@ -1139,6 +1166,12 @@
}
const EXAMPLES = Object.freeze({
hash_width: Object.freeze([
{ ...documentedDropRule("drop-one-byte-paths", "any", "all"), hashBytes: 1 },
]),
hash_width_two_plus: Object.freeze([
{ ...documentedDropRule("drop-two-plus-byte-paths", "any", "all"), hashBytes: "2+" },
]),
channel_scope: Object.freeze([
{
...defaultRule("rgdata-scope"),
@@ -1312,6 +1345,7 @@
hops: field("hops").value,
channel: field("channel").value,
incoming: field("incoming").value,
hashBytes: field("hash-bytes").value,
pathKind: field("path-kind").value,
pathPrefix: field("path-prefix").value,
sender: field("sender").value,
@@ -1346,6 +1380,7 @@
hops: rule.hops,
channel: rule.channel,
incoming: rule.incoming,
"hash-bytes": rule.hashBytes,
"path-kind": rule.pathKind,
"path-prefix": rule.pathPrefix,
sender: rule.sender,
@@ -1575,6 +1610,7 @@
hops: packetField("hops").value,
channel: packetField("channel").value,
path: packetField("path").value,
hashBytes: packetField("hash-bytes").value,
scopeStatus: packetField("scope-status").value,
scopeName: packetField("scope-name").value,
regionName: packetField("region-name").value,
@@ -1593,6 +1629,7 @@
packetField("type").value = decoded.type;
packetField("hops").value = String(decoded.hops);
packetField("path").value = decoded.pathIds.join(",");
packetField("hash-bytes").value = String(decoded.pathHashBytes);
packetField("channel").value = decoded.channelHash;
packetField("scope-status").value = decoded.routeCode === 0 ? "unknown" : "none";
packetField("scope-name").value = "";
+402 -53
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",
@@ -254,8 +259,13 @@
const acceptedTags = new Set(RELEASE_PAGE_PREFIXES.map(function (prefix) {
return prefix + familyTag;
}));
const chunkGroups = ["companion"].concat(RELEASE_PAGE_PREFIXES.filter(Boolean).map(function (prefix) {
return prefix.slice(0, -1);
}));
const chunkTag = new RegExp("^(?:" + chunkGroups.map(escapeRegExp).join("|") +
")-(?:[2-9]|[1-9][0-9]+)-" + escapeRegExp(familyTag) + "$");
const familyReleases = publicReleases.filter(function (release) {
return acceptedTags.has(release.tag_name);
return acceptedTags.has(release.tag_name) || chunkTag.test(release.tag_name);
}).sort(function (a, b) {
return releaseDate(b).localeCompare(releaseDate(a));
});
@@ -317,9 +327,9 @@
// Corrected assets can have a new source hash on the existing release
// page. Keep the complete version/flavor boundary, allowing its commit
// suffix to differ from the original tag's commit.
const familyRoot = familyTag.replace(/-[0-9a-f]{7,8}$/i, "");
const familyRoot = familyTag.replace(/-[0-9a-f]{7,40}$/i, "");
const sourceSuffix = familyRoot === familyTag
? "(?:-[0-9a-f]{7,8})?" : "-[0-9a-f]{7,8}(?:-[0-9a-f]{7,8})?";
? "(?:-[0-9a-f]{7,40})?" : "-[0-9a-f]{7,40}(?:-[0-9a-f]{7,40})?";
const versionPattern = new RegExp(
"^(.*?)-(ota-)?" + escapeRegExp(familyRoot) + sourceSuffix + "$",
"i"
@@ -407,6 +417,12 @@
function variantForProfile(role, tail) {
let value = String(tail);
if (["repeater", "room", "sensor"].includes(role)) {
// Publication suffixes distinguish downloads, not physical wiring.
// Sensor support itself is read from qualified release controls below.
value = value.replace(/-(?:full|reduced)-ota$/i, "")
.replace(/-reduced$/i, "");
}
if (role === "companion") {
value = value.replace(
/^(?:full|wifi_mqtt|usb_ble|usb_wifi|ble|usb|wifi|serial|ethernet)(?=$|[_-])/i,
@@ -468,9 +484,14 @@
? "lora-source"
: "";
let variant = variantForProfile(parts.role, parts.tail);
if (/^RAK_(?:3401|4631)_repeater_unified_lora_ota$/i.test(target)) {
if (/^RAK_(?:3401|4631)_repeater_unified_lora_ota(?:-(?:full|reduced)(?:-ota)?)?$/i.test(target)) {
variant = "default";
}
if (/^RAK_4631_repeater_ethernet(?:-(?:full|reduced)(?:-ota)?)?$/i.test(target)) {
// The RAK13800 Ethernet image is a real hardware choice, separate from
// adaptive storage. Label it consistently for both sensor policies.
variant = "ethernet";
}
if (sourceHardware === "Station_G2_logging") {
variant = variant === "default"
? "rx-boosted"
@@ -560,11 +581,13 @@
heltec_t096_repeater_bridge_rs232_lora_ota_no_external_sensors:
"heltec_t096_repeater_lora_ota_no_external_sensors",
rak_4631_repeater_bridge_rs232_serial1_lora_ota_no_external_sensors:
"rak_4631_repeater_lora_ota_no_external_sensors",
["rak_4631_repeater_unified_lora_ota", "rak_4631_repeater_lora_ota_no_external_sensors"],
rak_4631_repeater_bridge_rs232_serial2_lora_ota_no_external_sensors:
"rak_4631_repeater_lora_ota_no_external_sensors",
rak_4631_repeater_bridge_rs232_serial1: "rak_4631_repeater",
rak_4631_repeater_bridge_rs232_serial2: "rak_4631_repeater",
["rak_4631_repeater_unified_lora_ota", "rak_4631_repeater_lora_ota_no_external_sensors"],
rak_4631_repeater_bridge_rs232_serial1:
["rak_4631_repeater_unified_lora_ota", "rak_4631_repeater"],
rak_4631_repeater_bridge_rs232_serial2:
["rak_4631_repeater_unified_lora_ota", "rak_4631_repeater"],
promicro_repeater_bridge_rs232_serial1: "promicro_repeater",
heltec_t114_without_display_repeater_bridge_rs232:
"heltec_t114_without_display_repeater",
@@ -592,11 +615,19 @@
combinedUsbBleKeys.has(key);
const replacedTerminal = profile.role === "terminal" &&
terminalReplacementKeys.has(key);
const rs232Replacement = mergedRs232Targets[
String(profile.target || "").toLowerCase()
];
const target = String(profile.target || "").toLowerCase();
const sensorSuffix = (target.match(/-(?:full|reduced)$/) || [""])[0];
const baseTarget = sensorSuffix ? target.slice(0, -sensorSuffix.length) : target;
const rs232Replacement = mergedRs232Targets[baseTarget];
// Recommendations prefer the combined UART2 image on RAK4631. Exact
// Serial1 files remain in raw rows for existing GPS-free installations;
// this preference does not migrate their wiring or OTA identity.
// Preserve the exact sensor policy: a Full image does not replace a
// missing Reduced image, or an older unsuffixed compatibility image.
const replacedRs232 = Boolean(rs232Replacement &&
targets.has(rs232Replacement));
[].concat(rs232Replacement).some(function (replacement) {
return targets.has(replacement + sensorSuffix);
}));
return !(replacedAttachedTransport && fullKeys.has(key)) &&
!replacedByUsbBle && !replacedTerminal && !replacedRs232;
});
@@ -609,6 +640,11 @@
return (profiles || []).filter(function (profile) {
const target = String(profile && profile.target || "");
// TLora cannot fit UART, MQTT and ESP-NOW together. Its normal Full
// UART image and Full MQTT observer are both intentional choices.
if (/^LilyGo_TLora_V2_1_1_6_repeater_observer_mqtt_?-full-usb-wifi$/i.test(target)) {
return true;
}
// Same-partition ESP32 migrations publish the ordinary target's Full
// image with its established mOTA identity. The former observer-named
// Full image remains in the release for deployed devices that still use
@@ -628,8 +664,8 @@
}));
return (profiles || []).filter(function (profile) {
const target = String(profile && profile.target || "").toLowerCase();
const match = /^rak_(3401|4631)_repeater_(?:lora_ota_no_external_sensors|w25q16_lora_ota|rak15001_slot_c_lora_ota|rak13302_w25q16_lora_ota)$/.exec(target);
return !match || !targets.has("rak_" + match[1] + "_repeater_unified_lora_ota");
const match = /^rak_(3401|4631)_repeater_(?:lora_ota_no_external_sensors|w25q16_lora_ota|rak15001_slot_c_lora_ota|rak13302_w25q16_lora_ota)(-(?:full|reduced))?$/.exec(target);
return !match || !targets.has("rak_" + match[1] + "_repeater_unified_lora_ota" + (match[2] || ""));
});
}
@@ -652,10 +688,7 @@
function applyMergedRak4631RepeaterCapabilities(profiles) {
return (profiles || []).map(function (profile) {
const target = String(profile && profile.target || "").toLowerCase();
if (
target !== "rak_4631_repeater" &&
target !== "rak_4631_repeater_lora_ota_no_external_sensors"
) {
if (!/^rak_4631_repeater(?:_lora_ota_no_external_sensors|_unified_lora_ota)?(?:-(?:full|reduced))?$/.test(target)) {
return profile;
}
@@ -718,6 +751,29 @@
if (typeof controls.dedicatedUsbLogging === "boolean") {
profile.dedicatedUsbLogging = controls.dedicatedUsbLogging;
}
const sensorSource = controls.sensorProfileSource;
const sensorSourceMatches = typeof sensorSource === "string" && /^[0-9a-f]{40}$/i.test(sensorSource) &&
profile.files.length > 0 && profile.files.every(function (file) {
const suffix = file.name.replace(/\.(bin|hex|uf2|zip)$/i, "").match(/-([0-9a-f]{7,40})$/i);
return suffix && sensorSource.toLowerCase().startsWith(suffix[1].toLowerCase());
});
if (controls.platform === "NRF52_PLATFORM" && ["repeater", "room", "sensor"].includes(profile.role) &&
controls.otaRole === "lora-receiver" && Array.isArray(controls.updateMethods) &&
controls.updateMethods.includes("lora") && sensorSourceMatches &&
["full", "reduced"].includes(controls.sensorProfile)) {
profile.sensorProfile = controls.sensorProfile;
profile.feature = controls.sensorProfile === "full" ? "full" : "standard";
if (controls.sensorProfile === "full") {
// A full publication may retain an old reduced logical OTA identity.
// That identity must not mislabel the actual qualified sensor policy.
profile.variant = variantWithoutSensorMarker(profile.variant);
}
}
}
function variantWithoutSensorMarker(value) {
return String(value || "default").replace(/(?:^|-)no-external-sensors(?=-|$)/g, "")
.replace(/^-+|-+$/g, "").replace(/--+/g, "-") || "default";
}
function hardwareFamilyFor(hardware, hardwareNames) {
@@ -883,7 +939,7 @@
}
if (profile.controls.espnowBridge &&
["repeater", "room"].includes(profile.role) && profile.mode === "standard") {
profile.connectionModes = ["standard", "espnow"];
profile.connectionModes = (profile.connectionModes || ["standard"]).concat("espnow");
}
}
profile.hardwareFamily = hardwareFamilyFor(
@@ -998,6 +1054,11 @@
}
function firmwareProfileLabel(profile) {
if (profile.sensorProfile) {
const label = (profile.sensorProfile === "full" ? "Full supported sensors" : "Reduced sensors") + " + LoRa OTA";
const variant = variantWithoutSensorMarker(profile.variant);
return label + (variant !== "default" ? " - " + humanizeVariant(variant, profile.hardware) : "");
}
const parts = [];
if (profile.feature === "full") parts.push("Full");
else if (profile.feature === "standard" && profile.ota !== "lora-receiver") {
@@ -1045,8 +1106,113 @@
});
}
// Count compiled builds, not files: merged and app-only downloads can belong
// to the same build, but still require different installation instructions.
function selectionProgress(profiles, filters) {
const count = (profiles || []).filter(function (profile) {
return profileMatchesFacets(profile, filters);
}).length;
return {
count: count,
state: count === 1 ? "single" : count ? "multiple" : "empty",
title: count === 1 ? "1 firmware build left" : count ? count + " firmware builds remain"
: "No matching firmware builds",
note: count === 1
? filters && filters.install ? "Confirm your board and install choice."
: "Choose an install operation."
: count ? "Choose options to narrow the results."
: "Change or clear choices to see available builds.",
};
}
function choicesUseSameFirmware(profiles, filters, field, options) {
// Never equate install operations, even when they belong to one build.
if (!["hardware", "role", "logging", "mode", "firmwareProfile"].includes(field)) {
return false;
}
const ignored = field === "firmwareProfile" ? PROFILE_FIELDS : field;
const compatible = (profiles || []).filter(function (profile) {
return profileMatchesFacets(profile, filters, ignored);
});
const choices = (options || []).filter(function (option) { return option.value; });
if (!compatible.length || !choices.length) return false;
// Ignore the current choice before comparing alternatives. Having one
// result now does not imply that another button would keep the same build.
return choices.every(function (option) {
const choice = field === "firmwareProfile" ? firmwareProfileFilters(option.value)
: { [field]: option.value };
if (!choice) return false;
const selection = Object.assign({}, filters, choice);
return compatible.every(function (profile) {
return profileMatchesFacets(profile, selection);
});
});
}
function selectionRequirements(profiles, filters) {
const chosen = filters || {};
const hiddenFields = [];
let installKind = chosen.install || "";
// Board and role are deliberate choices. Once known, do not ask for
// settings that leave every compatible build unchanged.
if (chosen.hardware && chosen.role) {
const context = Object.assign({}, chosen);
// Classify against board/role, not mutually narrowing settings. A real
// alternative must stay visible so a selected filter can be cleared.
["logging", "mode"].concat(PROFILE_FIELDS).forEach(function (field) {
delete context[field];
});
const boardContext = Object.assign({}, context);
delete boardContext.install;
const boardProfiles = (profiles || []).filter(function (profile) {
return profileMatchesFacets(profile, boardContext);
});
function supportsSelection(field) {
const selection = field === "firmwareProfile"
? PROFILE_FIELDS.reduce(function (parts, part) {
if (chosen[part]) parts[part] = chosen[part];
return parts;
}, {}) : chosen[field] ? { [field]: chosen[field] } : {};
return boardProfiles.some(function (profile) {
return profileMatchesFacets(profile, selection);
});
}
// Invalid old links need a visible Any control. Do not conceal their
// requested value or replace it with another install format.
if (!supportsSelection("install")) delete context.install;
["logging", "firmwareProfile", "mode"].forEach(function (field) {
const options = field === "firmwareProfile"
? firmwareProfileChoices(profiles, context)
: facetValues(profiles, context, field).map(function (value) {
return { value: value };
});
if (supportsSelection(field) &&
choicesUseSameFirmware(profiles, context, field, options)) {
hiddenFields.push(field);
}
});
const installs = facetValues(profiles, context, "install");
if (installs.length === 1 && supportsSelection("install")) {
hiddenFields.push("install");
installKind = installs[0];
}
}
return {
hiddenFields: hiddenFields,
installKind: installKind,
missingFields: CHOICE_FIELDS.filter(function (field) {
if (field === "chipFamily" || hiddenFields.includes(field)) return false;
if (field === "firmwareProfile") return !PROFILE_FIELDS.every(function (part) {
return chosen[part];
});
return !chosen[field];
}),
};
}
function selectionUrl(url, filters, automaticChipFamily) {
const result = new URL(url);
result.searchParams.delete("s");
FACET_FIELDS.forEach(function (field) {
result.searchParams.delete(field);
if (filters[field]) result.searchParams.set(field, filters[field]);
@@ -1058,8 +1224,61 @@
return result.href;
}
function shortSelectionUrl(url, filters, automaticChipFamily) {
const result = new URL(url);
FACET_FIELDS.concat(["chipAuto", "s"]).forEach(function (field) {
result.searchParams.delete(field);
});
const parts = ["1"];
Object.keys(SHORT_SELECTION_FIELDS).forEach(function (key) {
const field = SHORT_SELECTION_FIELDS[key];
const value = filters[field];
if (!value || (field === "chipFamily" && automaticChipFamily) ||
(field === "hardwareFamily" && value === filters.hardware)) return;
// Dots delimit fields; escape them even though encodeURIComponent does not.
parts.push(key + encodeURIComponent(value).replace(/\./g, "%2E"));
});
if ((filters.hardwareFamily || filters.hardware) && !automaticChipFamily) {
parts.push("a0");
}
if (parts.length > 1) result.searchParams.set("s", parts.join("."));
return result.href;
}
function shortSelectionParams(params) {
const parts = params.get("s").split(".");
const result = new URLSearchParams();
if (params.getAll("s").length !== 1 || parts.shift() !== "1" || !parts.length) {
throw new Error("unsupported or incomplete short picker link");
}
const seen = new Set();
parts.forEach(function (part) {
const key = part.charAt(0);
if (seen.has(key) || (key !== "a" &&
!Object.prototype.hasOwnProperty.call(SHORT_SELECTION_FIELDS, key))) {
throw new Error("duplicate or unknown short picker field");
}
seen.add(key);
const value = decodeURIComponent(part.slice(1));
if (!value || (key === "a" && value !== "0" && value !== "1")) {
throw new Error("invalid short picker field");
}
result.set(key === "a" ? "chipAuto" : SHORT_SELECTION_FIELDS[key], value);
});
return result;
}
function selectionFromUrl(url, profiles) {
const params = new URL(url).searchParams;
let params = new URL(url).searchParams;
if (params.has("s")) {
try {
// The versioned selection is authoritative over any legacy fields.
params = shortSelectionParams(params);
} catch (error) {
return { filters: {}, automaticChipFamily: false,
unavailable: ["short link: " + error.message] };
}
}
const requested = {};
const filters = {};
const unavailable = [];
@@ -1342,7 +1561,12 @@
"Requires the external W25Q16 storage board, its exact documented wiring, and the matching storage-aware OTAFIX bootloader."
);
}
if (profile.variant.includes("no-external-sensors")) {
if (profile.sensorProfile === "full") {
extra.push("Includes the complete sensor drivers supported by this board's full recipe, with LoRa OTA retained. Attached sensor hardware and compatible wiring are still required.");
} else if (profile.sensorProfile === "reduced") {
extra.push("Omits selected optional environmental/ranging sensor drivers to reduce flash use, with LoRa OTA retained. Generic I2C and supported board peripherals remain available.");
}
if (!profile.sensorProfile && profile.variant.includes("no-external-sensors")) {
if (/^RAK_(3401|4631)$/i.test(profile.hardware)) {
extra.push("This profile uses internal storage for LoRa OTA; no external storage board is required.");
}
@@ -1424,6 +1648,7 @@
}
return {
label: LOGGING_LABELS[mode], commands: commands,
selection: { logging: mode },
text: full && info && info.mqtt
? (mode === "wifi" || mode === "both"
? "In WebConfig, enable the desired MQTT broker presets/settings and save."
@@ -1513,19 +1738,35 @@
if (profile.role === "repeater") toggle("Repeat mesh traffic", "set repeat", "get repeat");
if (info.rs232 && infrastructure) {
const mode = chosen.mode;
const uartNote = /^RAK_4631_repeater_unified_lora_ota-(?:full|reduced)$/i.test(profile.target) && profile.sensorProfile
? "Unified RAK4631 uses UART2 for the RS-232 bridge; select it with set bridge.uart 2 while the bridge is stopped. GPS safety guards reserve UART1 even when no GPS was detected. Use UART2 for new installations. Legacy Serial1 downloads remain available by exact filename for existing GPS-free wiring."
: /^RAK_4631_repeater_bridge_rs232_serial1(?:_lora_ota_no_external_sensors)?-(?:full|reduced)$/i.test(profile.target)
? "This GPS-free Serial1 image uses UART1. Do not fit a powered UART GPS or header-connected W25 flash on the same UART1 pins. Use the unified UART2 image when those pins are occupied."
: "Use the UART and pin map for this exact board. Canonical GPS-enabled RAK4631 uses UART2; select it with set bridge.uart 2 while the bridge is stopped. UART1 needs a compatible GPS-free image.";
// MQTT images keep the historical bridge.enabled ESP-NOW alias.
// Their independently saved UART uses the transport-specific controls.
const uartSetting = info.mqtt ? "rs232.enabled" : "bridge.enabled";
const uartRunning = info.mqtt ? "rs232.running" : "bridge.running";
section("RS232 bridge" + (mode === "rs232" ? " - selected" : ""), [
{ label: "On", commands: ["set bridge.enabled on", "get bridge.running"] },
{ label: "Off", commands: ["set bridge.enabled off"] },
{ label: "Set baud", commands: ["set bridge.enabled off", "set bridge.baud 115200", "set bridge.enabled on"] },
{ label: "On", commands: ["set " + uartSetting + " on", "get " + uartRunning] },
{ label: "Off", commands: ["set " + uartSetting + " off"] },
{ label: "Set baud", commands: ["set " + uartSetting + " off", "set bridge.baud 115200", "set " + uartSetting + " on"] },
].sort(function (a, b) {
return mode === "standard" ? Number(b.label === "Off") - Number(a.label === "Off") : 0;
}), "Use the UART and pin map for this exact board. Canonical GPS-enabled RAK4631 uses UART2; select it with set bridge.uart 2 while the bridge is stopped. UART1 needs a compatible GPS-free image.");
}), uartNote);
}
if (info.espnowBridge && infrastructure) {
toggle("ESP-NOW bridge", "set bridge.enabled", "get bridge.enabled",
info.mqtt
? "Independent of MQTT in this Full image. set espnow.enabled is an alias. get bridge.running (or get espnow.running) checks its live state."
: "set bridge.enabled controls the ESP-NOW bridge; get bridge.running checks its live state.");
// Combined images need the transport-specific switch: bridge.enabled
// keeps its historical meaning for the primary transport. Older
// dedicated ESP-NOW releases retain their original bridge.* commands.
const independentEspnow = info.rs232 || info.mqtt;
toggle("ESP-NOW bridge", independentEspnow ? "set espnow.enabled" : "set bridge.enabled",
independentEspnow ? "get espnow.running" : "get bridge.running",
info.rs232
? "Independent of RS-232. Newly merged repeaters start with ESP-NOW off until enabled; saved settings survive reboot. get espnow.enabled checks the saved setting; get espnow.running checks its live state."
: info.mqtt
? "Independent of MQTT in this Full image. get espnow.enabled checks the saved setting; get espnow.running checks its live state."
: "set bridge.enabled controls the ESP-NOW bridge; get bridge.running checks its live state.");
section("ESP-NOW bridge framing", ["wrapped", "raw"].map(function (mode) {
return { label: mode, commands: ["set bridge.format " + mode] };
}), info.mqtt
@@ -1552,7 +1793,40 @@
return sections;
}
function renderRuntimeDirections(card, profile, selection) {
function runtimePanelDirections(profile, selection, runtimeFields) {
const chosen = Object.assign({}, selection);
const moved = runtimeFields || [];
if (moved.includes("logging")) delete chosen.logging;
if (moved.includes("mode") && !chosen.mode &&
profileFieldValues(profile, "mode").includes("standard")) {
chosen.mode = "standard";
}
const sections = runtimeDirections(profile, chosen);
sections.forEach(function (item) {
if (moved.includes("logging") && item.actions.some(function (action) {
return action.selection && action.selection.logging;
})) {
const mode = selection && selection.logging || "none";
const selected = item.actions.findIndex(function (action) {
return action.selection && action.selection.logging === mode;
});
item.selectedIndex = selected < 0 ? 0 : selected;
item.title = "Logging / MQTT output";
}
if (moved.includes("mode") && (/^RS232 bridge/.test(item.title) ||
item.title === "ESP-NOW bridge")) {
const on = /^RS232 bridge/.test(item.title)
? chosen.mode === "rs232" : chosen.mode === "espnow";
item.selectedIndex = item.actions.findIndex(function (action) {
return action.label === (on ? "On" : "Off");
});
}
});
return sections;
}
function renderRuntimeDirections(card, profile, selection, options) {
const settings = options || {};
const panel = createElement("section");
panel.className = "firmware-picker-runtime";
panel.appendChild(createElement("h4", "Restore your settings after flashing"));
@@ -1572,7 +1846,7 @@
: "Use board and ver to identify the node.")));
}
if (!profile.controls) panel.appendChild(createElement("p", "Additional hardware controls have not been verified for this exact release image. Use the role guide for those settings."));
runtimeDirections(profile, selection).forEach(function (item, index) {
runtimePanelDirections(profile, selection, settings.runtimeFields).forEach(function (item, index) {
const details = createElement("details");
details.open = index === 0;
details.appendChild(createElement("summary", item.title));
@@ -1582,13 +1856,13 @@
fieldset.appendChild(createElement("legend", "Show steps for " + item.title));
const choices = createElement("div");
choices.className = "firmware-picker-radio-options";
let actionIndex = item.selectedIndex >= 0 ? item.selectedIndex : 0;
renderRadioChoices(choices, item.actions.map(function (action, i) {
return { value: String(i), label: action.label };
}), "0", "firmware-picker-runtime-" + (++runtimeGroupCount));
}), String(actionIndex), "firmware-picker-runtime-" + (++runtimeGroupCount));
fieldset.appendChild(choices);
details.appendChild(fieldset);
const output = createElement("div");
let actionIndex = 0;
function show() {
output.replaceChildren();
const action = item.actions[actionIndex];
@@ -1614,6 +1888,11 @@
choices.addEventListener("change", function (event) {
actionIndex = Number(event.target.value) || 0;
show();
const action = item.actions[actionIndex];
if (action.selection && settings.onSelectionChange &&
(settings.runtimeFields || []).includes("logging")) {
settings.onSelectionChange(action.selection);
}
});
show();
details.appendChild(output);
@@ -1630,7 +1909,7 @@
});
}
function renderProfileCard(container, profile, asset, installKind, selection) {
function renderProfileCard(container, profile, asset, installKind, selection, options) {
const card = createElement("article");
card.className = "firmware-picker-card";
card.appendChild(createElement(
@@ -1649,7 +1928,8 @@
return labelFor("mode", mode);
}).join(" / "),
],
["Logging", labelFor("logging", selection && selection.logging || profile.logging)],
["Logging", labelFor("logging", options && (options.runtimeFields || []).includes("logging")
? profile.logging : selection && selection.logging || profile.logging)],
["Firmware profile", firmwareProfileLabel(profile)],
["Install operation", labelFor("install", installKind)],
["File", asset.name],
@@ -1691,7 +1971,7 @@
});
steps.appendChild(list);
card.appendChild(steps);
renderRuntimeDirections(card, profile, selection);
renderRuntimeDirections(card, profile, selection, options);
container.appendChild(card);
}
@@ -1712,6 +1992,8 @@
const shareLink = root.querySelector('[data-role="share-link"]');
const copyLinkButton = root.querySelector('[data-action="copy-link"]');
const linkStatus = root.querySelector('[data-role="link-status"]');
const feedback = root.querySelector('[data-role="filter-feedback"]');
const resultLink = root.querySelector('[data-role="view-results"]');
const controls = {};
FACET_FIELDS.forEach(function (field) {
controls[field] = form.querySelector('[data-field="' + field + '"]');
@@ -1723,9 +2005,13 @@
const filters = {};
let automaticChipFamily = false;
const groupPrefix = "firmware-picker-" + (++pickerInstanceCount);
if (resultLink) {
result.id = result.id || groupPrefix + "-results";
resultLink.href = "#" + result.id;
}
function updateSelectionUrl() {
const url = selectionUrl(global.location.href, filters, automaticChipFamily);
const url = shortSelectionUrl(global.location.href, filters, automaticChipFamily);
if (url !== global.location.href) {
try {
global.history.replaceState(global.history.state, "", url);
@@ -1735,9 +2021,11 @@
}
}
if (shareLink) {
const base = global.location.protocol === "file:"
? root.getAttribute("data-share-url") : url;
shareLink.href = selectionUrl(base || url, filters, automaticChipFamily);
const base = new URL(global.location.protocol === "file:"
? root.getAttribute("data-share-url") || url : url);
base.pathname = base.pathname.replace(/\/firmware_picker\/$/, "/p/");
if (global.location.protocol === "file:") base.hash = global.location.hash;
shareLink.href = shortSelectionUrl(base.href, filters, automaticChipFamily);
shareLink.hidden = false;
}
if (copyLinkButton) copyLinkButton.disabled = false;
@@ -1760,13 +2048,13 @@
if (copyLinkButton && shareLink) copyLinkButton.addEventListener("click", function () {
const copy = global.navigator.clipboard && global.navigator.clipboard.writeText;
if (!copy) {
linkStatus.textContent = "Copy the address bar, or copy the 'Link to these settings' link.";
linkStatus.textContent = "Copy the address bar, or copy the 'Short link to these settings' link.";
return;
}
global.navigator.clipboard.writeText(shareLink.href).then(function () {
linkStatus.textContent = "Link copied.";
}).catch(function () {
linkStatus.textContent = "Copy the address bar, or copy the 'Link to these settings' link.";
linkStatus.textContent = "Copy the address bar, or copy the 'Short link to these settings' link.";
});
});
global.addEventListener("popstate", restoreSelectionUrl);
@@ -1816,6 +2104,35 @@
renderRadioChoices(container, options, selected, groupPrefix + "-" + field, field);
const fieldset = container.closest("fieldset");
if (fieldset) fieldset.disabled = values.length === 0;
setRefinementHint(container, field, options);
}
function setRefinementHint(container, field, options) {
const fieldset = container.closest("fieldset");
if (!fieldset) return;
let badge = fieldset.querySelector('[data-role="same-firmware"]');
let hint = fieldset.querySelector('[data-role="refinement-hint"]');
if (!badge) {
badge = createElement("span", "Same firmware");
badge.className = "firmware-picker-same-firmware";
badge.dataset.role = "same-firmware";
fieldset.querySelector("legend").appendChild(badge);
hint = createElement("p");
hint.className = "firmware-picker-control-help firmware-picker-refinement-hint";
hint.dataset.role = "refinement-hint";
hint.id = groupPrefix + "-" + field + "-hint";
fieldset.insertBefore(hint, container);
}
const same = choicesUseSameFirmware(catalog.profiles, filters, field, options);
badge.hidden = !same;
hint.hidden = !same;
hint.textContent = same ? "These choices do not narrow the firmware builds." +
(["logging", "mode"].includes(field) ? " They can change setup instructions." : "") : "";
const describedBy = (fieldset.getAttribute("aria-describedby") || "")
.split(/\s+/).filter(function (id) { return id && id !== hint.id; });
if (same) describedBy.push(hint.id);
if (describedBy.length) fieldset.setAttribute("aria-describedby", describedBy.join(" "));
else fieldset.removeAttribute("aria-describedby");
}
function setControlOptions(field, values) {
@@ -1892,6 +2209,12 @@
renderRadioChoices(profileControl, [{ value: "", label: "Any" }].concat(profileChoices),
firmwareProfileValue(filters), groupPrefix + "-firmwareProfile", "firmwareProfile");
profileControl.closest("fieldset").disabled = profileChoices.length === 0;
setRefinementHint(profileControl, "firmwareProfile", profileChoices);
const requirements = selectionRequirements(catalog.profiles, filters);
["logging", "firmwareProfile", "mode", "install"].forEach(function (field) {
const control = controls[field];
if (control) control.closest("fieldset").hidden = requirements.hiddenFields.includes(field);
});
const hardwareVariants = valuesForField("hardware");
const hardwareVariantControl = root.querySelector(
'[data-role="hardware-variant-control"]'
@@ -1917,16 +2240,33 @@
result.hidden = true;
missing.hidden = true;
resultList.replaceChildren();
const missingFields = CHOICE_FIELDS.filter(function (field) {
if (field === "firmwareProfile") return !PROFILE_FIELDS.every(function (part) {
return filters[part];
});
return field !== "chipFamily" && !filters[field];
});
const requirements = selectionRequirements(catalog.profiles, filters);
const missingFields = requirements.missingFields;
const cardOptions = {
runtimeFields: requirements.hiddenFields,
onSelectionChange: function (selection) {
Object.keys(selection).forEach(function (field) {
filters[field] = selection[field];
});
updateSelectionUrl();
},
};
const matches = matchingProfiles();
if (feedback) {
const progress = selectionProgress(catalog.profiles,
Object.assign({}, filters, { install: requirements.installKind }));
feedback.hidden = false;
feedback.dataset.state = progress.state;
feedback.querySelector('[data-role="filter-count"]').textContent = progress.title;
feedback.querySelector('[data-role="filter-note"]').textContent = missingFields.length
? progress.note : "Matching files and install instructions are shown below.";
}
if (resultLink) resultLink.hidden = true;
if (missingFields.length) {
if (missingFields.length === CHOICE_FIELDS.length - 1 && !filters.chipFamily) {
if (matches.length === 1) {
status.textContent = "One firmware build remains. Confirm the hardware, role and any remaining install choice.";
} else if (missingFields.length === CHOICE_FIELDS.length - 1 && !filters.chipFamily) {
status.textContent = "Pick options in any order. " +
matches.length + " compatible configurations are available.";
} else {
@@ -1936,7 +2276,7 @@
(missingFields.length === 1 ? "" : "s") + " in any order.";
}
if (shouldShowCandidateResults(matches)) {
const candidates = resolveProfileAssets(matches, filters.install);
const candidates = resolveProfileAssets(matches, requirements.installKind);
if (candidates.length) {
status.textContent += " Possible files are shown below.";
resultEyebrow.textContent = "Narrowed firmware candidates";
@@ -1950,16 +2290,18 @@
entry.profile,
entry.asset,
entry.installKind,
filters
filters,
cardOptions
);
});
result.hidden = false;
if (resultLink) resultLink.hidden = false;
}
}
return;
}
const installKind = filters.install;
const installKind = requirements.installKind;
const resolved = resolveProfileAssets(matches, installKind);
if (!resolved.length) {
missing.hidden = false;
@@ -1981,10 +2323,12 @@
entry.profile,
entry.asset,
entry.installKind,
filters
filters,
cardOptions
);
});
result.hidden = false;
if (resultLink) resultLink.hidden = false;
}
function renderBootloader() {
@@ -2226,12 +2570,17 @@
firmwareProfileFilters: firmwareProfileFilters,
firmwareProfileLabel: firmwareProfileLabel,
firmwareProfileChoices: firmwareProfileChoices,
selectionProgress: selectionProgress,
choicesUseSameFirmware: choicesUseSameFirmware,
selectionRequirements: selectionRequirements,
selectionUrl: selectionUrl,
shortSelectionUrl: shortSelectionUrl,
selectionFromUrl: selectionFromUrl,
canonicalAsset: canonicalAsset,
resolveProfileAssets: resolveProfileAssets,
shouldShowCandidateResults: shouldShowCandidateResults,
runtimeDirections: runtimeDirections,
runtimePanelDirections: runtimePanelDirections,
renderRuntimeDirections: renderRuntimeDirections,
installSteps: installSteps,
migrationReleaseUrl: migrationReleaseUrl,
+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);
}
})();
+153 -28
View File
@@ -7,9 +7,11 @@
// which Web Crypto exposes directly. AES-CBC with a zero IV supplies the
// required AES block primitive; only its first ciphertext block is used.
const HEADER = 83;
const CURRENT_HEADER = 111;
const TAG = 16;
const ENTRY = 13;
const PER_PAGE = 6;
const CURRENT_PER_PAGE = 4;
const MAX_KEYS = 36;
const ZERO_BLOCK = new Uint8Array(16);
const UTF8 = new TextEncoder();
@@ -147,7 +149,7 @@
for (let index = 0; index < 16; index += 1) d[index] ^= aadMac[index];
if (plaintext.length >= 16) {
const adjusted = plaintext.slice();
const adjusted = new Uint8Array(plaintext);
const last = adjusted.length - 16;
for (let index = 0; index < 16; index += 1) adjusted[last + index] ^= d[index];
return cmac(encryptBlock, adjusted);
@@ -163,7 +165,7 @@
async function ctrCrypt(keyBytes, tag, input) {
const subtle = cryptoApi();
const counter = tag.slice();
const counter = new Uint8Array(tag);
counter[8] &= 0x7f;
counter[12] &= 0x7f;
const key = await subtle.importKey("raw", keyBytes, { name: "AES-CTR" }, false, ["decrypt"]);
@@ -214,22 +216,34 @@
return (await hmacSha256(key, concatBytes(radio, normalized))).slice(0, 12);
}
function layout(payload) {
const magic = bytesToAscii(payload, 0, 4);
if (magic === "MGR1") return { header: HEADER, perPage: PER_PAGE };
if (magic === "MGR2") return { header: CURRENT_HEADER, perPage: CURRENT_PER_PAGE };
return null;
}
function canonicalLength(payload) {
if (payload.length < HEADER + TAG || bytesToAscii(payload, 0, 4) !== "MGR1") {
managementError("This data does not begin with an MGR1 management page.");
const format = layout(payload);
if (!format || payload.length < format.header + TAG) {
managementError("This data does not begin with a complete MGR1/MGR2 management page.");
}
if (format.header === CURRENT_HEADER && ((uint16LE(payload, 83) & 0xf800) || (payload[85] & 0xc0))) {
managementError("Reserved USB status bits must be zero.");
}
if (format.header === CURRENT_HEADER) usbWatchdogEvent(payload);
const page = payload[78];
const pages = payload[79];
const total = payload[80];
const first = payload[81];
const count = payload[82];
const expectedPages = total ? Math.ceil(total / PER_PAGE) : 1;
const expectedPages = total ? Math.ceil(total / format.perPage) : 1;
if (total > MAX_KEYS || pages !== expectedPages || page >= pages ||
first !== page * PER_PAGE || first > total ||
count !== Math.min(PER_PAGE, total - first)) {
managementError("The MGR1 page index or ACL bounds are invalid.");
first !== page * format.perPage || first > total ||
count !== Math.min(format.perPage, total - first)) {
managementError("The management page index or ACL bounds are invalid.");
}
return HEADER + count * ENTRY + TAG;
return format.header + count * ENTRY + TAG;
}
function paddedFloodLength(canonical) {
@@ -256,13 +270,14 @@
const hops = pathInfo & 0x3f;
if (width > 3 || hops * width > 64) return null;
const payloadOffset = pathOffset + 1 + width * hops;
if (payloadOffset >= bytes.length || bytesToAscii(bytes, payloadOffset, 4) !== "MGR1") return null;
if (payloadOffset >= bytes.length || !layout(bytes.slice(payloadOffset))) return null;
const payload = bytes.slice(payloadOffset);
const canonical = canonicalLength(payload);
const padded = paddedFloodLength(canonical);
if (payload.length !== canonical &&
(payload.length !== padded || !payload.slice(canonical).every((value) => value === 0))) {
managementError("MGR1 packet padding or length is invalid.");
// Every GroupData route uses the group cipher-compatible padded length,
// even though management pages protect their own ACL with AES-SIV.
if (payload.length !== padded || !payload.slice(canonical).every((value) => value === 0)) {
managementError("Management packet padding or length is invalid.");
}
return {
payload: payload.slice(0, canonical),
@@ -277,26 +292,35 @@
}
function parseCanonical(bytes) {
if (bytesToAscii(bytes, 0, 4) !== "MGR1") return null;
if (!layout(bytes)) return null;
const canonical = canonicalLength(bytes);
const padded = paddedFloodLength(canonical);
if (bytes.length !== canonical &&
(bytes.length !== padded || !bytes.slice(canonical).every((value) => value === 0))) {
managementError("MGR1 payload padding or length is invalid.");
managementError("Management payload padding or length is invalid.");
}
return { payload: bytes.slice(0, canonical), envelope: null };
}
function inputByteStreams(input) {
if (typeof input !== "string" || input.trim() === "") {
managementError("Paste one or more MGR1 payloads or GroupData packet hex values first.");
managementError("Paste one or more MGR1/MGR2 payloads or GroupData packet hex values first.");
}
if (input.length > 32768) managementError("The pasted value is too large to be management-report data.");
const candidates = [input, ...input.split(/\r?\n/)];
// A wrapped single page is one candidate, not an invalid first-line
// fragment. Conversely, joining complete page lines creates a bogus
// oversized canonical page: let each line retain its own bounds.
try {
const whole = hexToBytes(input);
if (parseCanonical(whole) || parsePacket(whole)) return [whole];
} catch (_error) {
// JSON/log wrappers and multiple complete page lines are handled below.
}
const candidates = input.split(/\r?\n/);
const quotedRaw = /["'](?:raw|data)["']\s*:\s*["']([^"']+)["']/gi;
let match;
while ((match = quotedRaw.exec(input)) !== null) candidates.push(match[1]);
const streams = input.match(/(?:0x)?[0-9a-f]{2}(?:(?:[\s:,_-]*)(?:0x)?[0-9a-f]{2}){15,}/gi);
const streams = input.match(/(?:0x)?[0-9a-f]{2}(?:(?:[ \t:,_-]*)(?:0x)?[0-9a-f]{2}){15,}/gi);
if (streams) candidates.push(...streams);
const unique = new Map();
@@ -319,7 +343,7 @@
try {
parsed = parseCanonical(bytes) || parsePacket(bytes);
} catch (error) {
if (error instanceof ManagementDecodeError && bytesToAscii(bytes, 0, 4) !== "MGR1") {
if (error instanceof ManagementDecodeError && !layout(bytes)) {
// It may be an unrelated hex stream alongside a valid packet.
continue;
}
@@ -333,7 +357,7 @@
}
}
if (!found.length) {
managementError("No complete MGR1 management page was found. Paste canonical MGR1 payload hex or an entire GroupData packet.");
managementError("No complete management page was found. Paste canonical MGR1/MGR2 payload hex or an entire GroupData packet.");
}
return found;
}
@@ -366,6 +390,45 @@
return ({ 1: "repeater", 2: "room server", 3: "sensor" })[value] || `unknown (${value})`;
}
function usbStatus(payload) {
if (bytesToAscii(payload, 0, 4) !== "MGR2") return null;
const flags = uint16LE(payload, 83);
const names = ["supported", "loggingEnabled", "watchdogEnabled", "hostConnected",
"readerConnected", "stalled", "recovering", "recoveryDeferred", "persistenceReady", "watchdogAuto", "loggerActive"];
const result = {};
names.forEach((name, bit) => { result[name] = Boolean(flags & (1 << bit)); });
result.stage = payload[85] & 3;
result.backoffStep = (payload[85] >> 2) & 15;
result.retrySeconds = uint32LE(payload, 86);
result.inactiveSeconds = uint32LE(payload, 90);
result.autoConnectedSeconds = uint32LE(payload, 94);
return result;
}
function usbWatchdogEvent(payload) {
if (bytesToAscii(payload, 0, 4) !== "MGR2") return null;
const code = payload[98];
const sequence = uint32LE(payload, 107);
if (!sequence) {
if (!payload.slice(98, 111).every((value) => value === 0)) {
managementError("Invalid empty USB watchdog event.");
}
return null;
}
const reasons = code & 15;
const actionCode = (code >> 4) & 7;
if (!reasons || actionCode < 1 || actionCode > 4) {
managementError("Invalid USB watchdog event reason/action.");
}
const names = ["host-absent", "reader-absent", "tx-stalled", "client-inactive"];
return {
reasons, reasonNames: names.filter((_name, bit) => reasons & (1 << bit)),
actionCode, action: [null, "soft-recovery", "reenumerate", "reboot-requested", "reboot-cancelled"][actionCode],
persisted: Boolean(code & 128), epoch: uint32LE(payload, 99),
uptimeSeconds: uint32LE(payload, 103), sequence,
};
}
function publicFields(payload, envelope) {
const valid = uint16LE(payload, 76);
const capabilities = payload[73];
@@ -374,6 +437,7 @@
const knownActive = featureNames(active & known);
const unknownActive = featureNames(capabilities & ~known);
return {
protocol: bytesToAscii(payload, 0, 4),
radioId: bytesToHex(payload.slice(4, 20)),
sequence: uint32LE(payload, 20),
timestamp: uint32LE(payload, 24),
@@ -396,6 +460,8 @@
acl: `${payload[80]} total; ${payload[82]} on this page`,
partialSince: Boolean(valid & 32),
mcuTemperature: Boolean(valid & 64),
usbLogging: usbStatus(payload),
usbWatchdogLast: usbWatchdogEvent(payload),
envelope,
};
}
@@ -417,9 +483,11 @@
});
const sorted = [...byPage.values()].sort((left, right) => left.payload[78] - right.payload[78]);
const reference = sorted[0].payload;
const header = layout(reference).header;
if (sorted.some((page) => !equalBytes(page.payload.slice(0, 78), reference.slice(0, 78)) ||
!equalBytes(page.payload.slice(HEADER, header), reference.slice(HEADER, header)) ||
page.payload[79] !== reference[79] || page.payload[80] !== reference[80])) {
managementError("MGR1 pages do not belong to the same snapshot.");
managementError("Management pages do not belong to the same snapshot.");
}
return sorted;
}
@@ -427,9 +495,10 @@
async function decryptPage(payload, password, candidate) {
const root = await passwordKey(password);
const key = await deriveKey(root, "MeshCore-MGR1-SIV", payload.slice(4, 20));
const header = layout(payload).header;
const privateLength = payload[82] * ENTRY;
const plaintext = await openSiv(key, payload.slice(0, HEADER),
payload.slice(HEADER, HEADER + privateLength), payload.slice(HEADER + privateLength));
const plaintext = await openSiv(key, payload.slice(0, header),
payload.slice(header, header + privateLength), payload.slice(header + privateLength));
try {
let wanted = null;
if (candidate) wanted = await aclFingerprint(password, payload.slice(4, 20), candidate);
@@ -470,6 +539,9 @@
return model;
}
for (const page of pages) model.acl.push(...await decryptPage(page.payload, password, candidate));
if (new Set(model.acl.map((entry) => entry.fingerprint)).size !== model.acl.length) {
managementError("Authenticated ACL data contains a duplicate fingerprint.");
}
model.authenticated = true;
if (!model.complete) {
model.warnings.push(`Authenticated ${pages.length} of ${first.payload[79]} pages. Paste the remaining pages to view the complete ACL.`);
@@ -508,6 +580,17 @@
const warnings = root.querySelector("[data-role='warnings']");
const warningList = root.querySelector("[data-role='warning-list']");
const aclTable = root.querySelector("[data-role='acl-table']");
let activeDecode = null;
function resetResults() {
results.hidden = true;
summary.replaceChildren();
aclTable.replaceChildren();
warningList.replaceChildren();
warnings.hidden = true;
status.textContent = "";
status.classList.toggle("management-status-authenticated", false);
}
function showError(value) {
results.hidden = true;
@@ -517,6 +600,7 @@
function render(model) {
const values = [
["Protocol", model.public.protocol],
["Reporter", model.public.radioId], ["Sequence", model.public.sequence],
["Report time", timestampText(model.public.timestamp)], ["Role", model.public.role],
["Firmware", model.public.firmware], ["Bootloader", model.public.bootloader],
@@ -528,6 +612,35 @@
["Compiled capabilities", model.public.compiled], ["Known active capabilities", model.public.active],
["Page", model.public.page], ["ACL", model.public.acl],
];
const usb = model.public.usbLogging;
if (usb) {
const stage = ["idle", "soft recovery wait", "re-enumeration wait", "reboot pending"][usb.stage];
values.push(["USB logging", usb.supported ? (usb.loggingEnabled ? "enabled" : "disabled") : "unsupported"],
["USB watchdog", usb.watchdogAuto ? "Auto (qualifying)" : usb.watchdogEnabled ? "On" : "Off"],
["USB Auto qualification", `${usb.autoConnectedSeconds}s of 1209600s`],
["USB host / reader", `${usb.hostConnected ? "host connected" : "no host"}; ${usb.readerConnected ? "reader connected" : "no reader"}`],
["USB logging client", usb.loggerActive ? "recent USB stats polling (15-minute lease)" : "inactive (stats lease or healthy USB link missing)"],
["USB recovery", `${usb.stalled ? "stalled; " : ""}${usb.recovering ? "recovering; " : ""}${usb.recoveryDeferred ? "deferred; " : ""}${stage}; backoff=${usb.backoffStep}`],
["USB retry interval / inactive", `${usb.retrySeconds}s / ${usb.inactiveSeconds}s`],
["USB persistence", usb.persistenceReady ? "ready" : "not ready"]);
} else {
values.push(["USB logging status", "unavailable in MGR1"]);
}
const event = model.public.usbWatchdogLast;
if (event) {
values.push(["Last USB watchdog event", `#${event.sequence}: ${event.action}`],
["USB watchdog event reasons", event.reasonNames.join(", ")],
["USB watchdog event time", `${timestampText(event.epoch)} (advisory RTC; raw epoch=${event.epoch})`],
["USB watchdog event uptime", `${event.uptimeSeconds}s at the recorded boot`],
["USB watchdog event persistence", event.persisted ? "saved" : "RAM-only"],
["USB watchdog action meaning", event.actionCode === 3
? "Reboot was requested; this does not prove a physical reset occurred."
: event.actionCode === 4 ? "Reboot request was cancelled; no physical reset is claimed."
: "Recovery attempt was recorded; this does not prove successful recovery."]);
} else {
values.push(["Last USB watchdog event", model.public.protocol === "MGR2"
? "none recorded" : "unavailable in MGR1"]);
}
summary.replaceChildren();
values.forEach(([term, description]) => {
const item = document.createElement("div");
@@ -540,7 +653,7 @@
});
status.textContent = model.authenticated
? `Password-authenticated ${model.suppliedPages}/${model.pageCount} page${model.pageCount === 1 ? "" : "s"}.`
: "Public-only decode — no authenticity claim without the management password.";
: "Public-only decode - no authenticity claim without the management password.";
status.classList.toggle("management-status-authenticated", model.authenticated);
warningList.replaceChildren();
@@ -579,24 +692,36 @@
}
async function decodeInput() {
const request = {};
activeDecode = request;
resetResults();
error.hidden = true;
decode.disabled = true;
try {
render(await decodeManagement(input.value, password.value, candidate.value.trim()));
const model = await decodeManagement(input.value, password.value, candidate.value.trim());
if (activeDecode === request) render(model);
} catch (failure) {
showError(failure);
if (activeDecode === request) showError(failure);
} finally {
decode.disabled = false;
if (activeDecode === request) {
activeDecode = null;
decode.disabled = false;
}
}
}
decode.addEventListener("click", decodeInput);
clear.addEventListener("click", () => {
// Web Crypto cannot be cancelled, but an older promise must never
// redisplay decrypted ACL entries after Clear or a newer decode.
activeDecode = null;
input.value = "";
password.value = "";
candidate.value = "";
results.hidden = true;
resetResults();
error.hidden = true;
error.textContent = "";
decode.disabled = false;
input.focus();
});
input.addEventListener("keydown", (event) => {
+23 -3
View File
@@ -5,7 +5,8 @@
const EXAMPLES = {
find: { label: "Find my node", kind: "all", id: "", when: "disconnected", vibration: "200,200,200", led: "100,100,100,700", sound: "find:d=8,o=6,b=180:c,e,g,p", screen: "on", gpio: "off", repeat: "forever", gap: "1000", stop: "connected" },
food: { label: "Your food truck order is ready", kind: "channel", id: "3", when: "any", vibration: "50,300,40,20,500", led: "100,100,100,500", sound: "order:d=8,o=5,b=180:c,e,g,4c6", screen: "on", gpio: "off", repeat: "forever", gap: "2000", stop: "button" },
vip: { label: "VIP chat", kind: "contact", id: "", when: "disconnected", vibration: "100,100,300", led: "50,300,40,20,500", sound: "vip:d=8,o=5,b=180:c,e,g,4g", screen: "on", gpio: "off", repeat: "2", gap: "500", stop: "button" }
vip: { label: "VIP chat", kind: "contact", id: "", when: "disconnected", vibration: "100,100,300", led: "50,300,40,20,500", sound: "vip:d=8,o=5,b=180:c,e,g,4g", screen: "on", gpio: "off", repeat: "2", gap: "500", stop: "button" },
channel9: { label: "Only channel 9", kind: "channel", id: "9", when: "any", vibration: "off", led: "off", sound: "ch9:d=8,o=5,b=180:c,e,g", screen: "off", gpio: "off", repeat: "1", gap: "500", stop: "button", quietOthers: "on" }
};
function pulse(text) {
if (text === "off" || text === "inherit") return [];
@@ -91,6 +92,14 @@
if (config.repeat !== "forever" && (!/^\d+$/.test(config.repeat) || Number(config.repeat) < 1 || Number(config.repeat) > 65535)) throw new Error("Repeat must be 1-65535 or forever.");
if (!/^\d+$/.test(config.gap) || Number(config.gap) < 1 || Number(config.gap) > 60000) throw new Error("Repeat gap must be 1-60000 ms.");
if (!["button", "connected", "never"].includes(config.stop)) throw new Error("Unknown stop condition.");
if (config.quietOthers === "on") {
if (config.kind !== "channel") throw new Error("Silencing other message alerts needs a channel exception.");
// This is a shared RULE, not master off: the selected channel can still
// override it. Copy and USB Save must apply the same two-rule recipe.
result.push("set notify.enabled on");
for (const name of Object.keys(OUTPUTS)) result.push("set notify." + name + " all off");
result.push("set notify.repeat all 1", "set notify.gap all 500", "set notify.stop all button");
}
for (const name of Object.keys(OUTPUTS)) {
if (!["off", "inherit"].includes(config[name])) result.push("set notify." + name + " on");
result.push("set notify." + name + " " + target + " " + config[name]);
@@ -104,6 +113,7 @@
return result;
}
function notificationText(config) {
if (config.quietOthers === "on") throw new Error("Use CLI commands or USB Save for the silent default and channel exception; a notification DM cannot configure these rules.");
const parts = [];
for (const name of ["vibration", "sound", "led", "screen"]) if (config[name] !== "inherit") parts.push(name + "=" + config[name]);
for (const name of ["repeat", "gap"]) parts.push(name + "=" + config[name]);
@@ -185,6 +195,10 @@
let client = null, supported = null, previewTimer = null, previewUrl = null, previewVersion = 0;
const config = () => Object.fromEntries(new FormData(form));
function render() {
const quiet = form.elements.quietOthers;
quiet.disabled = form.elements.kind.value !== "channel";
if (quiet.disabled) quiet.checked = false;
find("quiet-warning").hidden = !quiet.checked;
try {
output.textContent = commands(config()).join("\n"); error.textContent = "";
try { find("dm").textContent = notificationText(config()); }
@@ -193,8 +207,11 @@
catch (e) { output.textContent = ""; find("dm").textContent = ""; error.textContent = e.message; }
}
function preset(name) {
const values = { remote: "inherit", ...EXAMPLES[name] };
for (const [key, value] of Object.entries(values)) if (form.elements[key]) form.elements[key].value = value;
const values = { remote: "inherit", quietOthers: "off", ...EXAMPLES[name] };
for (const [key, value] of Object.entries(values)) if (form.elements[key]) {
if (key === "quietOthers") form.elements[key].checked = value === "on";
else form.elements[key].value = value;
}
render();
}
function stopPreview(status = "Stopped") {
@@ -296,6 +313,9 @@
const c = config(), list = commands(c);
if (!client || supported === null) throw new Error("Connect updated Companion firmware first.");
for (const name of Object.keys(OUTPUTS)) if (!["off", "inherit"].includes(c[name]) && !(supported & OUTPUTS[name])) throw new Error(name + " is unavailable on this device. Choose off or inherit.");
// Resolve/check numeric channel slots before changing the shared
// default: a missing exception channel must not leave all alerts mute.
if (c.quietOthers === "on") await run("get notify.sound " + selector(c));
for (const text of list) await run(text);
if (action === "apply-test") await run("notify.test " + selector(c));
}
+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']");
@@ -0,0 +1,101 @@
.partition-catalog {
min-width: 0;
margin: 1rem 0;
}
.partition-catalog-controls {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: 0.6rem 1rem;
}
.partition-catalog-controls label {
font-weight: 700;
}
.partition-catalog-controls select,
.partition-catalog-controls input {
max-width: 100%;
padding: 0.5rem;
border: 1px solid var(--md-default-fg-color--light);
border-radius: 0.25rem;
background: var(--md-default-bg-color);
color: var(--md-default-fg-color);
font: inherit;
}
.partition-catalog-controls select:focus-visible,
.partition-catalog-controls input:focus-visible,
.partition-catalog-table:focus-visible,
.partition-catalog-json:focus-visible {
outline: 2px solid var(--md-accent-fg-color);
outline-offset: 2px;
}
.partition-catalog-table {
max-height: 32rem;
overflow: auto;
overscroll-behavior: contain;
}
.partition-catalog [hidden] {
display: none !important;
}
.md-typeset .partition-catalog-table table {
display: table;
width: 100%;
margin: 0;
}
.partition-catalog-table thead th {
position: sticky;
top: 0;
background: var(--md-default-bg-color);
}
.partition-catalog-table tbody th {
overflow-wrap: anywhere;
}
.partition-catalog-table td small {
display: block;
color: var(--md-default-fg-color--light);
white-space: nowrap;
}
.partition-catalog-raw {
margin-top: 1rem;
}
.partition-catalog-raw summary {
cursor: pointer;
}
.partition-catalog-raw .partition-catalog-controls {
margin-top: 1rem;
}
@media (max-width: 600px) {
.md-typeset .partition-catalog-table table th,
.md-typeset .partition-catalog-table table td {
padding: 0.5rem;
}
}
.md-typeset .partition-catalog-json {
max-height: 32rem;
overflow: auto;
overscroll-behavior: contain;
border: 1px solid var(--md-default-fg-color--lightest);
border-radius: 0.25rem;
}
.md-typeset .partition-catalog-json > code {
display: block;
padding: 1rem;
white-space: pre;
overflow-wrap: normal;
word-break: normal;
}
+68
View File
@@ -38,6 +38,46 @@
margin: 1.2rem 0;
}
.firmware-picker [hidden] {
display: none !important;
}
.firmware-picker-feedback {
display: flex;
align-items: center;
justify-content: space-between;
gap: 0.6rem;
padding: 0.65rem 0.8rem;
border: 1px solid var(--picker-border);
border-left: 0.3rem solid var(--picker-accent);
border-radius: 0.4rem;
background: var(--md-default-bg-color, white);
}
.firmware-picker-feedback[data-state="single"] {
border-color: var(--picker-accent);
}
.firmware-picker-feedback p {
margin: 0.2rem 0 0;
font-size: 0.75rem;
line-height: 1.35;
}
.firmware-picker-feedback a {
flex-shrink: 0;
padding: 0.55rem 0.65rem;
border: 1px solid var(--picker-accent);
border-radius: 0.35rem;
font-size: 0.8rem;
font-weight: 700;
}
.firmware-picker-feedback a:focus-visible {
outline: 0.16rem solid var(--picker-accent);
outline-offset: 0.12rem;
}
.firmware-picker-order-note {
margin: 1rem 0 0;
color: var(--md-default-fg-color--light);
@@ -76,10 +116,26 @@
}
.firmware-picker-radio-control > legend {
display: flex;
flex-wrap: wrap;
align-items: center;
margin-bottom: 0.35rem;
padding: 0;
}
.firmware-picker-same-firmware {
padding: 0.15rem 0.45rem;
border: 1px solid var(--picker-border);
border-radius: 999px;
font-size: 0.65rem;
font-weight: 600;
background: color-mix(in srgb, var(--picker-accent) 10%, transparent);
}
.firmware-picker-refinement-hint {
font-size: 0.75rem;
}
.firmware-picker-select-control select,
.firmware-asset-browser input {
width: 100%;
@@ -190,6 +246,7 @@
.firmware-picker-result {
border-top: 0.3rem solid var(--picker-accent);
scroll-margin-top: 4rem;
}
.firmware-picker-eyebrow {
@@ -309,6 +366,17 @@
grid-template-columns: 1fr;
}
.firmware-picker-feedback {
position: sticky;
top: 0.5rem;
z-index: 2;
box-shadow: 0 0.2rem 0.5rem rgb(0 0 0 / 10%);
}
.md-typeset .firmware-picker-feedback {
top: calc(var(--md-header-height, 2.4rem) + 0.4rem);
}
.firmware-picker-facts {
grid-template-columns: 1fr;
}
@@ -7,6 +7,8 @@
.notification-builder small { font-size: .7rem; font-weight: 400; color: var(--md-default-fg-color--light); }
.notification-builder input, .notification-builder select { width: 100%; min-width: 0; border: 1px solid var(--md-default-fg-color--lighter); border-radius: 6px; padding: .6rem; background: var(--md-default-bg-color); color: var(--md-default-fg-color); font: inherit; font-weight: 400; }
.notification-builder input:focus, .notification-builder select:focus { outline: 2px solid var(--md-primary-fg-color); outline-offset: 2px; }
.notification-builder .notification-checkbox { flex-direction: row; align-items: center; margin-top: 1rem; }
.notification-builder .notification-checkbox input { width: auto; flex: 0 0 auto; padding: 0; }
.notification-builder button { border: 1px solid var(--md-primary-fg-color); border-radius: 6px; padding: .5rem .75rem; margin: .2rem; cursor: pointer; color: var(--md-primary-fg-color); font: inherit; }
.notification-builder button:hover { background: var(--md-primary-fg-color); color: var(--md-primary-bg-color); }
.notification-builder button:disabled { opacity: .5; cursor: wait; }
+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>
+80
View File
@@ -0,0 +1,80 @@
<!-- meshcore-hosted-doc-link:start -->
<p class="meshcore-hosted-doc-link"><a href="https://mikecarper.github.io/MeshCore/charge_voltage/">View this page on MeshCore Docs</a>.</p>
<!-- meshcore-hosted-doc-link:end -->
# Battery charge voltage
The `charge.voltage` commands change the battery charger's constant-voltage
target on supported boards. They are available through the shared text CLI in
the companion, repeater, room-server, sensor, and secure-chat roles. They do not change the
battery voltage display, low-voltage shutdown, battery chemistry, or charge
current.
```text
get charge.voltage
get charge.voltage.options
set charge.voltage 4.1
```
Values are in volts. Query the options first: supported targets depend on the
PMU fitted to the board. The firmware rejects values that the detected PMU
cannot represent; it does not round them to a supported target.
On T-Beam boards, a successful change is saved in NVS and reapplied after a
restart. The firmware reads the PMU setting back before reporting success.
Boards without a saved setting retain their existing startup behavior: the full
PMU driver sets 4.2 V, while the compact classic T-Beam observer driver leaves
the charger setting untouched. A failed hardware write, readback, or preference
save is reported as an error.
If the saved target cannot be read, is invalid, or cannot be restored, startup
does not overwrite it with a default target. `get charge.voltage` reports the
live charger voltage with `(boot restore failed)`. A successful saved change
clears that warning. This cannot guarantee restoration while the PMU or storage
is failing, but it avoids silently resetting a saved lower target to 4.2 V.
## Supported boards
| Board family | Detected PMU | Charge targets (V) |
| --- | --- | --- |
| LilyGo T-Beam SX1262 or SX1276 | AXP192 | 4.10, 4.15, 4.20, 4.36 |
| LilyGo T-Beam SX1262 or SX1276 | AXP2101 | 4.00, 4.10, 4.20, 4.35, 4.40 |
| LilyGo T-Beam S3 Supreme SX1262 | AXP2101 | 4.00, 4.10, 4.20, 4.35, 4.40 |
Targets above 4.2 V require batteries rated for the selected charge voltage.
The classic T-Beam driver detects the fitted AXP192 or AXP2101. Radio type does
not determine which PMU is present. T-Beam 1W uses a different power design and
does not support this setting.
Neither AXP192 nor AXP2101 supports a 3.65 V target. The lowest supported target
is 4.10 V for AXP192 and 4.00 V for AXP2101. These PMUs cannot be configured as
LiFePO4 chargers by changing this setting.
## Heltec Mesh Solar
Mesh Solar reports `charge.voltage` as unsupported because its CN3795 charger's
constant-voltage target is set by hardware. Heltec supplies separate Li-ion
(`I`) and LiFePO4 (`F`) hardware versions, with 4.2 V/cell and 3.6 V/cell charging
respectively. Cell-count soldering and switches must also match the battery
configuration. A software battery-type setting does not convert one hardware
version into the other. See [Heltec's usage guide](https://wiki.heltec.org/docs/devices/open-source-hardware/nrf52840-series/mesh-solar/usage-guide)
and [MeshSolar datasheet, sections 4.1 and 4.4](https://resource.heltec.cn/download/MeshSolar/datasheet/MeshSolar_V1.0.0.pdf).
The BQ4050 on this board is a battery gauge and protection controller. Its
`ChargingVoltage()` is a requested voltage for a compatible smart charger. The
pinned Mesh Solar library calls that parameter `eoc`, but writing it does not
change the CN3795's voltage-feedback circuit. Valid charge termination also
requires the charging current to taper, so lowering `eoc` alone does not create
a reliable lower-voltage charge cutoff. Charge-overvoltage protection is a
separate safeguard, not the charger's normal constant-voltage target. See
[TI's BQ4050 technical reference manual, sections 4.5, 4.6, and 4.11](https://www.ti.com/lit/pdf/SLUUAQ3).
These commands do not write Mesh Solar's BQ4050 data flash or change its battery
type, protection limits, cell count, or charge-FET configuration.
## Battery profiles
The nRF52 `battery.profile`, `battery.full`, and `battery.empty` settings control
voltage-based reporting and protection. They do not change any charger target.
See [Battery profile commands](cli_commands.md#configure-nrf52-battery-protection).
+117 -24
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:
@@ -28,8 +32,8 @@ and partition policy:
| Selection | Behavior |
| --- | --- |
| `auto` | For one explicit target, pass 1 builds the complete supported LoRa-OTA-capable recipe. ESP32 boards with a qualified expanded profile use it; other standalone repeaters and room servers attempt the complete recipe in their current application region. Other roles keep every capability declared by their resolved PlatformIO recipe, including LoRa OTA, and fail instead of silently removing one that does not fit. A measured repeater or room-server flash/partition overflow starts the standard `no_external_sensors` LoRa OTA pass. Internal-flash nRF52 repeaters and room servers publish that reduced pass even when the complete image fits, because the smaller running image leaves more room to stage a delta; matched QSPI/SD roles do not need the redundant artifact. Compiler errors and missing-capability checks never trigger or conceal a reduced build. Canonical bulk commands keep their established standard partition contract. |
| `standard` | Immediately uses the deployed/portable partition contract and its documented reductions. This is useful when the operator already knows the expanded or complete image is unsuitable. |
| `auto` | nRF52 repeater, room-server, and sensor builds publish the required pair: Full supported sensors + LoRa OTA and Reduced sensors + LoRa OTA. Both must pass flash, RAM, packaged-application OTA, and layout checks; a reduced image cannot substitute for a failing full image. The same policy applies to canonical bulk/release builds and external-QSPI/SD recipes. ESP32 boards with a qualified expanded profile use it; other standalone ESP32 repeaters and room servers attempt the complete recipe in their current application region, with a measured flash/partition-overflow fallback to their reduced LoRa OTA recipe. Other roles keep every capability declared by their resolved PlatformIO recipe and fail instead of silently removing one that does not fit. Compiler errors and missing-capability checks never trigger or conceal a reduced build. |
| `standard` | Preserves the deployed/portable partition contract and documented reductions. nRF52 infrastructure still publishes both OTA sensor profiles in that same exact layout; this is not an override to remove OTA or skip the full profile. |
| `full` | Requires a qualified ESP32 expanded-partition target (or an explicitly named Full Companion). Install a matching merged image when this changes the partition table. |
Successful builds also emit
@@ -49,24 +53,33 @@ The direct observer and Full-matrix paths use the same `full-usb-wifi-ota`
artifact and runtime output controls. The ordinary bulk release publishes one
Full OTA identity per exact ESP32 board and role. Audited plain targets keep
their identity while compiling the combined observer/ESP-NOW sources; G2 keeps
its deployed observer identity. T-Beam SX1262/SX1276 and TLora V2 choose the
observer in the ordinary release. Their plain Full recipes remain available
its deployed observer identity. T-Beam SX1262/SX1276 choose the observer in
the ordinary release. TLora V2 retains two Full images because UART, MQTT
and ESP-NOW together exceed its internal RAM budget: the normal repeater
provides UART plus ESP-NOW, and the observer provides MQTT plus ESP-NOW.
T-Beam plain Full recipes remain available
as explicit alternatives with different routing capacities. Build one with
`bash build.sh build-firmware <target> --full-exact`, then deliberately install
it over LoRa with `ota pull <MID8> flash` and `ota install` on compatible
hardware and partition layouts.
Standard and reduced OTA images, including images that fit the 1.25 MiB
portable application slot, remain available and remain in the standard matrix
pass. Select `--build-profile standard` to retain the original target and
partition contract. Moving from a plain target to its MQTT sibling changes the
LoRa OTA target identity; a partition layout change needs its matching
migration step.
For the normal-role partition migrations, the matrix also publishes a
canonical Full image under the normal target identity. Install its matching
merged image over USB once; mOTA rejects the incompatible partition signature
until that migration is complete, then later Full updates use the normal
target ID. The portable image remains available while deployed nodes move.
The ordinary ESP32 release publishes Full images. Standard and reduced OTA
recipes, including those that fit a 1.25 MiB portable application slot, remain
available through explicit builds for recovery and migration. Select
`--build-profile standard` to retain an exact target's original partition
contract. They are omitted from the ordinary release and interactive board
menu. That menu also omits transport/default aliases covered by Full and
development utilities; it retains distinct hardware, roles, storage, and
measured capacity exceptions. Exact legacy names remain accepted by
`build-firmware` and `build-matching-firmwares`.
For the normal-role partition migrations, the release publishes a canonical
Full image under the normal target identity. Use its qualified wireless
expansion package when one exists, or install its matching merged image over
USB. mOTA rejects an incompatible partition signature until that migration
is complete, then later Full updates use the normal target ID. Moving from a
plain target to a differently named sibling can also change the LoRa OTA
identity; partition expansion does not by itself authorize that transition.
Legacy supported ESP32 Wi-Fi repeaters can use the
[Wi-Fi partition migration](esp32_wifi_partition_migration.md) bridge to
@@ -104,15 +117,15 @@ getters; relaying a command over LoRa keeps its remote restrictions.
| Build/profile | Command availability |
|---|---|
| Standard non-MQTT repeater or room server | Keeps the normal role CLI and, where USB is a safe plaintext console, embeds debug/packet logging behind persistent `get/set usb.logging`. The explicitly selected portable policy can omit WebConfig and browser WiFi OTA, so those commands are unavailable and the omission is recorded in the capability manifest. |
| Standard non-MQTT repeater or room server | Keeps the normal role CLI and, where USB is a safe plaintext console, embeds packet logging behind persistent `get/set usb.logging` and separately saved `get/set usb.debug` verbosity. USB debug defaults off and requires compiled diagnostic support. The explicitly selected portable policy can omit WebConfig and browser WiFi OTA, so those commands are unavailable and the omission is recorded in the capability manifest. |
| Legacy standard logging | No longer emitted separately. Its behavior is compiled into the ordinary artifact. Size-constrained STM32 targets embed packet logging without verbose `MESH_DEBUG`. |
| LoRa-OTA (`-ota-`) | LoRa OTA adds the `ota ...` commands; it does not otherwise reduce the role CLI. ESP32 `no_external_sensors` artifacts retain the compact browser WiFi uploader, the complete CLI, and up to 254 neighbors, subject to recorded internal-DRAM reductions. |
| Internal-flash nRF52 repeater/room-server auto pair | `full-ota` retains the board's external-sensor drivers; `reduced-ota` omits the declared optional sensors to leave additional internal-flash staging room. RAK3401 and RAK4631 reduced builds retain INA219, INA226, INA260, and INA3221 I2C voltage/current monitors at a measured cost below 5 KiB. Both artifacts carry the same stable OTA target identity and are checked for `ota ...` and `retry.preset`; RAK artifacts also verify the retained monitor drivers. |
| nRF52 repeater/room-server/sensor release pair | `full-ota` retains the complete supported sensor recipe; `reduced-ota` omits declared optional sensor drivers. Both include verified LoRa OTA, use the same logical target identity and exact storage/layout contract within the pair, and retain the role's normal commands. Repeaters/room servers also verify `retry.preset`. RAK3401 and RAK4631 reduced builds retain GPS where compatible and INA219/INA226/INA260/INA3221 I2C voltage/current monitors. External QSPI/SD targets also publish both choices. Companion/Terminal Chat and KISS roles do not enter this policy. |
| ESP32 MQTT observer or ESP-NOW bridge | Always uses the expanded FULL partition profile. The build never substitutes a reduced CLI to fit the legacy application slot. |
| FULL ESP32 USB + WiFi | Uses the matching MQTT target with packet logging on, verbose debug off, and the complete command surface supported by that role and hardware. `get/set logging.output off\|usb\|wifi\|both` selects and persists the active output paths. |
| FULL ESP32 logging fallback | Uses the matching non-MQTT target only when no WiFi MQTT sibling exists, with debug and packet logging enabled and the complete command surface supported by that role and hardware. Its persistent USB gate also covers output-off operation, avoiding a second FULL ESP-NOW image. |
| nRF52 dual-CDC Full Companion | Fresh installs expose only interface `00`; it starts as an ASCII terminal and automatically hands a complete `<` frame to framed Companion. The same interface also carries exclusive serial mOTA traffic. Enabling logging and rebooting adds interface `02` for plaintext logs. BLE and source-only LoRa OTA remain available. `get/set usb.logging` persistently controls whether the logging interface is present. |
| ESP32 single-TTY Full Companion | Every ESP32 Full image starts with the ASCII terminal on its one USB TTY and automatically hands a complete `<` frame to framed Companion. On 1.17.1.5, run `set powersaving off` first. `set usb.logging on` switches that TTY to an input-capable plaintext logging terminal and makes framed Companion unavailable on USB; `set usb.logging off` stops logging but leaves the TTY in normal ASCII mode. The terminal stop token or a valid incoming framed probe then performs the ordinary switch to Binary Companion. A saved logging-on setting starts directly in that logging terminal and disables automatic frame detection. BLE, WiFi, and source-only LoRa OTA remain available. ESP32 Full uses Arduino-ESP32 2.x where supported; RC32 and ESP32-C6 keep their board-required Arduino 3.x platform but still expose only one TTY. |
| FULL ESP32 USB + WiFi | Uses the matching MQTT target with packet logging, USB debug defaulting off, and the complete command surface supported by that role and hardware. Debug verbosity can be enabled only for diagnostics compiled into that image. `get/set logging.output off\|usb\|wifi\|both` selects and persists the active output paths; `get/set usb.debug` separately saves USB verbosity. |
| FULL ESP32 logging fallback | Uses the matching non-MQTT target when no compatible MQTT sibling exists, preserving its hardware and role CLI. Full LoRa repeaters also include runtime ESP-NOW with `set espnow.enabled on\|off` and `get espnow.running`; newly combined modes default off. Dedicated images remain only for measured capacity exceptions. USB debug defaults off; `get/set usb.debug` selects verbose output independently of packet capture. |
| nRF52 dual-CDC Full Companion | Fresh installs expose only interface `00`; it starts as an ASCII terminal and automatically hands a complete `<` frame to framed Companion. The same interface also carries exclusive serial mOTA traffic. `set usb.logging on reboot` adds output-only interface `02` for ASCII RAW/RX/TX records and optional debug. Alternatively, `set usb.logging stream reboot` keeps CLI replies and packet records on interface `00` for unmodified one-port serial bridges; Binary Companion and serial mOTA require logging off on that port. BLE and source-only LoRa OTA remain available. `get/set usb.logging` persistently controls whether the logging interface is present; `get/set usb.debug` changes verbosity without a reboot. Send CLI/control queries to interface `00`, not the log endpoint. |
| ESP32 single-TTY Full Companion | Every ESP32 Full image starts with the ASCII terminal on its one USB TTY and automatically hands a complete `<` frame to framed Companion. On 1.17.1.5, run `set powersaving off` first. `set usb.logging on` switches that TTY to an input-capable plaintext RAW/RX/TX logging terminal and makes framed Companion unavailable on USB; `set usb.logging off` stops logging but leaves the TTY in normal ASCII mode. `get/set usb.debug` separately controls saved verbose diagnostics, defaulting off. The terminal stop token or a valid incoming framed probe then performs the ordinary switch to Binary Companion. A saved logging-on setting starts directly in that logging terminal and disables automatic frame detection. BLE, WiFi, and source-only LoRa OTA remain available. ESP32 Full uses Arduino-ESP32 2.x where supported; RC32 and ESP32-C6 keep their board-required Arduino 3.x platform but still expose only one TTY. |
| `no_external_sensors` | Trims selected optional environmental/ranging drivers and their settings; it does not remove generic I2C, core repeater discovery, routing, or runtime RS-232 commands. RAK3401 and RAK4631 profiles retain the four common INA I2C voltage/current monitors. GPS-preserving RAK nRF52 OTA profiles retain their GPS commands and provider; RAK4631 defaults the bridge to UART 2 because RAK12501/L76K GPS uses UART 1. Legacy target suffixes remain stable for OTA identity compatibility. |
The four retained INA drivers are entries in the optional environmental-sensor
@@ -129,12 +142,25 @@ both devices on one bus, leave RAK12500 at `0x42`, strap INA3221 A0 to SCL for
`logging`, `OTA`, and `FULL` describe independent build features in historical
filenames. Current standard artifacts use no `-logging-` infix because their
USB logging is runtime controlled.
USB packet capture and debug verbosity share the canonical artifact, not a new
build/profile name. `usb.logging` remains the master; verbose diagnostics need
both that switch and saved `usb.debug` on. Fresh and legacy preferences default
debug to off. Setting debug on cannot restore code compiled out of an image.
## Canonical bulk-build policy
Bulk and release-matrix commands omit legacy names whose behavior is already
available from a canonical image:
- Every canonical nRF52 repeater, room-server, and sensor recipe emits two
separately named, qualified OTA artifacts: `full-ota` and `reduced-ota`.
The capability sidecar records `sensor.profile.full` or
`sensor.profile.reduced` so the picker can identify the actual profile.
Boards without optional sensor drivers still receive the two named choices;
the size saving may be small or zero. A missing or unqualified member blocks
publication of an incomplete pair. This does not make an unsupported
bootloader or a different physical storage layout compatible.
- Companion `_ps` names are replaced by the ordinary Companion image plus the
persisted `set powersaving on|off` setting.
- Companion `_femoff` names are replaced by the matching controllable-FEM
@@ -172,15 +198,82 @@ available from a canonical image:
releases. Wio-E5 remains separate because its normal image has only 916
bytes free, while the combined image exceeds the fixed 240 KiB application
partition by 2,192 bytes.
`MKE_s3_repeater` includes the bridge on UART 2, with RX on GPIO16 and TX on
GPIO17. Fresh installs leave it disabled at 115200 baud; upgrades from normal
repeaters keep it disabled, while existing saved RS-232 settings are retained.
Use `set bridge.baud 115200` and `set bridge.enabled on` to enable it, or
`set bridge.enabled off` to stop it. `set bridge.uart 2` selects its fixed
UART; other UART numbers are rejected. Successful changes take effect
immediately and persist across reboots. The historical
`MKE_s3_repeater_bridge_rs232` name remains directly buildable with its
bridge-on default for compatibility but is omitted from bulk releases.
The same MKE image includes ESP-NOW, controlled independently with
`set espnow.enabled on|off` and `get espnow.running`. Both bridges can run
together and share `bridge.source` and `bridge.delay`; `bridge.channel`,
`bridge.secret`, and `bridge.format` apply only to ESP-NOW. Its first boot
after an upgrade leaves ESP-NOW off, including upgrades from the dedicated
ESP-NOW image: run `set espnow.enabled on` to enable it. Subsequent reboots
retain the saved setting. The historical `MKE_s3_repeater_bridge_espnow`
target remains directly buildable with its bridge-on default but is omitted
from bulk releases. Browser OTA pauses ESP-NOW while leaving RS-232 running;
`stop ota` restores ESP-NOW when its saved setting is on.
- Full Heltec V3, Wireless Stick Lite V3 and RAK3112 MQTT repeaters retain
RS-232 alongside MQTT and ESP-NOW. They use UART2 with RX on GPIO5 and
TX on GPIO6. Use `set rs232.enabled on|off` and
`get rs232.running` independently of `mqtt.enabled` and `espnow.enabled`.
`bridge.baud` and `bridge.uart` configure the UART. Old MQTT preferences
do not enable this newly added UART; its saved intent needs a validated
UART profile marker. Historical `bridge.enabled` keeps its ESP-NOW meaning
in MQTT images and its UART meaning in non-MQTT images. Full qualification
verifies the linked UART driver even when compiling observer sources for a
normal repeater, and resumed release selection rejects Full images without
that proof.
- LilyGo T-LoRa V2.1-1.6 retains a normal Full UART/ESP-NOW repeater and a
separate Full MQTT/ESP-NOW observer. Real triple-transport qualification
fell 6,496 bytes short of the required internal heap, including the UART
driver allowance. The normal UART remains on RX GPIO34 and TX GPIO25;
use `bridge.enabled` or `rs232.enabled` to control it. Both Full identities
remain in bulk releases instead of reducing routing capacity or RAM margins.
- MeshTower V2 repeaters use
`Heltec_tower_v2_sdcard_repeater_lora_ota_no_external_sensors` as the one
canonical target. Its SD OTA target `0A9DBBF0`, hardware ID
`Heltec_tower_v2`, layout, and filenames stay unchanged, with separate
required Full and Reduced sensor profiles. Both
`Heltec_tower_v2_repeater` and
`Heltec_tower_v2_repeater_lora_ota_no_external_sensors` resolve to this primary
in `build-firmware` and matching builds. Old internal-only bootloader/OTA
identities require one local USB/BLE DFU or SWD migration; the build aliases
do not make the new image a same-target LoRa upgrade for those installed nodes.
Existing SD users need no configuration change. The application selects SD
first and falls back automatically for a missing or unmountable card only
when the matching published combined OTAFIX profile declares exact
privileged storage `09` and optional application storage `02` in its separate
`MOTASTOR` record. Internal fallback accepts flash-staged signed application
in-place deltas from trusted signers that fit its smaller workspace;
SD-generated large deltas may be rejected. Full images, bootloader updates,
and archive capture remain SD-only. Storage stays pinned through a transfer:
use `ota cancel` before
selecting a different backend. The application does not use an `APPHYBRID`
retained-RAM staging arena. See [MeshTower SD OTA](ota_meshtower_v2_sdcard.md).
The old aliases still work with `build-firmware` and
`build-matching-firmwares`. Dedicated LoRa OTA repeater images are not
collapsed; they retain their exact storage, bootloader, role, and target
identity contracts. Companion boards keep transport-specific canonical images
only when no exact Full recipe has passed the combined flash/RAM qualification.
collapsed outside the MeshTower consolidation above; they retain their exact
storage, bootloader, role, and target identity contracts. Companion boards
keep transport-specific canonical images only when no exact Full recipe has
passed the combined flash/RAM qualification.
ESP32 deliberately uses one TTY: Binary Companion and plaintext USB logging
are mutually exclusive there. nRF52 retains its optional second CDC port.
The [OTAFIX 2.4.11 release](https://github.com/mikecarper/Adafruit_nRF52_Bootloader_OTAFIX/releases/tag/v0.11.0-OTAFIX2.4.11)
is the current bootloader reference, not an all-board qualification claim.
Consult its exact-board manifest and unavailable-profile reasons. Internal
application storage accepts exact-base in-place deltas that fit the workspace;
full application packages need the matching external NOR or microSD profile.
The retired internal-only MeshTower V2 bootloader requires one local
installation of the combined SD/internal profile. Its changed identity/layout
must not be crossed by an ordinary LoRa application or bootloader update.
## Complete CLI policy
The compact ESP32 CLI has been removed. `retry.preset` is a checked invariant
+46 -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`
@@ -38,6 +51,14 @@ Build columns mean:
where there is no MQTT sibling. It has the complete parser and a saved
USB-logging on/off gate. ESP-NOW bridges use this profile.
USB packet output and verbose diagnostics are separate saved controls in
current source. Where `usb.logging` is available, `get/set usb.debug on|off`
selects debug intent without changing packet capture or adding a build variant.
Debug defaults off for fresh and older preference images; effective output
requires `usb.logging` and `usb.debug` on plus compiled diagnostic support.
See [live USB logging](cli_commands.md#control-live-usb-logging) for transport
restrictions and Full Companion RAW output.
The firmware-configured INA3221 and RAK12500 addresses are both `0x42`; they
cannot coexist on one bus at those addresses. Leave RAK12500 at `0x42`, strap
INA3221 A0 to SCL for `0x43`, and use a build with
@@ -62,6 +83,12 @@ fix, no WiFi connection, an inactive bridge, or an nRF52 bootloader without
## Companion framed CLI
GPS-equipped Sensor builds also provide the local-only
[dog tracker settings](sensor_tracker.md): `get tracker`,
`get/set tracker.owner`, `get/set tracker.mode`, `get/set tracker.interval`,
`get/set tracker.lost.interval`, `get/set tracker.path`, and `tracker check`.
They are excluded from compact STM32 defaults. LoRa CLI cannot change them.
Companion builds are not represented by the role/profile matrices below. Their
local terminal and command `0x42` (`CMD_RUN_CLI_COMMAND`) expose these settings
over the normal binary USB, BLE, or TCP connection:
@@ -71,8 +98,10 @@ over the normal binary USB, BLE, or TCP connection:
| `stats-core`, `stats-radio`, `stats-radio-diag`, `stats-packets` | Local terminal and binary command `0x42` |
| `erase` | Local terminal and binary command `0x42`; reboot after erasing stored settings |
| `set freq <MHz>` | Local terminal, binary command `0x42`, and authorized LoRa CLI; reboot to apply |
| [`get public.key`](cli_commands.md#view-this-nodes-public-key) | Companion text terminal and binary command `0x42`; read-only, no private-key export feature required |
| `get prv.key` | Local terminal and binary command `0x42`; requires private key export enabled |
| `get password` | Reports that Companion has no admin password; infrastructure returns its password locally |
| [`get/set lost.reply`](companion_lost_reply.md) | Local Companion CLI and framed command `0x42` when `MESH_ENABLE_LOST_REPLY` is enabled; saved `off`, `no` or `yes` private-message auto response |
| `get wifi.pwd`, `get mqttN.password`, `get mqttN.token` | Local terminal and binary command `0x42`; corresponding WiFi/MQTT feature required |
| [`board`](cli_commands.md#show-the-hardware-name) | Every Companion text terminal and command `0x42` |
| [`version`](cli_commands.md#get-the-version) | Every Companion text terminal and protocol-v14 command `0x42`; returns the untruncated build identity |
@@ -82,6 +111,7 @@ over the normal binary USB, BLE, or TCP connection:
| [`get/set radio.fem.rxgain`](cli_commands.md#view-or-change-the-lora-fem-receive-path-gain-state-on-supported-boards) | Companion on a board with controllable LoRa FEM LNA |
| [`get/set wifi.powersave`](cli_commands.md#browser-configuration-portal-esp32-repeater-and-room-server) | ESP32 WiFi Companion; active transport constraints still apply |
| [`get/set bluetooth.name`](cli_commands.md#view-or-change-the-independent-bluetooth-name-companion) | Companion firmware |
| [`set pin <0-999999>`](cli_commands.md#set-the-bluetooth-pairing-pin-companion) | Companion text terminal, command `0x42`, and authorized LoRa CLI; saved, reboot to apply; affects Bluetooth-capable builds |
| [`get/set bluetooth.mac`](cli_commands.md#view-or-change-the-bluetooth-address-ble-companion) | Every Companion build with Bluetooth |
| [`get/set display.touch`](cli_commands.md#show-touchscreen-touch-areas) | Touchscreen Companions using the shared UI; text terminal and framed CLI; off after reboot |
| [`get/set display.inbox`](cli_commands.md#set-companion-inbox-behavior) | Shared Companion UI; `history` (default), `pending`, or `unread`; saved across reboot |
@@ -124,6 +154,9 @@ should use command `0x42`. See [Companion radio binary protocol](companion_proto
| Logging | [`log start`; `log stop`; `log erase`](cli_commands.md#logging) | Storage-backed roles retain data; other roles can return empty data | Yes | Yes | Yes |
| Logging | [`log`](cli_commands.md#print-the-captured-log-to-the-serial-terminal) | Local connection | Local | Local | Local |
| Logging | [`get/set usb.logging`; unified FULL `get/set logging.output`](cli_commands.md#control-live-usb-logging) | Ordinary safe-USB artifacts; CommonCLI USB gate is persistent; unified ESP32 FULL selects off/USB/WiFi/both; nRF52 Full Companion can add a reboot-controlled second CDC | Yes | Yes | No |
| Logging | [`get/set usb.debug`](cli_commands.md#control-live-usb-logging) | Same USB-control availability; saved intent defaults off; actual diagnostics need compiled support and USB master on | Yes | Yes | No |
| Logging | [`get/set usb.watchdog off\|on\|auto`](usb_logging_watchdog.md) | Current source; recovery requires native USB, live logging, and durable storage; Auto qualifies from USB stats polling | Yes | Yes | Feature |
| Logging | [`get usb.watchdog.last`](usb_logging_watchdog.md#last-event-review) | Current source; latest USB watchdog event with advisory timestamp, uptime, action, reasons, and durability; native USB required | Yes | Yes | Feature |
| Radio | [`get radio`; `set radio ...`](cli_commands.md#view-or-change-this-nodes-radio-parameters) | All text CLI roles | Yes | Yes | Yes |
| Radio | [`get tx`; `set tx <dbm>`](cli_commands.md#view-or-change-this-nodes-transmit-power) | Board TX-power limits apply | Yes | Yes | Yes |
| Radio | [`tempradio ...`; `normalradio`](cli_commands.md#change-the-radio-parameters-for-a-set-duration) | Full parser | Yes | Yes | Yes |
@@ -153,6 +186,7 @@ should use command `0x42`. See [Companion radio binary protocol](companion_proto
| System | [`get/set reboot.interval`](#reboot-interval) | Full parser | Yes | Yes | Yes |
| Clock sync | [`get/set clock.sync.*`; `clock.sync.mesh now`](cli_commands.md#estimate-and-correct-infrastructure-node-time-after-startup) | Repeater, sensor, and room server; `clock.sync.internet` needs MQTT repeater | Yes | Yes | Yes |
| Routing | [`get/set repeat`](cli_commands.md#view-or-change-this-nodes-repeat-flag) | Forwarding-capable role | Yes | Yes | Yes |
| Routing | [`get/set repeat.trace`](cli_commands.md#allow-routed-traces-while-repeating-is-disabled-repeater-only) | Repeater only; saved repeat-off trace exception, default off | Yes | Yes | Yes |
| Routing | [`get/set path.hash.mode`](cli_commands.md#view-or-change-this-nodes-advert-path-hash-size) | Role that supports path-hash selection | Yes | Yes | Yes |
| Routing | [`get/set loop.detect`](cli_commands.md#view-or-change-this-nodes-loop-detection) | Repeater | Yes | Yes | Yes |
| Routing | [`get/set txdelay`](cli_commands.md#view-or-change-the-retransmit-delay-factor-for-flood-traffic) | All text CLI roles, including bridges | Yes | Yes | Yes |
@@ -175,11 +209,12 @@ should use command `0x42`. See [Companion radio binary protocol](companion_proto
| Routing | [`get/set/del flood.channel.scope.require*`](cli_commands.md#require-valid-incoming-scopes-only-on-selected-channels) | Repeater | Yes | Yes | Yes |
| Routing | [`get/set/del flood.rule*`; `get/set/del flood.filter*`; `get/set/del flood.filter.blacklist*`](cli_commands.md#change-persistent-flood-rules-in-the-field) | Repeater; `flood.rule`/`flood.filter` also on FULL ESP32 room server (no blacklist) | Yes | Yes | Yes |
| Routing | [`get/set/del flood.moderation*`](cli_commands.md#moderate-flood-group-text-by-channel-sender-and-source-path) | Repeater | Yes | Yes | Yes |
| Routing | [`get/set outpath`](halo_keymind_settings.md#direct-path-overrides) | Repeater remote-client context | Yes | Yes | Yes |
| Routing | [`get/set altpath`](halo_keymind_settings.md#direct-path-overrides) | Repeater remote-client context | Yes | Yes | Yes |
| Routing | [`get/set outpath`](halo_keymind_settings.md#direct-path-overrides) | Repeater or [room user](room_services.md#your-own-return-routes), own remote-client context | Yes | Yes | Yes |
| Routing | [`get/set altpath`](halo_keymind_settings.md#direct-path-overrides) | Repeater or [room user](room_services.md#your-own-return-routes), own remote-client context | Yes | Yes | Yes |
| ACL | [`setperm <pubkey> <permissions>`](cli_commands.md#add-update-or-remove-permissions-for-a-companion) | Repeater, room server, or sensor | Yes | Yes | Yes |
| ACL | [`get acl [page]`](cli_commands.md#view-the-current-acl) | Repeater, room server, or sensor; admin over LoRa | Yes | Yes | Yes |
| ACL | [`get/set allow.read.only`](cli_commands.md#view-or-change-this-room-servers-read-only-flag) | Room server | Yes | Yes | No |
| Room | [`get/set topic`](cli_commands.md#view-or-change-the-room-topic) | Room server | Yes | Yes | No |
| Regions | [`region load`](cli_commands.md#bulk-load-region-lists); [`region save`](cli_commands.md#save-any-changes-to-regions-made-since-reboot) | Role with region storage | Yes | Yes | Yes |
| Regions | [`region allowf`](cli_commands.md#allow-a-region); [`region denyf`](cli_commands.md#block-a-region) | Role with region storage | Yes | Yes | Yes |
| Regions | [`region get`](cli_commands.md#show-information-for-a-region); [`region list`](cli_commands.md#view-all-regions); [`region`](cli_commands.md#dump-all-defined-regions-and-flood-permissions) | Role with region storage | Yes | Yes | Yes |
@@ -195,6 +230,7 @@ should use command `0x42`. See [Companion radio binary protocol](companion_proto
| Retry | [`get/set/clear recent.repeater`; `get recent.repeaters`](cli_commands.md#view-seed-or-clear-the-recent-repeater-table) | Repeater | Yes | Yes | Yes |
| GPS | [`get gps`; `set gps on/off`](cli_commands.md#view-or-change-gps-state) | Compiled GPS | Feature | Feature | Feature |
| GPS | [`gps sync`](cli_commands.md#sync-this-nodes-clock-with-gps-time) | Compiled GPS | Feature | Feature | Feature |
| GPS | [`get/set gps.sync.interval`](cli_commands.md#set-the-automatic-gps-clock-sync-interval) | Compiled GPS and provider; 1-336 hours | Feature | Feature | Feature |
| GPS | [`gps setloc`](cli_commands.md#set-this-nodes-location-based-on-the-gps-coordinates) | Compiled GPS | Feature | Feature | Feature |
| GPS | [`gps advert [none/share/prefs]`](cli_commands.md#view-or-change-the-gps-advert-policy) | Compiled GPS | Feature | Feature | Feature |
| Sensors | [`get/set telemetry.access`](cli_commands.md#view-or-change-telemetry-access-mode) | Sensor-capable role | Feature | Feature | Limited |
@@ -293,6 +329,9 @@ should use command `0x42`. See [Companion radio binary protocol](companion_proto
| Logging | [`log start`; `log stop`; `log erase`](cli_commands.md#logging) | Storage-backed roles retain data | Yes | Yes | Yes | Yes | Yes |
| Logging | [`log`](cli_commands.md#print-the-captured-log-to-the-serial-terminal) | Local connection | Local | Local | Local | Local | Local |
| Logging | [`get/set usb.logging`; unified FULL `get/set logging.output`](cli_commands.md#control-live-usb-logging) | Ordinary safe-USB artifacts; CommonCLI USB gate is persistent; unified ESP32 FULL selects off/USB/WiFi/both; every ESP32 Full Companion uses an input-capable single-TTY logging terminal with framed USB Companion disabled while logging | Yes | Yes | No | No | Yes |
| Logging | [`get/set usb.debug`](cli_commands.md#control-live-usb-logging) | Same USB-control availability; saved intent defaults off; actual diagnostics need compiled support and USB master on | Yes | Yes | No | No | Yes |
| Logging | [`get/set usb.watchdog off\|on\|auto`](usb_logging_watchdog.md) | Current source; recovery requires native USB, live logging, and durable storage; Auto qualifies from USB stats polling | Yes | Yes | Feature | Yes | Yes |
| Logging | [`get usb.watchdog.last`](usb_logging_watchdog.md#last-event-review) | Current source; latest USB watchdog event with advisory timestamp, uptime, action, reasons, and durability; native USB required | Yes | Yes | Feature | Yes | Yes |
| Radio | [`get radio`; `set radio ...`](cli_commands.md#view-or-change-this-nodes-radio-parameters) | All text CLI roles | Yes | Yes | Yes | Yes | Yes |
| Radio | [`get tx`; `set tx <dbm>`](cli_commands.md#view-or-change-this-nodes-transmit-power) | Board TX-power limits apply | Yes | Yes | Yes | Yes | Yes |
| Radio | [`tempradio ...`; `normalradio`](cli_commands.md#change-the-radio-parameters-for-a-set-duration) | Full parser | Yes | Yes | Yes | Yes | Yes |
@@ -323,6 +362,7 @@ should use command `0x42`. See [Companion radio binary protocol](companion_proto
| System | [`get/set reboot.interval`](#reboot-interval) | Full parser | Yes | Yes | Yes | Yes | Yes |
| Clock sync | [`get/set clock.sync.*`; `clock.sync.mesh now`](cli_commands.md#estimate-and-correct-infrastructure-node-time-after-startup) | Repeater, sensor, and room server; `clock.sync.internet` needs MQTT repeater | Yes | Yes | Yes | Yes | Yes |
| Routing | [`get/set repeat`](cli_commands.md#view-or-change-this-nodes-repeat-flag) | Forwarding-capable role | Yes | Yes | Yes | Yes | Yes |
| Routing | [`get/set repeat.trace`](cli_commands.md#allow-routed-traces-while-repeating-is-disabled-repeater-only) | Repeater only; saved repeat-off trace exception, default off | Yes | Yes | Yes | Yes | Yes |
| Routing | [`get/set path.hash.mode`](cli_commands.md#view-or-change-this-nodes-advert-path-hash-size) | Full parser | Yes | Yes | Yes | Yes | Yes |
| Routing | [`get/set loop.detect`](cli_commands.md#view-or-change-this-nodes-loop-detection) | Repeater, full common parser | Yes | Yes | Yes | Yes | Yes |
| Routing | [`get/set txdelay`](cli_commands.md#view-or-change-the-retransmit-delay-factor-for-flood-traffic) | All text CLI roles, including bridges | Yes | Yes | Yes | Yes | Yes |
@@ -345,11 +385,12 @@ should use command `0x42`. See [Companion radio binary protocol](companion_proto
| Routing | [`get/set/del flood.channel.scope.require*`](cli_commands.md#require-valid-incoming-scopes-only-on-selected-channels) | Repeater role handler | Yes | Yes | Yes | Yes | Yes |
| Routing | [`get/set/del flood.rule*`; `get/set/del flood.filter*`; `get/set/del flood.filter.blacklist*`](cli_commands.md#change-persistent-flood-rules-in-the-field) | Repeater role handler; `flood.rule`/`flood.filter` also on FULL ESP32 room server (no blacklist) | Yes | Yes | Yes | Yes | Yes |
| Routing | [`get/set/del flood.moderation*`](cli_commands.md#moderate-flood-group-text-by-channel-sender-and-source-path) | Repeater role handler | Yes | Yes | Yes | Yes | Yes |
| Routing | [`get/set outpath`](halo_keymind_settings.md#direct-path-overrides) | Repeater remote-client context | Yes | Yes | Yes | Yes | Yes |
| Routing | [`get/set altpath`](halo_keymind_settings.md#direct-path-overrides) | Repeater remote-client context | Yes | Yes | Yes | Yes | Yes |
| Routing | [`get/set outpath`](halo_keymind_settings.md#direct-path-overrides) | Repeater or [room user](room_services.md#your-own-return-routes), own remote-client context | Yes | Yes | Yes | Yes | Yes |
| Routing | [`get/set altpath`](halo_keymind_settings.md#direct-path-overrides) | Repeater or [room user](room_services.md#your-own-return-routes), own remote-client context | Yes | Yes | Yes | Yes | Yes |
| ACL | [`setperm <pubkey> <permissions>`](cli_commands.md#add-update-or-remove-permissions-for-a-companion) | Repeater, room server, or sensor | Yes | Yes | Yes | Yes | Yes |
| ACL | [`get acl [page]`](cli_commands.md#view-the-current-acl) | Repeater, room server, or sensor; admin over LoRa | Yes | Yes | Yes | Yes | Yes |
| ACL | [`get/set allow.read.only`](cli_commands.md#view-or-change-this-room-servers-read-only-flag) | Room server | Yes | Yes | No | Yes | Yes |
| Room | [`get/set topic`](cli_commands.md#view-or-change-the-room-topic) | Room server | Yes | Yes | No | Yes | Yes |
| Regions | [`region load`](cli_commands.md#bulk-load-region-lists); [`region save`](cli_commands.md#save-any-changes-to-regions-made-since-reboot) | Role with region storage | Yes | Yes | Yes | Yes | Yes |
| Regions | [`region allowf`](cli_commands.md#allow-a-region); [`region denyf`](cli_commands.md#block-a-region) | Role with region storage | Yes | Yes | Yes | Yes | Yes |
| Regions | [`region get`](cli_commands.md#show-information-for-a-region); [`region list`](cli_commands.md#view-all-regions); [`region`](cli_commands.md#dump-all-defined-regions-and-flood-permissions) | Role with region storage | Yes | Yes | Yes | Yes | Yes |
@@ -365,6 +406,7 @@ should use command `0x42`. See [Companion radio binary protocol](companion_proto
| Retry | [`get/set/clear recent.repeater`; `get recent.repeaters`](cli_commands.md#view-seed-or-clear-the-recent-repeater-table) | Repeater | Yes | Yes | Yes | Yes | Yes |
| GPS | [`get gps`; `set gps on/off`](cli_commands.md#view-or-change-gps-state) | Compiled onboard GPS | Feature | Feature | Feature | Feature | Feature |
| GPS | [`gps sync`](cli_commands.md#sync-this-nodes-clock-with-gps-time) | Compiled onboard GPS | Feature | Feature | Feature | Feature | Feature |
| GPS | [`get/set gps.sync.interval`](cli_commands.md#set-the-automatic-gps-clock-sync-interval) | Compiled GPS and provider; 1-336 hours | Feature | Feature | Feature | Feature | Feature |
| GPS | [`gps setloc`](cli_commands.md#set-this-nodes-location-based-on-the-gps-coordinates) | Compiled onboard GPS | Feature | Feature | Feature | Feature | Feature |
| GPS | [`gps advert [none/share/prefs]`](cli_commands.md#view-or-change-the-gps-advert-policy) | Compiled onboard GPS | Feature | Feature | Feature | Feature | Feature |
| Sensors | [`get/set telemetry.access`](cli_commands.md#view-or-change-telemetry-access-mode) | Sensor-capable full parser | Feature | Feature | Limited | Feature | Feature |
+593 -37
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
@@ -223,13 +252,18 @@ See the [OTA guide](ota_user_guide.md#adjust-lora-ota-speed).
On nRF52, `start ota` invokes Bluetooth DFU with the matching bootloader and
application DFU ZIP. The WiFi/AP instructions below apply to ESP32.
On ESP32, `start ota` serves the web upload page on the station IP when connected to WiFi;
otherwise it raises the `MeshCore-OTA` access point. `start ota ap` always raises
the access point, which is useful when the normal network uses client isolation.
On ESP32, `start ota` enables WiFi as needed and serves the web upload page on
the station IP when connected to WiFi; otherwise it raises the `MeshCore-OTA`
access point. The reply gives the URL to open and tells you which network to
use. `start ota ap` always selects the access point, including when OTA is
already running on a LAN address. This is useful with client-isolated networks.
On an ESP32 build with WebConfig, the manual OTA uploader and WebConfig both use HTTP
port 80 and cannot run together. Stop WebConfig before `start ota`, or stop OTA
before `start webconfig`.
Starting OTA automatically stops WebConfig and reports `WebConfig stopped` in
the reply. The existing LAN connection stays up; a setup hotspot is replaced
by `MeshCore-OTA`. WebConfig's delayed HTTP cleanup cannot stop OTA's WiFi.
Use the USB/Bluetooth or LoRa command line to start the uploader; WebConfig's
own terminal cannot deliver the handoff reply after shutting itself down.
Stop OTA before starting WebConfig again.
FULL ESP32 builds also expose the `.mota` folder seeder on TCP port 5001
whenever WiFi is usable. This listener is independent of port 80, so a host can
@@ -279,6 +313,13 @@ them entirely. Sign in with the node's admin password. If the node has no saved
WiFi SSID, the command starts the open `MeshCore-Setup-XXXX` captive AP at
<http://192.168.4.1/> instead.
If a saved WiFi network is unavailable and the node falls back to its setup
AP, sign in with the existing admin password before changing settings. Joining
that AP does not grant permission to replace the password. First-time setup
without a saved SSID keeps its AP provisioning flow. Images deliberately
configured without an admin password, including passwordless Companions,
retain their local no-password policy; use them only on a trusted network.
`start webconfig ap` forces captive-AP mode. It will not interrupt an active
MQTT bridge, so run `set mqtt.enabled off` first. Use `stop webconfig` to
close either mode for the current boot. (`stop webconfig` does not change a
@@ -291,6 +332,15 @@ again without rebooting. Once an SSID is saved, the cutoff no longer applies
and the selected WiFi/MQTT mode keeps reconnecting. Other setup sessions retain
their profile's idle timeout.
Full Repeater and Room Server suspend unconfigured network bridges during
automatic first-boot setup. Automatic ESP-NOW retries remain suspended after the AP
closes, so they cannot keep an unused WiFi driver running. Saved bridge choices
remain intact; explicitly enabling the bridge resumes it for this boot.
Manual WebConfig/WiFi starts preserve an explicitly enabled ESP-NOW bridge,
including when `set 2.4ghz on` restores the previously selected services.
Stopping a browser OTA session restores only bridges that session actually
paused, rather than starting services from saved intent alone.
Every ESP32 build with WebConfig supports the browser command terminal,
including WiFi Companion and Full Companion. Companions use their complete
USB/TCP terminal on the trusted LAN, including contact import, chat, recipient
@@ -372,6 +422,35 @@ credentials alone do not turn it on. ESP32 simultaneous-transport builds apply
the change from the main loop; exclusive-transport builds and Pico W apply it
after reboot. This does not disable a primary ESP-NOW mesh radio or add Bluetooth
to a WiFi-only build.
ESP32 WiFi Companions also accept `get wifi.tcp` over USB, BLE, or the browser
CLI. This checks the app's TCP listener (normally port `5000`), not just the
WiFi association. `listening` means the socket is open; `waiting-ip` means no
usable station or setup-AP address yet; `retrying` means listener startup
failed and will be retried automatically; `off` means no enabled listener
request. `reg=1` confirms the interface is registered for Companion commands.
`client=1` means a TCP client is connected. `acc`, `rx`, and `tx` count accepted
clients, complete received frames, and complete transmitted frames since boot;
the value after `/` is the last frame's hexadecimal opcode. `fail`, `err`, and
`retry` show listener failures, the last listener-start socket error, and seconds until retry.
`drop` gives the last client-disconnect reason. These counters contain no
message contents or credentials. An open listener does not prove a router or
guest-network firewall permits access from the phone.
Listener retries wait 1, 2, 4, 8, 16, then at most 30 seconds. They do not
reconnect WiFi, interrupt BLE/WebConfig/OTA, replace a healthy app connection,
or re-enable explicitly disabled WiFi. `get display.wifi` includes `tcp=1`
only when the listener is actually open; `services=1` alone is not proof.
The ESP32 TCP transport services input even while radio logs or other outgoing
frames are pending, so unsolicited traffic cannot starve the app handshake.
Socket sends are non-blocking. A client that makes no outbound progress for
10 seconds while data is pending is disconnected (`drop=tx-timeout`), without
blocking the other transports. `drop=tx-error` identifies a fatal send error.
Commands wait when all reply slots hold delivery-required data, rather than
executing with nowhere to return their reply. A partly sent frame stays pinned
until it finishes; input and output remain separate TCP streams.
See [local maintenance commands](terminal_chat_cli.md#local-maintenance-commands)
for the complete access rules. WiFi-only Companions accept
all three modes. A Full Companion that runs BLE and infrastructure WiFi
@@ -428,6 +507,24 @@ The browser portal is not compiled into the two 4 MB
retaining the two app slots required for LoRa OTA. Their normal CLI settings
remain available.
### Switch the OTA release channel (observer builds)
- `ota branch` - shows the selected channel, the channel this build was made for, and the manifest base `ota check`/`ota update` will use.
- `ota branch prod` (alias `stable`) / `ota branch beta` (alias `dev`) - pull future `ota update`s from that channel. The selection is saved; run `ota update` to switch.
- `ota branch default` - follow the channel this build was made for.
The selector is saved only after the preferences transaction succeeds. A queued
`ota update` keeps the channel it checked, even if `ota branch` is changed before
the download starts. Switching channels offers the target image even when its
commit matches the running image; build numbers are not comparable across channels.
Before an image is made bootable, its compatibility tag must confirm support for
this fork's saved-settings format and required transports. Upstream observer
images without the `keymind1` capability are refused, including images that use
the different `/mqtt.json` format. A refusal leaves the current image running;
select a compatible channel to continue OTA. Production and beta URLs are supplied
by the build recipe, not accepted as arbitrary CLI URLs.
---
### Erase/Factory Reset
@@ -462,7 +559,7 @@ command `0x42` where the role supports the command; unavailable over LoRa.
### Remove a neighbor
**Usage:**
- `neighbor.remove <pubkey_prefix>`
- `neighbor.remove` — remove all neighbors
- `neighbor.remove` - remove all neighbors
**Parameters:**
- `pubkey_prefix`: An even-length hexadecimal prefix (up to the full 64-character
@@ -914,7 +1011,31 @@ All display-equipped Companion builds show the screen at boot in the default
selected profile's timeout (15 seconds by default); button input restarts that
timer. This also applies to previously saved button modes. Saved `off` and
pairing-only modes still suppress the boot screen. Repeaters, room servers,
and sensors do not gain this Companion-only boot wake.
and sensors do not gain this Companion-only normal-UI boot wake.
On display-equipped Companions, repeaters, room servers, and sensors, a small
`Starting...` screen appears as soon as the board, primary filesystem, and
display are initialized, before radio initialization and serial-settle delays.
If a new identity is needed, it changes to `Generating key` with an animated
spinner during radio entropy collection. OLED/TFT updates are paced at 150 ms;
e-paper uses a slower one-second cadence. Saved `off` and pairing-only modes
still suppress this startup screen. The normal UI's boot timeout starts only
when that UI is ready, so key generation does not consume it. Filesystem
recovery and the display driver's own initialization can still delay the first
visible frame; the firmware cannot draw before those operations finish.
Startup shows `Loading identity` while reading a saved identity. `Generating key`
and its spinner are used only when creating a replacement/new identity;
they end before flash persistence, which shows `Saving identity`. A normal
boot with a valid saved identity never regenerates it. An erased filesystem
does require a new identity unless one is restored.
On Full Companion screens, an unpaired Bluetooth PIN takes priority over a
USB/TCP connected label. Button wake shows the PIN banner without requiring
USB to be disconnected; a second navigation press can leave this preview.
An actual phone pairing request wakes pairing-enabled display modes and keeps
the banner visible until completion or its two-minute expiry. Bluetooth
scanning alone is not a pairing request.
- `off`: stays dark, including boot and pairing.
- `on`: stays on while the device is awake.
@@ -1016,17 +1137,27 @@ flip state on display-enabled observer builds.
## Logging
Builds compiled with `MESH_PACKET_LOGGING` emit one `RAW:` line for every
received radio frame. Serial output uses backpressure: if a connected host
temporarily stops reading, packet processing waits for USB transmit space
instead of silently omitting the record. A disconnected host cannot retain an
unbounded capture, so logging deployments should keep the reader attached and
draining the serial port.
Non-compact builds compiled with `MESH_PACKET_LOGGING` emit a `RAW:` line
for received radio frames that reach the raw capture hook while `usb.logging`
is on, including Full Companion. Frames rejected by the early route policy
are not exposed to raw capture.
`RAW:` contains the complete received frame as uppercase hexadecimal, not only
its decoded payload. Packet records do not require verbose USB debug output.
Keep the host reader attached and draining the serial port: capture is best
effort, not durable storage. Native-USB transports use bounded queues and drop
records rather than block radio processing when the host stops reading;
`DROP:<count>` markers report capture loss when output capacity returns.
Every valid received frame also emits the decoded RX summary, including signal,
Successfully parsed received frames also emit the decoded RX summary, including signal,
timing, hash, type, route, and payload information. Frames that cannot be
decoded still emit their `RAW:` line. Transmitted packets emit the decoded TX
summary.
decoded, or cannot obtain a packet-pool slot, can still emit their `RAW:` line.
Transmitted packets emit the decoded TX
summary. RAW and RX summary are separate records, not an atomic pair; a queue
drop or disconnect can leave a summary without its RAW bytes. USB log consumers
must not associate a stale RAW record with the next unrelated summary.
Size-constrained `MESH_PACKET_LOGGING_COMPACT` images instead use `R<hex>` and
`T<hex>` records; that compact stream is not the full ASCII RAW/summary format
expected by ordinary USB-to-MQTT parsers.
Ordinary non-OTA artifacts compile packet logging into the canonical image and
control its live USB output at runtime; no separate `-logging-` artifact is
@@ -1048,8 +1179,27 @@ set usb.logging on
set usb.logging off
set usb.logging on reboot
set usb.logging off reboot
get usb.debug
set usb.debug on
set usb.debug off
```
Current source also provides `get usb.watchdog` and saved
`set usb.watchdog off|on|auto`. Missing watchdog state defaults to Auto, which
requires 14 continuous healthy logging-client days before saving On. Existing
five-minute USB stats polls confirm recent logging-client activity; a plugged
cable/open TTY alone does not. See the [USB logging watchdog](usb_logging_watchdog.md) for staged
five-minute USB recovery, physical-fault-only MCU resets, persisted backoff,
status fields, and safe shutdown deferrals. Older release binaries need an
upgrade before these commands exist.
`get usb.watchdog.last` shows the latest USB watchdog event: recovery action,
fault-reason bitmask, advisory node-clock epoch, uptime at the event, sequence,
and whether the record passed durable readback. It survives reboot on durable
storage. `reboot-requested` records intent, not proof that a physical reset
occurred; a later safety veto records `reboot-cancelled`. See the watchdog
documentation for storage and timestamp limitations.
**ESP32 1.17.1.5 USB logging procedure:** disable device sleep before enabling
the log stream, using separate text commands:
@@ -1064,16 +1214,44 @@ saving. This works around the [released USB sleep bug](old-releases/1.17.1.5.md#
nRF52 does not need this ESP32 workaround.
These commands are compiled into ordinary USB-loggable artifacts and every
Full Companion. They control live USB debug and packet output. CommonCLI roles
save the setting in `/com_prefs`, so it survives reboot; their first boot
defaults to on. Full Companion and ordinary USB Companion start off on a fresh
installation so diagnostics cannot corrupt framed traffic.
Full Companion. `usb.logging` is the saved USB output master and packet-log
gate. `usb.debug` is a separate saved preference for verbose diagnostics;
effective USB debug output requires both switches to be on. Packet RAW and
RX/TX summaries require only the master switch. `get usb.debug` reports saved
intent even when the master is off. Turning `usb.logging` off does not erase
that intent; turning it back on restores debug output if `usb.debug` is on.
CommonCLI roles save these settings in `/com_prefs`; their fresh USB logging
default is on. Full Companion and ordinary USB Companion start with the
master off on a fresh installation so diagnostics cannot corrupt framed
traffic. Verbose USB debug defaults to off for every role, including when
loading older preferences without a debug field. Updates retain an existing
saved `usb.logging` choice. Older binaries without the new `usb.debug`
command retain their previous behavior until upgraded.
For packet-only capture, use these separate text commands:
```text
set usb.debug off
set usb.logging on
```
Use `set usb.debug on` temporarily when troubleshooting, then turn it off
again to reduce USB traffic. This setting applies immediately and needs no
USB-interface reboot. It does not select a firmware variant or enable code
omitted from the image: verbose output still requires compiled `MESH_DEBUG`
or other relevant diagnostic support. A packet-only build can retain the
preference without gaining missing debug messages.
Debug off suppresses verbose diagnostic chatter, not functional CLI replies,
USB port identity/drop markers, or retained operational warnings. Readers
must still filter non-packet lines instead of treating every line as a packet.
On Full Companion these lines belong to its text terminal, not `meshcli`'s
Binary `get/set` parameter namespace. Open interface `00`, send
`+++MESHCORE-TERM-START`, and then issue the command. Running
`meshcli ... get usb.logging` directly can instead return
`Unknown var usb.logging` because that is a different protocol operation.
`meshcli ... get usb.logging` or `get usb.debug` directly can instead return
`Unknown var ...` because that is a different protocol operation.
nRF52 Full Companion defaults logging to off and enumerates only USB interface
`00`, which carries Companion, terminal, and serial mOTA traffic. Enabling
@@ -1083,6 +1261,8 @@ the optional `reboot` argument saves the choice and reports that a reboot is
required when the USB interface count must change. The exact
`set usb.logging on reboot` and `set usb.logging off reboot` forms save the
choice, send their reply, and reboot one second later only when needed.
Interface `02` is logging-only, not an input-capable CLI. Send logging/debug
commands to interface `00`; USB debug uses the same log endpoint as packets.
On every ESP32 Full Companion, enter the USB text terminal and use
`set usb.logging on` (preceded by `set powersaving off` on 1.17.1.5) to turn that TTY into a logging-repeater-style plaintext
@@ -1099,6 +1279,20 @@ frames active on interface `00`; ESP32 resumes the ordinary ASCII/Binary
switcher after the logging terminal turns logging off. This setting does not
change the node-storage capture controlled by `log start` and `log stop`.
For a USB-to-MQTT bridge, enable packet logging but leave `usb.debug` off.
Full Companion now supplies the ASCII RAW/RX/TX packet stream needed by such
consumers; it does not provide durable or lossless capture. A compatible host
must also support the role's identity/control commands and replies: packet
records alone do not make an existing bridge compatible. Companion now answers
`get public.key` with its node public key. ESP32 Full's logging TTY remains
input-capable and accepts that query. nRF52 Full's dedicated logging CDC is
not a command/reply console (its bounded stats-poll input only renews the
watchdog client lease):
a host integration must use interface `00` for commands and identity and
interface `02` for logs.
Existing single-port bridges that require CLI replies cannot simply point at
the dedicated log endpoint. This change does not add a two-port bridge driver.
Companion, Repeater, Room Server, and Sensor builds with both MQTT and USB
logging provide the same saved selector for both output paths:
@@ -1119,6 +1313,8 @@ in `both` mode; Full Companion starts with USB logging off. To change only
MQTT, use `set mqtt.enabled on|off`; `get mqtt.enabled` checks the saved
switch and `get mqtt.running` checks whether the MQTT service is running.
Turning MQTT off preserves all broker slots and credentials.
The output selector changes the USB master, not saved `usb.debug` intent;
WiFi MQTT logging is independent of the USB debug preference.
On **1.17.1.5 ESP32**, run `set powersaving off` before selecting
`set logging.output usb` or `set logging.output both`, since those modes
@@ -1606,7 +1802,17 @@ set bluetooth on
`get ble` and `set ble ...` are aliases. This controls the running Bluetooth
service on ESP32 and nRF52 Companions: `off` stops advertising and disconnects
Bluetooth clients; `on` enables it again. The change lasts until reboot.
Bluetooth clients; `on` enables it again. Both choices are saved and survive
reboots. USB remains available when Bluetooth is off. Older saved preferences
without this setting retain the previous default of Bluetooth on.
Successful local commands report `(saved)`. If saving fails, the command reports
an error and leaves Bluetooth unchanged. When requested over Bluetooth, `off`
first reports `(save pending)` so its reply can drain; the firmware rechecks the
alternate connection and saves the preference before disconnecting. If that
connection disappears or the save fails, shutdown is cancelled. `get bluetooth`
reports the current runtime state. Broad `2.4ghz` controls still apply to the
current boot only and do not change this saved Bluetooth preference.
Plain `off` requires another active management connection. A connected USB
terminal or binary client, TCP client, or browser terminal can provide that
@@ -1616,7 +1822,8 @@ bridge), issue the command through that USB client so the request itself proves
it is active.
Append `force` to `off` to permit disconnecting the only active connection.
Use USB, another management transport, or reboot to regain access afterward.
Use USB or another management transport and `set bluetooth on` to regain
access afterward. Reboot alone will not re-enable saved-off Bluetooth.
`force` is accepted only as the final argument to `off`.
Commands sent through Bluetooth allow its reply to drain before shutdown
@@ -1647,6 +1854,37 @@ the frame and reply format.
---
#### Set the Bluetooth pairing PIN (Companion)
**Usage:**
- `set pin <0-999999>`
Choose a six-digit code for a fixed Bluetooth pairing PIN. For example:
```text
set pin 654321
reboot
```
The command saves the PIN across reboots and replies `> pin is now 654321`.
Reboot to activate the new code; the current Bluetooth session keeps its
existing PIN until then. A failed save reports an error and retains the
previous saved setting.
`set pin 0` clears the saved override, rather than setting the pairing code to
`000000` or disabling Bluetooth. After reboot, the build's default policy
applies: normally `123456` without a display, or a generated PIN on eligible
display-equipped builds.
This command is available in the Companion text terminal, binary command
`0x42` (`CMD_RUN_CLI_COMMAND`), and authorized LoRa CLI; a Bluetooth-capable build
is needed to use the PIN. Existing bonded clients may reconnect without being
asked for a PIN.
Forget the device on the phone and pair again if you need to enter the new code.
---
#### View or change the Bluetooth address (BLE Companion)
**Usage:**
@@ -1860,7 +2098,7 @@ through binary command `0x42`; this command does not report the Bluetooth PIN.
**Note:** On nRF52 boards with power management and a boot-voltage setting,
this is a saved *relative* calibration: `1.000` keeps the board conversion,
`1.050` raises all reported battery millivolts by 5%, and `0` resets to
`1.000`. The allowed range is `0.500`–`1.500`. This affects boot protection,
`1.000`. The allowed range is `0.500`-`1.500`. This affects boot protection,
running cutoff, telemetry, battery alerts, and percentage. Calibrate against a
voltmeter before enabling a low cutoff. Other boards retain their existing
board-specific multiplier behavior, or return unsupported.
@@ -2132,6 +2370,13 @@ get clock.sync.status
#### View this node's public key
**Usage:** `get public.key`
Returns this node's identity public key without changing it or exposing its
private key. Companion supports the query through its text terminal and
framed CLI command `0x42` (`CMD_RUN_CLI_COMMAND`); its reply is `> ` followed
by exactly 64 uppercase hexadecimal characters. Private-key export support
is not required. On nRF52 Full Companion, send the query to control interface
`00`, not the output-only logging interface `02`.
---
#### View this node's firmware version
@@ -2164,6 +2409,23 @@ are shared by Companion and infrastructure. The saved preference controls
whether power saving is allowed; active USB, logging and network services may
keep the hardware awake. Actual sleep depends on the board.
On ESP32 infrastructure boards using an external USB-to-UART chip, such as the
Heltec V3's CP2102, the native USB host guard cannot detect an attached computer.
Current source blocks MCU light sleep while the raw UART console driver is
enabled, preserving the first web-console command after idle even with all
bridges and USB logging stopped. This guard has no idle timeout and also covers
a computer's first connection after boot. These builds enable that console at
startup, so MCU light sleep is unavailable while it remains enabled, including
on battery. `set powersaving on` still enables sensor/GPS power-saving policies;
the CPU continues normal idle yielding while keeping the console available.
A running MQTT, RS232 or ESP-NOW bridge keeps infrastructure awake even when
`get powersaving` reports `on`; that readback alone does not prove MCU sleep.
Negotiated RS232 standby can stop an unresponsive UART owner without changing
its saved enabled flag; see `rs232.autosleep` below. Other sleep blockers still apply.
Adding UART wake alone would still discard the characters that trigger wake,
so it cannot preserve an unmodified web console's first command. See the
[ESP-IDF UART wake documentation](https://docs.espressif.com/projects/esp-idf/en/v4.4.7/esp32s3/api-reference/system/sleep_modes.html#uart-wakeup-light-sleep-only).
For the **1.17.1.5 G3 USB-disconnect report**, use `set powersaving off` as the
workaround. The released ESP32 sleep code can lose native USB after two
minutes when the terminal is closed, even with a computer attached. The
@@ -2176,6 +2438,12 @@ binaries.
Companion firmware defaults this setting to `on`. Full Companion accepts the command from its local USB terminal and exposes the same setting in WebConfig. On ESP32, it lowers the CPU clock to 80 MHz, enables idle yielding, and enables the configured GPS duty cycle. USB and each active wireless transport remain available; SenseCAP Indicator Full keeps only its selected BLE or infrastructure-WiFi secondary transport active. `set powersaving off` restores the board's normal CPU clock and disables the GPS duty cycle. This device setting is separate from LoRa RXPS (`radio.rxps`) and WiFi modem power save (`wifi.powersave`). Infrastructure uses the same commands; its hardware and active-service sleep guards determine when the node can sleep.
Native USB ESP32 Full Companions can also use short light-sleep intervals when
the BLE/WiFi drivers, ESP-NOW, GPS UART, logging, and USB host sessions are
inactive and no work is pending. Compiling those transports into Full does not
itself prevent sleep. Turning off BLE advertising alone leaves its controller
running and therefore retains idle behavior rather than manual light sleep.
---
#### View or set the reboot interval (Repeater and room server)
@@ -2240,6 +2508,9 @@ The pin number is the Arduino pin number used by that target (the normal GPIO nu
**Notes:**
- These commands require remote client context and update the caller's ACL entry.
- Room Server readers and writers may manage their own `outpath` and `altpath`
without administrator permission. See [room return routes](room_services.md#your-own-return-routes)
for the command list, session persistence and alternate delivery behavior.
- `get outpath path` reports the reciprocal `PAYLOAD_TYPE_PATH` received after
the caller's latest flood login without changing the selected output route.
Because that packet is asynchronous, an immediate query can report
@@ -2265,7 +2536,48 @@ The pin number is the Arduino pin number used by that target (the normal GPIO nu
**Parameters:**
- `state`: `on`|`off`
**Default:** `flood.channel.data on`; `flood.channel.data.hops h=all`
**Default:** `repeat on`.
`repeat off` disables relay forwarding, not locally generated traffic or
configuration replies. Repeaters can separately opt in to the routed-trace
exception below.
#### Allow routed traces while repeating is disabled (repeater only)
```text
get repeat.trace
set repeat.trace on
set repeat.trace off
```
Default: `off`, including upgrades from older preference files. The setting is
saved and takes effect without reboot. Failed saves leave the active choice
unchanged. Room Server, Sensor, and Companion do not gain this exception.
To keep ordinary forwarding disabled while allowing routed trace diagnostics:
```text
set repeat off
set repeat.trace on
```
Only direct TRACE packets with a matching next hop may be relayed. Ordinary
messages, adverts, group data, ACKs, and control packets remain subject to
`repeat off`; flood traces remain invalid. Existing trace length, path-capacity,
duplicate, radio-profile, and channel/duty-cycle checks still apply. This is
a diagnostics opt-in, not authenticated-admin-only forwarding: trace packets
retain the protocol's existing authentication behavior.
`get repeat` and the disabled status bit continue to report repeating off.
With `repeat on`, traces use the ordinary forwarding policy regardless of
`repeat.trace`. Changing the exception to off while repeating is off blocks
subsequent relayed trace transmissions, including queued copies and retries;
a trace already on air is allowed to finish. Locally initiated traffic and
management replies are not cancelled.
The dynamic configuration field is `repeat.trace_off` (`0` or `1`). It is
appended to `/com_prefs`; all field offsets in the published upstream layout
stay unchanged.
---
@@ -2910,9 +3222,9 @@ worked moderation examples, see [Flood Filtering and Moderation](flood_filtering
- `get flood.rule`
- `get flood.rule.<n>`
- `get fr[.<n>]` (numbered form returns a complete short setter for copying)
- `set fr[.<n>] <type> [h=<range>] [m=r|b|c|bc] [c=<channel>] <d|s=<scope>|r=<region>|q=<N>|r|s> [...]`
- `set fr[.<n>] <type> [h=<range>] [m=r|b|c|bc] [c=<channel>] [pb=any|1|2|2+|3] <d|s=<scope>|r=<region>|q=<N>|r|s> [...]`
- `del fr.<n>` or `del fr all`
- `set flood.rule[.<n>] type=<type> [mode=radio|bridge|cross|bridge,cross] [hops=<range>] [channel=<channel>] [prefix=<path-prefix>] [in=<input-scope>] <drop|scope=<name>|region=<name>|rate=<N>/min|retry|stop> [priority=<0-255>] [tx=slow] [suspend=tempradio]`
- `set flood.rule[.<n>] type=<type> [mode=radio|bridge|cross|bridge,cross] [hops=<range>] [channel=<channel>] [hashbytes=any|1|2|2+|3] [prefix=<path-prefix>] [in=<input-scope>] <drop|scope=<name>|region=<name>|rate=<N>/min|retry|stop> [priority=<0-255>] [tx=slow] [suspend=tempradio]`
- `del flood.rule.<n>`
- `del flood.rule all`
- `get flood.filter`
@@ -2968,6 +3280,14 @@ compile this table.
MAC/decrypt check with the Public channel key. That distinguishes an ordinary
`0x11` collision, but it is channel authentication rather than sender
authentication: the group MAC is two bytes and the Public key is shared.
- `hashbytes=any|1|2|2+|3`: Optional encoded path-hash width, independent of hop
count and hash value. Omitted, `any`, or `*` matches every supported width.
Width-specific rules also match zero-hop packets. Four-byte hashes are
reserved and unsupported. `2+` selects both 2- and 3-byte paths in one row,
sharing any rate limit across both. A simultaneous `prefix=` must satisfy the
selector; a 2- or 3-byte prefix narrows `2+` to its own exact width.
Example: `set fr any pb=1 d` drops all 1-byte floods from forwarding, including
relayed login/admin traffic, but not direct packets or local delivery.
- `prefix=<ID[,ID...]>`: Optional ordered source-path prefix of one to three
pbyte IDs. IDs must all be 2, 4, or 6 hex characters, matching a packet's
1-, 2-, or 3-byte pbyte width. `path=<prefix>` is an alias.
@@ -3217,6 +3537,7 @@ have the same behavior as their long forms.
| `hops=...` | Positional range or `h=...`; `*` means `all` |
| `mode=radio/bridge/cross/bridge,cross` | `m=r/b/c/bc` (choose one value) |
| `channel=...`, `prefix=...` | `c=...`, `p=...` |
| `hashbytes=any/1/2/2+/3` | `pb=*/1/2/2+/3` (choose one value) |
| `path=blacklist` | `p=bl` (repeater only) |
| `in=any/none/scoped/allowed/unknown` | `i=*/n/s/a/u` (choose one value) |
| `in=scope:name`, `in=region:name` | `i=s:name`, `i=r:name` |
@@ -3508,16 +3829,65 @@ setperm <PUBKEY_USER_1> 2
```
With read-only mode on, a client without a valid room password can join as a
read-only guest. Permission `2` gives the specified public key read-write
read-only guest, including with a blank password. Empty configured admin and
room passwords disable those password credentials; they never grant Admin or
Read-Write access. Permission `2` gives the specified public key read-write
access. Replace `<PUBKEY_USER_1>` with the user's full public key and repeat
`setperm` for every approved writer.
An existing ACL identity keeps its assigned role when logging in with a blank,
stale, or room password. A valid nonempty admin password can explicitly upgrade
it to Admin. This prevents a guest-password login from demoting an admin, and
prevents a room password from overriding an explicit read-only assignment.
Only Read-Write (`2`) and Admin (`3`) clients can post.
This does not remove write access from clients that already know the room
password. Keep that password private, or replace it, if write access should be
limited to the explicitly permitted users.
---
#### View or change the room topic
**Availability:** Room Server firmware. Local consoles and authenticated remote
admins can use these commands.
```text
set topic Welcome to the Bellevue room. Please keep posts local.
get topic
set topic
```
`set topic <text>` saves an announcement of up to 151 UTF-8 bytes. Individual
command transports can impose a smaller input limit. `get topic` returns the
current text prefixed with `> `. `set topic` with no text clears it.
The topic survives reboot and appears as a normal room post authored by the
server, using the existing app message format. It does not require an app
update or provide a separate pinned-header display. Older unread retained posts
are delivered before the topic, so the companion's automatic catch-up cursor
cannot jump past them. A topic ACK updates only topic delivery state, leaving
the server's message-history cursor unchanged.
The server waits six seconds before delivering a new topic and attempts an
unacknowledged announcement up to three times per client. It then continues
ordinary post delivery; reconnecting or changing the topic renews that budget.
Changing a topic while its predecessor is awaiting an ACK does not mark the
replacement as received. Clearing the topic stops further announcement pushes;
a packet already queued for transmission can still arrive.
Topic saves are verified before publication. A failed save reports an error
and retains the previous live topic. Corrupt, unreadable, or unsupported stored
topic files are preserved and block replacement until their storage issue is
repaired. Ordinary room history uses the bounded RAM ring by default. Enable
optional retention of the newest 32 posts with `set room.history on`.
See [Room history, moderation, and information board](room_services.md) for
persistent history, saved ACL roles, full-key bans, per-user post/request
limits, the ESP32 `/room` browser page, and versioned article downloads.
---
### Region Management (v1.10.+)
#### Bulk-load region lists
@@ -4374,12 +4744,51 @@ clear recent.repeater
**Note:** These command names are shared with GPS-capable Companion builds.
Companion reports `on` or `off`; infrastructure includes receiver details:
- `off` when the GPS hardware is disabled
- `on, {active|deactivated}, {fix|no fix}, {sat count} sats` when the GPS hardware is enabled
- `off, {powered|unpowered}` when GPS is manually disabled
- `on, {powered|unpowered}, {fix|no fix}, {sat count} sats` when GPS is enabled
`powered` describes the supply, not proof that the receiver is tracking or
asleep. Shared-power hardware can remain powered while GPS processing is off.
On repeaters, `gps off` cancels any acquisition already in progress. A later
authorized location-telemetry request may wake the receiver, but only until its
first valid fix is cached; the receiver then powers down again.
first valid fix is cached; GPS processing then stops again. Whether its supply
can also turn off depends on the board's power policy below.
---
#### RAK4631 shared peripheral power
**Usage:**
```text
get peripherals.mode
set peripherals.mode gps-only
set peripherals.mode shared
reboot
```
**Default:** `gps-only` on RAK4631. The saved mode takes effect only after
reboot; setting it does not switch power immediately. `get peripherals.mode`
reports the saved choice, which may differ from the current boot's mode.
- `gps-only`: when no protected power owner exists, GPS-off and GPS sleep can
turn off the shared WisBlock `3V3_S` supply. Select this only when other
attached peripherals do not need that supply.
- `shared`: keeps `3V3_S` powered for other peripherals. Save this mode and
reboot when using other WisBlock modules; firmware does not infer this choice
from an I2C scan.
Ethernet/PoE builds and detected external NOR storage automatically keep the
rail on, even in `gps-only` mode or when the network service is disabled.
RAK3401 permanently protects the same rail because its radio frontend needs
it; `peripherals.mode` is a RAK4631 setting, not an override of that protection.
On a powered shared rail, RAK12501/L76K cannot be independently switched off:
GPS-off stops MCU parsing, not physical GNSS operation. Other receivers may use
internal power-saving commands, but `shared` does not promise physical sleep.
UART1 bridge ownership remains protected; changing this mode does not grant
permission to connect two transmitters to the GPS UART.
---
@@ -4395,6 +4804,43 @@ On repeaters with device power saving enabled, the automatic GPS cycle wakes
for up to 10 minutes and repeats seven days after GPS sleeps. A valid GPS time
can correct the RTC either forward or backward. With device power saving off,
an enabled GPS refreshes the RTC every 30 minutes.
These are the legacy defaults; `gps.sync.interval` below overrides them.
---
#### Set the automatic GPS clock-sync interval
```text
get gps.sync.interval
set gps.sync.interval 1
set gps.sync.interval 24
set gps.sync.interval 336
```
The interval is a whole number of **hours**, from **1 to 336** (two weeks).
Larger numbers are capped, not rejected. For example:
```text
set gps.sync.interval 999
OK - GPS sync interval 336 hours (saved)
```
Zero, negative numbers, fractional hours, and nonnumeric values are rejected.
The setting is saved across reboots and supported by GPS-equipped repeater,
room-server, sensor, and Companion builds. Until it is explicitly configured,
the getter reports `default (board GPS sync policy)` and existing board timings
are retained.
The selected interval controls periodic clock acquisitions both with GPS power
saving and with a continuously powered receiver. Changing it reschedules a
sleeping receiver without immediately powering it on. A GPS fix can take time;
an unsuccessful acquisition retains the existing bounded awake window, then
backs off before trying again. Device GPS/power switches still apply.
This does not change position-update `gps_interval`, location telemetry/cache
timings, or mesh/NTP time synchronization. Location requests can still power GPS
for a fix, but do not bypass the configured clock-sync interval. Explicit
`gps sync` remains an immediate acquisition request where that command exists.
---
@@ -4487,6 +4933,81 @@ bridge actually started in this boot; they can differ after a hardware conflict,
missing credentials, or a transient initialization failure. Normal merged
repeater images default to `off`; dedicated bridge images may default to `on`.
Full MQTT repeaters with RS-232 expose three independent transports. Use
`set rs232.enabled on|off` and `get rs232.enabled` for saved UART intent;
`get rs232.running` reports the actual UART state. `mqtt.enabled` and
`espnow.enabled` select the other two transports. In MQTT images the historical
`bridge.enabled` alias controls ESP-NOW; in non-MQTT UART images it controls
RS-232. `bridge.baud` and `bridge.uart` change only UART configuration, without
restarting MQTT or ESP-NOW. The new UART mode starts off when upgrading old
MQTT preferences and retains explicit settings across reboots.
---
#### RS232 heartbeat and automatic standby
Available on repeater builds with an RS232 bridge. This is a UART-local control
exchange, not an advert or a LoRa packet. Room builds without RS232 are unaffected.
**Usage:**
- `get rs232.status`
- `get rs232.heartbeat`
- `set rs232.heartbeat on|off`
- `get rs232.autosleep`
- `set rs232.autosleep on|off|force`
- `get rs232.idle`
- `set rs232.idle <seconds>` (60 through 3600; default 300)
Heartbeat and negotiated automatic standby are **on by default**, including
upgrades. An active UART pings every 30 seconds (52 bytes per ping).
Stock bridges ignore that traffic and still receive the original data frames;
they are not automatically put to sleep: standby is armed only after a fresh
heartbeat reply proves support. Fresh, session/challenge-matched replies
prove a bidirectional peer;
ordinary valid incoming data can keep a legacy link awake but cannot prove
heartbeat support. The status separates saved intent, actual UART state,
standby/probe phase, and confirmed versus legacy/unknown peer liveness.
Enable on both updated peers:
```text
set rs232.enabled on
set rs232.heartbeat on
set powersaving on
set rs232.idle 300
set rs232.autosleep on
get rs232.status
```
With heartbeat, autosleep, and global powersaving all on and the peer's heartbeat
support confirmed, when no peer responds for the idle interval the UART is soft-stopped while
`rs232.enabled` stays `on`. It wakes after 30 seconds for a 2-second probe;
every eighth probe listens for 40 seconds. That longer window spans the other
side's sleep interval, so two sleeping updated peers can reconnect without
manual intervention (normally within about five minutes with working wiring
and timely loop servicing). Healthy peers remain running; this is dead-link
power saving, not an idle-data duty cycle.
Stock receivers ignore these control frames; they are deliberately invalid
zero-length stock frames with an ASCII-only remainder. They do not allocate a
mesh packet or enter LoRa forwarding. Stock peers do **not** answer heartbeats,
so default negotiated standby never arms on a stock-only link. Valid legacy
data after heartbeat replies cease also disarms negotiated standby, allowing
a replacement stock peer to stay awake. Use `set rs232.autosleep force` only
to sleep an absent/unconfirmed UART peer; this explicitly bypasses stock
protection and can miss one-off stock packets.
If a previously confirmed updated peer is silently replaced with stock while
the local UART is already sleeping, its first packet can be missed before
replacement is detected. Set autosleep off before that change, or restart the
local bridge to clear the old negotiated session.
While the UART is stopped it cannot receive a wake byte; mesh traffic
during standby is not queued for later UART delivery. Turning autosleep or
global powersaving off resumes the saved-enabled UART; `rs232.enabled off`
remains an explicit off and never automatically wakes.
`rs232.heartbeat off` stops initiating heartbeats and prevents standby. An
updated active peer can still answer a valid incoming ping in passive mode.
---
#### Add a delay to packets routed through this bridge
@@ -4527,6 +5048,7 @@ repeater images default to `off`; dedicated bridge images may default to `on`.
- `set mqtt.enabled on`
- `set mqtt.enabled off`
- `get mqtt.running`
- `get mqtt.stopping`
- `get mqtt.status`
These commands are the same on MQTT-capable Companion, Repeater, Room Server,
@@ -4536,6 +5058,21 @@ brokers to reconnect. `get mqtt.running` checks the service's runtime state;
`get mqtt.status` reports individual broker connections. Enabling MQTT does
not imply that a broker is connected.
Custom `mqtts://` and `wss://` brokers require an image with the embedded CA
bundle. Without it, the slot stays disconnected and sends no credentials;
use a build with the bundle or a supported preset with its pinned CA.
Portable MQTT images retain those presets but do not include the general
bundle for custom TLS brokers. Explicit `mqtt://` and `ws://` configurations
remain available for trusted networks and use plaintext transport.
On ESP32 Repeater and Room Server images, `set mqtt.enabled off` acknowledges
the stop request while the MQTT task disconnects and destroys its clients.
`get mqtt.stopping` stays `on` until that cleanup completes; `mqtt.running`
also stays `on` while those resources are still owned. Wait for
`mqtt.stopping` to become `off` before enabling MQTT again. UART, ESP-NOW,
and the radio continue running during this cleanup. OTA still waits for a
completed MQTT shutdown before it can write firmware.
USB logging remains independent. On images with both outputs,
`set logging.output off|usb|wifi|both` selects USB and MQTT together.
@@ -4650,7 +5187,9 @@ Requires WiFi connected and the MQTT bridge running.
**Parameters:**
- `rate`: Integer baud rate from `9600` through the board's compiled
`BRIDGE_MAX_BAUD` (commonly `500000`); for example `115200`. Stop the bridge
with `set bridge.enabled off` before changing it, then enable it again.
with `set rs232.enabled off` in a combined MQTT image, or
`set bridge.enabled off` in a non-MQTT UART image, before changing it.
Enable the UART again with the matching switch.
**Default:** `115200`
@@ -4670,7 +5209,8 @@ Requires WiFi connected and the MQTT bridge running.
The setting is persistent and restarts an enabled bridge immediately. Normal
repeater artifacts start with `bridge.enabled off`; configure the UART and baud
rate before running `set bridge.enabled on`. On the canonical RAK4631 runtime
rate before running `set bridge.enabled on`. Combined MQTT images use
`rs232.enabled` for that independent UART setting. On the canonical RAK4631 runtime
image, UART 1 is reserved even if the bounded boot probe hears no RAK12501.
Silence cannot prove that a cold L76K is physically absent, and that module
remains powered by the shared WB_IO2/3V3_S rail. Use UART 2. UART 1 requires an
@@ -4817,6 +5357,22 @@ manufacturer images and the boards still requiring untouched factory dumps.
---
#### Configure battery charge voltage
T-Beam SX1262/SX1276 and T-Beam S3 Supreme support persistent charger-voltage
selection through the fitted AXP192 or AXP2101 PMU:
```text
get charge.voltage.options
get charge.voltage
set charge.voltage 4.1
```
Values are exact supported targets in volts. Neither T-Beam PMU supports 3.65 V.
Heltec Mesh Solar's CN3795 charge voltage is set by its Li-ion or LiFePO4 hardware
version, so these commands report that limitation without changing BMS settings.
See [charge-voltage behavior and supported targets](charge_voltage.md).
#### Configure nRF52 battery protection
**Usage:** `get pwrmgt.bootlock`, `set pwrmgt.bootlock <2500-4200>`
@@ -4865,12 +5421,12 @@ get adc.multiplier
set adc.multiplier 1.050
```
The custom empty/full endpoints may be 2000–4200 mV and must be at least 100 mV
The custom empty/full endpoints may be 2000-4200 mV and must be at least 100 mV
apart. Setting either endpoint automatically selects `custom`; boot lock and
cutoff remain separately adjustable. `get pwrmgt.bootlock` and
`get pwrmgt.cutoff` show the values in force after selecting a profile.
For a RAK3401/RAK13302 LiFePO₄ cell:
For a RAK3401/RAK13302 LiFePO4 cell:
```text
set battery.profile lifepo4
+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):
```
+148 -3
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).
@@ -47,6 +51,86 @@ a busy radio rollback remains pending for recovery instead of reporting success.
| LoRa self-update | No | No |
| Optional self-update | WiFi on existing dual-app layouts; USB on single-app layouts | Bluetooth DFU with a compatible bootloader; USB always supported |
### Screenless nRF52 spoken button confirmations
Full Companion builds for T1000-E, RAK WisMesh Tag, MeshTracker X1,
Muziworks R1 Neo and ThinkNode M3 use prerecorded spoken confirmations for
their shared single-button controls. Other roles, legacy USB/BLE-only builds,
screened boards and all Wio profiles retain their existing audio behavior.
Set `MESH_GPS_VOICE=0` at build time to retain tones on an eligible Full build.
| User-button gesture | Action / spoken confirmation |
| --- | --- |
| One press | Acknowledge an active notification: "Notification cleared"; otherwise "Ready" |
| Two presses | "Advert queued" or "Advert failed" |
| Three presses | "Sound on" or "Sound off" after the preference is saved |
| Four presses | "GPS on" or "GPS off" when the GPS setting is available |
| Hold more than three seconds | "Shutting down"; shutdown remains nonblocking while audio drains |
| Hold during the first eight seconds of UI startup | "USB setup" for terminal/rescue mode |
MeshTracker X1's three-press gesture instead cycles "Sound and vibration",
"Sound only", "Vibration only", and "Silent". The new setting must save before
either live alert output changes. A failed save restores both preferences and
announces "Action failed". An explicit mute or alert-mode change permits one
courtesy confirmation without unmuting ordinary notifications. The four
additional mode clips are compiled only for `HAS_DRV2605` builds.
Notification timers cannot cancel control speech, and the committed alert
mode remains authoritative for vibration even if a notification-mask save fails.
Power-on and wake wiring remain board-specific. Speech is nonblocking and uses
the existing nRF52 PWM player; each buzzer still needs a physical listening
test for intelligibility. All spoken button, alert-mode and GPS recordings use
Microsoft Zira Desktop with the selected compressed Companion voice profile.
GPS on/off use three stored audio spans: one shared encoded GPS prefix and two
phrase tails. Advert queued/failed also share an identical encoded prefix;
Sound off/Sound only share one only on `HAS_DRV2605` boards. The tails are not
independently decodable words. Playback treats each prefix and tail as one
continuous compressed stream, without resetting its codec, bit position,
interpolation or DMA at the seam. The approved full-phrase PCM and PWM output
remain bit-for-bit identical, including pronunciation and pauses.
The IMA audio payload is 3,881 bytes smaller on ordinary voice boards, or
4,971 bytes smaller on vibration-equipped boards, before the small segmented
descriptor/decoder overhead. Tiny shared silence prefixes are not split.
A representative Cortex-M4 GCC 14.2 size-optimized/LTO player-and-catalog
build saves 3,732 or 4,792 bytes respectively after that overhead; final
firmware savings can vary with toolchain and surrounding code.
The existing generators still synthesize the two complete GPS phrases and
button confirmations, then share byte-identical encoded prefixes; they do not
try to identify word boundaries or splice independently spoken words. Windows
Zira synthesis still requires Windows' local `System.Speech` tooling.
R1 Neo's buzzer pin aliases its optional QSPI clock, so voice is
not default-enabled when `QSPIFLASH` is selected. ThinkNode M4 is deliberately
excluded: its former
buzzer definitions incorrectly claimed I2C SDA and a battery-status LED, not
verified audio hardware. Its Companion recipes no longer drive those pins as
a buzzer.
The optional ESP32 RC32-without-display recipe is not part of this nRF52
extension: its fitted audio hardware needs verification and its `ui-new`
controls require an ESP32 playback backend, not the nRF52 PWM player.
#### Experimental G.726 speech
Ordinary builds still use the approved Zira IMA recordings. G.726 is a hardware
test option only: set both `MESH_BUTTON_AUDIO_HIL=1` and
`MESH_GPS_VOICE_G726_BITRATE=16` or `24`. Invalid bitrates or a missing test gate
are rejected. The generated catalog selects exactly one bitrate, retains the
original sample counts, and omits the X1-only mode prompts on other boards.
The decoder needs 52 bytes of state, no heap or full-clip buffer, and about
1.2 KiB of Cortex-M4 code/tables. Its packing, resets and PWM lifecycle are
checked against independently pinned FFmpeg output. At 24 kbps the audio
payload is 25% smaller; at 16 kbps it is 50% smaller with more waveform error.
Neither software comparisons nor successful builds qualify physical buzzer
intelligibility or the refill deadline under Bluetooth/radio load.
`scripts/generate_g726_voice.py` regenerates both experimental catalogs from
the approved mono 8 kHz Zira WAVs using FFmpeg's MSB-first `g726` encoder.
Production IMA headers cannot be selected as its output. The decoder's Sun
provenance, attributed FFmpeg MIX interpolation and LGPL license are retained
in `src/helpers/ui/g726`; no claim of independent ITU conformance is made.
### nRF52 Bluetooth reconnect qualification
Full Companions persist the paired client's notification subscriptions (CCCDs)
@@ -441,6 +525,26 @@ their views. Companion session state is device-wide, so use one active
Companion application at a time. On nRF52, BLE remains available while USB is
in terminal or mOTA mode.
The Companion TCP listener starts only after a usable WiFi station or setup-AP
address exists. If opening the socket fails, it closes any failed-start socket
and retries with a delay capped at 30 seconds, without restarting the WiFi
connection or disturbing BLE, WebConfig, OTA, or a healthy accepted TCP client.
Turning WiFi off or suspending it cancels the listener request; retry service
cannot turn it back on. Use `get wifi.tcp` through BLE, USB, or the browser CLI
to inspect listener state, registration, accepted clients, completed RX/TX
frames, and errors without a PC port probe. `get display.wifi` also shows the
actual `tcp` listener flag independently of the network-services flag. A
listening socket alone cannot detect LAN firewall or guest-network isolation.
ESP32 TCP sends are non-blocking, and inbound commands are serviced alongside
outgoing data rather than waiting for an empty transmit queue. Unsolicited
radio logs therefore cannot keep the app handshake unread. A live client with
pending output is disconnected after 10 seconds without write progress;
other transports continue running. Required replies retain their queue slots,
so a new command waits if no reply can be admitted. Partially sent frames
remain pinned to preserve framing, while already queued replies keep the same
reconnection rules below.
On WiFi TCP port 5000, reconnecting from the same IP retains queued outgoing
frames. A different IP takes over immediately, but never receives the previous
IP's replies: up to four queued frames are parked separately for 30 seconds
@@ -478,13 +582,44 @@ or UART backpressure pauses the contact stream; a frame may drain through a
smaller hardware FIFO in ordered chunks, but its remainder is retained and no
later frame can interleave with it or cause it to be discarded.
Binary login, status, telemetry, path-discovery, binary-request, and trace
results also have bounded producer retention when that transport queue is
full. One request result and one trace result can wait independently, on their
original captured interfaces. Their required `SENT` frames are retained and
admitted before the final result. A first valid radio result gets up to ten
seconds for whole-frame queue admission; a still-blocked `SENT` keeps its
earlier ten-second deadline. Duplicate callbacks cannot replace the retained
result or extend these deadlines. Disconnect, cancellation, or expiry drops
an unadmitted result. Queue admission is not proof of physical host receipt.
Reflected request tags are matched against the complete peer public key.
Recent request and trace history has eight slots each, reserved before radio
transmission; live entries are never evicted to accept a newer request. A full
history or a quarantined identifier returns `ERR_BAD_STATE`, so clients must
back off. Retired reflected tags and trace tag/auth tuples are suppressed for
at least ten seconds and through the original radio deadline plus ten seconds
when longer. Login retries to the same peer have at least a thirty-second
quarantine. Positively matched newer status/telemetry/binary replies still work
after login, but otherwise unsolicited responses from that login peer can be
suppressed during the quarantine.
These guards are finite and clear at reboot. Login replies carry a server
timestamp, not an echoed request nonce; sufficiently late replies or a
timestamp collision cannot always be distinguished without a protocol change.
Trace tag/auth fields are correlation data, not cryptographic authentication.
The guards do not authenticate a host process or retract frames already
accepted by a transport. In particular, the same-IP TCP queue replay described
above remains unchanged, including its lack of authentication.
When a BLE client requests pairing, a display-equipped build wakes the screen,
switches to the first home page, and keeps the active six-digit PIN visible
until Bluetooth connects or the two-minute pairing window expires. USB, WiFi,
Ethernet, and hardware-serial connections do not suppress this screen. With no
saved BLE PIN, display builds generate a new PIN at boot; builds without a
physical display use `123456`. A PIN saved through the Companion protocol takes
effect after reboot.
physical display use `123456`. Use `set pin 654321` in the Companion CLI to save
a fixed pairing PIN, then `reboot` to activate it. `set pin 0` restores the
default/generated PIN policy. A PIN saved through the Companion protocol also
takes effect after reboot. See the [PIN command reference](cli_commands.md#set-the-bluetooth-pairing-pin-companion).
The Bluetooth device name is independently configurable. In the text terminal,
use `get bluetooth.name` and `set bluetooth.name <name>`; use
@@ -613,7 +748,17 @@ that a reboot is required. Use `set usb.logging on reboot` to save it and have
the node reboot automatically after the reply. The second interface appears
after that reboot. Likewise, `set usb.logging off reboot` removes interface
`02`. The optional `reboot` word is accepted only in these exact command forms
and triggers a reboot only when the descriptor actually needs to change.
and triggers a reboot only when the USB logging mode or descriptor needs to change.
For serial bridges that use one port for both commands and packets, including
unmodified Cisien/meshcoretomqtt, use `set usb.logging stream reboot` instead.
This saved packet-stream mode keeps only interface `00` and emits plaintext
RAW/RX/TX records together with repeater-style ` -> ` CLI replies. Configure
the bridge with the stable `*-if00` path. Binary Companion and serial mOTA are
unavailable on that port while logging is enabled; `set usb.logging off` stops
the stream and leaves the ASCII terminal active. BLE remains available.
`get usb.logging` reports `stream` and the primary port after reboot. To return
to separate logging, use `set usb.logging on reboot`.
These are Full Companion **text-terminal** commands. The superficially similar
`meshcli ... get usb.logging` command uses the Binary Companion parameter
+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>
+44
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.
@@ -5,6 +9,46 @@ The canonical LUT is `firmware/esp32_partition_catalog.json` in this fork.
assets. MeshCore Open bundles an identical copy for offline phone updates.
There is no need to run a command on the phone or download old firmware there.
## Smallest partition by board
The table shows each board's **smallest known application partition** across
all cataloged releases, roles, and profiles. It does not count smaller NVS or
filesystem partitions. Sizes use MiB (1,048,576 bytes), with exact bytes below.
Mixed layouts means some releases have dual-slot OTA and others do not.
These are historical factory-image minimums, not measurements of your device;
use `get storage.layout` to check its actual installed layout.
<div class="partition-catalog" id="partition-catalog">
<div class="partition-catalog-controls">
<label for="partition-catalog-search">Board</label>
<input id="partition-catalog-search" type="search" placeholder="Search board name" autocomplete="off" disabled>
<a id="partition-catalog-open" href="../_data/esp32_partition_catalog.json">Open JSON</a>
<a href="../_data/esp32_partition_catalog.json" download="esp32_partition_catalog.json">Download JSON</a>
</div>
<p id="partition-catalog-status" role="status" aria-live="polite">Loading catalog JSON...</p>
<p id="partition-catalog-count" role="status" aria-live="polite"></p>
<div class="partition-catalog-table" id="partition-catalog-table" tabindex="0" aria-label="Smallest application partition by board" hidden>
<table>
<thead><tr><th scope="col">Board</th><th scope="col">Smallest app partition</th><th scope="col">OTA layouts</th></tr></thead>
<tbody id="partition-catalog-boards"></tbody>
</table>
</div>
<details class="partition-catalog-raw" id="partition-catalog-raw">
<summary>Show JSON details</summary>
<div class="partition-catalog-controls">
<label for="partition-catalog-view">JSON view</label>
<select id="partition-catalog-view" disabled>
<option value="layouts">Snapshot and partition layouts</option>
<option value="releases">Release inventory</option>
<option value="all">Complete catalog (large)</option>
</select>
</div>
<pre class="partition-catalog-json" tabindex="0" aria-label="Partition catalog JSON"><code id="partition-catalog-json">Expand this section to view the JSON.</code></pre>
</details>
</div>
With JavaScript disabled, use [the complete JSON file](_data/esp32_partition_catalog.json).
## Coverage
The initial snapshot enumerates every published release (including prereleases,
+154 -38
View File
@@ -1,11 +1,30 @@
# ESP32 legacy-partition migration over Wi-Fi or LoRa
<!-- meshcore-hosted-doc-link:start -->
<p class="meshcore-hosted-doc-link"><a href="https://mikecarper.github.io/MeshCore/esp32_wifi_partition_migration/">View this page on MeshCore Docs</a>.</p>
<!-- meshcore-hosted-doc-link:end -->
# ESP32 legacy-partition migration over USB, Wi-Fi or LoRa
The partition-migration bridge lets a supported ESP32 node move from a small
dual-OTA layout to a verified larger dual-OTA layout without cable flashing or
a full-chip erase. It fits a legacy 1.25 MiB slot. The Wi-Fi version exposes an
updater for the full image after migration. The LoRa version retains the old
OTA-capable repeater in the other expanded slot and returns to it after
restoring identity, so it can fetch the full image over LoRa.
updater for the Full image after migration. The LoRa version preserves a
verified old OTA receiver so it can fetch the Full image into the expanded
inactive slot. On 8/16 MiB flash the bridge restores identity before handing
off; the 4 MiB route keeps identity staged until the Full image's first boot.
The distinct USB version performs verified identity/configuration restoration
before the host writes the final app. It keeps application USB CDC disabled
during flash operations and prevents Arduino startup from automatically
erasing damaged/full NVS. S3/C3 return to ROM download automatically once
restoration settles; original ESP32 reports matching-MAC settled UART status
before the host uses DTR/RTS. Neither route needs physical BOOT access, but
classic boards require their working USB-UART automatic-reset circuit.
The host must verify the same physical USB device and original chip MAC, the
exact target table, CRC-valid NVS restored/no-pending markers, and current OTA
metadata before flashing the unchanged final app. USB reappearance, a timer,
or settled status alone is not recovery proof. An interrupted migration retains
its pending host record and device-side restoration markers for a safe retry.
It has explicit, byte-for-byte generated target plans for the 4 MiB MeshCore
dual-OTA Full table and Arduino ESP32 `default_8MB.csv` and
@@ -19,6 +38,37 @@ table-prefix test; the bridge core is otherwise shared.
| 8 MiB | `default_8MB.csv` | 0x330000 / 0x330000 | Seeed XIAO ESP32-S3, Heltec V3 |
| 16 MiB | `default_16MB.csv` | 0x640000 / 0x640000 | Heltec V4 / V4.3, Station G2 |
These are the supported migration plans. The 8/16 MiB tables retain more
filesystem storage and a coredump partition; their application slots are not
the absolute maximum permitted by the physical flash capacity.
## Ordinary Full releases and installation
Ordinary ESP32 node releases use Full for each exact board and firmware role.
The existing RAM and application-size guards still apply: a failed Full
qualification fails that release target rather than publishing a smaller
ordinary image. Direct `--standard` builds and reduced identities remain
compatibility/development tools outside the ordinary release selection.
An app-only update cannot change the installed partition table. If a device
already has the required layout, use the matching Full application through a
supported updater. If its layout needs expansion, use a verified migration
ZIP for its exact entry in `--list-boards` and an updater supported by the old
image. Otherwise, install the exact board/role's Full `-merged.bin` through
USB/serial using that board's flashing procedure; the merged image contains
the bootloader, partition table, and application. Never upload a merged image
through an app-only browser or LoRa updater.
The new Full defaults outside the migration catalog, including ESP32-C6
boards and boards introduced with `min_spiffs.csv`, have no reviewed wireless
expansion package in this recipe. If their Full app fits the unchanged live
layout and update identity, an ordinary app update can suffice. A table change
requires USB/serial until a matching migration package is reviewed. A Full
image fitting physical flash does not by itself prove it can be installed
through an old updater. Full Companion images with a single app slot likewise
need USB/serial for local firmware replacement; their host-backed LoRa sending
role does not provide a second local update slot.
## Repeatable ESP32 build recipe
From a clean commit, run the shell menu in WSL/Linux:
@@ -69,28 +119,68 @@ ZIPs appear under `.releases/esp32-expanded-<commit>/packages/`. The recipe
requires a clean checkout so each ZIP identifies the firmware commit it was
built from. It creates files only; it does not flash a device or erase flash.
## Independent USB firmware set
The USB intermediate firmware has six distinct chip/flash-capacity builds:
| Chip | Flash capacities | USB completion |
| --- | --- | --- |
| Original ESP32 | 4, 8 MiB | Settled UART status, then host DTR/RTS |
| ESP32-C3 | 4 MiB | Automatic ROM return |
| ESP32-S3 | 4, 8, 16 MiB | Automatic ROM return |
To build only this complete set, without rebuilding or relabeling any Full
firmware release, run serially in WSL/Linux:
```bash
python3 -B scripts/build_esp32_partition_migration.py --usb-only
```
This creates `meshcore-usb-partition-bridges.zip` under
`.releases/esp32-usb-bridges-<commit>/`. Its manifest records each binary's chip,
capacity, bytes and SHA-256, plus the utility source commit, dirty-tree state
and runtime/configuration source hashes. All six are mandatory, and both the
USB migration and NVS no-autoerase guard markers are verified. Existing Wi-Fi
and LoRa artifacts remain unchanged. Exact new board/role migration packages
also include their matching `usb-bridge.bin`.
A host can pair this standalone set with an older, exact Full migration ZIP;
the Full image retains its original release/source identity. Meshfirmware
downloads a versioned, checksum-pinned published USB set when a qualified
older package has no USB bridge. It does not use an untested `latest` bridge,
and an ordinary update that fits does not download migration utilities.
For an offline or explicitly selected set, use `MESHFIRMWARE_USB_MIGRATION_SET`,
or place the ZIP beside the flasher at `tools/meshcore-usb-partition-bridges.zip`.
The original Full
package must still match the selected final image and prove the target layout.
No app-only upload or manual partition-table write substitutes for that proof.
The catalog covers canonical ESP32 repeater, room-server, and sensor roles on
4, 8, and 16 MiB chip-family plans, including historical 1.25 MiB candidates
now built with expanded tables. This includes ESP32-S3, original ESP32, and
ESP32-C3 boards with an evidenced old default layout. `--list-boards` is the
authoritative list of exact targets; specialty ESP-NOW/observer aliases are
separate identities and are not packaged under their old IDs. With Wi-Fi OTA
they can migrate to the
same physical board's canonical role image; their feature settings may need
to be recreated. The LoRa route still requires an exact old target ID. The
they can migrate to the same physical board's canonical role image. The Wi-Fi
bridge verifies and restores its supported saved settings; other settings may
need to be recreated. The LoRa route still requires an exact old target ID. The
catalog omits boards with no evidenced legacy default-layout build, such as
boards introduced with `min_spiffs.csv` or another non-1.25 MiB layout. Some
listed boards use larger slots today but had historical default-layout builds.
A listed package is usable only when the installed old image has a working
Wi-Fi updater and the bridge verifies its live source table. Menu inclusion
does **not** override either check. The only LoRa
bridge packages currently available are the exact-target Heltec V4 and XIAO
S3 WIO repeater packages. On 4 MiB flash the expanded app1 overlaps old app1,
so the currently safe LoRa receiver-copy method cannot be offered there. A
4 MiB Full build offers 1,984 KiB slots, not 4 MiB per slot. A Wi-Fi-only ZIP
can omit `full-application.mota` when its application exceeds the current
2 KiB x 1024-block LoRa serving limit; `full-application.bin` remains the
verified Wi-Fi update image.
A listed package is usable only when the installed old image supports the
selected updater and the bridge verifies its live source table. Menu
inclusion does **not** override either check.
Every listed board/role has Wi-Fi and LoRa bridge recipes. Heltec V4 and XIAO
S3 WIO repeaters retain their board-specific bridge names; the other entries
use matching original-ESP32, ESP32-S3, or ESP32-C3 chip-family bridges. LoRa
still requires a verified same-target old receiver, and 4 MiB boards require
the slot-B-only sequence below. A 4 MiB Full build offers 1,984 KiB per slot.
The packager verifies both the Full application's slot fit and its mOTA
staging overhead. It supports at most 4096 blocks of 2 KiB for the Full
transfer, and records the seeder proof-scratch requirement in the manifest.
Older 4 KiB-scratch seeders are limited to 1024 blocks. A package fails if
its required Full LoRa transfer cannot fit the supported staging/block limits.
Other ESP32 boards need a reviewed source updater, chip/flash-size bridge,
verified Full image, and board entry before they can be added. The LoRa route
@@ -102,15 +192,23 @@ Matching flash capacity alone is not enough to claim a package is safe.
Before it touches the partition table, the bridge reads the historical
`/identity/_main.id` SPIFFS file. It requires all 96 bytes (32-byte public key
followed by its 64-byte private key), stages them in an NVS namespace that is
unchanged by both target layouts, then changes the table. On the first boot
with the expanded layout it writes the staged value into SPIFFS, reads it back
byte-for-byte, and only then clears the NVS staging record and offers the final
uploader.
unchanged by the source and target layouts, then changes the table. The Wi-Fi
bridge and 8/16 MiB LoRa bridge restore the staged value into expanded SPIFFS,
read it back byte-for-byte, and only then clear the NVS staging record. On the
4 MiB LoRa route, the Full application's first boot performs this restoration.
The private identity is the guaranteed preservation boundary. The old SPIFFS
image is deliberately not raw-copied into an expanded partition: SPIFFS is not
safe to resize that way. Other SPIFFS-backed settings can be recreated after
the full image boots.
The Wi-Fi bridge also stages and verifies its supported saved configuration:
node name and preferences, radio profiles, ACL and login replay state, region
keys, and selected network/OTA, filtering, display, and telemetry settings.
It refuses migration if the saved files cannot fit or verify in NVS. The older
LoRa handoff route preserves only the private identity, so saved settings may
reset. The two board-specific Partition Expander routes preserve their
supported configuration and automatically fetch the exact-target Full image;
follow the package README for those routes.
The old SPIFFS image is deliberately not raw-copied into an expanded partition:
SPIFFS is not safe to resize that way. Files outside the supported preservation
set, including logs and temporary radio sessions, are not preserved.
Migration does erase the 4 KiB partition-table sector and the destination app
sectors. Expanded SPIFFS can be reformatted. It never issues a full-chip erase.
@@ -129,8 +227,9 @@ This means the source app-slot and SPIFFS sizes can vary; the bridge validates
the live geometry instead of carrying board-name rules. It also ensures it is
running from one source OTA slot. If it arrived in B, it CRC-copies itself to
the future A address, selects A in OTA metadata, writes the table, and
restarts. It therefore works whether the browser OTA updater placed the bridge
in legacy A or B.
restarts. The Wi-Fi route therefore works whether the browser OTA updater
placed the bridge in legacy A or B. Single-app source layouts cannot use this
route.
Keep power stable while the bridge is replacing the 4 KiB table sector. A loss
of power before that point leaves the old table and source intact. After that
@@ -154,26 +253,43 @@ bootloader and table offsets for cable flashing, not browser OTA.
`xiao_s3_partition_legacy_seed` is a disposable-hardware test image only. It
models the legacy table and an identity file; it is not a deployable repeater.
## LoRa-only migration of a repeater
## LoRa-only migration of a listed role
The exact old repeater must already support MeshCore LoRa mOTA and have a valid
The exact old board/role must already support MeshCore LoRa mOTA and have a valid
EndF image identity. The LoRa bridge checks the old app's target ID and body
hash before any partition-table write; an unsupported or damaged receiver is
refused. It works from either legacy A or B. It copies legacy B to the future
B address, boots the bridge once to restore the private key in expanded
SPIFFS, then boots the preserved old repeater. That repeater can receive the
full image into its now-expanded inactive slot.
refused. Keep a capable seeder on the old receiver's compiled default radio
profile before starting: this route may reset saved radio settings. Verify
that the seeder lists the Full image and has the manifest's required proof
scratch before installing the first-stage bridge.
On 8/16 MiB flash the bridge can run from either legacy A or B. It copies
legacy B to the future B address, boots the bridge once to restore the private
key in expanded SPIFFS, then boots the preserved old receiver. That receiver
can fetch the Full image into its now-expanded inactive slot.
On 4 MiB flash the expanded app1 overlaps old app1, so that copy cannot be
used. A verified old same-target receiver must remain in old A and the bridge
must be installed in old B; a bridge in A refuses migration. If the old node
is running B, first install and boot a working same-target old receiver in A,
then install the bridge in B. After expansion the old receiver in A may use a
temporary identity. The original key stays staged in NVS until the Full image
restores it on first boot. Complete the second update before treating that
device as the migrated node.
The board-specific LoRa bridges are `heltec_v4_partition_migrator_lora_repeater`
and `xiao_s3_partition_migrator_lora_repeater`. Build the final repeater with
and `xiao_s3_partition_migrator_lora_repeater`; the other listed roles use their
configured chip-family LoRa bridges. Build the final application with
`bash build.sh build-firmware <target> --full-exact` so its mOTA target ID
still matches the old repeater. `scripts/package_esp32_partition_migration.py`
still matches the old role. The repeatable recipe already uses this selector.
`scripts/package_esp32_partition_migration.py`
checks the images, partition tables, hashes and mOTA containers and creates
separate Wi-Fi/LoRa migration ZIPs for those two boards. The ZIP README gives
one Wi-Fi/LoRa migration ZIP for each selected exact board/role. The ZIP README gives
the exact `ota ls`, `ota pull <id> flash`, `ota install` sequence.
A stock repeater without LoRa mOTA cannot use the LoRa-only route. The
Wi-Fi bridge remains the route for that device.
An old image without LoRa mOTA cannot use the LoRa-only route. Use its working
Wi-Fi updater with a listed package, or USB/serial if no supported updater or
package is available.
## Heltec V4 / V4.3
+31 -14
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
@@ -11,10 +15,13 @@ LoRa network A <--> Repeater A <-- ESP-NOW --> Repeater B <--> LoRa network B
## 1. Choose the firmware
Install the exact board's **Full ESP-NOW bridge repeater** image on each board.
The target name ends in `_repeater_bridge_espnow`; a standard Heltec V4 uses
`heltec_v4_repeater_bridge_espnow`. Full/MQTT and Full Companion images are
different choices. See the [firmware picker guide](firmware_picker.md).
Install the exact board's **Full repeater** image on each board. ESP-NOW is
included as a runtime transport on supported ESP32 Full repeaters. Newly
combined ordinary repeaters leave it off until explicitly enabled; that
choice is saved across reboot. Boards with measured capacity exceptions keep
separate `_repeater_bridge_espnow` images. Historical dedicated target names
remain available for direct builds with their original bridge-on defaults.
Full Companion is a different role. See the [firmware picker guide](firmware_picker.md).
Open each board's [USB console](https://flasher.meshcore.io/console) at
115200 baud and run:
@@ -23,8 +30,11 @@ Open each board's [USB console](https://flasher.meshcore.io/console) at
get bridge.type
```
The answer must be `espnow`. `mqtt`, `rs232`, or `none` means that image does
not provide the ESP-NOW bridge described here.
The answer can be `espnow`, `mqtt+espnow`, `rs232+espnow`, or
`mqtt+rs232+espnow`, depending on the image. Confirm the
independent ESP-NOW control is present with `get espnow.enabled` and
`get espnow.running`. The `espnow` commands select this transport without
changing another bridge in a combined image.
## 2. Set each board's LoRa network
@@ -33,7 +43,7 @@ the radio settings used by the network on that board's side. Use `Bridge-A`
for the first board and `Bridge-B` for the second, or choose your own names.
```text
set bridge.enabled off
set espnow.enabled off
set name Bridge-A
set radio <freq>,<bw>,<sf>,<cr>
reboot
@@ -60,14 +70,14 @@ case-sensitive secret on each board, using 1-15 characters. Channel `6` is
an example; both boards must use the same channel.
```text
set bridge.enabled off
set espnow.enabled off
set bridge.channel 6
set bridge.format wrapped
set bridge.secret YourSharedKey
set bridge.source tx
set bridge.delay 500
set repeat on
set bridge.enabled on
set espnow.enabled on
```
These settings are saved across reboot. Bridge changes apply immediately;
@@ -78,15 +88,16 @@ Stopping it first lets you finish configuring both sides before connecting them.
| Option | Choices and purpose |
| --- | --- |
| `bridge.enabled` | `on` starts the bridge; `off` stops it and keeps its saved settings. |
| `espnow.enabled` | `on` starts ESP-NOW; `off` stops it and keeps its saved settings. Independent of MQTT or RS-232. |
| `rs232.enabled` | On combined UART images, starts or stops RS-232 independently of MQTT and ESP-NOW. Use `get rs232.running` to check the live UART. |
| `bridge.channel` | The 2.4 GHz channel, 1-13. Match it on both boards. This is separate from the LoRa frequency in `set radio`. |
| `bridge.format` | `wrapped` is the default for this two-repeater setup and uses the shared secret. `raw` connects to primary-ESP-NOW nodes such as `Generic_ESPNOW` or `SenseCapIndicator-ESPNow`; it ignores the secret. Both peers must use compatible formats. |
| `bridge.secret` | A matching, case-sensitive value of 1-15 characters for `wrapped` mode. It separates bridge groups; it is not strong encryption. MeshCore's own message encryption remains in place. |
| `bridge.source` | `tx` crosses packets this repeater transmits on LoRa, after its forwarding decisions; use this for the setup above. `rx` crosses received packets before those decisions and can pass traffic the local repeater would not forward. |
| `bridge.delay` | Wait before processing a packet received from the other bridge, in milliseconds. Range: 0-10000. Start with the default `500`. |
| `repeat` | `on` enables normal LoRa forwarding; `off` disables forwarding. Keep it `on` on both bridge repeaters. Normal routing, scope, and packet-filter rules still apply. |
| `bridge.type` | Read-only firmware capability. It must report `espnow` for this guide. |
| `bridge.running` | Read-only runtime state. `on` means the local bridge started; it does not prove the other board is reachable. |
| `bridge.type` | Read-only firmware capability, including combined transports. |
| `espnow.running` | Read-only ESP-NOW runtime state. `on` means the local bridge started; it does not prove the other board is reachable. |
This fork uses **`set bridge.source tx`** or **`rx`**. Some upstream guides
use `logTx` and `logRx`; those are not the command values used here.
@@ -99,8 +110,8 @@ LoRa repeater, see [raw ESP-NOW compatibility](WiFi.md#wifi-companion-setup).
Run on both boards:
```text
get bridge.enabled
get bridge.running
get espnow.enabled
get espnow.running
get bridge.channel
get bridge.format
get bridge.secret
@@ -127,6 +138,12 @@ If it does not work:
and check the networks' forwarding and scope rules.
A running bridge keeps the device awake, even with device power saving enabled.
ESP-NOW shares the WiFi radio: use the connected WiFi network's channel when
WiFi is active. Browser OTA pauses ESP-NOW on ordinary non-MQTT repeaters,
reports that pause, and resumes saved-enabled ESP-NOW when OTA is stopped.
A failed OTA start restores the bridge. The RS-232 transport remains available
on combined boards while ESP-NOW is paused.
For every command's full details, see the [bridge CLI reference](cli_commands.md#bridge-when-bridge-support-is-compiled-in).
For a broader comparison with a Linux-based bridge, see the
+7 -3
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
@@ -872,13 +876,13 @@ After this bootloader is flashed onto the device, you can trigger an over-the-ai
filename).
2. From the MeshCore app, log in remotely to the repeater you want to update with admin privileges.
3. Go to the Command Line tab, type `start ota` and hit enter.
4. You should see `OK` to confirm the repeater device is now in OTA mode.
4. The reply confirms WiFi is on and gives the OTA upload URL and network to use.
5. If the ESP32 is not already joined to Wi-Fi, `start ota` starts an open hotspot named `MeshCore-OTA` at `192.168.4.1`. If it is already joined, the command reports and uses its router-assigned address instead.
6. From your phone or computer, connect to the `MeshCore-OTA` hotspot when one was started.
7. Open the URL reported by `start ota` and upload the non-merged bin from the flasher. When the device started `MeshCore-OTA`, the URL is <http://192.168.4.1/update>.
On an MQTT observer, stop WebConfig before running `start ota`; both servers use
HTTP port 80. Likewise, stop the OTA uploader before running `start webconfig`.
`start ota` automatically stops WebConfig and reports the change; both servers
use HTTP port 80. Stop the OTA uploader before running `start webconfig` again.
### 7.3. Q: Is there a way to lower the chance of a failed OTA device firmware update (DFU)?
+46
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
@@ -24,6 +28,20 @@ Start with an example or build a rule, then test the draft against packet facts
in the simulator below. The examples draw from
[Flood Filtering and Moderation](flood_filtering.md).
To match every 1-byte path, select **Path hash width → 1 byte**, **type → ANY**,
all hops, no channel/input-scope restriction, and no prefix condition (or use
the 1-byte example below). This also matches zero-hop packets. The firmware command is
`set fr any pb=1 d` (or `set flood.rule type=any hashbytes=1 drop`); it adds a
forwarding-drop rule, including for relayed login/admin floods. Direct routes
and local delivery remain unaffected. Width-only rules require updated
firmware. The simulator's policy definitions remain design-preview text, not
installable commands.
Select **2+ bytes (2 or 3)** to match both larger widths in one rule. The
firmware command is `set fr any pb=2+ d`. A rate-limit action on this rule uses
one allowance shared by both widths, rather than a separate limit for each.
Four-byte paths remain unsupported.
<div class="filter-tool" data-filter-tool>
<div class="filter-management-safety" role="note">
<strong>Remote login and direct routes</strong>
@@ -68,6 +86,7 @@ in the simulator below. The examples draw from
<tr><td><code>channel=</code></td><td><code>*</code> means no channel condition; a name or key authenticates one group channel; <code>hash:XX</code> is an unauthenticated one-byte fallback</td></tr>
<tr><td><code>rx.scope=</code></td><td>Original incoming transport scope</td></tr>
<tr><td><code>path=</code></td><td>Path prefix, blacklist, bucket, or loop match</td></tr>
<tr><td><code>hashbytes=</code></td><td>Encoded path-hash width: 1, 2, 3, or 2+ (2 and 3 bytes), including zero-hop packets</td></tr>
<tr><td><code>tempradio=</code></td><td>Temporary-radio state</td></tr>
<tr><td><code>do drop</code></td><td>Do not retransmit</td></tr>
<tr><td><code>do scope=</code></td><td>Set the outgoing transport scope</td></tr>
@@ -82,6 +101,14 @@ in the simulator below. The examples draw from
</p>
</div>
<div class="filter-example-grid">
<button type="button" data-example="hash_width">
<span>Drop all floods with 1-byte path hashes (including zero hops)</span>
<code>when type=any hops=all hashbytes=1 do drop</code>
</button>
<button type="button" data-example="hash_width_two_plus">
<span>Drop all floods with 2- or 3-byte path hashes (including zero hops)</span>
<code>when type=any hops=all hashbytes=2+ do drop</code>
</button>
<button type="button" data-example="channel_scope">
<span>Set #BlackHole86 scope on all #rgdata group traffic</span>
<code>when type=class:group hops=all channel=#rgdata do scope=#BlackHole86 timing=fast</code>
@@ -185,6 +212,16 @@ in the simulator below. The examples draw from
<option value="region:west"></option>
</datalist>
</label>
<label>
Path hash width
<select data-field="hash-bytes">
<option value="0">Any width</option>
<option value="1">1 byte</option>
<option value="2">2 bytes</option>
<option value="2+">2+ bytes (2 or 3)</option>
<option value="3">3 bytes</option>
</select>
</label>
<label>
Path matcher
<select data-field="path-kind">
@@ -497,6 +534,15 @@ in the simulator below. The examples draw from
Channel
<input data-packet="channel" value="#rgdata" placeholder="#name, public, hash:A7, key, or blank">
</label>
<label>
Encoded path hash width
<select data-packet="hash-bytes">
<option value="auto">Infer from path (empty defaults to 1 byte)</option>
<option value="1">1 byte</option>
<option value="2">2 bytes</option>
<option value="3">3 bytes</option>
</select>
</label>
<label>
Received pbyte path
<input data-packet="path" value="860C,12A4" placeholder="same-width IDs">
+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>
+126 -45
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
@@ -8,27 +12,46 @@ MQTT and GPS show their app/WebConfig steps instead. See
The [USB web console](https://flasher.meshcore.io/console) works with the
default ASCII terminal on USB Companion and infrastructure roles.
Pick the choices in any order. Every selection narrows all the other controls
to firmware combinations that were actually built in the current release set.
Pick the choices in any order. The controls stay limited to compatible
firmware combinations that were actually built in the current release set.
The result count stays visible while you scroll through the filters on a phone.
Once the exact board and role are selected, the picker skips logging,
connection, and profile choices that all select the same firmware. Logging
settings move into **Restore your settings after flashing** beneath the files.
Choices that select different images remain visible.
The optional chip-family filter (ESP32, nRF52, RP2040, or STM32) can narrow the
hardware list first. You can skip it: picking hardware fills it in automatically.
Chip family and hardware are the only dropdowns. All remaining choices use
buttons. **Firmware profile** combines OTA support, Full/standard, and sensor
or storage differences in one choice.
or storage differences when a choice between different images is needed.
RAK3401 and RAK4631 repeaters use one unified image per sensor policy for
supported internal and external OTA storage. **Full** and **Reduced** choose
optional sensor drivers; they do not select a storage module. The unified
RAK4631 image also includes RS-232 on UART2, which is recommended for new
installations. The unified image reserves UART1 for potentially powered GPS
hardware; header-connected W25 flash also occupies UART1 pins. Legacy GPS-free
Serial1 downloads remain available through exact-filename search for existing
wiring, but are omitted from normal choices when the matching unified sensor
profile is available. Ethernet remains a separate RAK4631 hardware image.
LoRa OTA still requires the matching
OTAFIX bootloader and a package compatible with the installed target.
The picker reads public release metadata from GitHub. It does not upload device
information. Hardware names, target names, and download links come directly
from the published firmware assets.
<div class="firmware-picker" data-firmware-picker data-release-repo="mikecarper/MeshCore" data-controls-url="../_data/firmware_controls.json?v=1.17.1.8-runtime-1" data-bootloaders-url="../_data/bootloader_manifest.json?v=20260929-2" data-share-url="https://mikecarper.github.io/MeshCore/firmware_picker/">
<div class="firmware-picker" data-firmware-picker data-release-repo="mikecarper/MeshCore" data-controls-url="../_data/firmware_controls.json?v=1.17.1.9-88c85108" data-bootloaders-url="../_data/bootloader_manifest.json?v=20261002-1" data-share-url="https://mikecarper.github.io/MeshCore/p/">
<div class="firmware-picker-intro" role="note">
<strong>Current release set</strong>
<p data-role="release-set">Loading release information...</p>
<p>
For a new installation, choose the exact board and role, prefer a
<strong>FULL / complete profile</strong> when it is available, and select
<strong>Full install / layout migration (merged .bin)</strong>. Narrower profiles
remain available when their reduced transport or feature set is intentional.
For a new installation, choose the exact board and role, and select
<strong>Full install / layout migration (merged .bin)</strong>. Current ESP32
releases combine the supported features in Full; use runtime settings to
choose the active transports. Where separate profiles remain, prefer
<strong>FULL / complete</strong> when it meets your update and hardware
requirements. Other chip families retain their qualified profiles.
</p>
<p>
Upgrading an existing ESP32 infrastructure node from a smaller partition
@@ -47,10 +70,18 @@ from the published firmware assets.
<p class="firmware-picker-order-note">
Pick in any order. Use <strong>Any</strong> to clear one choice, or clear
everything with the button below. The address bar updates with your choices;
copy its URL to reopen or share the same selection.
use <strong>Copy link to settings</strong> for a shorter link to the same selection.
</p>
<form class="firmware-picker-form" data-role="form">
<div class="firmware-picker-feedback firmware-picker-wide" data-role="filter-feedback" hidden>
<div aria-live="polite" aria-atomic="true">
<strong data-role="filter-count"></strong>
<p data-role="filter-note"></p>
</div>
<a data-role="view-results" hidden>View files</a>
</div>
<details class="firmware-picker-chip-family firmware-picker-wide">
<summary data-role="chip-family-summary">Optional: chip family</summary>
<p id="firmware-picker-chip-help">Skip this if you know your board. Picking hardware selects its chip family automatically. Choose Any to clear this filter.</p>
@@ -115,7 +146,7 @@ from the published firmware assets.
<div class="firmware-picker-form-actions firmware-picker-wide">
<button type="reset" data-action="clear" disabled>Clear all choices</button>
<button type="button" data-action="copy-link" disabled>Copy link to settings</button>
<a data-role="share-link" hidden>Link to these settings</a>
<a data-role="share-link" hidden>Short link to these settings</a>
<p data-role="link-status" aria-live="polite"></p>
</div>
</form>
@@ -168,9 +199,11 @@ from the published firmware assets.
| USB logging / USB-connected MQTT | Node remains attached to a computer over a data-capable USB cable |
| Wi-Fi MQTT observer | Firmware connects directly to MQTT over Wi-Fi; this is not USB logging |
| USB logging + Wi-Fi MQTT | Unified FULL image sends to both paths; avoid two publishers aimed at the same broker unless messages are deduplicated |
| No logging | Normal standalone operation without the dedicated logging/MQTT profile |
| No logging | Normal standalone operation with external logging output disabled |
| Receives LoRa OTA | Repeater, room server, or sensor image that can stage an exact matching update received over LoRa; any sensor/storage tradeoff appears on the same Firmware profile button |
| Receives LoRa OTA - Reduced optional sensors | Compact OTA image that omits selected optional environmental/ranging drivers while retaining generic I2C and supported board peripherals |
| Reduced sensors + LoRa OTA | Qualified nRF52 repeater, room server, or sensor build with the reduced optional sensor recipe and LoRa OTA retained |
| Full supported sensors + LoRa OTA | Qualified nRF52 build with all sensor drivers supported by that board's full recipe and LoRa OTA retained; does not add support for every possible attached sensor |
| LoRa OTA source only | Full Companion serving a host-supplied update to another node without self-installing it |
Connection and bridge choices depend on the selected role. Companion firmware
@@ -179,19 +212,28 @@ Ethernet transports.
Normal repeater firmware includes runtime-controlled RS-232 support where the
board has room; use `set bridge.enabled on` after configuring `bridge.uart` and
`bridge.baud`. The Wio-E5 remains the capacity exception and offers a separate
RS-232 image. Choose Wi-Fi MQTT under **Logging / MQTT**; it is an output mode,
not a second connection choice. Repeaters may still offer separate ESP-NOW or
Ethernet bridge firmware because those paths use different compiled drivers.
RS-232 image. Choose Wi-Fi MQTT in the result's saved logging settings when
it shares the Full image; separate MQTT images retain a **Logging / MQTT**
filter. Full ESP32 LoRa repeaters include runtime
ESP-NOW when capacity permits; dedicated ESP-NOW images remain for measured
capacity exceptions. Ethernet still needs its exact hardware recipe.
For commands and option explanations, follow the
[ESP-NOW bridge setup guide](espnow_bridge_setup.md).
An ESP-NOW bridge target keeps LoRa as its primary mesh radio. Expanded ESP32
Full repeater and room-server images combine ESP-NOW with WiFi MQTT in the
same firmware. MQTT and ESP-NOW are independent runtime transports: use
`set mqtt.enabled on|off` for MQTT and `set bridge.enabled on|off` (or
`set espnow.enabled on|off`) for ESP-NOW, so either or both can run. Both use
one 2.4 GHz radio, so when both are enabled the ESP-NOW bridge channel must
Full LoRa repeaters include ESP-NOW, including boards without an MQTT
recipe. Existing MQTT-backed Full repeater and room-server images combine
both transports. Use `set espnow.enabled on|off` for ESP-NOW and, where
supported, `set mqtt.enabled on|off` for MQTT. Newly combined ordinary
repeaters start with ESP-NOW off until explicitly enabled, including the first
upgrade from unmarked legacy preferences. Later reboots preserve that setting.
On combined RS-232 boards without MQTT, `bridge.enabled` controls the UART.
Full Heltec V3, WSL3 and RAK3112 MQTT images also retain RS-232;
use `set rs232.enabled on|off` and `get rs232.running` for their independent
UART. T-LoRa V2.1 keeps two Full choices due to its internal RAM limit:
the normal repeater has UART plus ESP-NOW, and the observer has MQTT plus
ESP-NOW. MQTT and ESP-NOW use one 2.4 GHz radio, so when both are enabled the ESP-NOW bridge channel must
match the connected WiFi access point's fixed channel. ESP-NOW-only mode does
not require WiFi credentials. Its runtime
`bridge.format` setting chooses the peer protocol: `wrapped` (the
@@ -201,48 +243,59 @@ backward-compatible bridge-to-bridge default using `bridge.secret`) or `raw`
firmware choice, not two board images. Match `bridge.channel` to the primary
nodes' `espnow.channel` before selecting `set bridge.format raw`.
For Heltec V4 specifically, `companion_radio_full` is still a LoRa-primary
Companion; choose the existing `heltec_v4_repeater_bridge_espnow` firmware to
make that board the LoRa/ESP-NOW gateway. Use its exact merged artifact when
Companion; choose the combined `heltec_v4_repeater` Full firmware and enable
ESP-NOW to make that board the LoRa/ESP-NOW gateway. Use its exact merged artifact when
changing roles or partition layouts.
## Share a selection
Select any combination of choices, then copy the address bar or use **Copy link
to settings**. Opening the link restores those choices after the release
Select any combination of choices, then use **Copy link to settings** for a
short URL under `https://mikecarper.github.io/MeshCore/p/`. Opening the link
restores those choices after the release
catalog loads. Partial selections work too, so you can share a board and role
while leaving other choices open. **Clear all choices** removes the picker
parameters from the URL. Changes update the current browser-history entry
without reloading the page or adding a Back-button entry for every click.
For example, [RAK3401 repeater with internal storage](?hardware=RAK_3401&role=repeater&ota=lora-receiver&variant=no-external-sensors)
preselects that board, role, OTA capability, and storage profile.
For example, [RAK3401 unified Full repeater](https://mikecarper.github.io/MeshCore/p/?s=1.hRAK_3401.rrepeater.olora-receiver.ffull.vdefault)
preselects that board, role, OTA capability, and Full sensor policy. Supported
internal or external storage is detected by the unified image.
The query parameters are `chipFamily`, `hardwareFamily`, `hardware`, `role`,
Short links encode only the selected choices in a versioned `s` parameter.
Hardware identifiers stay readable, and the link omits redundant hardware-family
and automatically selected chip-family values. It keeps profile and install
choices explicitly rather than relying on the current catalog's defaults.
The address bar also uses the compact selection format; copying it works too.
Existing long links still work. Their query parameters are `chipFamily`, `hardwareFamily`, `hardware`, `role`,
`logging`, `ota`, `mode`, `feature`, `variant`, and `install`. Values use the
picker's internal identifiers rather than the displayed labels. One Firmware
profile button sets `ota`, `feature`, and `variant` together. Existing links
with only some of those choices still work and show their partial selection.
The generated
link also records `chipAuto` so automatic chip-family selection or an explicit
The selection also records when chip-family filtering was chosen manually so
automatic chip-family selection or an explicit
**Any** choice behaves the same after reopening. Existing section anchors and
unrelated query parameters are preserved.
Links use the current release catalog. If a linked choice is no longer available
or conflicts with another choice, the picker identifies it and asks you to
review the remaining selections. The downloadable HTML's **Copy link to
settings** button creates a public website link that other people can open.
settings** button creates a short public website link that other people can open.
## FULL versus standard
For a new installation, use the FULL / complete profile when it exists and the
board has enough flash. FULL profiles keep the complete supported feature set
and CLI. Standard profiles remain useful for boards without a FULL build, for
an intentionally narrower transport, or when retaining an existing compatible
partition layout.
Current ESP32 ordinary releases use one Full / complete image per exact board
and role. Full keeps the supported feature set and CLI; runtime switches select
USB, Bluetooth, Wi-Fi, logging, and other qualified features. Separate portable,
minimal, and single-transport ESP32 images are installation/recovery recipes,
not additional ordinary release choices. The documented T-LoRa UART/MQTT RAM
split remains two Full images. Other chip families retain their qualified
capacity and sensor-profile choices.
Changing between standard and FULL ESP32 layouts requires a partition migration.
Moving an older ESP32 installation to an expanded Full layout requires a
partition migration.
Use the exact-board merged image over USB, or a supported exact board and role
staged migration ZIP from the [utility release](https://github.com/mikecarper/MeshCore/releases/tag/utility-v1.17.1.8-halo-keymind-cascade-dev-9053038f).
staged migration ZIP from the [utility release](https://github.com/mikecarper/MeshCore/releases/tag/utility-v1.17.1.9-halo-keymind-cascade-dev-88c85108).
Read that ZIP's README before choosing the Wi-Fi or LoRa route. Do not send a
loose app-only Full `.bin` directly to an older layout: the running application
cannot move its own active and inactive partitions. Run
@@ -253,17 +306,26 @@ guess cannot confirm the installed layout. See the
that command.
Current `full-usb-wifi` profiles use one binary for no external output, USB
packet logging/USB-connected MQTT, direct WiFi MQTT, or both. The picker shows
that same exact binary for each compatible logging choice; select the saved
packet logging/USB-connected MQTT, direct WiFi MQTT, or both. The picker skips
the logging filter when these modes use the same file; select the saved
runtime mode with `set logging.output off|usb|wifi|both`. A FULL logging-fallback
profile is listed only when no WiFi MQTT sibling exists; it appears for both
the no-output and USB choices because `set usb.logging off|on` is persistent.
profile is listed only when no WiFi MQTT sibling exists; it supports both
the no-output and USB settings because `set usb.logging off|on` is persistent.
On a fresh unified FULL install with no saved SSID, the setup AP and WiFi radio
remain available for 30 minutes per boot, then turn off automatically until the
next reboot or power cycle. An explicit administrator `start webconfig` remains
available as an override. A saved SSID switches to the normal indefinite
reconnect behavior instead.
Fresh ESP32 Full infrastructure defaults to USB logging off. Existing saved
logging and device power-saving settings survive an update. Automatic
unconfigured boot setup also pauses network-bridge retries until an explicit
bridge start, allowing its radio to turn off when the AP closes. Manual
WebConfig/WiFi starts preserve an explicitly enabled ESP-NOW bridge, including
when the master radio switch restores the selected services. Compact OTA
images scan before raising a new AP and verify live driver readiness before
reporting the update URL.
Full Companion profiles use one binary for USB, BLE, ordinary Wi-Fi on ESP32,
source-only LoRa OTA, Terminal Chat, optional USB packet logging, and any
board-qualified serial or Ethernet Companion transport. Bulk builds therefore
@@ -396,8 +458,13 @@ is available. ESP32 and other chip families do not show this section. The web
picker checks the latest stable OTAFIX release; the downloadable picker
includes the bootloader release named in its saved catalog. If a board has no
verified mapping or a file is missing, the picker explains that instead of
offering another board's bootloader. MeshTower internal and microSD remain
separate; RAK3401 and RAK4631 each use their adaptive storage bootloader.
offering another board's bootloader. OTAFIX 2.4.11 publishes a combined
MeshTower V2 SD/internal image, mapped only to `Heltec_tower_v2_sdcard`.
The historical `Heltec_tower_v2` internal-only profile is explicitly retired:
move to the normal combined image with a one-time local USB/BLE DFU or SWD
installation, not a cross-profile LoRa bootloader update. The picker does not
silently substitute its bootloader. RAK3401 and RAK4631 each use their
board-specific adaptive storage bootloader.
Ikoka Stick, Nano and Handheld plus SolarXiao use the XIAO bootloader;
Wio Tracker L1 1W and E-Ink use the L1 bootloader. See the
[board coverage audit](https://github.com/mikecarper/Adafruit_nRF52_Bootloader_OTAFIX/blob/feature/ota-delta-apply/docs/meshcore-hardware-coverage.md)
@@ -420,6 +487,20 @@ buttons. They separate revisions, display type, expansion kit, radio/PA layout,
pin map, and other physical differences without crowding the Hardware dropdown.
**Firmware profile** buttons combine OTA support, Full/standard, and choices
that need a different image or wiring, such as serial port or external storage.
The picker skips this step when all available profile choices lead to the
same firmware file.
The 1.17.1.9 nRF52 repeater, room-server and sensor releases publish two separately
qualified choices: **Reduced sensors + LoRa OTA** and
**Full supported sensors + LoRa OTA**, with `-reduced-ota` and `-full-ota` artifact suffixes. Each keeps
its exact board/role OTA identity and storage layout; the suffix is not a new
radio or partition identity. Labels come from the release's qualified
`sensor.profile.reduced` / `sensor.profile.full` metadata and verified LoRa
receiver, not from the filename alone. Both profiles remain separate in local
and published releases, including boards whose optional sensor set is small
enough that the images have little or no size difference. Packaging requires
both profiles with matching packaged OTA identity and flash geometry, even
when a matrix is resumed or partial publication is requested. A failed build
is reported, never presented as a supported download. Companion is unchanged.
The legacy `no_external_sensors` profile is labeled **Receives LoRa OTA -
Reduced optional sensors** on the same button. On RAK3401 and RAK4631 this
compact profile uses internal storage without an external storage board. The
@@ -455,7 +536,7 @@ retains its optional dedicated interface `02`.
Exact filename search finds compatibility images included in the selected
release family. For older releases, open their GitHub release pages.
The 1.17.1.8 public matrix keeps one ESP32 Full image per board and role.
The current public matrix keeps one ESP32 Full image per board and role.
Some richer images, including Station G2 Full, retain an observer-named OTA
identity. Exact older identities needed by a partition migration are packaged
inside that board and role's migration ZIP instead of adding a second ordinary
@@ -463,8 +544,8 @@ Full download. A deliberate target-ID override does not make a different
partition layout or physical board compatible.
Some normal ESP32 roles begin in the legacy 1.25 MiB dual-OTA layout but have
a Full image with larger slots. The 1.17.1.8 utility release contains 94 exact
board and role migration ZIPs. Each package checks its source layout and target
a Full image with larger slots. The utility release lists the exact
board and role migration ZIPs available for that release. Each package checks its source layout and target
identity, contains the bridge and Full application files for its supported
routes, and documents what saved data it preserves. Use the package's own Full
application after the bridge; do not substitute a loose Full image with a
+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).
+33 -1
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
@@ -445,7 +449,7 @@ The extended form is:
```text
set flood.rule[.<slot>] type=<type> [mode=radio|bridge|cross|bridge,cross]
[hops=<range>] [channel=<channel>]
[hops=<range>] [channel=<channel>] [hashbytes=any|1|2|2+|3]
[prefix=<ID[,ID...]>] [in=<input-scope>] <action> [rate=<N>/min]
[priority=<0-255>] [stop] [tx=fast|slow] [suspend=tempradio]
get flood.rule
@@ -495,6 +499,14 @@ Match fields:
collision, while `public` verifies and decrypts with the Public key. This is
channel authentication, not sender authentication: the group MAC is two
bytes and the Public key is shared.
- `hashbytes=any|1|2|2+|3` matches the encoded width of each path hash, independent
of its value or the hop count. Omitted or `any` means any width; `*` is an
alias for `any`. It includes zero-hop packets carrying the selected width.
`2+` matches the supported 2- and 3-byte widths in one rule; a rate limit is
shared across both widths. Four-byte path hashes are reserved and unsupported.
Combine this with other match fields as needed; a `prefix=` must satisfy the
width selector. With `2+`, a 2- or 3-byte prefix narrows the rule to that exact
width, and saved output shows the prefix. The compact alias is `pb=`.
- `prefix=` is a source-path prefix containing one to three comma-separated
pbyte IDs. Every ID must use the packet's pbyte width: 2, 4, or 6 hex
characters for 1-, 2-, or 3-byte paths. Order matters and matching begins at
@@ -684,6 +696,7 @@ set fr.3 any c=#wardriving m=bc d
| `mode=radio`, `bridge`, `cross`, `bridge,cross` | `m=r`, `m=b`, `m=c`, `m=bc` | Select the admission path. Omitted means radio; `cb`, `b,c`, and `c,b` also mean both transport paths. |
| `channel=...` | `c=...` | Same authenticated name/key or explicit `hash:XX` matcher. |
| `prefix=...` | `p=...` | Ordered path prefix. |
| `hashbytes=any`, `1`, `2`, `2+`, or `3` | `pb=*`, `1`, `2`, `2+`, or `3` | Encoded path-hash width, even with zero hops; `2+` matches 2 and 3. |
| `path=blacklist` | `p=bl` | Shared unordered blacklist; repeater only. |
| `in=any`, `none`, `scoped`, `allowed`, `unknown` | `i=*`, `i=n`, `i=s`, `i=a`, `i=u` | Incoming scope condition. |
| `in=scope:name`, `in=region:name` | `i=s:name`, `i=r:name` | Match a specific incoming scope or region. |
@@ -698,6 +711,25 @@ The `=` distinguishes `s=name` (scope) from `s` (stop), and `r=name`
(region) from `r` (retry). Short forms keep the same action restrictions;
for example, `m=bc r` is rejected because transport rules cannot enable retries.
### Filter every 1-byte flood path
```text
set flood.rule type=any hashbytes=1 drop
```
The equivalent short command is `set fr any pb=1 d`. This adds a rule in a free
slot without replacing the default rules. It stops this node forwarding all
1-byte flood packets, including advertisements and relayed login/admin traffic;
direct routes and local delivery are unchanged. Use a narrower `type=` or
`channel=` if those floods must remain reachable. Use `hashbytes=2` or `3` for
an exact width, or `set fr any pb=2+ d` to drop both larger supported widths with
one rule. Use `get fr.<slot>` to copy the saved command.
Width-only rules require updated firmware. Existing rules remain unchanged and
the table uses no additional RAM or storage. Before downgrading to firmware
without this selector, remove width-only rules: its older reader considers them
invalid and can reset the rule table to defaults.
```text
get fr.3
```
+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>
+24 -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/full_companion_features/">View this page on MeshCore Docs</a>.</p>
<!-- meshcore-hosted-doc-link:end -->
# Full Companion: turn features on and off
For side-by-side Companion and infrastructure commands, see
@@ -51,6 +55,7 @@ editor does not necessarily forward them.
| External FEM TX gain | `set radio.fem.txgain on` | `set radio.fem.txgain off` | Immediately; saved; controllable PA only |
| ESP32 USB packet/debug logging | For 1.17.1.5: `set powersaving off`, then `set usb.logging on` | `set usb.logging off` | Immediately; saved; logging owns the USB terminal |
| nRF52 separate USB logging port | `set usb.logging on reboot` | `set usb.logging off reboot` | Saves and reboots to add/remove the second USB port |
| nRF52 one-port packet stream | `set usb.logging stream reboot` | `set usb.logging off` | Saved; CLI and packets share the primary port for stock serial bridges |
| ESP32 browser settings | `set webui on` | `set webui off` | Saved; starts/stops WebConfig |
| ESP32 temporary setup portal | `start webconfig ap` | `stop webconfig` | This session; opens a setup network/QR where available |
| ESP32 WiFi firmware uploader | `start ota` or `start ota ap` | `stop ota` | This session; only usable with two application slots |
@@ -100,9 +105,16 @@ WiFi modem sleep is independent of device power saving. Use
state with `get wifi.powersave`. `max` is available only where the firmware's
radio coexistence policy permits it.
Most Full Companions provide BLE alongside their other transports. There is
no universal `set bluetooth off` command. SenseCAP Indicator selects one
secondary wireless transport per boot:
Most Full Companions provide BLE alongside their other transports. On
Bluetooth-capable ESP32 and nRF52 Companions, `get bluetooth` reports the
running state and `set bluetooth on|off` saves the service preference.
Plain `off` requires another active management connection;
`set bluetooth off force` explicitly permits disconnecting the only client.
Reboot does not undo a saved-off preference. See
[Bluetooth controls](cli_commands.md#turn-bluetooth-on-or-off-companion) for
reply draining, connection checks, and unavailable boot modes.
SenseCAP Indicator selects one secondary wireless transport per boot:
```text
set companion.transport wifi
@@ -118,6 +130,12 @@ The Companion app's `gps=1` / `gps=0` custom setting controls the same GPS.
Only boards with a compiled GPS provider expose this setting. Sharing location
with contacts is a separate setting.
Use `set gps.sync.interval <hours>` to save an automatic clock-sync interval of
1-336 hours (two weeks); `get gps.sync.interval` reports it. Larger inputs are
capped at 336 and the reply shows the applied value. Existing GPS timings are
unchanged until configured. This controls clock synchronization, not the
position-update `gps_interval` or location-sharing policy.
When the exact Full image includes MQTT, use WebConfig's MQTT cards or the
same CLI settings used by infrastructure, such as `set mqtt1.preset custom`
and `set mqtt1.server broker.example.com`. `set mqtt.enabled on|off` controls
@@ -181,8 +199,9 @@ For ESP32 with two application slots, `start ota` returns the WiFi uploader
URL, normally `http://DEVICE_IP:8080/update`. `start ota ap` explicitly opens
`MeshCore-OTA`; join it and use the returned URL. Upload the exact board's
application `.bin`. The device reboots when the upload succeeds. Use
`stop ota` to close an unused uploader. Port 8080 keeps WebConfig on port 80
available. Use a trusted local network or a temporary setup network.
`stop ota` to close an unused uploader. Starting OTA enables WiFi as needed,
stops WebConfig automatically, and reports the network and URL to use. Use a
trusted local network or a temporary setup network.
The 4 MB Full layouts and T-Beam 1W Full use a single application slot and
require USB; the command reports that limitation. The artifact's
+49 -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
@@ -82,8 +86,9 @@ switched; they remain binary.
1. Companion initializes the normal USB Binary Companion interface, then
gives its primary stream to the ASCII terminal before normal loop service
begins.
2. While the prompt has no buffered input, the terminal peeks at the next byte.
It does not remove that byte.
2. While the prompt has no buffered input, the terminal skips blank CR/LF
delimiters and peeks at the next byte without removing it. A queued binary
frame after a blank line is detected before any ASCII banner is emitted.
3. If the byte is `<`, the terminal temporarily releases the stream and enables
the existing `ArduinoSerialInterface` frame parser.
4. The parser consumes the original `<`, the two-byte length, and the payload.
@@ -94,11 +99,21 @@ switched; they remain binary.
6. If no complete frame arrives within one second, the parser state is reset
and the ASCII terminal prints a new banner and prompt.
The switcher checks framing, not client identity. Any syntactically complete
Binary Companion frame confirms binary mode; it does not require the first
The switcher checks framing, not client identity. A complete Binary Companion
frame with 1-176 payload bytes confirms binary mode; it does not require the first
command to be `CMD_APP_START` or `CMD_DEVICE_QUERY`. Normal command validation
still occurs after the frame parser returns the payload.
Oversized frames are consumed and discarded, never truncated into executable
commands or counted as proof of a Binary client. ASCII, framed Binary, and mOTA
input handlers process at most the initial queued-byte count, so a continuously
sending host cannot keep the radio loop trapped in one input call.
ASCII and mOTA reject an entire line containing an embedded NUL or exceeding its
line buffer, discarding through the next CR/LF. A valid-looking suffix is not
executed. Binary payloads may still contain NUL bytes. The immediate manual
terminal-stop token remains an exception to ordinary line-oriented handling.
The empty-prompt requirement prevents a literal `<` in the middle of a command
from silently changing modes. A literal `<` typed as the first character does
start a probe, but the terminal returns after the one-second timeout if no
@@ -173,6 +188,36 @@ base where supported; RC32 and ESP32-C6 keep their board-required Arduino 3.x
platform but follow the same one-TTY policy. No ESP32 Full build enumerates a
second CDC interface.
`usb.logging` is the saved master for USB packet output. `usb.debug` separately
saves verbose diagnostic intent and defaults off, including when older
preferences do not contain the new field. RAW/RX/TX packet records need only
the master; diagnostics need both switches and compiled debug support. Turning
the master off preserves debug intent. `get usb.debug` reports that saved
choice, not whether USB currently owns a plaintext logging stream.
Full Companion now emits complete received frames as uppercase ASCII `RAW:`
hex, separately from decoded RX summaries and TX summaries. Leave debug off
for USB-to-MQTT capture. These are bounded best-effort records; drops and host
disconnects can separate a RAW/summary pair. The dedicated nRF52 log CDC cannot
answer CLI/identity queries: programs expecting one bidirectional CLI/log port
need separate control-port support, not just a change to the serial pathname.
Companion supports `get public.key` in the primary terminal or framed CLI;
the reply is `> ` followed by 64 uppercase hex characters. This query does not
make the dedicated log CDC input-capable or establish full bridge compatibility.
Send `get/set usb.debug` to the primary ASCII terminal; changing verbosity
needs no USB-interface reboot and does not relax Binary/mOTA ownership rules.
Current source adds `get usb.watchdog` and saved `set usb.watchdog off|on|auto`, defaulting to
Auto until 14 continuous healthy logging-client days qualify it for On.
Existing USB stats polls renew a 15-minute client lease; a cable or DTR alone
does not count. A lost confirmed client can trigger USB-only recovery after
lease expiry plus five minutes. MCU resets additionally require On and a
full physical-fault interval, a verified tier save, and clear protocol/update
ownership. Active Binary USB and mOTA sessions veto recovery. On dual CDC,
only logging CDC1 accepts the bounded stats markers; primary CDC0 stats do not
renew that lease. CDC1 still provides no CLI/statistics replies.
See [USB logging watchdog](usb_logging_watchdog.md) for status and limitations.
With logging enabled on a shared port, the stop token is rejected with
`ERROR: set usb.logging off before Binary mode`. Turn logging off first; a
successful binary-mode acknowledgement must not immediately revert to logging.
+179
View File
@@ -0,0 +1,179 @@
<!-- meshcore-hosted-doc-link:start -->
<p class="meshcore-hosted-doc-link"><a href="https://mikecarper.github.io/MeshCore/full_profiles/">View this page on MeshCore Docs</a>.</p>
<!-- meshcore-hosted-doc-link:end -->
# Full firmware profiles
Full combines the features supported by an exact board and firmware role in
one image. Use runtime settings to choose the services you need instead of
flashing a separate image for each transport. Hardware, memory, and the
installed flash layout still determine which combinations can run.
**Full is not a runtime role switch.** Choose Companion, Repeater, Room Server,
or Sensor in the [firmware picker](firmware_picker.md). Changing roles requires
the matching firmware; enabling a bridge does not turn a Companion into a
repeater.
## Choose the board, role, and profile
| Choice | What Full combines | Important limits |
| --- | --- | --- |
| Full Companion | The board's supported USB, Bluetooth, ESP32 WiFi, and qualified serial/Ethernet Companion transports, plus terminal chat and mOTA serving | The primary radio and board interfaces remain those of the selected target. Full Companion serves updates to other nodes; it does not install LoRa mOTA on itself. |
| ESP32 Full Repeater or Room Server | Standalone operation, supported USB/WiFi services, and the qualified MQTT, ESP-NOW, or RS-232 combination | Not every image contains all three bridges. Read its capabilities and check the compiled controls. |
| ESP32 Full Sensor | Sensor operation, supported CLI/logging, and qualified WiFi firmware-update and TCP 5001 seeding services | Current Sensor profiles do not gain the repeater's MQTT/ESP-NOW/RS-232 bridges. |
| nRF52 Full supported sensors or Reduced sensors | Two separately qualified Repeater, Room Server, or Sensor recipes with LoRa OTA | These are sensor-driver choices, not ESP32 network features. Full Companion remains a separate profile. |
Select the exact hardware variant and storage choice. Read the selected
image's capability manifest for update methods and requirements; a similar
filename is not evidence that another board's image will work.
## Full Companion: choose the active transports
Close any application using the USB port, then open the
[USB web console](https://flasher.meshcore.io/console) at 115200 baud. A fresh
Full Companion starts in the text terminal. If an app already selected Binary
Companion mode, send this line and wait for the terminal banner:
```text
+++MESHCORE-TERM-START
```
Run `help`, `board`, and `version` to inspect the device. The same image can
serve an app through its compiled transports or provide
[terminal chat](terminal_chat_cli.md). USB remains the normal installation
and recovery path.
On a Bluetooth-capable Companion, `get bluetooth` shows the running state;
`set bluetooth on` enables the saved Bluetooth preference. On an ESP32
profile with WiFi, `start webconfig ap` opens a temporary setup portal where
you can save network credentials. MQTT-capable Companions also expose broker
settings there; MQTT is not included on every Companion board.
Most Full Companions can use multiple transports together. **SenseCAP
Indicator** selects Bluetooth or infrastructure WiFi per boot using
`set companion.transport ble` or `set companion.transport wifi`, followed
by `reboot`; USB remains available. Generic ESP-NOW and Indicator ESP-NOW
profiles use ESP-NOW as their primary mesh radio. Full does not convert those
profiles into LoRa radios.
USB logging shares the ESP32 Companion's single USB port. Use
`set usb.logging off`, then `+++MESHCORE-TERM-STOP` and close the terminal
before reconnecting a Binary Companion app. nRF52 Full Companion can instead
add a separate logging port with `set usb.logging on reboot`; use its primary
port for Companion commands and mOTA serving. A one-port host bridge can use
`set usb.logging stream reboot`, which occupies the primary port until logging
is disabled. See the [USB mode guide](full_companion_usb_switcher.md).
## ESP32 infrastructure: enable the compiled services
Full Repeater and Room Server images retain their normal role while their
supported bridges run. A LoRa/ESP-NOW bridge keeps LoRa as its primary radio
and exchanges packets with its ESP-NOW peers over 2.4 GHz. MQTT publishes
observed traffic to brokers; it does not subscribe to broker messages or
inject them into LoRa. RS-232 uses the board's configured UART and pins.
Check `get bridge.type` on bridge-capable images. Depending on the recipe,
it can report combinations such as `mqtt+espnow`, `rs232+espnow`, or
`mqtt+rs232+espnow`. Use only the controls compiled into your image:
| Service | Saved enable/disable control | Check actual operation |
| --- | --- | --- |
| MQTT | `set mqtt.enabled on` / `off` | `get mqtt.running`, `get mqtt.status` |
| ESP-NOW | `set espnow.enabled on` / `off` | `get espnow.running` |
| RS-232 | `set rs232.enabled on` / `off` | `get rs232.running` |
| USB packet logs | `set usb.logging on` / `off` | `get usb.logging` |
An enabled preference is not proof that a service started. Missing
credentials, a channel mismatch, or a UART conflict can prevent operation.
`bridge.enabled` is a historical alias with different meanings between
recipes; use the explicit transport controls on combined images.
For an MQTT-capable image, first follow its role's
[WiFi/MQTT setup](WiFi.md#mqtt-observer-setup) and verify the actual connection
with `get wifi.status`. Once connected, this example selects a custom broker:
```text
set mqtt.iata SEA
set mqtt1.preset custom
set mqtt1.server broker.example.com
set mqtt1.port 1883
set mqtt.enabled on
get mqtt.status
```
Replace the sample broker value. Add the broker's username and
password if needed. Use the [WiFi/MQTT guide](WiFi.md#how-the-mqtt-bridge-works)
for TLS, publication settings, broker-slot limits, and reconnect behavior.
Where MQTT and USB logging coexist, `set logging.output off|usb|wifi|both`
selects those two outputs together; it does not change LoRa repeating.
For ESP-NOW, configure both peers with the same channel and compatible
`bridge.format`. `wrapped` uses the shared `bridge.secret` for bridge peers;
`raw` connects to primary-ESP-NOW MeshCore nodes. When MQTT WiFi and ESP-NOW
run together, the ESP-NOW channel must match the access point's fixed WiFi
channel. ESP-NOW alone needs no WiFi credentials. Follow the
[two-board setup](espnow_bridge_setup.md) for the complete commands.
For RS-232, stop the UART bridge before changing its configuration:
```text
set rs232.enabled off
get bridge.uart
set bridge.baud 115200
set rs232.enabled on
get rs232.running
```
Use the UART and pin assignment supported by your board. GPS can reserve or
share a UART; enabling the bridge may be rejected or pause a yieldable GPS
provider. Check the board's recipe before changing `bridge.uart`.
### Capacity and role exceptions
MKE S3 combines UART and ESP-NOW in its normal Full repeater. Full Heltec V3,
WSL3, and RAK3112 MQTT repeaters also retain independent RS-232 alongside
MQTT and ESP-NOW. **T-LoRa V2.1** retains two Full repeater choices because
of internal RAM limits: UART plus ESP-NOW, or MQTT plus ESP-NOW. Other
measured capacity exceptions retain dedicated images. Non-ESP32 boards do
not inherit these ESP32 combinations; Wio-E5, for example, keeps a separate
RS-232 image.
## nRF52: Full and Reduced supported sensors
Qualified nRF52 Repeater, Room Server, and Sensor builds offer **Full
supported sensors + LoRa OTA** and **Reduced sensors + LoRa OTA**. Full
includes the drivers supported by that board's full recipe; it does not add
support for every possible attached sensor. Reduced omits selected optional
environmental/ranging drivers while retaining generic I2C and supported board
peripherals. Reduced RAK3401/RAK4631 recipes also retain their supported
INA219/INA226/INA260/INA3221 voltage/current drivers.
Both profiles retain their exact board/role OTA identity and storage layout.
Choose the matching storage variant and meet its staging/bootloader
requirements. These profiles are LoRa OTA receivers; nRF52 Full Companion
is an update source and does not acquire WiFi or on-device MQTT. An attached
Pi can publish its USB packets using a separate host bridge. See
[sensor/storage choices](firmware_picker.md#hardware-and-variant-names).
## Install or update the matching layout
Expanded ESP32 Full images need application slots large enough for the image.
Use `get storage.layout` on firmware that provides it to inspect the installed
partition table. Firmware version alone does not establish the layout; use
the [partition lookup guide](esp32_partition_catalog.md) for older firmware.
For a compatible layout, use the exact board/role application-only image
through a supported updater. To change an ESP32 layout, use the matching
merged image over USB or an exact board/role staged migration ZIP whose README
supports your installed source layout and update route. An app-only image
cannot change the partition table, and a merged image is not a browser-OTA
application. The picker links the matching migration package where available.
ESP32 Full Companion WiFi self-update requires two application slots; the
single-slot profiles require USB. Infrastructure WiFi/LoRa self-update also
depends on the exact image's declared capabilities and installed layout.
nRF52 updates use the matching application UF2/DFU package or a qualified
LoRa route; they do not use the ESP32 browser procedure. See the
[ESP32 WiFi update and partition guide](esp32_wifi_partition_migration.md)
for supported staged routes and the [WiFi guide](WiFi.md) for role-specific
network setup.
+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.
+20 -1
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
@@ -774,6 +778,20 @@ verbose device log contains its `COUNT` acknowledgement; an immediate device
`ERR` or a missing acknowledgement fails during startup instead of surfacing
later as a catalog timeout.
Newer `motatool` builds prepare raw-DEFLATE blocks with Zopfli before opening
the source link. This host-only work has a separate 30-minute preparation
limit (`--seeder-prepare-wait SECONDS`) and reports progress while waiting.
The five-second device acknowledgement timer (`--seeder-start-wait SECONDS`)
starts after the final `compressed N/N blocks` message, not when compression
starts. Neither compression progress nor a host "serving" message proves
device readiness: the runner still requires the device's `COUNT` reply.
Older motatool builds without a preparation phase keep the original device
startup timeout. Process exits and device errors still fail immediately.
Preparation happens after TempRadio is armed, so leave enough lease time for
both compression and transfer, especially on a slow host. The suggested
TempRadio budget includes the preparation limit; it remains a warning, not a
hard requirement to select that entire worst-case budget.
```bash
./tools/lora_ota/lora_ota.sh ./release.mota "Remote Target" \
--controller-serial /dev/ttyACM0 \
@@ -905,7 +923,8 @@ the destination.
target, far-to-near relays, and source. A separate controller is moved and
read back through Binary; a shared Full Companion instead schedules its
bounded local override while Binary remains the transport. The runner
rejects a TempRadio window that cannot cover setup, seeder startup,
rejects TempRadio windows under 20 minutes and warns when the selected
window does not cover worst-case setup, seeder preparation and startup,
discovery, the transfer timeout, final polling, and install checks.
7. Start `motatool serve`, discover the exact eight-hex manifest ID, request
`ota pull <id> flash`, and poll until that same ID reports ready. A seeder
+41 -6
View File
@@ -1,6 +1,10 @@
<!-- meshcore-hosted-doc-link:start -->
<p class="meshcore-hosted-doc-link"><a href="https://mikecarper.github.io/MeshCore/management_decoder/">View this page on MeshCore Docs</a>.</p>
<!-- meshcore-hosted-doc-link:end -->
# Management report decoder
Paste MGR1 management-report payloads or complete GroupData packets from a
Paste MGR1 or MGR2 management-report payloads or complete GroupData packets from a
packet analyzer to read a radio's public management information. Everything,
including password authentication and ACL decryption, happens locally in this
browser. The report, password, and candidate public key are never uploaded.
@@ -13,13 +17,13 @@ by someone who does not know that password.
## Decode a management report
<div class="management-tool" data-management-decoder>
<label for="management-packet-input">MGR1 payload or complete GroupData packet hex</label>
<label for="management-packet-input">MGR1/MGR2 payload or complete GroupData packet hex</label>
<textarea
id="management-packet-input"
data-role="input"
spellcheck="false"
autocomplete="off"
placeholder="Paste analyzer Raw Data, canonical MGR1 payload hex, or one MQTT raw value per line"
placeholder="Paste analyzer Raw Data, canonical MGR1/MGR2 payload hex, or one MQTT raw value per line"
aria-describedby="management-packet-help"
></textarea>
<p class="management-help" id="management-packet-help">
@@ -31,7 +35,7 @@ by someone who does not know that password.
<label for="management-password-input">Management password <span>(optional for public fields; required for ACLs)</span></label>
<input id="management-password-input" data-role="password" type="password" autocomplete="new-password">
<p class="management-help">
The password remains in this page only. The decoder derives the MGR1 AES-SIV key in
The password remains in this page only. The decoder derives the management AES-SIV key in
your browser and does not send it anywhere.
</p>
@@ -67,9 +71,9 @@ by someone who does not know that password.
## What the decoder accepts
- Complete `PAYLOAD_TYPE_GRP_DATA` (`0x06`) analyzer/MQTT packet hex. It checks
the MeshCore route header, encoded path length, MGR1 page bounds, and required
the MeshCore route header, encoded path length, version-specific page bounds, and required
zero padding.
- A canonical MGR1 payload beginning with `4D475231` (`MGR1`).
- A canonical payload beginning with `4D475231` (`MGR1`) or `4D475232` (`MGR2`).
- One raw packet or canonical payload per line; duplicate observations of an
identical page are deduplicated.
@@ -78,5 +82,36 @@ the report-specific fingerprints and the administrator and/or OTA-signer flags.
It cannot turn a fingerprint back into a full key. Use the optional candidate
field to test a specific full public key.
MGR2 also shows USB logging, host/reader connection, logger-client activity, stalled/recovering/deferred
state, recovery stage, saved backoff, retry interval/inactive seconds, Auto qualification
seconds, and persistence
readiness. Unsupported observation is shown explicitly; legacy MGR1 has no
USB block and shows it as unavailable. These values are the report-start
snapshot, not proof that a Pi or host application is healthy. Older decoder
copies require an update for MGR2.
MGR2 also shows the latest USB watchdog action, reason mask and names,
advisory node-clock timestamp, recorded-boot uptime seconds, event sequence,
and whether the record was durably saved. Software recovery and re-enumeration
mean attempted actions, not successful repairs. A reboot request is not proof
that a reboot completed; a later cancellation can replace it. The node clock
can be wrong, even when it resembles a valid date. Event uptime and sequence
remain useful for ordering. MGR1 does not carry this event field and shows it
as unavailable. All 36 ACL entries still fit, using up to nine MGR2 pages.
The earlier USB-only MGR2 development layout was never released. This decoder
accepts the finalized MGR2 format with its 111-byte public header and latest
watchdog event.
Reports contain a frozen latest-event snapshot, not immediate watchdog alerts
or a complete event history. See [USB logging watchdog](usb_logging_watchdog.md)
for local review and persistence limitations.
Host/reader connection only describes the USB link or open reader. Logger-client
activity means recent USB stats polling within a 15-minute lease, renewed by
the existing five-minute polls without a new registration or heartbeat command.
The flag also requires logging enabled and a connected, unstalled USB link.
It does not identify the client or prove MQTT delivery, disk writes, or Pi health.
For the report schedule, public-field layout, cryptographic design, and the
offline Python capture tool, see [Management reports](management_reports.md).
+124 -23
View File
@@ -1,4 +1,8 @@
# Management reports (MGR1)
<!-- meshcore-hosted-doc-link:start -->
<p class="meshcore-hosted-doc-link"><a href="https://mikecarper.github.io/MeshCore/management_reports/">View this page on MeshCore Docs</a>.</p>
<!-- meshcore-hosted-doc-link:end -->
# Management reports (MGR1/MGR2)
Available on repeater (including observer), room-server and sensor firmware.
Companions/terminal-chat nodes and KISS modems do not originate these reports.
@@ -6,6 +10,12 @@ Off by default. A management password and explicit enable are both required.
No radio settings, existing preferences layout, or OTA authorization policy is
changed by enabling reporting. This protocol does **not** authorize updates.
Current firmware emits `MGR2`, including a public USB logging/recovery snapshot
and its latest watchdog event. Updated decoders still accept released `MGR1`;
older decoder copies need an update to read MGR2. The earlier USB-only MGR2
development layout was never released and was replaced before release.
Reporting USB status does not enable logging or change retry policy.
## CLI
Run through the existing local CLI or an authenticated administrator session:
@@ -26,13 +36,17 @@ Use a long randomly generated password. There is no password getter. Firmware
stores the derived 32-byte key, not the plaintext password. That stored key is
equivalent authority to decrypt reports and must also be protected. Passwords
entered into terminal programs may still be recorded by those programs.
The recognized firmware password command wipes its value from the input buffer
on success and error, including unavailable management or allocation failure.
`data.tx` is the single shared route for management reports, telemetry history,
and future scheduled data producers. Its fresh-install defaults are `path=direct`
(zero hops) and `region=auto`; configuring it never enables a producer. Paths
use `1:`, `2:` or `3:` followed by complete hop hashes without separators, or
the comma-separated form accepted by `set outpath`. `none` removes the path.
the comma-separated form accepted by `set outpath`. Empty hops, including a
trailing comma, are rejected. `none` removes the path.
`get/set mgmt.path` remain compatibility aliases for `get/set data.tx path`.
Alias expansion that exceeds the command buffer is rejected, not truncated.
`region=auto` uses the radio's configured default region when it is usable;
otherwise it resolves the unique deepest flood-enabled entry in the region
@@ -48,14 +62,19 @@ identifies the reporter.
Direct and flood schedules are independently configurable and may each be
turned off. Fresh settings are `direct=5d` and `flood=21d`; global reporting is
still off until `mgmt.enabled on`. Direct accepts 5–90 days and requires the
shared path. Flood accepts 21–90 days and requires a resolvable shared region.
still off until `mgmt.enabled on`. Direct accepts 5-90 days and requires the
shared path. Flood accepts 21-90 days and requires a resolvable shared region.
At least one route must remain active while reporting is enabled. The legacy
`set mgmt.interval N` shorthand enables both, setting direct to `N` and flood to
`max(21,N)`. No transmission is sent merely by configuring a password. Initial
reporting waits for the configured schedules. Reports have deterministic
reporting waits for the configured schedules. Invalid settings leave saved
configuration, countdowns, and any report pages already in progress unchanged.
Day counts must be unsigned decimal integers; signs and trailing text are rejected.
Reports have deterministic
per-radio/per-sequence jitter of up to an hour; pages are spaced at least a
minute apart.
minute apart. Watchdog events appear in the next scheduled snapshot, not as
immediate radio alerts. Multiple events between snapshots can replace the
latest-event record; this is not a complete off-node event history.
The radio cannot know that an observer uploaded a packet to MQTT, so the flood
schedule is deliberately independent of direct transmission. A region-scoped
@@ -76,6 +95,12 @@ writes stop reporting; `get mgmt` shows `FAULT(no TX)` until storage is repaired
and the radio restarted. `/management` and the shared `/data_tx` are versioned,
CRC-protected, and replaced transactionally.
On upgrade, an existing saved `data.tx` route takes precedence over the legacy
management-only path. Otherwise that path must be saved successfully to
`/data_tx` before reporting resumes. A failed migration leaves the old
`/management` file intact for retry after storage is repaired and the radio
restarted.
Weekly history is collected once a minute while enabled. Hour-bucket extrema
cover 7 days to 7 days + 1 hour (conservative boundary bucket). The first report
after enable/reboot is marked partial where appropriate. Since-report extrema
@@ -90,8 +115,8 @@ is bounded to 1.5 KiB, plus a small configuration object and temporary stack use
## Routing and MQTT
Both direct/path and flood reports use **`PAYLOAD_TYPE_GRP_DATA` (`0x06`)**.
The route bits independently select direct or flood. `MGR1` is an application
extension with **literally plaintext public fields**, not a call to the ordinary
The route bits independently select direct or flood. `MGR1`/`MGR2` are application
extensions with **literally plaintext public fields**, not a call to the ordinary
encrypted `createGroupDatagram()` builder. A fixed public marker is not an owner
or password-derived channel ID. There is no outer channel encryption.
@@ -108,7 +133,7 @@ not flood-route it.
The observer's existing MQTT `PACKET` JSON supplies the complete frame in `raw`.
No broker configuration or password changes are necessary to capture a report.
The decoder understands all four route forms, 1–3-byte hashes, scope transport
The decoder understands all four route forms, 1-3-byte hashes, scope transport
codes, and duplicate copies heard by several uplinks:
```
@@ -118,19 +143,22 @@ python tools/management/report.py --mqtt capture.jsonl
The password is prompted, not supplied as a process argument. Input may be JSONL
or a JSON array of MQTT messages. Alternatively omit `--mqtt` for a JSON array
of canonical payload hex strings. `--match-admin FULL_PUBLIC_KEY` in canonical
payload mode matches a known administrator against the encrypted fingerprints.
of canonical payload hex strings. `--match-admin FULL_PUBLIC_KEY` matches a known
administrator against the encrypted fingerprints in either input mode. MQTT
captures match each radio's fingerprints separately. Invalid files or keys are
rejected before the password prompt; an unavailable password input gives a
command-line error instead of a traceback.
This is an offline capture decoder, not a broker subscriber or downlink service.
For a browser-local decoder that also decrypts and authenticates ACL entries,
use the [Management report decoder](management_decoder.md). It does not upload
the captured packet or password.
## Canonical payload (little endian)
## Canonical MGR2 payload (little endian)
| Offset | Bytes | Field |
|---:|---:|---|
| 0 | 4 | `MGR1` |
| 0 | 4 | `MGR2` |
| 4 | 16 | First 16 bytes of reporter public key |
| 20 | 4 | Persisted report sequence (never wraps; exhaustion stops TX) |
| 24 | 4 | RTC Unix timestamp; advisory, may be wrong |
@@ -142,7 +170,7 @@ the captured packet or password.
| 52 | 4 | Staging capacity; planning still checks actual package geometry |
| 56 | 4 | OTA capability bits |
| 60 | 2 | Uptime hours, saturated at 65535 |
| 62 | 4 | Weekly minimum mV, minimum °C, maximum °C |
| 62 | 4 | Weekly minimum mV, minimum C, maximum C |
| 66 | 4 | Since-report extrema in the same format |
| 70 | 1 | History coverage hours, capped at 168 |
| 71 | 1 | Interval days for this report's direct or flood schedule |
@@ -152,16 +180,87 @@ the captured packet or password.
| 75 | 1 | Mask of status bits whose state is known |
| 76 | 2 | Validity/partial-history flags |
| 78 | 1 | Zero-based page index |
| 79 | 1 | Total pages (1–6) |
| 80 | 1 | Unique ACL count (0–36) |
| 79 | 1 | Total pages (1-9) |
| 80 | 1 | Unique ACL count (0-36) |
| 81 | 1 | First ACL index in this page |
| 82 | 1 | ACL entries on this page (0–6) |
| 83 | 13 × count | AES-SIV encrypted ACL entries |
| 82 | 1 | ACL entries on this page (0-4) |
| 83 | 2 | USB status flags |
| 85 | 1 | Packed USB recovery stage and retry backoff step |
| 86 | 4 | USB full-reboot retry interval seconds |
| 90 | 4 | USB inactive seconds |
| 94 | 4 | USB continuous Auto qualification seconds |
| 98 | 1 | Latest watchdog reason/action/durability code |
| 99 | 4 | Latest watchdog event RTC Unix epoch; advisory, zero if unavailable |
| 103 | 4 | Uptime seconds at that event's boot |
| 107 | 4 | Latest watchdog event sequence, saturated at 4294967295 |
| 111 | 13 * count | AES-SIV encrypted ACL entries |
| after ACL | 16 | Full AES-SIV authentication tag |
| after tag | 0–15 | Zero padding to group-compatible length; not part of canonical payload |
| after tag | 0-15 | Zero padding to group-compatible length; not part of canonical payload |
MGR2 retains all 36 ACL entries, using four per page and up to nine pages. The
largest canonical page is 179 bytes and already group-compatible;
the radio payload budget is unchanged. The USB snapshot is frozen with the
other public fields at report start and repeated on each page. It is not a
live host-health monitor.
USB flag bits 0-10 are, in order: supported, logging enabled, watchdog enabled,
host connected, reader connected, stalled, recovering, recovery deferred,
persistence ready, watchdog Auto, and logger active. Bits 11-15 must be zero.
Byte 85 bits 0-1 hold stage: 0 idle, 1 soft-recovery wait, 2 re-enumeration
wait, and 3 reboot pending. Bits 2-5 hold the saved backoff step; bits 6-7
must be zero. Backoff is not a count of successful recoveries. The three
timers are unsigned seconds; local recovery/reboot counters are not sent.
The retry field is the saved backoff interval, not a countdown to an action;
inactive age is reported separately. USB-only cleanup can precede that interval.
Unsupported USB observation/recovery is explicitly represented by a false
supported bit, not omitted or mistaken for a connected healthy reader.
USB status describes the radio's observed transport and policy, not proof
that the host process, MQTT uplink, SSH service, or Pi is healthy.
Host/reader connection alone does not mean a logging client is running.
The logger-active bit means recent USB stats polling within a 15-minute lease
while logging is enabled and the observed USB host/reader link is not stalled,
separate from USB attachment or an open reader. The existing five-minute stats
polls renew this lease; no new client-registration or heartbeat command is
required. This does not identify the client, prove logs reached MQTT or disk,
or show that the Pi remains otherwise healthy.
Missing watchdog state on new or upgraded installations defaults to Auto.
Auto promotes to saved On after 14 continuous healthy logging-USB days with
renewed stats polling; its qualification age resets on reboot, detach, stall,
stats-lease expiry, or USB logging off.
Explicit On and Off remain available. Nonlogging/no-reader nodes remain
Auto without watchdog board reboots. The Auto flag and age report this
qualification separately from an enabled On policy.
Use the node's `get usb.watchdog` and `set usb.watchdog off|on|auto` CLI to
inspect or change policy; receiving a management snapshot changes nothing.
The 13-byte latest-event block is all zero when no event is available. Otherwise
byte 98 bits 0-3 are the reason mask: host absent, reader absent, TX stalled,
and confirmed stats client inactive. Bits 4-6 hold the action: 1 software
recovery attempted, 2 re-enumeration attempted, 3 reboot requested, or 4 reboot
cancelled after a final health/ownership check. Bit 7 means the event was saved
and verified on durable storage. Sequence is nonzero for a present event.
Physical faults may combine reason bits; stats-only expiry cannot authorize a
board reboot. Epoch zero is allowed when the clock source is unavailable.
Event time is the node's advisory clock, not verified UTC. Default clocks can
start at a plausible fixed date. Clock corrections do not rewrite old events;
recorded uptime and sequence help review them independently. A saved reboot
request is intent, not proof of a completed reset. The latest event survives
normal reboot and mode changes, but is not a log of hardware hangs or sudden
power failures. See [USB logging watchdog](usb_logging_watchdog.md) for storage
failure, cancellation, and review limitations.
Legacy MGR1 retains the same offsets through byte 82, followed immediately
by encrypted ACL entries at byte 83 and the authentication tag. It carries
six entries per page and up to six pages, with no USB block. Its absent USB
status is decoded as unavailable, not as all switches off.
Each private entry is a per-radio 12-byte keyed fingerprint followed by flags:
bit 0 administrator, bit 1 trusted OTA signer. Duplicate entries combine flags.
bit 0 administrator, bit 1 trusted OTA signer. The firmware combines permissions
for the same fingerprint before paging, so every transmitted entry is unique.
Decoders reject authenticated duplicate fingerprints within or across pages;
repeated identical packet copies from multiple uplinks are still deduplicated.
An oversized ACL fails closed rather than silently truncating. The reported
allowlist includes all current full administrators and all four possible trusted
OTA signing keys; region/filter managers and ordinary clients are excluded.
@@ -175,9 +274,9 @@ means compiled support; active means an established usable apply/store path.
Validity bits: 0 firmware version, 1 bootloader version, 2 EndF/base identity,
3 staging capacity, 4 partial week, 5 partial since-report period, 6 MCU temperature.
Voltage is unsigned millivolts, zero missing. Temperatures: zero missing,
1–251 represent −50…200 °C, 252 below range, 253 above range, 254–255 reserved.
1-251 represent -50..200 C, 252 below range, 253 above range, 254-255 reserved.
OTA bits: 0 protocol compiled, 1 transfer DEFLATE, 2 2-KiB app transfer blocks;
bits 8–23 are apply codec bits (full/sequential/in-place). Transfer DEFLATE is
bits 8-23 are apply codec bits (full/sequential/in-place). Transfer DEFLATE is
not compressed bootloader apply. There is **no manifest ID**. Exact old binaries
are still needed on the computer to generate a differential update; a hash alone
cannot reconstruct them. Unknown metadata must not be treated as OTA readiness.
@@ -193,7 +292,9 @@ needs a candidate administrator's full key to identify a fingerprint.
Encryption is [RFC 5297 AES-SIV-CMAC-256](https://www.rfc-editor.org/rfc/rfc5297),
using the existing rweather AES primitive. The single associated-data string is
the complete 83-byte clear header. Ciphertext is exactly the ACL byte length.
the complete clear header: 111 bytes for MGR2, including USB status and its event,
or 83 bytes for MGR1. Both retain the existing derivation domains; the authenticated
magic and layout distinguish their wire versions. Ciphertext is exactly the ACL byte length.
Full 16-byte tag, no truncation. This deterministic misuse-resistant mode avoids
reliance on the firmware's noncryptographic general-purpose RNG or RTC nonces.
An identical restored snapshot may repeat ciphertext, but does not expose XORs
+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>
+70 -1
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
@@ -13,6 +17,7 @@ stop policy. USB testing uses Binary Companion mode at 115200 baud.
<button type="button" data-example="find">Find my node</button>
<button type="button" data-example="food">Food order ready</button>
<button type="button" data-example="vip">VIP chat</button>
<button type="button" data-example="channel9">Only channel 9</button>
</div>
<form data-role="form" onsubmit="return false">
<section class="notification-card">
@@ -22,6 +27,12 @@ stop policy. USB testing uses Binary Companion mode at 115200 baud.
<label>Contact/room key or channel slot<input name="id" autocomplete="off" placeholder="Full contact/room public key, or channel slot such as 3"></label>
<label>Companion client status<select name="when"><option value="any">Connected or disconnected</option><option value="connected">Connected</option><option value="disconnected">Disconnected</option></select></label>
</div>
<label class="notification-checkbox"><input type="checkbox" name="quietOthers" value="on">Silence other message alerts (channel exception)</label>
<p data-role="quiet-warning" hidden>This saves a silent default plus the selected channel's rule.
It also silences ordinary DMs and room-message alerts. Existing specific/state rules
and authorized <code>!notify</code> strings can still alert; review the
<a href="#only-channel-9-alerts-all-other-channels-silent">channel 9 example and caveats</a>.
Copy commands and USB Save include both rules; preview only plays the exception.</p>
<p>Connected means an active phone/app, USB, WiFi, Ethernet or serial
companion client. It selects a message rule; connecting or disconnecting
alone does not create an alert. Contact matching uses the full public key.
@@ -106,7 +117,9 @@ stop policy. USB testing uses Binary Companion mode at 115200 baud.
<p>Save writes the generated commands in order and stops at the first device
error; earlier successful settings remain saved. A test with a connection
suffix selects that profile even over USB. Stop-on-connect still watches
real subsequent connections. USB testing requires Chrome or Edge.</p>
real subsequent connections. The silence-other-alerts option saves both
the silent default and the selected channel rule. Deleting the selected
rule does not delete that default. USB testing requires Chrome or Edge.</p>
<pre data-role="device-log" class="notification-log" aria-live="polite"></pre>
</section>
</div>
@@ -240,3 +253,59 @@ notification strings. Receiver master switches apply even while connected.
and vibration. Add a connected profile with sound/vibration off if the phone
should handle notifications while attached. Permission to send notification
strings is separate from a VIP's ordinary message rule.
### Only channel 9 alerts; all other channels silent
This example makes message alerts silent by default, then plays one short
melody for channel 9. It works both with and without a connected companion
client and requires a device with a buzzer. Messages still arrive normally;
this changes the node's alerts, not the phone app's notification settings.
Use the **Only channel 9** button above to load this setup into the builder.
You can change the channel slot or alert pattern before copying or saving.
Uncheck **Silence other message alerts** to generate only the selected rule;
this does not remove a silent default already saved on the node.
`channel:9` means the firmware's **zero-based slot 9** (the tenth slot), not
a channel named `ch9`. Configure that slot before running these commands.
The rule follows its saved channel key; recreate it if you replace the channel
in that slot.
Start with no conflicting custom rules. Use `get notify.rules`, then
`get notify.rules <rule-slot>` to inspect existing selectors. Remove only
unwanted rules with `notify.delete <selector>`: existing state-specific and
contact/channel rules can override the silent default below.
```text
set notify.enabled on
set notify.vibration all off
set notify.sound all off
set notify.led all off
set notify.screen all off
set notify.gpio all off
set notify.repeat all 1
set notify.gap all 500
set notify.stop all button
set notify.sound on
set notify.sound channel:9 ch9:d=8,o=5,b=180:c,e,g
```
Keep the sound **master** on: `set notify.sound all off` silences the default
rule, while `set notify.sound off` would also block the channel 9 exception.
The other outputs remain silent, including screen wake-ups for messages.
To add vibration for channel 9 on a device with a vibration motor:
```text
set notify.vibration on
set notify.vibration channel:9 100,100,300
```
Test the saved exception with `notify.test channel:9`; stop it with
`notify.stop`. Send a message on another configured channel to verify that
it stays quiet.
**Scope:** there is no channels-only wildcard: `all` also silences ordinary
DM and room-message alerts unless a more specific rule enables them.
Previously authorized contact/room `!notify` strings can still request alerts.
If those should stay quiet too, revoke their permission with
`set notify.remote contact:PUBLIC_KEY off` or
`set notify.remote room:ROOM_PUBLIC_KEY off`, using the full sender/server key.
+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
Loaded 100 of 1255 files, more files were not shown because too many files have changed in this diff. Show more