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_spi/* @apbodrov | ||||
| esphome/components/bme680_bsec/* @trvrnrth | ||||
| esphome/components/bme68x_bsec2/* @kbx81 @neffs | ||||
| esphome/components/bme68x_bsec2_i2c/* @kbx81 @neffs | ||||
| esphome/components/bmi160/* @flaviut | ||||
| esphome/components/bmp3xx/* @latonita | ||||
| 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 | ||||
|  | ||||
| // 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 | ||||
|   | ||||
							
								
								
									
										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