Commit Graph

1471 Commits

Author SHA1 Message Date
Austin
76f4340f3a Add explicit board_level = release (#11305)
Relying on board_level = <empty> was causing some inheritence footguns.
Let's be explicit about what's being released.
2026-07-31 16:04:24 +00:00
Jason P
575788ae7b Implement Fixes for Meshnology W12's Two Color Display (#11288)
* Round 1 Fixes

* Update GPS icon

* Update calculations for revised GPS icon after regression testing

* Add BICOLOR_OLED_DISPLAY and actually use it

---------

Co-authored-by: Austin <vidplace7@gmail.com>
2026-07-31 04:54:54 +00:00
Thomas Göttgens
ecd59e3120 Package meshtasticd for Windows as an MSI (#11289)
* 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>
2026-07-31 04:53:52 +00:00
Austin
02ea5baad4 Pioarduino 3.3.11 (#11238)
* Pioarduino 3.3.11

Update pioarduino from 3.3.9 to 3.3.11
There was no 3.3.10 pioarduino release

Release notes:
- https://github.com/pioarduino/platform-espressif32/releases/tag/55.03.311
- https://github.com/espressif/arduino-esp32/releases/tag/3.3.10
- https://github.com/espressif/arduino-esp32/releases/tag/3.3.11

* update sensecap indicator to 3.3.11

---------

Co-authored-by: mverch67 <manuel.verch@gmx.de>
2026-07-30 23:53:13 +00:00
Austin
a0c4987c75 Narrow "PR" candidates (now only 17) (#11294)
These boards should be built when changes are made to the Meshtastic Core. Narrowed signficantly to stop wasting time.
2026-07-30 17:20:54 -04:00
Thomas Göttgens
597f6767b5 Add Elecrow ThinkNode M8 board support (thinknode_m8) (#11226)
* 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>
2026-07-30 15:59:35 +00:00
brad112358
84322af4e0 Add support for a new nRF variant constructed by soldering an E22 (#7579)
* 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>
2026-07-30 15:46:59 +00:00
Thomas Göttgens
047c4e9feb add LR 2021 to portduino, and allow Framebuffer devices to rotate the screen from config (#11252)
* 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
2026-07-30 10:44:52 +00:00
Tom
2024bb8384 Arrival time fix perhaps (#11274)
* 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
2026-07-29 14:03:25 +00:00
renovate[bot]
b4ff1df864 chore(deps): update platformio/ststm32 to v19.7.1 (#11263)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-07-29 00:52:26 +00:00
Ben Meadors
2a20ee5954 ESP32: link via @response files across the family, not just P4
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).
2026-07-28 08:47:46 -05:00
Manuel
5f51bb1c1e MUI: Portduino curl maps (#11214)
* add libcurl, fix missing SDL2

* device-ui commit reference PR353

* add curl dependencies to CI build

* switch to gnutls curl version

* Update device-ui library dependency URL

* Update device-ui dependency to new version

* Fix formatting in Dockerfile package installation

* Update device-ui library dependency URL
2026-07-28 11:09:40 +00:00
Ben Meadors
134fe5ec3e Stop BLE log streaming from starving the NimBLE notify pool (#11253)
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
2026-07-27 15:56:23 -04:00
Ben Meadors
f61f65891c fix(promicro): exclude emoji to fit under the warm-store region (#11256)
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.
2026-07-27 15:20:57 +00:00
Austin
a640d44b5c Add T-Beam BPF - 144-148 Mhz LoRa @ ~37 dBm (5 Watts) (#10558)
* 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>
2026-07-27 13:54:53 +00:00
Austin
6d944e44d0 Switch to forked pioarduino platform (with penv fixes for CI issues) (#11219) 2026-07-25 15:31:56 -05:00
renovate[bot]
36fdbddd57 Update LovyanGFX to v1.2.26 (#11157)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-07-24 14:31:29 +00:00
Ben Meadors
becfb770c1 nrf52: fix BLE-task stack overflow crashing pairing and wiping LittleFS (#11190)
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.
2026-07-24 06:38:46 -05:00
Ben Meadors
b08c04f264 Add Meshnology W12 (WiFi LoRa 32 V5) LR2021 board support (#10910)
* 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.
2026-07-24 03:29:44 +00:00
Ben Meadors
cad3a3a5c7 Always exclude range test
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.
2026-07-22 10:25:54 -05:00
renovate[bot]
b800c18055 Update RAK13800-W5100S to v1.0.3 2026-07-22 00:37:15 +00:00
Ben Meadors
7d954e668d Merge branch 'develop' into codex/packet-auth-policy
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.
2026-07-21 06:09:57 -05:00
Jonathan Bennett
bd71ad283c Remove BASEUI_HAS_GAMES flag from portduino.ini (#11113)
Removed BASEUI_HAS_GAMES flag from build configuration.
2026-07-20 20:38:24 -05:00
Benjamin Faershtein
08cfb3d683 Merge upstream develop into packet authentication policy 2026-07-20 11:48:59 -07:00
Ben Meadors
829ff80a09 fix(t5s3-epaper): 40MHz octal PSRAM; guard EInk init; esp32s3 sw-atomic shim (#11062)
- 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.
2026-07-20 12:13:50 +02:00
Benjamin Faershtein
59ab12675a Merge develop into packet authentication policy 2026-07-19 15:33:16 -07:00
Thomas Göttgens
a808e992a1 Add native Windows build of meshtasticd (#11031) 2026-07-19 23:31:19 +02:00
Austin
a4ea55f919 Fix renovate comments (#11065)
Fix renovate comments for:
- Crypto
- Adafruit_nRF52_Arduino
- adafruit/Adafruit-MLX90614-Library
- meshtastic/Fusion
2026-07-18 21:34:38 -04:00
renovate[bot]
b56b35fcbb chore(deps): update platform-native digest to 86c62ed (#11054)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-07-18 21:12:12 -04:00
Ethac.chen
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
Andrew Yong
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
HarukiToreda
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
Andrew Yong
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
Jonathan Bennett
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
Ethac.chen
deea7be89b Add RAK WisMesh Pocket V3 variant (rak_wismesh_pocket env) (#10953) 2026-07-16 07:02:57 -05:00
renovate[bot]
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
Ethac.chen
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
Thomas Göttgens
fb922c2413 fix(tracker-x1): silence the buzzer during init (#11007) 2026-07-14 10:44:31 +02:00
Andrew Yong
fe288a70e4 stm32wl: exclude PKT_HISTORY_HASH, RANGETEST, WAYPOINT, POWER_TELEMETRY (#10992)
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>
2026-07-13 07:08:20 -05:00
Iris
5e99139a43 Guard LR1121/LR2021 radio configs with #ifdef (#10998)
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.
2026-07-13 05:50:56 -05:00
Ben Meadors
8abae90838 Give the 4MB T3-S3 boards an app partition that fits (#10978)
* 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.
2026-07-10 10:18:09 -05:00
Thomas Göttgens
838323cc07 Quote esp-idf include paths in esp32.ini for Windows builds (#10980)
${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.
2026-07-10 10:17:47 -05:00
HarukiToreda
f4efbd9149 tlora-t3s3-epaper: e-ink startup and build cleanup (#10973)
* T3s3 Eink

* Update SerialConsole.cpp

* Update

* Update SerialConsole.h

* Update EInkDisplay2.cpp
2026-07-10 05:50:30 -05:00
Benjamin Faershtein
d6b12ea3f1 feat(security): enforce packet authenticity policies 2026-07-09 17:48:55 -07:00
Ben Meadors
b4b1ece8f1 nrf52: fix loop-task stack overflow causing reset on BLE pairing/first-sync (#10944)
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.)
2026-07-08 17:11:32 -05:00
Ben Meadors
8cff9e0ac3 Setup renovate for nrf52 arduino platform package (#10946) 2026-07-08 13:57:35 -05:00
Andrew Yong
fcea931aeb fix(nrf52): enable USB/charge detection on Seeed Wio Tracker L1 (#10941)
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>
2026-07-08 11:10:23 -05:00
Manuel
20ad985f41 update sensecap arduino-esp32 fork to 3.3.9 (#10933) 2026-07-08 08:06:48 -05:00
Andrew Yong
d16ae2b098 Rename power.h to Power.h for casing consistency with Power.cpp (#10919)
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>
2026-07-07 10:15:01 -05:00
Ben Meadors
97302f1845 Add Meshnology W10 AIOT Dev Kit (SX1262 via MCP23017 I2C expander) (#10911)
* 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.
2026-07-07 09:58:54 -05:00