mirror of
				https://github.com/esphome/esphome.git
				synced 2025-10-31 15:12:06 +00:00 
			
		
		
		
	Add BME680 via BSEC integration (#1313)
This commit is contained in:
		| @@ -20,6 +20,7 @@ esphome/components/async_tcp/* @OttoWinter | |||||||
| esphome/components/atc_mithermometer/* @ahpohl | esphome/components/atc_mithermometer/* @ahpohl | ||||||
| esphome/components/bang_bang/* @OttoWinter | esphome/components/bang_bang/* @OttoWinter | ||||||
| esphome/components/binary_sensor/* @esphome/core | esphome/components/binary_sensor/* @esphome/core | ||||||
|  | esphome/components/bme680_bsec/* @trvrnrth | ||||||
| esphome/components/canbus/* @danielschramm @mvturnho | esphome/components/canbus/* @danielschramm @mvturnho | ||||||
| esphome/components/captive_portal/* @OttoWinter | esphome/components/captive_portal/* @OttoWinter | ||||||
| esphome/components/climate/* @esphome/core | esphome/components/climate/* @esphome/core | ||||||
|   | |||||||
							
								
								
									
										64
									
								
								esphome/components/bme680_bsec/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										64
									
								
								esphome/components/bme680_bsec/__init__.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,64 @@ | |||||||
|  | import esphome.codegen as cg | ||||||
|  | import esphome.config_validation as cv | ||||||
|  | from esphome.components import i2c | ||||||
|  | from esphome.const import CONF_ID | ||||||
|  |  | ||||||
|  | CODEOWNERS = ["@trvrnrth"] | ||||||
|  | DEPENDENCIES = ["i2c"] | ||||||
|  | AUTO_LOAD = ["sensor", "text_sensor"] | ||||||
|  |  | ||||||
|  | CONF_BME680_BSEC_ID = "bme680_bsec_id" | ||||||
|  | CONF_TEMPERATURE_OFFSET = "temperature_offset" | ||||||
|  | CONF_IAQ_MODE = "iaq_mode" | ||||||
|  | CONF_SAMPLE_RATE = "sample_rate" | ||||||
|  | CONF_STATE_SAVE_INTERVAL = "state_save_interval" | ||||||
|  |  | ||||||
|  | bme680_bsec_ns = cg.esphome_ns.namespace("bme680_bsec") | ||||||
|  |  | ||||||
|  | IAQMode = bme680_bsec_ns.enum("IAQMode") | ||||||
|  | IAQ_MODE_OPTIONS = { | ||||||
|  |     "STATIC": IAQMode.IAQ_MODE_STATIC, | ||||||
|  |     "MOBILE": IAQMode.IAQ_MODE_MOBILE, | ||||||
|  | } | ||||||
|  |  | ||||||
|  | SampleRate = bme680_bsec_ns.enum("SampleRate") | ||||||
|  | SAMPLE_RATE_OPTIONS = { | ||||||
|  |     "LP": SampleRate.SAMPLE_RATE_LP, | ||||||
|  |     "ULP": SampleRate.SAMPLE_RATE_ULP, | ||||||
|  | } | ||||||
|  |  | ||||||
|  | BME680BSECComponent = bme680_bsec_ns.class_( | ||||||
|  |     "BME680BSECComponent", cg.Component, i2c.I2CDevice | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | CONFIG_SCHEMA = cv.Schema( | ||||||
|  |     { | ||||||
|  |         cv.GenerateID(): cv.declare_id(BME680BSECComponent), | ||||||
|  |         cv.Optional(CONF_TEMPERATURE_OFFSET, default=0): cv.temperature, | ||||||
|  |         cv.Optional(CONF_IAQ_MODE, default="STATIC"): cv.enum( | ||||||
|  |             IAQ_MODE_OPTIONS, upper=True | ||||||
|  |         ), | ||||||
|  |         cv.Optional(CONF_SAMPLE_RATE, default="LP"): cv.enum( | ||||||
|  |             SAMPLE_RATE_OPTIONS, upper=True | ||||||
|  |         ), | ||||||
|  |         cv.Optional( | ||||||
|  |             CONF_STATE_SAVE_INTERVAL, default="6hours" | ||||||
|  |         ): cv.positive_time_period_minutes, | ||||||
|  |     } | ||||||
|  | ).extend(i2c.i2c_device_schema(0x76)) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def to_code(config): | ||||||
|  |     var = cg.new_Pvariable(config[CONF_ID]) | ||||||
|  |     yield cg.register_component(var, config) | ||||||
|  |     yield i2c.register_i2c_device(var, config) | ||||||
|  |  | ||||||
|  |     cg.add(var.set_temperature_offset(config[CONF_TEMPERATURE_OFFSET])) | ||||||
|  |     cg.add(var.set_iaq_mode(config[CONF_IAQ_MODE])) | ||||||
|  |     cg.add(var.set_sample_rate(config[CONF_SAMPLE_RATE])) | ||||||
|  |     cg.add( | ||||||
|  |         var.set_state_save_interval(config[CONF_STATE_SAVE_INTERVAL].total_milliseconds) | ||||||
|  |     ) | ||||||
|  |  | ||||||
|  |     cg.add_build_flag("-DUSING_BSEC") | ||||||
|  |     cg.add_library("BSEC Software Library", "1.6.1480") | ||||||
							
								
								
									
										398
									
								
								esphome/components/bme680_bsec/bme680_bsec.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										398
									
								
								esphome/components/bme680_bsec/bme680_bsec.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,398 @@ | |||||||
|  | #ifdef USING_BSEC | ||||||
|  |  | ||||||
|  | #include "bme680_bsec.h" | ||||||
|  | #include "esphome/core/log.h" | ||||||
|  | #include "esphome/core/helpers.h" | ||||||
|  | #include <string> | ||||||
|  |  | ||||||
|  | namespace esphome { | ||||||
|  | namespace bme680_bsec { | ||||||
|  |  | ||||||
|  | static const char *TAG = "bme680_bsec.sensor"; | ||||||
|  |  | ||||||
|  | static const std::string IAQ_ACCURACY_STATES[4] = {"Stabilizing", "Uncertain", "Calibrating", "Calibrated"}; | ||||||
|  |  | ||||||
|  | BME680BSECComponent *BME680BSECComponent::instance; | ||||||
|  |  | ||||||
|  | void BME680BSECComponent::setup() { | ||||||
|  |   ESP_LOGCONFIG(TAG, "Setting up BME680 via BSEC..."); | ||||||
|  |   BME680BSECComponent::instance = this; | ||||||
|  |  | ||||||
|  |   this->bsec_status_ = bsec_init(); | ||||||
|  |   if (this->bsec_status_ != BSEC_OK) { | ||||||
|  |     this->mark_failed(); | ||||||
|  |     return; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   this->bme680_.dev_id = this->address_; | ||||||
|  |   this->bme680_.intf = BME680_I2C_INTF; | ||||||
|  |   this->bme680_.read = BME680BSECComponent::read_bytes_wrapper; | ||||||
|  |   this->bme680_.write = BME680BSECComponent::write_bytes_wrapper; | ||||||
|  |   this->bme680_.delay_ms = BME680BSECComponent::delay_ms; | ||||||
|  |   this->bme680_.amb_temp = 25; | ||||||
|  |   this->bme680_.power_mode = BME680_FORCED_MODE; | ||||||
|  |  | ||||||
|  |   this->bme680_status_ = bme680_init(&this->bme680_); | ||||||
|  |   if (this->bme680_status_ != BME680_OK) { | ||||||
|  |     this->mark_failed(); | ||||||
|  |     return; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   if (this->sample_rate_ == SAMPLE_RATE_ULP) { | ||||||
|  |     const uint8_t bsec_config[] = { | ||||||
|  | #include "config/generic_33v_300s_28d/bsec_iaq.txt" | ||||||
|  |     }; | ||||||
|  |     this->set_config_(bsec_config); | ||||||
|  |     this->update_subscription_(BSEC_SAMPLE_RATE_ULP); | ||||||
|  |   } else { | ||||||
|  |     const uint8_t bsec_config[] = { | ||||||
|  | #include "config/generic_33v_3s_28d/bsec_iaq.txt" | ||||||
|  |     }; | ||||||
|  |     this->set_config_(bsec_config); | ||||||
|  |     this->update_subscription_(BSEC_SAMPLE_RATE_LP); | ||||||
|  |   } | ||||||
|  |   if (this->bsec_status_ != BSEC_OK) { | ||||||
|  |     this->mark_failed(); | ||||||
|  |     return; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   this->load_state_(); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void BME680BSECComponent::set_config_(const uint8_t *config) { | ||||||
|  |   uint8_t work_buffer[BSEC_MAX_WORKBUFFER_SIZE]; | ||||||
|  |   this->bsec_status_ = bsec_set_configuration(config, BSEC_MAX_PROPERTY_BLOB_SIZE, work_buffer, sizeof(work_buffer)); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void BME680BSECComponent::update_subscription_(float sample_rate) { | ||||||
|  |   bsec_sensor_configuration_t virtual_sensors[BSEC_NUMBER_OUTPUTS]; | ||||||
|  |   int num_virtual_sensors = 0; | ||||||
|  |  | ||||||
|  |   if (this->iaq_sensor_) { | ||||||
|  |     virtual_sensors[num_virtual_sensors].sensor_id = | ||||||
|  |         this->iaq_mode_ == IAQ_MODE_STATIC ? BSEC_OUTPUT_STATIC_IAQ : BSEC_OUTPUT_IAQ; | ||||||
|  |     virtual_sensors[num_virtual_sensors].sample_rate = sample_rate; | ||||||
|  |     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 = sample_rate; | ||||||
|  |     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 = sample_rate; | ||||||
|  |     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 = 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 = sample_rate; | ||||||
|  |     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 = 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 = sample_rate; | ||||||
|  |     num_virtual_sensors++; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   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(virtual_sensors, num_virtual_sensors, sensor_settings, &num_sensor_settings); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void BME680BSECComponent::dump_config() { | ||||||
|  |   ESP_LOGCONFIG(TAG, "BME680 via BSEC:"); | ||||||
|  |  | ||||||
|  |   bsec_version_t version; | ||||||
|  |   bsec_get_version(&version); | ||||||
|  |   ESP_LOGCONFIG(TAG, "  BSEC Version: %d.%d.%d.%d", version.major, version.minor, version.major_bugfix, | ||||||
|  |                 version.minor_bugfix); | ||||||
|  |  | ||||||
|  |   LOG_I2C_DEVICE(this); | ||||||
|  |  | ||||||
|  |   if (this->is_failed()) { | ||||||
|  |     ESP_LOGE(TAG, "Communication failed (BSEC Status: %d, BME680 Status: %d)", this->bsec_status_, | ||||||
|  |              this->bme680_status_); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   ESP_LOGCONFIG(TAG, "  Temperature Offset: %.2f", this->temperature_offset_); | ||||||
|  |   ESP_LOGCONFIG(TAG, "  IAQ Mode: %s", this->iaq_mode_ == IAQ_MODE_STATIC ? "Static" : "Mobile"); | ||||||
|  |   ESP_LOGCONFIG(TAG, "  Sample Rate: %s", this->sample_rate_ == SAMPLE_RATE_ULP ? "ULP" : "LP"); | ||||||
|  |   ESP_LOGCONFIG(TAG, "  State Save Interval: %ims", this->state_save_interval_ms_); | ||||||
|  |  | ||||||
|  |   LOG_SENSOR("  ", "Temperature", this->temperature_sensor_); | ||||||
|  |   LOG_SENSOR("  ", "Pressure", this->pressure_sensor_); | ||||||
|  |   LOG_SENSOR("  ", "Humidity", this->humidity_sensor_); | ||||||
|  |   LOG_SENSOR("  ", "Gas Resistance", this->gas_resistance_sensor_); | ||||||
|  |   LOG_SENSOR("  ", "IAQ", this->iaq_sensor_); | ||||||
|  |   LOG_SENSOR("  ", "Numeric IAQ Accuracy", this->iaq_accuracy_sensor_); | ||||||
|  |   LOG_TEXT_SENSOR("  ", "IAQ Accuracy", this->iaq_accuracy_text_sensor_); | ||||||
|  |   LOG_SENSOR("  ", "CO2 Equivalent", this->co2_equivalent_sensor_); | ||||||
|  |   LOG_SENSOR("  ", "Breath VOC Equivalent", this->breath_voc_equivalent_sensor_); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | float BME680BSECComponent::get_setup_priority() const { return setup_priority::DATA; } | ||||||
|  |  | ||||||
|  | void BME680BSECComponent::loop() { | ||||||
|  |   this->run_(); | ||||||
|  |  | ||||||
|  |   if (this->bsec_status_ < BSEC_OK || this->bme680_status_ < BME680_OK) { | ||||||
|  |     this->status_set_error(); | ||||||
|  |   } else { | ||||||
|  |     this->status_clear_error(); | ||||||
|  |   } | ||||||
|  |   if (this->bsec_status_ > BSEC_OK || this->bme680_status_ > BME680_OK) { | ||||||
|  |     this->status_set_warning(); | ||||||
|  |   } else { | ||||||
|  |     this->status_clear_warning(); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void BME680BSECComponent::run_() { | ||||||
|  |   int64_t curr_time_ns = this->get_time_ns_(); | ||||||
|  |   if (curr_time_ns < this->next_call_ns_) { | ||||||
|  |     return; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   ESP_LOGV(TAG, "Performing sensor run"); | ||||||
|  |  | ||||||
|  |   bsec_bme_settings_t bme680_settings; | ||||||
|  |   this->bsec_status_ = bsec_sensor_control(curr_time_ns, &bme680_settings); | ||||||
|  |   if (this->bsec_status_ < BSEC_OK) { | ||||||
|  |     ESP_LOGW(TAG, "Failed to fetch sensor control settings (BSEC Error Code %d)", this->bsec_status_); | ||||||
|  |     return; | ||||||
|  |   } | ||||||
|  |   this->next_call_ns_ = bme680_settings.next_call; | ||||||
|  |  | ||||||
|  |   this->bme680_.gas_sett.run_gas = bme680_settings.run_gas; | ||||||
|  |   this->bme680_.tph_sett.os_hum = bme680_settings.humidity_oversampling; | ||||||
|  |   this->bme680_.tph_sett.os_temp = bme680_settings.temperature_oversampling; | ||||||
|  |   this->bme680_.tph_sett.os_pres = bme680_settings.pressure_oversampling; | ||||||
|  |   this->bme680_.gas_sett.heatr_temp = bme680_settings.heater_temperature; | ||||||
|  |   this->bme680_.gas_sett.heatr_dur = bme680_settings.heating_duration; | ||||||
|  |   uint16_t desired_settings = | ||||||
|  |       BME680_OST_SEL | BME680_OSP_SEL | BME680_OSH_SEL | BME680_FILTER_SEL | BME680_GAS_SENSOR_SEL; | ||||||
|  |   this->bme680_status_ = bme680_set_sensor_settings(desired_settings, &this->bme680_); | ||||||
|  |   if (this->bme680_status_ != BME680_OK) { | ||||||
|  |     ESP_LOGW(TAG, "Failed to set sensor settings (BME680 Error Code %d)", this->bme680_status_); | ||||||
|  |     return; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   this->bme680_status_ = bme680_set_sensor_mode(&this->bme680_); | ||||||
|  |   if (this->bme680_status_ != BME680_OK) { | ||||||
|  |     ESP_LOGW(TAG, "Failed to set sensor mode (BME680 Error Code %d)", this->bme680_status_); | ||||||
|  |     return; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   uint16_t meas_dur = 0; | ||||||
|  |   bme680_get_profile_dur(&meas_dur, &this->bme680_); | ||||||
|  |   ESP_LOGV(TAG, "Queueing read in %ums", meas_dur); | ||||||
|  |   this->set_timeout("read", meas_dur, [this, bme680_settings]() { this->read_(bme680_settings); }); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void BME680BSECComponent::read_(bsec_bme_settings_t bme680_settings) { | ||||||
|  |   ESP_LOGV(TAG, "Reading data"); | ||||||
|  |   struct bme680_field_data data; | ||||||
|  |   this->bme680_status_ = bme680_get_sensor_data(&data, &this->bme680_); | ||||||
|  |  | ||||||
|  |   if (this->bme680_status_ != BME680_OK) { | ||||||
|  |     ESP_LOGW(TAG, "Failed to get sensor data (BME680 Error Code %d)", this->bme680_status_); | ||||||
|  |     return; | ||||||
|  |   } | ||||||
|  |   if (!(data.status & BME680_NEW_DATA_MSK)) { | ||||||
|  |     ESP_LOGD(TAG, "BME680 did not report new data"); | ||||||
|  |     return; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   bsec_input_t inputs[BSEC_MAX_PHYSICAL_SENSOR];  // Temperature, Pressure, Humidity & Gas Resistance | ||||||
|  |   uint8_t num_inputs = 0; | ||||||
|  |   int64_t curr_time_ns = this->get_time_ns_(); | ||||||
|  |  | ||||||
|  |   if (bme680_settings.process_data & BSEC_PROCESS_TEMPERATURE) { | ||||||
|  |     inputs[num_inputs].sensor_id = BSEC_INPUT_TEMPERATURE; | ||||||
|  |     inputs[num_inputs].signal = data.temperature / 100.0f; | ||||||
|  |     inputs[num_inputs].time_stamp = curr_time_ns; | ||||||
|  |     num_inputs++; | ||||||
|  |  | ||||||
|  |     // Temperature offset from the real temperature due to external heat sources | ||||||
|  |     inputs[num_inputs].sensor_id = BSEC_INPUT_HEATSOURCE; | ||||||
|  |     inputs[num_inputs].signal = this->temperature_offset_; | ||||||
|  |     inputs[num_inputs].time_stamp = curr_time_ns; | ||||||
|  |     num_inputs++; | ||||||
|  |   } | ||||||
|  |   if (bme680_settings.process_data & BSEC_PROCESS_HUMIDITY) { | ||||||
|  |     inputs[num_inputs].sensor_id = BSEC_INPUT_HUMIDITY; | ||||||
|  |     inputs[num_inputs].signal = data.humidity / 1000.0f; | ||||||
|  |     inputs[num_inputs].time_stamp = curr_time_ns; | ||||||
|  |     num_inputs++; | ||||||
|  |   } | ||||||
|  |   if (bme680_settings.process_data & BSEC_PROCESS_PRESSURE) { | ||||||
|  |     inputs[num_inputs].sensor_id = BSEC_INPUT_PRESSURE; | ||||||
|  |     inputs[num_inputs].signal = data.pressure; | ||||||
|  |     inputs[num_inputs].time_stamp = curr_time_ns; | ||||||
|  |     num_inputs++; | ||||||
|  |   } | ||||||
|  |   if (bme680_settings.process_data & BSEC_PROCESS_GAS) { | ||||||
|  |     inputs[num_inputs].sensor_id = BSEC_INPUT_GASRESISTOR; | ||||||
|  |     inputs[num_inputs].signal = data.gas_resistance; | ||||||
|  |     inputs[num_inputs].time_stamp = curr_time_ns; | ||||||
|  |     num_inputs++; | ||||||
|  |   } | ||||||
|  |   if (num_inputs < 1) { | ||||||
|  |     ESP_LOGD(TAG, "No signal inputs available for BSEC"); | ||||||
|  |     return; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   bsec_output_t outputs[BSEC_NUMBER_OUTPUTS]; | ||||||
|  |   uint8_t num_outputs = BSEC_NUMBER_OUTPUTS; | ||||||
|  |   this->bsec_status_ = bsec_do_steps(inputs, num_inputs, outputs, &num_outputs); | ||||||
|  |   if (this->bsec_status_ != BSEC_OK) { | ||||||
|  |     ESP_LOGW(TAG, "BSEC failed to process signals (BSEC Error Code %d)", this->bsec_status_); | ||||||
|  |     return; | ||||||
|  |   } | ||||||
|  |   if (num_outputs < 1) { | ||||||
|  |     ESP_LOGD(TAG, "No signal outputs provided by BSEC"); | ||||||
|  |     return; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   this->publish_(outputs, num_outputs); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void BME680BSECComponent::publish_(const bsec_output_t *outputs, uint8_t num_outputs) { | ||||||
|  |   ESP_LOGV(TAG, "Publishing sensor states"); | ||||||
|  |   for (uint8_t i = 0; i < num_outputs; i++) { | ||||||
|  |     switch (outputs[i].sensor_id) { | ||||||
|  |       case BSEC_OUTPUT_IAQ: | ||||||
|  |       case BSEC_OUTPUT_STATIC_IAQ: | ||||||
|  |         uint8_t accuracy; | ||||||
|  |         accuracy = outputs[i].accuracy; | ||||||
|  |         this->publish_sensor_state_(this->iaq_sensor_, outputs[i].signal); | ||||||
|  |         this->publish_sensor_state_(this->iaq_accuracy_text_sensor_, IAQ_ACCURACY_STATES[accuracy]); | ||||||
|  |         this->publish_sensor_state_(this->iaq_accuracy_sensor_, accuracy, true); | ||||||
|  |  | ||||||
|  |         // Queue up an opportunity to save state | ||||||
|  |         this->defer("save_state", [this, accuracy]() { this->save_state_(accuracy); }); | ||||||
|  |         break; | ||||||
|  |       case BSEC_OUTPUT_CO2_EQUIVALENT: | ||||||
|  |         this->publish_sensor_state_(this->co2_equivalent_sensor_, outputs[i].signal); | ||||||
|  |         break; | ||||||
|  |       case BSEC_OUTPUT_BREATH_VOC_EQUIVALENT: | ||||||
|  |         this->publish_sensor_state_(this->breath_voc_equivalent_sensor_, outputs[i].signal); | ||||||
|  |         break; | ||||||
|  |       case BSEC_OUTPUT_RAW_PRESSURE: | ||||||
|  |         this->publish_sensor_state_(this->pressure_sensor_, outputs[i].signal / 100.0f); | ||||||
|  |         break; | ||||||
|  |       case BSEC_OUTPUT_RAW_GAS: | ||||||
|  |         this->publish_sensor_state_(this->gas_resistance_sensor_, outputs[i].signal); | ||||||
|  |         break; | ||||||
|  |       case BSEC_OUTPUT_SENSOR_HEAT_COMPENSATED_TEMPERATURE: | ||||||
|  |         this->publish_sensor_state_(this->temperature_sensor_, outputs[i].signal); | ||||||
|  |         break; | ||||||
|  |       case BSEC_OUTPUT_SENSOR_HEAT_COMPENSATED_HUMIDITY: | ||||||
|  |         this->publish_sensor_state_(this->humidity_sensor_, outputs[i].signal); | ||||||
|  |         break; | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | int64_t BME680BSECComponent::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); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void BME680BSECComponent::publish_sensor_state_(sensor::Sensor *sensor, float value, bool change_only) { | ||||||
|  |   if (!sensor || (change_only && sensor->has_state() && sensor->state == value)) { | ||||||
|  |     return; | ||||||
|  |   } | ||||||
|  |   sensor->publish_state(value); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void BME680BSECComponent::publish_sensor_state_(text_sensor::TextSensor *sensor, std::string value) { | ||||||
|  |   if (!sensor || (sensor->has_state() && sensor->state == value)) { | ||||||
|  |     return; | ||||||
|  |   } | ||||||
|  |   sensor->publish_state(value); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | int8_t BME680BSECComponent::read_bytes_wrapper(uint8_t address, uint8_t a_register, uint8_t *data, uint16_t len) { | ||||||
|  |   return BME680BSECComponent::instance->read_bytes(a_register, data, len) ? 0 : -1; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | int8_t BME680BSECComponent::write_bytes_wrapper(uint8_t address, uint8_t a_register, uint8_t *data, uint16_t len) { | ||||||
|  |   return BME680BSECComponent::instance->write_bytes(a_register, data, len) ? 0 : -1; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void BME680BSECComponent::delay_ms(uint32_t period) { | ||||||
|  |   ESP_LOGV(TAG, "Delaying for %ums", period); | ||||||
|  |   delay(period); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void BME680BSECComponent::load_state_() { | ||||||
|  |   uint32_t hash = fnv1_hash("bme680_bsec_state_" + to_string(this->address_)); | ||||||
|  |   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(state, BSEC_MAX_STATE_BLOB_SIZE, work_buffer, sizeof(work_buffer)); | ||||||
|  |     if (this->bsec_status_ != BSEC_OK) { | ||||||
|  |       ESP_LOGW(TAG, "Failed to load state (BSEC Error Code %d)", this->bsec_status_); | ||||||
|  |     } | ||||||
|  |     ESP_LOGI(TAG, "Loaded state"); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void BME680BSECComponent::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(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 (BSEC 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 bme680_bsec | ||||||
|  | }  // namespace esphome | ||||||
|  |  | ||||||
|  | #endif | ||||||
							
								
								
									
										106
									
								
								esphome/components/bme680_bsec/bme680_bsec.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										106
									
								
								esphome/components/bme680_bsec/bme680_bsec.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,106 @@ | |||||||
|  | #ifdef USING_BSEC | ||||||
|  |  | ||||||
|  | #pragma once | ||||||
|  |  | ||||||
|  | #include "esphome/core/component.h" | ||||||
|  | #include "esphome/components/sensor/sensor.h" | ||||||
|  | #include "esphome/components/text_sensor/text_sensor.h" | ||||||
|  | #include "esphome/components/i2c/i2c.h" | ||||||
|  | #include "esphome/core/preferences.h" | ||||||
|  | #include <bsec.h> | ||||||
|  | #include <map> | ||||||
|  |  | ||||||
|  | namespace esphome { | ||||||
|  | namespace bme680_bsec { | ||||||
|  |  | ||||||
|  | enum IAQMode { | ||||||
|  |   IAQ_MODE_STATIC = 0, | ||||||
|  |   IAQ_MODE_MOBILE = 1, | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | enum SampleRate { | ||||||
|  |   SAMPLE_RATE_LP = 0, | ||||||
|  |   SAMPLE_RATE_ULP = 1, | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | class BME680BSECComponent : public Component, public i2c::I2CDevice { | ||||||
|  |  public: | ||||||
|  |   void set_temperature_offset(float offset) { this->temperature_offset_ = offset; } | ||||||
|  |   void set_iaq_mode(IAQMode iaq_mode) { this->iaq_mode_ = iaq_mode; } | ||||||
|  |   void set_sample_rate(SampleRate sample_rate) { this->sample_rate_ = sample_rate; } | ||||||
|  |   void set_state_save_interval(uint32_t interval) { this->state_save_interval_ms_ = interval; } | ||||||
|  |  | ||||||
|  |   void set_temperature_sensor(sensor::Sensor *temperature_sensor) { temperature_sensor_ = temperature_sensor; } | ||||||
|  |   void set_pressure_sensor(sensor::Sensor *pressure_sensor) { pressure_sensor_ = pressure_sensor; } | ||||||
|  |   void set_humidity_sensor(sensor::Sensor *humidity_sensor) { humidity_sensor_ = humidity_sensor; } | ||||||
|  |   void set_gas_resistance_sensor(sensor::Sensor *gas_resistance_sensor) { | ||||||
|  |     gas_resistance_sensor_ = gas_resistance_sensor; | ||||||
|  |   } | ||||||
|  |   void set_iaq_sensor(sensor::Sensor *iaq_sensor) { iaq_sensor_ = iaq_sensor; } | ||||||
|  |   void set_iaq_accuracy_text_sensor(text_sensor::TextSensor *iaq_accuracy_text_sensor) { | ||||||
|  |     iaq_accuracy_text_sensor_ = iaq_accuracy_text_sensor; | ||||||
|  |   } | ||||||
|  |   void set_iaq_accuracy_sensor(sensor::Sensor *iaq_accuracy_sensor) { iaq_accuracy_sensor_ = iaq_accuracy_sensor; } | ||||||
|  |   void set_co2_equivalent_sensor(sensor::Sensor *co2_equivalent_sensor) { | ||||||
|  |     co2_equivalent_sensor_ = co2_equivalent_sensor; | ||||||
|  |   } | ||||||
|  |   void set_breath_voc_equivalent_sensor(sensor::Sensor *breath_voc_equivalent_sensor) { | ||||||
|  |     breath_voc_equivalent_sensor_ = breath_voc_equivalent_sensor; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   static BME680BSECComponent *instance; | ||||||
|  |   static int8_t read_bytes_wrapper(uint8_t address, uint8_t a_register, uint8_t *data, uint16_t len); | ||||||
|  |   static int8_t write_bytes_wrapper(uint8_t address, uint8_t a_register, uint8_t *data, uint16_t len); | ||||||
|  |   static void delay_ms(uint32_t period); | ||||||
|  |  | ||||||
|  |   void setup() override; | ||||||
|  |   void dump_config() override; | ||||||
|  |   float get_setup_priority() const override; | ||||||
|  |   void loop() override; | ||||||
|  |  | ||||||
|  |  protected: | ||||||
|  |   void set_config_(const uint8_t *config); | ||||||
|  |   void update_subscription_(float sample_rate); | ||||||
|  |  | ||||||
|  |   void run_(); | ||||||
|  |   void read_(bsec_bme_settings_t bme680_settings); | ||||||
|  |   void publish_(const bsec_output_t *outputs, uint8_t num_outputs); | ||||||
|  |   int64_t get_time_ns_(); | ||||||
|  |  | ||||||
|  |   void publish_sensor_state_(sensor::Sensor *sensor, float value, bool change_only = false); | ||||||
|  |   void publish_sensor_state_(text_sensor::TextSensor *sensor, std::string value); | ||||||
|  |  | ||||||
|  |   void load_state_(); | ||||||
|  |   void save_state_(uint8_t accuracy); | ||||||
|  |  | ||||||
|  |   struct bme680_dev bme680_; | ||||||
|  |   bsec_library_return_t bsec_status_{BSEC_OK}; | ||||||
|  |   int8_t bme680_status_{BME680_OK}; | ||||||
|  |  | ||||||
|  |   int64_t last_time_ms_{0}; | ||||||
|  |   uint32_t millis_overflow_counter_{0}; | ||||||
|  |   int64_t next_call_ns_{0}; | ||||||
|  |  | ||||||
|  |   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}; | ||||||
|  |   IAQMode iaq_mode_{IAQ_MODE_STATIC}; | ||||||
|  |   SampleRate sample_rate_{SAMPLE_RATE_LP}; | ||||||
|  |  | ||||||
|  |   sensor::Sensor *temperature_sensor_; | ||||||
|  |   sensor::Sensor *pressure_sensor_; | ||||||
|  |   sensor::Sensor *humidity_sensor_; | ||||||
|  |   sensor::Sensor *gas_resistance_sensor_; | ||||||
|  |   sensor::Sensor *iaq_sensor_; | ||||||
|  |   text_sensor::TextSensor *iaq_accuracy_text_sensor_; | ||||||
|  |   sensor::Sensor *iaq_accuracy_sensor_; | ||||||
|  |   sensor::Sensor *co2_equivalent_sensor_; | ||||||
|  |   sensor::Sensor *breath_voc_equivalent_sensor_; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | }  // namespace bme680_bsec | ||||||
|  | }  // namespace esphome | ||||||
|  |  | ||||||
|  | #endif | ||||||
							
								
								
									
										91
									
								
								esphome/components/bme680_bsec/sensor.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										91
									
								
								esphome/components/bme680_bsec/sensor.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,91 @@ | |||||||
|  | import esphome.codegen as cg | ||||||
|  | import esphome.config_validation as cv | ||||||
|  | from esphome.components import sensor | ||||||
|  | from esphome.const import ( | ||||||
|  |     CONF_GAS_RESISTANCE, | ||||||
|  |     CONF_HUMIDITY, | ||||||
|  |     CONF_PRESSURE, | ||||||
|  |     CONF_TEMPERATURE, | ||||||
|  |     DEVICE_CLASS_EMPTY, | ||||||
|  |     DEVICE_CLASS_HUMIDITY, | ||||||
|  |     DEVICE_CLASS_PRESSURE, | ||||||
|  |     DEVICE_CLASS_TEMPERATURE, | ||||||
|  |     UNIT_CELSIUS, | ||||||
|  |     UNIT_EMPTY, | ||||||
|  |     UNIT_HECTOPASCAL, | ||||||
|  |     UNIT_OHM, | ||||||
|  |     UNIT_PARTS_PER_MILLION, | ||||||
|  |     UNIT_PERCENT, | ||||||
|  |     ICON_GAS_CYLINDER, | ||||||
|  |     ICON_GAUGE, | ||||||
|  |     ICON_THERMOMETER, | ||||||
|  |     ICON_WATER_PERCENT, | ||||||
|  | ) | ||||||
|  | from esphome.core import coroutine | ||||||
|  | from . import BME680BSECComponent, CONF_BME680_BSEC_ID | ||||||
|  |  | ||||||
|  | DEPENDENCIES = ["bme680_bsec"] | ||||||
|  |  | ||||||
|  | CONF_IAQ = "iaq" | ||||||
|  | CONF_IAQ_ACCURACY = "iaq_accuracy" | ||||||
|  | CONF_CO2_EQUIVALENT = "co2_equivalent" | ||||||
|  | CONF_BREATH_VOC_EQUIVALENT = "breath_voc_equivalent" | ||||||
|  | UNIT_IAQ = "IAQ" | ||||||
|  | ICON_ACCURACY = "mdi:checkbox-marked-circle-outline" | ||||||
|  | ICON_TEST_TUBE = "mdi:test-tube" | ||||||
|  |  | ||||||
|  | TYPES = { | ||||||
|  |     CONF_TEMPERATURE: "set_temperature_sensor", | ||||||
|  |     CONF_PRESSURE: "set_pressure_sensor", | ||||||
|  |     CONF_HUMIDITY: "set_humidity_sensor", | ||||||
|  |     CONF_GAS_RESISTANCE: "set_gas_resistance_sensor", | ||||||
|  |     CONF_IAQ: "set_iaq_sensor", | ||||||
|  |     CONF_IAQ_ACCURACY: "set_iaq_accuracy_sensor", | ||||||
|  |     CONF_CO2_EQUIVALENT: "set_co2_equivalent_sensor", | ||||||
|  |     CONF_BREATH_VOC_EQUIVALENT: "set_breath_voc_equivalent_sensor", | ||||||
|  | } | ||||||
|  |  | ||||||
|  | CONFIG_SCHEMA = cv.Schema( | ||||||
|  |     { | ||||||
|  |         cv.GenerateID(CONF_BME680_BSEC_ID): cv.use_id(BME680BSECComponent), | ||||||
|  |         cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema( | ||||||
|  |             UNIT_CELSIUS, ICON_THERMOMETER, 1, DEVICE_CLASS_TEMPERATURE | ||||||
|  |         ), | ||||||
|  |         cv.Optional(CONF_PRESSURE): sensor.sensor_schema( | ||||||
|  |             UNIT_HECTOPASCAL, ICON_GAUGE, 1, DEVICE_CLASS_PRESSURE | ||||||
|  |         ), | ||||||
|  |         cv.Optional(CONF_HUMIDITY): sensor.sensor_schema( | ||||||
|  |             UNIT_PERCENT, ICON_WATER_PERCENT, 1, DEVICE_CLASS_HUMIDITY | ||||||
|  |         ), | ||||||
|  |         cv.Optional(CONF_GAS_RESISTANCE): sensor.sensor_schema( | ||||||
|  |             UNIT_OHM, ICON_GAS_CYLINDER, 0, DEVICE_CLASS_EMPTY | ||||||
|  |         ), | ||||||
|  |         cv.Optional(CONF_IAQ): sensor.sensor_schema( | ||||||
|  |             UNIT_IAQ, ICON_GAUGE, 0, DEVICE_CLASS_EMPTY | ||||||
|  |         ), | ||||||
|  |         cv.Optional(CONF_IAQ_ACCURACY): sensor.sensor_schema( | ||||||
|  |             UNIT_EMPTY, ICON_ACCURACY, 0, DEVICE_CLASS_EMPTY | ||||||
|  |         ), | ||||||
|  |         cv.Optional(CONF_CO2_EQUIVALENT): sensor.sensor_schema( | ||||||
|  |             UNIT_PARTS_PER_MILLION, ICON_TEST_TUBE, 1, DEVICE_CLASS_EMPTY | ||||||
|  |         ), | ||||||
|  |         cv.Optional(CONF_BREATH_VOC_EQUIVALENT): sensor.sensor_schema( | ||||||
|  |             UNIT_PARTS_PER_MILLION, ICON_TEST_TUBE, 1, DEVICE_CLASS_EMPTY | ||||||
|  |         ), | ||||||
|  |     } | ||||||
|  | ) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | @coroutine | ||||||
|  | def setup_conf(config, key, hub, funcName): | ||||||
|  |     if key in config: | ||||||
|  |         conf = config[key] | ||||||
|  |         var = yield sensor.new_sensor(conf) | ||||||
|  |         func = getattr(hub, funcName) | ||||||
|  |         cg.add(func(var)) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def to_code(config): | ||||||
|  |     hub = yield cg.get_variable(config[CONF_BME680_BSEC_ID]) | ||||||
|  |     for key, funcName in TYPES.items(): | ||||||
|  |         yield setup_conf(config, key, hub, funcName) | ||||||
							
								
								
									
										41
									
								
								esphome/components/bme680_bsec/text_sensor.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										41
									
								
								esphome/components/bme680_bsec/text_sensor.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,41 @@ | |||||||
|  | import esphome.codegen as cg | ||||||
|  | import esphome.config_validation as cv | ||||||
|  | from esphome.components import text_sensor | ||||||
|  | from esphome.const import CONF_ID, CONF_ICON | ||||||
|  | from esphome.core import coroutine | ||||||
|  | from . import BME680BSECComponent, CONF_BME680_BSEC_ID | ||||||
|  |  | ||||||
|  | DEPENDENCIES = ["bme680_bsec"] | ||||||
|  |  | ||||||
|  | CONF_IAQ_ACCURACY = "iaq_accuracy" | ||||||
|  | ICON_ACCURACY = "mdi:checkbox-marked-circle-outline" | ||||||
|  |  | ||||||
|  | TYPES = {CONF_IAQ_ACCURACY: "set_iaq_accuracy_text_sensor"} | ||||||
|  |  | ||||||
|  | CONFIG_SCHEMA = cv.Schema( | ||||||
|  |     { | ||||||
|  |         cv.GenerateID(CONF_BME680_BSEC_ID): cv.use_id(BME680BSECComponent), | ||||||
|  |         cv.Optional(CONF_IAQ_ACCURACY): text_sensor.TEXT_SENSOR_SCHEMA.extend( | ||||||
|  |             { | ||||||
|  |                 cv.GenerateID(): cv.declare_id(text_sensor.TextSensor), | ||||||
|  |                 cv.Optional(CONF_ICON, default=ICON_ACCURACY): cv.icon, | ||||||
|  |             } | ||||||
|  |         ), | ||||||
|  |     } | ||||||
|  | ) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | @coroutine | ||||||
|  | def setup_conf(config, key, hub, funcName): | ||||||
|  |     if key in config: | ||||||
|  |         conf = config[key] | ||||||
|  |         var = cg.new_Pvariable(conf[CONF_ID]) | ||||||
|  |         yield text_sensor.register_text_sensor(var, conf) | ||||||
|  |         func = getattr(hub, funcName) | ||||||
|  |         cg.add(func(var)) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def to_code(config): | ||||||
|  |     hub = yield cg.get_variable(config[CONF_BME680_BSEC_ID]) | ||||||
|  |     for key, funcName in TYPES.items(): | ||||||
|  |         yield setup_conf(config, key, hub, funcName) | ||||||
		Reference in New Issue
	
	Block a user