* Package meshtasticd for Windows as an MSI
Adds a --service flag connecting meshtasticd to the Service Control
Manager, a WiX MSI installing it as an auto-start LocalSystem service with
config in %ProgramData%\Meshtastic, and a CI step attaching the MSI to
releases.
* Address review comments
Bind workflow expressions to env vars in run: bodies, and build the
service status per call with an atomic checkpoint.
* Fix service stop state and CI lint
Latch the stop under a mutex so a startup report cannot walk the state
back. Ignore the new workflows in semgrep and checkov, as main_matrix
already is.
* Drop the checkov ignore for the winget workflow
Resolve the newest release inside the job instead of taking
workflow_dispatch inputs, so CKV_GHA_7 no longer fires and checkov stays
active on the file.
* Carry the MSI architecture into the winget manifest
Parse it from the asset name instead of defaulting to x64, and fail on a
multi-arch release rather than validating one at random.
* Restore release/.gitignore
* Leave the main matrix alone
Release attachment moves to the matrix rework in #11151. The MSI is still
built and uploaded as a CI artifact.
---------
Co-authored-by: Austin <vidplace7@gmail.com>
* Add Elecrow ThinkNode M8 variant scaffold (thinknode_m8)
nRF52840 + SX1262 + 2.4" e-paper + ATGM336H-5NR32 GPS.
All pins resolved from ThinkNode_M8_V0.3.sch; cross-checked
against meshtastic/firmware#9181 (Elecrow V0.1 reference).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* Add Elecrow ThinkNode M8 board support (nRF52840/SX1262, 1.54in e-ink, ATGM336H GNSS, SC7A20, EC04 encoder)
* Address review: keep the stored backlight level out of blanking, match only the SC7A20 WHO_AM_I byte, and transfer detents atomically
* Use std::atomic for the press-and-turn detent counter so native builds compile
* Drop the ThinkNode M8 LED_BUILTIN redefinition that warned on every translation unit
---------
Co-authored-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
* Add support for an alternate pin assignment to the nrf52_promicro_diy variant
constructed by soldering a Pro Micro type nRF52840 board directly to an E22 module.
* Fix GPS connection documentation and Buzzer pin
---------
Co-authored-by: Tom <116762865+NomDeTom@users.noreply.github.com>
* add LR 2021 to portduino, and allow Framebuffer devices to rotate the screen from config. Requires https://github.com/meshtastic/device-ui/pull/355 and supersedes https://github.com/meshtastic/firmware/pull/10567 and https://github.com/meshtastic/firmware/pull/11138
Many thanks to the original authors https://github.com/a-li3n and https://github.com/jessm33
* Build LR2021Interface.cpp in the wasm env
initLoRa() constructs LR2021Interface for Lora.Module: lr2021, so excluding
the file from the native-wasm source filter left the constructor undefined at
link time. LR20x0Interface.cpp stays excluded; it is template-only and comes in
via the InterfacesTemplates.cpp amalgamation.
Also replace the non-UTF-8 degree signs in the framebuffer rotation comment and
correct the rfswitch alias cleanup comment.
* Trim comments
* Report setenv failure for the framebuffer rotation
* Add explicit presence for MeshPacket.rx_time (arrival time)
rx_time is now proto3 optional with a has_rx_time presence bit, matching
the rx_rssi treatment. A node with no GPS and no phone connected yet has
no time source at all, so a bare 0 was indistinguishable from a genuine
1970-01-01 reading; downstream consumers (replay packets, JSON
serialization) now check has_rx_time instead of the value.
* Dedupe rx_time stamping into a shared helper; trim a debug log string
Extract the repeated haveTime/rx_time/has_rx_time stamp logic (5 call
sites across Router.cpp, MeshBeaconModule.cpp, MeshService.cpp) into
Router::computeRxTimeStamp()/stampRxTime(). Also shorten the new RTC.cpp
LOG_DEBUG string. Saves 48 bytes of flash on rak4631 (measured), no
behavior change.
* Fix has_rx_rssi presence carried unconditionally through StoreForward replay
preparePayload() set has_rx_rssi = true unconditionally on replay, regardless
of whether the packet's rx_rssi at store time was a genuine measurement (e.g.
MQTT-relayed packets carry no real RSSI). Store the presence bit alongside
rx_rssi in PacketHistoryStruct and restore it on replay instead.
Flagged by Copilot on #11271 (same root cause the has_rx_time explicit
presence work fixes) but never addressed before that PR merged.
* Trim comment blocks to the repo's 1-2 line guideline
.github/copilot-instructions.md:338 caps code comments at 1-2 lines; several
blocks added across the rx_time explicit-presence work ran well past that.
Also consolidates Time.cpp's file-level doc comment into Time.h, where the
rest of the Time:: API contract already lives.
No behavior change.
* Add rx_time explicit-presence test coverage
- test_meshpacket_serializer: has_rx_time=false fixture plus tests asserting
JsonSerialize/JsonSerializeEncrypted emit 0 rather than leaking the
millis() placeholder, alongside the has_rx_time=true baseline.
- test_stream_api: two tests driving a real PhoneAPI handshake (want_config_id
through STATE_SEND_PACKETS) that simulate a phone time-giving transaction
arriving before vs. after a queued packet is drained - covering both the
reconciled and the ships-with-placeholder-absent paths of
MeshService::reconcilePendingRxTimes().
* Fix three correctness issues flagged in review
- Time.h: drop the reserved-identifier include guard (_MT_TIME_H); pragma
once already covers it, matching convention elsewhere (e.g. RTC.h).
- Time.cpp: rebase getMillis64()'s wrap accumulator when the test seam
swaps clock sources, so a real<->injected clock jump isn't miscounted
as a genuine 32-bit wrap.
- NodeInfoModule: the 12h reply-suppression window is a local dedup
duration, not a wall-clock reading - switch it to Time::getMillis64()
so RTC-quality jumps and replayed packets' stale rx_time can't perturb
it.
- StoreForwardModule: has_rx_time was derived from *current* RTC quality
at replay time rather than stored at capture time, so a history entry
saved while time-blind could be misreported as a valid epoch once the
clock later improved. Persist the presence bit in PacketHistoryStruct
instead.
* tryfix CI
* post review fixes
* more test fixes
The rak_wismesh_tap_v2-tft link failed on CI with "sh: Argument list too
long": the device-ui bump in #11214 grew the MUI object list enough that
the linker command line crossed the kernel exec arg limit (this env's long
name lengthens every object path, so it tipped first).
extra_scripts/ld_response_file.py already solves this for esp32p4; promote
it to esp32_common so every ESP32 target links through a response file,
and drop the now-inherited entry from esp32p4.ini.
Verified by building rak_wismesh_tap_v2-tft at the failing commit plus this
fix: links clean (macOS's exec arg limit is smaller than the Linux one that
tripped CI, so an unwrapped link could not have passed).
sendLog() notified once per firmware log line with no backpressure. With
debug_log_api_enabled and a subscribed client, a logging burst drains the
msys_1 mbuf pool that every GATT notification and ATT response allocates
from, so ble_gatts_notify_custom starts returning BLE_HS_ENOMEM (rc=6) --
for the fromNum doorbell too, not just the log line that exhausted it.
Each failure then prints an error over serial at ~12ms apiece, which is
itself enough to throttle the main loop during a burst.
Register a callback on the log characteristic so the host's own
ERROR_GATT verdict pauses log notifies for 250ms and lets the pool
refill. Keying off the failure rather than a free-block count keeps this
correct however the pools are sized.
Also restore CONFIG_BT_NIMBLE_MSYS_1_BLOCK_COUNT to the IDF default of
12. Pool selection is by block size and a notify request always resolves
to the smallest pool, so msys_1 is the only pool GATT draws from; 8 was
thin once anything chains or fragments. msys_2 and the ACL/EVT transport
pools are left trimmed -- neither is on the notify path, and restoring
the full defaults would re-spend the contiguous allocation that #10741
was fixing on heap-tight boards.
sendLog() also lacked the null check onNowHasData() already has, so a log
line arriving during BLE teardown dereferenced a freed characteristic.
Fixes#11245
nrf52_promicro_diy_tcxo has been failing the nrf52 warm-region guard on
develop: the image ends at 0xEA0C0, 192 bytes past the 12 KB WarmNodeStore
record-ring reserved at 0xEA000. This variant compiles four radio driver
families (SX126x/LLCC68, SX127x/RF95, LR11x0/LR1121, LR2021) so any module
can be soldered on, which makes it the largest nrf52 image we ship.
Building it with -D EXCLUDE_EMOJI saves 6,808 bytes and puts the image at
0xE8618, 6.6 KB clear of the warm region.
EXCLUDE_EMOJI was not previously usable: graphics::emotes[] becomes empty,
but the canned-message emote picker never checked for that. Entering the
picker clamped emotePickerIndex to numEmotes - 1 (i.e. -1), and selecting
read emotes[-1].label into a String - an out-of-bounds read of whatever
precedes the array in flash. Guard both entry points instead: refuse to
open the picker when there are no emotes, and bounce back to freetext if
the picker state is somehow reached anyway.
Under whole-image LTO those guards fold to constants, so the picker draw
and input paths dead-strip entirely on builds that set EXCLUDE_EMOJI -
which is where the savings come from beyond the bitmap data itself.
Received messages containing emoji render as text on this variant, and
the emote-list key is a no-op. Verified rak4631 (emoji enabled) is
unaffected: 0xE46D8, 22 KB clear.
* Add T-Beam BPF (144-148 Mhz LoRa)
* minor correction to fix compiler warnings
* Add T-Beam BPF (144-148 Mhz LoRa)
* minor correction to fix compiler warnings
* Add ITU regions for this device and make GPS work.
* Switch pin after defining it as output
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
* Lora CS is indeed 1, SD Card CS is 10
* Include the back option.
* Fix compilation with pioarduino (USB_MODE)
* Default ham to narrow_fast
* Default PROFILE_HAM to slot 17
This is an appropriate default in the USA but not the EU.
The slot override really should follow the region itself, not the regionprofile.
* Fix for ITU 2/3 split
* Add ITU region options to MenuAction enum
* Add HAS_HAM_2M definition to variant headers for 2M support
* Re-add PROFILE_HAM regionprofile
Accidentally removed in last merge
* Trunk fmt
* Initial default slots
* Switch to TinyFast
Still need to flesh out the default channels
* Adjust slotOverrides for TinyFast
* RadioLib doesn't accept 15.625 kHz
Use 15.6 instead
* Set RF95 pins for T-Beam Supreme
May cause regressions!!
* Remove other-variant changes (BPF-only)
These have been moved to other PRs
* Remove mismatch guarding (we need a more comprehensive approach)
* Add comment back
* Add template for PA curve
* This is a 5-6W radio!! Add TX_GAIN_LORA
* Trunk fmt because NomDeTom hates emdashes
---------
Co-authored-by: Thomas Göttgens <tgoettgens@gmail.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Since #10967 made Router::sendLocal handle self-addressed packets
synchronously, the entire phone-API chain for a BLE client runs inline in
the Bluefruit characteristic write callback: toRadioWriteCb ->
PhoneAPI::handleToRadio -> admin set-config -> radio reconfigure ->
NodeDB::saveToDisk. That callback executes on the Bluefruit BLE FreeRTOS
task, whose stock stack is 5 KB (CFG_BLE_TASK_STACKSIZE = 256*5 words) -
not the Arduino loop task that #10944 already raised to 8 KB. The loop-task
fix therefore protects the wrong task for BLE-originated writes.
On a Seeed Wio Tracker L1 the 5 KB stack overflows during pairing
first-sync, resetting the device mid-LittleFS-write, every single time.
Repeated mid-write resets tear the LittleFS metadata, lfs_assert fires on
the next boot, and the corruption handler formats the whole filesystem:
region, channels, module config, and the node's keypair are all lost
(critical fault #13, new node identity on next region set). Reproduced
end-to-end tonight on stock develop 6908d27; with this change the same
device pairs, serves config screens, and survives back-to-back
config.proto saves over BLE.
Raise the BLE task to the same 2048 words (8 KB) as LOOP_STACK_SZ, for the
same reason. bluefruit.cpp's #ifndef guard makes the -D take effect with no
framework patch. Costs 3 KB of RAM on nrf52840 targets only.
Credit where due: Ixitxachitl independently established in #11155 testing
that the save-path crash persists after #11185 and that re-queueing
sendLocal (moving the pipeline back to the Router thread) makes it go away
- which corroborates this diagnosis from the other direction. This commit
is the minimal capacity-side fix; #11155's relocation of the pipeline off
the BLE task remains the right architectural follow-up, and this guard
stays correct even after it lands.
Likely also explains #10905 (L1 display-thread crash when a client
requests full configuration) and the 2.8 field reports of idle nodes
losing region and keys after a BLE session.
* Add Meshnology W12 (WiFi LoRa 32 V5) variant with LR2021 radio
ESP32-S3R8 + 16 MB flash + Semtech LR2021 dual-band LoRa + SSD1315 OLED,
Heltec V3-style pinout.
Pin map comes from the vendor pin sheet and vendor Arduino demos, confirmed
on hardware: I2C scan finds the OLED at 0x3C on SDA17/SCL18, and the LR2021
answers on NSS=8 SCK=9 MISO=11 MOSI=10 with BUSY=13/NRESET=12 verified by
reset-pulse probing.
The IRQ line needs LR2021_IRQ_DIO_NUM 8. RadioLib defaults irqDioNum to 5,
but DIO5 is not bonded out on this board - the two IRQ lines are DIO7 -> IO7
and DIO8 -> IO14, established by driving each radio DIO as a GPIO output and
watching which ESP32 pin followed. Left at the default, every interrupt is
routed to a floating pin: RX/TX still appear to work because
RadioLibInterface::pollMissedIrqs() falls back to polling the IRQ status
register, but the blocking scanChannel() waits on the pin itself and never
returns.
A shadow pins_arduino.h is needed because the generic esp32s3 one defines
RGB_BUILTIN, which pulls the Arduino RMT HAL into the link and fails against
this build's FreeRTOS config.
* Address review: add W12 variant metadata, trim comments
Declares the custom_meshtastic_* metadata the build manifest expects. Without
it the emitted .mt.json carried no hwModel, slug, display name or support
level, so the board was invisible to the flasher - and board_level = extra
kept it out of PR builds entirely, which is why it never appeared in this
PR's board list. It now matches the W10 sibling at board_level = pr with
support level 1. hwModel stays 255/PRIVATE_HW until a W12 enum lands in the
protobufs.
Also trims the header comments to the one-or-two-line limit in AGENTS.md;
the vendor pin sheet and probing evidence live in the PR description.
Range test is excluded on every target as of 2.8, so set
MESHTASTIC_EXCLUDE_RANGETEST=1 globally in [env] build_flags and drop the
now-redundant per-variant flags from esp32, rak11200 and stm32.
Guard RangeTestModule.cpp with the same condition used in Modules.cpp so the
translation unit compiles to nothing rather than relying on --gc-sections to
strip it, and always report RANGETEST_CONFIG in the device metadata
excluded_modules bitmask so clients hide the config.
Resolve conflicts against the NodeDB signer/key primitives (#11050) and the
admin-key PKI decrypt budget (#11100).
- NodeDB: drop this branch's hasSeenXeddsaSigner in favour of develop's
isKnownXeddsaSigner. They answer the same question, but develop's reads the
dedicated warm signer bit (warmSignerOf) rather than the WarmProtected
category, and TrafficManagementModule already depends on it. Keep develop's
copyPublicKey/copyPublicKeyAuthoritative, isVerifiedSignerForKey and
commitRemoteKey/KeyCommitTrust.
- checkXeddsaReceivePolicy: keep this branch's Strict/Balanced/Compatible
policy, which is a superset of develop's balanced-only downgrade gate, and
call isKnownXeddsaSigner from it. develop's !pki_encrypted term is dropped
because the policy returns early for PKI packets before that check.
- perhapsDecode: keep develop's key resolution (NodeDB then pending-key, only
for real PKI candidates) plus its admin-key token bucket, and re-apply this
branch's pkiAttempted flag feeding the DECODE_OPAQUE verdict. Keep both
passesRoutingAuthGate and adminKeyFallbackAllowed/Refund.
- test_A17: model eviction the way NodeDB actually does it, passing the warm
signer bit as well as the XeddsaSigner category, since isKnownXeddsaSigner
reads the former.
Native suite: 38 suites, 743/743 cases, no sanitizer findings.
- t5s3-epaper: the board's 3.3V octal (AP_3v3) PSRAM is unreliable at the qio_opi
default of 80MHz (Total PSRAM reads 0, display dead / boot-loop); clock it at 40MHz.
- EInkParallelDisplay: skip EInk init and no-op display ops when the PSRAM framebuffer
is unavailable, so the node boots headless instead of hard-faulting on a NULL buffer.
- src/platform/esp32: weak software __atomic_*_{1,2,4} so esp32s3 links on toolchains
(e.g. macOS) that lack the sized libcalls GCC emits under -mdisable-hardware-atomics.
* 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>
* 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>
* 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>
* 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>
* 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>
* 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>
Adds four MESHTASTIC_EXCLUDE_* flags to stm32_base, applying them to
every stm32wl variant (rak3172, wio-e5, russell, milesight_gs301,
CDEBYTE_E77-MBL):
- PKT_HISTORY_HASH: mirrors the accepted nRF52 precedent
(variants/nrf52840/nrf52.ini) - drops PacketHistory's hash index;
the O(n) fallback is negligible given stm32wl's small node table.
No feature loss.
- POWER_TELEMETRY: only affects external power-monitor ICs
(INA219/INA260 etc.) via PowerTelemetryModule; internal battery ADC
reading (src/Power.cpp) is a separate, ungated code path and is
unaffected. No current stm32wl variant wires up external
power-monitor hardware.
- RANGETEST: real feature loss on wio-e5 specifically, the only
stm32wl variant with GPS enabled (RangeTest requires GPS to run at
all, so it was already dead on every other variant).
- WAYPOINT: real feature loss on rak3172/wio-e5 (russell already
excluded this individually; that duplicate is removed here since
stm32_base now covers it).
Measured on wio-e5 against upstream/develop @ 7a25ffd0a: 2,768 bytes
flash saved (240,648 -> 237,880), 260 bytes RAM saved. Sanity-built
russell and rak3172 to confirm no regressions from the russell
dedup.
Assisted-by: Claude Sonnet 5 <noreply@anthropic.com>
Signed-off-by: Andrew Yong <me@ndoo.sg>
Co-authored-by: Ben Meadors <benmmeadors@gmail.com>
Add conditional compilation guards for LR1121 and LR2021 radio chip configurations in rfswitch.h to prevent compilation errors when these radios are not in use. Move USE_LR2021 definition to the radio section in variant.h for consistency with other radio chip definitions.
* fix(device-install): flash littlefs at the offset from firmware metadata
device-install.sh read the spiffs offset out of the .mt.json metadata into
SPIFFS_OFFSET, but flashed the littlefs image at $OFFSET -- a separate
variable still holding the hardcoded 0x300000 default. The metadata value
was never used, so any board with a non-default partition table had its
filesystem written to the wrong place.
Unify on SPIFFS_OFFSET, and guard both metadata overrides so a table that
omits ota_1 or spiffs falls back to the built-in default instead of handing
esptool an empty offset. Quote both offsets at the call site: the spiffs one
is newly consumed from jq, and a multi-line result would otherwise word-split
into esptool's argv and silently mis-pair address with file.
device-install.bat already does all of this correctly; this brings the shell
script to parity.
* fix(tlora-t3s3): give the 4MB T3-S3 boards an app partition that fits
tlora-t3s3-v1 and tlora-t3s3-epaper have overflowed the shared 4MB
partition-table.csv app slot (ota_0 = 0x250000 = 2,424,832 bytes) on every
develop build since 22072c5f4 (2026-06-20). The last green build,
ca7d82629, cleared it by 881 bytes; develop is now ~38.7KB over. These envs
have no board_level, so they only build in the full matrix on push to
develop -- which is why no PR ever caught it.
Add partition-table-t3s3.csv, dedicated to the 4MB ESP32-S3FH4R2 T3-S3
boards, and point all three t3s3 envs at it:
app ota_0 0x010000 0x290000 (was 0x250000, +256KB)
flashApp ota_1 0x2A0000 0x0A0000 (unchanged size)
spiffs 0x340000 0x0C0000 (was 0x100000, -256KB)
The headroom comes out of spiffs, not flashApp: the unified BLE OTA image is
636,544 bytes against a 655,360 byte slot, so ota_1 has nothing to give. The
table is contiguous, ends exactly on the 4MB boundary, keeps both app
partitions 64KB-aligned, and leaves the littlefs image these boards ship well
inside the remaining 768KB.
Verified with a Docker build of all three envs:
tlora-t3s3-v1 2,463,504 91.7%
tlora-t3s3-epaper 2,458,487 91.5%
tlora-t3s3-epaper-inkhud 2,400,851 89.4%
Trimming was considered and rejected. The entire emoji set is 6,304 bytes
(measured A/B build), and cutting traffic management plus the paxcounter,
storeforward, rangetest and atak modules recovers 46,636 -- enough to land at
99.7% full, i.e. weeks of headroom at develop's observed growth rate, in
exchange for shipping a feature-reduced board. The slot was simply mis-sized
for a board this full.
Note this changes flash layout: existing T3-S3 units must be erased and
factory-flashed once to pick up the new offsets. They already cannot run a
current develop build, so no working configuration regresses.
${platformio.packages_dir} resolves to a backslash path on Windows.
PlatformIO parses build_flags with POSIX shell rules, which strip the
backslashes and turn the NimBLE include paths into broken relative
paths (d:platformiopackages/...), failing the build with
'host/ble_gap.h: No such file or directory'. Quoting the flags
preserves backslashes through parsing.
The Arduino loop task runs Meshtastic's entire cooperative OSThread
scheduler on the Adafruit core's stock 4 KB (1024-word) stack. The 2.8
first-sync path - AdminModule::handleSetConfig -> saveChanges ->
configChanged -> RadioInterface::reloadConfig -> LR11x0 reconfigure ->
SPI Lock::lock, with vsnprintf/USB-CDC logging frames stacked on top -
overflows it.
SWD fault capture on a T1000-E (pyocd vector_catch=h) proved it: the
loop task's SP was driven 40 bytes BELOW its own pxStack base, the
stack paint was consumed to the floor, and the crash was a BusFault in
xQueueSemaphoreTake dereferencing a semaphore handle that adjacent-heap
corruption had overwritten with log text (0x3f3f207c = "| ??"). The
core's HardFault handler is a bare NVIC_SystemReset, so in the field
this presents as a silent reboot ~seconds after a phone pairs (or a 30 s
zombie hang -> supervision timeout 0x8 when the corruption lands on task
TCBs instead). Also explains the set_time_only -> immediate-NodeInfo
crash variant reported on empty-DB nodes: same task, different deep
chain. 2.7.26 is unaffected because the deep frames (XEdDSA, satellite
map conversion, replay engine) did not exist.
Fix: set -DLOOP_STACK_SZ=2048 (words = 8 KB) for all nrf52840 targets.
Requires the #ifndef guard from meshtastic/Adafruit_nRF52_Arduino#7;
until that merges the flag is a harmless redefinition warning. Validated
on hardware: pairing + full sync completes, stack paint shows healthy
margin under load.
Also fix the boot-time reset-reason log: the core's init() caches
RESETREAS and W1C-clears the register before setup() runs, so the raw
read here has always printed 0. Use readResetReason() instead.
(preFSBegin's RESETREAS==0 gate is intentionally left as-is - its
degenerate GPREGRET-only behavior is what makes lfs recovery work.)
isCharging()/getHasUSB() were hard-compiled to always return false on
this board: the nrfx_power_usbstatus_get() block in Power.cpp that
derives them is gated behind #ifdef NRF_APM, and this board's variant.h
never defined it - unlike tracker-t1000-e and wio-tracker-wm1110, which
do. Confirmed on hardware: before this change, `Battery: usbPower=0,
isCharging=0` regardless of actual USB/charge state; after, `usbPower=1,
isCharging=1` while genuinely on USB power with a battery attached.
Same underlying gap as #4367, fixed for tracker-t1000-e and
wio-tracker-wm1110 in #4376 (which also fixed a deeper bug where
PowerStatus notifications were gated behind a battery-level null check -
that part is already fixed generically in Power.cpp today, so this board
only needed the define). Looks like a simple oversight from when this
board was added, since it's newer than the two boards #4376 touched.
Assisted-by: Claude Sonnet 5 <noreply@anthropic.com>
Signed-off-by: Andrew Yong <me@ndoo.sg>
Every other .cpp/.h pair in src/ (350 of 351) uses identical
capitalization between the two files. Power.cpp/power.h was the sole
outlier; this aligns it with the rest of the codebase. No functional
change — all includes already resolved this file the same way on
case-sensitive filesystems.
Assisted-by: Claude Sonnet 5 <noreply@anthropic.com>
Signed-off-by: Andrew Yong <me@ndoo.sg>
Co-authored-by: Ben Meadors <benmmeadors@gmail.com>
* Add software LoRa IRQ polling and an MCP23017 expander HAL for radios behind an I2C expander
Some boards route the SX126x control lines (NRESET/DIO1/BUSY) through an I2C
GPIO expander instead of native GPIOs, and don't wire the expander's /INT 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.
- 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() is the backup that bounds the
latency of the busy-Rx contention window). Behaviour is unchanged on all
boards that keep the hardware interrupt.
* Add Meshnology W10 AIOT Dev Kit variant (SX1262 via MCP23017 expander)
ESP32-S3R8 + EBYTE E22-900MM22S (SX1262) + AXP2101 PMIC + Quectel L76KB GPS +
SPI TFT, with the radio's RESET/DIO1/BUSY and the LCD reset routed through an
MCP23017 I2C expander at 0x20. The expander's /INT is not wired to the ESP32,
so DIO1 uses the software poll. Pins come from the board schematic and the
vendor firmware; the expander is an MCP23017 despite the V1.1 schematic's stale
'TCA9555' label (the V1.2 placement diagram and all vendor code use the MCP23017
register map).
A local pins_arduino.h shadows the generic esp32s3 variant to drop RGB_BUILTIN,
which otherwise pulls the Arduino RMT HAL into the link and fails against this
build's trimmed FreeRTOS config.
Reports HardwareModel 140 (MESHNOLOGY_W10, already present in the protobufs).
Hardware-verified on a real W10 AIOT Dev Kit: AXP2101 PMU, MCP23017, SX1262
init + a full over-the-air TX/RX round trip through the expander, PCF85063 RTC,
SHT41 and QMI8658 auto-detected, and an L76KB GPS fix.
* meshnology-w10: enable ES8311 speaker for notification tones
Bring up the ES8311 codec (I2C 0x18) -> NS4150 amp -> speaker so the board can
play notification tones / ringtones over the I2S buzzer path (the
use_i2s_as_buzzer external-notification option). Codec2 voice stays out of
scope: it is SX1280-only and this is a sub-GHz board.
- variant.h: HAS_I2S + DAC_I2S pins (MCLK=1, BCK=2, WS=4, DOUT=5, DIN=3)
- platformio.ini: arduino-audio-driver + ESP8266Audio + ESP8266SAM
- extra_variants/meshnology_w10/variant.cpp: lateInitVariant() configures the
ES8311, mirroring the other Meshtastic ES8311 boards. Kept codec-only (no
main.h) so the audio driver's 'using namespace audio_driver' does not pull a
conflicting GpioPin into scope alongside Meshtastic's class GpioPin.
- AudioThread.h: toggle the NS4150 amp (MCP23017 EXIO_PA_CTRL) around playback.
Verified on hardware: a test tune played audibly through the speaker.
* meshnology-w10: address review feedback on the MCP23017 driver
- ExtensionIOMCP23017: serialize register access with a mutex, since the radio
HAL (BUSY/DIO1/RESET) and AudioThread (amp enable) now reach the expander from
different threads and the read-modify-write paths are not atomic.
- digitalRead: return a fail-safe HIGH on a failed read instead of LOW, so a
transient I2C error on the LoRa BUSY line can't look like 'ready' and let
RadioLib start an SPI transaction early. (DIO1 is polled via the radio IRQ
register, not this pin.)
- enablePinChangeInterrupt: use the checked readReg() overload and skip on a
failed read, matching the other read-modify-write helpers.
- meshnology_w10 variant.cpp: log a warning if an ES8311 register write NACKs
instead of silently leaving the codec half-configured.
- RadioInterface.cpp: guard the MCP23017 HAL branch with ARCH_ESP32 to match the
include and driver-file guards.
* Replace board-model audio/sleep ifdefs with reusable variant capability macros
Two shared-code spots keyed off specific hardware models; move the board-specific
detail into opt-in macros the variants define, so the core code stays generic and
new boards can opt into the behavior class without touching shared files.
- AudioThread amp control: drop the T_LORA_PAGER / MESHNOLOGY_W10 ifdefs. A board
with an I2S amp now defines AUDIO_AMP_ENABLE(on) in its variant.h (T-LoRa Pager
and Meshnology W10 do), and AudioThread just calls it around playback. The
expander includes are keyed on USE_XL9555 / USE_MCP23017 (capabilities) instead
of the model name.
- Light-sleep / DIO1 wakeup: drop the SENSECAP_INDICATOR check. Boards whose
LORA_DIO1 is an expander pin (not an ESP32 GPIO) define LORA_DIO1_EXTENDED_IO
(SenseCAP Indicator and Meshnology W10), and sleep.cpp keys off that for both
the light-sleep skip and the DIO1 GPIO-wakeup skip. This also fixes a latent
issue on the SenseCAP: it previously reached the GPIO-wakeup path and called
gpio_pulldown_en() on a virtual expander pin; it now skips that cleanly.
Builds verified on meshnology_w10, tlora-pager, and seeed-sensecap-indicator.
* meshnology-w10: silence cppcheck constParameterPointer on the ISR-callback helper
isIsrTxCallback() takes a function pointer it only compares; cppcheck's
constParameterPointer wants it 'pointer to const', which is meaningless for a
function pointer (the codebase already globally suppresses the sibling
constParameterCallback). Inline-suppress it to keep the check green.
* HopScaling: qualify the member assignment as this->count
cppcheck (CI's version) flags 'count = newCount;' at the end of trimIfNeeded()
as uselessAssignmentArg ('assignment of function parameter has no effect') - a
false positive, since count is a member that outlives the call. Write it as
this->count (matching the Step-1 assignment above) so the analyzer sees a member
write. This finding comes in via the develop merge, not this board; fixing it
here to unblock the PR's cppcheck check.
* Right-size nRF52 heap tiers after 2.8.0 heap-exhaustion field reports
Field reports on 2.8.0 show nRF52840 devices at 99% heap (114/115 KB)
within minutes of boot; operator new asserts on OOM, so these devices
are one allocation from a reboot. The 2.8.0 cache sizing ladders gave
nRF52 the largest non-PSRAM tiers on the assumption that a BLE-only
part has a roomy heap - the arena is actually ~125 KB shared with the
FreeRTOS task stacks.
Per-target retiers (nRF52840 unless noted):
- Traffic Management cache 1000 -> 250 entries (10 KB -> 2.5 KB); the
unclassified fallthrough drops 1000 -> 400 to match the classic-ESP32
tier (also affects RP2040/RP2350)
- Warm node store 200 -> 100 entries (8 KB -> 4 KB); the non-XXAA
fallthrough drops 320 -> 100 so an unclassified RAM-constrained part
can't boot-allocate 12.8 KB
- MESSAGE_HISTORY_LIMIT 20 -> 10 (text pool 4.4 KB -> 2.2 KB), the tier
classic ESP32 already ships
- MAX_RX_TOPHONE 32 -> 16, shrinking the static packet pool 70 -> 54
slots (~6.6 KB of .bss returned to the heap arena)
- PacketHistory hash index off arch-wide (1 KB); O(n) over 240 records
is negligible at LoRa packet rates
- OLEDDISPLAY_REDUCE_MEMORY arch-wide (~1 KB OLED back buffer); the five
TFT variants -U it because TFTDisplay.cpp needs buffer_back for
dirty-window diffing
- Drop the stale "for testing" 1024-entry TMM override on T1000-E
Measured on rak4631: heap arena grows 124,572 -> 131,180 B and boot
allocations drop ~15.7 KB, roughly +22 KB free heap on the field-report
device class.
Migration: the nRF52840 warm flash ring replays through place() (LRU),
so the newest 100 identities survive the shrink; the file backend
rejects oversized snapshots cleanly (new test covers this). Native
suites pass (536/536 Docker, 13/13 native-macos warm store); rak4631,
heltec-mesh-node-t114 (TFT) and tracker-t1000-e build green.
* Add central memory-class ladder (MemClass.h) with fail-safe-small defaults
The 2.8.0 nRF52840 heap exhaustion happened because each RAM-sized cache
picked its per-platform tier from its own chip #ifdef ladder, and every
ladder's fallthrough default was its largest non-PSRAM tier - nRF52 was
never named, so it silently got 1000-entry caches on a ~115 KB arena.
This introduces src/memory/MemClass.h: a single MESHTASTIC_MEM_CLASS
(TINY / SMALL / MEDIUM / LARGE) ranked by usable app heap after platform
overheads, with the deliberate property that an unclassified chip lands
in SMALL - a new target boots with small caches until someone opts it up
in one visible place.
The TMM cache, warm store, MAX_RX_TOPHONE and MAX_SATELLITE_NODES
ladders in mesh-pb-constants.h now key off the class; branches pinned by
something other than RAM stay explicit and say why (nRF52840's SoftDevice
arena, RP2040's warm.dat watchdog bound). MAX_NUM_NODES intentionally
stays separate - it is flash-shaped (nodes.proto vs LittleFS), not
heap-shaped.
A per-class MESHTASTIC_BOOT_CACHE_BUDGET static_assert now covers the
three big boot-allocated caches, so the next cache-adding PR that would
blow a small platform's budget fails to compile instead of exhausting
heap in the field.
No values change for any existing target: rak4631, tbeam, rak11310 and
wio-e5 build byte-identical before/after; all ladders remain
#ifndef-guarded so variant overrides keep working.
* Address review: fix RP2350 class-table doc, add PacketRecord static_assert
- MemClass.h's class table claimed RP2350 was MEDIUM while the mapping
ladder classifies it SMALL (with RP2040) - the table now matches the
ladder, with a note that RP2350 is a MEDIUM candidate whenever someone
wants to tune it up (kept SMALL here so this header stays a behavioral
no-op).
- The boot-cache budget comment referenced a static_assert pinning
PacketHistory::PacketRecord at 20 B that did not exist (only a layout
comment). Add the real static_assert so the budget math in
mesh-pb-constants.h fails to compile if the record layout changes.
Also merges develop (the base #10898 landed there as a squash, which is
what made this stacked branch conflict); develop's mesh-pb-constants.h
is byte-identical to this branch's base, so the resolution keeps the
MemClass ladder unchanged. rak4631 and wio-e5 build green;
test_packet_history 47/47.
* Address review: share PACKETHISTORY_MAX, trim policy comments
- Hoist PACKETHISTORY_MAX from PacketHistory.cpp into
mesh-pb-constants.h (next to the MAX_NUM_NODES it derives from) so the
constructor clamp and the boot-cache budget static_assert use one
definition instead of hand-mirrored arithmetic that could drift. The
expression stays valid where MAX_NUM_NODES resolves at runtime
(ESP32-S3, portduino); the pointless 2.0 double math becomes integer.
- Trim the MemClass.h header (36 -> 16 comment lines) and the budget /
sizing-policy comments per the repo comment-length guideline, keeping
the class table, the fail-safe-small rule, the override mechanism, and
the include-order constraint.
rak4631 (compile-time MAX_NUM_NODES) and heltec-v3 (runtime) build
green; test_packet_history 47/47.