8static const char *
const TAG =
"bme680_bsec.sensor";
10static const std::string IAQ_ACCURACY_STATES[4] = {
"Stabilizing",
"Uncertain",
"Calibrating",
"Calibrated"};
12std::vector<BME680BSECComponent *>
28 this->
bme680_.intf = BME680_I2C_INTF;
53 const uint8_t config[] = {
54#include "config/generic_33v_300s_28d/bsec_iaq.txt"
59 const uint8_t config[] = {
60#include "config/generic_18v_300s_28d/bsec_iaq.txt"
67 const uint8_t config[] = {
68#include "config/generic_33v_3s_28d/bsec_iaq.txt"
73 const uint8_t config[] = {
74#include "config/generic_18v_3s_28d/bsec_iaq.txt"
86 return sample_rate ==
SAMPLE_RATE_ULP ? BSEC_SAMPLE_RATE_ULP : BSEC_SAMPLE_RATE_LP;
90 bsec_sensor_configuration_t virtual_sensors[BSEC_NUMBER_OUTPUTS];
91 int num_virtual_sensors = 0;
94 virtual_sensors[num_virtual_sensors].sensor_id =
97 num_virtual_sensors++;
101 virtual_sensors[num_virtual_sensors].sensor_id = BSEC_OUTPUT_CO2_EQUIVALENT;
103 num_virtual_sensors++;
107 virtual_sensors[num_virtual_sensors].sensor_id = BSEC_OUTPUT_BREATH_VOC_EQUIVALENT;
109 num_virtual_sensors++;
113 virtual_sensors[num_virtual_sensors].sensor_id = BSEC_OUTPUT_RAW_PRESSURE;
115 num_virtual_sensors++;
119 virtual_sensors[num_virtual_sensors].sensor_id = BSEC_OUTPUT_RAW_GAS;
121 num_virtual_sensors++;
125 virtual_sensors[num_virtual_sensors].sensor_id = BSEC_OUTPUT_SENSOR_HEAT_COMPENSATED_TEMPERATURE;
127 num_virtual_sensors++;
131 virtual_sensors[num_virtual_sensors].sensor_id = BSEC_OUTPUT_SENSOR_HEAT_COMPENSATED_HUMIDITY;
133 num_virtual_sensors++;
136 bsec_sensor_configuration_t sensor_settings[BSEC_MAX_PHYSICAL_SENSOR];
137 uint8_t num_sensor_settings = BSEC_MAX_PHYSICAL_SENSOR;
139 bsec_update_subscription(virtual_sensors, num_virtual_sensors, sensor_settings, &num_sensor_settings);
140 ESP_LOGV(TAG,
"%s: updating subscription for %d virtual sensors (out=%d sensors)", this->
device_id_.c_str(),
141 num_virtual_sensors, num_sensor_settings);
145 ESP_LOGCONFIG(TAG,
"%s via BSEC:", this->
device_id_.c_str());
147 bsec_version_t version;
148 bsec_get_version(&version);
149 ESP_LOGCONFIG(TAG,
" BSEC Version: %d.%d.%d.%d", version.major, version.minor, version.major_bugfix,
150 version.minor_bugfix);
152 LOG_I2C_DEVICE(
this);
155 ESP_LOGE(TAG,
"Communication failed (BSEC Status: %d, BME680 Status: %d)", this->
bsec_status_,
160 " Temperature Offset: %.2f\n"
162 " Supply Voltage: %sV\n"
164 " State Save Interval: %ims",
199 if (this->
queue_.size()) {
200 auto action = std::move(this->
queue_.front());
212 ESP_LOGV(TAG,
"%s: Performing sensor run", this->
device_id_.c_str());
217 if (BME680BSECComponent::instances.
size() > 1) {
225 ESP_LOGW(TAG,
"Failed to fetch sensor control settings (BSEC Error Code %d)", this->
bsec_status_);
237 this->
bme680_.power_mode = BME680_FORCED_MODE;
238 uint16_t desired_settings = BME680_OST_SEL | BME680_OSP_SEL | BME680_OSH_SEL | BME680_GAS_SENSOR_SEL;
241 ESP_LOGW(TAG,
"Failed to set sensor settings (BME680 Error Code %d)", this->
bme680_status_);
247 ESP_LOGW(TAG,
"Failed to set sensor mode (BME680 Error Code %d)", this->
bme680_status_);
251 uint16_t meas_dur = 0;
252 bme680_get_profile_dur(&meas_dur, &this->
bme680_);
257 if (BME680BSECComponent::instances.
size() > 1)
260 ESP_LOGV(TAG,
"Queueing read in %ums", meas_dur);
263 ESP_LOGV(TAG,
"Measurement not required");
269 ESP_LOGV(TAG,
"%s: Reading data", this->
device_id_.c_str());
274 while (this->
bme680_.power_mode != BME680_SLEEP_MODE) {
275 if (
millis() - start > 50) {
276 ESP_LOGE(TAG,
"Timeout waiting for BME680 to enter sleep mode");
281 ESP_LOGE(TAG,
"Failed to get sensor mode (BME680 Error Code %d)", this->
bme680_status_);
288 ESP_LOGV(TAG,
"Data processing not required");
292 struct bme680_field_data data;
295 if (this->bme680_status_ != BME680_OK) {
296 ESP_LOGW(TAG,
"Failed to get sensor data (BME680 Error Code %d)", this->bme680_status_);
299 if (!(data.status & BME680_NEW_DATA_MSK)) {
300 ESP_LOGD(TAG,
"BME680 did not report new data");
304 bsec_input_t inputs[BSEC_MAX_PHYSICAL_SENSOR];
305 uint8_t num_inputs = 0;
308 inputs[num_inputs].sensor_id = BSEC_INPUT_TEMPERATURE;
309 inputs[num_inputs].signal = data.temperature / 100.0f;
310 inputs[num_inputs].time_stamp = curr_time_ns;
314 inputs[num_inputs].sensor_id = BSEC_INPUT_HEATSOURCE;
316 inputs[num_inputs].time_stamp = curr_time_ns;
320 inputs[num_inputs].sensor_id = BSEC_INPUT_HUMIDITY;
321 inputs[num_inputs].signal = data.humidity / 1000.0f;
322 inputs[num_inputs].time_stamp = curr_time_ns;
326 inputs[num_inputs].sensor_id = BSEC_INPUT_PRESSURE;
327 inputs[num_inputs].signal = data.pressure;
328 inputs[num_inputs].time_stamp = curr_time_ns;
332 if (data.status & BME680_GASM_VALID_MSK) {
333 inputs[num_inputs].sensor_id = BSEC_INPUT_GASRESISTOR;
334 inputs[num_inputs].signal = data.gas_resistance;
335 inputs[num_inputs].time_stamp = curr_time_ns;
338 ESP_LOGD(TAG,
"BME680 did not report gas data");
341 if (num_inputs < 1) {
342 ESP_LOGD(TAG,
"No signal inputs available for BSEC");
357 ESP_LOGW(TAG,
"Failed to fetch sensor control settings (BSEC Error Code %d)", this->
bsec_status_);
362 bsec_output_t outputs[BSEC_NUMBER_OUTPUTS];
363 uint8_t num_outputs = BSEC_NUMBER_OUTPUTS;
364 this->
bsec_status_ = bsec_do_steps(inputs, num_inputs, outputs, &num_outputs);
366 ESP_LOGW(TAG,
"BSEC failed to process signals (BSEC Error Code %d)", this->
bsec_status_);
369 ESP_LOGV(TAG,
"%s: after bsec_do_steps: num_inputs=%d num_outputs=%d", this->
device_id_.c_str(), num_inputs,
376 if (num_outputs < 1) {
377 ESP_LOGD(TAG,
"No signal outputs provided by BSEC");
381 this->
publish_(outputs, num_outputs);
385 ESP_LOGV(TAG,
"%s: Queuing sensor state publish actions", this->
device_id_.c_str());
386 for (uint8_t i = 0; i < num_outputs; i++) {
387 float signal = outputs[i].signal;
388 switch (outputs[i].sensor_id) {
389 case BSEC_OUTPUT_IAQ:
390 case BSEC_OUTPUT_STATIC_IAQ: {
391 uint8_t accuracy = std::min<uint8_t>(outputs[i].accuracy, std::size(IAQ_ACCURACY_STATES) - 1);
401 case BSEC_OUTPUT_CO2_EQUIVALENT:
404 case BSEC_OUTPUT_BREATH_VOC_EQUIVALENT:
407 case BSEC_OUTPUT_RAW_PRESSURE:
410 case BSEC_OUTPUT_RAW_GAS:
413 case BSEC_OUTPUT_SENSOR_HEAT_COMPENSATED_TEMPERATURE:
416 case BSEC_OUTPUT_SENSOR_HEAT_COMPENSATED_HUMIDITY:
424 int64_t time_ms =
millis();
434 if (!sensor || (change_only && sensor->
has_state() && sensor->
state == value)) {
464 ESP_LOGV(TAG,
"Delaying for %ums", period);
471 uint32_t num_serialized_state = BSEC_MAX_STATE_BLOB_SIZE;
475 ESP_LOGW(TAG,
"%s: Failed to fetch BSEC library state for snapshot (BSEC Error Code %d)", this->
device_id_.c_str(),
486 ESP_LOGV(TAG,
"%s: BSEC state data NOT valid, aborting restore_state_()", this->
device_id_.c_str());
493 ESP_LOGW(TAG,
"Failed to restore BSEC library state (BSEC Error Code %d)", this->
bsec_status_);
531 ESP_LOGV(TAG,
"%s: Loading BSEC library state", this->
device_id_.c_str());
535 ESP_LOGW(TAG,
"%s: Failed to load BSEC library state (BSEC Error Code %d)", this->
device_id_.c_str(),
541 ESP_LOGI(TAG,
"%s: Loaded BSEC library state", this->
device_id_.c_str());
548 if (BME680BSECComponent::instances.
size() <= 1) {
556 ESP_LOGV(TAG,
"%s: Saving state", this->
device_id_.c_str());
559 ESP_LOGW(TAG,
"Failed to save state");
564 ESP_LOGI(TAG,
"Saved state");
void mark_failed()
Mark this component as failed.
void status_set_warning()
ESPDEPRECATED("Use const char* or uint32_t overload instead. Removed in 2026.7.0", "2026.1.0") void set_timeout(const std voi set_timeout)(const char *name, uint32_t timeout, std::function< void()> &&f)
Set a timeout function with a unique name.
void status_clear_error()
void status_clear_warning()
ESPPreferenceObject bsec_state_
bsec_library_return_t bsec_status_
uint32_t state_save_interval_ms_
SampleRate pressure_sample_rate_
uint32_t millis_overflow_counter_
float temperature_offset_
sensor::Sensor * iaq_accuracy_sensor_
std::queue< std::function< void()> > queue_
static std::vector< BME680BSECComponent * > instances
sensor::Sensor * humidity_sensor_
SampleRate temperature_sample_rate_
static int8_t read_bytes_wrapper(uint8_t devid, uint8_t a_register, uint8_t *data, uint16_t len)
bool bsec_state_data_valid_
sensor::Sensor * breath_voc_equivalent_sensor_
SupplyVoltage supply_voltage_
void save_state_(uint8_t accuracy)
sensor::Sensor * iaq_sensor_
static uint8_t work_buffer_[BSEC_MAX_WORKBUFFER_SIZE]
void queue_push_(std::function< void()> &&f)
float calc_sensor_sample_rate_(SampleRate sample_rate)
uint32_t last_state_save_ms_
sensor::Sensor * co2_equivalent_sensor_
void dump_config() override
static int8_t write_bytes_wrapper(uint8_t devid, uint8_t a_register, uint8_t *data, uint16_t len)
sensor::Sensor * pressure_sensor_
uint8_t bsec_state_data_[BSEC_MAX_STATE_BLOB_SIZE]
void publish_(const bsec_output_t *outputs, uint8_t num_outputs)
void publish_sensor_(sensor::Sensor *sensor, float value, bool change_only=false)
text_sensor::TextSensor * iaq_accuracy_text_sensor_
SampleRate humidity_sample_rate_
bsec_bme_settings_t bme680_settings_
sensor::Sensor * temperature_sensor_
sensor::Sensor * gas_resistance_sensor_
void update_subscription_()
struct bme680_dev bme680_
static void delay_ms(uint32_t period)
bool write_bytes(uint8_t a_register, const uint8_t *data, uint8_t len) const
bool read_bytes(uint8_t a_register, uint8_t *data, uint8_t len)
Compat APIs All methods below have been added for compatibility reasons.
Base-class for all sensors.
void publish_state(float state)
Publish a new state to the front-end.
float state
This member variable stores the last state that has passed through all filters.
void publish_state(const std::string &state)
constexpr uint32_t fnv1_hash_extend(uint32_t hash, T value)
Extend a FNV-1 hash with an integer (hashes each byte).
ESPPreferences * global_preferences
uint32_t fnv1_hash(const char *str)
Calculate a FNV-1 hash of str.
void HOT delay(uint32_t ms)
uint32_t IRAM_ATTR HOT millis()
ESPPreferenceObject make_preference(size_t, uint32_t, bool)