Add support for multiple devices in bme680_bsec (#3550)

* Add initial support for multiple devices

Re-introduce support for multiple I2C devices (it was suppressed in https://github.com/trvrnrth/esphome-bsec-bme680/commit/df37a7635f6acc3f7520f257d2645eda10b863a5). Devices are identified by their I2C address, and the BME680 can only have the 0x76 or 0x77 address, so this adds support for a maximum of two devices.

* Reintegrate commit ebf13a0b

Reintegrate commit https://github.com/esphome/esphome/commit/ebf13a0ba0a48453c2cc80bfc942d6a588859233 which was lost in my changes (I were working on old files)

* wrong commit

* wrong commit

* Reintegrate commit ebf13a0b

Reintegrate commit https://github.com/esphome/esphome/commit/ebf13a0ba0a48453c2cc80bfc942d6a588859233 which was lost due to me working on old files

* Reintroduce newlines at end of files

* Reintroduce newlines at end of files

* Adhere to codebase standards

Obey the "All uses of class members and member functions should be prefixed with this-> to distinguish them from global functions in code review" rule of the Codebase Standards

* Fix formatting according to clang-format

* Perform the BSEC library reinitialization+snapshot only when more than one device is present

* Fix formatting according to clang-format

* Degrade abort message in restore_state_() from warning to verbose

This always happen at initial setup, so it's not a really useful message; when some real problems arise, we'll have a more useful warning from snapshot_state_()

Co-authored-by: Trevor North <trevor@freedisc.co.uk>

* Reduce peak stack usage to avoid bootloops on ESP8266

Achieved mainly by moving the work_buffer needed by the BSEC library to the heap, as a single global work buffer shared by all instances.
::set_config_ has been reverted to a code path similar to the original, as that reduces peak stack usage enough to be OK on ESP8266 even without moving the work_buffer to the heap.

* Fix formatting according to clang-format

* Add support for devices with the same i2c address

Devices are now identified using their index in the BME680BSECComponent::instances member, which became a vector. This allows adding two devices with the same i2c address (which should be placed on different i2c buses). Since a BME680 can only have an address of 0x76 or 0x77, a maximum of 2 devices could be added before this commit. Now there is no theoretical limit on the number of devices which could be added.

* Fix formatting according to clang-format

* Fix formatting according to clang-format

---------

Co-authored-by: Trevor North <trevor@freedisc.co.uk>
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
This commit is contained in:
bisbastuner
2023-03-07 08:15:40 +01:00
committed by GitHub
parent bb5ab8b36d
commit 356efdb92c
3 changed files with 215 additions and 88 deletions
@@ -6,6 +6,7 @@ from esphome.const import CONF_ID
CODEOWNERS = ["@trvrnrth"]
DEPENDENCIES = ["i2c"]
AUTO_LOAD = ["sensor", "text_sensor"]
MULTI_CONF = True
CONF_BME680_BSEC_ID = "bme680_bsec_id"
CONF_TEMPERATURE_OFFSET = "temperature_offset"
@@ -54,6 +55,7 @@ async def to_code(config):
await cg.register_component(var, config)
await i2c.register_i2c_device(var, config)
cg.add(var.set_device_id(str(config[CONF_ID])))
cg.add(var.set_temperature_offset(config[CONF_TEMPERATURE_OFFSET]))
cg.add(var.set_iaq_mode(config[CONF_IAQ_MODE]))
cg.add(var.set_sample_rate(config[CONF_SAMPLE_RATE]))
+191 -81
View File
@@ -10,19 +10,24 @@ static const char *const TAG = "bme680_bsec.sensor";
static const std::string IAQ_ACCURACY_STATES[4] = {"Stabilizing", "Uncertain", "Calibrating", "Calibrated"};
BME680BSECComponent *BME680BSECComponent::instance; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
std::vector<BME680BSECComponent *>
BME680BSECComponent::instances; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
uint8_t BME680BSECComponent::work_buffer_[BSEC_MAX_WORKBUFFER_SIZE] = {0};
void BME680BSECComponent::setup() {
ESP_LOGCONFIG(TAG, "Setting up BME680 via BSEC...");
BME680BSECComponent::instance = this;
ESP_LOGCONFIG(TAG, "Setting up BME680(%s) via BSEC...", this->device_id_.c_str());
this->bsec_status_ = bsec_init();
if (this->bsec_status_ != BSEC_OK) {
this->mark_failed();
return;
}
uint8_t new_idx = BME680BSECComponent::instances.size();
BME680BSECComponent::instances.push_back(this);
this->bme680_.dev_id = this->address_;
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;
@@ -35,29 +40,30 @@ void BME680BSECComponent::setup() {
return;
}
if (this->sample_rate_ == SAMPLE_RATE_ULP) {
const uint8_t bsec_config[] = {
#include "config/generic_33v_300s_28d/bsec_iaq.txt"
};
this->set_config_(bsec_config);
} else {
const uint8_t bsec_config[] = {
#include "config/generic_33v_3s_28d/bsec_iaq.txt"
};
this->set_config_(bsec_config);
}
this->update_subscription_();
if (this->bsec_status_ != BSEC_OK) {
// Initialize the BSEC library
if (this->reinit_bsec_lib_() != 0) {
this->mark_failed();
return;
}
// Load the BSEC library state from storage
this->load_state_();
}
void BME680BSECComponent::set_config_(const uint8_t *config) {
uint8_t work_buffer[BSEC_MAX_WORKBUFFER_SIZE];
this->bsec_status_ = bsec_set_configuration(config, BSEC_MAX_PROPERTY_BLOB_SIZE, work_buffer, sizeof(work_buffer));
void BME680BSECComponent::set_config_() {
if (this->sample_rate_ == SAMPLE_RATE_ULP) {
const uint8_t config[] = {
#include "config/generic_33v_300s_28d/bsec_iaq.txt"
};
this->bsec_status_ =
bsec_set_configuration(config, BSEC_MAX_PROPERTY_BLOB_SIZE, this->work_buffer_, sizeof(this->work_buffer_));
} else {
const uint8_t config[] = {
#include "config/generic_33v_3s_28d/bsec_iaq.txt"
};
this->bsec_status_ =
bsec_set_configuration(config, BSEC_MAX_PROPERTY_BLOB_SIZE, this->work_buffer_, sizeof(this->work_buffer_));
}
}
float BME680BSECComponent::calc_sensor_sample_rate_(SampleRate sample_rate) {
@@ -118,10 +124,12 @@ void BME680BSECComponent::update_subscription_() {
uint8_t num_sensor_settings = BSEC_MAX_PHYSICAL_SENSOR;
this->bsec_status_ =
bsec_update_subscription(virtual_sensors, num_virtual_sensors, sensor_settings, &num_sensor_settings);
ESP_LOGV(TAG, "%s: updating subscription for %d virtual sensors (out=%d sensors)", this->device_id_.c_str(),
num_virtual_sensors, num_sensor_settings);
}
void BME680BSECComponent::dump_config() {
ESP_LOGCONFIG(TAG, "BME680 via BSEC:");
ESP_LOGCONFIG(TAG, "%s via BSEC:", this->device_id_.c_str());
bsec_version_t version;
bsec_get_version(&version);
@@ -185,23 +193,31 @@ void BME680BSECComponent::run_() {
return;
}
ESP_LOGV(TAG, "Performing sensor run");
ESP_LOGV(TAG, "%s: Performing sensor run", this->device_id_.c_str());
bsec_bme_settings_t bme680_settings;
this->bsec_status_ = bsec_sensor_control(curr_time_ns, &bme680_settings);
// Restore BSEC library state
// The reinit_bsec_lib_ method is computationally expensive: it takes 1200÷2900 microseconds on a ESP32.
// It can be skipped entirely when there is only one device (since the BSEC library won't be shared)
if (BME680BSECComponent::instances.size() > 1) {
int res = this->reinit_bsec_lib_();
if (res != 0)
return;
}
this->bsec_status_ = bsec_sensor_control(curr_time_ns, &this->bme680_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_ = bme680_settings.next_call;
this->next_call_ns_ = this->bme680_settings_.next_call;
if (bme680_settings.trigger_measurement) {
this->bme680_.tph_sett.os_temp = bme680_settings.temperature_oversampling;
this->bme680_.tph_sett.os_pres = bme680_settings.pressure_oversampling;
this->bme680_.tph_sett.os_hum = bme680_settings.humidity_oversampling;
this->bme680_.gas_sett.run_gas = bme680_settings.run_gas;
this->bme680_.gas_sett.heatr_temp = bme680_settings.heater_temperature;
this->bme680_.gas_sett.heatr_dur = bme680_settings.heating_duration;
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_);
@@ -218,19 +234,26 @@ void BME680BSECComponent::run_() {
uint16_t meas_dur = 0;
bme680_get_profile_dur(&meas_dur, &this->bme680_);
// 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
// bsec_do_steps() call
if (BME680BSECComponent::instances.size() > 1)
this->snapshot_state_();
ESP_LOGV(TAG, "Queueing read in %ums", meas_dur);
this->set_timeout("read", meas_dur,
[this, curr_time_ns, bme680_settings]() { this->read_(curr_time_ns, bme680_settings); });
this->set_timeout("read", meas_dur, [this]() { this->read_(); });
} else {
ESP_LOGV(TAG, "Measurement not required");
this->read_(curr_time_ns, bme680_settings);
this->read_();
}
}
void BME680BSECComponent::read_(int64_t trigger_time_ns, bsec_bme_settings_t bme680_settings) {
ESP_LOGV(TAG, "Reading data");
void BME680BSECComponent::read_() {
ESP_LOGV(TAG, "%s: Reading data", this->device_id_.c_str());
int64_t curr_time_ns = this->get_time_ns_();
if (bme680_settings.trigger_measurement) {
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) {
@@ -239,7 +262,7 @@ void BME680BSECComponent::read_(int64_t trigger_time_ns, bsec_bme_settings_t bme
}
}
if (!bme680_settings.process_data) {
if (!this->bme680_settings_.process_data) {
ESP_LOGV(TAG, "Data processing not required");
return;
}
@@ -259,35 +282,35 @@ void BME680BSECComponent::read_(int64_t trigger_time_ns, bsec_bme_settings_t bme
bsec_input_t inputs[BSEC_MAX_PHYSICAL_SENSOR]; // Temperature, Pressure, Humidity & Gas Resistance
uint8_t num_inputs = 0;
if (bme680_settings.process_data & BSEC_PROCESS_TEMPERATURE) {
if (this->bme680_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].time_stamp = trigger_time_ns;
inputs[num_inputs].time_stamp = curr_time_ns;
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 = trigger_time_ns;
inputs[num_inputs].time_stamp = curr_time_ns;
num_inputs++;
}
if (bme680_settings.process_data & BSEC_PROCESS_HUMIDITY) {
if (this->bme680_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].time_stamp = trigger_time_ns;
inputs[num_inputs].time_stamp = curr_time_ns;
num_inputs++;
}
if (bme680_settings.process_data & BSEC_PROCESS_PRESSURE) {
if (this->bme680_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 = trigger_time_ns;
inputs[num_inputs].time_stamp = curr_time_ns;
num_inputs++;
}
if (bme680_settings.process_data & BSEC_PROCESS_GAS) {
if (this->bme680_settings_.process_data & BSEC_PROCESS_GAS) {
if (data.status & BME680_GASM_VALID_MSK) {
inputs[num_inputs].sensor_id = BSEC_INPUT_GASRESISTOR;
inputs[num_inputs].signal = data.gas_resistance;
inputs[num_inputs].time_stamp = trigger_time_ns;
inputs[num_inputs].time_stamp = curr_time_ns;
num_inputs++;
} else {
ESP_LOGD(TAG, "BME680 did not report gas data");
@@ -298,6 +321,22 @@ void BME680BSECComponent::read_(int64_t trigger_time_ns, bsec_bme_settings_t bme
return;
}
// Restore BSEC library state
// The reinit_bsec_lib_ method is computationally expensive: it takes 1200÷2900 microseconds on a ESP32.
// It can be skipped entirely when there is only one device (since the BSEC library won't be shared)
if (BME680BSECComponent::instances.size() > 1) {
int res = this->reinit_bsec_lib_();
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_);
if (this->bsec_status_ < BSEC_OK) {
ESP_LOGW(TAG, "Failed to fetch sensor control settings (BSEC Error Code %d)", this->bsec_status_);
return;
}
}
bsec_output_t outputs[BSEC_NUMBER_OUTPUTS];
uint8_t num_outputs = BSEC_NUMBER_OUTPUTS;
this->bsec_status_ = bsec_do_steps(inputs, num_inputs, outputs, &num_outputs);
@@ -305,6 +344,13 @@ void BME680BSECComponent::read_(int64_t trigger_time_ns, bsec_bme_settings_t bme
ESP_LOGW(TAG, "BSEC failed to process signals (BSEC Error Code %d)", this->bsec_status_);
return;
}
ESP_LOGV(TAG, "%s: after bsec_do_steps: num_inputs=%d num_outputs=%d", this->device_id_.c_str(), num_inputs,
num_outputs);
// 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
if (BME680BSECComponent::instances.size() > 1)
this->snapshot_state_();
if (num_outputs < 1) {
ESP_LOGD(TAG, "No signal outputs provided by BSEC");
return;
@@ -314,7 +360,7 @@ void BME680BSECComponent::read_(int64_t trigger_time_ns, bsec_bme_settings_t bme
}
void BME680BSECComponent::publish_(const bsec_output_t *outputs, uint8_t num_outputs) {
ESP_LOGV(TAG, "Queuing sensor state publish actions");
ESP_LOGV(TAG, "%s: Queuing sensor state publish actions", this->device_id_.c_str());
for (uint8_t i = 0; i < num_outputs; i++) {
float signal = outputs[i].signal;
switch (outputs[i].sensor_id) {
@@ -376,12 +422,20 @@ void BME680BSECComponent::publish_sensor_(text_sensor::TextSensor *sensor, const
sensor->publish_state(value);
}
int8_t BME680BSECComponent::read_bytes_wrapper(uint8_t address, uint8_t a_register, uint8_t *data, uint16_t len) {
return BME680BSECComponent::instance->read_bytes(a_register, data, len) ? 0 : -1;
// 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];
// Use the I2CDevice::read_bytes method to perform the actual I2C register read
return inst->read_bytes(a_register, data, len) ? 0 : -1;
}
int8_t BME680BSECComponent::write_bytes_wrapper(uint8_t address, uint8_t a_register, uint8_t *data, uint16_t len) {
return BME680BSECComponent::instance->write_bytes(a_register, 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];
// Use the I2CDevice::write_bytes method to perform the actual I2C register write
return inst->write_bytes(a_register, data, len) ? 0 : -1;
}
void BME680BSECComponent::delay_ms(uint32_t period) {
@@ -389,41 +443,97 @@ void BME680BSECComponent::delay_ms(uint32_t period) {
delay(period);
}
// Fetch the BSEC library state and save it in the bsec_state_data_ member (volatile memory)
// Used to share the library when using more than one sensor
void BME680BSECComponent::snapshot_state_() {
uint32_t num_serialized_state = BSEC_MAX_STATE_BLOB_SIZE;
this->bsec_status_ = bsec_get_state(0, this->bsec_state_data_, BSEC_MAX_STATE_BLOB_SIZE, this->work_buffer_,
sizeof(this->work_buffer_), &num_serialized_state);
if (this->bsec_status_ != BSEC_OK) {
ESP_LOGW(TAG, "%s: Failed to fetch BSEC library state for snapshot (BSEC Error Code %d)", this->device_id_.c_str(),
this->bsec_status_);
return;
}
this->bsec_state_data_valid_ = true;
}
// Restores the BSEC library state from a snapshot in memory
// Used to share the library when using more than one sensor
void BME680BSECComponent::restore_state_() {
if (!this->bsec_state_data_valid_) {
ESP_LOGV(TAG, "%s: BSEC state data NOT valid, aborting restore_state_()", this->device_id_.c_str());
return;
}
this->bsec_status_ =
bsec_set_state(this->bsec_state_data_, BSEC_MAX_STATE_BLOB_SIZE, this->work_buffer_, sizeof(this->work_buffer_));
if (this->bsec_status_ != BSEC_OK) {
ESP_LOGW(TAG, "Failed to restore BSEC library state (BSEC Error Code %d)", this->bsec_status_);
return;
}
}
int BME680BSECComponent::reinit_bsec_lib_() {
this->bsec_status_ = bsec_init();
if (this->bsec_status_ != BSEC_OK) {
this->mark_failed();
return -1;
}
this->set_config_();
if (this->bsec_status_ != BSEC_OK) {
this->mark_failed();
return -2;
}
this->restore_state_();
this->update_subscription_();
if (this->bsec_status_ != BSEC_OK) {
this->mark_failed();
return -3;
}
return 0;
}
void BME680BSECComponent::load_state_() {
uint32_t hash = fnv1_hash("bme680_bsec_state_" + to_string(this->address_));
uint32_t hash = fnv1_hash("bme680_bsec_state_" + this->device_id_);
this->bsec_state_ = global_preferences->make_preference<uint8_t[BSEC_MAX_STATE_BLOB_SIZE]>(hash, true);
uint8_t state[BSEC_MAX_STATE_BLOB_SIZE];
if (this->bsec_state_.load(&state)) {
ESP_LOGV(TAG, "Loading state");
uint8_t work_buffer[BSEC_MAX_WORKBUFFER_SIZE];
this->bsec_status_ = bsec_set_state(state, BSEC_MAX_STATE_BLOB_SIZE, work_buffer, sizeof(work_buffer));
if (this->bsec_status_ != BSEC_OK) {
ESP_LOGW(TAG, "Failed to load state (BSEC Error Code %d)", this->bsec_status_);
}
ESP_LOGI(TAG, "Loaded state");
if (!this->bsec_state_.load(&this->bsec_state_data_)) {
// No saved BSEC library state available
return;
}
ESP_LOGV(TAG, "%s: Loading BSEC library state", this->device_id_.c_str());
this->bsec_status_ =
bsec_set_state(this->bsec_state_data_, BSEC_MAX_STATE_BLOB_SIZE, this->work_buffer_, sizeof(this->work_buffer_));
if (this->bsec_status_ != BSEC_OK) {
ESP_LOGW(TAG, "%s: Failed to load BSEC library state (BSEC Error Code %d)", this->device_id_.c_str(),
this->bsec_status_);
return;
}
// All OK: set the BSEC state data as valid
this->bsec_state_data_valid_ = true;
ESP_LOGI(TAG, "%s: Loaded BSEC library state", this->device_id_.c_str());
}
void BME680BSECComponent::save_state_(uint8_t accuracy) {
if (accuracy < 3 || (millis() - this->last_state_save_ms_ < this->state_save_interval_ms_)) {
return;
}
ESP_LOGV(TAG, "Saving state");
uint8_t state[BSEC_MAX_STATE_BLOB_SIZE];
uint8_t work_buffer[BSEC_MAX_STATE_BLOB_SIZE];
uint32_t num_serialized_state = BSEC_MAX_STATE_BLOB_SIZE;
this->bsec_status_ =
bsec_get_state(0, state, BSEC_MAX_STATE_BLOB_SIZE, work_buffer, BSEC_MAX_STATE_BLOB_SIZE, &num_serialized_state);
if (this->bsec_status_ != BSEC_OK) {
ESP_LOGW(TAG, "Failed fetch state for save (BSEC Error Code %d)", this->bsec_status_);
return;
if (BME680BSECComponent::instances.size() <= 1) {
// When a single device is in use, no snapshot is taken regularly so one is taken now
// On multiple devices, a snapshot is taken at every loop, so there is no need to take one here
this->snapshot_state_();
}
if (!this->bsec_state_data_valid_)
return;
if (!this->bsec_state_.save(&state)) {
ESP_LOGV(TAG, "%s: Saving state", this->device_id_.c_str());
if (!this->bsec_state_.save(&this->bsec_state_data_)) {
ESP_LOGW(TAG, "Failed to save state");
return;
}
+22 -7
View File
@@ -31,6 +31,7 @@ enum SampleRate {
class BME680BSECComponent : public Component, public i2c::I2CDevice {
public:
void set_device_id(const std::string &devid) { this->device_id_.assign(devid); }
void set_temperature_offset(float offset) { this->temperature_offset_ = offset; }
void set_iaq_mode(IAQMode iaq_mode) { this->iaq_mode_ = iaq_mode; }
void set_state_save_interval(uint32_t interval) { this->state_save_interval_ms_ = interval; }
@@ -50,9 +51,9 @@ class BME680BSECComponent : public Component, public i2c::I2CDevice {
void set_co2_equivalent_sensor(sensor::Sensor *sensor) { this->co2_equivalent_sensor_ = sensor; }
void set_breath_voc_equivalent_sensor(sensor::Sensor *sensor) { this->breath_voc_equivalent_sensor_ = sensor; }
static BME680BSECComponent *instance;
static int8_t read_bytes_wrapper(uint8_t address, uint8_t a_register, uint8_t *data, uint16_t len);
static int8_t write_bytes_wrapper(uint8_t address, uint8_t a_register, uint8_t *data, uint16_t len);
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);
void setup() override;
@@ -61,23 +62,33 @@ class BME680BSECComponent : public Component, public i2c::I2CDevice {
void loop() override;
protected:
void set_config_(const uint8_t *config);
void set_config_();
float calc_sensor_sample_rate_(SampleRate sample_rate);
void update_subscription_();
void run_();
void read_(int64_t trigger_time_ns, bsec_bme_settings_t bme680_settings);
void read_();
void publish_(const bsec_output_t *outputs, uint8_t num_outputs);
int64_t get_time_ns_();
void publish_sensor_(sensor::Sensor *sensor, float value, bool change_only = false);
void publish_sensor_(text_sensor::TextSensor *sensor, const std::string &value);
void load_state_();
void save_state_(uint8_t accuracy);
void snapshot_state_(); // Fetch the current BSEC library state and save it in the bsec_state_data_ member (volatile
// memory)
void restore_state_(); // Push the state contained in the bsec_state_data_ member (volatile memory) to the BSEC
// library
int reinit_bsec_lib_(); // Prepare the BSEC library to be used again after this object returns active
// (as the library may have been used by other objects)
void load_state_(); // Initialize the ESP preferences object; retrieve the BSEC library state from the ESP
// preferences (storage); then save it in the bsec_state_data_ member (volatile memory) and
// push it to the BSEC library
void save_state_(
uint8_t accuracy); // Save the bsec_state_data_ member (volatile memory) to the ESP preferences (storage)
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_;
bsec_library_return_t bsec_status_{BSEC_OK};
int8_t bme680_status_{BME680_OK};
@@ -88,10 +99,14 @@ class BME680BSECComponent : public Component, public i2c::I2CDevice {
std::queue<std::function<void()>> queue_;
bool bsec_state_data_valid_;
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_;
std::string device_id_;
float temperature_offset_{0};
IAQMode iaq_mode_{IAQ_MODE_STATIC};