/* definition of pressure; according to datasheet this is absolute barometric pressure = absolute pressure as opposed to corrected barometric pressure = corrected for altitude above sea level; corrected barometric pressure should be the value you would get at sea level; The IAQ accuracy is reflects the current state of the background calibration process, such as: ------------------------------------ https://community.bosch-sensortec.com/t5/Question-and-answers/What-does-the-IAQ-accuracy-mean-in-BSEC/qaq-p/5935 IAQ Accuracy=0 could either mean: BSEC was just started, and the sensor is stabilizing (this lasts normally 5min in LP mode or 20min in ULP mode), there was a timing violation (i.e. BSEC was called too early or too late), which should be indicated by a warning/error flag by BSEC, IAQ Accuracy=1 means the background history of BSEC is uncertain. This typically means the gas sensor data was too stable for BSEC to clearly define its references, IAQ Accuracy=2 means BSEC found a new calibration data and is currently calibrating, IAQ Accuracy=3 means BSEC calibrated successfully. ------------------------------------ https://community.bosch-sensortec.com/t5/MEMS-sensors-forum/BME680-Sensor-Life-in-Days/m-p/15919 In the integration guide we released with generic release package, such parameters are explained on Page 7: The history BSEC considers for the automatic background calibration of the IAQ in days. That means changes in this time period will influence the IAQ value. · 4days, means BSEC will consider the last 4 days of operation for the automatic background calibration. · 28days, means BSEC will consider the last 28 days of operation for the automatic background calibration. How to understand background calibration? When BME680 found better/worse gas than before, the accuracy would be 2 and it means our sensor is in calibration. Then after calibration finished, the accuracy comes to 3. From that moment, sensor is into background calibration. If you chose 4 days (28 days) as the time for background calibration, after 4 days (28 days) without any better/worse gas, the accuracy comes to 1 and this means BME680 needs gas trigger for calibration. (I think "gas trigger" means put an alcohol rag near it for a short/long? while. -- garberw) */ // #include #include #include #include #include // macros begin -------------------------------------------------------------- #define SEALEVELPRESSURE_HPA (1013.25) // Adafruit defaults #define BME_SCK 13 #define BME_MISO 12 #define BME_MOSI 11 #define BME_CS 10 // #define BME_SCK 9 // #define BME_MISO 8 // #define BME_MOSI 7 // #define BME_CS 6 #define STATE_SAVE_PERIOD UINT32_C(360 * 60 * 1000) // 360 minutes - 4 times a day // macros end -------------------------------------------------------------- // globals begin -------------------------------------------------------------- bool DISPLAY_IAQ = true; /* Configure the BSEC library with information about the sensor 18v/33v = Voltage at Vdd. 1.8V or 3.3V 3s/300s = BSEC operating mode, BSEC_SAMPLE_RATE_LP or BSEC_SAMPLE_RATE_ULP 4d/28d = Operating age of the sensor in days generic_18v_3s_4d generic_18v_3s_28d generic_18v_300s_4d generic_18v_300s_28d generic_33v_3s_4d generic_33v_3s_28d generic_33v_300s_4d generic_33v_300s_28d */ const uint8_t bsec_config_iaq[] = { #include "config/generic_33v_3s_4d/bsec_iaq.txt" }; uint8_t bsec_state[BSEC_MAX_STATE_BLOB_SIZE] = {0}; uint16_t stateUpdateCounter = 0; // adafruit part #5046; Temperature Humidity Pressure Gas Sensor BME688; // I2C ADDRESS 0x77; // I2C ADDRESS 0x76 when jumper from SDO to GND; // BME_CS SPI chip select // BME_MOSI SPI MOSI (Data from microcontroller to sensor) // BME_MISO SPI MISO (Data to microcontroller from sensor) // BME_SCK SPI Clock // SPI bitbanged Adafruit_SPIDevice spidev(BME_CS, BME_SCK, BME_MISO, BME_MOSI, 1000000, SPI_BITORDER_MSBFIRST, SPI_MODE0); Bsec iaq_sensor; // globals end -------------------------------------------------------------- void print_version(void); static int8_t spi_read(uint8_t reg_addr, uint8_t *reg_data, uint32_t len, void *intf_ptr); static int8_t spi_write(uint8_t reg_addr, const uint8_t *reg_data, uint32_t len, void *intf_ptr); static void delay_usec(uint32_t us, void *intf_ptr); bool check_iaq_sensor_status(void); float altitude(float pressure, float sea_level); String iaq_text(float iaq_score); void load_state(void); void update_state(void); void print_version(void) { Serial.println(F("BSEC library version iaq_sensor.version.xxx")); Serial.print(F("major = ")); Serial.println(iaq_sensor.version.major); Serial.print(F("minor = ")); Serial.println(iaq_sensor.version.minor); Serial.print(F("major_bugfix = ")); Serial.println(iaq_sensor.version.major_bugfix); Serial.print(F("minor_bugfix = ")); Serial.println(iaq_sensor.version.minor_bugfix); } // require Serial.begin() called first; then recommend delay(1000) or more; void IndexOfAirQuality::setup(bool display_iaq) { // we are using goto label so variable declarations have to come first in order to compile; bool res = false; bsec_virtual_sensor_t sensorList[13] = { BSEC_OUTPUT_IAQ, BSEC_OUTPUT_STATIC_IAQ, BSEC_OUTPUT_CO2_EQUIVALENT, BSEC_OUTPUT_BREATH_VOC_EQUIVALENT, BSEC_OUTPUT_RAW_TEMPERATURE, BSEC_OUTPUT_RAW_PRESSURE, BSEC_OUTPUT_RAW_HUMIDITY, BSEC_OUTPUT_RAW_GAS, BSEC_OUTPUT_STABILIZATION_STATUS, BSEC_OUTPUT_RUN_IN_STATUS, BSEC_OUTPUT_SENSOR_HEAT_COMPENSATED_TEMPERATURE, BSEC_OUTPUT_SENSOR_HEAT_COMPENSATED_HUMIDITY, BSEC_OUTPUT_GAS_PERCENTAGE }; DISPLAY_IAQ = display_iaq; // in original example this comes BEFORE Serial.begin(); // fixme // EEPROM.begin(BSEC_MAX_STATE_BLOB_SIZE + 1); if (DISPLAY_IAQ) { Serial.println(F("BME688 async test")); } // I2C // iaq_sensor.begin(BME68X_I2C_ADDR_HIGH, Wire); // SPI res = spidev.begin(); ERROR_RETURN((res), F("spidev.begin()")); iaq_sensor.begin(BME68X_SPI_INTF, &spi_read, &spi_write, delay_usec, (void *) &spidev); print_version(); res = check_iaq_sensor_status(); ERROR_RETURN((res), F("Could not find a valid BME680 sensor, check wiring!")); if (DISPLAY_IAQ) { Serial.println(F("Found a sensor")); } if (DISPLAY_IAQ) { Serial.println(F("set config")); } iaq_sensor.setConfig(bsec_config_iaq); res = check_iaq_sensor_status(); ERROR_RETURN((res), F("Could not set config")); if (DISPLAY_IAQ) { Serial.println(F("load_state")); } load_state(); if (DISPLAY_IAQ) { Serial.println(F("updateSubscription")); } iaq_sensor.updateSubscription(sensorList, 13, BSEC_SAMPLE_RATE_LP); res = check_iaq_sensor_status(); ERROR_RETURN((res), F("Could not updateSubscription")); // bme68x_dev iaq_sensor._bme68x // is private so we can not set these; that is probably on purpose; // iaq_sensor._bme68x.setTemperatureOversampling(BME680_OS_8X); // iaq_sensor._bme68x.setHumidityOversampling(BME680_OS_2X); // iaq_sensor._bme68x.setPressureOversampling(BME680_OS_4X); // iaq_sensor._bme68x.setIIRFilterSize(BME680_FILTER_SIZE_3); // iaq_sensor._bme68x.setGasHeater(320, 150); // 320 deg C for 150 msec if (DISPLAY_IAQ) { Serial.println(F("sensor setup pass")); } // fixme Serial.flush(); // weather_halt(); // alternatively // delay(120000); return; catch_block: wexception.print(); // fixme Serial.flush(); weather_halt(); } #define READ_TIMEOUT INT64_C(60000) bool IndexOfAirQuality::perform_reading(void) { bool res = false; int64_t meas_end_ms = iaq_sensor.nextCall; // measurement end; int64_t call_time_ms = iaq_sensor.getTimeMs(); // now; int64_t rem_time_ms = meas_end_ms - call_time_ms; // time left if (rem_time_ms > 0) { if (DISPLAY_IAQ) { Serial.print(F("delay rem_time_ms = ")); Serial.println(rem_time_ms); Serial.flush(); } delay(static_cast(rem_time_ms) * 2); } // true if new data is available; // this should be true since we delayed for rem_time_ms until iaq_sensor.nextCall; res = iaq_sensor.run(); if (!res) { check_iaq_sensor_status(); } ERROR_RETURN((res), F("run too early")); /* the Adafruit bme data types; float bme.temperature; // in C float bme.humidity; // in % RH (relative humidity) float bme.readAltitude(); // in meters uint32_t bme.pressure; // in Pa not hPa uint32_t bme.gas_resistance; // in Ohm not kOhm */ /* original bsec iaq_sensor data types; int8_t bme68xStatus; // Placeholder for the BME68x driver's error codes bsec_library_return_t bsecStatus; float iaq; float rawTemperature; float pressure; float rawHumidity; float gasResistance; float stabStatus; float runInStatus; float temperature; float humidity, float staticIaq; float co2Equivalent; float breathVocEquivalent; float compGasValue; float gasPercentage; uint8_t iaqAccuracy; uint8_t staticIaqAccuracy; uint8_t co2Accuracy; uint8_t breathVocAccuracy; uint8_t compGasAccuracy; uint8_t gasPercentageAccuracy; int64_t outputTimestamp; // Timestamp in ms of the output */ m_iaq_text = iaq_text(m_iaq_score); m_temperature = iaq_sensor.temperature; m_humidity = iaq_sensor.humidity; m_pressure = iaq_sensor.pressure; // bug in BSEC example; this is Pa not hPa m_gas_resistance = iaq_sensor.gasResistance; m_altitude = altitude(m_pressure, SEALEVELPRESSURE_HPA); m_co2_equiv = iaq_sensor.co2Equivalent; m_iaq_score = iaq_sensor.iaq; if (DISPLAY_IAQ) { Serial.println(F("update_state")); update_state(); } return true; catch_block: wexception.print(); return false; } int IndexOfAirQuality::advanced_read(void) { bool res; unsigned long time_trigger = millis(); res = perform_reading(); ERROR_RETURN((res), F("run too early")); if (DISPLAY_IAQ) { Serial.println(F("--------------------------------------------------------------")); Serial.print(F("Timestamp [ms ]")); Serial.println(time_trigger); Serial.print(F("IAQ ")); Serial.println(iaq_sensor.iaq); Serial.print(F("IAQ accuracy ")); Serial.println(iaq_sensor.iaqAccuracy); Serial.print(F("Static IAQ ")); Serial.println(iaq_sensor.staticIaq); Serial.print(F("CO2 equivalent ")); Serial.println(iaq_sensor.co2Equivalent); Serial.print(F("breath VOC equiv ")); Serial.println(iaq_sensor.breathVocEquivalent); Serial.print(F("raw temp [C ]")); Serial.println(iaq_sensor.rawTemperature); Serial.print(F("pressure [Pa ]")); Serial.println(iaq_sensor.pressure); Serial.print(F("raw relative humid [% ]")); Serial.println(iaq_sensor.rawHumidity); Serial.print(F("gas [Ohm]")); Serial.println(iaq_sensor.gasResistance); Serial.print(F("Stab Status ")); Serial.println(iaq_sensor.stabStatus); Serial.print(F("run in status ")); Serial.println(iaq_sensor.runInStatus); Serial.print(F("comp temp [C ]")); Serial.println(iaq_sensor.temperature); Serial.print(F("comp humidity [% ]")); Serial.println(iaq_sensor.humidity); Serial.print(F("gas percentage ")); Serial.println(iaq_sensor.gasPercentage); Serial.print(F("iaq_text ")); Serial.println(m_iaq_text); Serial.println(F("--------------------------------------------------------------")); } // fixme Serial.flush(); // delay(2000); return 0; catch_block: wexception.print(); // fixme Serial.flush(); // weather_halt(); return -1; } /* Calculates the altitude (in meters). Reads the current atmostpheric pressure (in hPa) from the sensor and calculates via the provided sea-level pressure (in hPa). seaLevel Sea-level pressure in hPa return Altitude in meters Equation taken from BMP180 datasheet (page 16): http://www.adafruit.com/datasheets/BST-BMP180-DS000-09.pdf Note that using the equation from wikipedia can give bad results at high altitude. See this thread for more information: http://forums.adafruit.com/viewtopic.php?f=22&t=58064 */ float altitude(float pressure, float sea_level) { float atmospheric = pressure / 100.0F; return 44330.0 * (1.0 - pow(atmospheric / sea_level, 0.1903)); } // update m_iaq_score // update m_iaq_text String iaq_text(float iaq_score) { if (iaq_score > 350) return "air quality is Extremely polluted; identify contamination; leave"; if (iaq_score > 250) return "air quality is Severely polluted; identify contamination; leave"; if (iaq_score > 200) return "air quality is Heavily polluted; maximum ventilation"; if (iaq_score > 150) return "air quality is Moderately polluted; irritation possible; more ventilation"; if (iaq_score > 100) return "air quality is Lightly polluted; problems possible; ventilate"; if (iaq_score > 50) return "air quality is Good; no irritation"; if (iaq_score > 00) return "air quality is Excellent; pure;"; return "air quality is error negative"; } // Reads 8 bit values over SPI static int8_t spi_read(uint8_t reg_addr, uint8_t *reg_data, uint32_t len, void *intf_ptr) { Adafruit_SPIDevice *_dev = (Adafruit_SPIDevice *)intf_ptr; reg_addr |= 0x80; if (!_dev->write_then_read(®_addr, 1, reg_data, len, 0x0)) { return -1; } return 0; } // Writes 8 bit values over SPI static int8_t spi_write(uint8_t reg_addr, const uint8_t *reg_data, uint32_t len, void *intf_ptr) { Adafruit_SPIDevice *_dev = (Adafruit_SPIDevice *)intf_ptr; if (!_dev->write((uint8_t *)reg_data, len, ®_addr, 1)) { return -1; } return 0; } static void delay_usec(uint32_t us, void *intf_ptr) { (void)intf_ptr; // Unused parameter delayMicroseconds(us); yield(); } // Helper function definitions bool check_iaq_sensor_status(void) { if (iaq_sensor.bsecStatus != BSEC_OK) { if (iaq_sensor.bsecStatus < BSEC_OK) { Serial.print(F("BSEC error code : ")); Serial.println(iaq_sensor.bsecStatus); Serial.flush(); weather_halt(); } else { Serial.print(F("BSEC warning code : ")); Serial.println(iaq_sensor.bsecStatus); } } if (iaq_sensor.bme68xStatus != BME68X_OK) { if (iaq_sensor.bme68xStatus < BME68X_OK) { Serial.print(F("BME68X error code : ")); Serial.println(iaq_sensor.bme68xStatus); Serial.flush(); weather_halt(); } else { Serial.print(F("BME68X warning code : ")); Serial.println(iaq_sensor.bme68xStatus); } } if ((iaq_sensor.bsecStatus == BSEC_OK) && (iaq_sensor.bme68xStatus == BME68X_OK)) { return true; } return false; } void load_state(void) { /* if (EEPROM.read(0) == BSEC_MAX_STATE_BLOB_SIZE) { // Existing state in EEPROM Serial.println("Reading state from EEPROM"); for (uint8_t i = 0; i < BSEC_MAX_STATE_BLOB_SIZE; i++) { bsec_state[i] = EEPROM.read(i + 1); Serial.println(bsec_state[i], HEX); } iaqSensor.setState(bsec_state); checkIaqSensorStatus(); } else { // Erase the EEPROM with zeroes Serial.println("Erasing EEPROM"); for (uint8_t i = 0; i < BSEC_MAX_STATE_BLOB_SIZE + 1; i++) EEPROM.write(i, 0); EEPROM.commit(); } */ } void update_state(void) { /* bool update = false; if (stateUpdateCounter == 0) { // First state update when IAQ accuracy is >= 3 if (iaqSensor.iaqAccuracy >= 3) { update = true; stateUpdateCounter++; } } else { // Update every STATE_SAVE_PERIOD minutes if ((stateUpdateCounter * STATE_SAVE_PERIOD) < millis()) { update = true; stateUpdateCounter++; } } if (update) { iaqSensor.getState(bsec_state); checkIaqSensorStatus(); Serial.println("Writing state to EEPROM"); for (uint8_t i = 0; i < BSEC_MAX_STATE_BLOB_SIZE ; i++) { EEPROM.write(i + 1, bsec_state[i]); Serial.println(bsec_state[i], HEX); } EEPROM.write(0, BSEC_MAX_STATE_BLOB_SIZE); EEPROM.commit(); } */ } // eee eof