bme680_bsec: use BOSCH BSEC Library 1.8.1492

This commit is contained in:
bisbastuner 2024-07-08 18:35:45 +02:00 committed by GitHub
parent 4c6a17e304
commit 366061b0f2
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 118 additions and 83 deletions

View file

@ -1,7 +1,7 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import i2c, esp32
from esphome.const import CONF_ID, CONF_SAMPLE_RATE, CONF_TEMPERATURE_OFFSET
from esphome.const import CONF_ID
CODEOWNERS = ["@trvrnrth"]
DEPENDENCIES = ["i2c"]
@ -9,8 +9,10 @@ AUTO_LOAD = ["sensor", "text_sensor"]
MULTI_CONF = True
CONF_BME680_BSEC_ID = "bme680_bsec_id"
CONF_TEMPERATURE_OFFSET = "temperature_offset"
CONF_IAQ_MODE = "iaq_mode"
CONF_SUPPLY_VOLTAGE = "supply_voltage"
CONF_SAMPLE_RATE = "sample_rate"
CONF_STATE_SAVE_INTERVAL = "state_save_interval"
bme680_bsec_ns = cg.esphome_ns.namespace("bme680_bsec")
@ -85,4 +87,4 @@ async def to_code(config):
cg.add_library("SPI", None)
cg.add_define("USE_BSEC")
cg.add_library("boschsensortec/BSEC Software Library", "1.6.1480")
cg.add_library("boschsensortec/BSEC Software Library", "1.8.1492")

View file

@ -15,27 +15,24 @@ std::vector<BME680BSECComponent *>
uint8_t BME680BSECComponent::work_buffer_[BSEC_MAX_WORKBUFFER_SIZE] = {0};
void BME680BSECComponent::setup() {
ESP_LOGCONFIG(TAG, "Setting up BME680(%s) via BSEC...", this->device_id_.c_str());
ESP_LOGCONFIG(TAG, "Setting up BME68x(%s) via BSEC...", this->device_id_.c_str());
uint8_t new_idx = BME680BSECComponent::instances.size();
BME680BSECComponent::instances.push_back(this);
this->bsec_state_data_valid_ = false;
// Initialize the bme680_ structure (passed-in to the bme680_* functions) and the BME680 device
this->bme680_.dev_id =
new_idx; // This is a "Place holder to store the id of the device structure" (see bme680_defs.h).
// This will be passed-in as first parameter to the next "read" and "write" function pointers.
// We currently use the index of the object in the BME680BSECComponent::instances vector to identify
// the different devices in the system.
this->bme680_.intf = BME680_I2C_INTF;
this->bme680_.read = BME680BSECComponent::read_bytes_wrapper;
this->bme680_.write = BME680BSECComponent::write_bytes_wrapper;
this->bme680_.delay_ms = BME680BSECComponent::delay_ms;
this->bme680_.amb_temp = 25;
// Initialize the bme68x_ structure (passed-in to the bme68x_* functions) and the BME68x device
this->bme68x_.intf_ptr =
this; // This will be passed-in as last parameter to the next "read" and "write" function pointers.
this->bme68x_.intf = BME68X_I2C_INTF; // We currently only support I2C interface
this->bme68x_.read = BME680BSECComponent::read_bytes_wrapper;
this->bme68x_.write = BME680BSECComponent::write_bytes_wrapper;
this->bme68x_.delay_us = BME680BSECComponent::delay_us;
this->bme68x_.amb_temp = 25;
this->bme680_status_ = bme680_init(&this->bme680_);
if (this->bme680_status_ != BME680_OK) {
this->bme68x_status_ = bme68x_init(&this->bme68x_);
if (this->bme68x_status_ != BME68X_OK) {
this->mark_failed();
return;
}
@ -155,8 +152,8 @@ void BME680BSECComponent::dump_config() {
LOG_I2C_DEVICE(this);
if (this->is_failed()) {
ESP_LOGE(TAG, "Communication failed (BSEC Status: %d, BME680 Status: %d)", this->bsec_status_,
this->bme680_status_);
ESP_LOGE(TAG, "Communication failed (BSEC Status: %d, BME68x Status: %d)", this->bsec_status_,
this->bme68x_status_);
}
ESP_LOGCONFIG(TAG, " Temperature Offset: %.2f", this->temperature_offset_);
@ -184,12 +181,12 @@ float BME680BSECComponent::get_setup_priority() const { return setup_priority::D
void BME680BSECComponent::loop() {
this->run_();
if (this->bsec_status_ < BSEC_OK || this->bme680_status_ < BME680_OK) {
if (this->bsec_status_ < BSEC_OK || this->bme68x_status_ < BME68X_OK) {
this->status_set_error();
} else {
this->status_clear_error();
}
if (this->bsec_status_ > BSEC_OK || this->bme680_status_ > BME680_OK) {
if (this->bsec_status_ > BSEC_OK || this->bme68x_status_ > BME68X_OK) {
this->status_set_warning();
} else {
this->status_clear_warning();
@ -221,36 +218,50 @@ void BME680BSECComponent::run_() {
return;
}
this->bsec_status_ = bsec_sensor_control(curr_time_ns, &this->bme680_settings_);
this->bsec_status_ = bsec_sensor_control(curr_time_ns, &this->bseclib_settings_);
if (this->bsec_status_ < BSEC_OK) {
ESP_LOGW(TAG, "Failed to fetch sensor control settings (BSEC Error Code %d)", this->bsec_status_);
return;
}
this->next_call_ns_ = this->bme680_settings_.next_call;
this->next_call_ns_ = this->bseclib_settings_.next_call;
if (this->bme680_settings_.trigger_measurement) {
this->bme680_.tph_sett.os_temp = this->bme680_settings_.temperature_oversampling;
this->bme680_.tph_sett.os_pres = this->bme680_settings_.pressure_oversampling;
this->bme680_.tph_sett.os_hum = this->bme680_settings_.humidity_oversampling;
this->bme680_.gas_sett.run_gas = this->bme680_settings_.run_gas;
this->bme680_.gas_sett.heatr_temp = this->bme680_settings_.heater_temperature;
this->bme680_.gas_sett.heatr_dur = this->bme680_settings_.heating_duration;
this->bme680_.power_mode = BME680_FORCED_MODE;
uint16_t desired_settings = BME680_OST_SEL | BME680_OSP_SEL | BME680_OSH_SEL | BME680_GAS_SENSOR_SEL;
this->bme680_status_ = bme680_set_sensor_settings(desired_settings, &this->bme680_);
if (this->bme680_status_ != BME680_OK) {
ESP_LOGW(TAG, "Failed to set sensor settings (BME680 Error Code %d)", this->bme680_status_);
if (this->bseclib_settings_.trigger_measurement) {
// Time to perform a new measurement
// Set general sensor configuration
struct bme68x_conf bme68x_general_conf;
bme68x_general_conf.os_temp = this->bseclib_settings_.temperature_oversampling;
bme68x_general_conf.os_pres = this->bseclib_settings_.pressure_oversampling;
bme68x_general_conf.os_hum = this->bseclib_settings_.humidity_oversampling;
bme68x_general_conf.filter = BME68X_FILTER_OFF;
bme68x_general_conf.odr = BME68X_ODR_NONE;
this->bme68x_status_ = bme68x_set_conf(&bme68x_general_conf, &this->bme68x_);
if (this->bme68x_status_ != BME68X_OK) {
ESP_LOGW(TAG, "Failed to set general sensor config (BME68x Error Code %d)", this->bme68x_status_);
return;
}
this->bme680_status_ = bme680_set_sensor_mode(&this->bme680_);
if (this->bme680_status_ != BME680_OK) {
ESP_LOGW(TAG, "Failed to set sensor mode (BME680 Error Code %d)", this->bme680_status_);
// Set heater configuration
struct bme68x_heatr_conf bme68x_heater_conf;
bme68x_heater_conf.enable = this->bseclib_settings_.run_gas;
bme68x_heater_conf.heatr_temp = this->bseclib_settings_.heater_temperature;
bme68x_heater_conf.heatr_dur = this->bseclib_settings_.heating_duration;
this->bme68x_status_ = bme68x_set_heatr_conf(BME68X_FORCED_MODE, &bme68x_heater_conf, &this->bme68x_);
if (this->bme68x_status_ != BME68X_OK) {
ESP_LOGW(TAG, "Failed to set gas sensor config (BME68x Error Code %d)", this->bme68x_status_);
return;
}
uint16_t meas_dur = 0;
bme680_get_profile_dur(&meas_dur, &this->bme680_);
// Set sensor operation mode
this->bme68x_status_ = bme68x_set_op_mode(BME68X_FORCED_MODE, &this->bme68x_);
if (this->bme68x_status_ != BME68X_OK) {
ESP_LOGW(TAG, "Failed to set sensor mode (BME68x Error Code %d)", this->bme68x_status_);
return;
}
// Calculate time for the measurement to be ready
uint32_t meas_dur_us = bme68x_get_meas_dur(BME68X_FORCED_MODE, &bme68x_general_conf, &this->bme68x_) +
bme68x_heater_conf.heatr_dur * 1000;
// Since we are about to go "out of scope" in the loop, take a snapshot of the state now so we can restore it later
// TODO: it would be interesting to see if this is really needed here, or if it's needed only after each
@ -258,6 +269,8 @@ void BME680BSECComponent::run_() {
if (BME680BSECComponent::instances.size() > 1)
this->snapshot_state_();
// Queuing read for later, so ESPHome can do something more productive in the meantime
uint32_t meas_dur = meas_dur_us / 1000;
ESP_LOGV(TAG, "Queueing read in %ums", meas_dur);
this->set_timeout("read", meas_dur, [this]() { this->read_(); });
} else {
@ -270,67 +283,85 @@ void BME680BSECComponent::read_() {
ESP_LOGV(TAG, "%s: Reading data", this->device_id_.c_str());
int64_t curr_time_ns = this->get_time_ns_();
if (this->bme680_settings_.trigger_measurement) {
while (this->bme680_.power_mode != BME680_SLEEP_MODE) {
this->bme680_status_ = bme680_get_sensor_mode(&this->bme680_);
if (this->bme680_status_ != BME680_OK) {
ESP_LOGW(TAG, "Failed to get sensor mode (BME680 Error Code %d)", this->bme680_status_);
if (this->bseclib_settings_.trigger_measurement) {
uint8_t op_mode = BME68X_FORCED_MODE;
while (op_mode != BME68X_SLEEP_MODE) {
this->bme68x_status_ = bme68x_get_op_mode(&op_mode, &this->bme68x_);
if (this->bme68x_status_ != BME68X_OK) {
ESP_LOGW(TAG, "Failed to get sensor mode (BME68x Error Code %d)", this->bme68x_status_);
return;
}
}
}
if (!this->bme680_settings_.process_data) {
if (!this->bseclib_settings_.process_data) {
ESP_LOGV(TAG, "Data processing not required");
return;
}
struct bme680_field_data data;
this->bme680_status_ = bme680_get_sensor_data(&data, &this->bme680_);
if (this->bme680_status_ != BME680_OK) {
ESP_LOGW(TAG, "Failed to get sensor data (BME680 Error Code %d)", this->bme680_status_);
struct bme68x_data data;
uint8_t data_count = 0;
this->bme68x_status_ = bme68x_get_data(BME68X_FORCED_MODE, &data, &data_count, &this->bme68x_);
if (this->bme68x_status_ != BME68X_OK) {
ESP_LOGW(TAG, "Failed to get sensor data (BME68x Error Code %d)", this->bme68x_status_);
return;
}
if (!(data.status & BME680_NEW_DATA_MSK)) {
ESP_LOGD(TAG, "BME680 did not report new data");
if (!(data.status & BME68X_NEW_DATA_MSK)) {
ESP_LOGD(TAG, "BME68x did not report new data");
return;
}
bsec_input_t inputs[BSEC_MAX_PHYSICAL_SENSOR]; // Temperature, Pressure, Humidity & Gas Resistance
bsec_input_t
inputs[BSEC_MAX_PHYSICAL_SENSOR]; // Temperature, Temperature offset, Pressure, Humidity & Gas Resistance
uint8_t num_inputs = 0;
if (this->bme680_settings_.process_data & BSEC_PROCESS_TEMPERATURE) {
if (this->bseclib_settings_.process_data & BSEC_PROCESS_TEMPERATURE) {
inputs[num_inputs].sensor_id = BSEC_INPUT_TEMPERATURE;
inputs[num_inputs].signal = data.temperature / 100.0f;
inputs[num_inputs].signal = data.temperature;
inputs[num_inputs].time_stamp = curr_time_ns;
ESP_LOGV(TAG, "%s: BSEC In[%d](Temperature)=%f", this->device_id_.c_str(), num_inputs, inputs[num_inputs].signal);
num_inputs++;
// Temperature offset from the real temperature due to external heat sources
inputs[num_inputs].sensor_id = BSEC_INPUT_HEATSOURCE;
inputs[num_inputs].signal = this->temperature_offset_;
inputs[num_inputs].time_stamp = curr_time_ns;
ESP_LOGV(TAG, "%s: BSEC In[%d](TemperatureOffset)=%f", this->device_id_.c_str(), num_inputs,
inputs[num_inputs].signal);
num_inputs++;
}
if (this->bme680_settings_.process_data & BSEC_PROCESS_HUMIDITY) {
if (this->bseclib_settings_.process_data & BSEC_PROCESS_HUMIDITY) {
inputs[num_inputs].sensor_id = BSEC_INPUT_HUMIDITY;
inputs[num_inputs].signal = data.humidity / 1000.0f;
inputs[num_inputs].signal =
data.humidity; // Comment in struct bme68x_data wrongly states that humidity unit is %RH x1000, but inspecting
// the data when using float type the unit is plain %RH
inputs[num_inputs].time_stamp = curr_time_ns;
ESP_LOGV(TAG, "%s: BSEC In[%d](Humidity)=%f", this->device_id_.c_str(), num_inputs, inputs[num_inputs].signal);
num_inputs++;
}
if (this->bme680_settings_.process_data & BSEC_PROCESS_PRESSURE) {
if (this->bseclib_settings_.process_data & BSEC_PROCESS_PRESSURE) {
inputs[num_inputs].sensor_id = BSEC_INPUT_PRESSURE;
inputs[num_inputs].signal = data.pressure;
inputs[num_inputs].time_stamp = curr_time_ns;
ESP_LOGV(TAG, "%s: BSEC In[%d](Pressure)=%f", this->device_id_.c_str(), num_inputs, inputs[num_inputs].signal);
num_inputs++;
}
if (this->bme680_settings_.process_data & BSEC_PROCESS_GAS) {
if (data.status & BME680_GASM_VALID_MSK) {
if (this->bseclib_settings_.process_data & BSEC_PROCESS_GAS) {
if (data.status & BME68X_GASM_VALID_MSK) {
inputs[num_inputs].sensor_id = BSEC_INPUT_GASRESISTOR;
inputs[num_inputs].signal = data.gas_resistance;
inputs[num_inputs].signal = min(
data.gas_resistance,
12800000.0f); // Limit the maximum value to avoid BSEC error -2 (BSEC_E_DOSTEPS_VALUELIMITS)
// Starting with clean calibration data, a sensor has been observed generating constant gas
// resistance values at 12917167, which then decreased over time. A signal value at 12800001.0f
// results in the BSEC library v1.8.1492 returning error -2 (BSEC_E_DOSTEPS_VALUELIMITS).
// 12800000 and below are OK; no problem at all in BSEC v1.6.1480
inputs[num_inputs].time_stamp = curr_time_ns;
ESP_LOGV(TAG, "%s: BSEC In[%d](GasResistance)=%f (original=%f)", this->device_id_.c_str(), num_inputs,
inputs[num_inputs].signal, data.gas_resistance);
num_inputs++;
} else {
ESP_LOGD(TAG, "BME680 did not report gas data");
ESP_LOGD(TAG, "BME68x did not report gas data");
}
}
if (num_inputs < 1) {
@ -346,8 +377,8 @@ void BME680BSECComponent::read_() {
if (res != 0)
return;
// Now that the BSEC library has been re-initialized, bsec_sensor_control *NEEDS* to be called in order to support
// multiple devices with a different set of enabled sensors (even if the bme680_settings_ data is not used)
this->bsec_status_ = bsec_sensor_control(curr_time_ns, &this->bme680_settings_);
// multiple devices with a different set of enabled sensors (even if the bseclib_settings_ data is not used)
this->bsec_status_ = bsec_sensor_control(curr_time_ns, &this->bseclib_settings_);
if (this->bsec_status_ < BSEC_OK) {
ESP_LOGW(TAG, "Failed to fetch sensor control settings (BSEC Error Code %d)", this->bsec_status_);
return;
@ -440,24 +471,26 @@ void BME680BSECComponent::publish_sensor_(text_sensor::TextSensor *sensor, const
}
// Communication function - read
// First parameter is the "dev_id" member of our "bme680_" object, which is passed-back here as-is
int8_t BME680BSECComponent::read_bytes_wrapper(uint8_t devid, uint8_t a_register, uint8_t *data, uint16_t len) {
BME680BSECComponent *inst = instances[devid];
// Last parameter is a pointer to our BME680BSECComponent object, which is passed-back here as-is
BME68X_INTF_RET_TYPE BME680BSECComponent::read_bytes_wrapper(uint8_t reg_addr, uint8_t *reg_data, uint32_t len,
void *ptr) {
BME680BSECComponent *inst = (BME680BSECComponent *) ptr;
// Use the I2CDevice::read_bytes method to perform the actual I2C register read
return inst->read_bytes(a_register, data, len) ? 0 : -1;
return inst->read_bytes(reg_addr, reg_data, len) ? 0 : -1;
}
// Communication function - write
// First parameter is the "dev_id" member of our "bme680_" object, which is passed-back here as-is
int8_t BME680BSECComponent::write_bytes_wrapper(uint8_t devid, uint8_t a_register, uint8_t *data, uint16_t len) {
BME680BSECComponent *inst = instances[devid];
// Last parameter is a pointer to our BME680BSECComponent object, which is passed-back here as-is
BME68X_INTF_RET_TYPE BME680BSECComponent::write_bytes_wrapper(uint8_t reg_addr, const uint8_t *reg_data, uint32_t len,
void *ptr) {
BME680BSECComponent *inst = (BME680BSECComponent *) ptr;
// Use the I2CDevice::write_bytes method to perform the actual I2C register write
return inst->write_bytes(a_register, data, len) ? 0 : -1;
return inst->write_bytes(reg_addr, reg_data, len) ? 0 : -1;
}
void BME680BSECComponent::delay_ms(uint32_t period) {
ESP_LOGV(TAG, "Delaying for %ums", period);
delay(period);
void BME680BSECComponent::delay_us(uint32_t period, void *ptr) {
ESP_LOGV(TAG, "Delaying for %uus", period);
delay_microseconds_safe(period);
}
// Fetch the BSEC library state and save it in the bsec_state_data_ member (volatile memory)
@ -515,7 +548,7 @@ int BME680BSECComponent::reinit_bsec_lib_() {
}
void BME680BSECComponent::load_state_() {
uint32_t hash = fnv1_hash("bme680_bsec_state_" + this->device_id_);
uint32_t hash = fnv1_hash("bme68x_bsec_state_" + this->device_id_);
this->bsec_state_ = global_preferences->make_preference<uint8_t[BSEC_MAX_STATE_BLOB_SIZE]>(hash, true);
if (!this->bsec_state_.load(&this->bsec_state_data_)) {

View file

@ -58,9 +58,9 @@ class BME680BSECComponent : public Component, public i2c::I2CDevice {
void set_breath_voc_equivalent_sensor(sensor::Sensor *sensor) { this->breath_voc_equivalent_sensor_ = sensor; }
static std::vector<BME680BSECComponent *> instances;
static int8_t read_bytes_wrapper(uint8_t devid, uint8_t a_register, uint8_t *data, uint16_t len);
static int8_t write_bytes_wrapper(uint8_t devid, uint8_t a_register, uint8_t *data, uint16_t len);
static void delay_ms(uint32_t period);
static BME68X_INTF_RET_TYPE read_bytes_wrapper(uint8_t reg_addr, uint8_t *reg_data, uint32_t len, void *ptr);
static BME68X_INTF_RET_TYPE write_bytes_wrapper(uint8_t reg_addr, const uint8_t *reg_data, uint32_t len, void *ptr);
static void delay_us(uint32_t period, void *ptr);
void setup() override;
void dump_config() override;
@ -95,9 +95,9 @@ class BME680BSECComponent : public Component, public i2c::I2CDevice {
void queue_push_(std::function<void()> &&f) { this->queue_.push(std::move(f)); }
static uint8_t work_buffer_[BSEC_MAX_WORKBUFFER_SIZE];
struct bme680_dev bme680_;
struct bme68x_dev bme68x_;
bsec_library_return_t bsec_status_{BSEC_OK};
int8_t bme680_status_{BME680_OK};
int8_t bme68x_status_{BME68X_OK};
int64_t last_time_ms_{0};
uint32_t millis_overflow_counter_{0};
@ -109,8 +109,8 @@ class BME680BSECComponent : public Component, public i2c::I2CDevice {
uint8_t bsec_state_data_[BSEC_MAX_STATE_BLOB_SIZE]; // This is the current snapshot of the BSEC library state
ESPPreferenceObject bsec_state_;
uint32_t state_save_interval_ms_{21600000}; // 6 hours - 4 times a day
uint32_t last_state_save_ms_ = 0;
bsec_bme_settings_t bme680_settings_;
uint32_t last_state_save_ms_{0};
bsec_bme_settings_t bseclib_settings_;
std::string device_id_;
float temperature_offset_{0};