* Snake!
* Add spiLock to snake score saving
* Check fixes
* More careful locking
* WIP: Big Display Node
* Update src/graphics/HUB75Display.cpp
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
* Add HUB75 Native
* Add Tetris game module with core logic and UI integration
- Implement TetrisGame class for game logic, including piece movement, rotation, and line clearing.
- Create TetrisModule class to manage game state, input handling, and rendering on OLED display.
- Introduce high score tracking with persistence and optional mesh broadcasting.
- Define UI states for title, playing, paused, game over, and high scores.
- Implement input handling for game controls and state transitions.
- Add rendering functions for the game board, high scores, and title screen.
* feat(snake): add snake graphics and update display logic in SnakeModule
* Prompt for Initials for Tetris, too
* refactor games module
* Games refactor
* hub75 native double buffer
* Games tuning
* Make joystick repeat events on held button
* Add clouds and colors
* Fix breakout and more color
* difficulty tuning
* trunk
* refactor game announcements, etc
* Scale chirpy gravity as game speed increases
* Portduino, check for hub75 display before reading in hub75 options
* Final(?) games tuning
* Potential fix for pull request finding
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
* Properly ignore input when games screen not shown
* Fail gracefully when HUB75 is selected but not supported.
---------
Co-authored-by: Thomas Göttgens <tgoettgens@gmail.com>
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Co-authored-by: Ixitxachitl <kramerfm@gmail.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
* Add MemAudit: per-subsystem heap accounting in the boot log
The 2.8.0 nRF52840 heap-exhaustion field reports had to be diagnosed by
hand, reconstructing each subsystem's heap footprint from source and
build flags one report at a time. This makes every future report
self-diagnosing from the serial log: a tiny fixed-size registry
(src/memory/MemAudit.*) that big long-lived allocations report into,
printed as one line at the end of setup() and alongside the periodic
"Heap free:" log:
MemAudit[boot]: tmm=2500 warm=4000 pkthist=5824 nodedb=13440
msgstore=2200 pktpool(live)=3270 total=31234
Instrumented: NodeDB hot vector (nodedb) + satellite maps (satmaps,
rb-tree overhead estimated), WarmNodeStore (warm), PacketHistory records
and hash index (pkthist), TrafficManagement caches (tmm/tmm_ni),
MessageStore text pool (msgstore), TFT line/repaint buffers (display),
and live in-flight packets (pktpool(live)) via an optional audit tag on
the packet pool allocator - the one hot path, counted with a relaxed
32-bit atomic add (single instructions on Cortex-M, no locks).
Cost: 128 B RAM for the 16-tag table, well under 1 KB flash on rak4631.
MESHTASTIC_MEM_AUDIT=0 compiles it out to inline no-op stubs (call
sites need no ifdefs); STM32WL, the tightest flash target, defaults off.
New native suite test_mem_audit covers add/set/snapshot arithmetic, tag
reuse (pointer and cross-TU strcmp fallback), null/unknown tags, and
table-full behavior; test/native-suite-count bumped to 28.
* native-wasm: add src/memory/ to the curated source filter
The wasm env denies all sources and adds an explicit file list; MemAudit
callers (main, MeshService, NodeDB, PacketHistory) are in that list but
src/memory/MemAudit.cpp was not, so wasm-ld failed on undefined
memaudit:: symbols.
Audit of the XEdDSA packet-signing implementation (#10478) surfaced several
issues in when unsigned packets are accepted on receive or emitted on send.
This fixes them and adds regression coverage.
- Unicast NodeInfo exchange no longer breaks against signer nodes: the
NodeInfoModule downgrade drop is gated to broadcasts, since senders never
sign unicast (want_response replies, directed exchanges).
- Replace the payload-size sign heuristic with an exact encoded-size gate
(signedDataFits) and mirror it on the receive side, removing a dead band
where 167-168 B broadcasts were signed then failed TOO_LARGE.
- Extract the receive policy into checkXeddsaReceivePolicy() and apply it to
plaintext-MQTT decoded downlink, which previously skipped signature
verification and downgrade protection entirely.
- Reject signatures whose length is neither 0 nor 64 as malformed, so a
crafted partial signature can't inflate the size estimate and dodge the
unsigned-downgrade drop.
- Hold cryptLock on the MQTT verify path (shared Ed25519 key cache).
- Clear any client-preset signature on packets we originate, on all builds.
- Randomized (hedged) signing per the Signal XEdDSA spec: bump the
meshtastic/Crypto pin to the build where XEdDSA::sign mixes 32 bytes of
caller randomness into the nonce as Z (meshtastic/Crypto#3), and seed those
bytes in xeddsa_sign from HardwareRNG (checked, with a seeded-CSPRNG
fallback). test_crypto pins that repeated signs differ and both verify.
Adds test coverage: test_packet_signing groups A-E (receive matrix, send
policy, NodeInfo backstop, encoding invariants, decoded-ingress policy),
test_mqtt end-to-end downlink cases, and a test_crypto randomization check.
* Add ARCH_PORTDUINO_WASM build: meshtasticd in WebAssembly over WebUSB
Compile the full portduino firmware to WebAssembly (Emscripten) so a real node runs in a browser tab or headless Node, driving a LoRa radio over WebUSB through a CH341 — the desktop Ch341Hal path with its libusb backend swapped for a WebUSB one. The native/desktop portduino build is unchanged.
New build env under src/platform/portduino/wasm/ (excluded from the native build_src_filter): WebUSB libpinedio backend, config/FS/region/MAC/PhoneAPI glue, wasm setup/loop, JS WebUSB runtime, and build stubs. bin/build-portduino-wasm.sh runs a standalone cached emcc build to build/wasm/meshnode.{mjs,wasm}.
Six firmware sources gain #ifdef ARCH_PORTDUINO_WASM guards (single-threaded cooperative emscripten_sleep loop, continuous RX, US region default, std RNG, no-popen exec); none affect non-wasm builds.
* PortDuino WASM: CI build job, cross-platform libdeps, configurable adapter
bin/build-portduino-wasm.sh: auto-detect native-macos (macOS) vs native (Linux/CI) libdeps with a NATIVE_ENV override, and use the SAME env's Crypto with an XEdDSA-present guard. Drops the heltec-v3/Crypto borrow — the meshtastic/Crypto pin already ships XEdDSA; the native libdeps cache was just stale. No env pin change.
Add .github/workflows/build_portduino_wasm.yml: build the ARCH_PORTDUINO_WASM target in CI (ubuntu + emsdk, native libdeps) so it can't silently bit-rot; asserts build/wasm/meshnode.{mjs,wasm}.
src/platform/portduino/wasm: add wasm_set_lora_* setters so the JS host can configure any CH341 LoRa adapter (module, USB ids, DIO/TCXO, SPI speed, pins); wasm_config_apply falls back to the MeshToad defaults when unset.
* WASM: build as a first-class [env:wasm] via platform-wasm (retire emcc script)
Replace the standalone emcc build (bin/build-portduino-wasm.sh) with a normal
PlatformIO env, `pio run -e wasm`, using the new meshtastic/platform-wasm
platform (emcc/em++ + Asyncify, the WASM sibling of platform-native). The
portduino WebAssembly node is now built the same way as every other target.
- variants/native/portduino/platformio.ini: add [env:wasm] (platform pinned to
platform-wasm, board wasm). Translates the script's source set into a curated
build_src_filter, the ~30 EXCLUDE_* defines, and the lib set; defines
ARCH_PORTDUINO_WASM in-repo so correctness doesn't hinge on the platform's
board.json.
- extra_scripts/wasm_link_flags.py: the firmware-specific emcc *link* settings
(EXPORT_NAME, EXPORTED_RUNTIME_METHODS, EXPORTED_FUNCTIONS, ASYNCIFY_IMPORTS).
PlatformIO feeds build_flags to compile only, so these must ride LINKFLAGS;
without it the WebUSB Asyncify seam and the JS host's runtime methods are
dropped (the _wasm_* exports survive only via EMSCRIPTEN_KEEPALIVE).
- PortduinoGlue.{h,cpp}: guard the yaml-cpp dependency out of the WASM build
(#ifndef ARCH_PORTDUINO_WASM around the include, emit_yaml/loadConfig/
readGPIOFromYaml). The browser node configures via the wasm_set_lora_* setters
and dead-strips the YAML path; this drops the host yaml-cpp build dependency
entirely. Native is unchanged (guards are inert there).
- portduino_glue_wasm.cpp / portduino_main_wasm.cpp: repair EM_ASM JS that a
formatter had mangled (!== -> != =, regex split) in the prior landing; the
emcc link succeeds regardless, so CI now runs `node --check meshnode.mjs`.
- .github/workflows/build_portduino_wasm.yml: build via `pio run -e wasm`
(artifacts under .pio/build/wasm/), trigger on the shared inputs the env
inherits (root platformio.ini, bin/platformio-*.py).
- NodeDB.cpp: drop the dead ARCH_PORTDUINO_WASM region-default branch (region
now defaults the same as native).
- Crypto renovate pins: add the missing gitBranch so they track upstream.
Output: .pio/build/wasm/meshnode.{mjs,wasm} (ES module, factory createMeshNode).
Verified: pio run -e wasm (against the published platform archive), node --check,
module instantiates in Node with all exports; native-macos + Docker native unit
tests (450/450) still pass.
* Fix name on the Piggystick
* wasm: pin platform-wasm at the GPL-3.0-relicensed commit
platform-wasm's LICENSE was always GPLv3 (matching this firmware), but its
platform.json/README still declared Apache-2.0 (mis-copied from platform-native).
That's fixed upstream in b83fa5b; bump the [env:wasm] pin to it. Build output is
unchanged (license metadata only). Verified: pio run -e wasm against b83fa5b.
* wasm: make reboot() actually restart the node (was a no-op)
In wasm the reboot path is live (main.cpp -> Power::powerCommandsCheck ->
Power::reboot), but Power::reboot's ARCH_PORTDUINO arm tore down SPI/Wire/Serial
and then called the no-op ::reboot() stub — leaving the node running with a dead
radio until the tab was manually reloaded. Triggers include an admin/phone
reboot, factory reset, the "reconfigure failed" path, and the 60 s stuck-TX
hardware watchdog (RadioLibInterface).
- Power::reboot(): add an ARCH_PORTDUINO_WASM arm (before ARCH_PORTDUINO, since
the wasm build defines both) that skips the host teardown and just calls
::reboot(). notifyReboot already let modules persist.
- ::reboot() (glue): hand off to the JS host — browser reloads the tab (NodeDB
state survives via IDBFS, same identity returns); headless calls Module.onReboot
if provided, else logs. Loose !=/== so clang-format doesn't mangle the EM_ASM JS.
- README: document the reboot handoff + the Module.onReboot hook.
Verified: pio run -e wasm + node --check (EM_ASM intact); native-macos unaffected.
* wasm: rename env to native-wasm and run it in the main CI matrix
Rename [env:wasm] -> [env:native-wasm] for consistency with the portduino
native family (native, native-macos, native-tft). The build dir follows to
.pio/build/native-wasm/ (artifact is still meshnode.{mjs,wasm}); the PIOENV
guard in extra_scripts/wasm_link_flags.py, the README, and the companion wrapper
move with it. The board stays `wasm`.
Also wire the build into normal CI: build_portduino_wasm.yml becomes a reusable
workflow (workflow_call) invoked as the `build-wasm` job of main_matrix.yml, so
the WebAssembly node is built like every other platform instead of on a separate
path trigger.
* native-wasm: auto-locate the Emscripten SDK (pre-build script)
`pio run -e native-wasm` failed with "emcc not found" whenever it was invoked
from a shell that hadn't sourced emsdk_env.sh — a VS Code task, an IDE build
button, a bare terminal. Add a pre: extra script that probes the usual emsdk
locations ($EMSDK_ENV, $EMSDK, ~/emsdk, ./.emsdk, the sibling companion
checkout), sources emsdk_env.sh, and imports the resulting environment so the
platform builder and emcc see PATH/EMSDK/EM_CONFIG. No-op when emcc is already
reachable (CI), silent when no SDK is found (the platform emits its own error).
* wasm: address PR review feedback
- js/bridge.js: import CH341 from "./ch341.js" (sibling in this layout), not
"../src/ch341.js" which doesn't resolve here.
- js/ch341.js: a zero-length transferIn while MISO bytes are still outstanding
now throws instead of breaking out with a partially-filled buffer — silent SPI
corruption becomes a loud error, matching the comment above it.
- libpinedio_webusb.c: webusb_set_auto_cs honors the AUTO_CS option (? 1 : 0)
instead of the always-on ? 1 : 1. Runtime behavior is unchanged — Ch341Hal sets
AUTO_CS=0 right after pinedio_init (RadioLib drives the active-low NSS); the
option just isn't set yet at init, so this now correctly defaults off.
- SX126xInterface.cpp: the RX-start error log now names the method actually
called (startReceive vs startReceiveDutyCycleAuto) instead of hardcoding the
duty-cycle name in the WASM branch.
* native-wasm: drop the emsdk bootstrap shim (now in platform-wasm)
The Emscripten SDK auto-location moved into the platform-wasm builder, so the
firmware no longer needs its own pre: extra script. Remove
extra_scripts/wasm_emsdk_env.py and bump the platform pin to the build that
carries the bootstrap. The wasm_link_flags.py post script stays — those exported
fns / runtime methods / Asyncify import seam are firmware-app-specific.
* wasm: use the canonical companion name (meshtasticd-wasm-node)
The companion repo was renamed meshtastic-web-node -> meshtasticd-wasm-node; fix
the stale name in the wasm README and bump the platform pin to the build that
promotes the canonical name in its emsdk auto-location.
* wasm: re-entrancy guard for the API/region entry points + flaky-open retry
Two robustness fixes for the browser node:
- Re-entrancy guard. The node is single-threaded + Asyncify: while setup()/loop()
is suspended inside a WebUSB transfer, the JS event loop is free, so a stray
DOM/timer callback that re-enters a wasm_* entry point starts a second Asyncify
unwind ("async operation already in flight" abort) or clobbers shared PhoneAPI
state (observed as a "PhoneAPI::available unexpected state" flood). Add a
g_wasm_in_firmware flag set around setup()/loop() (portduino_main_wasm.cpp); the
wasm_set_region / wasm_api_to_radio / wasm_api_from_radio / wasm_api_available
entry points now reject a mid-tick call (return busy) instead of corrupting or
aborting. The host must still call them between ticks — this is the safety net
the design lacked, not a substitute for the JS queue.
- CH341 open retry (js/bridge.js). First-connect WebUSB is flaky — the interface
is briefly unclaimable right after the grant, or held by a prior session,
giving a transient "Could not open SPI: -1". Retry the open with a short
backoff, closing the device between attempts so claimInterface starts clean.
* wasm: exclude emscripten-only sources from cppcheck
The `check` board matrix runs `pio check` (cppcheck) over all of src/,
including src/platform/portduino/wasm/. cppcheck can't parse the EM_ASYNC_JS/
EM_JS macros (Syntax Error: AST broken at libpinedio_webusb.c:39,
internalAstError) and these sources are not part of any checked board build
([env:native-wasm] is board_level=extra, compiled by the build-wasm CI job).
Suppress the wasm dir in suppressions.txt, the same way generated/ and .pio/
are already excluded.
* wasm: coalesce FS.syncfs so two never run at once
IDBFS syncfs is async; the explicit wasm_fs_sync (5s timer + post-save +
beforeunload) could overlap a prior in-flight sync, warning "2 FS.syncfs
operations in flight at once". Serialize: if a sync is running, mark a pending
re-sync and let the in-flight one chain it on completion — at most one in flight,
trailing writes still flushed. (Companion drops IDBFS autoPersist so this is the
single persistence path.)
* wasm: silence false-positive SAST on the emscripten glue
- extra_scripts/wasm_link_flags.py: restore the trunk-ignore-all(ruff/F821,
flake8/F821) header every other SCons extra_script carries; Import/env are
SConscript-injected globals, so ruff/flake8 flag them as undefined.
- .semgrepignore: exclude src/platform/portduino/wasm/js/ (browser WebUSB glue,
not part of the firmware binary). The unsafe-formatstring rule false-positives
on its benign retry/diagnostic console logs.
* Update .github/workflows/build_portduino_wasm.yml
Co-authored-by: Austin <vidplace7@gmail.com>
---------
Co-authored-by: Austin <vidplace7@gmail.com>
Adds the Traffic Management module (TMM) plus the NodeDB/warm-store and
next-hop foundations it builds on:
- Unified per-node cache (flat array, 8-bit relative ticks) shared by all
features; role-aware throttles for tracker / lost-and-found.
- Position deduplication: drop unchanged position rebroadcasts within a
configurable interval; precision driven off the channel ceiling (clamped to
the public-key max on well-known channels). Enabled by default at 11h.
- Per-node rate limiting and unknown-packet filtering (config-driven; a
non-zero companion field enables each feature -- no bool toggles).
- NodeInfo direct response from cache with role-based hop clamps.
- Persistent next-hop overflow store: confirmed hops have no TTL, are seeded
from NodeInfoLite at boot, and survive hot-store eviction.
- Three-tier sender-role resolution (hot NodeInfoLite -> warm store -> TMM
cache). Role is cached write-time (seeded on first track, refreshed from
NodeInfo), pins its cache entry like a next-hop hint, and is evicted last.
- Warm store caches device role + protected category across reboot/eviction.
- PositionModule stationary floor for tracker / lost-and-found.
- PSRAM gating for warm/satellite/TMM cache sizes; STM32WL excluded.
Protobufs: TrafficManagementConfig trimmed to the five uint32 fields actually
used; submodule repointed to protobufs develop.
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
* Test commit for XEdDSA support
* Update to Crypto lib in Meshtatic org
* Generate a new node identity on key generation (#7628)
* Generate a new node identity on key generation
* Fixes
* Fixes
* Fixes
* Messed up
* Fixes
* Update src/modules/AdminModule.cpp
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
* Update src/mesh/NodeDB.cpp
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
* Figured it out!
* Cleanup
* Update src/mesh/NodeDB.h
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
* Update src/mesh/NodeDB.cpp
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
* Update src/modules/AdminModule.cpp
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
---------
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
* Update crypto commit hash
* Some fixes for xeddsa pr (#9610)
* fix: add null check for getMeshNode() in NodeInfoModule
getMeshNode() can return nullptr for unknown nodes. Dereferencing
without a check crashes the firmware when receiving NodeInfo from
a node not yet in the database.
* fix: enforce XEdDSA signature verification and prevent stripping
Previously, failed signature verification still allowed the packet
through, making signatures purely cosmetic. Now:
- Failed verification drops the packet (DECODE_FAILURE)
- Successfully verified nodes get HAS_XEDDSA_SIGNED bitfield set
- Unsigned packets from previously-signing nodes are rejected
- Log levels reduced from WARN/ERROR to DEBUG/WARN as appropriate
* fix: include packet metadata in XEdDSA signature
The signature now covers [fromNode | packetId | portnum | payload]
instead of just the payload bytes. This prevents:
- Replay attacks (different packetId fails verification)
- Reattribution (different fromNode fails verification)
- Portnum redirection (different portnum fails verification)
Also adds a key initialization check to xeddsa_sign (returns false
if XEdDSA keys are all zeros) and checks the return value in the
encode path.
* fix: handle existing key pair in AdminModule security config
When a user provides both a valid private key and public key via
admin config, the crypto engine's DH private key and owner public
key were never loaded. DMs and XEdDSA signing would silently break.
Add an else branch to load both keys into the crypto engine.
* perf: cache Ed25519 public key conversion in xeddsa_verify
curve_to_ed_pub() performs field element parsing, inversion, and
multiplication on every call. Since packets from the same node
tend to arrive in bursts, a single-entry cache avoids repeating
this expensive conversion for consecutive packets from one sender.
* fix: skip identity cleanup when node number is unchanged
createNewIdentity() was called on every generateCryptoKeyPair(),
including normal boots where the same key is regenerated. This
caused unnecessary NodeDB writes and old-node cleanup logic to
run when the node number hadn't actually changed.
Also fixes only zeroing byte[0] of the old node's public key
instead of clearing the entire array.
* fix: replace hardcoded 120 with derived XEDDSA_SIGNATURE_SIZE constant
The payload size check for XEdDSA signing used a magic number (120).
Replace with a derivation from DATA_PAYLOAD_LEN and XEDDSA_SIGNATURE_SIZE
so the limit adjusts automatically if constants change. This also
increases the max signable payload from 120 to 169 bytes, which is
still safe since the actual encoded size is checked after pb_encode.
* fix: add const qualifiers to XEdDSA verify and curve_to_ed_pub inputs
pubKey, payload, and signature parameters in xeddsa_verify are
input-only and should not be modified. Same for curve_pubkey in
curve_to_ed_pub.
* chore: remove commented-out old Crypto dependency in portduino.ini
* Leave out the admin module change for now
---------
Co-authored-by: Jonathan Bennett <jbennett@incomsystems.biz>
* trunk
* protobuf re-update
* Protobufs
* Merge resolution fix
* Put XEDDSA on the right bit
* NodeDB update to new nodeInfoLite accessors, etc
* Potential fix for pull request finding
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
* Potential fix for pull request finding
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
* Potential fix for pull request finding
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
* Potential fix for pull request finding
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
* Potential fix for pull request finding
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
* Potential fix for pull request finding
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
* Potential fix for pull request finding
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
* Potential fix for pull request finding
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
* Potential fix for pull request finding
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
* Potential fix for pull request finding
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
* Refine unsigned packet rejection logic in Router (#10534)
* use hardware random to fill the first 32 signature bytes with entropy prior to signing.
* Add XEdDSA packet-signing policy tests and update dependencies for macos
* Minor fixes
* integrate XEdDSA support and update dependencies across multiple modules
---------
Co-authored-by: Ben Meadors <benmmeadors@gmail.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Wessel <github@weebl.me>
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: thebentern <9000580+thebentern@users.noreply.github.com>
* asdf
* Implement SphereOfInfluenceModule for traffic management and eviction tracking
* Implement Sphere of Influence module for dynamic hop limit adjustment and role-based floor
* Update SAMPLING_DENOMINATOR to improve mesh size estimation accuracy
* Add debug logging for scale factor estimation and per-hop node counts in SphereOfInfluenceModule
* Enable variable hop limits and role-based hop floors in Sphere of Influence module
* Respond to copilot review
* Disable variable hop limits and role-based hop floors in Sphere of Influence module
* Apply suggestions from code review
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
* Implement adaptive sampling for unique node ID tracking in Sphere of Influence module
* Add state persistence for Sphere of Influence module
* Enhance Sphere of Influence module with state management and adaptive sampling adjustments
* Refactor hop scaling functionality: remove SphereOfInfluenceModule and introduce HopScalingModule
- Deleted SphereOfInfluenceModule.h, consolidating its responsibilities into the new HopScalingModule.
- Added HopScalingModule.h and HopScalingModule.cpp to manage hop scaling logic, including eviction tracking and sampling-based mesh size estimation.
- Implemented methods for recording evictions and packet senders, estimating scale factors, and computing required hops based on node activity.
- Introduced state persistence for hop scaling parameters to maintain continuity across reboots.
- Enhanced thread safety and modularity by utilizing concurrency features.
* Guard out STM32. Sowwy.
* Refactor HopScalingModule: enhance sampling logic and improve state management
* Add unit tests for HopScalingModule: implement mock database and various test scenarios
* Refactor test output in HopScalingModule tests: replace printf with TEST_MESSAGE for better integration with Unity
* Refactor HopScalingModule logging: replace lastStatusMode with descriptive mode names for improved readability
* Refactor test_main.cpp: change NodeNum variable to static and improve comments for clarity
* Remove unnecessary delay in setup function for improved test performance
* Add missing include for MeshTypes in test_main.cpp
* Refactor HopScalingModule tests: enhance mesh topology scenarios and improve test clarity
* Update HopScalingModule tests: flesh out node scenarios and improve clarity for dense and sparse mesh cases
* Fix politeness factor calculations in HopScalingModule and update related test scenarios for clarity. Remove outdated design doc.
* Enhance HopScalingModule: add sampled estimate for scaling decisions and refactor initial run state management
* Add sample traffic injection for HopScaling tests to enhance sampledEst visibility
* Enhance HopScalingModule: adjust windowFraction calculation for early triggers and improve test output formatting
* Enhance HopScalingModule: add jitter functionality to sampling denominator and update tests for consistent behavior
* Enhance HopScalingModule: implement adaptive sampling denominator adjustment and add reset functionality for tests
* Enhance HopScalingTestShim: add test-only clock and window helpers, update injectSampleTraffic for adaptive sampling, and improve scenario summary output
* Enhance HopScalingModule: add detailed documentation for functions, improve clarity of jitter and sampling logic, and reset functionality in tests
* Enhance HopScalingModule: add evictionEstimate parameter to estimateScaleFactor and update related logging for improved mesh size estimation
* Enhance HopScalingModule: adjust effective rolls calculation for improved accuracy, add eviction estimate logic, responding to all copilot review points
* Implement CompactHistogram for parallel hop scaling sampling
- Added CompactHistogram class to track node hop distances with bitwise sampling.
- Integrated CompactHistogram into HopScalingModule for independent packet sampling.
- Updated NodeDB to feed both the hop scaling module and the new histogram sampler.
- Enhanced HopScalingModule with methods to sample packets for the histogram and retrieve hop distribution statistics.
- Implemented tests for CompactHistogram functionality, including sampling, window rolling, and adaptive denominator scaling.
- Updated existing tests to validate the integration of the new histogram sampling mechanism.
* Enhance CompactHistogram and HopScalingModule: add per-hop distribution functionality, improve time handling for unit tests, and refine test setup for deterministic behavior
* CompactHistogram: add mesh size estimation, improve entry replacement logic, and update logging for per-hop distribution
* Refactor HopScalingModule and CompactHistogram integration
- Removed the suggestedHopFromCompactHistogram function to streamline hop suggestion logic.
- Updated HopScalingModule to directly utilize CompactHistogram's internal methods for hop suggestions and sampling.
- Enhanced logging in HopScalingModule to provide detailed histogram statistics.
- Modified test cases to ensure comprehensive coverage of new histogram behaviors and sampling logic.
- Improved node ID distribution in tests to better exercise sampling mechanisms.
- Ensured that filtering denominators are held for 12 hours before dropping, enhancing stability in sampling.
* Refactor CompactHistogram to support 13-hour activity tracking and introduce politeness regimes
- Updated the bitfield structure to accommodate 13-hour seen tracking.
- Changed the logic in rollHour() to analyze activity over the last 0-2 hours vs. 1-3 hours for politeness factor calculation.
- Introduced three politeness levels: GENEROUS, DEFAULT, and STRICT based on recent activity ratios.
- Adjusted filtering and sampling logic to reflect the new 13-hour tracking period.
- Updated unit tests to validate new behavior and ensure proper functionality of politeness regimes.
* Enhance CompactHistogram and HopScalingModule for improved sampling and decision-making
- Introduced a session-specific hash seed in CompactHistogram to reduce bias in node ID sampling.
- Updated sampling logic to use hashed node IDs instead of raw IDs for filtering and entry management.
- Added histogram rollover tracking in HopScalingModule to ensure proper decision-making after initial data collection.
- Adjusted logging to reflect the active state of the histogram and its comparison with NodeDB advisory hops.
- Enhanced unit tests to validate new sampling logic and memory layout changes.
* Expose hashNodeId for testing in CompactHistogram
* Add mesh trend statistics to CompactHistogram for enhanced activity tracking
* Implement histogram state persistence in CompactHistogram with save and load functions
* Refactor CompactHistogram to improve entry management and enhance rollHour logging
* feat: add HopScalingModule for adaptive hop limit recommendations
Introduces HopScalingModule, a sampled hop-distance histogram that
recommends the minimum hop limit needed to reach ~40 nodes, and
automatically reducing the hops as the mesh grows.
Key design:
- 512-byte packed histogram (128 × 4-byte Record entries) embedded
in a new HopScalingModule.
- Each Record: 16-bit node hash, 3-bit hop distance, 13-bit seen bitmap
- Sampling filter: only nodes where (hash & (denom-1)) == 0 are kept;
denominator doubles on overflow and halves when utilisation is low
- Hourly rollHour(): tallies per-hop counts, walks scaled buckets to
find the minimum hop satisfying TARGET_AFFECTED_NODES (40), applies a
politeness extension based on recent/older activity ratio, shifts all
seen bitmaps, and persists state to /prefs/hopScalingState.bin
- Hop recommendation gated by bootstrap (requires >=1 rollHour before
overriding HOP_MAX)
- NodeDB calls samplePacketForHistogram() on every non-MQTT rx packet
- Module also estimates total mesh size and logs useful information about
mesh characteristics.
Changes:
- src/modules/HopScalingModule.h/.cpp: new module
- src/mesh/NodeDB.cpp: wire up samplePacketForHistogram
- src/mesh/Router.cpp: consume getLastRequiredHop()
- test/test_hop_scaling/: 12-test suite covering all mesh topologies and
anticipated operational requirements
* test: increase run iterations in sparse to dense transition test
* feat: refactor HopScalingModule to use RUNS_PER_HOUR constant and improve logging
* feat: enhance HopScalingModule with filtering denominator management and add tests for state transitions
* refactor: remove CompactHistogram module and related files
* address copilot review comments
* Tweak: packet sampling only lora
* ove role-based hop floor logic and related definitions into the module - keep it in one place.
* Refactor MockNodeDB to use nodeInfoLiteSetBit for MQTT flag setting
* Refactor hop scaling parameters and logic to integer maths and put default values in defaults.h. Small flash size reduction, no functional impact.
* Update unit test preprocessor directives to PIO_UNIT_TESTING for consistency
* refactor: improve test output organization and clarity in hop scaling tests
---------
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
* feat: add Nordic nRF54L15-DK variant (Zephyr + BLE + LoRa)
Adds a community hardware variant for the Nordic nRF54L15-DK (PCA10156)
with an external EBYTE E22-900M30S (SX1262) LoRa module. First Meshtastic
port running on the Zephyr RTOS; all other Nordic targets use the nRF5
SoftDevice stack.
Scope
-----
- New Zephyr-based platform layer under src/platform/nrf54l15/ providing
Arduino-compatible shims (Arduino.h, SPI, Wire, Print, Stream) over the
Zephyr APIs plus a LittleFS-backed InternalFileSystem on SPIM20.
- Bluetooth LE peripheral (NRF54L15Bluetooth.*) built on the Zephyr BT
host stack, exposing the Meshtastic GATT service with legacy
connectable advertising, just-works pairing, dynamic MTU exchange
(up to 247 bytes), and iOS connection-parameter tweaks.
- Variant directory variants/nrf54l15/nrf54l15dk/ with pin map for the
E22 module on connector J1, PlatformIO env (nrf54l15dk), Zephyr
DT overlay and a wiring README.
- Zephyr project config (zephyr/prj.conf + board overlay) tuned for
BT + LoRa: 16 KB main stack, 4 KB BT RX thread, RTT logging in
immediate mode, newlib-nano heap sized to leave room for the GATT
pools while still allowing ATT MTU=247.
- extra_scripts/nrf54l15_linker.py works around a PlatformIO + old Ninja
issue where Zephyr's two-pass linker script generation does not run
automatically; the post-script parses build.ninja and invokes the
gcc -E step directly before the final link.
- boards/nrf54l15dk.json board definition (PlatformIO needs it for the
DK; the Seeed platform only ships the XIAO variants).
- variants/rp2350/rp2350.ini excludes platform/nrf54l15/ from RP2350
build_src_filter so the shared platform tree does not leak between
targets.
- .gitignore: add nRF J-Link / RTT debug artifacts (flash.jlink,
rtt_*.txt).
Shared source changes
---------------------
- src/main.{cpp,h}, src/RedirectablePrint.cpp, src/FSCommon.{cpp,h},
src/mesh/{Channels,NodeDB,RadioLibInterface,MeshService,PhoneAPI}.cpp,
src/mesh/RadioLibInterface.h, src/modules/AdminModule.cpp: add small
guards / helpers so the Zephyr build compiles alongside the Arduino
targets. Behavior on existing boards is unchanged.
Hardware model
--------------
HW_VENDOR maps to meshtastic_HardwareModel_PRIVATE_HW until a dedicated
protobuf enum value is assigned upstream. The variant declares
custom_meshtastic_hw_model = 132 so the maintainers can wire the new
enum value through the protobufs repo after merge.
Hardware note
-------------
The E22-900M30S does not connect its DIO2 pin to TXEN internally — a
wire/solder bridge between DIO2 and TXEN on the module is required for
TX to work. Details and full pin map are in the variant README.
Validation
----------
Built clean against develop. On real hardware (April 2026) the port
passes end-to-end: iOS companion app pairs and connects, configuration
round-trip works, LoRa TX/RX reaches a canonical tbeam on the same mesh
channel, NodeDB updates propagate both ways, and traceroute completes.
* fix(nrf54l15): use atomic fs_rename instead of copy fallback
Zephyr LittleFS on nrf54l15 supports fs_rename natively, so route it
through the same atomic path as ESP32. The previous copyFile+remove
fallback truncated the destination before copying, leaving 0-byte files
if interrupted mid-write.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(nrf54l15): expand storage_partition from 36KB to 700KB
LittleFS on the default 9-block (36KB) storage_partition ran out of
space during copy-on-write of config.proto, causing fs_write to return
ENOSPC and pb_encode to surface "io error" when saving configuration
via the mobile app.
Reclaim slot1_partition (the MCUboot secondary slot — unused since we
flash directly via J-Link) and grow storage_partition to span
0xb6000..0x165000 (~175 blocks).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(nrf54l15): drop USERPREFS_LORACONFIG_* so LoRa config stays mutable
NodeDB rewrites LoRa config from USERPREFS_LORACONFIG_* on every boot,
which prevented reconfiguration via the BLE/serial app. Drop the
variant-level defaults; users configure region and modem preset through
the app like every other variant.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(nrf54l15): enforce MITM passkey pairing on GATT service
- Add MESH_PERM_READ/MESH_PERM_WRITE macros (READ_AUTHEN/WRITE_AUTHEN)
on all mesh service characteristics so clients must complete passkey
exchange before accessing fromNum/fromRadio/toRadio/logRadio.
- Wire FIXED_PIN mode to bt_passkey_set() so the device advertises a
known PIN (config.bluetooth.fixed_pin); RANDOM_PIN keeps default
per-pairing random passkey.
- Reduce BleDeferredThread HARD_WATCHDOG_MS from 3min to 1min.
- prj.conf: CONFIG_BT_SMP_ENFORCE_MITM=y, CONFIG_BT_FIXED_PASSKEY=y,
CONFIG_BT_SMP_SC_PAIR_ONLY=n (legacy fallback for clients that abort
SC pairing with reason 0x01 within 150ms).
* fix(nrf54l15): resolve develop-merge conflict + cppcheck warnings
The `Merge branch 'develop'` left two ~RadioLibInterface() declarations
in src/mesh/RadioLibInterface.h: the inline version added upstream by
PR #10254 (which independently applied the same UAF guard this PR was
carrying) and the out-of-line version this PR introduced. GCC rejects
the duplicate, breaking every platform build. Drop the out-of-line
declaration + definition; keep upstream's inline form.
Also silence the 13 cppcheck low warnings introduced by the new
nrf54l15 Arduino shim — Arduino's `String`/`SPISettings` API contract
relies on implicit single-arg constructors used pervasively by
existing Meshtastic code, so suppress `noExplicitConstructor` inline
with a comment instead of breaking the API. The few mechanical wins
(`const tmp[2]`, `const uint32_t *sp`) are applied directly.
* fmt: fix Trunk Check lint issues on nrf54l15-port
- extra_scripts/nrf54l15_linker.py: move regular imports above
Import("env") to silence E402, add trunk-ignore-all(F821) for the
PIO/SCons SConstruct injection (matches esp32_pre.py / nrf52_extra.py
convention)
- src/platform/nrf54l15/NRF54L15Bluetooth.cpp: clang-format 16.0.3
- boards/nrf54l15dk.json + variants/nrf54l15/nrf54l15dk/README.md:
prettier 3.8.3 (also resolves markdownlint MD060 on README tables)
No behavior change.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(nrf54l15): address Copilot review comments + correct clang-format style
Six review threads from the 2026-04-30 Copilot review:
- src/platform/nrf54l15/nrf54l15_main.cpp: validate PSP against the nRF54L15
SRAM range (0x20000000..0x20040000) and 4-byte alignment before walking the
faulting thread's stack, and clamp the walk so it never reads past the end
of RAM. Prevents a second fault inside the fatal handler when PSP is
corrupted (common in real faults).
- src/platform/nrf54l15/nrf54l15_arduino.cpp: gate the bring-up printk traces
in digitalWrite/digitalRead (CS/NRESET toggle log, BUSY-before-NRESET
snapshot, BUSY periodic timeline) behind a new -DNRF54L15_GPIO_DEBUG flag
that is off by default. The "dev NOT READY" message stays unconditional —
it indicates a genuine hardware/DTS misconfig.
- src/modules/AdminModule.cpp: don't mutate config.device.output_gpio_enabled
from handleGetConfig(). Reflect the live pin state in the response payload
only — a getter must not write back to disk-persisted state.
- src/platform/nrf54l15/InternalFileSystem.h: derive totalBytes() from
FIXED_PARTITION_SIZE(storage_partition) at compile time so it tracks the
DK overlay's ~700 KB partition instead of the stale 36 KB hard-coded value.
Updated the file header comment accordingly.
- extra_scripts/nrf54l15_linker.py: make _extract_gcc_command() handle the
POSIX Ninja COMMAND format (no `cmd.exe /C "..."` wrapper) in addition to
the Windows form, so the script doesn't hard-fail on Linux/macOS hosts.
- src/platform/nrf54l15/NRF54L15Bluetooth.cpp: clamp NO_PIN to RANDOM_PIN
with a one-shot LOG_WARN. The mesh GATT permissions are declared with
BT_GATT_PERM_*_AUTHEN and prj.conf sets CONFIG_BT_SMP_ENFORCE_MITM=y, so
NO_PIN with no auth callbacks would leave every characteristic returning
BT_ATT_ERR_AUTHENTICATION. Falling back to RANDOM_PIN keeps the link
usable instead of silently broken. Also re-formatted this file with the
project's .trunk/configs/.clang-format (Linux braces, 4-space indent,
130-col) — the previous lint-fix commit a2aca3234 accidentally used the
default LLVM style here, which CI's clang-format would have rejected.
Build verified: pio run -e nrf54l15dk passes, RAM 47.4%, Flash 28.6%.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(nrf54l15): address remaining Copilot review threads
Round 2/3 review fixes — bugs first, then docs/portability:
BLE concurrency (NRF54L15Bluetooth.cpp):
- onNowHasData / sendLog / BleDeferredThread / shutdown: acquire
active_conn under ble_mutex via new acquire_active_conn() helper so
disconnected_cb can't free the conn between the null check and
bt_conn_ref/bt_gatt_notify (use-after-unref).
- write_toradio: reject writes that exceed MAX_TO_FROM_RADIO_SIZE with
ATT_ERR_INVALID_ATTRIBUTE_LEN instead of returning success and silently
dropping the payload (would hide failed config writes from the phone).
- start_advertising: truncate the device name to fit the 31-byte legacy
scan-response limit and switch to BT_DATA_NAME_SHORTENED so
bt_le_adv_start() doesn't reject the payload when the name approaches
CONFIG_BT_DEVICE_NAME_MAX=32.
Linker / portability:
- main.h: drop the rp2040Loop() forward declaration that had no
definition and no callers — would surface as a link error if any RP2040
build added a call to the symbol.
- nrf54l15_arduino.cpp: transfer16() now uses static __aligned(4) DMA
buffers (matching transfer()), removing the EasyDMA-reachability hazard
of caller-stack buffers on this part.
Filesystem (InternalFileSystem.h):
- usedBytes(): return real usage from fs_statvfs() instead of 0 so OTA
/ range-test free-space guards work.
- rewindDirectory(): close the dir before reopening — Zephyr fs_dir_t has
no rewind, and re-fs_opendir on an open handle leaks LittleFS state.
Crash handler (nrf54l15_main.cpp):
- After the stack walk, busy-wait 50 ms to flush RTT/printk and call
sys_reboot(SYS_REBOOT_COLD) directly so the saved_crash record is
actually reported on the next boot. Default Zephyr config has
RESET_ON_FATAL_ERROR=n, so the previous k_fatal_halt() spun forever.
Generalization / config:
- PhoneAPI.cpp: replace the NRF54L15_DK ifdef with a
MESHTASTIC_EXCLUDE_FILES_MANIFEST capability flag (defined in the
nrf54l15dk env) so future variants can opt in/out without touching
shared code.
- variants/nrf54l15/nrf54l15.ini: parameterize libdeps include paths via
${PIOENV} so additional nRF54L15 envs sharing nrf54l15_base don't break.
- prj.conf: drop the stale "36 KB storage_partition" comments — the DK
overlay reclaims slot1 to ~700 KB and runtime size comes from
FIXED_PARTITION_SIZE.
- nrf54l15dk overlay: remove the zephyr,console / zephyr,shell-uart
chosen entries that conflicted with CONFIG_UART_CONSOLE=n + RTT
console; keep uart30 enabled so swapping the console is one Kconfig
flip away.
Build: nrf54l15dk SUCCESS (flash 28.6%, RAM 47.4%); wiznet_5500_evb_pico2
SUCCESS (verifies the shared main.h change).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(nrf54l15dk): use PRIVATE_HW (255) for custom_meshtastic_hw_model
Per @thebentern's review: the nRF54L15-DK is a development kit, not a
canonical Meshtastic SKU, so it falls under HardwareModel::PRIVATE_HW
(255) — the same enum value already used at runtime via HW_VENDOR. The
placeholder 132 is removed; no dedicated enum number will be assigned
for DK boards. Slug stays NRF54L15_DK as a human-readable identifier.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(nrf54l15): unstarve bt_long_wq so SC pairing completes
bt_pub_key_gen() runs the ECC P256 key generation on bt_long_wq. At default
prio=10 (preemptible) and stack=1400 it gets starved by Meshtastic app
threads at boot — sc_public_key stays NULL for minutes, smp_public_key()
defers with SMP_FLAG_PKEY_SEND, and every SC pairing attempt stalls right
after the public-key exchange. iOS shows "Connecting…" forever with no
PIN prompt; bleak/CLI fails the first CCC notify write with
"Protocol Error 0x05: Insufficient Authentication".
Set CONFIG_BT_LONG_WQ_PRIO=0 (highest preemptible, ties with main) and
CONFIG_BT_LONG_WQ_STACK_SIZE=4096 (margin for the P256M driver frames).
Validated E2E with iOS Meshtastic app: bt_smp_pkey_ready fires within ~40 s
of boot, 20 SC Passkey Entry rounds complete with matching pcnf/cfm,
encrypt 0x01 / sec_level 0x04 (Authenticated MITM), bonded=1.
* feat(nrf54l15): hardware I2C bus via TWIM30 + sensor telemetry
Adds the Arduino TwoWire layer for the nRF54L15-DK so Meshtastic's
sensor drivers can talk to external I2C devices over the hardware
TWIM30 peripheral.
Bus binding:
- &uart30 disabled in the board overlay (peripheral instance 30 is
shared between UARTE30 / TWIM30 / SPIM30 — pick one). Console stays
on RTT via CONFIG_RTT_CONSOLE.
- New i2c30_default / i2c30_sleep pinctrl with SDA=P0.03 / SCL=P0.04.
External 4.7 kOhm pull-ups required on both lines.
- &i2c30 enabled at I2C_BITRATE_FAST (400 kHz).
- button_3 (SW3, P0.04) deleted from DTS so the pad can be claimed by
i2c30 pinctrl; SW3 is still wired to the pad on the DK, do not press
it during I2C use or it will short SCL to GND.
Arduino layer:
- src/platform/nrf54l15/Wire.cpp resolves the DT node at compile time
via DEVICE_DT_GET(DT_NODELABEL(i2c30)) and dispatches Arduino's
beginTransmission / write / endTransmission / requestFrom to
i2c_write / i2c_write_read / i2c_read. Buffer is sized to 256 bytes
for forward compatibility with the SE050 secure element on the
custom PCB.
- Wire.h drops the prior compile-only stubs and exposes the real
TwoWire surface.
- Arduino.h: BitOrder becomes an enum (not #define) so Adafruit_BusIO's
`typedef BitOrder BusIOBitOrder;` compiles.
Variant + build flags:
- nrf54l15.ini flips HAS_WIRE / HAS_SENSOR / HAS_TELEMETRY from 0 to 1
and cherry-picks the sensor libs Meshtastic needs (BusIO, Sensor,
BMP280, BME280, INA219/226/260/3221, SHT4X). The full
environmental_base group is avoided because it pulls
Adafruit_SSD1306 / Adafruit_GFX which rely on Arduino pin macros the
Zephyr shim does not implement.
- nrf54l15dk variant.h defines PIN_WIRE_SDA / PIN_WIRE_SCL for parity
with the Arduino convention used by other variants. The actual bus
wiring is fixed by the overlay pinctrl above.
Validated 2026-05-14/15 on the DK with BMP280 @ 0x76 (temperature +
barometric pressure) and INA3221 @ 0x42 (rail voltage / current);
EnvironmentTelemetry / PowerTelemetry packets transmit successfully
over LoRa.
Footprint cost on nrf54l15dk: +45 KB flash, +1.7 KB RAM.
* feat(nodedb): honor USERPREFS for environment telemetry on first boot
installDefaultConfig() now respects two new compile-time prefs:
USERPREFS_CONFIG_ENV_TELEM_UPDATE_INTERVAL
USERPREFS_CONFIG_ENVIRONMENT_MEASUREMENT_ENABLED
The mobile apps enforce a 30 min floor on environment_update_interval
in the settings UI, which makes short-interval bring-up testing of new
sensor hardware painful — you have to wait half an hour for the first
LoRa packet to confirm wiring + driver. With these prefs baked into
the variant, the firmware can ship a freshly-flashed device that
broadcasts on a shorter cadence (e.g. 900 s) the moment storage_partition
is empty.
Both prefs are gated on #ifdef so the behavior is unchanged for any
variant that does not opt in. Documented in userPrefs.jsonc with the
existing telemetry-interval pref block.
* fix(nrf54l15): allow multiple bonded BLE peers
CONFIG_BT_MAX_PAIRED defaults to 1, so once the first peer (e.g. an
iOS phone) has paired and bonded, every subsequent pairing attempt
from a different MAC fails inside bt_keys_get_addr() with no free
key slot — the host returns BT_SECURITY_ERR_KEY_DOES_NOT_EXIST and
the second peer never gets past SMP.
Raise the slot count to 4 so the device can simultaneously hold an
iOS phone, a Windows host, a Linux host, and one spare bond. Add
BT_KEYS_OVERWRITE_OLDEST so that once the table fills, the LRU peer
is evicted on the next pairing rather than rejecting the new peer.
This matches the behavior other Meshtastic ports already provide
(nRF52 uses CONFIG_BT_PERIPHERAL_PRIO_CONN with similar semantics).
Discovered while bringing up the Python CLI on Windows alongside
the existing iOS bond.
* fix(nrf54l15): zero-initialize TwoWire buffers + clang-format Wire
cppcheck on every CI target (esp32s3, rp2040, rp2350, nrf52840, ...) was
failing the build with two `uninitMemberVar` warnings on TwoWire's
constructor: `txBuf` and `rxBuf` (256-byte arrays) were not initialized.
Even though the buffers are only read after txLen/rxLen is set, leaving
them uninitialized is a footgun if any future caller bypasses the
len-set step. Use C++11 value-initialization in the member initializer
list — costs ~512 B of memset at boot, gains a clean cppcheck pass and
defensive-against-future-changes semantics.
Also reformat Wire.{cpp,h} with the project's `.trunk/configs/.clang-format`
config so the Trunk Check Runner passes — clang-format moved the
`<errno.h>` include before the Zephyr-namespaced ones in Wire.cpp and
collapsed two inline overloads to single lines in Wire.h.
* fix(AdminModule): remove dead OUTPUT_GPIO_PIN/GpioOutputModule references
OUTPUT_GPIO_PIN is never defined and modules/GpioOutputModule.h doesn't
exist in the codebase; all #ifdef OUTPUT_GPIO_PIN branches were dead code
introduced by the nRF54L15-DK variant commit. Strips the include, the
output_gpio_enabled OFF→ON/ON→OFF transition logic in handleSetConfig(),
and the digitalRead() reflection in handleGetConfig().
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-authored-by: Ben Meadors <benmmeadors@gmail.com>
* macOS: enable CH341 LoRa-hardware path — fix serial truncation, document setup
Verified on Apple Silicon with a CH341A USB-SPI bridge (VID 0x1A86,
PID 0x5512) wired to an SX1262 (Meshstick variant) that the existing
`pine64/libch341-spi-userspace` lib_dep works on macOS as-is — Apple's
bundled CH34x driver only matches the CH340 *UART* variant
(PID 0x7523), so the CH341A's interface 0 is left unclaimed and
libusb opens / configures / claims it directly via IOUSBHostInterface.
End-to-end test: meshtasticd boots, libusb claim succeeds, SX1262 init
returns 0, TCP API serves the meshtastic CLI's --info / --sendtext flow.
Two changes:
1. **`PortduinoGlue.cpp:497`**: pass `sizeof(serial)` (= 9) instead of
the literal `8` to `Ch341Hal::getSerialString()`. The function in
`USBHal.h:61-68` treats `len` as buffer size and reserves one slot
for the null terminator (`bytesCopied = (len - 1) < 8 ? (len - 1) : 8`),
so passing 8 produced a 7-char serial — which then broke the
`strlen(serial) == 8` check at line 502, skipping the auto-MAC
derivation from serial + product string. On Linux this was masked
by the BlueZ HCI MAC fallback in `getMacAddr()` at lines 139-157,
but on macOS that fallback is `__linux__`-guarded so the serial path
is mandatory and the truncation left `mac_address` empty, causing
the daemon to exit with `*** Blank MAC Address not allowed!`.
2. **`variants/native/portduino/platformio.ini`**: expand the
`[env:native-macos]` comment block with a "Real LoRa hardware on
macOS" section. Documents:
- Why no upstream library change is needed (Apple kext targets
CH340/UART, not CH341A/SPI; libusb's `#ifdef __linux__` skip is
correct for macOS in this case).
- How to point `meshtasticd` at an existing platform-agnostic
`bin/config.d/lora-*.yaml` for CH341 hardware.
- The auto-MAC-derivation contract (now working with this fix).
- `ioreg` and `LIBUSB_DEBUG=4` diagnostic recipes for the failure
mode where a third-party WCH `CH34xVCPDriver` *would* claim
interface 0 (`kmutil unload -b <bundleID>` workaround).
No upstream library forks, no PR chain, no additional lib_deps —
the existing `pine64/libch341-spi-userspace` + libusb-1.0 stack does
the right thing on macOS already.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* Apply suggestion from @Copilot
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
* macOS: enable CH341 LoRa-hardware path — fix serial truncation, document setup
Verified on Apple Silicon with a CH341A USB-SPI bridge (VID 0x1A86,
PID 0x5512) wired to an SX1262 (Meshstick variant) that the existing
`pine64/libch341-spi-userspace` lib_dep works on macOS as-is — Apple's
bundled CH34x driver only matches the CH340 *UART* variant
(PID 0x7523), so the CH341A's interface 0 is left unclaimed and
libusb opens / configures / claims it directly via IOUSBHostInterface.
End-to-end test: meshtasticd boots, libusb claim succeeds, SX1262 init
returns 0, TCP API serves the meshtastic CLI's --info / --sendtext flow.
Two changes:
1. **`PortduinoGlue.cpp:497`**: pass `sizeof(serial)` (= 9) instead of
the literal `8` to `Ch341Hal::getSerialString()`. The function in
`USBHal.h:61-68` treats `len` as buffer size and reserves one slot
for the null terminator (`bytesCopied = (len - 1) < 8 ? (len - 1) : 8`),
so passing 8 produced a 7-char serial — which then broke the
`strlen(serial) == 8` check at line 502, skipping the auto-MAC
derivation from serial + product string. On Linux this was masked
by the BlueZ HCI MAC fallback in `getMacAddr()` at lines 139-157,
but on macOS that fallback is `__linux__`-guarded so the serial path
is mandatory and the truncation left `mac_address` empty, causing
the daemon to exit with `*** Blank MAC Address not allowed!`.
2. **`variants/native/portduino/platformio.ini`**: expand the
`[env:native-macos]` comment block with a "Real LoRa hardware on
macOS" section. Documents:
- Why no upstream library change is needed (Apple kext targets
CH340/UART, not CH341A/SPI; libusb's `#ifdef __linux__` skip is
correct for macOS in this case).
- How to point `meshtasticd` at an existing platform-agnostic
`bin/config.d/lora-*.yaml` for CH341 hardware.
- The auto-MAC-derivation contract (now working with this fix).
- `ioreg` and `LIBUSB_DEBUG=4` diagnostic recipes for the failure
mode where a third-party WCH `CH34xVCPDriver` *would* claim
interface 0 (`kmutil unload -b <bundleID>` workaround).
No upstream library forks, no PR chain, no additional lib_deps —
the existing `pine64/libch341-spi-userspace` + libusb-1.0 stack does
the right thing on macOS already.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* Apply suggestion from @Copilot
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
* Add C++17 support
* Add C++17 runtime probes and update build configurations for STM32 and Portduino
* Remove unflags
* Update C++ standard flags across the platforms
* Convert a couple of instances to structured bindings
* NRF52 platform.txt to add C++17 support
* Still need the unflags apparently
* Remove C++17 runtime probe tests from test_main.cpp
* Reconfigured doesnt need a nodiscard
* Remove nodiscard attribute from init() method in RadioInterface
* Remove mbedtls/error.h from build flags
Removed include directive for mbedtls/error.h from build flags.
* Fix IRAM overflow
* Fix IRAM overflow
* Add build flag to exclude MQTT from rak11200
* Update C++ standard from gnu17 to gnu++17
* Add ESP32 Power Management lessons learned document
Documents our experimentation with ESP-IDF DFS and why it doesn't
work well for Meshtastic (RTOS locks, BLE locks, USB issues).
Proposes simpler alternative: manual setCpuFrequencyMhz() control
with explicit triggers for when to go fast vs slow.
* Addition of traffic management module
* Fixing compile issues, but may still need to update protobufs.
* Fixing log2Floor in cuckoo hash function
* Adding support for traffic management in PhoneAPI.
* Making router_preserve_hops work without checking if the previous hop was a router. Also works for CLIENT_BASE.
* Adding station-g2 and portduino varients to be able to use this module.
* Spoofing from address for nodeinfo cache
* Changing name and behavior for zero_hop_telemetry / zero_hop_position
* Name change for exhausting telemetry packets and setting hop_limit to 1 so it will be 0 when sent.
* Updated hop logic, including exhaustRequested flag to bypass some checks later in the code.
* Reducing memory on nrf52 nodes further to 12 bytes per entry, 12KB total using 8 bit hashes with 0.4% collision. Probably ok. Adding portduino to the platforms that don't need to worry about memory as much.
* Fixing hopsAway for nodeinfo responses.
* traffic_management.nodeinfo_direct_response_min_hops -> traffic_management.nodeinfo_direct_response_max_hops
* Removing dry run mode
* Updates to UnifiedCacheEntry to use a common cache, created defaults for some values, reduced a couple bytes per entry by using a resolution-scale time selection based on configuration value.
* Enhance traffic management logging and configuration. Updated log messages in NextHopRouter and Router to include more context. Adjusted traffic management configuration checks in AdminModule and improved cache handling in TrafficManagementModule. Ensured consistent enabling of traffic management across various variants.
* Implement destructor for TrafficManagementModule and improve cache allocation handling. The destructor ensures proper deallocation of cache memory based on its allocation source (PSRAM or heap). Additionally, updated cache allocation logic to log warnings only when PSRAM allocation fails.
* Update TrafficManagementModule with enhanced comments for clarity and improve cache handling logic. Update protobuf submodule to latest commit.
* Creating consistent log messages
* Remove docs/ESP32_Power_Management.md from traffic_module
* Add unit tests for Traffic Management Module functionality
* Fixing compile issues, but may still need to update protobufs.
* Adding support for traffic management in PhoneAPI.
* Making router_preserve_hops work without checking if the previous hop was a router. Also works for CLIENT_BASE.
* Enhance traffic management logging and configuration. Updated log messages in NextHopRouter and Router to include more context. Adjusted traffic management configuration checks in AdminModule and improved cache handling in TrafficManagementModule. Ensured consistent enabling of traffic management across various variants.
* Implement destructor for TrafficManagementModule and improve cache allocation handling. The destructor ensures proper deallocation of cache memory based on its allocation source (PSRAM or heap). Additionally, updated cache allocation logic to log warnings only when PSRAM allocation fails.
* Update TrafficManagementModule with enhanced comments for clarity and improve cache handling logic. Update protobuf submodule to latest commit.
* Add mock classes and unit tests for Traffic Management Module functionality.
* Refactor setup and loop functions in test_main.cpp to include extern "C" linkage
* Update comment to include reduced memory requirements
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
* Re-arranging comments for programmers with the attention span of less than 5 lines of code.
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
* Update comments in TrafficManagementModule to reflect changes in timestamp epoch handling and memory optimization details.
* bug: Use node-wide config_ok_to_mqtt setting for cached NodeInfo replies.
* Better way to handle clearing the ok_to_mqtt bit
* Add bucketing to cuckoo hashing, allowing for 95% occupied rate before major eviction problems.
* Extend nodeinfo cache for psram devices.
* Refactor traffic management to make hop exhaustion packet-scoped. Nice catch.
* Implement better position precision sanitization in TrafficManagementModule.
* Added logic in TrafficManagementModule to invalidate stale traffic state. Also, added some tests to avoid future me from creating a regression here.
* Fixing tests for native
* Enhance TrafficManagementModule to improve NodeInfo response handling and position deduplication logic. Added tests to ensure local packets bypass transit filters and that NodeInfo requests correctly update the requester information in the cache. Updated deduplication checks to prevent dropping valid position packets under certain conditions.
---------
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>