Commit Graph
12223 Commits
Author SHA1 Message Date
kevin 4d919d13c1 切换到v6引导 2026-07-19 17:46:04 +08:00
kevin e25f4ee2d1 修复led问题 2026-07-19 16:19:59 +08:00
kevin fbbfcf072f 增加mini模型 2026-07-19 15:13:41 +08:00
kevin 9237cf9012 moonshine: 加充电检测 P0.16 + 整理构建烧录文档
variant.h: 新增 EXT_CHRG_DETECT (P0.16, 高电平=充电中),
Power.cpp 据此驱动 isCharging() 和充电状态 LED 行为。

readme: 引脚表加 CHARGE_DET 行;合并 Build/Flash 章节为
'Build & Flash',分首次烧录(recover+SD+app)和增量烧录(app only)
两个命令块,便于复用。
2026-07-19 03:10:02 +08:00
kevin 30563c90a6 修复 moonshine NRF52 启动: LFRC + S140 v7 + 禁用 I2C
RF-BM-ND05 无 32.768kHz 晶振,USE_LFXO 导致 SoftDevice 卡在
RTC1_IRQHandler,BLE 静默失败。改用 USE_LFRC。

P0.09/P0.10 是 NFC 引脚,被模块内部电路拉低,TWIM endTransmission
无限等待。禁用 I2C 及依赖功能(环境传感器/输入代理/加速度计/磁力计)。

linker 改用 nrf52840_s140_v7.ld 匹配仓库打包的 S140 v7.3.0
SoftDevice(app @ 0x27000)。readme 补充 OpenOCD SWD 烧录流程。
2026-07-19 02:45:26 +08:00
kevin 421838fd1d 原来没流控 2026-07-18 22:50:46 +08:00
kevin 8eaea338e8 增加moonshine NRF NODE 2026-07-18 22:47:50 +08:00
吴文峰andGitHub 68e66309f2 Merge branch 'meshtastic:develop' into develop 2026-07-18 21:37:51 +08:00
b20d89974a Stop accelerometer thread when double-tap/wake-on-motion disabled at runtime (#11025)
* Stop accelerometer thread when double-tap/wake-on-motion disabled at runtime

double_tap_as_button_press and wake_on_tap_or_motion are applied live: the
OFF->ON edge calls accelerometerThread->start(), but there was no ON->OFF
branch, so turning either flag off left the sensor thread running (polling
I2C, drawing power) until reboot. Worse, because enabled stayed true, a later
OFF->ON edge was a no-op (the enabled==false guard blocked re-start), leaving
the feature un-restartable without a reboot.

Add the symmetric ON->OFF branch in both handlers. When a flag goes true->off
and the other consumer of the shared thread is also off, call
accelerometerThread->disable() (stops runOnce polling and clears enabled so a
later re-enable can start() again). Each branch checks the other flag first so
disabling one feature never stops the sensor while the other still needs it.

* Keep accelerometer thread running when its sensor drives the compass

* Guard accelerometer thread config toggles against a null thread pointer

* AdminModule: factor shared accelerometer start/stop into a helper

The device and display config handlers had mirror-image blocks reconciling the
shared accelerometer thread. Extract reconcileAccelerometerThread(wasOn, nowOn,
otherFeatureOn) so the null guard, edge logic, compass (providesHeading) guard,
and rationale live in one place; each call site is now a single call. Behavior
is unchanged. Also drops the redundant per-field assignment that the
whole-struct `config.device = ...` / `config.display = ...` overwrites anyway.

---------

Co-authored-by: Thomas Göttgens <tgoettgens@gmail.com>
2026-07-18 06:26:20 -05:00
renovate[bot]GitHubrenovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
40d0d80f35 chore(deps): update adafruit pct2075 to v1.3.1 (#11055)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-07-18 06:13:49 -05:00
37fdbb49bf Add RAK WisMesh Pod variant and fix Tag LPCOMP wake on user shutdown (#11049)
* Add RAK WisMesh Pod variant and fix Tag LPCOMP wake on user shutdown

* chore: trunk fmt - replace Unicode em-dash in Pod comment

Co-authored-by: Cursor <cursoragent@cursor.com>

---------

Co-authored-by: Ben Meadors <benmmeadors@gmail.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-18 06:13:20 -05:00
d4aa7760cc Use hardware RNG for session passkey and PKC extra nonce (#11047)
* Use hardware RNG for session passkey and PKC extra nonce

The admin session passkey and the Curve25519 extra nonce were drawn
from Arduino random(), which is not a CSPRNG. Source them from
HardwareRNG::fill, mirroring the signing path, and fall back to the
seeded CSPRNG (CryptRNG) only when no hardware source is available.

* AdminModule: make session passkey expiry rollover-safe

session_time was compared as millis()/1000 seconds with additive
thresholds, which breaks across the millis() wrap and could keep a stale
admin session key valid. Store session_time in millis() and use
Throttle::isWithinTimespanMs for the 150s refresh and 300s validity
windows.

* AdminModule: track session passkey validity with an explicit flag

session_time == 0 was used as the uninitialized sentinel, but millis()
is legitimately 0 in the first millisecond of uptime, so a passkey
issued then would be treated as no session. Use a dedicated
session_passkey_valid flag instead.

* AdminModule: camelCase the session passkey validity flag

---------

Co-authored-by: Ben Meadors <benmmeadors@gmail.com>
2026-07-17 15:49:00 -05:00
renovate[bot]GitHubrenovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
ac027ec5ca chore(deps): update meshtastic-esp8266-oled-ssd1306 digest to 9d9ba7e (#11053)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-07-17 15:18:23 -05:00
oscgonferandGitHub 73180141b3 Add userprefs for telemetry screen settings (#11051)
* Add telemetry update interval to userprefs

* Add telemetry screen configs to userprefs

* Removed duped code from cherry-pick
2026-07-17 15:08:06 -05:00
985b983f67 TrafficManagement: gate role/NodeInfo cache writes on signer authenticity (#11048)
* TrafficManagement: gate role/NodeInfo cache writes on signer authenticity

The tier-3 role cache and the PSRAM NodeInfo response cache were updated
from any received NodeInfo with no authenticity check, so a spoofed
NodeInfo could set a node's cached role (granting dedup exceptions) or
poison the cached user served in direct responses. Skip both cache
writes when a known signer's NodeInfo arrives unsigned, matching the
identity-update gate on the direct-response path.

* TrafficManagement: hoist shared NodeInfo signer lookup

Compute the sender node lookup and unauthenticated-signer check once per
NodeInfo packet and reuse it for both the cache-refresh gate and the
direct-response identity gate, avoiding a second O(N) getMeshNode scan.

---------

Co-authored-by: Ben Meadors <benmmeadors@gmail.com>
2026-07-17 16:32:01 +01:00
Ben Meadors 6e0e9c174d Protobufs 2026-07-17 09:45:56 -05:00
Thomas GöttgensandGitHub cfecef5376 Channels: fix off-by-one bound in decryptForHash (#11046)
decryptForHash accepted chIndex == getNumChannels() before reading
getHash(chIndex), which indexes one past hashes[MAX_NUM_CHANNELS].
Use >= so an out-of-range index is rejected before the array read.
2026-07-17 08:15:19 -05:00
Thomas GöttgensandGitHub 8147970957 AdminModule: only accept admin responses to requests we sent (#11024)
* AdminModule: only accept admin responses to requests we sent

An admin *_response short-circuited the auth and session-passkey checks that gate
every other admin message, so any node could deliver one. On a channel the module
listens to unauthenticated, a get_module_config_response drives the remote-hardware
pin handler with attacker-supplied values.

Track the destination of outgoing admin requests (per remote, with the pinned PKC
key when there is one) and accept a response only from a node with a matching
outstanding request, inside the same window as the session passkey. Local (from == 0)
admin is unchanged; PhoneAPI already gates it.

Also fix the response dispatch: get_module_config_response.which_payload_variant is a
ModuleConfig oneof tag, but it was compared against the AdminMessage ModuleConfigType
enum (different numbering), so the handler never ran. Compare against the oneof tag.

* AdminModule: rollover-safe request window, bind response to request type

Two review refinements to the request/response pairing:

Use Throttle::isWithinTimespanMs for the outstanding-request expiry instead of
comparing millis()/1000 sums, which mis-expired across the millis() rollover.

Bind each accepted response to a request type actually sent to that node. Each
outstanding record now carries a bitmask of the response variants its requests
authorize, so a get_owner request no longer admits a get_module_config response.
The mask accumulates per remote, so a client may still pipeline several request
types to one node and have every answer accepted.

* AdminModule: track admin requests per-request, not per-node

Reworks the outstanding-request table so each request is its own entry with its own
expiry window and pinned key, replacing the per-node bitmask that shared one timestamp
and one key across every response variant.

That sharing let a later request to the same node extend an earlier one's window and,
worse, clear its PKC pin: an unpinned request cleared keyValid, so a plaintext response
to an earlier PKC-pinned request was then accepted. Per-request entries keep each pin
intact. Identical requests are de-duplicated (a client may fetch several config subtypes,
all answered by one response variant) and eviction compares elapsed time, which is
rollover-safe.

Test: a pinned request's response still requires its key after an unpinned request to the
same node.

* AdminModule: match module-config subtype and consume answered requests

Two refinements to the request/response pairing:

Only remote_hardware get_module_config_response mutates state (the pin table), so it
must answer a request for that exact ModuleConfigType, not just any module-config
request. Each entry records the requested subtype and the gate checks it.

A matched request is now consumed on accept, so a node cannot replay a state-mutating
response within the window. Because one request yields one response, request de-dup is
dropped (a client's N indexed get_channel requests are N entries, each consumed once).

Tests: a non-remote-hardware request does not admit a remote_hardware response, and a
second copy of an answered response is rejected.
2026-07-17 07:50:41 -05:00
Ben MeadorsandGitHub f6f3284cfc fix(lockdown): re-lock serial console admin auth on USB link drop (#11028)
On lockdown builds (MESHTASTIC_PHONEAPI_ACCESS_CONTROL, nRF52) the SerialConsole
is a process-lifetime singleton, so the per-connection admin-auth slot keyed by
its inherited PhoneAPI* is reused for every USB/serial client for the whole boot.
An operator's admin unlock stayed latched across serial client swaps: an attacker
plugging into the USB/serial port before the prior session's 15-minute inactivity
timeout inherited admin authorization -- the serial analog of the BLE stale-session
reuse bug closed by resetting state in onConnect()/onDisconnect().

Sample the USB-CDC host link (DTR/mount) state each runOnce(); on the link-drop
edge call close(), which frees the auth slot and resets PhoneAPI state so whoever
connects next re-locks via handleStartConfig()'s !isConnected() branch on their
first want_config -- the same physical-link boundary BLE enforces in onConnect().
On the nRF52 TinyUSB (Adafruit) core, (bool)Port == tud_cdc_n_connected(), which
goes false on cable unplug or host port-close. Console transports without a real
DTR line fall back to the existing inactivity timeout, no worse than before.

Entirely nRF52-lockdown-gated; non-lockdown builds are byte-identical.
2026-07-17 07:05:11 -05:00
d5f78a37d3 XModem: reject path-traversal filenames in the transfer handler (#11037)
The SOH/STX control frame carries a client-supplied filename that was passed
straight to FSCom open/remove/exists, so a ".." component could write, read, or
delete outside the filesystem root. On embedded LittleFS this is largely inert
(no parent of the partition root); on the Portduino daemon FSCom is the host
filesystem under a mountpoint, so it is a real arbitrary-path write/read/delete.

Validate the filename before any FS access: reject empty and any ".." path
component, and NAK the transfer. Absolute and subdirectory paths are still
accepted - the file manager transfers them from the manifest and PortduinoFS
confines them to its mountpoint - so only traversal out of the root is blocked.

Reachable only from a local client connection (PhoneAPI: BLE/USB/serial/TCP),
not over the RF mesh; on the daemon the TCP API makes it network-reachable.

native-suite-count goes to 34: +1 for the new test_xmodem suite and +1 correcting
a pre-existing miscount (it read 32 for 33 suite directories).

Co-authored-by: Ben Meadors <benmmeadors@gmail.com>
2026-07-17 06:31:20 -05:00
Andrew YongandGitHub a3c8778032 stm32wl(rak3172): implement cpuDeepSleep()/shutdown() via STM32LowPower (#10993)
* stm32wl(rak3172): implement cpuDeepSleep()/shutdown() via STM32LowPower

cpuDeepSleep() was an empty stub, so the SDS deep-sleep PowerFSM state
and low-battery shutdown did nothing but leave the CPU running at full
power. Power::shutdown() also didn't include STM32WL in its arch list,
so an explicit shutdown command just logged a FIXME warning.

Adds STM32LowPower as a lib_dep (rak3172 only, mirroring STM32RTC) and
implements cpuDeepSleep() using it. Standby mode is used for both the
finite-wake (SDS/low battery) and forever (shutdown) paths, via
LowPower.shutdown(), with or without an RTC alarm.

If the LSE-backed hardware RTC never came up (stm32wlRtcAvailable()
false), this is a no-op - safer than sleeping without a confirmed wake
source. LowPower.shutdown() resets the MCU on wake and should never
return; if it somehow does, force a reset via HAL_NVIC_SystemReset()
rather than hanging the device silently forever.

Gated behind the existing HAS_LSE flag, so this is inert on every
variant but rak3172.

Assisted-by: Claude Sonnet 5 <noreply@anthropic.com>
Signed-off-by: Andrew Yong <me@ndoo.sg>

* feat(stm32wl): Warn and reset when trying to deepSleep without RTC

This is to prevent leaving the firmware catanonic when firmware has run
its shutdown routine but doesn't actually shutdown.

Signed-off-by: Andrew Yong <me@ndoo.sg>

---------

Signed-off-by: Andrew Yong <me@ndoo.sg>
2026-07-17 05:26:12 -05:00
a7fde715c6 PhoneAPI: gate local admin on the connection, not the wire from (#11033)
* PhoneAPI: gate local admin on the connection, not the wire from

The lockdown admin check in handleToRadioPacket only ran when p.from == 0. from is
a client-supplied wire field, and MeshService::handleToRadio rewrites it to 0 before
AdminModule sees the packet. A client could therefore set from != 0 to skip the
!getAdminAuthorized() drop, then have the packet normalized back to a local-admin
identity and executed - unauthorized admin from an unauthorized connection.

Every packet in handleToRadioPacket already comes from the local connection, so
locality is a property of the connection, not of from. Move the decision into
classifyLocalAdminPacket(), which ignores from and keys only on the admin variant and
the connection's authorization: lockdown_auth is delivered inline, any other admin
from an unauthorized connection is dropped, authorized admin passes through.

The classifier is compiled unconditionally and unit-tested; the guarded caller (built
only in the nRF52 lockdown config) calls it. Test: an unauthorized connection's
ADMIN_APP packet with from != 0 is classified DropUnauthorized.

* PhoneAPI: wipe the encoded lockdown passphrase, shorten comments

---------

Co-authored-by: Ben Meadors <benmmeadors@gmail.com>
2026-07-17 05:21:32 -05:00
Thomas GöttgensandGitHub ed3afdbc61 TrafficManagement: don't learn identity from a signer's unsigned NodeInfo (#11035) 2026-07-17 09:43:22 +02:00
Thomas GöttgensandGitHub 8ebdae9921 Bound payload.bytes prints by payload.size (#11032)
decoded.payload.bytes is a 233-byte protobuf field that is not NUL-terminated.
Printing it with a plain "%s" reads until a NUL, which for a full-length payload
with no NUL runs past the field. Use "%.*s" with payload.size, matching the write
form already used a few lines up in SerialModule.

Live sites: RangeTestModule appendFile, SerialModule text output. The same fix is
applied to the commented-out debug lines in RangeTestModule and Router so the
pattern is consistent if they are re-enabled.
2026-07-16 19:45:22 -05:00
HarukiToredaGitHubcopilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
88f20d349a T5S3 ePaper build fix (#10791)
* Build fix

* update

* Update platformio.ini

* Update SerialConsole.cpp

* Remove Network provisioning for this build

* Update platformio.ini

Per Vid, this is not needed once his PR is merged

* fix: resolve merge conflict in SerialConsole.cpp - use setHostDraining(false) from develop

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
2026-07-16 19:31:30 -05:00
Thomas GöttgensandGitHub bdf92e1183 AudioModule: validate codec2 header and bound the RX decode reads (#11036)
* AudioModule: validate codec2 header and bound the RX decode reads

Two issues reachable from a crafted AUDIO_APP payload:

The RX path built a temp codec2 from rx_encode_frame[3] whenever the frame header
did not match ours. codec2_create returns NULL for an invalid mode byte, and the
next call dereferenced it. Only decode frames that carry our own header (magic +
mode) and drop the rest, so the untrusted mode byte never reaches codec2_create.

The decode loop advanced by the frame size while testing only i < rx_encode_frame_index,
so a payload length that was not a multiple of the frame size read past the received
data and could read past rx_encode_frame. Bound each read to i + frameSize <= the
received length clamped to the buffer, and clamp the receive memcpy to the buffer.

Behavior change: audio frames whose codec2 mode differs from this node's configured
mode are dropped instead of decoded with a temporary codec.

* AudioModule: pass byte count (not sample count) to i2s_write

* AudioModule: fix the mirror sample/byte bug on the i2s_read capture path
2026-07-16 19:18:31 -05:00
renovate[bot]GitHubrenovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
166c808cdf Update meshtastic/device-ui digest to 33917d5 (#11039)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-07-16 19:04:45 -05:00
renovate[bot]GitHubrenovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
df73185274 Update meshtastic-esp8266-oled-ssd1306 digest to 8999e86 (#11038)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-07-16 19:04:10 -05:00
b71c1adb26 stm32wl: add hardware RTC support (rak3172) (#10961)
* stm32wl: add hardware RTC support infrastructure

Wires the STM32WL chip's internal RTC (running off the LSE 32.768kHz
crystal) into meshtastic's existing time-of-day framework
(perhapsSetRTC()/readFromRTC()), following the same pattern already
used for I2C RTC chips (RV3028, PCF8563/85063, RX8130CE).

LSE is started and polled manually before ever calling into the
STM32RTC library, with our own bounded timeout - the library's own
internal LSE startup path has no bounded fallback and hangs forever
via Error_Handler() if the crystal never locks, so this is required
for a board with a missing/faulty crystal to boot normally rather
than hang.

Gated behind a new HAS_LSE variant flag (currently unset everywhere,
so this is inert until a variant opts in - see follow-up commit).

Signed-off-by: Andrew Yong <me@ndoo.sg>
Assisted-by: Claude Sonnet 5 <noreply@anthropic.com>

* gps: qualify RTC.h includes to avoid case-insensitive filesystem collision with STM32RTC

The stm32duino STM32RTC library (added to lib_deps in a follow-up
commit) ships its own src/rtc.h. On case-insensitive filesystems
(the macOS default), an unqualified #include "RTC.h"/<RTC.h> from
any file outside src/gps/ resolves to the library's rtc.h instead of
src/gps/RTC.h, since PlatformIO's LDF puts lib_deps include paths
ahead of the project's own -Isrc/gps.

Qualify every include as gps/RTC.h so it can't collide with any
same-named header a future dependency might ship, regardless of
filesystem case sensitivity. Purely mechanical, no behavior change.

Signed-off-by: Andrew Yong <me@ndoo.sg>
Assisted-by: Claude Sonnet 5 <noreply@anthropic.com>

* stm32wl(rak3172): enable hardware RTC support

Opts rak3172 into the HAS_LSE infrastructure added previously: sets
STM32WL_LSE_DRIVE to a conservative default and pulls in the
STM32RTC library. rak3172 has ~63KB flash headroom going in;
build-verified at 76.7% flash usage after this change (up from a
73.8% baseline), well within budget.

wio-e5 is not opted in here despite sharing the same STM32WLE5 chip
- it's already at 96.8% flash usage today (GPS + I2C sensor support
compiled in, unlike rak3172), leaving too little headroom to safely
add STM32RTC without first trimming something else.

Signed-off-by: Andrew Yong <me@ndoo.sg>
Assisted-by: Claude Sonnet 5 <noreply@anthropic.com>

* stm32wl: add docstrings for LSE/RTC setup functions

Addresses CodeRabbit's docstring coverage check on PR #10961.

Signed-off-by: Andrew Yong <me@ndoo.sg>
Assisted-by: Claude Sonnet 5 <noreply@anthropic.com>

* stm32wl: address CodeRabbit nitpicks on PR #10961

- Brace the single-statement HAS_LSE branch in perhapsSetRTC() to
  match the sibling readFromRTC() branch's style.
- Quote the RTC.h include in PhoneAPI.cpp for consistency with every
  other qualified include site.

Signed-off-by: Andrew Yong <me@ndoo.sg>
Assisted-by: Claude Sonnet 5 <noreply@anthropic.com>

---------

Signed-off-by: Andrew Yong <me@ndoo.sg>
Co-authored-by: Ben Meadors <benmmeadors@gmail.com>
2026-07-16 19:00:45 -05:00
JorropoandGitHub cf0cb9087a Remove some deadcode (#11034)
* chore: remove empty handleWebResponse() stub in PiWebServer

* chore: remove unused PowerStatus::knowsUSB()

* chore: remove orphaned fsListFiles() declaration

* chore: remove stale #if 0 SPI-comms test in SX126xInterface

* chore: remove stale #if 0 cert-delete debug block in WebServer
2026-07-16 18:57:50 -05:00
Jonathan BennettGitHubThomas Göttgenscoderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>IxitxachitlCopilot Autofix powered by AI
8d1fbbf55f Snake! (#10936)
* 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>
2026-07-16 18:35:33 -05:00
Thomas GöttgensandGitHub d3d02af817 AdminModule: don't return the device private key to remote config reads (#11030)
A SECURITY_CONFIG get_config response copied config.security verbatim, so a remote
request was answered with the device identity private_key and sent over the air.
Only the local owner needs it, for backup.

Zero private_key in the SECURITY_CONFIG response when the request is remote
(from != 0); the local BLE/USB/TCP path (from == 0) still receives it. public_key
and admin_key are public and stay as they were.
2026-07-16 16:56:47 -05:00
oscgonferandGitHub f18a8b0552 Add AQ Telemetry to userPrefs (#11029) 2026-07-16 15:19:57 -05:00
Ben MeadorsandGitHub 5cf85d523a fix: heap leaks on display wake and BLE re-enable; remove dead PacketCache (#11019)
* fix(display): don't leak a TFT driver instance on every screen wake

On variants whose Screen::handleSetOn() re-runs ui->init() on each display
wake (Heltec Tracker V1.x/V2, VTFT_LEDA and ST7796 boards, MUZI/Cardputer
via dispdev->init()), OLEDDisplay::init() unconditionally re-invokes
connect(), and TFTDisplay::connect() allocated a fresh driver instance
each time without freeing the old one. Each OFF->ON transition orphaned a
full LGFX device (measured: exactly 1,260 bytes per wake on a Heltec
Wireless Tracker V2). Since PowerFSM wakes the screen on every received
text message, a device on a busy channel leaked tens of KB per day -
matching field reports of heap climbing from 78% to 92% within a day.

Null-guard the driver allocations (matching the existing linePixelBuffer/
repaintChunkBuffer guards below them) so re-entry re-runs tft->init() on
the existing instance. Bench-validated on a Tracker V2: free heap now flat
across 13 consecutive wake cycles, with the panel still re-initializing
and rendering on every wake.

* fix(ble): don't leak BluetoothPhoneAPI and callbacks on BLE re-enable

NimbleBluetooth::setup()/setupService() re-run when Bluetooth is
re-enabled after deinit(), but BLEDevice::deinit() only frees the GATT
objects - the caller-owned allocations were re-created unguarded on every
cycle:

- bluetoothPhoneAPI: ~3.5KB PhoneAPI object per cycle, and the orphaned
  instance is an OSThread that stays registered with the scheduler, so a
  duplicate thread kept servicing the same static queues
- toRadioCallbacks / fromRadioCallbacks / security + server callbacks:
  one small object each per cycle
- the BLESecurity shim was heap-allocated and never freed even on first
  boot; it only forwards to static setters, so use a stack instance

Reuse the existing objects on re-setup; the setCallbacks() calls still
run every time since the characteristics themselves are new.

* chore: remove unused PacketCache

PacketCache landed in #8341 but no consumer was ever wired up - repo-wide,
the only references to PacketCache/packetCache are in its own two files.
As designed it also malloc()s per cached packet with no eviction or size
cap, so it should be re-reviewed for bounds before any future use. Remove
the dead code; git history preserves it if a bounded revival is wanted.

* fix(ble): reset stale session state when reusing BluetoothPhoneAPI

Review follow-up: reusing bluetoothPhoneAPI across BLE enable cycles could
hand the next session the previous session's dirty state. The only full
cleanup path (onDisconnect) is skipped when deinit()'s bounded disconnect
wait expires before the event lands (its 2s cap matches the connection
supervision timeout, so a phone that walked out of range makes this a
coin flip): the reused object then enters the next session mid-config,
with stale queue contents served to the new phone and a stale connection
handle that defeats the checkConnectionTimeout self-heal.

Factor onDisconnect's cleanup into resetBleSessionState() and run it from
setupService() when reusing the instance, restoring the old fresh-object
invariant. Also switch the four stateless callback objects to function-
local statics (the resolved framework BLE wrapper stores plain pointers
and never frees them, so static instances are safe and avoid the guarded
heap allocations), correct the comment that claimed deinit() frees the
GATT objects (it deletes only the BLEServer itself; the services and
characteristics it created remain a small library-side leak per cycle),
and fix the inverted connection check in getRssi() that made BLE RSSI
always read 0 on ESP32-S3/C6.

* refactor(display): construct-once guard around the whole driver ladder

Review follow-up: one if (!tft) around the #if/#elif/#else construction
block instead of a guard per branch, so a future display family can't
reintroduce the per-wake leak by copying an unguarded branch. Make the
HACKADAY bus pointer local to the construction (it was a write-only
global), and point the comment at the Screen::handleSetOn gates instead
of hand-listing boards that would go stale.

* fix(ble): use-after-free notifying a freed characteristic after deinit()

deinit() nulled bleServer and BatteryCharacteristic but left
fromNumCharacteristic dangling after BLEDevice::deinit(true) freed the
GATT graph, and it never detached the PhoneAPI fromNumChanged observer.
When deinit()'s bounded 2s disconnect wait expires before onDisconnect
runs (the same stale-bond / host-reset race resetBleSessionState was
built for), close() is skipped, so the observer stays attached with
state == STATE_SEND_PACKETS. After BLE is off, the next mesh packet
drives MeshService fromNumChanged -> PhoneAPI::onNotify -> onNowHasData
-> fromNumCharacteristic->notify() on freed memory (the framework BLE
wrapper's notify() dereferences the freed server via getConnectedCount).
Unlike sendLog(), onNowHasData() had no isConnected guard.

deinit() now calls resetBleSessionState() to detach the observer and
reset session state unconditionally (also forcing the conn handle to
NONE so checkConnectionTimeout can't be fooled by the stale handle),
nulls fromNumCharacteristic/logRadioCharacteristic like the other freed
pointers, and onNowHasData() bails on a null characteristic.

Reachable on all ESP32/S3/C6 NimBLE boards via admin disable-bluetooth
or a sleep transition while a phone is connected. Found by a follow-up
lifecycle audit of the re-enable changes in this PR.
2026-07-16 15:13:26 -05:00
Thomas GöttgensandGitHub 381f9c196d NodeDB: clear only the slots removeNodeByNum() vacates (#11022) 2026-07-16 21:48:25 +02:00
Thomas GöttgensandGitHub e219e24b09 WarmNodeStore: carry the XEdDSA signer flag through the warm tier (#11020) 2026-07-16 21:46:46 +02:00
Ben MeadorsandGitHub 43084873a6 nRF52 BLE: restore pairing passkey callback on re-enable (#11027)
shutdown() installs onUnwantedPairing to actively refuse pairing (used on
the factory-reset / BT-disable teardown path), but the correct callback
(onPairingPasskey) is installed only in setup(). Re-enabling BLE on an
already-constructed nrf52Bluetooth goes through resumeAdvertising(), not
setup() (main-nrf52.cpp), which only re-armed advertising and never
restored the pairing callback. So a shutdown()->resumeAdvertising() cycle
without a reboot left the device silently refusing all pairing until the
next reboot.

Restore the correct pairing passkey callback in resumeAdvertising(),
guarded by the same config.bluetooth.mode check setup() uses. Only PIN
modes drive a passkey-display callback; NO_PIN (Just Works) never invokes
it, so no restore is needed there.

Reachable today only via the PowerStress module's BT_OFF/BT_ON opcodes
(normal runtime BT-disable paths reboot), so low severity, but a real
teardown-latches / re-enable-doesn't-restore asymmetry.
2026-07-16 13:02:43 -05:00
Andrew YongandGitHub 53a6b5e01f Auto-enable nanopb PB_NO_ERRMSG whenever DEBUG_MUTE is set (#10990)
DEBUG_MUTE (set for all of stm32) already compiles every LOG_* call
out entirely at the preprocessor stage, but nanopb's own error-message
strings are a separate mechanism it doesn't touch - PB_RETURN_ERROR
still embeds descriptive text in .rodata regardless of whether
anything ever logs it.

Rather than hardcoding -DPB_NO_ERRMSG=1 next to every place DEBUG_MUTE
is set, derive it in bin/platformio-custom.py via the SCons build
environment, mirroring the existing meshtastic-device-ui/APP_VERSION
CPPDEFINES pattern already in that file. This reaches both the main
project env and the Nanopb library builder specifically, since nanopb
is a separate LibraryBuilder whose own CPPDEFINES aren't otherwise
touched by a plain env.Append() on the app env. The CPPDEFINES
membership check normalizes both bare (-DDEBUG_MUTE) and value-bearing
(-DDEBUG_MUTE=1) forms, since SCons represents the latter as a tuple.

DEBUG_MUTE currently only appears in variants/stm32/stm32.ini and
variants/stm32/milesight_gs301/platformio.ini, so today this only
affects stm32wl builds - but it will apply automatically to any future
platform that sets DEBUG_MUTE too, without that platform's .ini
needing to know about the pairing. Saves 1,248 bytes flash on wio-e5,
no RAM change, no feature loss beyond terser protobuf decode/encode
error text.


Assisted-by: Claude Sonnet 5 <noreply@anthropic.com>

Signed-off-by: Andrew Yong <me@ndoo.sg>
2026-07-16 08:56:46 -05:00
36bfb86937 Remove UA_868 as obsolete (#10994)
Decree of the Cabinet of Ministers of Ukraine dated February 25, 2026,*
No. 262, on amendments to Section 2 of the Plan for the Allocation and
Use of the Radio Frequency Spectrum in Ukraine, has harmonised
Ukrainian regulations on the 868 MHz spectrum with the EU's, thereby
making UA_868 legally obsolete.

* https://zakon.rada.gov.ua/laws/show/262-2026-п

Co-authored-by: Tom <116762865+NomDeTom@users.noreply.github.com>
2026-07-16 08:52:50 -05:00
TomandGitHub 35ab69a2d6 Deterministic packets (#11014) 2026-07-16 08:51:47 -05:00
Thomas GöttgensandGitHub f52d7753ad Router: size the fit check from the decoded Data (#11018) 2026-07-16 08:51:10 -05:00
Ben MeadorsandGitHub f7fd058308 Fix packet-pool slot leak in canned message destination picker (#11017)
updateDestinationSelectionList() allocated a MeshPacket via
allocDataPacket() that was never sent or released, permanently
consuming one packetPool slot every time the destination-selection
picker was rebuilt.

On non-PSRAM targets packetPool is a static 70-slot BSS pool, so
repeated picker use exhausts it and eventually blocks all packet
allocation (TX/RX failures). On PSRAM/portduino (MemoryDynamic)
targets it is a true heap leak of ~424B per rebuild.

The allocation and its two field writes (pki_encrypted, channel) were
a copy/paste artifact of the PKI setup in sendText() and had no effect
in this function. Remove the dead allocation.
2026-07-16 08:22:42 -05:00
75f406745d Double-press: fall back to first channel with position enabled when primary precision == 0 (#11005)
* Double-press: fall back to first channel with position enabled when primary precision == 0
* Add missing include
* Fix trunk fmt
* Enhance logging for fallback nodeinfo sending
---------
Co-authored-by: Thomas Göttgens <tgoettgens@gmail.com>
2026-07-16 14:58:02 +02:00
Ethac.chenandGitHub deea7be89b Add RAK WisMesh Pocket V3 variant (rak_wismesh_pocket env) (#10953) 2026-07-16 07:02:57 -05:00
YellowcoolnandGitHub de0380c18b Add PiMesh-1W V1/V2 Portduino LoRa config files (#9857)
* add PiMesh-1W v1 and v2 lora config presets

* Modify Lora Pimesh configuration settings

Updated configuration for Lora Pimesh module with new CS pin and added comments.

* Enhance header in lora-pimesh-1w-v1.yaml

Updated header to include module information and URL.

* Update comments in lora-pimesh-1w-v2.yaml

* Add metadata to lora-pimesh-1w-v1.yaml

Added metadata section with name, support, and compatibility information.

* Add metadata to lora-pimesh-1w-v2.yaml

Added metadata section with name, support, and compatibility.
2026-07-15 16:42:21 -05:00
renovate[bot]GitHubrenovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
5b1c1a9e04 Update LovyanGFX to v1.2.25 (#10968)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-07-15 10:14:35 -05:00
834357e94c Add RAK WisMesh Repeater Mini V2 and HP variants (#10918)
* fix(esp32): skip RTC timer wake on user shutdown

Do not arm esp_sleep_enable_timer_wakeup when msecToWake is portMAX_DELAY (UI shutdown), matching nRF52 system_off semantics.

fix(rak_wismesh_tap_v2): Tag OCV curve and 16MB partition

Add OCV_ARRAY matching WisMesh Tag for accurate SOC. Use 16MB flash partition scheme for TAP V2 hardware.

Co-authored-by: Cursor <cursoragent@cursor.com>

* Trunkt

* Add RAK WisMesh Repeater Mini V2 and HP variants
Introduce rak_wismesh_repeater_mini (RAK4631) and
rak_wismesh_repeater_mini_hp (RAK3401) with low-VDD System OFF.
Skip LPCOMP battery wake on user-initiated shutdown to avoid
immediate reboot near the LPCOMP threshold; keep LPCOMP for
brownout-driven shutdown via variant_enableBatteryLpcompWake.

* docs(variants): update RAK BOM numbers for Repeater Mini V2 & HP

* chore: run trunk fmt

---------

Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Ben Meadors <benmmeadors@gmail.com>
2026-07-15 06:27:04 -05:00
952c825167 Allow key verification to work for unknown nodes. (#10669)
* Allow key verification to work for unknown nodes.

* trunk

* More reliable admin key decryption

* Add admin key fallback tests

* Actually check haveRemoteKey

* Logging cleanup

* Address review feedback

- Persist the committed key + manually-verified flag in
  commitVerifiedRemoteNode via saveToDisk(SEGMENT_NODEDATABASE),
  replacing the "todo: initiate save"
- Guard the CryptoEngine pending-key slot with a dedicated internal
  lock; the Router reads it while already holding the non-recursive
  cryptLock, so the accessors cannot reuse that lock
- Draw the security number from the hardware RNG (CryptRNG fallback)
  under cryptLock instead of random(); on nRF52 the entropy fill
  toggles the same CC310 the BLE task's packet crypto uses
- Return true after fully handling the hash2 response (restores
  develop behavior; consistent with the hash1 branch)
- Take uint32_t in the number-picker callbacks so 8-digit hex
  nodenums can't truncate through int
- Trim over-long comments flagged by review

* feat(tests): add deterministic tests for admin session-key behavior

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-authored-by: Ben Meadors <benmmeadors@gmail.com>
2026-07-14 21:38:41 -05:00
renovate[bot]GitHubrenovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
fc91a69ca4 Update actions/setup-node action to v7 (#11012)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-07-14 21:10:07 -05:00
Ben MeadorsandGitHub 0902c13473 feat: build-flagged frame injection into the RX pipeline for testing (#11011)
MeshService::injectAsReceived (gated by MESHTASTIC_ENABLE_FRAME_INJECTION, off by default) extends
the portduino SimRadio SIMULATOR_APP path to real hardware: a client-supplied frame, wrapped in a
Compressed envelope on the SIMULATOR_APP portnum, is delivered through the real receive pipeline
(router->enqueueReceivedMessage) as if it arrived off the LoRa chip. It therefore exercises from!=0
enforcement, channel/PKC decryption, remote-admin authorization, and hop/dedup/module dispatch -
paths the toRadio API cannot reach (it forces from=0). from==0 is dropped to match real RX.

This forges over-the-air traffic, so the flag must never ship enabled. Drive it with the
meshtastic-mcp inject_frame tool / cli/meshinject.py. Documents the technique in the agent-facing
copilot-instructions.md + AGENTS.md.
2026-07-14 20:48:54 -05:00