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