// -----------------------------------------------------------------------------
|
|
// BME680 Sensor over I2C
|
|
// Copyright (C) 2020 by Rui Marinho <ruipmarinho at gmail dot com>
|
|
//
|
|
// 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 <Arduino.h>
|
|
#include <bsec.h>
|
|
|
|
#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 <config/generic_18v_3s_28d/bsec_iaq.txt>
|
|
#elif BME680_BSEC_CONFIG == BME680_BSEC_CONFIG_GENERIC_18V_3S_28D
|
|
#include <config/generic_18v_300s_4d/bsec_iaq.txt>
|
|
#elif BME680_BSEC_CONFIG == BME680_BSEC_CONFIG_GENERIC_18V_300S_4D
|
|
#include <config/generic_18v_300s_28d/bsec_iaq.txt>
|
|
#elif BME680_BSEC_CONFIG == BME680_BSEC_CONFIG_GENERIC_18V_300S_28D
|
|
#include <config/generic_33v_3s_4d/bsec_iaq.txt>
|
|
#elif BME680_BSEC_CONFIG == BME680_BSEC_CONFIG_GENERIC_33V_3S_4D
|
|
#include <config/generic_33v_3s_28d/bsec_iaq.txt>
|
|
#elif BME680_BSEC_CONFIG == BME680_BSEC_CONFIG_GENERIC_33V_3S_28D
|
|
#include <config/generic_33v_300s_4d/bsec_iaq.txt>
|
|
#elif BME680_BSEC_CONFIG == BME680_BSEC_CONFIG_GENERIC_33V_300S_4D
|
|
#include <config/generic_33v_300s_28d/bsec_iaq.txt>
|
|
#elif BME680_BSEC_CONFIG == BME680_BSEC_CONFIG_GENERIC_33V_300S_28D
|
|
#include <config/generic_33v_3s_4d/bsec_iaq.txt>
|
|
#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
|