mirror of
https://github.com/meshtastic/firmware.git
synced 2026-09-12 22:29:00 -04:00
* 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
101 lines
2.8 KiB
C++
101 lines
2.8 KiB
C++
// Replays a captured BME680 CSV trace (gas_ohms,rh[,bsec_iaq]) through
|
|
// BME680IaqEstimator for offline tuning. See docs/bme680_iaq_replay.md.
|
|
|
|
#include "modules/Telemetry/Sensor/BME680IaqEstimator.h"
|
|
|
|
#include <cmath>
|
|
#include <cstdio>
|
|
|
|
namespace
|
|
{
|
|
// Same buckets the device UI uses (EnvironmentTelemetry drawFrame)
|
|
int band(int iaq)
|
|
{
|
|
if (iaq <= 25)
|
|
return 0; // Excellent
|
|
if (iaq <= 50)
|
|
return 1; // Good
|
|
if (iaq <= 100)
|
|
return 2; // Moderate
|
|
if (iaq <= 150)
|
|
return 3; // Poor
|
|
if (iaq <= 200)
|
|
return 4; // Unhealthy
|
|
if (iaq <= 300)
|
|
return 5; // Very Unhealthy
|
|
return 6; // Hazardous
|
|
}
|
|
} // namespace
|
|
|
|
int main(int argc, char **argv)
|
|
{
|
|
FILE *in = stdin;
|
|
if (argc > 1) {
|
|
in = fopen(argv[1], "r");
|
|
if (!in) {
|
|
fprintf(stderr, "cannot open %s\n", argv[1]);
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
BME680IaqEstimator est;
|
|
char line[256];
|
|
long lineNo = 0, n = 0, skipped = 0, produced = 0, compared = 0, bandHits = 0;
|
|
double absErrSum = 0;
|
|
|
|
printf("n,gas_ohms,rh,est_iaq,bsec_iaq\n");
|
|
while (fgets(line, sizeof(line), in)) {
|
|
lineNo++;
|
|
if (line[0] == '#' || line[0] == '\n')
|
|
continue;
|
|
float gas, rh, bsec = NAN;
|
|
int fields = sscanf(line, "%f,%f,%f", &gas, &rh, &bsec);
|
|
if (fields < 2) {
|
|
// Tolerate one header row silently; anything else malformed is
|
|
// reported so a damaged trace can't produce a quiet, biased summary
|
|
if (lineNo > 1) {
|
|
skipped++;
|
|
fprintf(stderr, "skipping malformed line %ld: %s", lineNo, line);
|
|
}
|
|
continue;
|
|
}
|
|
n++;
|
|
uint16_t iaq;
|
|
bool got = est.update(gas, rh, &iaq);
|
|
bool haveBsec = fields >= 3 && std::isfinite(bsec);
|
|
|
|
printf("%ld,%.0f,%.2f,", n, gas, rh);
|
|
if (got)
|
|
printf("%u", (unsigned)iaq);
|
|
if (haveBsec)
|
|
printf(",%.0f\n", bsec);
|
|
else
|
|
printf(",\n");
|
|
|
|
if (got) {
|
|
produced++;
|
|
if (haveBsec) {
|
|
compared++;
|
|
absErrSum += std::fabs((double)iaq - (double)bsec);
|
|
if (band(iaq) == band((int)std::lround(bsec)))
|
|
bandHits++;
|
|
}
|
|
}
|
|
}
|
|
if (ferror(in)) {
|
|
fprintf(stderr, "input read error at line %ld\n", lineNo);
|
|
if (in != stdin)
|
|
fclose(in);
|
|
return 1;
|
|
}
|
|
|
|
fprintf(stderr, "samples: %ld, estimator outputs: %ld, malformed lines skipped: %ld\n", n, produced, skipped);
|
|
if (compared) {
|
|
fprintf(stderr, "vs BSEC (%ld comparable): mean abs error %.1f IAQ points, band agreement %.1f%%\n", compared,
|
|
absErrSum / compared, 100.0 * bandHits / compared);
|
|
}
|
|
if (in != stdin)
|
|
fclose(in);
|
|
return 0;
|
|
}
|