mirror of
				https://github.com/esphome/esphome.git
				synced 2025-10-30 22:53:59 +00:00 
			
		
		
		
	[bme68x_bsec2_i2c] BME68X Temperature+Pressure+Humidity+Gas Sensor via BSEC2 (#4585)
* Added initial bme68x component * Initialize all child sensors to nullptr This was added to all other sensors in #3808 * Update BSEC2 and BME68x Libraries Current versions from Bosch Sensortec * Add myself to codeowners for bme68x_bsec * Move constants to const.py, according to ci-custom checks Move constants to const.py, according to ci-custom checks * Update library dependencies We'll stick with 1.4.2200 for now. 1.4.2200 is not on platform.io registry, use tag instead. Update to 1.5.2400 needs some work due to multi instance support. * Update BSEC2 to 1.6.2400 * Add consts to bme680x_bsec Enable inclusion with external_components * Update device class for pressure * Update to use multisensor API * Tidy up some constants * Add tests * Remove scd30 changes * Import CONF_SAMPLE_RATE * Pull BSEC config blob from repo based on config * Rename component to `bme68x_bsec_i2c` * Fix tests + codeowners * Cleanup for review * Rename using `bsec2` * Apply suggestions from code review Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> * Download file during validation stage, instead * Make `dump_config()` only dump stuff * Compile safely without sensor and text sensor headers * Use `intf_ptr` * Save state if measuring static IAQ, too * Update CODEOWNERS * Simplify esphome/components/bme68x_bsec2_i2c/__init__.py Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> * Remove extraneous colon & imports * Track & save the maximum accuracy value * Polish up accuracy sensor handling * Log static sensor, update `defines.h` * Walruses make it better * Add some logging of setup failures * Update esphome/components/bme68x_bsec2_i2c/bme68x_bsec2_i2c.cpp Co-authored-by: Trevor North <trevor@freedisc.co.uk> * Break out some things * Update CODEOWNERS * Update CODEOWNERS take 2 * Use `add_extra` in base schema * Another walrus in the sensor Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> --------- Co-authored-by: Keith Burzinski <kbx81x@gmail.com> Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Co-authored-by: Trevor North <trevor@freedisc.co.uk>
This commit is contained in:
		| @@ -65,6 +65,8 @@ esphome/components/bluetooth_proxy/* @jesserockz | |||||||
| esphome/components/bme280_base/* @esphome/core | esphome/components/bme280_base/* @esphome/core | ||||||
| esphome/components/bme280_spi/* @apbodrov | esphome/components/bme280_spi/* @apbodrov | ||||||
| esphome/components/bme680_bsec/* @trvrnrth | esphome/components/bme680_bsec/* @trvrnrth | ||||||
|  | esphome/components/bme68x_bsec2/* @kbx81 @neffs | ||||||
|  | esphome/components/bme68x_bsec2_i2c/* @kbx81 @neffs | ||||||
| esphome/components/bmi160/* @flaviut | esphome/components/bmi160/* @flaviut | ||||||
| esphome/components/bmp3xx/* @latonita | esphome/components/bmp3xx/* @latonita | ||||||
| esphome/components/bmp3xx_base/* @latonita @martgras | esphome/components/bmp3xx_base/* @latonita @martgras | ||||||
|   | |||||||
							
								
								
									
										196
									
								
								esphome/components/bme68x_bsec2/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										196
									
								
								esphome/components/bme68x_bsec2/__init__.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,196 @@ | |||||||
|  | import hashlib | ||||||
|  | from pathlib import Path | ||||||
|  |  | ||||||
|  | from esphome import core, external_files | ||||||
|  | import esphome.codegen as cg | ||||||
|  | import esphome.config_validation as cv | ||||||
|  | from esphome.const import ( | ||||||
|  |     CONF_ID, | ||||||
|  |     CONF_MODEL, | ||||||
|  |     CONF_RAW_DATA_ID, | ||||||
|  |     CONF_SAMPLE_RATE, | ||||||
|  |     CONF_TEMPERATURE_OFFSET, | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | CODEOWNERS = ["@neffs", "@kbx81"] | ||||||
|  |  | ||||||
|  | DOMAIN = "bme68x_bsec2" | ||||||
|  |  | ||||||
|  | BSEC2_LIBRARY_VERSION = "v1.7.2502" | ||||||
|  |  | ||||||
|  | CONF_ALGORITHM_OUTPUT = "algorithm_output" | ||||||
|  | CONF_BME68X_BSEC2_ID = "bme68x_bsec2_id" | ||||||
|  | CONF_IAQ_MODE = "iaq_mode" | ||||||
|  | CONF_OPERATING_AGE = "operating_age" | ||||||
|  | CONF_STATE_SAVE_INTERVAL = "state_save_interval" | ||||||
|  | CONF_SUPPLY_VOLTAGE = "supply_voltage" | ||||||
|  |  | ||||||
|  | bme68x_bsec2_ns = cg.esphome_ns.namespace("bme68x_bsec2") | ||||||
|  | BME68xBSEC2Component = bme68x_bsec2_ns.class_("BME68xBSEC2Component", cg.Component) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | MODEL_OPTIONS = ["bme680", "bme688"] | ||||||
|  |  | ||||||
|  | AlgorithmOutput = bme68x_bsec2_ns.enum("AlgorithmOutput") | ||||||
|  | ALGORITHM_OUTPUT_OPTIONS = { | ||||||
|  |     "classification": AlgorithmOutput.ALGORITHM_OUTPUT_CLASSIFICATION, | ||||||
|  |     "regression": AlgorithmOutput.ALGORITHM_OUTPUT_REGRESSION, | ||||||
|  | } | ||||||
|  |  | ||||||
|  | OperatingAge = bme68x_bsec2_ns.enum("OperatingAge") | ||||||
|  | OPERATING_AGE_OPTIONS = { | ||||||
|  |     "4d": OperatingAge.OPERATING_AGE_4D, | ||||||
|  |     "28d": OperatingAge.OPERATING_AGE_28D, | ||||||
|  | } | ||||||
|  |  | ||||||
|  | SampleRate = bme68x_bsec2_ns.enum("SampleRate") | ||||||
|  | SAMPLE_RATE_OPTIONS = { | ||||||
|  |     "LP": SampleRate.SAMPLE_RATE_LP, | ||||||
|  |     "ULP": SampleRate.SAMPLE_RATE_ULP, | ||||||
|  | } | ||||||
|  |  | ||||||
|  | Voltage = bme68x_bsec2_ns.enum("Voltage") | ||||||
|  | VOLTAGE_OPTIONS = { | ||||||
|  |     "1.8V": Voltage.VOLTAGE_1_8V, | ||||||
|  |     "3.3V": Voltage.VOLTAGE_3_3V, | ||||||
|  | } | ||||||
|  |  | ||||||
|  | ALGORITHM_OUTPUT_FILE_NAME = { | ||||||
|  |     "classification": "sel", | ||||||
|  |     "regression": "reg", | ||||||
|  | } | ||||||
|  |  | ||||||
|  | SAMPLE_RATE_FILE_NAME = { | ||||||
|  |     "LP": "3s", | ||||||
|  |     "ULP": "300s", | ||||||
|  | } | ||||||
|  |  | ||||||
|  | VOLTAGE_FILE_NAME = { | ||||||
|  |     "1.8V": "18v", | ||||||
|  |     "3.3V": "33v", | ||||||
|  | } | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def _compute_local_file_path(url: str) -> Path: | ||||||
|  |     h = hashlib.new("sha256") | ||||||
|  |     h.update(url.encode()) | ||||||
|  |     key = h.hexdigest()[:8] | ||||||
|  |     base_dir = external_files.compute_local_file_dir(DOMAIN) | ||||||
|  |     return base_dir / key | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def _compute_url(config: dict) -> str: | ||||||
|  |     model = config.get(CONF_MODEL) | ||||||
|  |     operating_age = config.get(CONF_OPERATING_AGE) | ||||||
|  |     sample_rate = SAMPLE_RATE_FILE_NAME[config.get(CONF_SAMPLE_RATE)] | ||||||
|  |     volts = VOLTAGE_FILE_NAME[config.get(CONF_SUPPLY_VOLTAGE)] | ||||||
|  |     if model == "bme688": | ||||||
|  |         algo = ALGORITHM_OUTPUT_FILE_NAME[ | ||||||
|  |             config.get(CONF_ALGORITHM_OUTPUT, "classification") | ||||||
|  |         ] | ||||||
|  |         filename = "bsec_selectivity" | ||||||
|  |     else: | ||||||
|  |         algo = "iaq" | ||||||
|  |         filename = "bsec_iaq" | ||||||
|  |     return f"https://raw.githubusercontent.com/boschsensortec/Bosch-BSEC2-Library/{BSEC2_LIBRARY_VERSION}/src/config/{model}/{model}_{algo}_{volts}_{sample_rate}_{operating_age}/{filename}.txt" | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def download_bme68x_blob(config): | ||||||
|  |     url = _compute_url(config) | ||||||
|  |     path = _compute_local_file_path(url) | ||||||
|  |     external_files.download_content(url, path) | ||||||
|  |  | ||||||
|  |     return config | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def validate_bme68x(config): | ||||||
|  |     if CONF_ALGORITHM_OUTPUT not in config: | ||||||
|  |         return config | ||||||
|  |  | ||||||
|  |     if config[CONF_MODEL] != "bme688": | ||||||
|  |         raise cv.Invalid(f"{CONF_ALGORITHM_OUTPUT} is only valid for BME688") | ||||||
|  |  | ||||||
|  |     if config[CONF_ALGORITHM_OUTPUT] == "regression" and ( | ||||||
|  |         config[CONF_OPERATING_AGE] != "4d" | ||||||
|  |         or config[CONF_SAMPLE_RATE] != "ULP" | ||||||
|  |         or config[CONF_SUPPLY_VOLTAGE] != "1.8V" | ||||||
|  |     ): | ||||||
|  |         raise cv.Invalid( | ||||||
|  |             f" To use '{CONF_ALGORITHM_OUTPUT}: regression', {CONF_OPERATING_AGE} must be '4d', {CONF_SAMPLE_RATE} must be 'ULP' and {CONF_SUPPLY_VOLTAGE} must be '1.8V'" | ||||||
|  |         ) | ||||||
|  |     return config | ||||||
|  |  | ||||||
|  |  | ||||||
|  | CONFIG_SCHEMA_BASE = ( | ||||||
|  |     cv.Schema( | ||||||
|  |         { | ||||||
|  |             cv.GenerateID(): cv.declare_id(BME68xBSEC2Component), | ||||||
|  |             cv.GenerateID(CONF_RAW_DATA_ID): cv.declare_id(cg.uint8), | ||||||
|  |             cv.Required(CONF_MODEL): cv.one_of(*MODEL_OPTIONS, lower=True), | ||||||
|  |             cv.Optional(CONF_ALGORITHM_OUTPUT): cv.enum( | ||||||
|  |                 ALGORITHM_OUTPUT_OPTIONS, lower=True | ||||||
|  |             ), | ||||||
|  |             cv.Optional(CONF_OPERATING_AGE, default="28d"): cv.enum( | ||||||
|  |                 OPERATING_AGE_OPTIONS, lower=True | ||||||
|  |             ), | ||||||
|  |             cv.Optional(CONF_SAMPLE_RATE, default="LP"): cv.enum( | ||||||
|  |                 SAMPLE_RATE_OPTIONS, upper=True | ||||||
|  |             ), | ||||||
|  |             cv.Optional(CONF_SUPPLY_VOLTAGE, default="3.3V"): cv.enum( | ||||||
|  |                 VOLTAGE_OPTIONS, upper=True | ||||||
|  |             ), | ||||||
|  |             cv.Optional(CONF_TEMPERATURE_OFFSET, default=0): cv.temperature, | ||||||
|  |             cv.Optional( | ||||||
|  |                 CONF_STATE_SAVE_INTERVAL, default="6hours" | ||||||
|  |             ): cv.positive_time_period_minutes, | ||||||
|  |         }, | ||||||
|  |     ) | ||||||
|  |     .add_extra(cv.only_with_arduino) | ||||||
|  |     .add_extra(validate_bme68x) | ||||||
|  |     .add_extra(download_bme68x_blob) | ||||||
|  | ) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | async def to_code_base(config): | ||||||
|  |     var = cg.new_Pvariable(config[CONF_ID]) | ||||||
|  |     await cg.register_component(var, config) | ||||||
|  |  | ||||||
|  |     if algo_output := config.get(CONF_ALGORITHM_OUTPUT): | ||||||
|  |         cg.add(var.set_algorithm_output(algo_output)) | ||||||
|  |     cg.add(var.set_operating_age(config[CONF_OPERATING_AGE])) | ||||||
|  |     cg.add(var.set_sample_rate(config[CONF_SAMPLE_RATE])) | ||||||
|  |     cg.add(var.set_voltage(config[CONF_SUPPLY_VOLTAGE])) | ||||||
|  |     cg.add(var.set_temperature_offset(config[CONF_TEMPERATURE_OFFSET])) | ||||||
|  |     cg.add( | ||||||
|  |         var.set_state_save_interval(config[CONF_STATE_SAVE_INTERVAL].total_milliseconds) | ||||||
|  |     ) | ||||||
|  |  | ||||||
|  |     path = _compute_local_file_path(_compute_url(config)) | ||||||
|  |  | ||||||
|  |     try: | ||||||
|  |         with open(path, encoding="utf-8") as f: | ||||||
|  |             bsec2_iaq_config = f.read() | ||||||
|  |     except Exception as e: | ||||||
|  |         raise core.EsphomeError(f"Could not open binary configuration file {path}: {e}") | ||||||
|  |  | ||||||
|  |     # Convert retrieved BSEC2 config to an array of ints | ||||||
|  |     rhs = [int(x) for x in bsec2_iaq_config.split(",")] | ||||||
|  |     # Create an array which will reside in program memory and configure the sensor instance to use it | ||||||
|  |     bsec2_arr = cg.progmem_array(config[CONF_RAW_DATA_ID], rhs) | ||||||
|  |     cg.add(var.set_bsec2_configuration(bsec2_arr, len(rhs))) | ||||||
|  |  | ||||||
|  |     # Although this component does not use SPI, the BSEC2 library requires the SPI library | ||||||
|  |     cg.add_library("SPI", None) | ||||||
|  |     cg.add_library( | ||||||
|  |         "BME68x Sensor library", | ||||||
|  |         "1.1.40407", | ||||||
|  |     ) | ||||||
|  |     cg.add_library( | ||||||
|  |         "BSEC2 Software Library", | ||||||
|  |         None, | ||||||
|  |         f"https://github.com/boschsensortec/Bosch-BSEC2-Library.git#{BSEC2_LIBRARY_VERSION}", | ||||||
|  |     ) | ||||||
|  |  | ||||||
|  |     cg.add_define("USE_BSEC2") | ||||||
|  |  | ||||||
|  |     return var | ||||||
							
								
								
									
										523
									
								
								esphome/components/bme68x_bsec2/bme68x_bsec2.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										523
									
								
								esphome/components/bme68x_bsec2/bme68x_bsec2.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,523 @@ | |||||||
|  | #include "esphome/core/defines.h" | ||||||
|  | #include "esphome/core/helpers.h" | ||||||
|  | #include "esphome/core/log.h" | ||||||
|  |  | ||||||
|  | #ifdef USE_BSEC2 | ||||||
|  | #include "bme68x_bsec2.h" | ||||||
|  |  | ||||||
|  | #include <string> | ||||||
|  |  | ||||||
|  | namespace esphome { | ||||||
|  | namespace bme68x_bsec2 { | ||||||
|  |  | ||||||
|  | #define BME68X_BSEC2_ALGORITHM_OUTPUT_LOG(a) (a == ALGORITHM_OUTPUT_CLASSIFICATION ? "Classification" : "Regression") | ||||||
|  | #define BME68X_BSEC2_OPERATING_AGE_LOG(o) (o == OPERATING_AGE_4D ? "4 days" : "28 days") | ||||||
|  | #define BME68X_BSEC2_SAMPLE_RATE_LOG(r) (r == SAMPLE_RATE_DEFAULT ? "Default" : (r == SAMPLE_RATE_ULP ? "ULP" : "LP")) | ||||||
|  | #define BME68X_BSEC2_VOLTAGE_LOG(v) (v == VOLTAGE_3_3V ? "3.3V" : "1.8V") | ||||||
|  |  | ||||||
|  | static const char *const TAG = "bme68x_bsec2.sensor"; | ||||||
|  |  | ||||||
|  | static const std::string IAQ_ACCURACY_STATES[4] = {"Stabilizing", "Uncertain", "Calibrating", "Calibrated"}; | ||||||
|  |  | ||||||
|  | void BME68xBSEC2Component::setup() { | ||||||
|  |   ESP_LOGCONFIG(TAG, "Setting up BME68X via BSEC2..."); | ||||||
|  |  | ||||||
|  |   this->bsec_status_ = bsec_init_m(&this->bsec_instance_); | ||||||
|  |   if (this->bsec_status_ != BSEC_OK) { | ||||||
|  |     this->mark_failed(); | ||||||
|  |     ESP_LOGE(TAG, "bsec_init_m failed: status %d", this->bsec_status_); | ||||||
|  |     return; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   bsec_get_version_m(&this->bsec_instance_, &this->version_); | ||||||
|  |  | ||||||
|  |   this->bme68x_status_ = bme68x_init(&this->bme68x_); | ||||||
|  |   if (this->bme68x_status_ != BME68X_OK) { | ||||||
|  |     this->mark_failed(); | ||||||
|  |     ESP_LOGE(TAG, "bme68x_init failed: status %d", this->bme68x_status_); | ||||||
|  |     return; | ||||||
|  |   } | ||||||
|  |   if (this->bsec2_configuration_ != nullptr && this->bsec2_configuration_length_) { | ||||||
|  |     this->set_config_(this->bsec2_configuration_, this->bsec2_configuration_length_); | ||||||
|  |     if (this->bsec_status_ != BSEC_OK) { | ||||||
|  |       this->mark_failed(); | ||||||
|  |       ESP_LOGE(TAG, "bsec_set_configuration_m failed: status %d", this->bsec_status_); | ||||||
|  |       return; | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   this->update_subscription_(); | ||||||
|  |   if (this->bsec_status_ != BSEC_OK) { | ||||||
|  |     this->mark_failed(); | ||||||
|  |     ESP_LOGE(TAG, "bsec_update_subscription_m failed: status %d", this->bsec_status_); | ||||||
|  |     return; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   this->load_state_(); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void BME68xBSEC2Component::dump_config() { | ||||||
|  |   ESP_LOGCONFIG(TAG, "BME68X via BSEC2:"); | ||||||
|  |  | ||||||
|  |   ESP_LOGCONFIG(TAG, "  BSEC2 version: %d.%d.%d.%d", this->version_.major, this->version_.minor, | ||||||
|  |                 this->version_.major_bugfix, this->version_.minor_bugfix); | ||||||
|  |  | ||||||
|  |   ESP_LOGCONFIG(TAG, "  BSEC2 configuration blob:"); | ||||||
|  |   ESP_LOGCONFIG(TAG, "    Configured: %s", YESNO(this->bsec2_blob_configured_)); | ||||||
|  |   if (this->bsec2_configuration_ != nullptr && this->bsec2_configuration_length_) { | ||||||
|  |     ESP_LOGCONFIG(TAG, "    Size: %" PRIu32, this->bsec2_configuration_length_); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   if (this->is_failed()) { | ||||||
|  |     ESP_LOGE(TAG, "Communication failed (BSEC2 status: %d, BME68X status: %d)", this->bsec_status_, | ||||||
|  |              this->bme68x_status_); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   if (this->algorithm_output_ != ALGORITHM_OUTPUT_IAQ) { | ||||||
|  |     ESP_LOGCONFIG(TAG, "  Algorithm output: %s", BME68X_BSEC2_ALGORITHM_OUTPUT_LOG(this->algorithm_output_)); | ||||||
|  |   } | ||||||
|  |   ESP_LOGCONFIG(TAG, "  Operating age: %s", BME68X_BSEC2_OPERATING_AGE_LOG(this->operating_age_)); | ||||||
|  |   ESP_LOGCONFIG(TAG, "  Sample rate: %s", BME68X_BSEC2_SAMPLE_RATE_LOG(this->sample_rate_)); | ||||||
|  |   ESP_LOGCONFIG(TAG, "  Voltage: %s", BME68X_BSEC2_VOLTAGE_LOG(this->voltage_)); | ||||||
|  |   ESP_LOGCONFIG(TAG, "  State save interval: %ims", this->state_save_interval_ms_); | ||||||
|  |   ESP_LOGCONFIG(TAG, "  Temperature offset: %.2f", this->temperature_offset_); | ||||||
|  |  | ||||||
|  | #ifdef USE_SENSOR | ||||||
|  |   LOG_SENSOR("  ", "Temperature", this->temperature_sensor_); | ||||||
|  |   ESP_LOGCONFIG(TAG, "    Sample rate: %s", BME68X_BSEC2_SAMPLE_RATE_LOG(this->temperature_sample_rate_)); | ||||||
|  |   LOG_SENSOR("  ", "Pressure", this->pressure_sensor_); | ||||||
|  |   ESP_LOGCONFIG(TAG, "    Sample rate: %s", BME68X_BSEC2_SAMPLE_RATE_LOG(this->pressure_sample_rate_)); | ||||||
|  |   LOG_SENSOR("  ", "Humidity", this->humidity_sensor_); | ||||||
|  |   ESP_LOGCONFIG(TAG, "    Sample rate: %s", BME68X_BSEC2_SAMPLE_RATE_LOG(this->humidity_sample_rate_)); | ||||||
|  |   LOG_SENSOR("  ", "Gas resistance", this->gas_resistance_sensor_); | ||||||
|  |   LOG_SENSOR("  ", "CO2 equivalent", this->co2_equivalent_sensor_); | ||||||
|  |   LOG_SENSOR("  ", "Breath VOC equivalent", this->breath_voc_equivalent_sensor_); | ||||||
|  |   LOG_SENSOR("  ", "IAQ", this->iaq_sensor_); | ||||||
|  |   LOG_SENSOR("  ", "IAQ static", this->iaq_static_sensor_); | ||||||
|  |   LOG_SENSOR("  ", "Numeric IAQ accuracy", this->iaq_accuracy_sensor_); | ||||||
|  | #endif | ||||||
|  | #ifdef USE_TEXT_SENSOR | ||||||
|  |   LOG_TEXT_SENSOR("  ", "IAQ accuracy", this->iaq_accuracy_text_sensor_); | ||||||
|  | #endif | ||||||
|  | } | ||||||
|  |  | ||||||
|  | float BME68xBSEC2Component::get_setup_priority() const { return setup_priority::DATA; } | ||||||
|  |  | ||||||
|  | void BME68xBSEC2Component::loop() { | ||||||
|  |   this->run_(); | ||||||
|  |  | ||||||
|  |   if (this->bsec_status_ < BSEC_OK || this->bme68x_status_ < BME68X_OK) { | ||||||
|  |     this->status_set_error(); | ||||||
|  |   } else { | ||||||
|  |     this->status_clear_error(); | ||||||
|  |   } | ||||||
|  |   if (this->bsec_status_ > BSEC_OK || this->bme68x_status_ > BME68X_OK) { | ||||||
|  |     this->status_set_warning(); | ||||||
|  |   } else { | ||||||
|  |     this->status_clear_warning(); | ||||||
|  |   } | ||||||
|  |   // Process a single action from the queue. These are primarily sensor state publishes | ||||||
|  |   // that in totality take too long to send in a single call. | ||||||
|  |   if (this->queue_.size()) { | ||||||
|  |     auto action = std::move(this->queue_.front()); | ||||||
|  |     this->queue_.pop(); | ||||||
|  |     action(); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void BME68xBSEC2Component::set_config_(const uint8_t *config, uint32_t len) { | ||||||
|  |   if (len > BSEC_MAX_PROPERTY_BLOB_SIZE) { | ||||||
|  |     ESP_LOGE(TAG, "Configuration is larger than BSEC_MAX_PROPERTY_BLOB_SIZE"); | ||||||
|  |     this->mark_failed(); | ||||||
|  |     return; | ||||||
|  |   } | ||||||
|  |   uint8_t work_buffer[BSEC_MAX_PROPERTY_BLOB_SIZE]; | ||||||
|  |   this->bsec_status_ = bsec_set_configuration_m(&this->bsec_instance_, config, len, work_buffer, sizeof(work_buffer)); | ||||||
|  |   if (this->bsec_status_ == BSEC_OK) { | ||||||
|  |     this->bsec2_blob_configured_ = true; | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | float BME68xBSEC2Component::calc_sensor_sample_rate_(SampleRate sample_rate) { | ||||||
|  |   if (sample_rate == SAMPLE_RATE_DEFAULT) { | ||||||
|  |     sample_rate = this->sample_rate_; | ||||||
|  |   } | ||||||
|  |   return sample_rate == SAMPLE_RATE_ULP ? BSEC_SAMPLE_RATE_ULP : BSEC_SAMPLE_RATE_LP; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void BME68xBSEC2Component::update_subscription_() { | ||||||
|  |   bsec_sensor_configuration_t virtual_sensors[BSEC_NUMBER_OUTPUTS]; | ||||||
|  |   uint8_t num_virtual_sensors = 0; | ||||||
|  | #ifdef USE_SENSOR | ||||||
|  |   if (this->iaq_sensor_) { | ||||||
|  |     virtual_sensors[num_virtual_sensors].sensor_id = BSEC_OUTPUT_IAQ; | ||||||
|  |     virtual_sensors[num_virtual_sensors].sample_rate = this->calc_sensor_sample_rate_(SAMPLE_RATE_DEFAULT); | ||||||
|  |     num_virtual_sensors++; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   if (this->iaq_static_sensor_) { | ||||||
|  |     virtual_sensors[num_virtual_sensors].sensor_id = BSEC_OUTPUT_STATIC_IAQ; | ||||||
|  |     virtual_sensors[num_virtual_sensors].sample_rate = this->calc_sensor_sample_rate_(SAMPLE_RATE_DEFAULT); | ||||||
|  |     num_virtual_sensors++; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   if (this->co2_equivalent_sensor_) { | ||||||
|  |     virtual_sensors[num_virtual_sensors].sensor_id = BSEC_OUTPUT_CO2_EQUIVALENT; | ||||||
|  |     virtual_sensors[num_virtual_sensors].sample_rate = this->calc_sensor_sample_rate_(SAMPLE_RATE_DEFAULT); | ||||||
|  |     num_virtual_sensors++; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   if (this->breath_voc_equivalent_sensor_) { | ||||||
|  |     virtual_sensors[num_virtual_sensors].sensor_id = BSEC_OUTPUT_BREATH_VOC_EQUIVALENT; | ||||||
|  |     virtual_sensors[num_virtual_sensors].sample_rate = this->calc_sensor_sample_rate_(SAMPLE_RATE_DEFAULT); | ||||||
|  |     num_virtual_sensors++; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   if (this->pressure_sensor_) { | ||||||
|  |     virtual_sensors[num_virtual_sensors].sensor_id = BSEC_OUTPUT_RAW_PRESSURE; | ||||||
|  |     virtual_sensors[num_virtual_sensors].sample_rate = this->calc_sensor_sample_rate_(this->pressure_sample_rate_); | ||||||
|  |     num_virtual_sensors++; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   if (this->gas_resistance_sensor_) { | ||||||
|  |     virtual_sensors[num_virtual_sensors].sensor_id = BSEC_OUTPUT_RAW_GAS; | ||||||
|  |     virtual_sensors[num_virtual_sensors].sample_rate = this->calc_sensor_sample_rate_(SAMPLE_RATE_DEFAULT); | ||||||
|  |     num_virtual_sensors++; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   if (this->temperature_sensor_) { | ||||||
|  |     virtual_sensors[num_virtual_sensors].sensor_id = BSEC_OUTPUT_SENSOR_HEAT_COMPENSATED_TEMPERATURE; | ||||||
|  |     virtual_sensors[num_virtual_sensors].sample_rate = this->calc_sensor_sample_rate_(this->temperature_sample_rate_); | ||||||
|  |     num_virtual_sensors++; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   if (this->humidity_sensor_) { | ||||||
|  |     virtual_sensors[num_virtual_sensors].sensor_id = BSEC_OUTPUT_SENSOR_HEAT_COMPENSATED_HUMIDITY; | ||||||
|  |     virtual_sensors[num_virtual_sensors].sample_rate = this->calc_sensor_sample_rate_(this->humidity_sample_rate_); | ||||||
|  |     num_virtual_sensors++; | ||||||
|  |   } | ||||||
|  | #endif | ||||||
|  |   bsec_sensor_configuration_t sensor_settings[BSEC_MAX_PHYSICAL_SENSOR]; | ||||||
|  |   uint8_t num_sensor_settings = BSEC_MAX_PHYSICAL_SENSOR; | ||||||
|  |   this->bsec_status_ = bsec_update_subscription_m(&this->bsec_instance_, virtual_sensors, num_virtual_sensors, | ||||||
|  |                                                   sensor_settings, &num_sensor_settings); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void BME68xBSEC2Component::run_() { | ||||||
|  |   int64_t curr_time_ns = this->get_time_ns_(); | ||||||
|  |   if (curr_time_ns < this->next_call_ns_) { | ||||||
|  |     return; | ||||||
|  |   } | ||||||
|  |   this->op_mode_ = this->bsec_settings_.op_mode; | ||||||
|  |   uint8_t status; | ||||||
|  |  | ||||||
|  |   ESP_LOGV(TAG, "Performing sensor run"); | ||||||
|  |  | ||||||
|  |   struct bme68x_conf bme68x_conf; | ||||||
|  |   this->bsec_status_ = bsec_sensor_control_m(&this->bsec_instance_, curr_time_ns, &this->bsec_settings_); | ||||||
|  |   if (this->bsec_status_ < BSEC_OK) { | ||||||
|  |     ESP_LOGW(TAG, "Failed to fetch sensor control settings (BSEC2 error code %d)", this->bsec_status_); | ||||||
|  |     return; | ||||||
|  |   } | ||||||
|  |   this->next_call_ns_ = this->bsec_settings_.next_call; | ||||||
|  |  | ||||||
|  |   if (this->bsec_settings_.trigger_measurement) { | ||||||
|  |     bme68x_get_conf(&bme68x_conf, &this->bme68x_); | ||||||
|  |  | ||||||
|  |     bme68x_conf.os_hum = this->bsec_settings_.humidity_oversampling; | ||||||
|  |     bme68x_conf.os_temp = this->bsec_settings_.temperature_oversampling; | ||||||
|  |     bme68x_conf.os_pres = this->bsec_settings_.pressure_oversampling; | ||||||
|  |     bme68x_set_conf(&bme68x_conf, &this->bme68x_); | ||||||
|  |  | ||||||
|  |     switch (this->bsec_settings_.op_mode) { | ||||||
|  |       case BME68X_FORCED_MODE: | ||||||
|  |         this->bme68x_heatr_conf_.enable = BME68X_ENABLE; | ||||||
|  |         this->bme68x_heatr_conf_.heatr_temp = this->bsec_settings_.heater_temperature; | ||||||
|  |         this->bme68x_heatr_conf_.heatr_dur = this->bsec_settings_.heater_duration; | ||||||
|  |  | ||||||
|  |         status = bme68x_set_op_mode(this->bsec_settings_.op_mode, &this->bme68x_); | ||||||
|  |         status = bme68x_set_heatr_conf(BME68X_FORCED_MODE, &this->bme68x_heatr_conf_, &this->bme68x_); | ||||||
|  |         status = bme68x_set_op_mode(BME68X_FORCED_MODE, &this->bme68x_); | ||||||
|  |         this->op_mode_ = BME68X_FORCED_MODE; | ||||||
|  |         this->sleep_mode_ = false; | ||||||
|  |         ESP_LOGV(TAG, "Using forced mode"); | ||||||
|  |  | ||||||
|  |         break; | ||||||
|  |       case BME68X_PARALLEL_MODE: | ||||||
|  |         if (this->op_mode_ != this->bsec_settings_.op_mode) { | ||||||
|  |           this->bme68x_heatr_conf_.enable = BME68X_ENABLE; | ||||||
|  |           this->bme68x_heatr_conf_.heatr_temp_prof = this->bsec_settings_.heater_temperature_profile; | ||||||
|  |           this->bme68x_heatr_conf_.heatr_dur_prof = this->bsec_settings_.heater_duration_profile; | ||||||
|  |           this->bme68x_heatr_conf_.profile_len = this->bsec_settings_.heater_profile_len; | ||||||
|  |           this->bme68x_heatr_conf_.shared_heatr_dur = | ||||||
|  |               BSEC_TOTAL_HEAT_DUR - | ||||||
|  |               (bme68x_get_meas_dur(BME68X_PARALLEL_MODE, &bme68x_conf, &this->bme68x_) / INT64_C(1000)); | ||||||
|  |  | ||||||
|  |           status = bme68x_set_heatr_conf(BME68X_PARALLEL_MODE, &this->bme68x_heatr_conf_, &this->bme68x_); | ||||||
|  |  | ||||||
|  |           status = bme68x_set_op_mode(BME68X_PARALLEL_MODE, &this->bme68x_); | ||||||
|  |           this->op_mode_ = BME68X_PARALLEL_MODE; | ||||||
|  |           this->sleep_mode_ = false; | ||||||
|  |           ESP_LOGV(TAG, "Using parallel mode"); | ||||||
|  |         } | ||||||
|  |         break; | ||||||
|  |       case BME68X_SLEEP_MODE: | ||||||
|  |         if (!this->sleep_mode_) { | ||||||
|  |           bme68x_set_op_mode(BME68X_SLEEP_MODE, &this->bme68x_); | ||||||
|  |           this->sleep_mode_ = true; | ||||||
|  |           ESP_LOGV(TAG, "Using sleep mode"); | ||||||
|  |         } | ||||||
|  |         break; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     uint32_t meas_dur = 0; | ||||||
|  |     meas_dur = bme68x_get_meas_dur(this->op_mode_, &bme68x_conf, &this->bme68x_); | ||||||
|  |     ESP_LOGV(TAG, "Queueing read in %uus", meas_dur); | ||||||
|  |     this->set_timeout("read", meas_dur / 1000, [this, curr_time_ns]() { this->read_(curr_time_ns); }); | ||||||
|  |   } else { | ||||||
|  |     ESP_LOGV(TAG, "Measurement not required"); | ||||||
|  |     this->read_(curr_time_ns); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void BME68xBSEC2Component::read_(int64_t trigger_time_ns) { | ||||||
|  |   ESP_LOGV(TAG, "Reading data"); | ||||||
|  |  | ||||||
|  |   if (this->bsec_settings_.trigger_measurement) { | ||||||
|  |     uint8_t current_op_mode; | ||||||
|  |     this->bme68x_status_ = bme68x_get_op_mode(¤t_op_mode, &this->bme68x_); | ||||||
|  |  | ||||||
|  |     if (current_op_mode == BME68X_SLEEP_MODE) { | ||||||
|  |       ESP_LOGV(TAG, "Still in sleep mode, doing nothing"); | ||||||
|  |       return; | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   if (!this->bsec_settings_.process_data) { | ||||||
|  |     ESP_LOGV(TAG, "Data processing not required"); | ||||||
|  |     return; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   struct bme68x_data data[3]; | ||||||
|  |   uint8_t nFields = 0; | ||||||
|  |   this->bme68x_status_ = bme68x_get_data(this->op_mode_, &data[0], &nFields, &this->bme68x_); | ||||||
|  |  | ||||||
|  |   if (this->bme68x_status_ != BME68X_OK) { | ||||||
|  |     ESP_LOGW(TAG, "Failed to get sensor data (BME68X error code %d)", this->bme68x_status_); | ||||||
|  |     return; | ||||||
|  |   } | ||||||
|  |   if (nFields < 1) { | ||||||
|  |     ESP_LOGD(TAG, "BME68X did not provide new data"); | ||||||
|  |     return; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   for (uint8_t i = 0; i < nFields; i++) { | ||||||
|  |     bsec_input_t inputs[BSEC_MAX_PHYSICAL_SENSOR];  // Temperature, Pressure, Humidity & Gas Resistance | ||||||
|  |     uint8_t num_inputs = 0; | ||||||
|  |  | ||||||
|  |     if (BSEC_CHECK_INPUT(this->bsec_settings_.process_data, BSEC_INPUT_TEMPERATURE)) { | ||||||
|  |       inputs[num_inputs].sensor_id = BSEC_INPUT_TEMPERATURE; | ||||||
|  |       inputs[num_inputs].signal = data[i].temperature; | ||||||
|  |       inputs[num_inputs].time_stamp = trigger_time_ns; | ||||||
|  |       num_inputs++; | ||||||
|  |     } | ||||||
|  |     if (BSEC_CHECK_INPUT(this->bsec_settings_.process_data, BSEC_INPUT_HEATSOURCE)) { | ||||||
|  |       inputs[num_inputs].sensor_id = BSEC_INPUT_HEATSOURCE; | ||||||
|  |       inputs[num_inputs].signal = this->temperature_offset_; | ||||||
|  |       inputs[num_inputs].time_stamp = trigger_time_ns; | ||||||
|  |       num_inputs++; | ||||||
|  |     } | ||||||
|  |     if (BSEC_CHECK_INPUT(this->bsec_settings_.process_data, BSEC_INPUT_HUMIDITY)) { | ||||||
|  |       inputs[num_inputs].sensor_id = BSEC_INPUT_HUMIDITY; | ||||||
|  |       inputs[num_inputs].signal = data[i].humidity; | ||||||
|  |       inputs[num_inputs].time_stamp = trigger_time_ns; | ||||||
|  |       num_inputs++; | ||||||
|  |     } | ||||||
|  |     if (BSEC_CHECK_INPUT(this->bsec_settings_.process_data, BSEC_INPUT_PRESSURE)) { | ||||||
|  |       inputs[num_inputs].sensor_id = BSEC_INPUT_PRESSURE; | ||||||
|  |       inputs[num_inputs].signal = data[i].pressure; | ||||||
|  |       inputs[num_inputs].time_stamp = trigger_time_ns; | ||||||
|  |       num_inputs++; | ||||||
|  |     } | ||||||
|  |     if (BSEC_CHECK_INPUT(this->bsec_settings_.process_data, BSEC_INPUT_GASRESISTOR)) { | ||||||
|  |       if (data[i].status & BME68X_GASM_VALID_MSK) { | ||||||
|  |         inputs[num_inputs].sensor_id = BSEC_INPUT_GASRESISTOR; | ||||||
|  |         inputs[num_inputs].signal = data[i].gas_resistance; | ||||||
|  |         inputs[num_inputs].time_stamp = trigger_time_ns; | ||||||
|  |         num_inputs++; | ||||||
|  |       } else { | ||||||
|  |         ESP_LOGD(TAG, "BME68X did not report gas data"); | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |     if (BSEC_CHECK_INPUT(this->bsec_settings_.process_data, BSEC_INPUT_PROFILE_PART) && | ||||||
|  |         (data[i].status & BME68X_GASM_VALID_MSK)) { | ||||||
|  |       inputs[num_inputs].sensor_id = BSEC_INPUT_PROFILE_PART; | ||||||
|  |       inputs[num_inputs].signal = (this->op_mode_ == BME68X_FORCED_MODE) ? 0 : data[i].gas_index; | ||||||
|  |       inputs[num_inputs].time_stamp = trigger_time_ns; | ||||||
|  |       num_inputs++; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     if (num_inputs < 1) { | ||||||
|  |       ESP_LOGD(TAG, "No signal inputs available for BSEC2"); | ||||||
|  |       return; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     bsec_output_t outputs[BSEC_NUMBER_OUTPUTS]; | ||||||
|  |     uint8_t num_outputs = BSEC_NUMBER_OUTPUTS; | ||||||
|  |     this->bsec_status_ = bsec_do_steps_m(&this->bsec_instance_, inputs, num_inputs, outputs, &num_outputs); | ||||||
|  |     if (this->bsec_status_ != BSEC_OK) { | ||||||
|  |       ESP_LOGW(TAG, "BSEC2 failed to process signals (BSEC2 error code %d)", this->bsec_status_); | ||||||
|  |       return; | ||||||
|  |     } | ||||||
|  |     if (num_outputs < 1) { | ||||||
|  |       ESP_LOGD(TAG, "No signal outputs provided by BSEC2"); | ||||||
|  |       return; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     this->publish_(outputs, num_outputs); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void BME68xBSEC2Component::publish_(const bsec_output_t *outputs, uint8_t num_outputs) { | ||||||
|  |   ESP_LOGV(TAG, "Publishing sensor states"); | ||||||
|  |   bool update_accuracy = false; | ||||||
|  |   uint8_t max_accuracy = 0; | ||||||
|  |   for (uint8_t i = 0; i < num_outputs; i++) { | ||||||
|  |     float signal = outputs[i].signal; | ||||||
|  |     switch (outputs[i].sensor_id) { | ||||||
|  |       case BSEC_OUTPUT_IAQ: | ||||||
|  |         max_accuracy = std::max(outputs[i].accuracy, max_accuracy); | ||||||
|  |         update_accuracy = true; | ||||||
|  | #ifdef USE_SENSOR | ||||||
|  |         this->queue_push_([this, signal]() { this->publish_sensor_(this->iaq_sensor_, signal); }); | ||||||
|  | #endif | ||||||
|  |         break; | ||||||
|  |       case BSEC_OUTPUT_STATIC_IAQ: | ||||||
|  |         max_accuracy = std::max(outputs[i].accuracy, max_accuracy); | ||||||
|  |         update_accuracy = true; | ||||||
|  | #ifdef USE_SENSOR | ||||||
|  |         this->queue_push_([this, signal]() { this->publish_sensor_(this->iaq_static_sensor_, signal); }); | ||||||
|  | #endif | ||||||
|  |         break; | ||||||
|  |       case BSEC_OUTPUT_CO2_EQUIVALENT: | ||||||
|  | #ifdef USE_SENSOR | ||||||
|  |         this->queue_push_([this, signal]() { this->publish_sensor_(this->co2_equivalent_sensor_, signal); }); | ||||||
|  | #endif | ||||||
|  |         break; | ||||||
|  |       case BSEC_OUTPUT_BREATH_VOC_EQUIVALENT: | ||||||
|  | #ifdef USE_SENSOR | ||||||
|  |         this->queue_push_([this, signal]() { this->publish_sensor_(this->breath_voc_equivalent_sensor_, signal); }); | ||||||
|  | #endif | ||||||
|  |         break; | ||||||
|  |       case BSEC_OUTPUT_RAW_PRESSURE: | ||||||
|  | #ifdef USE_SENSOR | ||||||
|  |         this->queue_push_([this, signal]() { this->publish_sensor_(this->pressure_sensor_, signal / 100.0f); }); | ||||||
|  | #endif | ||||||
|  |         break; | ||||||
|  |       case BSEC_OUTPUT_RAW_GAS: | ||||||
|  | #ifdef USE_SENSOR | ||||||
|  |         this->queue_push_([this, signal]() { this->publish_sensor_(this->gas_resistance_sensor_, signal); }); | ||||||
|  | #endif | ||||||
|  |         break; | ||||||
|  |       case BSEC_OUTPUT_SENSOR_HEAT_COMPENSATED_TEMPERATURE: | ||||||
|  | #ifdef USE_SENSOR | ||||||
|  |         this->queue_push_([this, signal]() { this->publish_sensor_(this->temperature_sensor_, signal); }); | ||||||
|  | #endif | ||||||
|  |         break; | ||||||
|  |       case BSEC_OUTPUT_SENSOR_HEAT_COMPENSATED_HUMIDITY: | ||||||
|  | #ifdef USE_SENSOR | ||||||
|  |         this->queue_push_([this, signal]() { this->publish_sensor_(this->humidity_sensor_, signal); }); | ||||||
|  | #endif | ||||||
|  |         break; | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |   if (update_accuracy) { | ||||||
|  | #ifdef USE_SENSOR | ||||||
|  |     this->queue_push_( | ||||||
|  |         [this, max_accuracy]() { this->publish_sensor_(this->iaq_accuracy_sensor_, max_accuracy, true); }); | ||||||
|  | #endif | ||||||
|  | #ifdef USE_TEXT_SENSOR | ||||||
|  |     this->queue_push_([this, max_accuracy]() { | ||||||
|  |       this->publish_sensor_(this->iaq_accuracy_text_sensor_, IAQ_ACCURACY_STATES[max_accuracy]); | ||||||
|  |     }); | ||||||
|  | #endif | ||||||
|  |     // Queue up an opportunity to save state | ||||||
|  |     this->queue_push_([this, max_accuracy]() { this->save_state_(max_accuracy); }); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | int64_t BME68xBSEC2Component::get_time_ns_() { | ||||||
|  |   int64_t time_ms = millis(); | ||||||
|  |   if (this->last_time_ms_ > time_ms) { | ||||||
|  |     this->millis_overflow_counter_++; | ||||||
|  |   } | ||||||
|  |   this->last_time_ms_ = time_ms; | ||||||
|  |  | ||||||
|  |   return (time_ms + ((int64_t) this->millis_overflow_counter_ << 32)) * INT64_C(1000000); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #ifdef USE_SENSOR | ||||||
|  | void BME68xBSEC2Component::publish_sensor_(sensor::Sensor *sensor, float value, bool change_only) { | ||||||
|  |   if (!sensor || (change_only && sensor->has_state() && sensor->state == value)) { | ||||||
|  |     return; | ||||||
|  |   } | ||||||
|  |   sensor->publish_state(value); | ||||||
|  | } | ||||||
|  | #endif | ||||||
|  |  | ||||||
|  | #ifdef USE_TEXT_SENSOR | ||||||
|  | void BME68xBSEC2Component::publish_sensor_(text_sensor::TextSensor *sensor, const std::string &value) { | ||||||
|  |   if (!sensor || (sensor->has_state() && sensor->state == value)) { | ||||||
|  |     return; | ||||||
|  |   } | ||||||
|  |   sensor->publish_state(value); | ||||||
|  | } | ||||||
|  | #endif | ||||||
|  |  | ||||||
|  | void BME68xBSEC2Component::load_state_() { | ||||||
|  |   uint32_t hash = this->get_hash(); | ||||||
|  |   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_m(&this->bsec_instance_, state, BSEC_MAX_STATE_BLOB_SIZE, work_buffer, sizeof(work_buffer)); | ||||||
|  |     if (this->bsec_status_ != BSEC_OK) { | ||||||
|  |       ESP_LOGW(TAG, "Failed to load state (BSEC2 error code %d)", this->bsec_status_); | ||||||
|  |     } | ||||||
|  |     ESP_LOGI(TAG, "Loaded state"); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void BME68xBSEC2Component::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_m(&this->bsec_instance_, 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 (BSEC2 error code %d)", this->bsec_status_); | ||||||
|  |     return; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   if (!this->bsec_state_.save(&state)) { | ||||||
|  |     ESP_LOGW(TAG, "Failed to save state"); | ||||||
|  |     return; | ||||||
|  |   } | ||||||
|  |   this->last_state_save_ms_ = millis(); | ||||||
|  |  | ||||||
|  |   ESP_LOGI(TAG, "Saved state"); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | }  // namespace bme68x_bsec2 | ||||||
|  | }  // namespace esphome | ||||||
|  | #endif | ||||||
							
								
								
									
										163
									
								
								esphome/components/bme68x_bsec2/bme68x_bsec2.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										163
									
								
								esphome/components/bme68x_bsec2/bme68x_bsec2.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,163 @@ | |||||||
|  | #pragma once | ||||||
|  |  | ||||||
|  | #include "esphome/core/component.h" | ||||||
|  | #include "esphome/core/defines.h" | ||||||
|  | #include "esphome/core/preferences.h" | ||||||
|  |  | ||||||
|  | #ifdef USE_BSEC2 | ||||||
|  |  | ||||||
|  | #ifdef USE_SENSOR | ||||||
|  | #include "esphome/components/sensor/sensor.h" | ||||||
|  | #endif | ||||||
|  |  | ||||||
|  | #ifdef USE_TEXT_SENSOR | ||||||
|  | #include "esphome/components/text_sensor/text_sensor.h" | ||||||
|  | #endif | ||||||
|  |  | ||||||
|  | #include <cinttypes> | ||||||
|  | #include <queue> | ||||||
|  |  | ||||||
|  | #include <bsec2.h> | ||||||
|  |  | ||||||
|  | namespace esphome { | ||||||
|  | namespace bme68x_bsec2 { | ||||||
|  |  | ||||||
|  | enum AlgorithmOutput { | ||||||
|  |   ALGORITHM_OUTPUT_IAQ, | ||||||
|  |   ALGORITHM_OUTPUT_CLASSIFICATION, | ||||||
|  |   ALGORITHM_OUTPUT_REGRESSION, | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | enum OperatingAge { | ||||||
|  |   OPERATING_AGE_4D, | ||||||
|  |   OPERATING_AGE_28D, | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | enum SampleRate { | ||||||
|  |   SAMPLE_RATE_LP = 0, | ||||||
|  |   SAMPLE_RATE_ULP = 1, | ||||||
|  |   SAMPLE_RATE_DEFAULT = 2, | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | enum Voltage { | ||||||
|  |   VOLTAGE_1_8V, | ||||||
|  |   VOLTAGE_3_3V, | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | class BME68xBSEC2Component : public Component { | ||||||
|  |  public: | ||||||
|  |   void setup() override; | ||||||
|  |   void dump_config() override; | ||||||
|  |   float get_setup_priority() const override; | ||||||
|  |   void loop() override; | ||||||
|  |  | ||||||
|  |   void set_algorithm_output(AlgorithmOutput algorithm_output) { this->algorithm_output_ = algorithm_output; } | ||||||
|  |   void set_operating_age(OperatingAge operating_age) { this->operating_age_ = operating_age; } | ||||||
|  |   void set_temperature_offset(float offset) { this->temperature_offset_ = offset; } | ||||||
|  |   void set_voltage(Voltage voltage) { this->voltage_ = voltage; } | ||||||
|  |  | ||||||
|  |   void set_sample_rate(SampleRate sample_rate) { this->sample_rate_ = sample_rate; } | ||||||
|  |   void set_temperature_sample_rate(SampleRate sample_rate) { this->temperature_sample_rate_ = sample_rate; } | ||||||
|  |   void set_pressure_sample_rate(SampleRate sample_rate) { this->pressure_sample_rate_ = sample_rate; } | ||||||
|  |   void set_humidity_sample_rate(SampleRate sample_rate) { this->humidity_sample_rate_ = sample_rate; } | ||||||
|  |  | ||||||
|  |   void set_bsec2_configuration(const uint8_t *data, const uint32_t len) { | ||||||
|  |     this->bsec2_configuration_ = data; | ||||||
|  |     this->bsec2_configuration_length_ = len; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   void set_state_save_interval(uint32_t interval) { this->state_save_interval_ms_ = interval; } | ||||||
|  |  | ||||||
|  | #ifdef USE_SENSOR | ||||||
|  |   void set_temperature_sensor(sensor::Sensor *sensor) { this->temperature_sensor_ = sensor; } | ||||||
|  |   void set_pressure_sensor(sensor::Sensor *sensor) { this->pressure_sensor_ = sensor; } | ||||||
|  |   void set_humidity_sensor(sensor::Sensor *sensor) { this->humidity_sensor_ = sensor; } | ||||||
|  |   void set_gas_resistance_sensor(sensor::Sensor *sensor) { this->gas_resistance_sensor_ = sensor; } | ||||||
|  |   void set_iaq_sensor(sensor::Sensor *sensor) { this->iaq_sensor_ = sensor; } | ||||||
|  |   void set_iaq_static_sensor(sensor::Sensor *sensor) { this->iaq_static_sensor_ = sensor; } | ||||||
|  |   void set_iaq_accuracy_sensor(sensor::Sensor *sensor) { this->iaq_accuracy_sensor_ = sensor; } | ||||||
|  |   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; } | ||||||
|  | #endif | ||||||
|  | #ifdef USE_TEXT_SENSOR | ||||||
|  |   void set_iaq_accuracy_text_sensor(text_sensor::TextSensor *sensor) { this->iaq_accuracy_text_sensor_ = sensor; } | ||||||
|  | #endif | ||||||
|  |   virtual uint32_t get_hash() = 0; | ||||||
|  |  | ||||||
|  |  protected: | ||||||
|  |   void set_config_(const uint8_t *config, u_int32_t len); | ||||||
|  |   float calc_sensor_sample_rate_(SampleRate sample_rate); | ||||||
|  |   void update_subscription_(); | ||||||
|  |  | ||||||
|  |   void run_(); | ||||||
|  |   void read_(int64_t trigger_time_ns); | ||||||
|  |   void publish_(const bsec_output_t *outputs, uint8_t num_outputs); | ||||||
|  |   int64_t get_time_ns_(); | ||||||
|  |  | ||||||
|  | #ifdef USE_SENSOR | ||||||
|  |   void publish_sensor_(sensor::Sensor *sensor, float value, bool change_only = false); | ||||||
|  | #endif | ||||||
|  | #ifdef USE_TEXT_SENSOR | ||||||
|  |   void publish_sensor_(text_sensor::TextSensor *sensor, const std::string &value); | ||||||
|  | #endif | ||||||
|  |  | ||||||
|  |   void load_state_(); | ||||||
|  |   void save_state_(uint8_t accuracy); | ||||||
|  |  | ||||||
|  |   void queue_push_(std::function<void()> &&f) { this->queue_.push(std::move(f)); } | ||||||
|  |  | ||||||
|  |   struct bme68x_dev bme68x_; | ||||||
|  |   bsec_bme_settings_t bsec_settings_; | ||||||
|  |   bsec_version_t version_; | ||||||
|  |   uint8_t bsec_instance_[BSEC_INSTANCE_SIZE]; | ||||||
|  |  | ||||||
|  |   struct bme68x_heatr_conf bme68x_heatr_conf_; | ||||||
|  |   uint8_t op_mode_;  // operating mode of sensor | ||||||
|  |   bool sleep_mode_; | ||||||
|  |   bsec_library_return_t bsec_status_{BSEC_OK}; | ||||||
|  |   int8_t bme68x_status_{BME68X_OK}; | ||||||
|  |  | ||||||
|  |   int64_t last_time_ms_{0}; | ||||||
|  |   uint32_t millis_overflow_counter_{0}; | ||||||
|  |   int64_t next_call_ns_{0}; | ||||||
|  |  | ||||||
|  |   std::queue<std::function<void()>> queue_; | ||||||
|  |  | ||||||
|  |   uint8_t const *bsec2_configuration_{nullptr}; | ||||||
|  |   uint32_t bsec2_configuration_length_{0}; | ||||||
|  |   bool bsec2_blob_configured_{false}; | ||||||
|  |  | ||||||
|  |   ESPPreferenceObject bsec_state_; | ||||||
|  |   uint32_t state_save_interval_ms_{21600000};  // 6 hours - 4 times a day | ||||||
|  |   uint32_t last_state_save_ms_ = 0; | ||||||
|  |  | ||||||
|  |   float temperature_offset_{0}; | ||||||
|  |  | ||||||
|  |   AlgorithmOutput algorithm_output_{ALGORITHM_OUTPUT_IAQ}; | ||||||
|  |   OperatingAge operating_age_{OPERATING_AGE_28D}; | ||||||
|  |   Voltage voltage_{VOLTAGE_3_3V}; | ||||||
|  |  | ||||||
|  |   SampleRate sample_rate_{SAMPLE_RATE_LP};  // Core/gas sample rate | ||||||
|  |   SampleRate temperature_sample_rate_{SAMPLE_RATE_DEFAULT}; | ||||||
|  |   SampleRate pressure_sample_rate_{SAMPLE_RATE_DEFAULT}; | ||||||
|  |   SampleRate humidity_sample_rate_{SAMPLE_RATE_DEFAULT}; | ||||||
|  |  | ||||||
|  | #ifdef USE_SENSOR | ||||||
|  |   sensor::Sensor *temperature_sensor_{nullptr}; | ||||||
|  |   sensor::Sensor *pressure_sensor_{nullptr}; | ||||||
|  |   sensor::Sensor *humidity_sensor_{nullptr}; | ||||||
|  |   sensor::Sensor *gas_resistance_sensor_{nullptr}; | ||||||
|  |   sensor::Sensor *iaq_sensor_{nullptr}; | ||||||
|  |   sensor::Sensor *iaq_static_sensor_{nullptr}; | ||||||
|  |   sensor::Sensor *iaq_accuracy_sensor_{nullptr}; | ||||||
|  |   sensor::Sensor *co2_equivalent_sensor_{nullptr}; | ||||||
|  |   sensor::Sensor *breath_voc_equivalent_sensor_{nullptr}; | ||||||
|  | #endif | ||||||
|  | #ifdef USE_TEXT_SENSOR | ||||||
|  |   text_sensor::TextSensor *iaq_accuracy_text_sensor_{nullptr}; | ||||||
|  | #endif | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | }  // namespace bme68x_bsec2 | ||||||
|  | }  // namespace esphome | ||||||
|  | #endif | ||||||
							
								
								
									
										130
									
								
								esphome/components/bme68x_bsec2/sensor.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										130
									
								
								esphome/components/bme68x_bsec2/sensor.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,130 @@ | |||||||
|  | import esphome.codegen as cg | ||||||
|  | from esphome.components import sensor | ||||||
|  | import esphome.config_validation as cv | ||||||
|  | from esphome.const import ( | ||||||
|  |     CONF_GAS_RESISTANCE, | ||||||
|  |     CONF_HUMIDITY, | ||||||
|  |     CONF_IAQ_ACCURACY, | ||||||
|  |     CONF_PRESSURE, | ||||||
|  |     CONF_SAMPLE_RATE, | ||||||
|  |     CONF_TEMPERATURE, | ||||||
|  |     DEVICE_CLASS_ATMOSPHERIC_PRESSURE, | ||||||
|  |     DEVICE_CLASS_HUMIDITY, | ||||||
|  |     DEVICE_CLASS_TEMPERATURE, | ||||||
|  |     ICON_GAS_CYLINDER, | ||||||
|  |     ICON_GAUGE, | ||||||
|  |     ICON_THERMOMETER, | ||||||
|  |     ICON_WATER_PERCENT, | ||||||
|  |     STATE_CLASS_MEASUREMENT, | ||||||
|  |     UNIT_CELSIUS, | ||||||
|  |     UNIT_HECTOPASCAL, | ||||||
|  |     UNIT_OHM, | ||||||
|  |     UNIT_PARTS_PER_MILLION, | ||||||
|  |     UNIT_PERCENT, | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | from . import CONF_BME68X_BSEC2_ID, SAMPLE_RATE_OPTIONS, BME68xBSEC2Component | ||||||
|  |  | ||||||
|  | DEPENDENCIES = ["bme68x_bsec2"] | ||||||
|  |  | ||||||
|  | CONF_BREATH_VOC_EQUIVALENT = "breath_voc_equivalent" | ||||||
|  | CONF_CO2_EQUIVALENT = "co2_equivalent" | ||||||
|  | CONF_IAQ = "iaq" | ||||||
|  | CONF_IAQ_STATIC = "iaq_static" | ||||||
|  | ICON_ACCURACY = "mdi:checkbox-marked-circle-outline" | ||||||
|  | ICON_TEST_TUBE = "mdi:test-tube" | ||||||
|  | UNIT_IAQ = "IAQ" | ||||||
|  |  | ||||||
|  | TYPES = [ | ||||||
|  |     CONF_TEMPERATURE, | ||||||
|  |     CONF_PRESSURE, | ||||||
|  |     CONF_HUMIDITY, | ||||||
|  |     CONF_GAS_RESISTANCE, | ||||||
|  |     CONF_IAQ, | ||||||
|  |     CONF_IAQ_STATIC, | ||||||
|  |     CONF_IAQ_ACCURACY, | ||||||
|  |     CONF_CO2_EQUIVALENT, | ||||||
|  |     CONF_BREATH_VOC_EQUIVALENT, | ||||||
|  | ] | ||||||
|  |  | ||||||
|  | CONFIG_SCHEMA = cv.Schema( | ||||||
|  |     { | ||||||
|  |         cv.GenerateID(CONF_BME68X_BSEC2_ID): cv.use_id(BME68xBSEC2Component), | ||||||
|  |         cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema( | ||||||
|  |             unit_of_measurement=UNIT_CELSIUS, | ||||||
|  |             icon=ICON_THERMOMETER, | ||||||
|  |             accuracy_decimals=1, | ||||||
|  |             device_class=DEVICE_CLASS_TEMPERATURE, | ||||||
|  |             state_class=STATE_CLASS_MEASUREMENT, | ||||||
|  |         ).extend( | ||||||
|  |             {cv.Optional(CONF_SAMPLE_RATE): cv.enum(SAMPLE_RATE_OPTIONS, upper=True)} | ||||||
|  |         ), | ||||||
|  |         cv.Optional(CONF_PRESSURE): sensor.sensor_schema( | ||||||
|  |             unit_of_measurement=UNIT_HECTOPASCAL, | ||||||
|  |             icon=ICON_GAUGE, | ||||||
|  |             accuracy_decimals=1, | ||||||
|  |             device_class=DEVICE_CLASS_ATMOSPHERIC_PRESSURE, | ||||||
|  |             state_class=STATE_CLASS_MEASUREMENT, | ||||||
|  |         ).extend( | ||||||
|  |             {cv.Optional(CONF_SAMPLE_RATE): cv.enum(SAMPLE_RATE_OPTIONS, upper=True)} | ||||||
|  |         ), | ||||||
|  |         cv.Optional(CONF_HUMIDITY): sensor.sensor_schema( | ||||||
|  |             unit_of_measurement=UNIT_PERCENT, | ||||||
|  |             icon=ICON_WATER_PERCENT, | ||||||
|  |             accuracy_decimals=1, | ||||||
|  |             device_class=DEVICE_CLASS_HUMIDITY, | ||||||
|  |             state_class=STATE_CLASS_MEASUREMENT, | ||||||
|  |         ).extend( | ||||||
|  |             {cv.Optional(CONF_SAMPLE_RATE): cv.enum(SAMPLE_RATE_OPTIONS, upper=True)} | ||||||
|  |         ), | ||||||
|  |         cv.Optional(CONF_GAS_RESISTANCE): sensor.sensor_schema( | ||||||
|  |             unit_of_measurement=UNIT_OHM, | ||||||
|  |             icon=ICON_GAS_CYLINDER, | ||||||
|  |             accuracy_decimals=0, | ||||||
|  |             state_class=STATE_CLASS_MEASUREMENT, | ||||||
|  |         ), | ||||||
|  |         cv.Optional(CONF_IAQ): sensor.sensor_schema( | ||||||
|  |             unit_of_measurement=UNIT_IAQ, | ||||||
|  |             icon=ICON_GAUGE, | ||||||
|  |             accuracy_decimals=0, | ||||||
|  |             state_class=STATE_CLASS_MEASUREMENT, | ||||||
|  |         ), | ||||||
|  |         cv.Optional(CONF_IAQ_STATIC): sensor.sensor_schema( | ||||||
|  |             unit_of_measurement=UNIT_IAQ, | ||||||
|  |             icon=ICON_GAUGE, | ||||||
|  |             accuracy_decimals=0, | ||||||
|  |             state_class=STATE_CLASS_MEASUREMENT, | ||||||
|  |         ), | ||||||
|  |         cv.Optional(CONF_IAQ_ACCURACY): sensor.sensor_schema( | ||||||
|  |             icon=ICON_ACCURACY, | ||||||
|  |             accuracy_decimals=0, | ||||||
|  |             state_class=STATE_CLASS_MEASUREMENT, | ||||||
|  |         ), | ||||||
|  |         cv.Optional(CONF_CO2_EQUIVALENT): sensor.sensor_schema( | ||||||
|  |             unit_of_measurement=UNIT_PARTS_PER_MILLION, | ||||||
|  |             icon=ICON_TEST_TUBE, | ||||||
|  |             accuracy_decimals=1, | ||||||
|  |             state_class=STATE_CLASS_MEASUREMENT, | ||||||
|  |         ), | ||||||
|  |         cv.Optional(CONF_BREATH_VOC_EQUIVALENT): sensor.sensor_schema( | ||||||
|  |             unit_of_measurement=UNIT_PARTS_PER_MILLION, | ||||||
|  |             icon=ICON_TEST_TUBE, | ||||||
|  |             accuracy_decimals=1, | ||||||
|  |             state_class=STATE_CLASS_MEASUREMENT, | ||||||
|  |         ), | ||||||
|  |     } | ||||||
|  | ) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | async def setup_conf(config, key, hub): | ||||||
|  |     if conf := config.get(key): | ||||||
|  |         sens = await sensor.new_sensor(conf) | ||||||
|  |         cg.add(getattr(hub, f"set_{key}_sensor")(sens)) | ||||||
|  |         if sample_rate := conf.get(CONF_SAMPLE_RATE): | ||||||
|  |             cg.add(getattr(hub, f"set_{key}_sample_rate")(sample_rate)) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | async def to_code(config): | ||||||
|  |     hub = await cg.get_variable(config[CONF_BME68X_BSEC2_ID]) | ||||||
|  |     for key in TYPES: | ||||||
|  |         await setup_conf(config, key, hub) | ||||||
							
								
								
									
										33
									
								
								esphome/components/bme68x_bsec2/text_sensor.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										33
									
								
								esphome/components/bme68x_bsec2/text_sensor.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,33 @@ | |||||||
|  | import esphome.codegen as cg | ||||||
|  | from esphome.components import text_sensor | ||||||
|  | import esphome.config_validation as cv | ||||||
|  | from esphome.const import CONF_IAQ_ACCURACY | ||||||
|  |  | ||||||
|  | from . import CONF_BME68X_BSEC2_ID, BME68xBSEC2Component | ||||||
|  |  | ||||||
|  | DEPENDENCIES = ["bme68x_bsec2"] | ||||||
|  |  | ||||||
|  | ICON_ACCURACY = "mdi:checkbox-marked-circle-outline" | ||||||
|  |  | ||||||
|  | TYPES = [CONF_IAQ_ACCURACY] | ||||||
|  |  | ||||||
|  | CONFIG_SCHEMA = cv.Schema( | ||||||
|  |     { | ||||||
|  |         cv.GenerateID(CONF_BME68X_BSEC2_ID): cv.use_id(BME68xBSEC2Component), | ||||||
|  |         cv.Optional(CONF_IAQ_ACCURACY): text_sensor.text_sensor_schema( | ||||||
|  |             icon=ICON_ACCURACY | ||||||
|  |         ), | ||||||
|  |     } | ||||||
|  | ) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | async def setup_conf(config, key, hub): | ||||||
|  |     if conf := config.get(key): | ||||||
|  |         sens = await text_sensor.new_text_sensor(conf) | ||||||
|  |         cg.add(getattr(hub, f"set_{key}_text_sensor")(sens)) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | async def to_code(config): | ||||||
|  |     hub = await cg.get_variable(config[CONF_BME68X_BSEC2_ID]) | ||||||
|  |     for key in TYPES: | ||||||
|  |         await setup_conf(config, key, hub) | ||||||
							
								
								
									
										28
									
								
								esphome/components/bme68x_bsec2_i2c/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										28
									
								
								esphome/components/bme68x_bsec2_i2c/__init__.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,28 @@ | |||||||
|  | import esphome.codegen as cg | ||||||
|  | from esphome.components import i2c | ||||||
|  | from esphome.components.bme68x_bsec2 import ( | ||||||
|  |     CONFIG_SCHEMA_BASE, | ||||||
|  |     BME68xBSEC2Component, | ||||||
|  |     to_code_base, | ||||||
|  | ) | ||||||
|  | import esphome.config_validation as cv | ||||||
|  |  | ||||||
|  | CODEOWNERS = ["@neffs", "@kbx81"] | ||||||
|  |  | ||||||
|  | AUTO_LOAD = ["bme68x_bsec2"] | ||||||
|  | DEPENDENCIES = ["i2c"] | ||||||
|  |  | ||||||
|  | bme68x_bsec2_i2c_ns = cg.esphome_ns.namespace("bme68x_bsec2_i2c") | ||||||
|  | BME68xBSEC2I2CComponent = bme68x_bsec2_i2c_ns.class_( | ||||||
|  |     "BME68xBSEC2I2CComponent", BME68xBSEC2Component, i2c.I2CDevice | ||||||
|  | ) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | CONFIG_SCHEMA = CONFIG_SCHEMA_BASE.extend( | ||||||
|  |     cv.Schema({cv.GenerateID(): cv.declare_id(BME68xBSEC2I2CComponent)}) | ||||||
|  | ).extend(i2c.i2c_device_schema(0x76)) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | async def to_code(config): | ||||||
|  |     var = await to_code_base(config) | ||||||
|  |     await i2c.register_i2c_device(var, config) | ||||||
							
								
								
									
										53
									
								
								esphome/components/bme68x_bsec2_i2c/bme68x_bsec2_i2c.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										53
									
								
								esphome/components/bme68x_bsec2_i2c/bme68x_bsec2_i2c.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,53 @@ | |||||||
|  | #include "esphome/core/defines.h" | ||||||
|  | #include "esphome/core/helpers.h" | ||||||
|  | #include "esphome/core/log.h" | ||||||
|  |  | ||||||
|  | #ifdef USE_BSEC2 | ||||||
|  | #include "bme68x_bsec2_i2c.h" | ||||||
|  | #include "esphome/components/i2c/i2c.h" | ||||||
|  |  | ||||||
|  | #include <cinttypes> | ||||||
|  |  | ||||||
|  | namespace esphome { | ||||||
|  | namespace bme68x_bsec2_i2c { | ||||||
|  |  | ||||||
|  | static const char *const TAG = "bme68x_bsec2_i2c.sensor"; | ||||||
|  |  | ||||||
|  | void BME68xBSEC2I2CComponent::setup() { | ||||||
|  |   // must set up our bme68x_dev instance before calling setup() | ||||||
|  |   this->bme68x_.intf_ptr = (void *) this; | ||||||
|  |   this->bme68x_.intf = BME68X_I2C_INTF; | ||||||
|  |   this->bme68x_.read = BME68xBSEC2I2CComponent::read_bytes_wrapper; | ||||||
|  |   this->bme68x_.write = BME68xBSEC2I2CComponent::write_bytes_wrapper; | ||||||
|  |   this->bme68x_.delay_us = BME68xBSEC2I2CComponent::delay_us; | ||||||
|  |   this->bme68x_.amb_temp = 25; | ||||||
|  |  | ||||||
|  |   BME68xBSEC2Component::setup(); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void BME68xBSEC2I2CComponent::dump_config() { | ||||||
|  |   LOG_I2C_DEVICE(this); | ||||||
|  |   BME68xBSEC2Component::dump_config(); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | uint32_t BME68xBSEC2I2CComponent::get_hash() { return fnv1_hash("bme68x_bsec_state_" + to_string(this->address_)); } | ||||||
|  |  | ||||||
|  | int8_t BME68xBSEC2I2CComponent::read_bytes_wrapper(uint8_t a_register, uint8_t *data, uint32_t len, void *intfPtr) { | ||||||
|  |   ESP_LOGVV(TAG, "read_bytes_wrapper: reg = %u", a_register); | ||||||
|  |   return static_cast<BME68xBSEC2I2CComponent *>(intfPtr)->read_bytes(a_register, data, len) ? 0 : -1; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | int8_t BME68xBSEC2I2CComponent::write_bytes_wrapper(uint8_t a_register, const uint8_t *data, uint32_t len, | ||||||
|  |                                                     void *intfPtr) { | ||||||
|  |   ESP_LOGVV(TAG, "write_bytes_wrapper: reg = %u", a_register); | ||||||
|  |   return static_cast<BME68xBSEC2I2CComponent *>(intfPtr)->write_bytes(a_register, data, len) ? 0 : -1; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void BME68xBSEC2I2CComponent::delay_us(uint32_t period, void *intfPtr) { | ||||||
|  |   ESP_LOGVV(TAG, "Delaying for %" PRIu32 "us", period); | ||||||
|  |   delayMicroseconds(period); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | }  // namespace bme68x_bsec2_i2c | ||||||
|  | }  // namespace esphome | ||||||
|  | #endif | ||||||
							
								
								
									
										28
									
								
								esphome/components/bme68x_bsec2_i2c/bme68x_bsec2_i2c.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										28
									
								
								esphome/components/bme68x_bsec2_i2c/bme68x_bsec2_i2c.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,28 @@ | |||||||
|  | #pragma once | ||||||
|  |  | ||||||
|  | #include "esphome/core/component.h" | ||||||
|  | #include "esphome/core/defines.h" | ||||||
|  | #include "esphome/core/preferences.h" | ||||||
|  |  | ||||||
|  | #ifdef USE_BSEC2 | ||||||
|  |  | ||||||
|  | #include "esphome/components/bme68x_bsec2/bme68x_bsec2.h" | ||||||
|  | #include "esphome/components/i2c/i2c.h" | ||||||
|  |  | ||||||
|  | namespace esphome { | ||||||
|  | namespace bme68x_bsec2_i2c { | ||||||
|  |  | ||||||
|  | class BME68xBSEC2I2CComponent : public bme68x_bsec2::BME68xBSEC2Component, public i2c::I2CDevice { | ||||||
|  |   void setup() override; | ||||||
|  |   void dump_config() override; | ||||||
|  |  | ||||||
|  |   uint32_t get_hash() override; | ||||||
|  |  | ||||||
|  |   static int8_t read_bytes_wrapper(uint8_t a_register, uint8_t *data, uint32_t len, void *intfPtr); | ||||||
|  |   static int8_t write_bytes_wrapper(uint8_t a_register, const uint8_t *data, uint32_t len, void *intfPtr); | ||||||
|  |   static void delay_us(uint32_t period, void *intfPtr); | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | }  // namespace bme68x_bsec2_i2c | ||||||
|  | }  // namespace esphome | ||||||
|  | #endif | ||||||
| @@ -158,6 +158,7 @@ | |||||||
| #endif | #endif | ||||||
|  |  | ||||||
| // Disabled feature flags | // Disabled feature flags | ||||||
| // #define USE_BSEC  // Requires a library with proprietary license. | // #define USE_BSEC   // Requires a library with proprietary license | ||||||
|  | // #define USE_BSEC2  // Requires a library with proprietary license | ||||||
|  |  | ||||||
| #define USE_DASHBOARD_IMPORT | #define USE_DASHBOARD_IMPORT | ||||||
|   | |||||||
							
								
								
									
										34
									
								
								tests/components/bme68x_bsec2_i2c/common.yaml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										34
									
								
								tests/components/bme68x_bsec2_i2c/common.yaml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,34 @@ | |||||||
|  | i2c: | ||||||
|  |   - id: i2c_bme68x | ||||||
|  |     scl: ${scl_pin} | ||||||
|  |     sda: ${sda_pin} | ||||||
|  |  | ||||||
|  | bme68x_bsec2_i2c: | ||||||
|  |   address: 0x76 | ||||||
|  |   model: bme688 | ||||||
|  |   algorithm_output: classification | ||||||
|  |   operating_age: 28d | ||||||
|  |   sample_rate: LP | ||||||
|  |   supply_voltage: 3.3V | ||||||
|  |  | ||||||
|  | sensor: | ||||||
|  |   - platform: bme68x_bsec2 | ||||||
|  |     temperature: | ||||||
|  |       name: BME68X Temperature | ||||||
|  |     pressure: | ||||||
|  |       name: BME68X Pressure | ||||||
|  |     humidity: | ||||||
|  |       name: BME68X Humidity | ||||||
|  |     gas_resistance: | ||||||
|  |       name: BME68X Gas Sensor | ||||||
|  |     iaq: | ||||||
|  |       name: BME68X IAQ | ||||||
|  |     co2_equivalent: | ||||||
|  |       name: BME68X eCO2 | ||||||
|  |     breath_voc_equivalent: | ||||||
|  |       name: BME68X Breath eVOC | ||||||
|  |  | ||||||
|  | text_sensor: | ||||||
|  |   - platform: bme68x_bsec2 | ||||||
|  |     iaq_accuracy: | ||||||
|  |       name: BME68X Accuracy | ||||||
							
								
								
									
										5
									
								
								tests/components/bme68x_bsec2_i2c/test.esp32-ard.yaml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								tests/components/bme68x_bsec2_i2c/test.esp32-ard.yaml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,5 @@ | |||||||
|  | substitutions: | ||||||
|  |   scl_pin: GPIO16 | ||||||
|  |   sda_pin: GPIO17 | ||||||
|  |  | ||||||
|  | <<: !include common.yaml | ||||||
							
								
								
									
										5
									
								
								tests/components/bme68x_bsec2_i2c/test.esp32-c3-ard.yaml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								tests/components/bme68x_bsec2_i2c/test.esp32-c3-ard.yaml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,5 @@ | |||||||
|  | substitutions: | ||||||
|  |   scl_pin: GPIO6 | ||||||
|  |   sda_pin: GPIO7 | ||||||
|  |  | ||||||
|  | <<: !include common.yaml | ||||||
							
								
								
									
										5
									
								
								tests/components/bme68x_bsec2_i2c/test.esp32-s2-ard.yaml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								tests/components/bme68x_bsec2_i2c/test.esp32-s2-ard.yaml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,5 @@ | |||||||
|  | substitutions: | ||||||
|  |   scl_pin: GPIO16 | ||||||
|  |   sda_pin: GPIO17 | ||||||
|  |  | ||||||
|  | <<: !include common.yaml | ||||||
							
								
								
									
										5
									
								
								tests/components/bme68x_bsec2_i2c/test.esp32-s3-ard.yaml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								tests/components/bme68x_bsec2_i2c/test.esp32-s3-ard.yaml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,5 @@ | |||||||
|  | substitutions: | ||||||
|  |   scl_pin: GPIO16 | ||||||
|  |   sda_pin: GPIO17 | ||||||
|  |  | ||||||
|  | <<: !include common.yaml | ||||||
							
								
								
									
										5
									
								
								tests/components/bme68x_bsec2_i2c/test.esp8266-ard.yaml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								tests/components/bme68x_bsec2_i2c/test.esp8266-ard.yaml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,5 @@ | |||||||
|  | substitutions: | ||||||
|  |   scl_pin: GPIO5 | ||||||
|  |   sda_pin: GPIO4 | ||||||
|  |  | ||||||
|  | <<: !include common.yaml | ||||||
		Reference in New Issue
	
	Block a user