From 8f1eb77e05f963986414e0fc972f2dd5c0b66fbc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niccol=C3=B2=20Maggioni?= Date: Mon, 22 Mar 2021 00:59:41 +0100 Subject: [PATCH] Background calibration & ABC commands for SenseAir S8 (#1623) --- esphome/components/senseair/senseair.cpp | 73 ++++++++++++++++++++++-- esphome/components/senseair/senseair.h | 59 ++++++++++++++++++- esphome/components/senseair/sensor.py | 48 ++++++++++++++++ tests/test1.yaml | 5 -- tests/test2.yaml | 12 ++++ 5 files changed, 185 insertions(+), 12 deletions(-) diff --git a/esphome/components/senseair/senseair.cpp b/esphome/components/senseair/senseair.cpp index 812a4aa42d..80b67dfa17 100644 --- a/esphome/components/senseair/senseair.cpp +++ b/esphome/components/senseair/senseair.cpp @@ -6,12 +6,20 @@ namespace senseair { static const char *TAG = "senseair"; static const uint8_t SENSEAIR_REQUEST_LENGTH = 8; -static const uint8_t SENSEAIR_RESPONSE_LENGTH = 13; -static const uint8_t SENSEAIR_COMMAND_GET_PPM[] = {0xFE, 0x04, 0x00, 0x00, 0x00, 0x04, 0xE5, 0xC6}; +static const uint8_t SENSEAIR_PPM_STATUS_RESPONSE_LENGTH = 13; +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() { - uint8_t response[SENSEAIR_RESPONSE_LENGTH]; - if (!this->senseair_write_command_(SENSEAIR_COMMAND_GET_PPM, response)) { + uint8_t response[SENSEAIR_PPM_STATUS_RESPONSE_LENGTH]; + if (!this->senseair_write_command_(SENSEAIR_COMMAND_GET_PPM_STATUS, response, SENSEAIR_PPM_STATUS_RESPONSE_LENGTH)) { ESP_LOGW(TAG, "Reading data from SenseAir failed!"); this->status_set_warning(); return; @@ -69,14 +77,67 @@ uint16_t SenseAirComponent::senseair_checksum_(uint8_t *ptr, uint8_t length) { 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->write_array(command, SENSEAIR_REQUEST_LENGTH); if (response == nullptr) return true; - bool ret = this->read_array(response, SENSEAIR_RESPONSE_LENGTH); + bool ret = this->read_array(response, response_length); this->flush(); return ret; } diff --git a/esphome/components/senseair/senseair.h b/esphome/components/senseair/senseair.h index 23bcf40b5a..c03a0848e9 100644 --- a/esphome/components/senseair/senseair.h +++ b/esphome/components/senseair/senseair.h @@ -1,6 +1,7 @@ #pragma once #include "esphome/core/component.h" +#include "esphome/core/automation.h" #include "esphome/components/sensor/sensor.h" #include "esphome/components/uart/uart.h" @@ -15,12 +16,68 @@ class SenseAirComponent : public PollingComponent, public uart::UARTDevice { void update() override; void dump_config() override; + void background_calibration(); + void background_calibration_result(); + void abc_get_period(); + void abc_enable(); + void abc_disable(); + protected: 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}; }; +template class SenseAirBackgroundCalibrationAction : public Action { + public: + SenseAirBackgroundCalibrationAction(SenseAirComponent *senseair) : senseair_(senseair) {} + + void play(Ts... x) override { this->senseair_->background_calibration(); } + + protected: + SenseAirComponent *senseair_; +}; + +template class SenseAirBackgroundCalibrationResultAction : public Action { + public: + SenseAirBackgroundCalibrationResultAction(SenseAirComponent *senseair) : senseair_(senseair) {} + + void play(Ts... x) override { this->senseair_->background_calibration_result(); } + + protected: + SenseAirComponent *senseair_; +}; + +template class SenseAirABCEnableAction : public Action { + public: + SenseAirABCEnableAction(SenseAirComponent *senseair) : senseair_(senseair) {} + + void play(Ts... x) override { this->senseair_->abc_enable(); } + + protected: + SenseAirComponent *senseair_; +}; + +template class SenseAirABCDisableAction : public Action { + public: + SenseAirABCDisableAction(SenseAirComponent *senseair) : senseair_(senseair) {} + + void play(Ts... x) override { this->senseair_->abc_disable(); } + + protected: + SenseAirComponent *senseair_; +}; + +template class SenseAirABCGetPeriodAction : public Action { + public: + SenseAirABCGetPeriodAction(SenseAirComponent *senseair) : senseair_(senseair) {} + + void play(Ts... x) override { this->senseair_->abc_get_period(); } + + protected: + SenseAirComponent *senseair_; +}; + } // namespace senseair } // namespace esphome diff --git a/esphome/components/senseair/sensor.py b/esphome/components/senseair/sensor.py index 8f5c7caa68..6f48651563 100644 --- a/esphome/components/senseair/sensor.py +++ b/esphome/components/senseair/sensor.py @@ -1,5 +1,7 @@ import esphome.codegen as cg 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.const import ( CONF_CO2, @@ -15,6 +17,21 @@ senseair_ns = cg.esphome_ns.namespace("senseair") SenseAirComponent = senseair_ns.class_( "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 = ( cv.Schema( @@ -38,3 +55,34 @@ def to_code(config): if CONF_CO2 in config: sens = yield sensor.new_sensor(config[CONF_CO2]) 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) diff --git a/tests/test1.yaml b/tests/test1.yaml index f08cea7770..461d8e5da1 100644 --- a/tests/test1.yaml +++ b/tests/test1.yaml @@ -660,11 +660,6 @@ sensor: - platform: pulse_width name: Pulse Width pin: GPIO12 - - platform: senseair - uart_id: uart0 - co2: - name: 'SenseAir CO2 Value' - update_interval: 15s - platform: sm300d2 uart_id: uart0 co2: diff --git a/tests/test2.yaml b/tests/test2.yaml index 333025358c..616fe32139 100644 --- a/tests/test2.yaml +++ b/tests/test2.yaml @@ -70,6 +70,18 @@ sensor: - platform: ble_rssi service_uuid: '11223344-5566-7788-99aa-bbccddeeff00' 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 mac_address: FF:56:D3:2F:7D:E8 humidity: