* 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
* logging: gate LOG_TRACE behind MESHTASTIC_TRACE_LOGGING, drop redundant reclock logs
LOG_TRACE now compiles out by default so trace-level diagnostics cost no
flash; enable with -DMESHTASTIC_TRACE_LOGGING. Portduino keeps it on for
the traceFilename packet-trace feature.
Remove the 66 caller-side I2C reclock/restore log lines in the telemetry
sensors: ReClockI2C::setClock/restoreClock already log both frequencies
internally (now at trace level, since they fire every sensor read).
Also unify near-duplicate literals (colon/case/punctuation variants) so
linker string dedup applies, and drop an information-free bare 'done'.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LBiZc9sfPrH1MZ2L3Fxgt1
* logging: demote chatty per-packet/per-poll DEBUG lines to trace level
With LOG_TRACE compiled out by default, per-iteration chatter (packet
bookkeeping, sensor poll values, e-ink refresh reasons, GPS pin states,
UI runState traces) now costs no flash on device builds while remaining
one -DMESHTASTIC_TRACE_LOGGING away. 108 lines demoted, 4 information-
free lines removed; failure paths, drop reasons, and one-time init logs
all stay at debug level.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LBiZc9sfPrH1MZ2L3Fxgt1
* logging: address CodeRabbit review on trace-gate PR
- GPS: pass serial-derived buffers as %s args, never as format strings
(untrusted bytes could contain % directives)
- 0x%08x for packet id / NodeNum per convention (Router, CannedMessage,
NeighborInfo); unsigned casts for size_t args; %u for uint32_t delta
- EInk: async full-refresh begin/complete back to DEBUG (rare state
transitions); per-frame SKIPPED lines stay trace
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LBiZc9sfPrH1MZ2L3Fxgt1
* logging: gate trace on the flag's value, not its presence
-DMESHTASTIC_TRACE_LOGGING=0 previously *enabled* trace logging because
the gate tested definedness. The flag now defaults per-platform
(portduino 1, else 0) and both backends test the value, so =0 disables,
=1 or a bare -D enables. Also cast tx_after-millis() to uint32_t for %u
(millis() is unsigned long on native).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LBiZc9sfPrH1MZ2L3Fxgt1
* logging: clang-format rewrap after specifier widening
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LBiZc9sfPrH1MZ2L3Fxgt1
* Even fewer bytes!
* logging: keep compile-gated debug lines at debug level; fix native-suite-count
Lines already inside default-off #ifdef blocks (GPS_DEBUG,
DEBUG_LOOP_TIMING) cost no flash and should stay visible at debug level
when their gate is enabled, rather than also requiring
MESHTASTIC_TRACE_LOGGING.
test/native-suite-count lags the two test_event_channel_* suites added
by #11045 (develop's Native Suite Count check has the same mismatch);
bump 46 -> 47.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LBiZc9sfPrH1MZ2L3Fxgt1
* gps: route GPS_DEBUG diagnostics through a LOG_DEBUG_GPS() macro (#11414)
Replaces 27 log-only #ifdef GPS_DEBUG blocks across GPS.cpp,
PositionModule, MeshService, and GPSStatus.h with a single-line
LOG_DEBUG_GPS() call (src/gps/GPSLog.h, modeled on LOG_MIGRATION:
value-gated, ((void)0) when off). Blocks containing declarations,
control flow, hexDump, or nested conditionals keep an explicit
'#if GPS_DEBUG' guard. RTC.cpp's per-reading raw time dumps and
per-candidate rejection chatter fold under the same gate; quality
transitions and boot-time seeding stay at debug.
Also fixes the '// define GPS_DEBUG' missing-# typo in two variant
headers and updates all seven commented examples to the value form
('#define GPS_DEBUG 1') required by the value-based gate.
Claude-Session: https://claude.ai/code/session_01LBiZc9sfPrH1MZ2L3Fxgt1
Co-authored-by: Claude <noreply@anthropic.com>
* gps: declare RTC gmtime result as pointer to const (cppcheck)
With the setTime debug dump gated behind GPS_DEBUG, all remaining uses
of t are reads; cppcheck (constVariablePointer) now flags it.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LBiZc9sfPrH1MZ2L3Fxgt1
---------
Co-authored-by: Claude <noreply@anthropic.com>
* nrf52: drop BSEC2/BME68x from the three targets nearest the flash cap
nrf52_promicro_diy_tcxo has been failing the nrf52_warm_region guard on
develop since 2026-08-05 (image ends at 0xEA918, 2328 B past the 0xEA000
warm-store cap). rak4631 and rak_wismesh_repeater_mini are ~14 KiB clear
while the nrf52 fleet drifts ~2 KiB/day, so both are close behind.
lib_ignore bsec2 + BME68x Sensor library defeats BME680Sensor's
__has_include(<bsec2.h>) gate, recovering ~46 KiB on each:
nrf52_promicro_diy_tcxo 0xEA918 (FAILED) -> 0xDF1A8, 43.6 KiB clear
rak4631 0xE6550 -> 0xDADE8, 60.0 KiB clear
rak_wismesh_repeater_mini 0xE65AC -> 0xDAE64, 59.9 KiB clear
This costs BME680/BME688 support, i.e. RAK1906 on WisBlock. It is a
temporary measure until the environmental sensor roster is opt-in per
board rather than linked into every target.
rak_wismesh_pocket inherits from env:rak4631 but has ~37 KiB clear, so it
is explicitly held at the nrf52 default and keeps BME680. Both lib_ignore
lists extend ${nrf52_base.lib_ignore} so BluetoothOTA and lvgl stay
ignored.
* nrf52: give the RAK boards BME680 back, drop their Ethernet instead
Excluding BSEC2 on rak4631 cost BME680/BME688 support, i.e. the RAK1906
WisBlock module - a common peripheral on the most popular nrf52 board. The
Ethernet stack is the better thing to lose: RAK13800 W5100S users are served
by the dedicated env:rak4631_eth_gw build, which has its own variant.h and
~163 KB clear, so the general-purpose image was carrying a networking stack
for hardware almost none of its users have.
rak_wismesh_pocket already set HAS_ETHERNET 0 for the same reason, so this
follows an existing in-tree precedent rather than inventing one.
rak4631 0xE6578 -> 0xE0D08, 35.7 KB clear (-21.6 KB)
rak_wismesh_repeater_mini -> 0xE0D58, 35.7 KB clear
rak4631_eth_gw -> 0xC1050, 163 KB clear (unchanged)
rak4631_eth_gw_dbg -> 0xC1210, 163 KB clear
That also brings rak4631 back under its declared flash budget in
bin/ram_budgets.json (766,216 vs 786,000 bytes), which develop is currently
failing - size-budget-gate is what has been ejecting PRs from the merge queue.
mqtt/ is deliberately kept: src/mqtt/MQTT.cpp carries both the networked
client (gated on HAS_NETWORKING, now 0) and the phone client-proxy path, so
dropping the directory would have removed MQTT-over-phone as well.
nrf52_promicro_diy_tcxo keeps the BSEC2 exclusion from the previous commit.
It has no Ethernet to reclaim - nrf52_base already filters mesh/eth, mesh/api
and mesh/wifi, and HAS_ETHERNET defaults to 0 - so the sensor library is the
only lever there. It goes 0xEA918 (FAILED) -> 0xDF1C0, 43.6 KB clear.
env:rak4631_eth_gw_dbg now extends env:rak4631_eth_gw rather than env:rak4631:
it already used the gateway env's build_flags and lib_deps, and only that env
still compiles mesh/eth + mesh/api.
* Address CodeRabbit review comments on PR #11363
- Condense the HAS_ETHERNET comment in rak4631/variant.h to one line
- Clarify rak4631/platformio.ini src-filter comment: Ethernet-free,
not networking-free, since MQTT is retained
- Restore custom_meshtastic_* metadata on env:rak4631_eth_gw so
env:rak4631_eth_gw_dbg (which now extends it) doesn't lose the
hw_model/display metadata it previously inherited via env:rak4631
---------
Co-authored-by: Jason P <applewiz@mac.com>
Co-authored-by: Claude <noreply@anthropic.com>
* 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>
* 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>
nrf52_promicro_diy_tcxo has been failing the nrf52 warm-region guard on
develop: the image ends at 0xEA0C0, 192 bytes past the 12 KB WarmNodeStore
record-ring reserved at 0xEA000. This variant compiles four radio driver
families (SX126x/LLCC68, SX127x/RF95, LR11x0/LR1121, LR2021) so any module
can be soldered on, which makes it the largest nrf52 image we ship.
Building it with -D EXCLUDE_EMOJI saves 6,808 bytes and puts the image at
0xE8618, 6.6 KB clear of the warm region.
EXCLUDE_EMOJI was not previously usable: graphics::emotes[] becomes empty,
but the canned-message emote picker never checked for that. Entering the
picker clamped emotePickerIndex to numEmotes - 1 (i.e. -1), and selecting
read emotes[-1].label into a String - an out-of-bounds read of whatever
precedes the array in flash. Guard both entry points instead: refuse to
open the picker when there are no emotes, and bounce back to freetext if
the picker state is somehow reached anyway.
Under whole-image LTO those guards fold to constants, so the picker draw
and input paths dead-strip entirely on builds that set EXCLUDE_EMOJI -
which is where the savings come from beyond the bitmap data itself.
Received messages containing emoji render as text on this variant, and
the emote-list key is a no-op. Verified rak4631 (emoji enabled) is
unaffected: 0xE46D8, 22 KB clear.
Add 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.
* add noise floor
* Sliding window noise floor
* Add getCurrentRSSI() to SimRadio for noise floor support
* Remove sendLocalStatsToPhone call from runOnce
* Change noise floor to int32_t type
* Use int32_t for RSSI sample storage in noise floor
* Remove float cast from noise floor assignment
* Fix Copilot review issues: fix noise floor logic, types, and null pointer
- Use robust busyTx/busyRx checks instead of simple isReceiving check
- Initialize noiseFloorSamples to NOISE_FLOOR_MIN instead of 0
- Move noise_floor assignment inside null check to prevent potential crash
- Change getNoiseFloor() and getAverageNoiseFloor() to return int32_t
- Fix RSSI validation to check for positive values (rssi > 0)
- Fix format specifier from %.1f to %d for int32_t
- Update comments to accurately reflect the sampling logic
* Fix RSSI condition to include zero value
* Change noise floor initialization to zero
* Disable noise floor for LR11x0 chips: getRSSI(bool) unsupported
* Remove updateNoiseFloor call from onNotify to avoid radio queue overflow
Per PR review feedback, calling updateNoiseFloor() in onNotify() for every
ISR event (ISR_TX, ISR_RX, TRANSMIT_DELAY_COMPLETED) can cause the LoRa
radio queue to get full. The noise floor sampling still happens in
startReceive() and after transmitting.
* fix lr11x0 current rssi
* Address noise floor review comments
* Address Copilot SimRadio noise floor comments
* Fix RadioLibInterface formatting
---------
Co-authored-by: Ben Meadors <benmmeadors@gmail.com>
Co-authored-by: Jonathan Bennett <jbennett@incomsystems.biz>
* LR2021 radio on NRF_Promicro
Co-authored-by: Copilot <copilot@github.com>
* Refactor LR2021 interface includes and conditional compilation for improved clarity
Co-authored-by: Copilot <copilot@github.com>
* Refactor LR20x0 interface: remove unused includes and update comments for clarity
* Fix LR2021 max power definitions and add radio type detection tests
* remove potato radio type detection tests
* Include placeholder for DCDC - currently requires godmode
* Added godmode features - not enabled by default
---------
Co-authored-by: Copilot <copilot@github.com>
Co-authored-by: Ben Meadors <benmmeadors@gmail.com>
* Remove noop CANNED_MESSAGE_MODULE_ENABLE define
* Remove over-eager warning removal
* Remove unused LED_CONN
* Dead defines removal
* Rename oddball LED pin name
* Rename second oddball LED pin name
* Remove another dead define
* Just set LED_BUILTIN universally to -1, as we don't use it.
* LUD_BUILTIN workarounds
* Squash the LED_BUILTINs that sneaked in
* Don't kill valid pin derfine
Thus PR updates extra_scripts definition.
Current env.extra_scripts don't triggers `extra_scripts/nrf52_extra.py` and user have to convert hex to uf2 manually.
* Migrate all of the Meshtastic API attributes into the ini as a source of truth
* Cleanup garbage coalescing
* Another spot
* We already account for inkhud and mui
* Consolidate
* Removed them
* Boogers
* Infer
* Copying manifest should always succeed
* Remove portduino guards
* Rename
* None
I thought git would be smart enough to understand all the whitespace changes but even with all the flags I know to make it ignore theses it still blows up if there are identical changes on both sides.
I have a solution but it require creating a new commit at the merge base for each conflicting PR and merging it into develop.
I don't think blowing up all PRs is worth for now, maybe if we can coordinate this for V3 let's say.
This reverts commit 0d11331d18.
* fix on nrf52_promicro
* try fix for GPS issue
* fix GPS pin assignment in variant.h
* cleared up some comments and confirmed pinouts from schematics
---------
Co-authored-by: macvenez <macvenez@gmail.com>
Remove lib_deps section for all PlatformIO envs which are unneeded (only references the `extends` lib_deps, thus pointless)
This makes the configs more concise and make future PIO variants/ libdeps audits easier.
* changes of variants/nrf52840/nrf52.ini and variants/nrf52840/rak4631/platformio.ini
* try for nrf52 size reduction, faketec exclude unused radios and meshlink refactor
* can't exclude LR11X0 as in schematic there's option for LR1121
* remove -Map flag and -Wl
* removed spaces causing error
---------
Co-authored-by: macvenez <macvenez@gmail.com>
* Delete variants/nrf52840/diy/nrf52_promicro_diy_tcxo/Schematic_Pro-Micro_Pinouts.pdf
remove old file
* Add updated schematic
* Update GPS TX and RX pin definitions after swap
* Update GPS pin definitions and schematic link
Updated the schematic link to reflect GPS pin definition changes.
* Update to Pro-micro variants
Schematic updated
Xtal variant removed
Extra module added to list
Extra explanation added to readme.
* Fix markdown formatting in readme.md
* Fix formatting in readme.md for RF switch section
---------
Co-authored-by: Tom <116762865+Nestpebble@users.noreply.github.com>