* Load optional modules dropped into src/modules/optional/
bin/optional-modules.py scans src/modules/optional/ for a directory <Name>/ holding <Name>.h and generates $BUILD_DIR/OptionalModules.h with an include and a setup<Name>() call for each, which Modules.cpp picks up through __has_include. The directory does not exist in a stock checkout, so a stock build generates a header that defines nothing, OPTIONAL_MODULES_SETUP compiles away, and nothing is registered. Sources under the directory are already covered by the default recursive build_src_filter, so dropping a module in needs no platformio.ini edit.
* Address review: skip a module directory that is not a usable identifier
The directory name becomes a setup<Name>() call, so foo-bar/ would have generated setupfoo-bar() and failed to compile with the error pointing at generated code rather than at the directory. Names that cannot form an identifier are now skipped with a message that names the directory.
* fix(sx126x): allow boards to opt out of the PA optimization table
Boards driving an external PA can define SX126X_NO_POWER_OPTIMIZATION_TABLE
to use the fixed PA config instead of RadioLib's table, which is tuned for a
bare SX126x.
Default behaviour is unchanged. init() applies the fixed config after begin(),
which programs power through the table.
* feat(variants): add Seeed Wio Tracker L1 Pro 1W
nRF52840 + SX1262 with a 1 W external PA, L76K GNSS, SH1106 OLED.
Uses hw_model 144 (meshtastic/protobufs#1038), opts into
SX126X_NO_POWER_OPTIMIZATION_TABLE and declares SX126X_MAX_POWER explicitly.
The PA gain table is indexed by SX1262 output power in dBm.
Requires protobufs#1038 and a protobuf regen before it builds.
* chore(deps): bump RadioLib to 510e00cf
Carries the current LR11x0 and LR2021 fixes.
* fix(variants): correct L1 Pro 1W QSPI pins and clean up comments
PIN_QSPI_* are logical pin indices. The QSPI flash sits at D19-D24 in
variant.cpp, but the defines carried D21-D26 from seeed_solar_node, where
that block does start at D21. D25 and D26 are trackball pins.
Also replaces mis-encoded characters in the pin comments and drops the
migration note, which referenced a private repo path and a stale PINS_COUNT.
* fix(variants): move L1 Pro 1W out of the per-PR build matrix
board_level = pr is the high-attention tier that builds on every PR. This
board belongs with the mainline set, which uses board_level = release.
* Add AS3935 lightning sensor support
Implements meshtastic/firmware#10774: an AS3935Sensor (TelemetrySensor
subclass) that reports lightning_strike_count_1h and lightning_distance_km
on the normal environment telemetry interval, like a rain gauge -
strikes are counted over a fixed rolling ~1h window and read
non-destructively, so replying to a peer's telemetry request in
between broadcasts can't silently drop counted strikes.
The AS3935's IRQ pin (opt-in per board via AS3935_IRQ) is polled with a
plain digitalRead() in runOnce(), deliberately not attachInterrupt():
the IRQ line is a level that stays asserted until its interrupt
register is read, so polling can't miss an event regardless of timing,
matching the SparkFun library's own reference examples. An interrupt
would also buy nothing here even setting that aside - classification
requires an I2C read (readInterruptReg(), which itself calls delay(2)
per the datasheet's settle-time requirement), and blocking I2C/delay()
calls aren't safe from ISR context on any of this codebase's target
platforms, so the ISR could only ever set a flag for later draining -
no less work than just polling the pin directly on the next tick.
A genuine lightning classification also requests an immediate
out-of-cycle send via a new EnvironmentTelemetryModule::
requestImmediateSend() hook. There's no fixed debounce on the request
itself - EnvironmentTelemetryModule's existing airtime/duty-cycle gate
already paces every send, so it sends as often as airtime allows rather
than an arbitrary fixed rate. The request does expire after 5 minutes
unfulfilled, so it can't fire an arbitrarily stale broadcast if airtime
was blocked for a long stretch.
The AS3935's I2C addresses (0x01-0x03) fall inside the range this
codebase's I2C scanner otherwise skips as reserved, so detection is a
small dedicated probe gated behind AS3935_IRQ and respecting the
caller's address filter, rather than a change to the general scan
loop. Presence is confirmed via a register write/readback round-trip
rather than a fixed expected value, since the AS3935 has no WHOAMI
register and a power-on-reset-only check can't survive a warm reboot
that doesn't power-cycle the sensor (initDevice() permanently rewrites
that register on first configuration).
Generated files under src/mesh/generated/ are intentionally excluded
from this commit - they're regenerated from the protobufs submodule by
update_protobufs.yml, and hand edits get overwritten and conflict once
the companion protobufs PR merges and the submodule pointer updates.
Assisted-by: Claude Sonnet 5 <noreply@anthropic.com>
Signed-off-by: Andrew Yong <me@ndoo.sg>
* fix(as3935): calibration and telemetry logging
initDevice() never called the library's calibrateOsc(). The AS3935's
internal oscillators are calibrated against the antenna's resonance,
which the AFE/watchdog/spike-rejection thresholds depend on; without
it, only a directly-driven IRQ pin (bypassing detection entirely)
reacted during testing.
The sensor could already have a historical detection event latching
the IRQ pin high before our initialization. Added an explicit drain
read after the IRQ pin is configured, so the sensor doesn't start out
stuck asserting IRQ.
EnvironmentTelemetryModule::sendTelemetry() logs every other
environment metric category on send but was missing lightning; added
a matching log line.
Assisted-by: Claude Sonnet 5 <noreply@anthropic.com>
Signed-off-by: Andrew Yong <me@ndoo.sg>
* Support AS3935 without an IRQ line, make the antenna trim configurable
Detection no longer requires AS3935_IRQ. The probe is gated like the
other environmental sensors, so an I2C-only breakout is found on any
board. Where AS3935_IRQ is defined the pin still gates the I2C read,
otherwise runOnce() polls the interrupt register, which latches until
read.
Antenna tuning capacitance moves to
AdminMessage.sensor_config.as3935_config, persisted to /prefs/as3935.dat
and defaulting to 96pF. The chip does not retain it across power loss.
Disturbers are masked in the chip, since runOnce() now polls every
second. The lightning telemetry log is guarded so nodes without the
sensor no longer log it on every send.
Requires meshtastic/protobufs#981.
* Revert protobufs pointer to the develop baseline
The submodule bump conflicts on merge and the generated headers come
from an out of band CI job, so the pointer moves with that job rather
than in this branch.
* Report lightning strikes over a true rolling hour
strikeCountWindow was zeroed on a fixed interval, so
lightning_strike_count_1h reported strikes since the last reset rather
than over the preceding hour.
RollingCounter is a fixed memory sliding window: one counter per bucket,
nothing stored per event, so a storm cannot grow it. The ring holds one
bucket more than the window needs so none is recycled while part of it
is still inside, and the oldest bucket contributes only the fraction
still in range. Both are needed to hold the span at exactly the window
length rather than letting it drift by a bucket either way.
Expiry is exact to one bucket rather than to the event, which is below
the 5 minute floor on mesh telemetry sends.
The distance expires with the last strike in the window instead of on
the interval reset. Covered by test/test_rolling_counter.
* Widen the RollingCounter edge weighting to 64 bit
counts * inWindow is a 32 bit product, so a bucket holding more than
2^32 / BucketMs events wraps. At a 5 minute width that is about 14k: a
bucket of 50000 reported 11367 instead of 40000 once it reached the
window edge.
Below the threshold nothing changes, so lightning was unaffected, but
the helper is meant to be reused by counters with far higher rates.
test_large_burst_at_window_edge covers it. The existing burst test
sampled only inside the window, where the bucket is whole and never
weighted.
* Trim RollingCounter comments to the house limit
---------
Signed-off-by: Andrew Yong <me@ndoo.sg>
Co-authored-by: Thomas Göttgens <tgoettgens@gmail.com>
* Remove proprietary Bosch BSEC blob; open in-tree IAQ estimator for BME680
BSEC2 cost ~37-39 KB flash and ~4-5 KB static RAM on ~190 of ~240 build
targets, linked whether or not a BME680 was attached, and was a no-source
proprietary archive inside GPLv3 release binaries. The firmware consumed
exactly one BSEC-exclusive output: the IAQ value.
- New BME680IaqEstimator: clean-room log-domain baseline tracker
(humidity-compensated gas resistance vs a rise-fast/decay-slow ceiling,
0-500 scale matching the existing UI bands), pure math, unit-tested on
native (test_bme680_iaq, 15 tests incl. a deep-sleep reboot simulation).
Warm-up/burn-in progress persists to /prefs/bme680.dat via SafeFile so
one-sample-per-wake SENSOR nodes converge across reboots; stale
/prefs/bsec.dat is removed once.
- BME680Sensor: single-path rewrite on Adafruit_BME680 with async
once-per-minute sampling (~20x lower heater duty than BSEC LP mode),
a hard 2-minute publish-freshness bound (a dead sensor stops reporting
instead of freezing its last reading on the wire), and suppression of
bogus gas_resistance=0 points from heater-unstable cycles.
- platformio.ini: environmental_extra_common/_extra/_no_bsec collapsed
into one section; Bosch BSEC2 + BME68x deps deleted; per-variant BSEC
link-path hacks and the TEMPORARY promicro lib_ignore removed.
nrf52_promicro_diy_tcxo regains BME680 support at 36 KB clear of the
warm-store cap; rak4631 lands at 75 KB clear.
- EnvironmentTelemetry: iaq rendering gates on has_iaq (a genuine IAQ of
0 now displays); stale BSEC comments rewritten.
- rak4631 size budgets tightened (113000->108000 RAM, 786000->746000
flash) to lock in the reclaimed headroom.
- bin/bme680_iaq_replay.cpp: host-side replay harness for tuning the
estimator against captured BSEC traces (mean abs error + band
agreement), no reflashing needed.
Measured (develop -> this branch): rak4631 -38.8 KB flash / -4.9 KB RAM;
heltec-v3 -36.4 KB / -4.0 KB; tlora-v2-1-1_6 +1.3 KB (its IAQ
approximation had been dead code since #9663 due to an inverted isfinite
check and now actually runs).
Note: gas_resistance stays kOhm on the wire for fleet compatibility; the
proto comment claiming MOhm gets a separate meshtastic/protobufs docs PR.
* Address CodeRabbit review feedback
- Use Throttle::isWithinTimespanMs for all elapsed-time predicates in
BME680Sensor per coding guidelines (deadline math for the async reading
completion stays raw, as it targets an absolute timestamp)
- Make the state file name members static constexpr
- Replay tool: cast uint16_t before %u (default argument promotion), report
malformed input lines instead of silently skipping, and fail non-zero on
stream read errors
* Address CodeRabbit nitpicks
- Replace the local clampf helper with std::clamp (meshUtils.h's clamp drags
in Arduino.h, which would break the estimator's standalone host build that
the replay harness depends on)
- Trim the replay tool's file header to a two-line summary; the full build,
capture, and tuning workflow moves to docs/bme680_iaq_replay.md
* Add ADS1X15 class
* Initialize bus and address in ADS1X15Sensor
* Initialize member variables and pass bus to ads1x15 begin object
* More register values options for ADS1X15
* Mark constructor as explicit
* Move ADS1X15 from PowerTelemetry to EnvironmentTelemetry:
* Adds ADS to Environment Telemetry
* Adds possibility to use template function with multiple devices of the same type on the same bus with different addresses
* Moves moduleConfig dev overrides to i2cScan function
* Make logs in env telemetry only show what has been collected
* Fix channel naming and logging
* Remove scannerToSensorsMap for ADS1X15
* Fix ADS1X15 reclock
* Fix merge
* Trunk format issue
* Set port
* Remove status overrride
* Return status on boot
* Set data rate as per coderabbit request.
* Add define for ADS type and use it to set SPS
* Add object based on define
---------
Co-authored-by: Thomas Göttgens <tgoettgens@gmail.com>
* 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
* Add multi-sensor measurements for one-wire sensors using wildcard message
* Move to unpacked measurements in one-wire
* Add admin command to set main temperature in 8-channel one-wire bridge.
* Fix merge ref
* Trunk fmt
* Remove unused variable
---------
Co-authored-by: Thomas Göttgens <tgoettgens@gmail.com>
* 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>
* 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>
* 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.
* 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>
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>
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.
* Seeed Tracker X1 Support
* silence CPPCheck
* fix macro order regression and adapt stm32hal for new radiolib
* STM32 is a radiolib upstream fix ( https://github.com/jgromes/RadioLib/issues/1825 )
* fix STM32 regression
* Seeed Tracker X1 Support
* silence CPPCheck
* fix macro order regression and adapt stm32hal for new radiolib
* STM32 is a radiolib upstream fix ( https://github.com/jgromes/RadioLib/issues/1825 )
* fix STM32 regression
* address copilot OCD
* Split behavior only when the two LEDs are on distinct pins.
* bring naming in line with the other seeed devices
* update env name to fit convention too
* guarantee evaluation order
* Guard sensor readings against null values, make sensor use less power
Reconciles develop to master's latest renovate values for deps bumped on the
2.7 line but never back-merged, so the develop->master 2.8 promotion (#10777)
doesn't regress them. Done as a value reconcile, not a cherry-pick: several
master commits are superseded (5 device-ui bumps, 2 ststm32 bumps), and
develop's stale archive/refs/tags/ URL form actually blocked renovate from
bumping these (the regex expects archive/<version>.zip).
GitHub Actions:
- actions/checkout v6 -> v7 (35 refs)
- actions/cache v5 -> v6
- actions/github-script v8 -> v9 (3 refs)
- actions/stale v10.2.0 -> v10.3.0
Build / platform:
- alpine 3.23 -> 3.24 (alpine.Dockerfile)
- platformio/ststm32 19.5.0 -> 19.7.0
- platformio/nordicnrf52 10.11.0 -> 10.12.0
Libraries:
- Adafruit SSD1306 2.5.16 -> 2.5.17
- SparkFun MMC5983MA v1.1.4 -> v1.1.5
- Sensirion I2C SCD30 1.0.0 -> 1.1.1
- meshtastic esp8266-oled-ssd1306 6bfd1f1 -> 2e26010
Deliberately excluded:
- meshtastic/device-ui digest: coupled to firmware protobuf/API and develop has
diverged hard (NodeDB v25); left for a separate maintainer bump + visual check.
- libpax: develop uses the mverch67 fork (pinned by Arduino-3.x migration #9122),
master uses dbinfrago -- a fork divergence, not a version bump; not reconciled.
- platform-native digest: develop already at 61067ac (equal to master).