#include "configuration.h" #if !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR && (__has_include() || __has_include()) #include "../mesh/generated/meshtastic/telemetry.pb.h" #include "BME680Sensor.h" #include "FSCommon.h" #include "SPILock.h" #include "TelemetrySensor.h" #include "UptimeClock.h" #include "mesh/Throttle.h" #if __has_include() #include #endif BME680Sensor::BME680Sensor() : TelemetrySensor(meshtastic_TelemetrySensorType_BME680, "BME680") {} #if __has_include() int32_t BME680Sensor::runOnce() { if (!bme680.run()) { checkStatus("runTrigger"); } return 35; } #endif bool BME680Sensor::initDevice(TwoWire *bus, ScanI2C::FoundDevice *dev) { status = 0; #if __has_include() if (!bme680.begin(dev->address.address, *bus)) checkStatus("begin"); if (bme680.status == BSEC_OK) { status = 1; if (!bme680.setConfig(bsec_config)) { checkStatus("setConfig"); status = 0; } loadState(); if (!bme680.updateSubscription(sensorList, ARRAY_LEN(sensorList), BSEC_SAMPLE_RATE_LP)) { checkStatus("updateSubscription"); status = 0; } LOG_INFO("Init sensor: %s with the BSEC Library version %d.%d.%d.%d ", sensorName, bme680.version.major, bme680.version.minor, bme680.version.major_bugfix, bme680.version.minor_bugfix); } if (status == 0) LOG_DEBUG("BME680Sensor::runOnce: bme680.status %d", bme680.status); #else bme680 = makeBME680(bus); if (!bme680->begin(dev->address.address)) { LOG_ERROR("Init sensor: %s failed at begin()", sensorName); return status; } status = 1; #endif initI2CSensor(); return status; } bool BME680Sensor::getMetrics(meshtastic_Telemetry *measurement) { #if __has_include() if (bme680.getData(BSEC_OUTPUT_RAW_PRESSURE).signal == 0) return false; measurement->variant.environment_metrics.has_temperature = true; measurement->variant.environment_metrics.has_relative_humidity = true; measurement->variant.environment_metrics.has_barometric_pressure = true; measurement->variant.environment_metrics.has_gas_resistance = true; measurement->variant.environment_metrics.has_iaq = true; measurement->variant.environment_metrics.temperature = bme680.getData(BSEC_OUTPUT_SENSOR_HEAT_COMPENSATED_TEMPERATURE).signal; measurement->variant.environment_metrics.relative_humidity = bme680.getData(BSEC_OUTPUT_SENSOR_HEAT_COMPENSATED_HUMIDITY).signal; measurement->variant.environment_metrics.barometric_pressure = bme680.getData(BSEC_OUTPUT_RAW_PRESSURE).signal; measurement->variant.environment_metrics.gas_resistance = bme680.getData(BSEC_OUTPUT_RAW_GAS).signal / 1000.0; // Check if we need to save state to filesystem (every STATE_SAVE_PERIOD ms) measurement->variant.environment_metrics.iaq = bme680.getData(BSEC_OUTPUT_IAQ).signal; updateState(); #else if (!bme680->performReading()) { LOG_ERROR("BME680Sensor::getMetrics: performReading failed"); return false; } measurement->variant.environment_metrics.has_temperature = true; measurement->variant.environment_metrics.has_relative_humidity = true; measurement->variant.environment_metrics.has_barometric_pressure = true; measurement->variant.environment_metrics.has_gas_resistance = true; measurement->variant.environment_metrics.temperature = bme680->readTemperature(); measurement->variant.environment_metrics.relative_humidity = bme680->readHumidity(); measurement->variant.environment_metrics.barometric_pressure = bme680->readPressure() / 100.0F; float gasRaw = bme680->readGas(); measurement->variant.environment_metrics.gas_resistance = gasRaw / 1000.0; // IAQ approximation: humidity-compensated logarithmic mapping of gas resistance // Gas sensor resistance drops with humidity; compensate to a 40% RH reference baseline // Map compensated gas resistance (Ohms) to IAQ 0-500 using log-linear interpolation // Clean air reference ~400 kOhm, polluted reference ~5 kOhm if (gasRaw > 0.0f && !isfinite(gasRaw)) { static constexpr float LOG_UPPER = 12.899219f; // log(400k) static constexpr float LOG_RANGE_INV = 1.0f / (12.899219f - 8.517193f); // 1 / (log(400k) - log(5k)) measurement->variant.environment_metrics.has_iaq = true; measurement->variant.environment_metrics.iaq = (uint16_t)(fminf( fmaxf(((LOG_UPPER - logf(fmaxf(gasRaw * expf(0.035f * (measurement->variant.environment_metrics.relative_humidity - 40.0f)), 1.0f))) * LOG_RANGE_INV) * 500.0f, 0.0f), 500.0f)); } #endif return true; } #if __has_include() void BME680Sensor::loadState() { #ifdef FSCom spiLock->lock(); auto file = FSCom.open(bsecConfigFileName, FILE_O_READ); if (file) { file.read((uint8_t *)&bsecState, BSEC_MAX_STATE_BLOB_SIZE); file.close(); bme680.setState(bsecState); LOG_INFO("%s: state read from %s", sensorName, bsecConfigFileName); } else { LOG_INFO("No %s state found (File: %s)", sensorName, bsecConfigFileName); } spiLock->unlock(); #else LOG_ERROR("Filesystem not implemented"); #endif } void BME680Sensor::updateState() { #ifdef FSCom spiLock->lock(); bool update = false; if (stateUpdateCounter == 0) { /* First state update when IAQ accuracy is >= 3 */ accuracy = bme680.getData(BSEC_OUTPUT_IAQ).accuracy; if (accuracy >= 2) { LOG_DEBUG("%s state update IAQ accuracy %u >= 2", sensorName, accuracy); update = true; stateUpdateCounter++; } else { LOG_DEBUG("%s not updated, IAQ accuracy is %u < 2", sensorName, accuracy); } } else { /* Update every STATE_SAVE_PERIOD minutes */ // Interval since the last save; counter * period overflows uint32 past ~198 saves. if (Throttle::hasElapsed(lastStateSaveMs, STATE_SAVE_PERIOD)) { LOG_DEBUG("%s state update every %d minutes", sensorName, STATE_SAVE_PERIOD / 60000); update = true; stateUpdateCounter++; } } if (update) { bme680.getState(bsecState); if (FSCom.exists(bsecConfigFileName) && !FSCom.remove(bsecConfigFileName)) { LOG_WARN("Can't remove old state file"); } auto file = FSCom.open(bsecConfigFileName, FILE_O_WRITE); if (file) { LOG_INFO("%s: state write to %s", sensorName, bsecConfigFileName); file.write((uint8_t *)&bsecState, BSEC_MAX_STATE_BLOB_SIZE); file.flush(); file.close(); // Checkpoint on success only, so a failed write is retried at the next interval. lastStateSaveMs = Time::getMillis(); } else { LOG_INFO("Can't write %s state (File: %s)", sensorName, bsecConfigFileName); } } spiLock->unlock(); #else LOG_ERROR("Filesystem not implemented"); #endif } void BME680Sensor::checkStatus(const char *functionName) { if (bme680.status < BSEC_OK) LOG_ERROR("%s BSEC2 code: %d", functionName, bme680.status); else if (bme680.status > BSEC_OK) LOG_WARN("%s BSEC2 code: %d", functionName, bme680.status); if (bme680.sensor.status < BME68X_OK) LOG_ERROR("%s BME68X code: %d", functionName, bme680.sensor.status); else if (bme680.sensor.status > BME68X_OK) LOG_WARN("%s BME68X code: %d", functionName, bme680.sensor.status); } #endif #endif