Commit Graph
12413 Commits
Author SHA1 Message Date
oscgonferandThomas Göttgens b59cd58d75 Add HM330x PM Sensor (#11069)
* Add HM330X PM Sensor

* Update HM330X library

* Bring reclock I2C to HM330x sensor

* Fix probeHM330x for variants without AQ telemetry or telemetry in general

* Remove old import

* Bring back SHT2X from develop

* Remove test comment and unused method in HM330X class

* Reorder detection method. Add pending TODO for INA219 detection

* Rework detection order and add INA219 register check

---------

Co-authored-by: Thomas Göttgens <tgoettgens@gmail.com>
2026-08-05 08:35:59 +00:00
e78b121d9f Lr1121 tcxo optional tries xtal first, and get all my yamls in a row (#11215)
* LR11x0: try XTAL before TCXO when oscillator type is uncertain

On boards with TCXO_OPTIONAL, a TCXO-first attempt either hangs RadioLib's
calibration wait forever on a bare/non-TCXO module (unpatched upstream), or
costs a slow failed attempt before falling back even once that's fixed with
a timeout. Measured on hardware: XTAL succeeds immediately on a bare module
(~350ms) and fails fast and cleanly on a genuine TCXO module (~300ms,
RADIOLIB_ERR_SPI_CMD_FAILED), so trying XTAL first is a strict improvement
for hang-avoidance regardless of which oscillator is actually present.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* compacted

* fix review comment

* fix femtofox switches

* correct the correction

* 13

* 3s timeout

* Treat SPI_CMD_TIMEOUT as an LR11x0 init failure

The BUSY watchdog breaks RadioLib's wait, so the next bounded transfer
returns SPI_CMD_TIMEOUT rather than SPI_CMD_FAILED. Only the latter was
checked, so a watchdog-triggered failure fell through to getVersionInfo(),
setRfSwitchTable() and startReceive() against an unresponsive chip.

Also use Throttle::isWithinTimespanMs() for the watchdog's elapsed-time
check instead of raw millis() arithmetic.

* Drop the BUSY watchdog and probe XTAL before TCXO

The watchdog bounded RadioLib's unbounded BUSY wait in LR11x0::config() by
having LockingArduinoHal::digitalRead() report a stuck pin low exactly once.
That let a TCXO-first attempt fail cleanly rather than hang, but it meant
lying to RadioLib about a GPIO from a HAL shared by every radio driver.

Ordering the attempts XTAL-first avoids the hang outright instead: attempt 1
configures no DIO3 Vref, so there is no calibration wait to get stuck in, and
the TCXO fallback is only reached on a module that answered and refused XTAL.

Attempts are now XTAL, then TCXO, then a settling retry on whichever
oscillator was settled on - after a fallback that is a second TCXO attempt.
Only TCXO_OPTIONAL builds probe XTAL; a variant that declares a Vref
unconditionally still goes straight to it and never probes XTAL at all.

SPI_CMD_TIMEOUT stays a failure alongside SPI_CMD_FAILED: a bounded
per-command BUSY wait in Module::SPItransferStream() reports it in its own
right, independently of the removed watchdog.

* Drop a stray tab from the promicro TCXO readme

trunk fmt: prettier flags the whitespace-only line inside the <summary>
block, which was the only failing check on the PR.

---------

Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
Co-authored-by: Thomas Göttgens <tgoettgens@gmail.com>
2026-08-05 08:29:23 +00:00
03e6b80989 Serial config validation (#11339)
* fix(serial): validate serial module config on every platform

AdminModule guarded the serial config validation by architecture but not the
assignment beneath it:

    #if ARCH_ESP32 || ARCH_NRF52 || ARCH_RP2040
        if (!SerialModule::isValidConfig(...)) return false;
        disableBluetooth();
    #endif
        moduleConfig.serial = c.payload_variant.serial;

So on every other platform an admin "set module config: serial" stored a config
the firmware rejects on ESP32. override_console_serial_port combined with
DEFAULT, SIMPLE, TEXTMSG or PROTO is accepted and persisted today.

Two families are affected, for different reasons:

  - portduino/meshtasticd, where the validation did not exist at all:
    isValidConfig was a static member of SerialModule, and that class is inside
    the same architecture guard, so `nm` finds no such symbol in the native
    object.
  - STM32WL (rak3172, wio-e5, CDEBYTE_E77-MBL, russell), where it existed and
    was never called: the class guard includes ARCH_STM32WL and the AdminModule
    call site did not.

Validation is pure config logic with no serial hardware behind it, so it moves
out of the class and out of the guard as a free serialConfigIsValid(). Its only
external references - clientNotificationPool, service, getValidTime - are
already unguarded elsewhere, so it links on every target. AdminModule's include
of SerialModule.h is unguarded for the same reason; the class itself stays
guarded inside the header. Only disableBluetooth() remains architecture-specific.

This changes what meshtasticd and the STM32WL targets accept: a host relying on
the unvalidated path (override_console_serial_port with a mode other than NMEA,
CalTopo or MS_CONFIG) is now rejected, as it already is on ESP32.

test/test_serial has asserted nothing since it was added in 28aeb0f09e
(2025-07-26): its body is behind the same guard, so on portduino it logged a
warning and ran zero assertions while counting as one of the canonical suites.
Enabling it showed the code did not even compile - its designated initializers
list .override_console_serial_port before .mode, which is not declaration order,
and C++ requires that. PlatformIO only builds test/ for the native env, so no
build had ever compiled these lines. Reordered; all nine now run and pass.

* Potential fix for pull request finding

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>

* I'd say gimme 5 bees for a dollar. That's what we called a nickel, because they had bees on em.

* style: wrap over-long warning string to the 120-col limit

* Cover MS_CONFIG override and correct the validator comment

serialConfigIsValid() accepts MS_CONFIG alongside NMEA and CALTOPO when
override_console_serial_port is set, but only the first two had a valid-case
test. Add the missing one.

The declaration comment described the function as pure config logic; it also
logs and, in non-test builds, sends a client notification on rejection.

---------

Co-authored-by: Ben Meadors <benmmeadors@gmail.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
2026-08-05 06:55:31 +00:00
renovate[bot] 40f4a95d72 Update meshtastic-esp8266-oled-ssd1306 digest to ace0fcb (#11356)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-08-05 06:48:29 +00:00
Matias Denda 1dde97f807 Fix stack buffer overflow in aes_ccm_encr for partial blocks (#11347)
* Fix out-of-bounds write in aes_ccm_encr for partial blocks

aes_ccm_encr() writes a full 16-byte AES block to the output before XOR-ing with
the input, so a trailing partial block writes up to 15 bytes past the length the
caller asked for. Every caller in the tree passes a buffer with enough slack, so
nothing misbehaves today, but the decrypt path clears it by only a few bytes.

Encrypt into a temporary block and XOR out of it, matching what
aes_ccm_encr_auth() and aes_ccm_decr_auth() already do in this same file. The
ciphertext is unchanged.

* Add regression test for the CCM partial-block write and drop stale workarounds

The guard bytes past the caller's buffer catch the overflow without relying on a
sanitizer, so the test is meaningful in the native environment too.

encryptCurve25519() no longer needs to write extraNonce before aes_ccm_ae(): the
call stays inside numBytes now, so the copy after it is the only one required.
The comment warning about the 15-byte overshoot no longer describes the code.
2026-08-04 22:31:38 +00:00
HarukiToredaandJason P 39e7aa6a6c Full T-echo card support + Compact UI (#11342)
* T-echo card

* Update NRF52I2SOutput.cpp

* Update NRF52I2SOutput.h

* cleanup

* Update buzz.cpp

* use consistent runtime compact-panel check instead of mixing with compile-time macro

* Update NodeDB.cpp

* Update ExternalNotificationModule.cpp

* switched to Throttle::isWithinTimespanMs

* Update SharedUIDisplay.h

* trunk fix

* last cleanup

* ClockRenderer.cpp for OLED_COMPACT_UI and setup Unit C6L for new UI.

* Fixed regressions in standard OLED and TFT

---------

Co-authored-by: Jason P <applewiz@mac.com>
2026-08-04 14:27:35 +00:00
Carlos Valdes d89f3eb8fc fix(api): bound the stream drain so a full config dump can't starve the watchdog (#11164)
writeStream() drained the whole queue in one call - "send every packet we can".
A client asking for the full config gets the node database, then the file
manifest, then the packet backlog and the position replay, and none of that
returns to loop(). On a full node database (120 entries) the dump runs past
eight seconds, so on RP2350, where rp2040Loop() arms an 8s hardware watchdog
and is the only thing that calls watchdog_update(), the board resets in the
middle of the manifest. Reproducible on every connection; with a small node
database the dump finished under the timeout and nothing looked wrong.

Measured on a pico2_w5500_e22: last loop iteration at millis=27242, ServerAPI
kept logging until uptime 35s, reset at 35.2s = 27.242 + 8.0.

Take a slice instead. The PhoneAPI state machine is resumable, so writeStream()
stops after STREAM_WRITE_BUDGET_MSEC and reports whether anything is left;
runOncePart() then asks to be re-run immediately rather than sleeping out
readStream's idle delay, so the dump keeps its throughput while loop() gets to
feed the watchdog between slices. Backpressure on a retained frame still
returns the normal delay - re-running at once would just spin on a full
transport.

Verified on hardware: 10/10 full config dumps against a node with 120 entries,
no resets, dump still completes in ~8s.
2026-08-04 14:14:08 +00:00
Jonathan Bennett 7302db1672 Update LoRa firmware on Thinknode M7 (#11337) 2026-08-04 13:03:30 +00:00
Jonathan Bennett b026bfbd98 8mb partition table for ThinkNode-M7 (#11338)
This will break OTA and result in FS wipes. Wouldn't hurt to recommend complete wipes for the install, too.
2026-08-04 13:02:46 +00:00
Thomas Göttgens 98813fe79d Shared e-ink hardware layer: foundation for target-by-target migration off GxEPD2 (#11142)
* Add shared e-ink hardware layer (graphics/eink) alongside legacy drivers

Foundation for a target-by-target migration off the GxEPD2-based
EInkDisplay2/EInkDynamicDisplay/EInkParallelDisplay stack:

- src/graphics/eink/: chipset drivers, panel profiles, backlight helper
  (promoted from the InkHUD driver set, shared by BaseUI and InkHUD)
- src/graphics/BaseUIEInkDisplay: OLEDDisplay adapter driving the new
  layer, with EINK_* compat macros matching EInkDynamicDisplay
- [niche] build helper in platformio.ini; graphics/eink/ excluded from
  arduino_base so unconverted targets are unaffected
- Screen/CannedMessageModule dispatch between the two stacks per env
- InkHUD-specific touch code in TouchScreenImpl1 guarded with
  MESHTASTIC_INCLUDE_INKHUD (no-op today, required once BaseUI variants
  define MESHTASTIC_INCLUDE_NICHE_GRAPHICS without InkHUD)

No variant is converted and no legacy file is removed; every existing
env builds identical firmware.

* Fix clang-format comment alignment in Screen.cpp

* Address review findings in the e-ink driver layer

- Screen.cpp: exclude InkHUD builds from all NicheGraphics BaseUI guards
- BaseUIEInkDisplay: size the OLEDDisplay buffer from its actual indexing
- EInkParallel: defer update() while an async refresh is in flight, honor
  the selected clear mode in the async task, never delete a live task
- ED047TC1: clean up on failed initPanel, fix inverted bbepI2CWrite checks
- UC8175: drop bogus 0x12 soft reset (0x12 is display refresh on UC8175)
- LCMEN2R13EFC1: guard absent reset pin, bound the busy wait
- SSD16XX/SSD1682: build the RAM window from the instance, not statics
- Doc corrections in driver banners and Drivers/README

* SSD16XX/SSD1682: send inclusive Y-end address (height - 1)

* LCMEN213EFC1: adopt the shared wait timeout / fail-through pattern

wait() now bounds the busy poll via Throttle and sets the EInk failed
flag on timeout; sendCommand/sendData fail through like the SSD16XX and
UC8175 drivers. EInk::runOnce clears the flag after the failed cycle.
2026-08-04 12:34:45 +00:00
Austin 56da28fd27 Update earlephilhower/arduino-pico to 6.0.0 (#11351)
https://github.com/earlephilhower/arduino-pico/releases/tag/6.0.0
2026-08-04 09:42:55 +00:00
Manuel 6eac181c2b use PSRAM for MBED TLS (#11350) 2026-08-04 00:35:40 +00:00
Austin ee5f24f24c T-Beam BPF: Use the correct SVG (#11352)
Added to the web-flasher repo in https://github.com/meshtastic/web-flasher/commit/1b885865f12be306e312674f68c74fa3eab51317
2026-08-03 22:23:41 +00:00
Austin b4969a4a23 Update earlephilhower/arduino-pico to 5.7.0 (#11333) 2026-08-03 19:13:38 +00:00
Jason P 9e60b23334 Targeted build fixes, especially Heltec T1 (#11345) 2026-08-03 14:13:52 +00:00
oscgonferandThomas Göttgens 8c0ef44b0b Add one wire i2c bridge support (#10078)
* First version of DS248X bridge

* Add first iteration of DS248X sensor

* Supports single readings on DS2484
* Supports readings on ch0 for DS2484_800
* Detection of variant for DS248X

* Minor fix on retries for sensor init

* Allow multiple channel detect passes on 8-ch version

* Always read temperature via ROM matching

* Small comment to show how to send all channels

* Minor logging changes

* Prevent one-wire double definitions

* Detect ROMs per round

* Fix comment

* Prevent skipping on DS2482 ALT3 check

* Fix comment (again)

* Fix style  checks

* Remove comment for multiple measurements

* Address CodeRabbit review findings on DS248X sensor

Set _variant on every detectVariant path and branch on the member, so a
failed variant probe retries instead of falling into single-channel init.

Search DS2482-800 channels into a scratch buffer so a transient one-wire
failure cannot erase a ROM found on an earlier pass, and count channels
that already hold a ROM.

Check every one-wire return value in readTemperatureROM, validate the
scratchpad CRC, and return DS248X_INVALID_TEMPERATURE on failure so the
existing sentinel checks reject failed reads instead of reporting stale
or uninitialised data.

* Probe IIS2MDCTR WHO_AM_I before the DS248X status check

At HMC5883L_ADDR the DS248X probe reads 0xF0. On the IIS2MDCTR that
sub-address sets auto-increment and targets 0x70, which is reserved and
returns an unspecified value; any of bits 0x02, 0x04 or 0x10 makes the
probe claim the magnetometer as a DS2482.

Reading the WHO_AM_I at 0x4F first is deterministic for both parts. A
DS2482 does not acknowledge 0x4F, an invalid command code, and leaves
its read pointer untouched, so the subsequent read returns Status,
Configuration, Channel Selection or Read Data. None of those can hold
0x40 at scan time, so the DS248X probe still runs and detects it.

This also restores develop's detection order and matches the structure
already used at BMA423_ADDR: specific ID match, then probe, then the
generic fallback.

* Read only the reported channel in getMetrics

getMetrics walked all eight DS2482-800 channels, but only channel 0 is
ever written into the measurement. Each populated channel costs a
blocking 750ms conversion inside readTemperatureROM, so a fully wired
bridge stalled telemetry for roughly 6s per cycle and discarded seven of
the eight readings.

Channels without a sensor were already cheap thanks to the isValidROM
guard, so this only affects boards that actually use more than one
channel, which is the reason to fit a DS2482-800 in the first place.

Multi-channel reporting is handled separately in #10192; a note records
that it should start the conversion on every channel before waiting,
rather than reading each channel end to end.

* Trim DS248X comments to one line each

---------

Co-authored-by: Thomas Göttgens <tgoettgens@gmail.com>
2026-08-03 13:52:02 +00:00
Thomas Göttgens 58657d484a Re-enable mbedtls secp384r1 on ESP32 (#11344)
Let's Encrypt Generation Y chains sign a P-256 leaf with the P-384
intermediate YE1 under ISRG Root YE. mqtt.meshtastic.org switched to
this chain on 2026-07-29. With CONFIG_MBEDTLS_ECP_DP_SECP384R1_ENABLED=n
mbedtls cannot parse the peer chain and the TLS handshake aborts with
MBEDTLS_ERR_PK_UNKNOWN_NAMED_CURVE, breaking MQTT over TLS on every
ESP32 target.

Costs about 4 kB of flash.

Fixes #11316
2026-08-03 12:49:21 +00:00
LILYGO_L ddb7bbf090 fix(power): support SGM41562 family on T-Impulse Plus (#11298)
* fix(power): support SGM41562 family on T-Impulse Plus

* chore: format SGM41562 code
2026-08-03 10:56:45 +00:00
oscgonfer 5792e8751b Fix PMSA003 Enables on RAK4631 (#11331)
* Minor fix for PMSA003I

* Remove class from State, make state verbose, sleep after init

* Minor changes to gate some ifdefs and state class

* Make decision tree explicit in AQ Telemetry. Also enable on phone

* Minor change in comment

* Fix issue staying on if failed enable. Minor change in log
2026-08-03 08:47:09 +00:00
Tom 41c5c92874 serial heap status messages (#11340) 2026-08-03 01:30:36 +00:00
Jason P c0ddb209b5 Fix up the BiColor boot screen (#11334)
* Fix up the BiColor boot screen

Fits the color below the divide of the two OLED colors for better boot appearance. Calculates out the same math for any other screens.

* Failed builds because of math, means you change the math
2026-08-03 01:03:08 +00:00
Austin 8d3ad2a146 Remove board_check (now using normal matrix) (#11312)
This was being misused / misunderstood (most were a no-op already).
2026-08-01 21:41:55 -04:00
Austin 854eae456b QMA6100PSingleton constructor fix pio check warnings (#11313)
Fixes cppcheck issues
  src/motion/QMA6100PSensor.cpp:173: [medium:warning] Member variable 'QMA6100P::rawAccelData' is not initialized in the constructor. Maybe it should be initialized directly in the class QMA6100P? [uninitDerivedMemberVar]
  src/motion/QMA6100PSensor.cpp:173: [medium:warning] Member variable 'QMA6100P::_i2cPort' is not initialized in the constructor. Maybe it should be initialized directly in the class QMA6100P? [uninitDerivedMemberVar]
  src/motion/QMA6100PSensor.cpp:173: [medium:warning] Member variable 'QMA6100P::_deviceAddress' is not initialized in the constructor. Maybe it should be initialized directly in the class QMA6100P? [uninitDerivedMemberVar]
2026-08-01 23:13:22 +00:00
Austin 88a18663f6 Actions: Restrict 'check' jobs to only run for pull requests and merge queue events (#11332) 2026-08-01 20:48:52 +00:00
Austin dc67d3b7e8 Update stale bot action to version 11 (#11330) 2026-08-01 18:41:51 +00:00
AustinandClaude Opus 5 f481585de4 Make GxEPD2_Multi non-copyable to address cppcheck warnings (#11326)
cppcheck reports noCopyConstructor and noOperatorEq against GxEPD2_Multi
on every e-ink environment (over 80 duplicate pairs on a single
heltec-wireless-paper run, one per template instantiation point):

  src/graphics/GxEPD2Multi.h:123: [medium:warning] Class 'GxEPD2_Multi <
  GxEPD2_213_FC1 , GxEPD2_213_E0213A367 >' does not have a copy
  constructor which is recommended since it has dynamic memory/resource
  allocation(s). [noCopyConstructor]

The warning is correct. The constructor news one of two GxEPD2_BW drivers
into a raw pointer member and caches &driver->epd2 in epd2.m_epd2, so the
compiler-generated copy operations would alias that driver: two objects
would drive the same panel, and the second to be destroyed would free a
driver the first still points at.

Nothing copies it - EInkDisplay2 heap-allocates a single instance and
holds a pointer - so declare that intent by deleting the copy operations
rather than adding a suppression.

Also null the unselected driver pointer. Only one of driver0/driver1 is
allocated and the other was left indeterminate; every method branches on
`which` before dereferencing, so this is latent rather than a live bug,
but an indeterminate owning pointer is one refactor away from a wild
dereference.

Behaviour is unchanged: no caller could have copied this type, and the
two added stores only initialize a pointer that is never read.

Verified on heltec-wireless-paper: `./bin/check-all.sh
heltec-wireless-paper` now reports "No defects found" (exit 0), and
`pio run -e heltec-wireless-paper` builds clean. The build matters
separately here because syntaxError is suppressed in suppressions.txt,
so cppcheck alone would stay green on a malformed declaration.

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
2026-08-01 18:13:20 +00:00
Austin d9f1622ec1 Update ci-gate condition to check event types and cancellation status (#11329) 2026-08-01 17:18:35 +00:00
AustinandClaude Opus 5 ad5d877c16 Fix cppcheck redundantAssignment for OLED_GEOMETRY_OVERRIDE variants (#11325)
`pio check` reports at src/main.cpp:878:

  [low:style] Variable 'screen_geometry' is reassigned a value before the
  old one has been used. [redundantAssignment]

for every variant that pins its panel size with OLED_GEOMETRY_OVERRIDE
(t-impulse-plus -> GEOMETRY_64_32, t-echo-card -> GEOMETRY_72_40). The
diagnostic pairs the override write with the `GEOMETRY_128_128` write in
the SH1107 normalization branch: on those boards that write is a dead
store, clobbered a few lines later.

Skip the geometry writes when the variant pins the panel size. The
screen_model normalization still runs (the driver needs it) and
precedence is unchanged - the override still wins on those boards, and
nothing changes for boards without one. The compile-time USE_SH1107
write is guarded the same way so the defect can't reappear if a future
variant combines the two.

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
2026-08-01 17:03:03 +00:00
4b4e82bd72 SenseCAP Indicator: RP2040 peripherals for the main firmware (#6220)
* indicator: RP2040 peripherals for the main firmware

The SenseCAP Indicator RP2040 co-processor serves as a generic
peripheral bridge over a serial protobuf link (interdevice.proto):

- FakeI2C implements TwoWire and tunnels write and read transactions,
  so the standard sensor drivers and the I2C scan work unmodified on
  the bridged second bus (WIRE1)
- FakeUART forwards GPS NMEA to the regular GPS driver
- SD card access with chunked file transfers, paged directory
  listings and card statistics; device-ui loads map tiles and map
  styles from the card behind the RP2040
- link at 2M baud with 4KB chunks, message structs kept off task
  stacks

Log messages carrying their own bracket tag render it like a thread
name. Replaces the earlier IndicatorSensor/COBS approach.

* indicator: address review

Correlate responses with request ids, serialize the shared TX buffer,
reject oversized frames, fix RX buffer overflow and NMEA truncation,
full-length file paths.

* indicator: assign the GPS FakeUART at runtime

Static initialization order across translation units is undefined,
so createGps() assigns and null-checks the bridged serial instead.
Bound the NMEA length defensively.

* indicator: bump device-ui pin to 27e6c0c

* indicator: ping/pong link probe, non-blocking runOnce, FakeI2C locking

The RP2040 sends nothing unsolicited without a GPS module attached, so
wait_ready now probes with the new ping message instead of listening
passively. runOnce skips its pump while a requester holds link_lock,
keeping the main loop from blocking for a full request timeout. FakeI2C
serializes transactions between the UI task and the main loop with an
owner-tracked lock held from beginTransmission to transaction end.

* indicator: link resync, config-honoring GPS, bridged-bus routing, stats validity

Frame resync scans to the next magic instead of flushing the RX buffer,
and the pump handles all buffered frames per pass. The RX drain reads in
bulk and the protobuf encoder gets the correct buffer bound. GPS honors
the gps_mode setting on the Indicator instead of always running. RTC,
I2C keyboard and motion sensor drivers resolve WIRE1 through
ScanI2CTwoWire::fetchI2CBus so bridged buses reach the right transport.
FakeUART implements flush/availableForWrite/const-write from the Stream
contract and fences its cross-core ring buffer. SdCardInfo.stats_valid
is passed through to device-ui, and the remote FS backend gains the
remove operation used for cleanup of failed tile saves.

* indicator: retry lost link round trips, I2CResult UNSPECIFIED

Remote FS operations retry once on a transport timeout. Correlation ids
drop late responses of the first attempt; a retried append whose first
attempt landed is recognized by the offset conflict carrying the
resulting file size. Definitive failures are not retried, missing-tile
probes stay a single round trip. Regenerated bindings add the
I2CResult.Status UNSPECIFIED zero value so an empty result cannot
decode as success.

* indicator: nack responses, rename bridge classes to I2CProxy/UARTProxy

A request the co-processor cannot decode or handle is nacked, so the
requester fails fast instead of burning its timeout. All requests stage
the shared tx_message under link_lock. FakeI2C and FakeUART are renamed
to I2CProxy and UARTProxy after the pattern they implement, with their
instances following suit. Drops dead code (unused NO_NEWS_PAUSE,
unreachable not-running branches, doubled include guards) and the GPS
pin log line that is meaningless on the tunneled port.

* indicator: refuse a co-processor that speaks another protocol version

The ping/pong handshake now carries InterdeviceVersion. A pong reporting
a version other than ours means the RP2040 runs firmware that does not
match this build, so the bridge stays shut down for the session and the
mismatch is logged with both versions. Requests fail fast instead of
being misinterpreted by the other side.

* indicator: regen protos, interdevice protocol version 2

* indicator: per-task I2C contexts, gated handshake, retryable link failures

The bridged I2C bus is shared between the main loop and the UI task, and
TwoWire has no transaction bracket a lock can span: drivers drain the read
buffer with available()/read() long after requestFrom() returned. Each
calling task therefore gets its own staging and read buffers instead of a
lock that could be left held (or that could not protect the read buffer
anyway). The transaction is staged inside the link, under its lock.

No request is sent before the co-processor has completed the version
handshake, and runOnce keeps probing until it does, so a co-processor that
boots slowly or reboots on its watchdog no longer leaves the bridge dead
for the session. Requests in flight are counted, not flagged: two threads
can be in a request and the first one out must not clear the other's state.

File operations are retried on a lost frame and on a co-processor busy with
card maintenance, but not on a refusal (nack) or a definitive failure, and
they release the SPI lock while they wait so a slow link does not starve
the radio.

* indicator: fail safe on a peer mismatch, wait out card maintenance

FileStatus moved to a fresh tag: reusing the tag of the removed success flag
made every failure status decode as success on a peer that predates it.

A card being mounted (busy) is retried rather than reported as an empty
slot, and a co-processor busy with card maintenance is waited out: mounting
takes seconds and the free space scan of a large card walks its whole FAT,
which is not a reason to report a missing tile. The bridged I2C bus releases
the SPI lock as well, so the keyboard scan on the UI task cannot starve the
radio either. Slot claims in the I2C proxy are atomic, NMEA is not sent to a
peer we refuse to talk to, and the handshake is completed by the unsolicited
ping the co-processor sends when it has booted, which also reports a
reboot.

* indicator: regen protos, FileStatus back on the original tags

* indicator: regen protos, ping/pong carry the InterdeviceVersion enum

* indicator: point the protobufs submodule at the merged interdevice protos

* indicator: pin device-ui to the branch with the remote SD support

* indicator: honor the txOnly flag of flush, report dropped GPS writes

flush() through a Stream pointer discarded the receive buffer: the flag is
txOnly, and HardwareSerial::flush() keeps what has been received. write()
reported bytes as written even when the link refused to send them. The link
probe uses Throttle for its rate limit.

* indicator: decide the log tag on the formatted message, hex request ids

The thread tag was suppressed based on the printf template, which disagrees
with the rendered message it is compared against: a format starting with a
conversion could produce two tags, and one without a trailing bracket-space
lost the tag entirely. vprintf now receives the thread name and picks. Also
shifts only the bytes actually buffered after a frame, throttles with
Throttle and logs request ids as hex.

* indicator: SD mount, eject and format commands over the link

* indicator: bound how long a busy card state blocks the UI task

* indicator: a busy co-processor must not block the UI task for ever

The busy retry re-armed its own budget on every busy answer, so a
co-processor that stayed busy kept the caller in the loop with no way out.
Transport retries and the wait for a busy card are now separate budgets that
only count down.

* indicator: start each request from an aligned receive buffer

A byte run lost mid-response (a UART overflow during a 4KB tile chunk, when
the display starves the RX interrupt) misaligns the assembly buffer. The
buffer was never reset, so the poison outlived the request and cascaded into
the following chunks of the same tile: one glitch dropped a whole multi-chunk
tile, while single-chunk tiles resynced in the idle gap and survived. Each
request now flushes the buffer first, bounding a glitch to the one chunk it
hit. Adds resync/decode/timeout counters, logged rarely, to see the rate.

* indicator: enlarge the LVGL heap for low-zoom map tiles

The heap was 3MB and the image cache reserves 1.5MB of it, so a low-zoom map
tile could not find a large enough contiguous block to decode and rendered
white. 5MB of the 8MB PSRAM fixes it with room to spare.

* indicator: advance the device-ui and protobufs pins to the merged commits

Point the protobufs submodule at the merged SD command protos (protobufs
#986) so it matches the checked in interdevice sources, and bump the
device-ui archive to the current indicator branch tip that carries the SD
button and format UI.

* Update device-ui library dependency URL

* remove cutom sdkconfig

* remove duplicated synchronisation (after PR11278 is in place)

* set commit reference to updated RemoteSDService class

* Add board_level configuration for release

* fix cppcheck errors

---------

Co-authored-by: Manuel <71137295+mverch67@users.noreply.github.com>
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Co-authored-by: CodeRabbit <noreply@coderabbit.ai>
Co-authored-by: mverch67 <manuel.verch@gmx.de>
2026-08-01 16:23:30 +00:00
Ben Meadors 25996e9330 Fix W12 battery reading: add ADC_CTRL and correct the divider ratio (#11323)
* Fix W12 battery reading: add ADC_CTRL and correct the divider ratio

The W12 battery config was taken from the vendor's Demo_06_ADC_Read.ino,
which reads GPIO1, multiplies by 2.0 under a literal "Assumption: 2:1
voltage divider" comment, and never touches the ADC enable at all. All
three of those details are wrong.

Schematic W12-MB-V0.2 sheet 1 has the divider behind a P-MOSFET high-side
switch so it only draws from the cell during a reading:

  BAT --S[Q6 AO3401A]D-- R50 390K --IO1_ADC_IN-- R51 100K -- GND
         |G  R49 1K to BAT (gate pull-up: Q6 off by default)
         +-- R48 1K -- C[Q7 S8050 NPN]E -- GND,  base <- R52 1K <- IO2

So GPIO2 is ADC_CTRL, not "a second (solar/VUSB) divider" as the variant
claimed, and the NPN inverts it, making it active HIGH. Left undriven, Q6
stays off and GPIO1 sits at ground through R51 - a hard 0 raw rather than
the 100-250mV of noise a floating pin gives - so every boot reported
"battery hardware absent (USB-only)" and battery_level 101.

The divider is 390K/100K, so the multiplier is 4.9, not 2.0. That puts a
4.2V cell at only ~857mV on the pin, so drop the attenuation from the
12dB default (0-3100mV) to 2.5dB (0-1250mV) to use the range properly.
This matches the Heltec V3/V4 network, but their ADC_CTRL 37 cannot be
reused here: GPIO33-37 are consumed by this board's octal PSRAM.

Verified on hardware: reports 4067mV / 91%, stable to the mV across
consecutive samples, where it previously read 0mV with a cell attached.

* Trim the battery comment block to house style

Per the repo guideline that code comments stay to one or two lines and
avoid multi-paragraph blocks, drop the ASCII schematic from the header.
The full circuit trace lives in the previous commit message and the PR
description, which is where that rationale belongs.

Comment-only; both define values are unchanged.
2026-08-01 13:16:03 +00:00
Jason P 1d2fb23a0f Consolidate drawGps related code (#11304)
* Update UIRenderer.cpp

* Update GPS code path
2026-08-01 12:30:50 +00:00
renovate[bot] d0e9d02cf8 Update meshtastic/device-ui digest to 95483d8 (#11318)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-08-01 01:59:28 +00:00
Austin 478206ea72 NodeDB: Change pointer to const for mesh node retrieval in loadFromDisk (#11315)
Addresses cppcheck
src/mesh/NodeDB.cpp:2431: [low:style] Variable 'us' can be declared as pointer to const [constVariablePointer]
2026-08-01 00:33:33 +00:00
Austin 85c1bd4836 Address STM32 cppcheck warnings (#11314)
- Suppress GenericThreadModule warning when DEBUG_MUTE
- Suppress warning stemming from check_skip_packages
- Cast pointers to uint32_t before subtracting to avoid cppcheck warning
2026-08-01 00:28:01 +00:00
Austin 5bfad255e0 Remove Ubuntu 'questing' series from PPA and release channel workflows (#11308)
questing went EOL July 9th and no longer builds on PPA
2026-07-31 17:36:13 +00:00
Jason P 3d8ff48fab BaseUI: Clean up dead code (#11306)
* Clean up dead code

* Fix up the WiFi macro to be more directly called
2026-07-31 17:29:21 +00:00
Austin 44dbcaac3d Actions: Add caching for PlatformIO in native tests workflow (#11309)
Also switch to arm64 runners for the tests themelves. In my experience so far, they are faster (and these jobs are heavyy)
2026-07-31 17:35:20 +00:00
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
Austin 33a0f8ffb7 Add libcurl as a build dependency for meshtasticd packages (#11303)
Followup to #11214
Adds libcurl as a dependency in Debian and Fedora packages.
2026-07-31 13:40:38 +00:00
Ben MeadorsandManuel e2460b546e MUI: stop holding the SPI bus across the whole UI cycle (#11278)
tft_task_handler held spiLock for the entire LVGL cycle. Most of that
cycle is timer work and rendering into the draw buffer, which issues no
SPI at all - but on boards where the TFT, SD card and LoRa radio share
one bus (T-Deck), every radio operation on the main loop still waited it
out. That is tens to hundreds of milliseconds whenever the UI animates,
felt as mesh RX/TX latency.

device-ui now takes the lock around its own transfers instead
(meshtastic/device-ui#356), so the coarse hold here can go and the bus is
contended only during real traffic.

Lend it spiLock through a reentrant adapter. device-ui nests its guards -
SdFsCard::usedBytes() calls cardSize() and freeBytes(), each of which
takes the lock - while spiLock is a plain binary semaphore that would
self-deadlock on the second take, so track the owning task and only touch
the underlying lock on the outermost acquire.

Requires the device-ui pin bump included here.

Tested on T-Deck: flush, touch, panel init, SD detect, powersave sleep
and wake all exercised; LoRa RX decoding under a live UI, no deadlocks,
no watchdog resets. Also builds seeed-sensecap-indicator-tft.

Co-authored-by: Manuel <71137295+mverch67@users.noreply.github.com>
2026-07-31 10:49:39 +00:00
TomandClaude Opus 4.8 e26b6bfc5a TMM & Warmstore key source naming (#11119)
* one becomes two

* warmstore clarify

* Address PR review: key-provenance terminology consistency

- Log line now says "not key-proven" (gate is XEdDSA OR manual, not just signer)
- Rename markKeySignerProvenForTest -> markKeyXeddsaSignedForTest (sets only the XEdDSA bit)
- Docs + test comments: "signer bit" -> "XEdDSA-signed bit"

clod helped too

* Rename signer-proven -> key-proven for broadened provenance predicate

Address PR #11119 review: the copyPublicKey()/copyUser() out-parameter and
the cache-path replay gate now report entry->keyProven() (XEdDSA-signed OR
manually verified), so the "signerProven" name and "signer-proven" comments
were misleading. Rename the public out-param to keyProven, the local
cachedKeySignerProven to cachedKeyProven, and update coupled callers, log
strings, docs headings, and comments to say "key-proven".

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* nitpicks

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-31 10:39:39 +00:00
Thomas GöttgensandAndrew Yong 6367132919 Fix the NMEA checksum offset and harden the buffer writes around it (#11293)
* Checksum NMEA sentences from the $ delimiter

The PositionLite printWPL() format begins with a CRLF, so the fixed start offset of 1 folded the newline and the $ into the checksum and every sentence went out with a wrong value. Locate the $ instead and stop at the terminator or a \*.

* Clamp truncated writes and harden the remaining fixed buffers

snprintf returns the length it would have written, so a truncated NMEA sentence
made buf + len point past the buffer and bufsz - len underflow into a huge size
for the checksum append. Clamp after each write.

Also pulls in the rest of #11236: the two remaining Dropzone sprintf calls, the
dead strcpy in mt_sprintf that wrote one byte past a zero-size allocation for an
empty format, and the 10-byte errcode buffer that INT32_MIN overflows.

Co-Authored-By: Andrew Yong <me@ndoo.sg>

* Bail out on a zero-sized buffer and cast err for %ld

snprintf writes nothing at all when bufsz is 0, not even a terminator, so the
checksum helper would run strchr over whatever the buffer already held. Return
before touching it.

int32_t is not long on every target, so cast before formatting with %ld.

Co-Authored-By: Andrew Yong <me@ndoo.sg>

* Add NMEA sentence regression tests

Covers checksum computation from the $ delimiter for both printWPL
overloads and printGGA, zero-sized buffers, and truncated buffers down
to one byte.

Co-Authored-By: Andrew Yong <me@ndoo.sg>

* Tighten checksum parsing and pin the WPL fixture checksum

Require exactly two hex digits followed by the sentence terminator, and
assert both WPL overloads against a known checksum instead of comparing
them to each other.

* Bump native suite count to 43

---------

Co-authored-by: Andrew Yong <me@ndoo.sg>
2026-07-31 08:20:56 +00:00
Jason PandAustin 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öttgensandAustin 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
Austinandmverch67 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 2192087579 Actions: Add explicit ci-gate, cleanup conditionals (#11296)
Add explicit ci-gate to the matrix workflow, and cleanup conditionals to make them more readable.
Stop gathering artifacts for PRs/merge-queue, as they are not needed and just take up time/space.
2026-07-30 23:13:49 -04: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
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
brad112358andTom 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
Tom 06577e6b36 tying up some loose ends (#11292) 2026-07-30 13:33:31 +00:00