ESPHome 2026.5.0-dev
Loading...
Searching...
No Matches
bme68x_bsec2.cpp
Go to the documentation of this file.
2#include "esphome/core/hal.h"
4#include "esphome/core/log.h"
5
6#ifdef USE_BSEC2
7#include "bme68x_bsec2.h"
8
10
11#define BME68X_BSEC2_ALGORITHM_OUTPUT_LOG(a) (a == ALGORITHM_OUTPUT_CLASSIFICATION ? "Classification" : "Regression")
12#define BME68X_BSEC2_OPERATING_AGE_LOG(o) (o == OPERATING_AGE_4D ? "4 days" : "28 days")
13#define BME68X_BSEC2_SAMPLE_RATE_LOG(r) (r == SAMPLE_RATE_DEFAULT ? "Default" : (r == SAMPLE_RATE_ULP ? "ULP" : "LP"))
14#define BME68X_BSEC2_VOLTAGE_LOG(v) (v == VOLTAGE_3_3V ? "3.3V" : "1.8V")
15
16static const char *const TAG = "bme68x_bsec2.sensor";
17
18static constexpr const char *const IAQ_ACCURACY_STATES[4] = {"Stabilizing", "Uncertain", "Calibrating", "Calibrated"};
19
20static bool is_no_new_data_warning(int8_t status) {
21#ifdef BME68X_W_NO_NEW_DATA
22 return status == BME68X_W_NO_NEW_DATA;
23#else
24 return status == 2;
25#endif
26}
27
29 this->warn_if_blocking_over_ = 60; // initial reads may block for up to 60ms
30
31 this->bsec_status_ = bsec_init_m(&this->bsec_instance_);
32 if (this->bsec_status_ != BSEC_OK) {
33 this->mark_failed();
34 ESP_LOGE(TAG, "bsec_init_m failed: status %d", this->bsec_status_);
35 return;
36 }
37
38 bsec_get_version_m(&this->bsec_instance_, &this->version_);
39
40 this->bme68x_status_ = bme68x_init(&this->bme68x_);
41 if (this->bme68x_status_ != BME68X_OK) {
42 this->mark_failed();
43 ESP_LOGE(TAG, "bme68x_init failed: status %d", this->bme68x_status_);
44 return;
45 }
46 if (this->bsec2_configuration_ != nullptr && this->bsec2_configuration_length_) {
48 if (this->bsec_status_ != BSEC_OK) {
49 this->mark_failed();
50 ESP_LOGE(TAG, "bsec_set_configuration_m failed: status %d", this->bsec_status_);
51 return;
52 }
53 }
54
56 if (this->bsec_status_ != BSEC_OK) {
57 this->mark_failed();
58 ESP_LOGE(TAG, "bsec_update_subscription_m failed: status %d", this->bsec_status_);
59 return;
60 }
61
62 this->load_state_();
63}
64
66 ESP_LOGCONFIG(TAG,
67 "BME68X via BSEC2:\n"
68 " BSEC2 version: %d.%d.%d.%d\n"
69 " BSEC2 configuration blob:\n"
70 " Configured: %s",
71 this->version_.major, this->version_.minor, this->version_.major_bugfix, this->version_.minor_bugfix,
72 YESNO(this->bsec2_blob_configured_));
73 if (this->bsec2_configuration_ != nullptr && this->bsec2_configuration_length_) {
74 ESP_LOGCONFIG(TAG, " Size: %" PRIu32, this->bsec2_configuration_length_);
75 }
76
77 if (this->is_failed()) {
78 ESP_LOGE(TAG, "Communication failed (BSEC2 status: %d, BME68X status: %d)", this->bsec_status_,
79 this->bme68x_status_);
80 if (this->bsec_status_ == BSEC_I_SU_SUBSCRIBEDOUTPUTGATES) {
81 ESP_LOGE(TAG, "No sensors, add at least one sensor to the config");
82 }
83 }
84
86 ESP_LOGCONFIG(TAG, " Algorithm output: %s", BME68X_BSEC2_ALGORITHM_OUTPUT_LOG(this->algorithm_output_));
87 }
88 ESP_LOGCONFIG(TAG,
89 " Operating age: %s\n"
90 " Sample rate: %s\n"
91 " Voltage: %s\n"
92 " State save interval: %" PRIu32 "ms\n"
93 " Temperature offset: %.2f",
94 BME68X_BSEC2_OPERATING_AGE_LOG(this->operating_age_), BME68X_BSEC2_SAMPLE_RATE_LOG(this->sample_rate_),
95 BME68X_BSEC2_VOLTAGE_LOG(this->voltage_), this->state_save_interval_ms_, this->temperature_offset_);
96
97#ifdef USE_SENSOR
98 LOG_SENSOR(" ", "Temperature", this->temperature_sensor_);
99 ESP_LOGCONFIG(TAG, " Sample rate: %s", BME68X_BSEC2_SAMPLE_RATE_LOG(this->temperature_sample_rate_));
100 LOG_SENSOR(" ", "Pressure", this->pressure_sensor_);
101 ESP_LOGCONFIG(TAG, " Sample rate: %s", BME68X_BSEC2_SAMPLE_RATE_LOG(this->pressure_sample_rate_));
102 LOG_SENSOR(" ", "Humidity", this->humidity_sensor_);
103 ESP_LOGCONFIG(TAG, " Sample rate: %s", BME68X_BSEC2_SAMPLE_RATE_LOG(this->humidity_sample_rate_));
104 LOG_SENSOR(" ", "Gas resistance", this->gas_resistance_sensor_);
105 LOG_SENSOR(" ", "CO2 equivalent", this->co2_equivalent_sensor_);
106 LOG_SENSOR(" ", "Breath VOC equivalent", this->breath_voc_equivalent_sensor_);
107 LOG_SENSOR(" ", "IAQ", this->iaq_sensor_);
108 LOG_SENSOR(" ", "IAQ static", this->iaq_static_sensor_);
109 LOG_SENSOR(" ", "Numeric IAQ accuracy", this->iaq_accuracy_sensor_);
110#endif
111#ifdef USE_TEXT_SENSOR
112 LOG_TEXT_SENSOR(" ", "IAQ accuracy", this->iaq_accuracy_text_sensor_);
113#endif
114}
115
117 this->run_();
118
120 this->status_set_error();
121 } else {
122 this->status_clear_error();
123 }
124 const bool has_bme68x_warning = this->bme68x_status_ > BME68X_OK && !is_no_new_data_warning(this->bme68x_status_);
125 if (this->bsec_status_ > BSEC_OK || has_bme68x_warning) {
126 this->status_set_warning();
127 } else {
128 this->status_clear_warning();
129 }
130 // Process a single action from the queue. These are primarily sensor state publishes
131 // that in totality take too long to send in a single call.
132 if (this->queue_.size()) {
133 auto action = std::move(this->queue_.front());
134 this->queue_.pop();
135 action();
136 }
137}
138
139void BME68xBSEC2Component::set_config_(const uint8_t *config, uint32_t len) {
140 if (len > BSEC_MAX_PROPERTY_BLOB_SIZE) {
141 ESP_LOGE(TAG, "Configuration blob too large");
142 this->mark_failed();
143 return;
144 }
145 uint8_t work_buffer[BSEC_MAX_PROPERTY_BLOB_SIZE];
146 this->bsec_status_ = bsec_set_configuration_m(&this->bsec_instance_, config, len, work_buffer, sizeof(work_buffer));
147 if (this->bsec_status_ == BSEC_OK) {
148 this->bsec2_blob_configured_ = true;
149 }
150}
151
153 if (sample_rate == SAMPLE_RATE_DEFAULT) {
154 sample_rate = this->sample_rate_;
155 }
156 return sample_rate == SAMPLE_RATE_ULP ? BSEC_SAMPLE_RATE_ULP : BSEC_SAMPLE_RATE_LP;
157}
158
160 bsec_sensor_configuration_t virtual_sensors[BSEC_NUMBER_OUTPUTS];
161 uint8_t num_virtual_sensors = 0;
162#ifdef USE_SENSOR
163 if (this->iaq_sensor_) {
164 virtual_sensors[num_virtual_sensors].sensor_id = BSEC_OUTPUT_IAQ;
165 virtual_sensors[num_virtual_sensors].sample_rate = this->calc_sensor_sample_rate_(SAMPLE_RATE_DEFAULT);
166 num_virtual_sensors++;
167 }
168
169 if (this->iaq_static_sensor_) {
170 virtual_sensors[num_virtual_sensors].sensor_id = BSEC_OUTPUT_STATIC_IAQ;
171 virtual_sensors[num_virtual_sensors].sample_rate = this->calc_sensor_sample_rate_(SAMPLE_RATE_DEFAULT);
172 num_virtual_sensors++;
173 }
174
175 if (this->co2_equivalent_sensor_) {
176 virtual_sensors[num_virtual_sensors].sensor_id = BSEC_OUTPUT_CO2_EQUIVALENT;
177 virtual_sensors[num_virtual_sensors].sample_rate = this->calc_sensor_sample_rate_(SAMPLE_RATE_DEFAULT);
178 num_virtual_sensors++;
179 }
180
182 virtual_sensors[num_virtual_sensors].sensor_id = BSEC_OUTPUT_BREATH_VOC_EQUIVALENT;
183 virtual_sensors[num_virtual_sensors].sample_rate = this->calc_sensor_sample_rate_(SAMPLE_RATE_DEFAULT);
184 num_virtual_sensors++;
185 }
186
187 if (this->pressure_sensor_) {
188 virtual_sensors[num_virtual_sensors].sensor_id = BSEC_OUTPUT_RAW_PRESSURE;
189 virtual_sensors[num_virtual_sensors].sample_rate = this->calc_sensor_sample_rate_(this->pressure_sample_rate_);
190 num_virtual_sensors++;
191 }
192
193 if (this->gas_resistance_sensor_) {
194 virtual_sensors[num_virtual_sensors].sensor_id = BSEC_OUTPUT_RAW_GAS;
195 virtual_sensors[num_virtual_sensors].sample_rate = this->calc_sensor_sample_rate_(SAMPLE_RATE_DEFAULT);
196 num_virtual_sensors++;
197 }
198
199 if (this->temperature_sensor_) {
200 virtual_sensors[num_virtual_sensors].sensor_id = BSEC_OUTPUT_SENSOR_HEAT_COMPENSATED_TEMPERATURE;
201 virtual_sensors[num_virtual_sensors].sample_rate = this->calc_sensor_sample_rate_(this->temperature_sample_rate_);
202 num_virtual_sensors++;
203 }
204
205 if (this->humidity_sensor_) {
206 virtual_sensors[num_virtual_sensors].sensor_id = BSEC_OUTPUT_SENSOR_HEAT_COMPENSATED_HUMIDITY;
207 virtual_sensors[num_virtual_sensors].sample_rate = this->calc_sensor_sample_rate_(this->humidity_sample_rate_);
208 num_virtual_sensors++;
209 }
210#endif
211 bsec_sensor_configuration_t sensor_settings[BSEC_MAX_PHYSICAL_SENSOR];
212 uint8_t num_sensor_settings = BSEC_MAX_PHYSICAL_SENSOR;
213 this->bsec_status_ = bsec_update_subscription_m(&this->bsec_instance_, virtual_sensors, num_virtual_sensors,
214 sensor_settings, &num_sensor_settings);
215}
216
218 this->op_mode_ = this->bsec_settings_.op_mode;
219 int64_t curr_time_ns = this->get_time_ns_();
220 if (curr_time_ns < this->bsec_settings_.next_call) {
221 return;
222 }
223 ESP_LOGV(TAG, "Performing sensor run");
224
225 struct bme68x_conf bme68x_conf;
226 this->bsec_status_ = bsec_sensor_control_m(&this->bsec_instance_, curr_time_ns, &this->bsec_settings_);
227 if (this->bsec_status_ < BSEC_OK) {
228 ESP_LOGW(TAG, "Fetching control settings failed (BSEC2 error code %d)", this->bsec_status_);
229 return;
230 }
231
232 switch (this->bsec_settings_.op_mode) {
233 case BME68X_FORCED_MODE:
234 bme68x_get_conf(&bme68x_conf, &this->bme68x_);
235
236 bme68x_conf.os_hum = this->bsec_settings_.humidity_oversampling;
237 bme68x_conf.os_temp = this->bsec_settings_.temperature_oversampling;
238 bme68x_conf.os_pres = this->bsec_settings_.pressure_oversampling;
239 bme68x_set_conf(&bme68x_conf, &this->bme68x_);
240 this->bme68x_heatr_conf_.enable = BME68X_ENABLE;
241 this->bme68x_heatr_conf_.heatr_temp = this->bsec_settings_.heater_temperature;
242 this->bme68x_heatr_conf_.heatr_dur = this->bsec_settings_.heater_duration;
243
244 // this->bme68x_status_ = bme68x_set_op_mode(this->bsec_settings_.op_mode, &this->bme68x_);
245 this->bme68x_status_ = bme68x_set_heatr_conf(BME68X_FORCED_MODE, &this->bme68x_heatr_conf_, &this->bme68x_);
246 this->bme68x_status_ = bme68x_set_op_mode(BME68X_FORCED_MODE, &this->bme68x_);
247 this->op_mode_ = BME68X_FORCED_MODE;
248 ESP_LOGV(TAG, "Using forced mode");
249
250 break;
251 case BME68X_PARALLEL_MODE:
252 if (this->op_mode_ != this->bsec_settings_.op_mode) {
253 bme68x_get_conf(&bme68x_conf, &this->bme68x_);
254
255 bme68x_conf.os_hum = this->bsec_settings_.humidity_oversampling;
256 bme68x_conf.os_temp = this->bsec_settings_.temperature_oversampling;
257 bme68x_conf.os_pres = this->bsec_settings_.pressure_oversampling;
258 bme68x_set_conf(&bme68x_conf, &this->bme68x_);
259
260 this->bme68x_heatr_conf_.enable = BME68X_ENABLE;
261 this->bme68x_heatr_conf_.heatr_temp_prof = this->bsec_settings_.heater_temperature_profile;
262 this->bme68x_heatr_conf_.heatr_dur_prof = this->bsec_settings_.heater_duration_profile;
263 this->bme68x_heatr_conf_.profile_len = this->bsec_settings_.heater_profile_len;
264 this->bme68x_heatr_conf_.shared_heatr_dur =
265 BSEC_TOTAL_HEAT_DUR -
266 (bme68x_get_meas_dur(BME68X_PARALLEL_MODE, &bme68x_conf, &this->bme68x_) / INT64_C(1000));
267
268 this->bme68x_status_ = bme68x_set_heatr_conf(BME68X_PARALLEL_MODE, &this->bme68x_heatr_conf_, &this->bme68x_);
269 this->bme68x_status_ = bme68x_set_op_mode(BME68X_PARALLEL_MODE, &this->bme68x_);
270 this->op_mode_ = BME68X_PARALLEL_MODE;
271 ESP_LOGV(TAG, "Using parallel mode");
272 }
273 break;
274 case BME68X_SLEEP_MODE:
275 if (this->op_mode_ != this->bsec_settings_.op_mode) {
276 bme68x_set_op_mode(BME68X_SLEEP_MODE, &this->bme68x_);
277 this->op_mode_ = BME68X_SLEEP_MODE;
278 ESP_LOGV(TAG, "Using sleep mode");
279 }
280 break;
281 }
282
283 if (this->bsec_settings_.trigger_measurement && this->bsec_settings_.op_mode != BME68X_SLEEP_MODE) {
284 bme68x_get_conf(&bme68x_conf, &this->bme68x_);
285 uint32_t meas_dur = bme68x_get_meas_dur(this->op_mode_, &bme68x_conf, &this->bme68x_);
286 ESP_LOGV(TAG, "Queueing read in %" PRIu32 "us", meas_dur);
287 this->trigger_time_ns_ = curr_time_ns;
288 this->set_timeout("read", meas_dur / 1000, [this]() { this->read_(this->trigger_time_ns_); });
289 } else {
290 ESP_LOGV(TAG, "Measurement not required, queueing immediate read");
291 this->trigger_time_ns_ = curr_time_ns;
292 this->set_timeout("read", 0, [this]() { this->read_(this->trigger_time_ns_); });
293 }
294}
295
296void BME68xBSEC2Component::read_(int64_t trigger_time_ns) {
297 ESP_LOGV(TAG, "Reading data");
298
299 if (!this->bsec_settings_.process_data) {
300 ESP_LOGV(TAG, "Data processing not required");
301 return;
302 }
303
304 struct bme68x_data data[3];
305 uint8_t nFields = 0;
306 this->bme68x_status_ = bme68x_get_data(this->op_mode_, &data[0], &nFields, &this->bme68x_);
307
308 if (is_no_new_data_warning(this->bme68x_status_)) {
309 ESP_LOGV(TAG, "BME68X did not provide new data");
310 return;
311 }
312 if (this->bme68x_status_ != BME68X_OK) {
313 ESP_LOGW(TAG, "Fetching data failed (BME68X error code %d)", this->bme68x_status_);
314 return;
315 }
316 if (nFields < 1) {
317 ESP_LOGV(TAG, "BME68X did not provide new fields");
318 return;
319 }
320
321 for (uint8_t i = 0; i < nFields; i++) {
322 bsec_input_t inputs[BSEC_MAX_PHYSICAL_SENSOR]; // Temperature, Pressure, Humidity & Gas Resistance
323 uint8_t num_inputs = 0;
324
325 if (BSEC_CHECK_INPUT(this->bsec_settings_.process_data, BSEC_INPUT_TEMPERATURE)) {
326 inputs[num_inputs].sensor_id = BSEC_INPUT_TEMPERATURE;
327 inputs[num_inputs].signal = data[i].temperature;
328 inputs[num_inputs].time_stamp = trigger_time_ns;
329 num_inputs++;
330 }
331 if (BSEC_CHECK_INPUT(this->bsec_settings_.process_data, BSEC_INPUT_HEATSOURCE)) {
332 inputs[num_inputs].sensor_id = BSEC_INPUT_HEATSOURCE;
333 inputs[num_inputs].signal = this->temperature_offset_;
334 inputs[num_inputs].time_stamp = trigger_time_ns;
335 num_inputs++;
336 }
337 if (BSEC_CHECK_INPUT(this->bsec_settings_.process_data, BSEC_INPUT_HUMIDITY)) {
338 inputs[num_inputs].sensor_id = BSEC_INPUT_HUMIDITY;
339 inputs[num_inputs].signal = data[i].humidity;
340 inputs[num_inputs].time_stamp = trigger_time_ns;
341 num_inputs++;
342 }
343 if (BSEC_CHECK_INPUT(this->bsec_settings_.process_data, BSEC_INPUT_PRESSURE)) {
344 inputs[num_inputs].sensor_id = BSEC_INPUT_PRESSURE;
345 inputs[num_inputs].signal = data[i].pressure;
346 inputs[num_inputs].time_stamp = trigger_time_ns;
347 num_inputs++;
348 }
349 if (BSEC_CHECK_INPUT(this->bsec_settings_.process_data, BSEC_INPUT_GASRESISTOR)) {
350 if (data[i].status & BME68X_GASM_VALID_MSK) {
351 inputs[num_inputs].sensor_id = BSEC_INPUT_GASRESISTOR;
352 inputs[num_inputs].signal = data[i].gas_resistance;
353 inputs[num_inputs].time_stamp = trigger_time_ns;
354 num_inputs++;
355 } else {
356 ESP_LOGD(TAG, "BME68X did not report gas data");
357 }
358 }
359 if (BSEC_CHECK_INPUT(this->bsec_settings_.process_data, BSEC_INPUT_PROFILE_PART) &&
360 (data[i].status & BME68X_GASM_VALID_MSK)) {
361 inputs[num_inputs].sensor_id = BSEC_INPUT_PROFILE_PART;
362 inputs[num_inputs].signal = (this->op_mode_ == BME68X_FORCED_MODE) ? 0 : data[i].gas_index;
363 inputs[num_inputs].time_stamp = trigger_time_ns;
364 num_inputs++;
365 }
366
367 if (num_inputs < 1) {
368 ESP_LOGD(TAG, "No signal inputs available for BSEC2");
369 return;
370 }
371
372 bsec_output_t outputs[BSEC_NUMBER_OUTPUTS];
373 uint8_t num_outputs = BSEC_NUMBER_OUTPUTS;
374 this->bsec_status_ = bsec_do_steps_m(&this->bsec_instance_, inputs, num_inputs, outputs, &num_outputs);
375 if (this->bsec_status_ != BSEC_OK) {
376 ESP_LOGW(TAG, "Signal processing failed (BSEC2 error code %d)", this->bsec_status_);
377 return;
378 }
379 if (num_outputs < 1) {
380 ESP_LOGD(TAG, "No signal outputs provided by BSEC2");
381 return;
382 }
383
384 this->publish_(outputs, num_outputs);
385 }
386}
387
388void BME68xBSEC2Component::publish_(const bsec_output_t *outputs, uint8_t num_outputs) {
389 ESP_LOGV(TAG, "Publishing sensor states");
390 bool update_accuracy = false;
391 uint8_t max_accuracy = 0;
392 for (uint8_t i = 0; i < num_outputs; i++) {
393 float signal = outputs[i].signal;
394 switch (outputs[i].sensor_id) {
395 case BSEC_OUTPUT_IAQ:
396 max_accuracy = std::max(outputs[i].accuracy, max_accuracy);
397 update_accuracy = true;
398#ifdef USE_SENSOR
399 this->queue_push_([this, signal]() { this->publish_sensor_(this->iaq_sensor_, signal); });
400#endif
401 break;
402 case BSEC_OUTPUT_STATIC_IAQ:
403 max_accuracy = std::max(outputs[i].accuracy, max_accuracy);
404 update_accuracy = true;
405#ifdef USE_SENSOR
406 this->queue_push_([this, signal]() { this->publish_sensor_(this->iaq_static_sensor_, signal); });
407#endif
408 break;
409 case BSEC_OUTPUT_CO2_EQUIVALENT:
410#ifdef USE_SENSOR
411 this->queue_push_([this, signal]() { this->publish_sensor_(this->co2_equivalent_sensor_, signal); });
412#endif
413 break;
414 case BSEC_OUTPUT_BREATH_VOC_EQUIVALENT:
415#ifdef USE_SENSOR
416 this->queue_push_([this, signal]() { this->publish_sensor_(this->breath_voc_equivalent_sensor_, signal); });
417#endif
418 break;
419 case BSEC_OUTPUT_RAW_PRESSURE:
420#ifdef USE_SENSOR
421 this->queue_push_([this, signal]() { this->publish_sensor_(this->pressure_sensor_, signal / 100.0f); });
422#endif
423 break;
424 case BSEC_OUTPUT_RAW_GAS:
425#ifdef USE_SENSOR
426 this->queue_push_([this, signal]() { this->publish_sensor_(this->gas_resistance_sensor_, signal); });
427#endif
428 break;
429 case BSEC_OUTPUT_SENSOR_HEAT_COMPENSATED_TEMPERATURE:
430#ifdef USE_SENSOR
431 this->queue_push_([this, signal]() { this->publish_sensor_(this->temperature_sensor_, signal); });
432#endif
433 break;
434 case BSEC_OUTPUT_SENSOR_HEAT_COMPENSATED_HUMIDITY:
435#ifdef USE_SENSOR
436 this->queue_push_([this, signal]() { this->publish_sensor_(this->humidity_sensor_, signal); });
437#endif
438 break;
439 }
440 }
441 if (update_accuracy) {
442 max_accuracy = std::min<uint8_t>(max_accuracy, std::size(IAQ_ACCURACY_STATES) - 1);
443#ifdef USE_SENSOR
444 this->queue_push_(
445 [this, max_accuracy]() { this->publish_sensor_(this->iaq_accuracy_sensor_, max_accuracy, true); });
446#endif
447#ifdef USE_TEXT_SENSOR
448 this->queue_push_([this, max_accuracy]() {
449 this->publish_sensor_(this->iaq_accuracy_text_sensor_, IAQ_ACCURACY_STATES[max_accuracy]);
450 });
451#endif
452 // Queue up an opportunity to save state
453 this->queue_push_([this, max_accuracy]() { this->save_state_(max_accuracy); });
454 }
455}
456
458 int64_t time_ms = millis();
459 if (this->last_time_ms_ > time_ms) {
461 }
462 this->last_time_ms_ = time_ms;
463
464 return (time_ms + ((int64_t) this->millis_overflow_counter_ << 32)) * INT64_C(1000000);
465}
466
467#ifdef USE_SENSOR
468void BME68xBSEC2Component::publish_sensor_(sensor::Sensor *sensor, float value, bool change_only) {
469 if (!sensor || (change_only && sensor->has_state() && sensor->state == value)) {
470 return;
471 }
472 sensor->publish_state(value);
473}
474#endif
475
476#ifdef USE_TEXT_SENSOR
478 if (!sensor || (sensor->has_state() && sensor->state == value)) {
479 return;
480 }
481 sensor->publish_state(value);
482}
483#endif
484
486 uint32_t hash = this->get_hash();
487 this->bsec_state_ = global_preferences->make_preference<uint8_t[BSEC_MAX_STATE_BLOB_SIZE]>(hash, true);
488
489 uint8_t state[BSEC_MAX_STATE_BLOB_SIZE];
490 if (this->bsec_state_.load(&state)) {
491 ESP_LOGV(TAG, "Loading state");
492 uint8_t work_buffer[BSEC_MAX_WORKBUFFER_SIZE];
493 this->bsec_status_ =
494 bsec_set_state_m(&this->bsec_instance_, state, BSEC_MAX_STATE_BLOB_SIZE, work_buffer, sizeof(work_buffer));
495 if (this->bsec_status_ != BSEC_OK) {
496 ESP_LOGW(TAG, "Failed to load state (BSEC2 error code %d)", this->bsec_status_);
497 }
498 ESP_LOGI(TAG, "Loaded state");
499 }
500}
501
502void BME68xBSEC2Component::save_state_(uint8_t accuracy) {
503 if (accuracy < 3 || (millis() - this->last_state_save_ms_ < this->state_save_interval_ms_)) {
504 return;
505 }
506
507 ESP_LOGV(TAG, "Saving state");
508
509 uint8_t state[BSEC_MAX_STATE_BLOB_SIZE];
510 uint8_t work_buffer[BSEC_MAX_STATE_BLOB_SIZE];
511 uint32_t num_serialized_state = BSEC_MAX_STATE_BLOB_SIZE;
512
513 this->bsec_status_ = bsec_get_state_m(&this->bsec_instance_, 0, state, BSEC_MAX_STATE_BLOB_SIZE, work_buffer,
514 BSEC_MAX_STATE_BLOB_SIZE, &num_serialized_state);
515 if (this->bsec_status_ != BSEC_OK) {
516 ESP_LOGW(TAG, "Failed fetch state for save (BSEC2 error code %d)", this->bsec_status_);
517 return;
518 }
519
520 if (!this->bsec_state_.save(&state)) {
521 ESP_LOGW(TAG, "Failed to save state");
522 return;
523 }
524 this->last_state_save_ms_ = millis();
525
526 ESP_LOGI(TAG, "Saved state");
527}
528
529} // namespace esphome::bme68x_bsec2
530#endif
uint8_t status
Definition bl0942.h:8
void mark_failed()
Mark this component as failed.
bool is_failed() const
Definition component.h:284
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.
Definition component.h:510
void status_clear_error()
Definition component.h:312
uint8_t warn_if_blocking_over_
Warn threshold in centiseconds (max 2550ms)
Definition component.h:580
void status_clear_warning()
Definition component.h:306
bool has_state() const
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_
struct bme68x_heatr_conf bme68x_heatr_conf_
uint8_t bsec_instance_[BSEC_INSTANCE_SIZE]
void set_config_(const uint8_t *config, u_int32_t len)
float calc_sensor_sample_rate_(SampleRate sample_rate)
std::queue< std::function< void()> > queue_
void queue_push_(std::function< void()> &&f)
Base-class for all sensors.
Definition sensor.h:47
void publish_state(float state)
Publish a new state to the front-end.
Definition sensor.cpp:68
float state
This member variable stores the last state that has passed through all filters.
Definition sensor.h:138
void publish_state(const std::string &state)
bool state
Definition fan.h:2
std::string size_t len
Definition helpers.h:1045
ESPPreferences * global_preferences
uint32_t IRAM_ATTR HOT millis()
Definition core.cpp:26
static void uint32_t
ESPPreferenceObject make_preference(size_t, uint32_t, bool)
Definition preferences.h:24