// ----------------------------------------------------------------------------- // BME680 Sensor over I2C // Copyright (C) 2020 by Rui Marinho // // The BSEC software binaries and includes are only available for use after accepting its software // license agreement. By enabling this sensor integration, you are agreeing to the terms of the license // agreement available at the following URL: // // https://ae-bst.resource.bosch.com/media/_tech/media/bsec/2017-07-17_ClickThrough_License_Terms_Environmentalib_SW_CLEAN.pdf // // The Arduino wrapper and BME680 Sensor API used for this integration are licensed under the following terms: // // Copyright (c) 2020 Bosch Sensortec GmbH. All rights reserved. // // BSD-3-Clause // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are met: // // 1. Redistributions of source code must retain the above copyright // notice, this list of conditions and the following disclaimer. // // 2. Redistributions in binary form must reproduce the above copyright // notice, this list of conditions and the following disclaimer in the // documentation and/or other materials provided with the distribution. // // 3. Neither the name of the copyright holder nor the names of its // contributors may be used to endorse or promote products derived from // this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS // FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE // COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, // INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES // (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR // SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) // HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, // STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING // IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE // POSSIBILITY OF SUCH DAMAGE. // // For more details, please refer to https://github.com/BoschSensortec/BSEC-Arduino-library. // ----------------------------------------------------------------------------- #if SENSOR_SUPPORT && BME680_SUPPORT #pragma once #include #include #include "I2CSensor.h" // Available configuration modes based on parameters: // voltage / maximum time between sensor calls / time considered // for background calibration. #define BME680_BSEC_CONFIG_GENERIC_18V_3S_4D 0 #define BME680_BSEC_CONFIG_GENERIC_18V_3S_28D 1 #define BME680_BSEC_CONFIG_GENERIC_18V_300S_4D 2 #define BME680_BSEC_CONFIG_GENERIC_18V_300S_28D 3 #define BME680_BSEC_CONFIG_GENERIC_33V_3S_4D 4 #define BME680_BSEC_CONFIG_GENERIC_33V_3S_28D 5 #define BME680_BSEC_CONFIG_GENERIC_33V_300S_4D 6 #define BME680_BSEC_CONFIG_GENERIC_33V_300S_28D 7 const uint8_t bsec_config_iaq[] = { #if BME680_BSEC_CONFIG == BME680_BSEC_CONFIG_GENERIC_18V_3S_4D #include #elif BME680_BSEC_CONFIG == BME680_BSEC_CONFIG_GENERIC_18V_3S_28D #include #elif BME680_BSEC_CONFIG == BME680_BSEC_CONFIG_GENERIC_18V_300S_4D #include #elif BME680_BSEC_CONFIG == BME680_BSEC_CONFIG_GENERIC_18V_300S_28D #include #elif BME680_BSEC_CONFIG == BME680_BSEC_CONFIG_GENERIC_33V_3S_4D #include #elif BME680_BSEC_CONFIG == BME680_BSEC_CONFIG_GENERIC_33V_3S_28D #include #elif BME680_BSEC_CONFIG == BME680_BSEC_CONFIG_GENERIC_33V_300S_4D #include #elif BME680_BSEC_CONFIG == BME680_BSEC_CONFIG_GENERIC_33V_300S_28D #include #endif }; class BME680Sensor : public I2CSensor<> { public: // --------------------------------------------------------------------- // Public // --------------------------------------------------------------------- BME680Sensor() { _error = SENSOR_ERROR_OK; _sensor_id = SENSOR_BME680_ID; _count = 9; } // --------------------------------------------------------------------- // Sensor API // --------------------------------------------------------------------- void begin() { if (!_dirty) { return; } // I2C auto-discover unsigned char addresses[] = {BME680_I2C_ADDR_PRIMARY, BME680_I2C_ADDR_SECONDARY}; _address = _begin_i2c(_address, sizeof(addresses), addresses); if (_address == 0) return; iaqSensor.begin(_address, Wire); DEBUG_MSG_P(PSTR("[BME680] BSEC library version v%u.%u.%u.%u\n"), iaqSensor.version.major, iaqSensor.version.minor, iaqSensor.version.major_bugfix, iaqSensor.version.minor_bugfix ); if (!_isOk()) { _showSensorErrors(); _error = SENSOR_ERROR_OTHER; return; } iaqSensor.setConfig(bsec_config_iaq); _loadState(); float sampleRate; // BSEC configuration with 300s allows for the sensor to sleep for 300s // on the ULP mode in order to minimize power consumption. if (BME680_BSEC_CONFIG == BME680_BSEC_CONFIG_GENERIC_18V_300S_4D || BME680_BSEC_CONFIG == BME680_BSEC_CONFIG_GENERIC_18V_300S_4D || BME680_BSEC_CONFIG == BME680_BSEC_CONFIG_GENERIC_18V_300S_4D) { sampleRate = BSEC_SAMPLE_RATE_ULP; } else { sampleRate = BSEC_SAMPLE_RATE_LP; } iaqSensor.updateSubscription(sensorList, 12, sampleRate); if (!_isOk()) { _showSensorErrors(); _error = SENSOR_ERROR_OTHER; return; } _error = SENSOR_ERROR_OK; _ready = true; _dirty = false; } // Descriptive name of the sensor String description() { char buffer[21]; snprintf(buffer, sizeof(buffer), "BME680 @ I2C (0x%02X)", _address); return String(buffer); } // Type for slot # index unsigned char type(unsigned char index) { if (index == 0) return MAGNITUDE_TEMPERATURE; if (index == 1) return MAGNITUDE_HUMIDITY; if (index == 2) return MAGNITUDE_PRESSURE; if (index == 3) return MAGNITUDE_RESISTANCE; if (index == 4) return MAGNITUDE_IAQ_ACCURACY; if (index == 5) return MAGNITUDE_IAQ; if (index == 6) return MAGNITUDE_IAQ_STATIC; if (index == 7) return MAGNITUDE_CO2; if (index == 8) return MAGNITUDE_VOC; return MAGNITUDE_NONE; } // The maximum allowed time between two `bsec_sensor_control` calls depends on // configuration profile `bsec_config_iaq` below. void tick() { if (iaqSensor.run()) { _rawTemperature = iaqSensor.rawTemperature; _rawHumidity = iaqSensor.rawHumidity; _temperature = iaqSensor.temperature; _humidity = iaqSensor.humidity; _pressure = iaqSensor.pressure / 100; _gasResistance = iaqSensor.gasResistance; _iaqAccuracy = iaqSensor.iaqAccuracy; _iaq = iaqSensor.iaq; _iaqStatic = iaqSensor.staticIaq; _co2Equivalent = iaqSensor.co2Equivalent; _breathVocEquivalent = iaqSensor.breathVocEquivalent; _saveState(); _error = SENSOR_ERROR_OK; } else if (_isError()) { _error = SENSOR_ERROR_OTHER; } } // Ensure we show any possible issues with the sensor, post() is called regardless of sensor status() / error() codes void post() override { _showSensorErrors(); } // Current value for slot # index double value(unsigned char index) { if (index == 0) return _temperature; if (index == 1) return _humidity; if (index == 2) return _pressure; if (index == 3) return _gasResistance; if (index == 4) return _iaqAccuracy; if (index == 5) return _iaq; if (index == 6) return _iaqStatic; if (index == 7) return _co2Equivalent; if (index == 8) return _breathVocEquivalent; return 0; } protected: void _loadState() { String storedState = getSetting("bsecState"); if (!storedState.length()) { return; } DEBUG_MSG_P(PSTR("[BME680] Restoring previous state\n")); hexDecode(storedState.c_str(), storedState.length(), _bsecState, sizeof(_bsecState)); iaqSensor.setState(_bsecState); _showSensorErrors(); } void _saveState() { if (!BME680_STATE_SAVE_INTERVAL) return; static unsigned long last_millis = 0; if (_iaqAccuracy < 3 || (millis() - last_millis < BME680_STATE_SAVE_INTERVAL)) { return; } iaqSensor.getState(_bsecState); char storedState[BSEC_MAX_STATE_BLOB_SIZE * 2 + 1] = {0}; hexEncode(_bsecState, BSEC_MAX_STATE_BLOB_SIZE, storedState, sizeof(storedState)); setSetting("bsecState", storedState); last_millis = millis(); } bool _isError() { return (iaqSensor.status < BSEC_OK) || (iaqSensor.bme680Status < BME680_OK); } bool _isOk() { return (iaqSensor.status == BSEC_OK) && (iaqSensor.bme680Status == BME680_OK); } void _showSensorErrors() { // see `enum { ... } bsec_library_return_t` values & description at: // BSEC Software Library/src/inc/bsec_datatypes.h if (iaqSensor.status != BSEC_OK) { if (iaqSensor.status < BSEC_OK) { DEBUG_MSG_P(PSTR("[BME680] BSEC error code (%d)\n"), iaqSensor.status); } else { DEBUG_MSG_P(PSTR("[BME680] BSEC warning code (%d)\n"), iaqSensor.status); } } // see `BME680_{W,E}_...` at: // BSEC Software Library/src/bme680/bme680_defs.h switch (iaqSensor.bme680Status) { case BME680_OK: break; case BME680_E_COM_FAIL: case BME680_E_DEV_NOT_FOUND: DEBUG_MSG_P(PSTR("[BME680] Communication error / device not found (%d)\n"), iaqSensor.bme680Status); case BME680_W_DEFINE_PWR_MODE: DEBUG_MSG_P(PSTR("[BME680] Power mode not defined (%d)\n"), iaqSensor.bme680Status); break; case BME680_W_NO_NEW_DATA: DEBUG_MSG_P(PSTR("[BME680] No new data (%d)\n"), iaqSensor.bme680Status); break; default: if (iaqSensor.bme680Status < BME680_OK) { DEBUG_MSG_P(PSTR("[BME680] Error code (%d)\n"), iaqSensor.bme680Status); } else { DEBUG_MSG_P(PSTR("[BME680] Warning code (%d)\n"), iaqSensor.bme680Status); } break; } } bsec_virtual_sensor_t sensorList[12] = { BSEC_OUTPUT_RAW_TEMPERATURE, // Unscaled (raw) temperature (ºC). BSEC_OUTPUT_RAW_PRESSURE, // Unscaled (raw) pressure (Pa). BSEC_OUTPUT_RAW_HUMIDITY, // Unscaled (raw) relative humidity (%). BSEC_OUTPUT_RAW_GAS, // Gas resistance (Ohm). The resistance value changes according to the // VOC concentration (the higher the concentration of reducing VOCs, // the lower the resistance and vice versa). BSEC_OUTPUT_IAQ, // Scaled Indoor Air Quality based on the recent sensor history, ideal // for mobile applications (e.g. carry-on devices). The scale ranges from // 0 (clean air) to 500 (heavily polluted air). The automatic background // calibration process ensures that consistent IAQ performance is achieved // after certain of days (depending on BSEC configuration - 4d or 28d). BSEC_OUTPUT_STATIC_IAQ, // Unscaled Indoor Air Quality, optimized for stationary applications // (e.g. fixed indoor devices). BSEC_OUTPUT_CO2_EQUIVALENT, // Estimate of CO2 measured in the air. BSEC_OUTPUT_BREATH_VOC_EQUIVALENT, // Breath VOC represents the most important compounds in an exhaled // breath of healthy humans. BSEC_OUTPUT_SENSOR_HEAT_COMPENSATED_TEMPERATURE, // Temperature compensated for the influence of sensor heater (ºC). BSEC_OUTPUT_SENSOR_HEAT_COMPENSATED_HUMIDITY, // Relative humidity compensated for the influence of sensor heater (%). BSEC_OUTPUT_STABILIZATION_STATUS, // Indicates initial stabilization status of the gas sensor element: // ongoing (0) or finished (1). BSEC_OUTPUT_RUN_IN_STATUS, // Indicates power-on stabilization status of the gas sensor element: // ongoing (0) or finished (1). }; float _breathVocEquivalent = 0.0f; float _co2Equivalent = 0.0f; float _gasResistance = 0.0f; float _humidity = 0.0f; float _iaq = 0.0f; float _pressure = 0.0f; float _rawHumidity = 0.0f; float _rawTemperature = 0.0f; float _temperature = 0.0f; uint8_t _bsecState[BSEC_MAX_STATE_BLOB_SIZE] = {0}; uint8_t _iaqAccuracy = 0; float _iaqStatic = 0; Bsec iaqSensor; }; #endif // SENSOR_SUPPORT && BME680_SUPPORT