mirror of
				https://github.com/esphome/esphome.git
				synced 2025-10-31 07:03:55 +00:00 
			
		
		
		
	Background calibration & ABC commands for SenseAir S8 (#1623)
This commit is contained in:
		| @@ -6,12 +6,20 @@ namespace senseair { | |||||||
|  |  | ||||||
| static const char *TAG = "senseair"; | static const char *TAG = "senseair"; | ||||||
| static const uint8_t SENSEAIR_REQUEST_LENGTH = 8; | static const uint8_t SENSEAIR_REQUEST_LENGTH = 8; | ||||||
| static const uint8_t SENSEAIR_RESPONSE_LENGTH = 13; | static const uint8_t SENSEAIR_PPM_STATUS_RESPONSE_LENGTH = 13; | ||||||
| static const uint8_t SENSEAIR_COMMAND_GET_PPM[] = {0xFE, 0x04, 0x00, 0x00, 0x00, 0x04, 0xE5, 0xC6}; | static const uint8_t SENSEAIR_ABC_PERIOD_RESPONSE_LENGTH = 7; | ||||||
|  | static const uint8_t SENSEAIR_CAL_RESULT_RESPONSE_LENGTH = 7; | ||||||
|  | static const uint8_t SENSEAIR_COMMAND_GET_PPM_STATUS[] = {0xFE, 0x04, 0x00, 0x00, 0x00, 0x04, 0xE5, 0xC6}; | ||||||
|  | static const uint8_t SENSEAIR_COMMAND_CLEAR_ACK_REGISTER[] = {0xFE, 0x06, 0x00, 0x00, 0x00, 0x00, 0x9D, 0xC5}; | ||||||
|  | static const uint8_t SENSEAIR_COMMAND_BACKGROUND_CAL[] = {0xFE, 0x06, 0x00, 0x01, 0x7C, 0x06, 0x6C, 0xC7}; | ||||||
|  | static const uint8_t SENSEAIR_COMMAND_BACKGROUND_CAL_RESULT[] = {0xFE, 0x03, 0x00, 0x00, 0x00, 0x01, 0x90, 0x05}; | ||||||
|  | static const uint8_t SENSEAIR_COMMAND_ABC_ENABLE[] = {0xFE, 0x06, 0x00, 0x1F, 0x00, 0xB4, 0xAC, 0x74};  // 180 hours | ||||||
|  | static const uint8_t SENSEAIR_COMMAND_ABC_DISABLE[] = {0xFE, 0x06, 0x00, 0x1F, 0x00, 0x00, 0xAC, 0x03}; | ||||||
|  | static const uint8_t SENSEAIR_COMMAND_ABC_GET_PERIOD[] = {0xFE, 0x03, 0x00, 0x1F, 0x00, 0x01, 0xA1, 0xC3}; | ||||||
|  |  | ||||||
| void SenseAirComponent::update() { | void SenseAirComponent::update() { | ||||||
|   uint8_t response[SENSEAIR_RESPONSE_LENGTH]; |   uint8_t response[SENSEAIR_PPM_STATUS_RESPONSE_LENGTH]; | ||||||
|   if (!this->senseair_write_command_(SENSEAIR_COMMAND_GET_PPM, response)) { |   if (!this->senseair_write_command_(SENSEAIR_COMMAND_GET_PPM_STATUS, response, SENSEAIR_PPM_STATUS_RESPONSE_LENGTH)) { | ||||||
|     ESP_LOGW(TAG, "Reading data from SenseAir failed!"); |     ESP_LOGW(TAG, "Reading data from SenseAir failed!"); | ||||||
|     this->status_set_warning(); |     this->status_set_warning(); | ||||||
|     return; |     return; | ||||||
| @@ -69,14 +77,67 @@ uint16_t SenseAirComponent::senseair_checksum_(uint8_t *ptr, uint8_t length) { | |||||||
|   return crc; |   return crc; | ||||||
| } | } | ||||||
|  |  | ||||||
| bool SenseAirComponent::senseair_write_command_(const uint8_t *command, uint8_t *response) { | void SenseAirComponent::background_calibration() { | ||||||
|  |   ESP_LOGD(TAG, "SenseAir Starting background calibration"); | ||||||
|  |   this->senseair_write_command_(SENSEAIR_COMMAND_CLEAR_ACK_REGISTER, nullptr, 0); | ||||||
|  |   this->senseair_write_command_(SENSEAIR_COMMAND_BACKGROUND_CAL, nullptr, 0); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void SenseAirComponent::background_calibration_result() { | ||||||
|  |   ESP_LOGD(TAG, "SenseAir Requesting background calibration result"); | ||||||
|  |   uint8_t response[SENSEAIR_CAL_RESULT_RESPONSE_LENGTH]; | ||||||
|  |   if (!this->senseair_write_command_(SENSEAIR_COMMAND_BACKGROUND_CAL_RESULT, response, | ||||||
|  |                                      SENSEAIR_CAL_RESULT_RESPONSE_LENGTH)) { | ||||||
|  |     ESP_LOGE(TAG, "Requesting background calibration result from SenseAir failed!"); | ||||||
|  |     return; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   if (response[0] != 0xFE || response[1] != 0x03) { | ||||||
|  |     ESP_LOGE(TAG, "Invalid reply from SenseAir! %02x%02x%02x %02x%02x %02x%02x", response[0], response[1], response[2], | ||||||
|  |              response[3], response[4], response[5], response[6]); | ||||||
|  |     return; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   ESP_LOGD(TAG, "SenseAir Result=%s (%02x%02x%02x)", response[2] == 2 ? "OK" : "NOT_OK", response[2], response[3], | ||||||
|  |            response[4]); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void SenseAirComponent::abc_enable() { | ||||||
|  |   ESP_LOGD(TAG, "SenseAir Enabling automatic baseline calibration"); | ||||||
|  |   this->senseair_write_command_(SENSEAIR_COMMAND_ABC_ENABLE, nullptr, 0); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void SenseAirComponent::abc_disable() { | ||||||
|  |   ESP_LOGD(TAG, "SenseAir Disabling automatic baseline calibration"); | ||||||
|  |   this->senseair_write_command_(SENSEAIR_COMMAND_ABC_DISABLE, nullptr, 0); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void SenseAirComponent::abc_get_period() { | ||||||
|  |   ESP_LOGD(TAG, "SenseAir Requesting ABC period"); | ||||||
|  |   uint8_t response[SENSEAIR_ABC_PERIOD_RESPONSE_LENGTH]; | ||||||
|  |   if (!this->senseair_write_command_(SENSEAIR_COMMAND_ABC_GET_PERIOD, response, SENSEAIR_ABC_PERIOD_RESPONSE_LENGTH)) { | ||||||
|  |     ESP_LOGE(TAG, "Requesting ABC period from SenseAir failed!"); | ||||||
|  |     return; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   if (response[0] != 0xFE || response[1] != 0x03) { | ||||||
|  |     ESP_LOGE(TAG, "Invalid reply from SenseAir! %02x%02x%02x %02x%02x %02x%02x", response[0], response[1], response[2], | ||||||
|  |              response[3], response[4], response[5], response[6]); | ||||||
|  |     return; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   const uint16_t hours = (uint16_t(response[3]) << 8) | response[4]; | ||||||
|  |   ESP_LOGD(TAG, "SenseAir Read ABC Period: %u hours", hours); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | bool SenseAirComponent::senseair_write_command_(const uint8_t *command, uint8_t *response, uint8_t response_length) { | ||||||
|   this->flush(); |   this->flush(); | ||||||
|   this->write_array(command, SENSEAIR_REQUEST_LENGTH); |   this->write_array(command, SENSEAIR_REQUEST_LENGTH); | ||||||
|  |  | ||||||
|   if (response == nullptr) |   if (response == nullptr) | ||||||
|     return true; |     return true; | ||||||
|  |  | ||||||
|   bool ret = this->read_array(response, SENSEAIR_RESPONSE_LENGTH); |   bool ret = this->read_array(response, response_length); | ||||||
|   this->flush(); |   this->flush(); | ||||||
|   return ret; |   return ret; | ||||||
| } | } | ||||||
|   | |||||||
| @@ -1,6 +1,7 @@ | |||||||
| #pragma once | #pragma once | ||||||
|  |  | ||||||
| #include "esphome/core/component.h" | #include "esphome/core/component.h" | ||||||
|  | #include "esphome/core/automation.h" | ||||||
| #include "esphome/components/sensor/sensor.h" | #include "esphome/components/sensor/sensor.h" | ||||||
| #include "esphome/components/uart/uart.h" | #include "esphome/components/uart/uart.h" | ||||||
|  |  | ||||||
| @@ -15,12 +16,68 @@ class SenseAirComponent : public PollingComponent, public uart::UARTDevice { | |||||||
|   void update() override; |   void update() override; | ||||||
|   void dump_config() override; |   void dump_config() override; | ||||||
|  |  | ||||||
|  |   void background_calibration(); | ||||||
|  |   void background_calibration_result(); | ||||||
|  |   void abc_get_period(); | ||||||
|  |   void abc_enable(); | ||||||
|  |   void abc_disable(); | ||||||
|  |  | ||||||
|  protected: |  protected: | ||||||
|   uint16_t senseair_checksum_(uint8_t *ptr, uint8_t length); |   uint16_t senseair_checksum_(uint8_t *ptr, uint8_t length); | ||||||
|   bool senseair_write_command_(const uint8_t *command, uint8_t *response); |   bool senseair_write_command_(const uint8_t *command, uint8_t *response, uint8_t response_length); | ||||||
|  |  | ||||||
|   sensor::Sensor *co2_sensor_{nullptr}; |   sensor::Sensor *co2_sensor_{nullptr}; | ||||||
| }; | }; | ||||||
|  |  | ||||||
|  | template<typename... Ts> class SenseAirBackgroundCalibrationAction : public Action<Ts...> { | ||||||
|  |  public: | ||||||
|  |   SenseAirBackgroundCalibrationAction(SenseAirComponent *senseair) : senseair_(senseair) {} | ||||||
|  |  | ||||||
|  |   void play(Ts... x) override { this->senseair_->background_calibration(); } | ||||||
|  |  | ||||||
|  |  protected: | ||||||
|  |   SenseAirComponent *senseair_; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | template<typename... Ts> class SenseAirBackgroundCalibrationResultAction : public Action<Ts...> { | ||||||
|  |  public: | ||||||
|  |   SenseAirBackgroundCalibrationResultAction(SenseAirComponent *senseair) : senseair_(senseair) {} | ||||||
|  |  | ||||||
|  |   void play(Ts... x) override { this->senseair_->background_calibration_result(); } | ||||||
|  |  | ||||||
|  |  protected: | ||||||
|  |   SenseAirComponent *senseair_; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | template<typename... Ts> class SenseAirABCEnableAction : public Action<Ts...> { | ||||||
|  |  public: | ||||||
|  |   SenseAirABCEnableAction(SenseAirComponent *senseair) : senseair_(senseair) {} | ||||||
|  |  | ||||||
|  |   void play(Ts... x) override { this->senseair_->abc_enable(); } | ||||||
|  |  | ||||||
|  |  protected: | ||||||
|  |   SenseAirComponent *senseair_; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | template<typename... Ts> class SenseAirABCDisableAction : public Action<Ts...> { | ||||||
|  |  public: | ||||||
|  |   SenseAirABCDisableAction(SenseAirComponent *senseair) : senseair_(senseair) {} | ||||||
|  |  | ||||||
|  |   void play(Ts... x) override { this->senseair_->abc_disable(); } | ||||||
|  |  | ||||||
|  |  protected: | ||||||
|  |   SenseAirComponent *senseair_; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | template<typename... Ts> class SenseAirABCGetPeriodAction : public Action<Ts...> { | ||||||
|  |  public: | ||||||
|  |   SenseAirABCGetPeriodAction(SenseAirComponent *senseair) : senseair_(senseair) {} | ||||||
|  |  | ||||||
|  |   void play(Ts... x) override { this->senseair_->abc_get_period(); } | ||||||
|  |  | ||||||
|  |  protected: | ||||||
|  |   SenseAirComponent *senseair_; | ||||||
|  | }; | ||||||
|  |  | ||||||
| }  // namespace senseair | }  // namespace senseair | ||||||
| }  // namespace esphome | }  // namespace esphome | ||||||
|   | |||||||
| @@ -1,5 +1,7 @@ | |||||||
| import esphome.codegen as cg | import esphome.codegen as cg | ||||||
| import esphome.config_validation as cv | import esphome.config_validation as cv | ||||||
|  | from esphome import automation | ||||||
|  | from esphome.automation import maybe_simple_id | ||||||
| from esphome.components import sensor, uart | from esphome.components import sensor, uart | ||||||
| from esphome.const import ( | from esphome.const import ( | ||||||
|     CONF_CO2, |     CONF_CO2, | ||||||
| @@ -15,6 +17,21 @@ senseair_ns = cg.esphome_ns.namespace("senseair") | |||||||
| SenseAirComponent = senseair_ns.class_( | SenseAirComponent = senseair_ns.class_( | ||||||
|     "SenseAirComponent", cg.PollingComponent, uart.UARTDevice |     "SenseAirComponent", cg.PollingComponent, uart.UARTDevice | ||||||
| ) | ) | ||||||
|  | SenseAirBackgroundCalibrationAction = senseair_ns.class_( | ||||||
|  |     "SenseAirBackgroundCalibrationAction", automation.Action | ||||||
|  | ) | ||||||
|  | SenseAirBackgroundCalibrationResultAction = senseair_ns.class_( | ||||||
|  |     "SenseAirBackgroundCalibrationResultAction", automation.Action | ||||||
|  | ) | ||||||
|  | SenseAirABCEnableAction = senseair_ns.class_( | ||||||
|  |     "SenseAirABCEnableAction", automation.Action | ||||||
|  | ) | ||||||
|  | SenseAirABCDisableAction = senseair_ns.class_( | ||||||
|  |     "SenseAirABCDisableAction", automation.Action | ||||||
|  | ) | ||||||
|  | SenseAirABCGetPeriodAction = senseair_ns.class_( | ||||||
|  |     "SenseAirABCGetPeriodAction", automation.Action | ||||||
|  | ) | ||||||
|  |  | ||||||
| CONFIG_SCHEMA = ( | CONFIG_SCHEMA = ( | ||||||
|     cv.Schema( |     cv.Schema( | ||||||
| @@ -38,3 +55,34 @@ def to_code(config): | |||||||
|     if CONF_CO2 in config: |     if CONF_CO2 in config: | ||||||
|         sens = yield sensor.new_sensor(config[CONF_CO2]) |         sens = yield sensor.new_sensor(config[CONF_CO2]) | ||||||
|         cg.add(var.set_co2_sensor(sens)) |         cg.add(var.set_co2_sensor(sens)) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | CALIBRATION_ACTION_SCHEMA = maybe_simple_id( | ||||||
|  |     { | ||||||
|  |         cv.Required(CONF_ID): cv.use_id(SenseAirComponent), | ||||||
|  |     } | ||||||
|  | ) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | @automation.register_action( | ||||||
|  |     "senseair.background_calibration", | ||||||
|  |     SenseAirBackgroundCalibrationAction, | ||||||
|  |     CALIBRATION_ACTION_SCHEMA, | ||||||
|  | ) | ||||||
|  | @automation.register_action( | ||||||
|  |     "senseair.background_calibration_result", | ||||||
|  |     SenseAirBackgroundCalibrationResultAction, | ||||||
|  |     CALIBRATION_ACTION_SCHEMA, | ||||||
|  | ) | ||||||
|  | @automation.register_action( | ||||||
|  |     "senseair.abc_enable", SenseAirABCEnableAction, CALIBRATION_ACTION_SCHEMA | ||||||
|  | ) | ||||||
|  | @automation.register_action( | ||||||
|  |     "senseair.abc_disable", SenseAirABCDisableAction, CALIBRATION_ACTION_SCHEMA | ||||||
|  | ) | ||||||
|  | @automation.register_action( | ||||||
|  |     "senseair.abc_get_period", SenseAirABCGetPeriodAction, CALIBRATION_ACTION_SCHEMA | ||||||
|  | ) | ||||||
|  | def senseair_action_to_code(config, action_id, template_arg, args): | ||||||
|  |     paren = yield cg.get_variable(config[CONF_ID]) | ||||||
|  |     yield cg.new_Pvariable(action_id, template_arg, paren) | ||||||
|   | |||||||
| @@ -660,11 +660,6 @@ sensor: | |||||||
|   - platform: pulse_width |   - platform: pulse_width | ||||||
|     name: Pulse Width |     name: Pulse Width | ||||||
|     pin: GPIO12 |     pin: GPIO12 | ||||||
|   - platform: senseair |  | ||||||
|     uart_id: uart0 |  | ||||||
|     co2: |  | ||||||
|       name: 'SenseAir CO2 Value' |  | ||||||
|     update_interval: 15s |  | ||||||
|   - platform: sm300d2 |   - platform: sm300d2 | ||||||
|     uart_id: uart0 |     uart_id: uart0 | ||||||
|     co2: |     co2: | ||||||
|   | |||||||
| @@ -70,6 +70,18 @@ sensor: | |||||||
|   - platform: ble_rssi |   - platform: ble_rssi | ||||||
|     service_uuid: '11223344-5566-7788-99aa-bbccddeeff00' |     service_uuid: '11223344-5566-7788-99aa-bbccddeeff00' | ||||||
|     name: 'BLE Test Service 128' |     name: 'BLE Test Service 128' | ||||||
|  |   - platform: senseair | ||||||
|  |     id: senseair0 | ||||||
|  |     co2: | ||||||
|  |       name: 'SenseAir CO2 Value' | ||||||
|  |       on_value: | ||||||
|  |         then: | ||||||
|  |           - senseair.background_calibration: senseair0 | ||||||
|  |           - senseair.background_calibration_result: senseair0 | ||||||
|  |           - senseair.abc_get_period: senseair0 | ||||||
|  |           - senseair.abc_enable: senseair0 | ||||||
|  |           - senseair.abc_disable: senseair0 | ||||||
|  |     update_interval: 15s | ||||||
|   - platform: ruuvitag |   - platform: ruuvitag | ||||||
|     mac_address: FF:56:D3:2F:7D:E8 |     mac_address: FF:56:D3:2F:7D:E8 | ||||||
|     humidity: |     humidity: | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user