Remove proprietary Bosch BSEC blob; open in-tree IAQ estimator for BME680 (#11381)

* 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
This commit is contained in:
Ben Meadors
2026-08-13 13:21:16 -04:00
committed by GitHub
parent 3e71c679c1
commit b565a07a83
15 changed files with 927 additions and 226 deletions
+3 -3
View File
@@ -44,15 +44,15 @@ custom_sdkconfig =
CONFIG_BT_NIMBLE_ENABLED=y
CONFIG_SPI_FLASH_SUPPORT_BOYA_CHIP=y
; Override lib_deps to use environmental_extra_no_bsec instead of environmental_extra
; BSEC library uses ~3.5KB DRAM which causes overflow on original ESP32 targets
; Overrides esp32_common's lib_deps: adds networking_extra and omits
; esp32_https_server (mesh/http is excluded from this target's build_src_filter)
lib_deps =
${arduino_base.lib_deps}
${networking_base.lib_deps}
${networking_extra.lib_deps}
${radiolib_base.lib_deps}
${environmental_base.lib_deps}
${environmental_extra_no_bsec.lib_deps}
${environmental_extra.lib_deps}
# TODO renovate
https://github.com/mverch67/libpax/archive/6f52ee989301cdabaeef00bcbf93bff55708ce2f.zip
# renovate: datasource=custom.pio depName=XPowersLib packageName=lewisxhe/library/XPowersLib
-1
View File
@@ -93,7 +93,6 @@ lib_ignore =
${esp32_common.lib_ignore}
libpax
esp8266-oled-ssd1306
bsec2
esp32_idf5_https_server
esp_driver_cam
esp_http_server
@@ -18,7 +18,6 @@ build_flags =
-DELECROW_ThinkNode_M3
-DGPS_POWER_TOGGLE
-D CONFIG_NFCT_PINS_AS_GPIOS=1
-L "${platformio.libdeps_dir}/${this.__env__}/bsec2/src/cortex-m4/fpv4-sp-d16-hard"
build_src_filter = ${nrf52_base.build_src_filter} +<../variants/nrf52840/ELECROW-ThinkNode-M3>
lib_deps =
${nrf52840_base.lib_deps}
@@ -20,18 +20,6 @@ build_flags = ${nrf52840_base.build_flags}
build_src_filter = ${nrf52_base.build_src_filter} +<../variants/nrf52840/diy/nrf52_promicro_diy_tcxo>
debug_tool = jlink
; TEMPORARY: drop BSEC2 + its BME68x driver. This image is ~2.3 KB OVER the 0xEA000
; warm-store cap and has been failing the nrf52_warm_region guard on develop since
; 2026-08-05. Unlike the RAK boards there is no Ethernet stack to reclaim here -- nrf52_base
; already filters mesh/eth, mesh/api and mesh/wifi, and HAS_ETHERNET defaults to 0 -- so the
; sensor library is what has to go. BME680Sensor is gated on __has_include(<bsec2.h>), so
; ignoring the libraries compiles it out. Revert once the environmental sensor roster is
; opt-in per board rather than linked into every target.
lib_ignore =
${nrf52_base.lib_ignore}
bsec2
BME68x Sensor library
; NRF52 ProMicro w/ E-Ink display
[env:nrf52_promicro_diy-inkhud]
board_level = extra
@@ -15,7 +15,6 @@ build_flags = ${nrf52840_base.build_flags}
-I variants/nrf52840/muzi_base
-D MUZI_BASE
-D CONFIG_NFCT_PINS_AS_GPIOS=1
-L "${platformio.libdeps_dir}/${this.__env__}/bsec2/src/cortex-m4/fpv4-sp-d16-hard"
build_src_filter = ${nrf52840_base.build_src_filter} +<../variants/nrf52840/muzi_base>
lib_deps =