* chore(deps): bump RadioLib to 510e00cf
Carries the current LR11x0 and LR2021 fixes.
* fix(sx126x): allow boards to opt out of the PA optimization table
Boards driving an external PA can define SX126X_NO_POWER_OPTIMIZATION_TABLE
to use the fixed PA config instead of RadioLib's table, which is tuned for a
bare SX126x.
Default behaviour is unchanged. init() applies the fixed config after begin(),
which programs power through the table.
* feat(variants): add Seeed Wio Tracker L1 Pro 1W
nRF52840 + SX1262 with a 1 W external PA, L76K GNSS, SH1106 OLED.
Uses hw_model 144 (meshtastic/protobufs#1038), opts into
SX126X_NO_POWER_OPTIMIZATION_TABLE and declares SX126X_MAX_POWER explicitly.
The PA gain table is indexed by SX1262 output power in dBm.
Requires protobufs#1038 and a protobuf regen before it builds.
* fix(variants): correct L1 Pro 1W QSPI pins and clean up comments
PIN_QSPI_* are logical pin indices. The QSPI flash sits at D19-D24 in
variant.cpp, but the defines carried D21-D26 from seeed_solar_node, where
that block does start at D21. D25 and D26 are trackball pins.
Also replaces mis-encoded characters in the pin comments and drops the
migration note, which referenced a private repo path and a stale PINS_COUNT.
* fix(variants): move L1 Pro 1W out of the per-PR build matrix
board_level = pr is the high-attention tier that builds on every PR. This
board belongs with the mainline set, which on this branch carries no
board_level and builds in the release matrix.
Bumps the submodule to the protobufs 2.7 branch tip, adding
SEEED_WIO_TRACKER_L1_PRO_1W = 144 (protobufs#1041).
Regenerated with nanopb 0.4.9.1 against protobufs 2.7, which is the
branch this line tracks.
Both are enabled but never defined on master. Their definitions and the
bin/lint-*.sh scripts they invoke exist only on develop, so Trunk Check
exits with a config error before scanning any files, failing every PR.
Reapplies #10869, which was undone by the trunk upgrade in #10895.
* Implement SD card backup/restore of preferences; add SD support for Cardputer Advanced
Fills the two 'TODO: After more mainline SD card support' stubs in
NodeDB::backupPreferences / restorePreferences so the existing
backup_preferences / restore_preferences admin messages work with
BackupLocation_SD. Same BackupPreferences proto as the FLASH location,
stored at /backups/backup.proto on the card; guarded by
HAS_SDCARD && !SDCARD_USE_SOFT_SPI; SD access under spiLock.
Also defines the SD card slot pins for the M5Stack Cardputer Advanced
(shares the SPI bus with the SX1262, separate CS), whose physical slot
was previously unused by the firmware.
Hardware-tested on two Cardputer Advanced units (as a v2.7.26
backport): mount, backup write, and full restore (config, module
config, channels, owner, security keys) verified; LoRa unaffected.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* Deduplicate backup construction and restore application
Extract buildBackupPreferences() and applyRestoredPreferences() shared
by the FLASH and SD locations, per review feedback, so the two paths
cannot drift apart.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
---------
Co-authored-by: Maksym Tereshko <maxim.tereshko@gmail.com>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Co-authored-by: Maksym Tereshko <termax@ter-air-12.taila94f88.ts.net>
Back-port of #10911 to master. Adds support for boards that route the SX126x
control lines (NRESET/DIO1/BUSY) through an I2C GPIO expander with no /INT wired
to the MCU, so RadioLib cannot take a hardware DIO1 interrupt.
Two reusable pieces, both gated behind USE_MCP23017 / LORA_DIO1_SOFTWARE_POLL so
builds that don't define them are unaffected:
- ExtensionIOMCP23017 + MCP23017LockingArduinoHal: a RadioLib HAL that maps
virtual pins (MCP23017_VPIN_BASE .. +15) onto an MCP23017's GPIO, so
RESET/BUSY/DIO1 reads and writes become I2C transactions while the real SPI
GPIOs (SCK/MOSI/MISO/CS) pass straight through. A failed I2C read skips the
read-modify-write rather than clobbering the rest of the bank. Expander access
is mutex-guarded since it is touched from both the radio thread and setup.
- LORA_DIO1_SOFTWARE_POLL: with no DIO1 interrupt available, the SX126x
interface polls the radio IRQ status register from the radio thread and
synthesizes the ISR_TX/ISR_RX events, filtering noisy preamble/header IRQs so
they can't starve TX. A 1 ms ISR_POLL_TICK drives the poll; TX timers may
overwrite the pending tick (pollMissedIrqs() bounds the busy-Rx contention
latency). Behaviour is unchanged on boards that keep the hardware interrupt.
Variants opt into shared behavior via reusable capability macros instead of
per-model #ifdefs: AUDIO_AMP_ENABLE(on) (speaker amp power, shared with the
T-LoRa Pager) and LORA_DIO1_EXTENDED_IO (DIO1 lives on an expander, not a
wake-capable GPIO; shared with the SenseCAP Indicator so sleep.cpp skips the
GPIO wakeup path).
The Meshnology W10 variant: ESP32-S3R8 + EBYTE E22-900MM22S (SX1262) + AXP2101
PMIC + Quectel L76KB GPS + SPI TFT + ES8311 codec, with the radio's
RESET/DIO1/BUSY and the LCD reset routed through an MCP23017 at 0x20. Pins come
from the board schematic and were verified on hardware. master's protobufs
predate the MESHNOLOGY_W10 HardwareModel enum, so the board reports PRIVATE_HW
until that enum lands here via a protobuf bump.
* Backport Tracker X1 to Master
* Add SPA06 to generated TelemetrySensorType enum
* Rename TRACKER_T1000_E_PRO to MESH_TRACKER_X1 in generated HardwareModel enum
* Update RadioLib dependency to the fixed version
---------
Co-authored-by: Ben Meadors <benmmeadors@gmail.com>
The automated trunk upgrade (#10865) enabled ascii-dash@SYSTEM and
too-many-defined@SYSTEM without any lint.definitions for them, so trunk
reports 'not a supported linter' (trunk/config-error) and fails the Trunk
Check on every open PR. trunk_check.yml runs on pull_request only, so
master's push CI never caught it. Remove the two undefined entries to
restore a valid config; the version bumps from #10865 are kept.
Avoid nested spiLock acquisition in MessageStore::clearAllMessages() by
only holding the lock during the write() call. SafeFile's constructor
and close() methods already handle their own spiLock acquisition, so
nesting the guard around the entire operation causes a deadlock until
the watchdog timer triggers a reset.
Relates to PR #10809.
* fix(nrf52): restore BLE security state on resume
NRF52Bluetooth::shutdown() swaps the passkey callback to onUnwantedPairing() while Bluetooth is disabled. resumeAdvertising() used to restart GAP advertising without rebuilding that security state, which could leave the node advertising while normal pairing attempts were still rejected until reboot.
Restore the configured security state before advertising resumes. PIN-based modes reinstall the pairing, completion, and secured callbacks, explicitly restore display-only IO capabilities, and explicitly require MITM. NO_PIN mode clears those callbacks and clears MITM after setPairPasskeyCallback(nullptr), because Bluefruit enables MITM as a callback-setter side effect even when clearing the callback.
Share the same restore helpers with setup(), keep TX power setup symmetric with the resume path, and log unexpected onUnwantedPairing() hits so callback poisoning is visible in diagnostics.
* fix(nrf52): remove blocking BLE pairing wait
NRF52Bluetooth::onPairingPasskey() blocked the firmware loop for up to 30 seconds when match_request was true. That wait did not yield or delay, so it could starve application-level BLE event handling while the SoftDevice connection was active.
Remove the busy-wait. The passkey UI remains visible because pairing completion and disconnect callbacks already dismiss it, so pairing can continue through the normal callback flow without stalling the rest of the firmware.
* fix(nrf52): redact BLE pairing secrets from logs
The nRF52 BLE pairing path logged both the configured boot PIN and the per-pairing passkey at INFO level. Those values can be exposed through serial diagnostics or log streaming even though they are intended only for the pairing UI and status observers.
Keep the diagnostic signal without printing secret digits: log that a PIN is configured, log the pairing start with match_request state, and remove the now-unused passkey-splitting locals and duplicate match_request log.
* fix(fs): bound getFiles() result size and harden traversal
The recursive filesystem traversal in getFiles() could grow its result
vector without limit, contributing to heap pressure on memory-constrained
devices during client config starts. The implementation also relied on
unbounded strcpy and per-entry String() allocations for dot-suffix
filtering, and leaked the directory handle when the opened path was not
a directory.
Rework the traversal to address all of those at once:
- Add optional maxCount (default 64) and wasLimited out-param. The walk
stops once maxCount entries have been collected and wasLimited is set,
so callers can distinguish a complete listing from a truncated one.
Default arguments preserve backward compatibility for existing callers.
- Extract the recursive walk into a collectFiles() helper in an
anonymous namespace, accumulating into a single caller-owned vector
instead of building and inserting temporary vectors at every recursion
level.
- Replace strcpy with strlcpy so file_name writes are bounded by the
destination buffer.
- Replace String(name).endsWith(".") with a stack-based
pathEndsWithDot() helper to avoid a heap allocation per entry.
- Close the directory handle when the opened path is not a directory,
and close the file in both branches of recursion instead of only the
leaf branch (the previous code leaked the handle when depth was
exhausted).
- Compile-gate the std::bad_alloc reserve probe-down behind
__cpp_exceptions / __EXCEPTIONS so the fallback only compiles where
bad_alloc is actually catchable. No-exception targets (nRF52, RP2040,
STM32, ESP32-C6) skip the probe and rely on the bounded maxCount
alone, which is safe because the default cap is small enough to fit
comfortably in available RAM on every supported platform.
* fix(phone-api): skip manifest scan for node-info-only config requests
A client can request a node-info-only config response by sending
want_config_id with the SPECIAL_NONCE_ONLY_NODES nonce. The manifest
walk in handleStartConfig() ran unconditionally even though
STATE_SEND_FILEMANIFEST short-circuits past the manifest on that path,
so the SPI lock and recursive filesystem traversal were wasted work.
On memory-constrained devices this extra heap pressure was enough to
trigger allocation aborts and spontaneous reboots before the node-info
response could complete.
Gate the manifest call behind config_nonce != SPECIAL_NONCE_ONLY_NODES
so the node-info-only path no longer touches the filesystem at all, and
release any stale manifest storage left over from a previous run.
For normal full-config requests, also tighten the scan limits from
depth 10 / unbounded to depth 3 / max 64 entries via the new getFiles()
parameters. A 10-level recursive listing was never useful for a client
file picker and amplified heap pressure for no benefit. Log a warning
when the listing was truncated so operators can distinguish a bounded
result from a genuinely empty filesystem.
A few small cleanups fall out of the above:
- Replace raw spiLock->lock()/unlock() with a LockGuard so the lock is
released even if getFiles() throws on exception-enabled targets.
- Use a releaseFilesManifest() helper (swap-with-empty idiom) in
close() and the STATE_SEND_FILEMANIFEST drain path instead of the
previous clear()+shrink_to_fit() pair. Equivalent behavior, but the
swap idiom is the canonical vector-freeing pattern and centralizing
it avoids divergence between call sites.
- Use %zu for the size_t log arguments.
* fix(fs): harden bounded directory traversal
Close each child directory entry before recursive descent so bounded manifest scans do not stack file handles on constrained filesystems.
Treat null or overlong paths as truncated listings, handle reserve length failures like allocation failures, and keep manifest constants aligned with repository naming.
* Potential fix for pull request finding
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
---------
Co-authored-by: Ben Meadors <benmmeadors@gmail.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
* 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
---------
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Ben Meadors <benmmeadors@gmail.com>
* Wipe message Store on factory reset
* Check for destination 0 in a new message, and convert to broadcast
* Make sure CHARGE_LED_state gets turned off, to avoid stuck LEDs
* Take the spiLock in MessageStore when clearing messages
* Trunk
* Add thinknode M5 voltage curve
* Fix the oops