12#define BME68X_BSEC2_ALGORITHM_OUTPUT_LOG(a) (a == ALGORITHM_OUTPUT_CLASSIFICATION ? "Classification" : "Regression")
13#define BME68X_BSEC2_OPERATING_AGE_LOG(o) (o == OPERATING_AGE_4D ? "4 days" : "28 days")
14#define BME68X_BSEC2_SAMPLE_RATE_LOG(r) (r == SAMPLE_RATE_DEFAULT ? "Default" : (r == SAMPLE_RATE_ULP ? "ULP" : "LP"))
15#define BME68X_BSEC2_VOLTAGE_LOG(v) (v == VOLTAGE_3_3V ? "3.3V" : "1.8V")
19static constexpr uint32_t READ_TIMEOUT_ID = 0;
20static constexpr uint32_t RUN_TIMEOUT_ID = 1;
22static constexpr const char *
const IAQ_ACCURACY_STATES[4] = {
"Stabilizing",
"Uncertain",
"Calibrating",
"Calibrated"};
24static bool is_no_new_data_warning(int8_t status) {
25#ifdef BME68X_W_NO_NEW_DATA
26 return status == BME68X_W_NO_NEW_DATA;
38 ESP_LOGE(TAG,
"bsec_init_m failed: status %d", this->
bsec_status_);
47 ESP_LOGE(TAG,
"bme68x_init failed: status %d", this->
bme68x_status_);
54 ESP_LOGE(TAG,
"bsec_set_configuration_m failed: status %d", this->
bsec_status_);
62 ESP_LOGE(TAG,
"bsec_update_subscription_m failed: status %d", this->
bsec_status_);
74 " BSEC2 version: %d.%d.%d.%d\n"
75 " BSEC2 configuration blob:\n"
77 this->
version_.major, this->version_.minor, this->version_.major_bugfix, this->version_.minor_bugfix,
84 ESP_LOGE(TAG,
"Communication failed (BSEC2 status: %d, BME68X status: %d)", this->
bsec_status_,
86 if (this->
bsec_status_ == BSEC_I_SU_SUBSCRIBEDOUTPUTGATES) {
87 ESP_LOGE(TAG,
"No sensors, add at least one sensor to the config");
92 ESP_LOGCONFIG(TAG,
" Algorithm output: %s", BME68X_BSEC2_ALGORITHM_OUTPUT_LOG(this->
algorithm_output_));
95 " Operating age: %s\n"
98 " State save interval: %" PRIu32
"ms\n"
99 " Temperature offset: %.2f",
117#ifdef USE_TEXT_SENSOR
135 if (this->
queue_.size()) {
136 auto action = std::move(this->
queue_.front());
145 if (
len > BSEC_MAX_PROPERTY_BLOB_SIZE) {
146 ESP_LOGE(TAG,
"Configuration blob too large");
150 uint8_t work_buffer[BSEC_MAX_PROPERTY_BLOB_SIZE];
161 return sample_rate ==
SAMPLE_RATE_ULP ? BSEC_SAMPLE_RATE_ULP : BSEC_SAMPLE_RATE_LP;
165 bsec_sensor_configuration_t virtual_sensors[BSEC_NUMBER_OUTPUTS];
166 uint8_t num_virtual_sensors = 0;
169 virtual_sensors[num_virtual_sensors].sensor_id = BSEC_OUTPUT_IAQ;
171 num_virtual_sensors++;
175 virtual_sensors[num_virtual_sensors].sensor_id = BSEC_OUTPUT_STATIC_IAQ;
177 num_virtual_sensors++;
181 virtual_sensors[num_virtual_sensors].sensor_id = BSEC_OUTPUT_CO2_EQUIVALENT;
183 num_virtual_sensors++;
187 virtual_sensors[num_virtual_sensors].sensor_id = BSEC_OUTPUT_BREATH_VOC_EQUIVALENT;
189 num_virtual_sensors++;
193 virtual_sensors[num_virtual_sensors].sensor_id = BSEC_OUTPUT_RAW_PRESSURE;
195 num_virtual_sensors++;
199 virtual_sensors[num_virtual_sensors].sensor_id = BSEC_OUTPUT_RAW_GAS;
201 num_virtual_sensors++;
205 virtual_sensors[num_virtual_sensors].sensor_id = BSEC_OUTPUT_SENSOR_HEAT_COMPENSATED_TEMPERATURE;
207 num_virtual_sensors++;
211 virtual_sensors[num_virtual_sensors].sensor_id = BSEC_OUTPUT_SENSOR_HEAT_COMPENSATED_HUMIDITY;
213 num_virtual_sensors++;
216 bsec_sensor_configuration_t sensor_settings[BSEC_MAX_PHYSICAL_SENSOR];
217 uint8_t num_sensor_settings = BSEC_MAX_PHYSICAL_SENSOR;
219 sensor_settings, &num_sensor_settings);
225 ESP_LOGV(TAG,
"Performing sensor run");
227 struct bme68x_conf bme68x_conf;
229 if (this->bsec_status_ < BSEC_OK) {
230 ESP_LOGW(TAG,
"Fetching control settings failed (BSEC2 error code %d)", this->bsec_status_);
238 case BME68X_FORCED_MODE:
239 bme68x_get_conf(&bme68x_conf, &this->
bme68x_);
242 bme68x_conf.os_temp = this->
bsec_settings_.temperature_oversampling;
244 bme68x_set_conf(&bme68x_conf, &this->
bme68x_);
252 this->
op_mode_ = BME68X_FORCED_MODE;
253 ESP_LOGV(TAG,
"Using forced mode");
256 case BME68X_PARALLEL_MODE:
258 bme68x_get_conf(&bme68x_conf, &this->
bme68x_);
261 bme68x_conf.os_temp = this->
bsec_settings_.temperature_oversampling;
263 bme68x_set_conf(&bme68x_conf, &this->
bme68x_);
270 BSEC_TOTAL_HEAT_DUR -
271 (bme68x_get_meas_dur(BME68X_PARALLEL_MODE, &bme68x_conf, &this->
bme68x_) / INT64_C(1000));
274 this->bme68x_status_ = bme68x_set_op_mode(BME68X_PARALLEL_MODE, &this->
bme68x_);
275 this->
op_mode_ = BME68X_PARALLEL_MODE;
276 ESP_LOGV(TAG,
"Using parallel mode");
279 case BME68X_SLEEP_MODE:
281 bme68x_set_op_mode(BME68X_SLEEP_MODE, &this->
bme68x_);
283 ESP_LOGV(TAG,
"Using sleep mode");
288 if (this->
bsec_settings_.trigger_measurement && this->bsec_settings_.op_mode != BME68X_SLEEP_MODE) {
289 bme68x_get_conf(&bme68x_conf, &this->
bme68x_);
291 ESP_LOGV(TAG,
"Queueing read in %" PRIu32
"us", meas_dur);
293 this->
set_timeout(READ_TIMEOUT_ID, meas_dur / 1000, [
this]() {
298 ESP_LOGV(TAG,
"Measurement not required, queueing immediate read");
308 uint32_t delay_ms = delay_ns > 0 ?
static_cast<uint32_t>((delay_ns + 999999) / 1000000) : 1;
309 ESP_LOGV(TAG,
"Next run in: %" PRIu32
"ms", delay_ms);
310 this->
set_timeout(RUN_TIMEOUT_ID, delay_ms, [
this]() { this->
run_(); });
315 ESP_LOGV(TAG,
"Reading data");
318 ESP_LOGV(TAG,
"Data processing not required");
322 struct bme68x_data data[3];
327 ESP_LOGV(TAG,
"BME68X did not provide new data");
331 ESP_LOGW(TAG,
"Fetching data failed (BME68X error code %d)", this->
bme68x_status_);
335 ESP_LOGV(TAG,
"BME68X did not provide new fields");
339 for (uint8_t i = 0; i < nFields; i++) {
340 bsec_input_t inputs[BSEC_MAX_PHYSICAL_SENSOR];
341 uint8_t num_inputs = 0;
343 if (BSEC_CHECK_INPUT(this->
bsec_settings_.process_data, BSEC_INPUT_TEMPERATURE)) {
344 inputs[num_inputs].sensor_id = BSEC_INPUT_TEMPERATURE;
345 inputs[num_inputs].signal = data[i].temperature;
346 inputs[num_inputs].time_stamp = trigger_time_ns;
349 if (BSEC_CHECK_INPUT(this->
bsec_settings_.process_data, BSEC_INPUT_HEATSOURCE)) {
350 inputs[num_inputs].sensor_id = BSEC_INPUT_HEATSOURCE;
352 inputs[num_inputs].time_stamp = trigger_time_ns;
355 if (BSEC_CHECK_INPUT(this->
bsec_settings_.process_data, BSEC_INPUT_HUMIDITY)) {
356 inputs[num_inputs].sensor_id = BSEC_INPUT_HUMIDITY;
357 inputs[num_inputs].signal = data[i].humidity;
358 inputs[num_inputs].time_stamp = trigger_time_ns;
361 if (BSEC_CHECK_INPUT(this->
bsec_settings_.process_data, BSEC_INPUT_PRESSURE)) {
362 inputs[num_inputs].sensor_id = BSEC_INPUT_PRESSURE;
363 inputs[num_inputs].signal = data[i].pressure;
364 inputs[num_inputs].time_stamp = trigger_time_ns;
367 if (BSEC_CHECK_INPUT(this->
bsec_settings_.process_data, BSEC_INPUT_GASRESISTOR)) {
368 if (data[i].
status & BME68X_GASM_VALID_MSK) {
369 inputs[num_inputs].sensor_id = BSEC_INPUT_GASRESISTOR;
370 inputs[num_inputs].signal = data[i].gas_resistance;
371 inputs[num_inputs].time_stamp = trigger_time_ns;
374 ESP_LOGD(TAG,
"BME68X did not report gas data");
377 if (BSEC_CHECK_INPUT(this->
bsec_settings_.process_data, BSEC_INPUT_PROFILE_PART) &&
378 (data[i].status & BME68X_GASM_VALID_MSK)) {
379 inputs[num_inputs].sensor_id = BSEC_INPUT_PROFILE_PART;
380 inputs[num_inputs].signal = (this->
op_mode_ == BME68X_FORCED_MODE) ? 0 : data[i].gas_index;
381 inputs[num_inputs].time_stamp = trigger_time_ns;
385 if (num_inputs < 1) {
386 ESP_LOGD(TAG,
"No signal inputs available for BSEC2");
390 bsec_output_t outputs[BSEC_NUMBER_OUTPUTS];
391 uint8_t num_outputs = BSEC_NUMBER_OUTPUTS;
394 ESP_LOGW(TAG,
"Signal processing failed (BSEC2 error code %d)", this->
bsec_status_);
397 if (num_outputs < 1) {
398 ESP_LOGD(TAG,
"No signal outputs provided by BSEC2");
402 this->
publish_(outputs, num_outputs);
407 ESP_LOGV(TAG,
"Publishing sensor states");
408 bool update_accuracy =
false;
409 uint8_t max_accuracy = 0;
410 for (uint8_t i = 0; i < num_outputs; i++) {
411 float signal = outputs[i].signal;
412 switch (outputs[i].sensor_id) {
413 case BSEC_OUTPUT_IAQ:
414 max_accuracy = std::max(outputs[i].accuracy, max_accuracy);
415 update_accuracy =
true;
420 case BSEC_OUTPUT_STATIC_IAQ:
421 max_accuracy = std::max(outputs[i].accuracy, max_accuracy);
422 update_accuracy =
true;
427 case BSEC_OUTPUT_CO2_EQUIVALENT:
432 case BSEC_OUTPUT_BREATH_VOC_EQUIVALENT:
437 case BSEC_OUTPUT_RAW_PRESSURE:
442 case BSEC_OUTPUT_RAW_GAS:
447 case BSEC_OUTPUT_SENSOR_HEAT_COMPENSATED_TEMPERATURE:
452 case BSEC_OUTPUT_SENSOR_HEAT_COMPENSATED_HUMIDITY:
459 if (update_accuracy) {
460 max_accuracy = std::min<uint8_t>(max_accuracy, std::size(IAQ_ACCURACY_STATES) - 1);
465#ifdef USE_TEXT_SENSOR
477 int64_t time_ms =
millis();
488 if (!sensor || (change_only && sensor->
has_state() && sensor->
state == value)) {
495#ifdef USE_TEXT_SENSOR
508 uint8_t
state[BSEC_MAX_STATE_BLOB_SIZE];
510 ESP_LOGV(TAG,
"Loading state");
511 uint8_t work_buffer[BSEC_MAX_WORKBUFFER_SIZE];
513 bsec_set_state_m(&this->
bsec_instance_, state, BSEC_MAX_STATE_BLOB_SIZE, work_buffer,
sizeof(work_buffer));
515 ESP_LOGW(TAG,
"Failed to load state (BSEC2 error code %d)", this->
bsec_status_);
518 ESP_LOGI(TAG,
"Loaded state");
528 ESP_LOGV(TAG,
"Saving state");
530 uint8_t
state[BSEC_MAX_STATE_BLOB_SIZE];
531 uint8_t work_buffer[BSEC_MAX_STATE_BLOB_SIZE];
532 uint32_t num_serialized_state = BSEC_MAX_STATE_BLOB_SIZE;
535 BSEC_MAX_STATE_BLOB_SIZE, &num_serialized_state);
537 ESP_LOGW(TAG,
"Failed fetch state for save (BSEC2 error code %d)", this->
bsec_status_);
543 ESP_LOGW(TAG,
"Failed to save state");
548 ESP_LOGI(TAG,
"Saved state");
uint32_t IRAM_ATTR HOT get_loop_component_start_time() const
Get the cached time in milliseconds from when the current component started its loop execution.
void mark_failed()
Mark this component as failed.
void status_clear_error()
bool status_comm_result(bool ok)
Clears the warning when ok, else sets it with message (default "Communication failed"); returns ok.
void enable_loop()
Enable this component's loop.
void set_timeout(const char *name, uint32_t timeout, std::function< void()> &&f)
Set a timeout function with a const char* name.
void disable_loop()
Disable this component's loop.
uint8_t warn_if_blocking_over_
Warn threshold in centiseconds (max 2550ms)
void publish_(const bsec_output_t *outputs, uint8_t num_outputs)
uint32_t state_save_interval_ms_
void update_subscription_()
OperatingAge operating_age_
void publish_sensor_(sensor::Sensor *sensor, float value, bool change_only=false)
sensor::Sensor * pressure_sensor_
sensor::Sensor * temperature_sensor_
uint32_t last_state_save_ms_
uint32_t bsec2_configuration_length_
void read_(int64_t trigger_time_ns)
text_sensor::TextSensor * iaq_accuracy_text_sensor_
sensor::Sensor * gas_resistance_sensor_
uint32_t millis_overflow_counter_
sensor::Sensor * humidity_sensor_
SampleRate temperature_sample_rate_
struct bme68x_dev bme68x_
bsec_library_return_t bsec_status_
struct bme68x_heatr_conf bme68x_heatr_conf_
bsec_bme_settings_t bsec_settings_
uint8_t const * bsec2_configuration_
sensor::Sensor * breath_voc_equivalent_sensor_
uint8_t bsec_instance_[BSEC_INSTANCE_SIZE]
AlgorithmOutput algorithm_output_
sensor::Sensor * iaq_sensor_
void set_config_(const uint8_t *config, u_int32_t len)
sensor::Sensor * iaq_static_sensor_
SampleRate pressure_sample_rate_
virtual uint32_t get_hash()=0
bool bsec2_blob_configured_
sensor::Sensor * iaq_accuracy_sensor_
float calc_sensor_sample_rate_(SampleRate sample_rate)
SampleRate humidity_sample_rate_
void save_state_(uint8_t accuracy)
std::queue< std::function< void()> > queue_
void queue_push_(std::function< void()> &&f)
void dump_config() override
sensor::Sensor * co2_equivalent_sensor_
float temperature_offset_
ESPPreferenceObject bsec_state_
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)
ESPHOME_LOG_TAG(TAG, "bme68x_bsec2.sensor")
ESPPreferences * global_preferences
uint32_t IRAM_ATTR HOT millis()
Application App
Global storage of Application pointer - only one Application can exist.
ESPPreferenceObject make_preference(size_t, uint32_t, bool)