mirror of
				https://github.com/esphome/esphome.git
				synced 2025-10-31 07:03:55 +00:00 
			
		
		
		
	Add pressure compensation during runtime (#2493)
Co-authored-by: Oxan van Leeuwen <oxan@oxanvanleeuwen.nl>
This commit is contained in:
		| @@ -1,4 +1,5 @@ | |||||||
| #include "scd4x.h" | #include "scd4x.h" | ||||||
|  | #include "esphome/core/hal.h" | ||||||
| #include "esphome/core/log.h" | #include "esphome/core/log.h" | ||||||
|  |  | ||||||
| namespace esphome { | namespace esphome { | ||||||
| @@ -38,6 +39,7 @@ void SCD4XComponent::setup() { | |||||||
|       return; |       return; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     uint32_t stop_measurement_delay = 0; | ||||||
|     // In order to query the device periodic measurement must be ceased |     // In order to query the device periodic measurement must be ceased | ||||||
|     if (raw_read_status[0]) { |     if (raw_read_status[0]) { | ||||||
|       ESP_LOGD(TAG, "Sensor has data available, stopping periodic measurement"); |       ESP_LOGD(TAG, "Sensor has data available, stopping periodic measurement"); | ||||||
| @@ -46,68 +48,72 @@ void SCD4XComponent::setup() { | |||||||
|         this->mark_failed(); |         this->mark_failed(); | ||||||
|         return; |         return; | ||||||
|       } |       } | ||||||
|  |       // According to the SCD4x datasheet the sensor will only respond to other commands after waiting 500 ms after | ||||||
|  |       // issuing the stop_periodic_measurement command | ||||||
|  |       stop_measurement_delay = 500; | ||||||
|     } |     } | ||||||
|  |     this->set_timeout(stop_measurement_delay, [this]() { | ||||||
|  |       if (!this->write_command_(SCD4X_CMD_GET_SERIAL_NUMBER)) { | ||||||
|  |         ESP_LOGE(TAG, "Failed to write get serial command"); | ||||||
|  |         this->error_code_ = COMMUNICATION_FAILED; | ||||||
|  |         this->mark_failed(); | ||||||
|  |         return; | ||||||
|  |       } | ||||||
|  |  | ||||||
|     if (!this->write_command_(SCD4X_CMD_GET_SERIAL_NUMBER)) { |       uint16_t raw_serial_number[3]; | ||||||
|       ESP_LOGE(TAG, "Failed to write get serial command"); |       if (!this->read_data_(raw_serial_number, 3)) { | ||||||
|       this->error_code_ = COMMUNICATION_FAILED; |         ESP_LOGE(TAG, "Failed to read serial number"); | ||||||
|       this->mark_failed(); |         this->error_code_ = SERIAL_NUMBER_IDENTIFICATION_FAILED; | ||||||
|       return; |         this->mark_failed(); | ||||||
|     } |         return; | ||||||
|  |       } | ||||||
|  |       ESP_LOGD(TAG, "Serial number %02d.%02d.%02d", (uint16_t(raw_serial_number[0]) >> 8), | ||||||
|  |                uint16_t(raw_serial_number[0] & 0xFF), (uint16_t(raw_serial_number[1]) >> 8)); | ||||||
|  |  | ||||||
|     uint16_t raw_serial_number[3]; |       if (!this->write_command_(SCD4X_CMD_TEMPERATURE_OFFSET, | ||||||
|     if (!this->read_data_(raw_serial_number, 3)) { |                                 (uint16_t)(temperature_offset_ * SCD4X_TEMPERATURE_OFFSET_MULTIPLIER))) { | ||||||
|       ESP_LOGE(TAG, "Failed to read serial number"); |         ESP_LOGE(TAG, "Error setting temperature offset."); | ||||||
|       this->error_code_ = SERIAL_NUMBER_IDENTIFICATION_FAILED; |  | ||||||
|       this->mark_failed(); |  | ||||||
|       return; |  | ||||||
|     } |  | ||||||
|     ESP_LOGD(TAG, "Serial number %02d.%02d.%02d", (uint16_t(raw_serial_number[0]) >> 8), |  | ||||||
|              uint16_t(raw_serial_number[0] & 0xFF), (uint16_t(raw_serial_number[1]) >> 8)); |  | ||||||
|  |  | ||||||
|     if (!this->write_command_(SCD4X_CMD_TEMPERATURE_OFFSET, |  | ||||||
|                               (uint16_t)(temperature_offset_ * SCD4X_TEMPERATURE_OFFSET_MULTIPLIER))) { |  | ||||||
|       ESP_LOGE(TAG, "Error setting temperature offset."); |  | ||||||
|       this->error_code_ = MEASUREMENT_INIT_FAILED; |  | ||||||
|       this->mark_failed(); |  | ||||||
|       return; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     // If pressure compensation available use it |  | ||||||
|     // else use altitude |  | ||||||
|     if (ambient_pressure_compensation_) { |  | ||||||
|       if (!this->write_command_(SCD4X_CMD_AMBIENT_PRESSURE_COMPENSATION, ambient_pressure_compensation_)) { |  | ||||||
|         ESP_LOGE(TAG, "Error setting ambient pressure compensation."); |  | ||||||
|         this->error_code_ = MEASUREMENT_INIT_FAILED; |         this->error_code_ = MEASUREMENT_INIT_FAILED; | ||||||
|         this->mark_failed(); |         this->mark_failed(); | ||||||
|         return; |         return; | ||||||
|       } |       } | ||||||
|     } else { |  | ||||||
|       if (!this->write_command_(SCD4X_CMD_ALTITUDE_COMPENSATION, altitude_compensation_)) { |       // If pressure compensation available use it | ||||||
|         ESP_LOGE(TAG, "Error setting altitude compensation."); |       // else use altitude | ||||||
|  |       if (ambient_pressure_compensation_) { | ||||||
|  |         if (!this->update_ambient_pressure_compensation_(ambient_pressure_)) { | ||||||
|  |           ESP_LOGE(TAG, "Error setting ambient pressure compensation."); | ||||||
|  |           this->error_code_ = MEASUREMENT_INIT_FAILED; | ||||||
|  |           this->mark_failed(); | ||||||
|  |           return; | ||||||
|  |         } | ||||||
|  |       } else { | ||||||
|  |         if (!this->write_command_(SCD4X_CMD_ALTITUDE_COMPENSATION, altitude_compensation_)) { | ||||||
|  |           ESP_LOGE(TAG, "Error setting altitude compensation."); | ||||||
|  |           this->error_code_ = MEASUREMENT_INIT_FAILED; | ||||||
|  |           this->mark_failed(); | ||||||
|  |           return; | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |  | ||||||
|  |       if (!this->write_command_(SCD4X_CMD_AUTOMATIC_SELF_CALIBRATION, enable_asc_ ? 1 : 0)) { | ||||||
|  |         ESP_LOGE(TAG, "Error setting automatic self calibration."); | ||||||
|         this->error_code_ = MEASUREMENT_INIT_FAILED; |         this->error_code_ = MEASUREMENT_INIT_FAILED; | ||||||
|         this->mark_failed(); |         this->mark_failed(); | ||||||
|         return; |         return; | ||||||
|       } |       } | ||||||
|     } |  | ||||||
|  |  | ||||||
|     if (!this->write_command_(SCD4X_CMD_AUTOMATIC_SELF_CALIBRATION, enable_asc_ ? 1 : 0)) { |       // Finally start sensor measurements | ||||||
|       ESP_LOGE(TAG, "Error setting automatic self calibration."); |       if (!this->write_command_(SCD4X_CMD_START_CONTINUOUS_MEASUREMENTS)) { | ||||||
|       this->error_code_ = MEASUREMENT_INIT_FAILED; |         ESP_LOGE(TAG, "Error starting continuous measurements."); | ||||||
|       this->mark_failed(); |         this->error_code_ = MEASUREMENT_INIT_FAILED; | ||||||
|       return; |         this->mark_failed(); | ||||||
|     } |         return; | ||||||
|  |       } | ||||||
|  |  | ||||||
|     // Finally start sensor measurements |       initialized_ = true; | ||||||
|     if (!this->write_command_(SCD4X_CMD_START_CONTINUOUS_MEASUREMENTS)) { |       ESP_LOGD(TAG, "Sensor initialized"); | ||||||
|       ESP_LOGE(TAG, "Error starting continuous measurements."); |     }); | ||||||
|       this->error_code_ = MEASUREMENT_INIT_FAILED; |  | ||||||
|       this->mark_failed(); |  | ||||||
|       return; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     initialized_ = true; |  | ||||||
|     ESP_LOGD(TAG, "Sensor initialized"); |  | ||||||
|   }); |   }); | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -150,6 +156,13 @@ void SCD4XComponent::update() { | |||||||
|     return; |     return; | ||||||
|   } |   } | ||||||
|  |  | ||||||
|  |   if (this->ambient_pressure_source_ != nullptr) { | ||||||
|  |     float pressure = this->ambient_pressure_source_->state / 1000.0f; | ||||||
|  |     if (!std::isnan(pressure)) { | ||||||
|  |       set_ambient_pressure_compensation(this->ambient_pressure_source_->state / 1000.0f); | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |  | ||||||
|   // Check if data is ready |   // Check if data is ready | ||||||
|   if (!this->write_command_(SCD4X_CMD_GET_DATA_READY_STATUS)) { |   if (!this->write_command_(SCD4X_CMD_GET_DATA_READY_STATUS)) { | ||||||
|     this->status_set_warning(); |     this->status_set_warning(); | ||||||
| @@ -191,6 +204,28 @@ void SCD4XComponent::update() { | |||||||
|  |  | ||||||
|   this->status_clear_warning(); |   this->status_clear_warning(); | ||||||
| } | } | ||||||
|  | // Note pressure in bar here. Convert to hPa | ||||||
|  | void SCD4XComponent::set_ambient_pressure_compensation(float pressure_in_bar) { | ||||||
|  |   ambient_pressure_compensation_ = true; | ||||||
|  |   uint16_t new_ambient_pressure = (uint16_t)(pressure_in_bar * 1000); | ||||||
|  |   // remove millibar from comparison to avoid frequent updates +/- 10 millibar doesn't matter | ||||||
|  |   if (initialized_ && (new_ambient_pressure / 10 != ambient_pressure_ / 10)) { | ||||||
|  |     update_ambient_pressure_compensation_(new_ambient_pressure); | ||||||
|  |     ambient_pressure_ = new_ambient_pressure; | ||||||
|  |   } else { | ||||||
|  |     ESP_LOGD(TAG, "ambient pressure compensation skipped - no change required"); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | bool SCD4XComponent::update_ambient_pressure_compensation_(uint16_t pressure_in_hpa) { | ||||||
|  |   if (this->write_command_(SCD4X_CMD_AMBIENT_PRESSURE_COMPENSATION, pressure_in_hpa)) { | ||||||
|  |     ESP_LOGD(TAG, "setting ambient pressure compensation to %d hPa", pressure_in_hpa); | ||||||
|  |     return true; | ||||||
|  |   } else { | ||||||
|  |     ESP_LOGE(TAG, "Error setting ambient pressure compensation."); | ||||||
|  |     return false; | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
| uint8_t SCD4XComponent::sht_crc_(uint8_t data1, uint8_t data2) { | uint8_t SCD4XComponent::sht_crc_(uint8_t data1, uint8_t data2) { | ||||||
|   uint8_t bit; |   uint8_t bit; | ||||||
|   | |||||||
| @@ -18,10 +18,8 @@ class SCD4XComponent : public PollingComponent, public i2c::I2CDevice { | |||||||
|  |  | ||||||
|   void set_automatic_self_calibration(bool asc) { enable_asc_ = asc; } |   void set_automatic_self_calibration(bool asc) { enable_asc_ = asc; } | ||||||
|   void set_altitude_compensation(uint16_t altitude) { altitude_compensation_ = altitude; } |   void set_altitude_compensation(uint16_t altitude) { altitude_compensation_ = altitude; } | ||||||
|   void set_ambient_pressure_compensation(float pressure) { |   void set_ambient_pressure_compensation(float pressure_in_bar); | ||||||
|     ambient_pressure_compensation_ = true; |   void set_ambient_pressure_source(sensor::Sensor *pressure) { ambient_pressure_source_ = pressure; } | ||||||
|     ambient_pressure_ = (uint16_t)(pressure * 1000); |  | ||||||
|   } |  | ||||||
|   void set_temperature_offset(float offset) { temperature_offset_ = offset; }; |   void set_temperature_offset(float offset) { temperature_offset_ = offset; }; | ||||||
|  |  | ||||||
|   void set_co2_sensor(sensor::Sensor *co2) { co2_sensor_ = co2; } |   void set_co2_sensor(sensor::Sensor *co2) { co2_sensor_ = co2; } | ||||||
| @@ -33,6 +31,7 @@ class SCD4XComponent : public PollingComponent, public i2c::I2CDevice { | |||||||
|   bool read_data_(uint16_t *data, uint8_t len); |   bool read_data_(uint16_t *data, uint8_t len); | ||||||
|   bool write_command_(uint16_t command); |   bool write_command_(uint16_t command); | ||||||
|   bool write_command_(uint16_t command, uint16_t data); |   bool write_command_(uint16_t command, uint16_t data); | ||||||
|  |   bool update_ambient_pressure_compensation_(uint16_t pressure_in_hpa); | ||||||
|  |  | ||||||
|   ERRORCODE error_code_; |   ERRORCODE error_code_; | ||||||
|  |  | ||||||
| @@ -47,6 +46,8 @@ class SCD4XComponent : public PollingComponent, public i2c::I2CDevice { | |||||||
|   sensor::Sensor *co2_sensor_{nullptr}; |   sensor::Sensor *co2_sensor_{nullptr}; | ||||||
|   sensor::Sensor *temperature_sensor_{nullptr}; |   sensor::Sensor *temperature_sensor_{nullptr}; | ||||||
|   sensor::Sensor *humidity_sensor_{nullptr}; |   sensor::Sensor *humidity_sensor_{nullptr}; | ||||||
|  |   // used for compensation | ||||||
|  |   sensor::Sensor *ambient_pressure_source_{nullptr}; | ||||||
| }; | }; | ||||||
|  |  | ||||||
| }  // namespace scd4x | }  // namespace scd4x | ||||||
|   | |||||||
| @@ -29,6 +29,7 @@ CONF_AUTOMATIC_SELF_CALIBRATION = "automatic_self_calibration" | |||||||
| CONF_ALTITUDE_COMPENSATION = "altitude_compensation" | CONF_ALTITUDE_COMPENSATION = "altitude_compensation" | ||||||
| CONF_AMBIENT_PRESSURE_COMPENSATION = "ambient_pressure_compensation" | CONF_AMBIENT_PRESSURE_COMPENSATION = "ambient_pressure_compensation" | ||||||
| CONF_TEMPERATURE_OFFSET = "temperature_offset" | CONF_TEMPERATURE_OFFSET = "temperature_offset" | ||||||
|  | CONF_AMBIENT_PRESSURE_COMPENSATION_SOURCE = "ambient_pressure_compensation_source" | ||||||
|  |  | ||||||
| CONFIG_SCHEMA = ( | CONFIG_SCHEMA = ( | ||||||
|     cv.Schema( |     cv.Schema( | ||||||
| @@ -62,6 +63,9 @@ CONFIG_SCHEMA = ( | |||||||
|             ), |             ), | ||||||
|             cv.Optional(CONF_AMBIENT_PRESSURE_COMPENSATION): cv.pressure, |             cv.Optional(CONF_AMBIENT_PRESSURE_COMPENSATION): cv.pressure, | ||||||
|             cv.Optional(CONF_TEMPERATURE_OFFSET, default="4°C"): cv.temperature, |             cv.Optional(CONF_TEMPERATURE_OFFSET, default="4°C"): cv.temperature, | ||||||
|  |             cv.Optional(CONF_AMBIENT_PRESSURE_COMPENSATION_SOURCE): cv.use_id( | ||||||
|  |                 sensor.Sensor | ||||||
|  |             ), | ||||||
|         } |         } | ||||||
|     ) |     ) | ||||||
|     .extend(cv.polling_component_schema("60s")) |     .extend(cv.polling_component_schema("60s")) | ||||||
| @@ -92,7 +96,10 @@ async def to_code(config): | |||||||
|             cg.add(getattr(var, funcName)(config[key])) |             cg.add(getattr(var, funcName)(config[key])) | ||||||
|  |  | ||||||
|     for key, funcName in SENSOR_MAP.items(): |     for key, funcName in SENSOR_MAP.items(): | ||||||
|  |  | ||||||
|         if key in config: |         if key in config: | ||||||
|             sens = await sensor.new_sensor(config[key]) |             sens = await sensor.new_sensor(config[key]) | ||||||
|             cg.add(getattr(var, funcName)(sens)) |             cg.add(getattr(var, funcName)(sens)) | ||||||
|  |  | ||||||
|  |     if CONF_AMBIENT_PRESSURE_COMPENSATION_SOURCE in config: | ||||||
|  |         sens = await cg.get_variable(config[CONF_AMBIENT_PRESSURE_COMPENSATION_SOURCE]) | ||||||
|  |         cg.add(var.set_ambient_pressure_source(sens)) | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user