Mirror of espurna firmware for wireless switches and more
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

387 lines
15 KiB

// -----------------------------------------------------------------------------
// 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 "I2CSensor.h"
#include <bsec.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:
// ---------------------------------------------------------------------
// Sensor API
// ---------------------------------------------------------------------
static constexpr Magnitude Magnitudes[] {
MAGNITUDE_TEMPERATURE,
MAGNITUDE_HUMIDITY,
MAGNITUDE_PRESSURE,
MAGNITUDE_RESISTANCE,
MAGNITUDE_IAQ_ACCURACY,
MAGNITUDE_IAQ,
MAGNITUDE_IAQ_STATIC,
MAGNITUDE_CO2,
MAGNITUDE_VOC,
};
unsigned char id() const override {
return SENSOR_BME680_ID;
}
unsigned char count() const override {
return std::size(Magnitudes);
}
void begin() override {
if (!_dirty) {
return;
}
// I2C auto-discover
static constexpr uint8_t addresses[] {BME680_I2C_ADDR_PRIMARY, BME680_I2C_ADDR_SECONDARY};
auto address = findAndLock(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();
_last_state = TimeSource::now();
// BSEC configuration with 300s allows for the sensor to sleep for 300s
// on the ULP mode in order to minimize power consumption.
const float sampleRate =
((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))
? BSEC_SAMPLE_RATE_ULP
: BSEC_SAMPLE_RATE_LP;
iaqSensor.updateSubscription(
SensorList, std::size(SensorList), sampleRate);
if (!_isOk()) {
_showSensorErrors();
_error = SENSOR_ERROR_OTHER;
return;
}
_error = SENSOR_ERROR_OK;
_ready = true;
_dirty = false;
}
// Descriptive name of the sensor
String description() const override {
char buffer[21];
snprintf_P(buffer, sizeof(buffer),
PSTR("BME680 @ I2C (0x%02X)"), lockedAddress());
return String(buffer);
}
// Type for slot # index
unsigned char type(unsigned char index) const override {
if (index < std::size(Magnitudes)) {
return Magnitudes[index].type;
}
return MAGNITUDE_NONE;
}
// The maximum allowed time between two `bsec_sensor_control` calls depends on
// configuration profile `bsec_config_iaq` below.
void tick() override {
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) override {
if (index < std::size(Magnitudes)) {
switch (Magnitudes[index].type) {
case MAGNITUDE_TEMPERATURE:
return _temperature;
case MAGNITUDE_HUMIDITY:
return _humidity;
case MAGNITUDE_PRESSURE:
return _pressure;
case MAGNITUDE_RESISTANCE:
return _gasResistance;
case MAGNITUDE_IAQ_ACCURACY:
return _iaqAccuracy;
case MAGNITUDE_IAQ:
return _iaq;
case MAGNITUDE_IAQ_STATIC:
return _iaqStatic;
case MAGNITUDE_CO2:
return _co2Equivalent;
case MAGNITUDE_VOC:
return _breathVocEquivalent;
}
}
return 0;
}
protected:
void _loadState() {
auto storedState = getSetting("bsecState");
if (!storedState.length()) {
return;
}
if (hexDecode(storedState.c_str(), storedState.length(), _state, sizeof(_state))) {
DEBUG_MSG_P(PSTR("[BME680] Restoring saved state...\n"));
iaqSensor.setState(_state);
_showSensorErrors();
}
}
void _saveState() {
if (!SaveInterval.count()) {
return;
}
if (_iaqAccuracy < 3) {
return;
}
const auto now = TimeSource::now();
if (now - _last_state < SaveInterval) {
return;
}
iaqSensor.getState(_state);
setSetting("bsecState", hexEncode(_state));
_last_state = now;
}
bool _isError() const {
return (iaqSensor.status < BSEC_OK)
|| (iaqSensor.bme680Status < BME680_OK);
}
bool _isOk() const {
return (iaqSensor.status == BSEC_OK)
&& (iaqSensor.bme680Status == BME680_OK);
}
void _showSensorErrors() const {
// 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 _temperature = 0.0f;
float _rawTemperature = 0.0f;
float _rawHumidity = 0.0f;
float _humidity = 0.0f;
float _pressure = 0.0f;
float _gasResistance = 0.0f;
uint8_t _iaqAccuracy = 0;
float _iaq = 0.0f;
float _iaqStatic = 0;
float _breathVocEquivalent = 0.0f;
float _co2Equivalent = 0.0f;
using TimeSource = espurna::time::CoreClock;
static constexpr auto SaveInterval = TimeSource::duration { BME680_STATE_SAVE_INTERVAL };
TimeSource::time_point _last_state;
uint8_t _state[BSEC_MAX_STATE_BLOB_SIZE] = {0};
Bsec iaqSensor;
};
#if __cplusplus < 201703L
constexpr BaseSensor::Magnitude BME680Sensor::Magnitudes[];
#endif
#endif // SENSOR_SUPPORT && BME680_SUPPORT