From d620b6dd5e5193ad37abd91b1626c89fa49f0276 Mon Sep 17 00:00:00 2001 From: Martin <25747549+martgras@users.noreply.github.com> Date: Wed, 13 Apr 2022 00:19:48 +0200 Subject: [PATCH] Refactor Sensirion Sensors (#3374) Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> --- CODEOWNERS | 1 + esphome/components/i2c/i2c_bus.h | 1 + esphome/components/scd30/scd30.cpp | 91 ++-------- esphome/components/scd30/scd30.h | 8 +- esphome/components/scd30/sensor.py | 6 +- esphome/components/scd4x/scd4x.cpp | 111 ++----------- esphome/components/scd4x/scd4x.h | 8 +- esphome/components/scd4x/sensor.py | 7 +- esphome/components/sdp3x/sdp3x.cpp | 79 +++------ esphome/components/sdp3x/sdp3x.h | 6 +- esphome/components/sdp3x/sensor.py | 6 +- .../components/sensirion_common/__init__.py | 10 ++ .../sensirion_common/i2c_sensirion.cpp | 128 +++++++++++++++ .../sensirion_common/i2c_sensirion.h | 155 ++++++++++++++++++ esphome/components/sgp30/sensor.py | 13 +- esphome/components/sgp30/sgp30.cpp | 79 +-------- esphome/components/sgp30/sgp30.h | 7 +- esphome/components/sgp40/sensor.py | 13 +- esphome/components/sgp40/sgp40.cpp | 116 +++---------- esphome/components/sgp40/sgp40.h | 8 +- esphome/components/sht3xd/sensor.py | 5 +- esphome/components/sht3xd/sht3xd.cpp | 64 +------- esphome/components/sht3xd/sht3xd.h | 7 +- esphome/components/sht4x/sensor.py | 7 +- esphome/components/sht4x/sht4x.cpp | 17 +- esphome/components/sht4x/sht4x.h | 4 +- esphome/components/shtcx/sensor.py | 7 +- esphome/components/shtcx/shtcx.cpp | 65 +------- esphome/components/shtcx/shtcx.h | 6 +- esphome/components/sps30/sensor.py | 7 +- esphome/components/sps30/sps30.cpp | 81 +-------- esphome/components/sps30/sps30.h | 7 +- esphome/components/sts3x/sensor.py | 1 + esphome/components/sts3x/sts3x.cpp | 61 +------ esphome/components/sts3x/sts3x.h | 8 +- esphome/const.py | 2 + 36 files changed, 484 insertions(+), 718 deletions(-) create mode 100644 esphome/components/sensirion_common/__init__.py create mode 100644 esphome/components/sensirion_common/i2c_sensirion.cpp create mode 100644 esphome/components/sensirion_common/i2c_sensirion.h diff --git a/CODEOWNERS b/CODEOWNERS index 79626c4a38..7595fc52e2 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -170,6 +170,7 @@ esphome/components/sdm_meter/* @jesserockz @polyfaces esphome/components/sdp3x/* @Azimath esphome/components/selec_meter/* @sourabhjaiswal esphome/components/select/* @esphome/core +esphome/components/sensirion_common/* @martgras esphome/components/sensor/* @esphome/core esphome/components/sgp40/* @SenexCrenshaw esphome/components/sht4x/* @sjtrny diff --git a/esphome/components/i2c/i2c_bus.h b/esphome/components/i2c/i2c_bus.h index c07a2dd1dd..2633a7adf6 100644 --- a/esphome/components/i2c/i2c_bus.h +++ b/esphome/components/i2c/i2c_bus.h @@ -15,6 +15,7 @@ enum ErrorCode { ERROR_NOT_INITIALIZED = 4, ERROR_TOO_LARGE = 5, ERROR_UNKNOWN = 6, + ERROR_CRC = 7, }; struct ReadBuffer { diff --git a/esphome/components/scd30/scd30.cpp b/esphome/components/scd30/scd30.cpp index 8603072bd5..103b7a255d 100644 --- a/esphome/components/scd30/scd30.cpp +++ b/esphome/components/scd30/scd30.cpp @@ -33,14 +33,8 @@ void SCD30Component::setup() { #endif /// Firmware version identification - if (!this->write_command_(SCD30_CMD_GET_FIRMWARE_VERSION)) { - this->error_code_ = COMMUNICATION_FAILED; - this->mark_failed(); - return; - } uint16_t raw_firmware_version[3]; - - if (!this->read_data_(raw_firmware_version, 3)) { + if (!this->get_register(SCD30_CMD_GET_FIRMWARE_VERSION, raw_firmware_version, 3)) { this->error_code_ = FIRMWARE_IDENTIFICATION_FAILED; this->mark_failed(); return; @@ -49,7 +43,7 @@ void SCD30Component::setup() { uint16_t(raw_firmware_version[0] & 0xFF)); if (this->temperature_offset_ != 0) { - if (!this->write_command_(SCD30_CMD_TEMPERATURE_OFFSET, (uint16_t)(temperature_offset_ * 100.0))) { + if (!this->write_command(SCD30_CMD_TEMPERATURE_OFFSET, (uint16_t)(temperature_offset_ * 100.0))) { ESP_LOGE(TAG, "Sensor SCD30 error setting temperature offset."); this->error_code_ = MEASUREMENT_INIT_FAILED; this->mark_failed(); @@ -69,7 +63,7 @@ void SCD30Component::setup() { delay(30); #endif - if (!this->write_command_(SCD30_CMD_MEASUREMENT_INTERVAL, update_interval_)) { + if (!this->write_command(SCD30_CMD_MEASUREMENT_INTERVAL, update_interval_)) { ESP_LOGE(TAG, "Sensor SCD30 error setting update interval."); this->error_code_ = MEASUREMENT_INIT_FAILED; this->mark_failed(); @@ -81,7 +75,7 @@ void SCD30Component::setup() { // The start measurement command disables the altitude compensation, if any, so we only set it if it's turned on if (this->altitude_compensation_ != 0xFFFF) { - if (!this->write_command_(SCD30_CMD_ALTITUDE_COMPENSATION, altitude_compensation_)) { + if (!this->write_command(SCD30_CMD_ALTITUDE_COMPENSATION, altitude_compensation_)) { ESP_LOGE(TAG, "Sensor SCD30 error setting altitude compensation."); this->error_code_ = MEASUREMENT_INIT_FAILED; this->mark_failed(); @@ -92,7 +86,7 @@ void SCD30Component::setup() { delay(30); #endif - if (!this->write_command_(SCD30_CMD_AUTOMATIC_SELF_CALIBRATION, enable_asc_ ? 1 : 0)) { + if (!this->write_command(SCD30_CMD_AUTOMATIC_SELF_CALIBRATION, enable_asc_ ? 1 : 0)) { ESP_LOGE(TAG, "Sensor SCD30 error setting automatic self calibration."); this->error_code_ = MEASUREMENT_INIT_FAILED; this->mark_failed(); @@ -103,7 +97,7 @@ void SCD30Component::setup() { #endif /// Sensor initialization - if (!this->write_command_(SCD30_CMD_START_CONTINUOUS_MEASUREMENTS, this->ambient_pressure_compensation_)) { + if (!this->write_command(SCD30_CMD_START_CONTINUOUS_MEASUREMENTS, this->ambient_pressure_compensation_)) { ESP_LOGE(TAG, "Sensor SCD30 error starting continuous measurements."); this->error_code_ = MEASUREMENT_INIT_FAILED; this->mark_failed(); @@ -151,14 +145,14 @@ void SCD30Component::dump_config() { } void SCD30Component::update() { - uint16_t raw_read_status[1]; - if (!this->read_data_(raw_read_status, 1) || raw_read_status[0] == 0x00) { + uint16_t raw_read_status; + if (!this->read_data(raw_read_status) || raw_read_status == 0x00) { this->status_set_warning(); ESP_LOGW(TAG, "Data not ready yet!"); return; } - if (!this->write_command_(SCD30_CMD_READ_MEASUREMENT)) { + if (!this->write_command(SCD30_CMD_READ_MEASUREMENT)) { ESP_LOGW(TAG, "Error reading measurement!"); this->status_set_warning(); return; @@ -166,7 +160,7 @@ void SCD30Component::update() { this->set_timeout(50, [this]() { uint16_t raw_data[6]; - if (!this->read_data_(raw_data, 6)) { + if (!this->read_data(raw_data, 6)) { this->status_set_warning(); return; } @@ -197,77 +191,16 @@ void SCD30Component::update() { } bool SCD30Component::is_data_ready_() { - if (!this->write_command_(SCD30_CMD_GET_DATA_READY_STATUS)) { + if (!this->write_command(SCD30_CMD_GET_DATA_READY_STATUS)) { return false; } delay(4); uint16_t is_data_ready; - if (!this->read_data_(&is_data_ready, 1)) { + if (!this->read_data(&is_data_ready, 1)) { return false; } return is_data_ready == 1; } -bool SCD30Component::write_command_(uint16_t command) { - // Warning ugly, trick the I2Ccomponent base by setting register to the first 8 bit. - return this->write_byte(command >> 8, command & 0xFF); -} - -bool SCD30Component::write_command_(uint16_t command, uint16_t data) { - uint8_t raw[5]; - raw[0] = command >> 8; - raw[1] = command & 0xFF; - raw[2] = data >> 8; - raw[3] = data & 0xFF; - raw[4] = sht_crc_(raw[2], raw[3]); - return this->write(raw, 5) == i2c::ERROR_OK; -} - -uint8_t SCD30Component::sht_crc_(uint8_t data1, uint8_t data2) { - uint8_t bit; - uint8_t crc = 0xFF; - - crc ^= data1; - for (bit = 8; bit > 0; --bit) { - if (crc & 0x80) { - crc = (crc << 1) ^ 0x131; - } else { - crc = (crc << 1); - } - } - - crc ^= data2; - for (bit = 8; bit > 0; --bit) { - if (crc & 0x80) { - crc = (crc << 1) ^ 0x131; - } else { - crc = (crc << 1); - } - } - - return crc; -} - -bool SCD30Component::read_data_(uint16_t *data, uint8_t len) { - const uint8_t num_bytes = len * 3; - std::vector buf(num_bytes); - - if (this->read(buf.data(), num_bytes) != i2c::ERROR_OK) { - return false; - } - - for (uint8_t i = 0; i < len; i++) { - const uint8_t j = 3 * i; - uint8_t crc = sht_crc_(buf[j], buf[j + 1]); - if (crc != buf[j + 2]) { - ESP_LOGE(TAG, "CRC8 Checksum invalid! 0x%02X != 0x%02X", buf[j + 2], crc); - return false; - } - data[i] = (buf[j] << 8) | buf[j + 1]; - } - - return true; -} - } // namespace scd30 } // namespace esphome diff --git a/esphome/components/scd30/scd30.h b/esphome/components/scd30/scd30.h index 64193d0cb6..c434bf0dea 100644 --- a/esphome/components/scd30/scd30.h +++ b/esphome/components/scd30/scd30.h @@ -2,13 +2,13 @@ #include "esphome/core/component.h" #include "esphome/components/sensor/sensor.h" -#include "esphome/components/i2c/i2c.h" +#include "esphome/components/sensirion_common/i2c_sensirion.h" namespace esphome { namespace scd30 { /// This class implements support for the Sensirion scd30 i2c GAS (VOC and CO2eq) sensors. -class SCD30Component : public Component, public i2c::I2CDevice { +class SCD30Component : public Component, public sensirion_common::SensirionI2CDevice { public: void set_co2_sensor(sensor::Sensor *co2) { co2_sensor_ = co2; } void set_humidity_sensor(sensor::Sensor *humidity) { humidity_sensor_ = humidity; } @@ -27,10 +27,6 @@ class SCD30Component : public Component, public i2c::I2CDevice { float get_setup_priority() const override { return setup_priority::DATA; } protected: - bool write_command_(uint16_t command); - bool write_command_(uint16_t command, uint16_t data); - bool read_data_(uint16_t *data, uint8_t len); - uint8_t sht_crc_(uint8_t data1, uint8_t data2); bool is_data_ready_(); enum ErrorCode { diff --git a/esphome/components/scd30/sensor.py b/esphome/components/scd30/sensor.py index cd25649f2a..3cfd861a63 100644 --- a/esphome/components/scd30/sensor.py +++ b/esphome/components/scd30/sensor.py @@ -2,6 +2,7 @@ from esphome import core import esphome.codegen as cg import esphome.config_validation as cv from esphome.components import i2c, sensor +from esphome.components import sensirion_common from esphome.const import ( CONF_ID, CONF_HUMIDITY, @@ -18,9 +19,12 @@ from esphome.const import ( ) DEPENDENCIES = ["i2c"] +AUTO_LOAD = ["sensirion_common"] scd30_ns = cg.esphome_ns.namespace("scd30") -SCD30Component = scd30_ns.class_("SCD30Component", cg.Component, i2c.I2CDevice) +SCD30Component = scd30_ns.class_( + "SCD30Component", cg.Component, sensirion_common.SensirionI2CDevice +) CONF_AUTOMATIC_SELF_CALIBRATION = "automatic_self_calibration" CONF_ALTITUDE_COMPENSATION = "altitude_compensation" diff --git a/esphome/components/scd4x/scd4x.cpp b/esphome/components/scd4x/scd4x.cpp index 4bd512394f..559c95df32 100644 --- a/esphome/components/scd4x/scd4x.cpp +++ b/esphome/components/scd4x/scd4x.cpp @@ -25,15 +25,8 @@ void SCD4XComponent::setup() { // the sensor needs 1000 ms to enter the idle state this->set_timeout(1000, [this]() { - // Check if measurement is ready before reading the value - if (!this->write_command_(SCD4X_CMD_GET_DATA_READY_STATUS)) { - ESP_LOGE(TAG, "Failed to write data ready status command"); - this->mark_failed(); - return; - } - - uint16_t raw_read_status[1]; - if (!this->read_data_(raw_read_status, 1)) { + uint16_t raw_read_status; + if (!this->get_register(SCD4X_CMD_GET_DATA_READY_STATUS, raw_read_status)) { ESP_LOGE(TAG, "Failed to read data ready status"); this->mark_failed(); return; @@ -41,9 +34,9 @@ void SCD4XComponent::setup() { uint32_t stop_measurement_delay = 0; // In order to query the device periodic measurement must be ceased - if (raw_read_status[0]) { + if (raw_read_status) { ESP_LOGD(TAG, "Sensor has data available, stopping periodic measurement"); - if (!this->write_command_(SCD4X_CMD_STOP_MEASUREMENTS)) { + if (!this->write_command(SCD4X_CMD_STOP_MEASUREMENTS)) { ESP_LOGE(TAG, "Failed to stop measurements"); this->mark_failed(); return; @@ -53,15 +46,8 @@ void SCD4XComponent::setup() { 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; - } - uint16_t raw_serial_number[3]; - if (!this->read_data_(raw_serial_number, 3)) { + if (!this->get_register(SCD4X_CMD_GET_SERIAL_NUMBER, raw_serial_number, 3, 1)) { ESP_LOGE(TAG, "Failed to read serial number"); this->error_code_ = SERIAL_NUMBER_IDENTIFICATION_FAILED; this->mark_failed(); @@ -70,8 +56,8 @@ void SCD4XComponent::setup() { 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))) { + 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(); @@ -88,7 +74,7 @@ void SCD4XComponent::setup() { return; } } else { - if (!this->write_command_(SCD4X_CMD_ALTITUDE_COMPENSATION, altitude_compensation_)) { + 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(); @@ -96,7 +82,7 @@ void SCD4XComponent::setup() { } } - if (!this->write_command_(SCD4X_CMD_AUTOMATIC_SELF_CALIBRATION, enable_asc_ ? 1 : 0)) { + 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->mark_failed(); @@ -104,7 +90,7 @@ void SCD4XComponent::setup() { } // Finally start sensor measurements - if (!this->write_command_(SCD4X_CMD_START_CONTINUOUS_MEASUREMENTS)) { + if (!this->write_command(SCD4X_CMD_START_CONTINUOUS_MEASUREMENTS)) { ESP_LOGE(TAG, "Error starting continuous measurements."); this->error_code_ = MEASUREMENT_INIT_FAILED; this->mark_failed(); @@ -164,19 +150,19 @@ void SCD4XComponent::update() { } // 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(); return; } - uint16_t raw_read_status[1]; - if (!this->read_data_(raw_read_status, 1) || raw_read_status[0] == 0x00) { + uint16_t raw_read_status; + if (!this->read_data(raw_read_status) || raw_read_status == 0x00) { this->status_set_warning(); ESP_LOGW(TAG, "Data not ready yet!"); return; } - if (!this->write_command_(SCD4X_CMD_READ_MEASUREMENT)) { + if (!this->write_command(SCD4X_CMD_READ_MEASUREMENT)) { ESP_LOGW(TAG, "Error reading measurement!"); this->status_set_warning(); return; @@ -184,7 +170,7 @@ void SCD4XComponent::update() { // Read off sensor data uint16_t raw_data[3]; - if (!this->read_data_(raw_data, 3)) { + if (!this->read_data(raw_data, 3)) { this->status_set_warning(); return; } @@ -218,7 +204,7 @@ void SCD4XComponent::set_ambient_pressure_compensation(float pressure_in_bar) { } bool SCD4XComponent::update_ambient_pressure_compensation_(uint16_t pressure_in_hpa) { - if (this->write_command_(SCD4X_CMD_AMBIENT_PRESSURE_COMPENSATION, 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 { @@ -227,70 +213,5 @@ bool SCD4XComponent::update_ambient_pressure_compensation_(uint16_t pressure_in_ } } -uint8_t SCD4XComponent::sht_crc_(uint8_t data1, uint8_t data2) { - uint8_t bit; - uint8_t crc = 0xFF; - - crc ^= data1; - for (bit = 8; bit > 0; --bit) { - if (crc & 0x80) { - crc = (crc << 1) ^ 0x131; - } else { - crc = (crc << 1); - } - } - - crc ^= data2; - for (bit = 8; bit > 0; --bit) { - if (crc & 0x80) { - crc = (crc << 1) ^ 0x131; - } else { - crc = (crc << 1); - } - } - - return crc; -} - -bool SCD4XComponent::read_data_(uint16_t *data, uint8_t len) { - const uint8_t num_bytes = len * 3; - std::vector buf(num_bytes); - - if (this->read(buf.data(), num_bytes) != i2c::ERROR_OK) { - return false; - } - - for (uint8_t i = 0; i < len; i++) { - const uint8_t j = 3 * i; - uint8_t crc = sht_crc_(buf[j], buf[j + 1]); - if (crc != buf[j + 2]) { - ESP_LOGE(TAG, "CRC8 Checksum invalid! 0x%02X != 0x%02X", buf[j + 2], crc); - return false; - } - data[i] = (buf[j] << 8) | buf[j + 1]; - } - return true; -} - -bool SCD4XComponent::write_command_(uint16_t command) { - const uint8_t num_bytes = 2; - uint8_t buffer[num_bytes]; - - buffer[0] = (command >> 8); - buffer[1] = command & 0xff; - - return this->write(buffer, num_bytes) == i2c::ERROR_OK; -} - -bool SCD4XComponent::write_command_(uint16_t command, uint16_t data) { - uint8_t raw[5]; - raw[0] = command >> 8; - raw[1] = command & 0xFF; - raw[2] = data >> 8; - raw[3] = data & 0xFF; - raw[4] = sht_crc_(raw[2], raw[3]); - return this->write(raw, 5) == i2c::ERROR_OK; -} - } // namespace scd4x } // namespace esphome diff --git a/esphome/components/scd4x/scd4x.h b/esphome/components/scd4x/scd4x.h index 4fe2bf14cc..3e534bcf98 100644 --- a/esphome/components/scd4x/scd4x.h +++ b/esphome/components/scd4x/scd4x.h @@ -2,14 +2,14 @@ #include "esphome/core/component.h" #include "esphome/components/sensor/sensor.h" -#include "esphome/components/i2c/i2c.h" +#include "esphome/components/sensirion_common/i2c_sensirion.h" namespace esphome { namespace scd4x { enum ERRORCODE { COMMUNICATION_FAILED, SERIAL_NUMBER_IDENTIFICATION_FAILED, MEASUREMENT_INIT_FAILED, UNKNOWN }; -class SCD4XComponent : public PollingComponent, public i2c::I2CDevice { +class SCD4XComponent : public PollingComponent, public sensirion_common::SensirionI2CDevice { public: float get_setup_priority() const override { return setup_priority::DATA; } void setup() override; @@ -27,10 +27,6 @@ class SCD4XComponent : public PollingComponent, public i2c::I2CDevice { void set_humidity_sensor(sensor::Sensor *humidity) { humidity_sensor_ = humidity; } protected: - uint8_t sht_crc_(uint8_t data1, uint8_t data2); - bool read_data_(uint16_t *data, uint8_t len); - bool write_command_(uint16_t command); - bool write_command_(uint16_t command, uint16_t data); bool update_ambient_pressure_compensation_(uint16_t pressure_in_hpa); ERRORCODE error_code_; diff --git a/esphome/components/scd4x/sensor.py b/esphome/components/scd4x/sensor.py index 3e814ffe78..6ab0e1ba99 100644 --- a/esphome/components/scd4x/sensor.py +++ b/esphome/components/scd4x/sensor.py @@ -1,7 +1,7 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome.components import i2c, sensor - +from esphome.components import sensirion_common from esphome.const import ( CONF_ID, CONF_CO2, @@ -21,9 +21,12 @@ from esphome.const import ( CODEOWNERS = ["@sjtrny"] DEPENDENCIES = ["i2c"] +AUTO_LOAD = ["sensirion_common"] scd4x_ns = cg.esphome_ns.namespace("scd4x") -SCD4XComponent = scd4x_ns.class_("SCD4XComponent", cg.PollingComponent, i2c.I2CDevice) +SCD4XComponent = scd4x_ns.class_( + "SCD4XComponent", cg.PollingComponent, sensirion_common.SensirionI2CDevice +) CONF_AUTOMATIC_SELF_CALIBRATION = "automatic_self_calibration" CONF_ALTITUDE_COMPENSATION = "altitude_compensation" diff --git a/esphome/components/sdp3x/sdp3x.cpp b/esphome/components/sdp3x/sdp3x.cpp index eb1543f2c2..251d59607a 100644 --- a/esphome/components/sdp3x/sdp3x.cpp +++ b/esphome/components/sdp3x/sdp3x.cpp @@ -7,55 +7,50 @@ namespace esphome { namespace sdp3x { static const char *const TAG = "sdp3x.sensor"; -static const uint8_t SDP3X_SOFT_RESET[2] = {0x00, 0x06}; -static const uint8_t SDP3X_READ_ID1[2] = {0x36, 0x7C}; -static const uint8_t SDP3X_READ_ID2[2] = {0xE1, 0x02}; -static const uint8_t SDP3X_START_DP_AVG[2] = {0x36, 0x15}; -static const uint8_t SDP3X_START_MASS_FLOW_AVG[2] = {0x36, 0x03}; -static const uint8_t SDP3X_STOP_MEAS[2] = {0x3F, 0xF9}; +static const uint16_t SDP3X_SOFT_RESET = 0x0006; +static const uint16_t SDP3X_READ_ID1 = 0x367C; +static const uint16_t SDP3X_READ_ID2 = 0xE102; +static const uint16_t SDP3X_START_DP_AVG = 0x3615; +static const uint16_t SDP3X_START_MASS_FLOW_AVG = 0x3603; +static const uint16_t SDP3X_STOP_MEAS = 0x3FF9; void SDP3XComponent::update() { this->read_pressure_(); } void SDP3XComponent::setup() { ESP_LOGD(TAG, "Setting up SDP3X..."); - if (this->write(SDP3X_STOP_MEAS, 2) != i2c::ERROR_OK) { + if (!this->write_command(SDP3X_STOP_MEAS)) { ESP_LOGW(TAG, "Stop SDP3X failed!"); // This sometimes fails for no good reason } - if (this->write(SDP3X_SOFT_RESET, 2) != i2c::ERROR_OK) { + if (!this->write_command(SDP3X_SOFT_RESET)) { ESP_LOGW(TAG, "Soft Reset SDP3X failed!"); // This sometimes fails for no good reason } this->set_timeout(20, [this] { - if (this->write(SDP3X_READ_ID1, 2) != i2c::ERROR_OK) { + if (!this->write_command(SDP3X_READ_ID1)) { ESP_LOGE(TAG, "Read ID1 SDP3X failed!"); this->mark_failed(); return; } - if (this->write(SDP3X_READ_ID2, 2) != i2c::ERROR_OK) { + if (!this->write_command(SDP3X_READ_ID2)) { ESP_LOGE(TAG, "Read ID2 SDP3X failed!"); this->mark_failed(); return; } - uint8_t data[18]; - if (this->read(data, 18) != i2c::ERROR_OK) { + uint16_t data[6]; + if (this->read_data(data, 6) != i2c::ERROR_OK) { ESP_LOGE(TAG, "Read ID SDP3X failed!"); this->mark_failed(); return; } - if (!(check_crc_(&data[0], 2, data[2]) && check_crc_(&data[3], 2, data[5]))) { - ESP_LOGE(TAG, "CRC ID SDP3X failed!"); - this->mark_failed(); - return; - } // SDP8xx // ref: // https://www.sensirion.com/fileadmin/user_upload/customers/sensirion/Dokumente/8_Differential_Pressure/Datasheets/Sensirion_Differential_Pressure_Datasheet_SDP8xx_Digital.pdf - if (data[2] == 0x02) { - switch (data[3]) { + if (data[1] >> 8 == 0x02) { + switch (data[1] & 0xFF) { case 0x01: // SDP800-500Pa ESP_LOGCONFIG(TAG, "Sensor is SDP800-500Pa"); break; @@ -75,15 +70,16 @@ void SDP3XComponent::setup() { ESP_LOGCONFIG(TAG, "Sensor is SDP810-125Pa"); break; } - } else if (data[2] == 0x01) { - if (data[3] == 0x01) { + } else if (data[1] >> 8 == 0x01) { + if ((data[1] & 0xFF) == 0x01) { ESP_LOGCONFIG(TAG, "Sensor is SDP31-500Pa"); - } else if (data[3] == 0x02) { + } else if ((data[1] & 0xFF) == 0x02) { ESP_LOGCONFIG(TAG, "Sensor is SDP32-125Pa"); } } - if (this->write(measurement_mode_ == DP_AVG ? SDP3X_START_DP_AVG : SDP3X_START_MASS_FLOW_AVG, 2) != i2c::ERROR_OK) { + if (this->write_command(measurement_mode_ == DP_AVG ? SDP3X_START_DP_AVG : SDP3X_START_MASS_FLOW_AVG) != + i2c::ERROR_OK) { ESP_LOGE(TAG, "Start Measurements SDP3X failed!"); this->mark_failed(); return; @@ -101,22 +97,16 @@ void SDP3XComponent::dump_config() { } void SDP3XComponent::read_pressure_() { - uint8_t data[9]; - if (this->read(data, 9) != i2c::ERROR_OK) { + uint16_t data[3]; + if (this->read_data(data, 3) != i2c::ERROR_OK) { ESP_LOGW(TAG, "Couldn't read SDP3X data!"); this->status_set_warning(); return; } - if (!(check_crc_(&data[0], 2, data[2]) && check_crc_(&data[3], 2, data[5]) && check_crc_(&data[6], 2, data[8]))) { - ESP_LOGW(TAG, "Invalid SDP3X data!"); - this->status_set_warning(); - return; - } - - int16_t pressure_raw = encode_uint16(data[0], data[1]); - int16_t temperature_raw = encode_uint16(data[3], data[4]); - int16_t scale_factor_raw = encode_uint16(data[6], data[7]); + int16_t pressure_raw = data[0]; + int16_t temperature_raw = data[1]; + int16_t scale_factor_raw = data[2]; // scale factor is in Pa - convert to hPa float pressure = pressure_raw / (scale_factor_raw * 100.0f); ESP_LOGV(TAG, "Got raw pressure=%d, raw scale factor =%d, raw temperature=%d ", pressure_raw, scale_factor_raw, @@ -129,26 +119,5 @@ void SDP3XComponent::read_pressure_() { float SDP3XComponent::get_setup_priority() const { return setup_priority::DATA; } -// Check CRC function from SDP3X sample code provided by sensirion -// Returns true if a checksum is OK -bool SDP3XComponent::check_crc_(const uint8_t data[], uint8_t size, uint8_t checksum) { - uint8_t crc = 0xFF; - - // calculates 8-Bit checksum with given polynomial 0x31 (x^8 + x^5 + x^4 + 1) - for (int i = 0; i < size; i++) { - crc ^= (data[i]); - for (uint8_t bit = 8; bit > 0; --bit) { - if (crc & 0x80) { - crc = (crc << 1) ^ 0x31; - } else { - crc = (crc << 1); - } - } - } - - // verify checksum - return (crc == checksum); -} - } // namespace sdp3x } // namespace esphome diff --git a/esphome/components/sdp3x/sdp3x.h b/esphome/components/sdp3x/sdp3x.h index 0e74d0883d..e3d3533c74 100644 --- a/esphome/components/sdp3x/sdp3x.h +++ b/esphome/components/sdp3x/sdp3x.h @@ -2,14 +2,14 @@ #include "esphome/core/component.h" #include "esphome/components/sensor/sensor.h" -#include "esphome/components/i2c/i2c.h" +#include "esphome/components/sensirion_common/i2c_sensirion.h" namespace esphome { namespace sdp3x { enum MeasurementMode { MASS_FLOW_AVG, DP_AVG }; -class SDP3XComponent : public PollingComponent, public i2c::I2CDevice, public sensor::Sensor { +class SDP3XComponent : public PollingComponent, public sensirion_common::SensirionI2CDevice, public sensor::Sensor { public: /// Schedule temperature+pressure readings. void update() override; @@ -23,8 +23,6 @@ class SDP3XComponent : public PollingComponent, public i2c::I2CDevice, public se protected: /// Internal method to read the pressure from the component after it has been scheduled. void read_pressure_(); - - bool check_crc_(const uint8_t data[], uint8_t size, uint8_t checksum); MeasurementMode measurement_mode_; }; diff --git a/esphome/components/sdp3x/sensor.py b/esphome/components/sdp3x/sensor.py index 66ee475b11..60608818a2 100644 --- a/esphome/components/sdp3x/sensor.py +++ b/esphome/components/sdp3x/sensor.py @@ -1,6 +1,7 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome.components import i2c, sensor +from esphome.components import sensirion_common from esphome.const import ( DEVICE_CLASS_PRESSURE, STATE_CLASS_MEASUREMENT, @@ -8,10 +9,13 @@ from esphome.const import ( ) DEPENDENCIES = ["i2c"] +AUTO_LOAD = ["sensirion_common"] CODEOWNERS = ["@Azimath"] sdp3x_ns = cg.esphome_ns.namespace("sdp3x") -SDP3XComponent = sdp3x_ns.class_("SDP3XComponent", cg.PollingComponent, i2c.I2CDevice) +SDP3XComponent = sdp3x_ns.class_( + "SDP3XComponent", cg.PollingComponent, sensirion_common.SensirionI2CDevice +) MeasurementMode = sdp3x_ns.enum("MeasurementMode") diff --git a/esphome/components/sensirion_common/__init__.py b/esphome/components/sensirion_common/__init__.py new file mode 100644 index 0000000000..b27f37099d --- /dev/null +++ b/esphome/components/sensirion_common/__init__.py @@ -0,0 +1,10 @@ +import esphome.codegen as cg + +from esphome.components import i2c + + +CODEOWNERS = ["@martgras"] + +sensirion_common_ns = cg.esphome_ns.namespace("sensirion_common") + +SensirionI2CDevice = sensirion_common_ns.class_("SensirionI2CDevice", i2c.I2CDevice) diff --git a/esphome/components/sensirion_common/i2c_sensirion.cpp b/esphome/components/sensirion_common/i2c_sensirion.cpp new file mode 100644 index 0000000000..a2232a7d1b --- /dev/null +++ b/esphome/components/sensirion_common/i2c_sensirion.cpp @@ -0,0 +1,128 @@ +#include "i2c_sensirion.h" +#include "esphome/core/log.h" +#include "esphome/core/hal.h" +#include + +namespace esphome { +namespace sensirion_common { + +static const char *const TAG = "sensirion_i2c"; +// To avoid memory allocations for small writes a stack buffer is used +static const size_t BUFFER_STACK_SIZE = 16; + +bool SensirionI2CDevice::read_data(uint16_t *data, uint8_t len) { + const uint8_t num_bytes = len * 3; + std::vector buf(num_bytes); + + last_error_ = this->read(buf.data(), num_bytes); + if (last_error_ != i2c::ERROR_OK) { + return false; + } + + for (uint8_t i = 0; i < len; i++) { + const uint8_t j = 3 * i; + uint8_t crc = sht_crc_(buf[j], buf[j + 1]); + if (crc != buf[j + 2]) { + ESP_LOGE(TAG, "CRC8 Checksum invalid at pos %d! 0x%02X != 0x%02X", i, buf[j + 2], crc); + last_error_ = i2c::ERROR_CRC; + return false; + } + data[i] = encode_uint16(buf[j], buf[j + 1]); + } + return true; +} +/*** + * write command with parameters and insert crc + * use stack array for less than 4 paramaters. Most sensirion i2c commands have less parameters + */ +bool SensirionI2CDevice::write_command_(uint16_t command, CommandLen command_len, const uint16_t *data, + uint8_t data_len) { + uint8_t temp_stack[BUFFER_STACK_SIZE]; + std::unique_ptr temp_heap; + uint8_t *temp; + size_t required_buffer_len = data_len * 3 + 2; + + // Is a dynamic allocation required ? + if (required_buffer_len >= BUFFER_STACK_SIZE) { + temp_heap = std::unique_ptr(new uint8_t[required_buffer_len]); + temp = temp_heap.get(); + } else { + temp = temp_stack; + } + // First byte or word is the command + uint8_t raw_idx = 0; + if (command_len == 1) { + temp[raw_idx++] = command & 0xFF; + } else { + // command is 2 bytes +#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ + temp[raw_idx++] = command >> 8; + temp[raw_idx++] = command & 0xFF; +#else + temp[raw_idx++] = command & 0xFF; + temp[raw_idx++] = command >> 8; +#endif + } + // add parameters folllowed by crc + // skipped if len == 0 + for (size_t i = 0; i < data_len; i++) { +#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ + temp[raw_idx++] = data[i] >> 8; + temp[raw_idx++] = data[i] & 0xFF; +#else + temp[raw_idx++] = data[i] & 0xFF; + temp[raw_idx++] = data[i] >> 8; +#endif + temp[raw_idx++] = sht_crc_(data[i]); + } + last_error_ = this->write(temp, raw_idx); + return last_error_ == i2c::ERROR_OK; +} + +bool SensirionI2CDevice::get_register_(uint16_t reg, CommandLen command_len, uint16_t *data, uint8_t len, + uint8_t delay_ms) { + if (!this->write_command_(reg, command_len, nullptr, 0)) { + ESP_LOGE(TAG, "Failed to write i2c register=0x%X (%d) err=%d,", reg, command_len, this->last_error_); + return false; + } + delay(delay_ms); + bool result = this->read_data(data, len); + if (!result) { + ESP_LOGE(TAG, "Failed to read data from register=0x%X err=%d,", reg, this->last_error_); + } + return result; +} + +// The 8-bit CRC checksum is transmitted after each data word +uint8_t SensirionI2CDevice::sht_crc_(uint16_t data) { + uint8_t bit; + uint8_t crc = 0xFF; +#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ + crc ^= data >> 8; +#else + crc ^= data & 0xFF; +#endif + for (bit = 8; bit > 0; --bit) { + if (crc & 0x80) { + crc = (crc << 1) ^ crc_polynomial_; + } else { + crc = (crc << 1); + } + } +#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ + crc ^= data & 0xFF; +#else + crc ^= data >> 8; +#endif + for (bit = 8; bit > 0; --bit) { + if (crc & 0x80) { + crc = (crc << 1) ^ crc_polynomial_; + } else { + crc = (crc << 1); + } + } + return crc; +} + +} // namespace sensirion_common +} // namespace esphome diff --git a/esphome/components/sensirion_common/i2c_sensirion.h b/esphome/components/sensirion_common/i2c_sensirion.h new file mode 100644 index 0000000000..88e1d59984 --- /dev/null +++ b/esphome/components/sensirion_common/i2c_sensirion.h @@ -0,0 +1,155 @@ +#pragma once +#include "esphome/components/i2c/i2c.h" + +namespace esphome { +namespace sensirion_common { + +/** + * Implementation of a i2c functions for Sensirion sensors + * Sensirion data requires crc checking. + * Each 16 bit word is/must be followed 8 bit CRC code + * (Applies to read and write - note the i2c command code doesn't need a CRC) + * Format: + * | 16 Bit Command Code | 16 bit Data word 1 | CRC of DW 1 | 16 bit Data word 1 | CRC of DW 2 | .. + */ +class SensirionI2CDevice : public i2c::I2CDevice { + public: + enum CommandLen : uint8_t { ADDR_8_BIT = 1, ADDR_16_BIT = 2 }; + + /** Read data words from i2c device. + * handles crc check used by Sensirion sensors + * @param data pointer to raw result + * @param len number of words to read + * @return true if reading succeded + */ + bool read_data(uint16_t *data, uint8_t len); + + /** Read 1 data word from i2c device. + * @param data reference to raw result + * @return true if reading succeded + */ + bool read_data(uint16_t &data) { return this->read_data(&data, 1); } + + /** get data words from i2c register. + * handles crc check used by Sensirion sensors + * @param i2c register + * @param data pointer to raw result + * @param len number of words to read + * @param delay milliseconds to to wait between sending the i2c commmand and reading the result + * @return true if reading succeded + */ + bool get_register(uint16_t command, uint16_t *data, uint8_t len, uint8_t delay = 0) { + return get_register_(command, ADDR_16_BIT, data, len, delay); + } + /** Read 1 data word from 16 bit i2c register. + * @param i2c register + * @param data reference to raw result + * @param delay milliseconds to to wait between sending the i2c commmand and reading the result + * @return true if reading succeded + */ + bool get_register(uint16_t i2c_register, uint16_t &data, uint8_t delay = 0) { + return this->get_register_(i2c_register, ADDR_16_BIT, &data, 1, delay); + } + + /** get data words from i2c register. + * handles crc check used by Sensirion sensors + * @param i2c register + * @param data pointer to raw result + * @param len number of words to read + * @param delay milliseconds to to wait between sending the i2c commmand and reading the result + * @return true if reading succeded + */ + bool get_8bit_register(uint8_t i2c_register, uint16_t *data, uint8_t len, uint8_t delay = 0) { + return get_register_(i2c_register, ADDR_8_BIT, data, len, delay); + } + + /** Read 1 data word from 8 bit i2c register. + * @param i2c register + * @param data reference to raw result + * @param delay milliseconds to to wait between sending the i2c commmand and reading the result + * @return true if reading succeded + */ + bool get_8bit_register(uint8_t i2c_register, uint16_t &data, uint8_t delay = 0) { + return this->get_register_(i2c_register, ADDR_8_BIT, &data, 1, delay); + } + + /** Write a command to the i2c device. + * @param command i2c command to send + * @return true if reading succeded + */ + template bool write_command(T i2c_register) { return write_command(i2c_register, nullptr, 0); } + + /** Write a command and one data word to the i2c device . + * @param command i2c command to send + * @param data argument for the i2c command + * @return true if reading succeded + */ + template bool write_command(T i2c_register, uint16_t data) { return write_command(i2c_register, &data, 1); } + + /** Write a command with arguments as words + * @param i2c_register i2c command to send - an be uint8_t or uint16_t + * @param data vector arguments for the i2c command + * @return true if reading succeded + */ + template bool write_command(T i2c_register, const std::vector &data) { + return write_command_(i2c_register, sizeof(T), data.data(), data.size()); + } + + /** Write a command with arguments as words + * @param i2c_register i2c command to send - an be uint8_t or uint16_t + * @param data arguments for the i2c command + * @param len number of arguments (words) + * @return true if reading succeded + */ + template bool write_command(T i2c_register, const uint16_t *data, uint8_t len) { + // limit to 8 or 16 bit only + static_assert(sizeof(i2c_register) == 1 || sizeof(i2c_register) == 2, + "only 8 or 16 bit command types are supported."); + return write_command_(i2c_register, CommandLen(sizeof(T)), data, len); + } + + protected: + uint8_t crc_polynomial_{0x31u}; // default for sensirion + /** Write a command with arguments as words + * @param command i2c command to send can be uint8_t or uint16_t + * @param command_len either 1 for short 8 bit command or 2 for 16 bit command codes + * @param data arguments for the i2c command + * @param data_len number of arguments (words) + * @return true if reading succeded + */ + bool write_command_(uint16_t command, CommandLen command_len, const uint16_t *data, uint8_t data_len); + + /** get data words from i2c register. + * handles crc check used by Sensirion sensors + * @param i2c register + * @param command_len either 1 for short 8 bit command or 2 for 16 bit command codes + * @param data pointer to raw result + * @param len number of words to read + * @param delay milliseconds to to wait between sending the i2c commmand and reading the result + * @return true if reading succeded + */ + bool get_register_(uint16_t reg, CommandLen command_len, uint16_t *data, uint8_t len, uint8_t delay); + + /** 8-bit CRC checksum that is transmitted after each data word for read and write operation + * @param command i2c command to send + * @param data data word for which the crc8 checksum is calculated + * @param len number of arguments (words) + * @return 8 Bit CRC + */ + uint8_t sht_crc_(uint16_t data); + + /** 8-bit CRC checksum that is transmitted after each data word for read and write operation + * @param command i2c command to send + * @param data1 high byte of data word + * @param data2 low byte of data word + * @return 8 Bit CRC + */ + uint8_t sht_crc_(uint8_t data1, uint8_t data2) { return sht_crc_(encode_uint16(data1, data2)); } + + /** last error code from i2c operation + */ + i2c::ErrorCode last_error_; +}; + +} // namespace sensirion_common +} // namespace esphome diff --git a/esphome/components/sgp30/sensor.py b/esphome/components/sgp30/sensor.py index 14a078b501..0029e2c515 100644 --- a/esphome/components/sgp30/sensor.py +++ b/esphome/components/sgp30/sensor.py @@ -1,10 +1,13 @@ import esphome.codegen as cg import esphome.config_validation as cv -from esphome.components import i2c, sensor +from esphome.components import i2c, sensor, sensirion_common + from esphome.const import ( CONF_ID, CONF_BASELINE, CONF_ECO2, + CONF_STORE_BASELINE, + CONF_TEMPERATURE_SOURCE, CONF_TVOC, ICON_RADIATOR, DEVICE_CLASS_CARBON_DIOXIDE, @@ -17,17 +20,19 @@ from esphome.const import ( ) DEPENDENCIES = ["i2c"] +AUTO_LOAD = ["sensirion_common"] sgp30_ns = cg.esphome_ns.namespace("sgp30") -SGP30Component = sgp30_ns.class_("SGP30Component", cg.PollingComponent, i2c.I2CDevice) +SGP30Component = sgp30_ns.class_( + "SGP30Component", cg.PollingComponent, sensirion_common.SensirionI2CDevice +) CONF_ECO2_BASELINE = "eco2_baseline" CONF_TVOC_BASELINE = "tvoc_baseline" -CONF_STORE_BASELINE = "store_baseline" CONF_UPTIME = "uptime" CONF_COMPENSATION = "compensation" CONF_HUMIDITY_SOURCE = "humidity_source" -CONF_TEMPERATURE_SOURCE = "temperature_source" + CONFIG_SCHEMA = ( cv.Schema( diff --git a/esphome/components/sgp30/sgp30.cpp b/esphome/components/sgp30/sgp30.cpp index b55097fcd0..a6572620c4 100644 --- a/esphome/components/sgp30/sgp30.cpp +++ b/esphome/components/sgp30/sgp30.cpp @@ -36,14 +36,8 @@ void SGP30Component::setup() { ESP_LOGCONFIG(TAG, "Setting up SGP30..."); // Serial Number identification - if (!this->write_command_(SGP30_CMD_GET_SERIAL_ID)) { - this->error_code_ = COMMUNICATION_FAILED; - this->mark_failed(); - return; - } uint16_t raw_serial_number[3]; - - if (!this->read_data_(raw_serial_number, 3)) { + if (!this->get_register(SGP30_CMD_GET_SERIAL_ID, raw_serial_number, 3)) { this->mark_failed(); return; } @@ -52,16 +46,12 @@ void SGP30Component::setup() { ESP_LOGD(TAG, "Serial Number: %" PRIu64, this->serial_number_); // Featureset identification for future use - if (!this->write_command_(SGP30_CMD_GET_FEATURESET)) { + uint16_t raw_featureset; + if (!this->get_register(SGP30_CMD_GET_FEATURESET, raw_featureset)) { this->mark_failed(); return; } - uint16_t raw_featureset[1]; - if (!this->read_data_(raw_featureset, 1)) { - this->mark_failed(); - return; - } - this->featureset_ = raw_featureset[0]; + this->featureset_ = raw_featureset; if (uint16_t(this->featureset_ >> 12) != 0x0) { if (uint16_t(this->featureset_ >> 12) == 0x1) { // ID matching a different sensor: SGPC3 @@ -76,7 +66,7 @@ void SGP30Component::setup() { ESP_LOGD(TAG, "Product version: 0x%0X", uint16_t(this->featureset_ & 0x1FF)); // Sensor initialization - if (!this->write_command_(SGP30_CMD_IAQ_INIT)) { + if (!this->write_command(SGP30_CMD_IAQ_INIT)) { ESP_LOGE(TAG, "Sensor sgp30_iaq_init failed."); this->error_code_ = MEASUREMENT_INIT_FAILED; this->mark_failed(); @@ -119,14 +109,14 @@ bool SGP30Component::is_sensor_baseline_reliable_() { void SGP30Component::read_iaq_baseline_() { if (this->is_sensor_baseline_reliable_()) { - if (!this->write_command_(SGP30_CMD_GET_IAQ_BASELINE)) { + if (!this->write_command(SGP30_CMD_GET_IAQ_BASELINE)) { ESP_LOGD(TAG, "Error getting baseline"); this->status_set_warning(); return; } this->set_timeout(50, [this]() { uint16_t raw_data[2]; - if (!this->read_data_(raw_data, 2)) { + if (!this->read_data(raw_data, 2)) { this->status_set_warning(); return; } @@ -274,14 +264,14 @@ void SGP30Component::dump_config() { } void SGP30Component::update() { - if (!this->write_command_(SGP30_CMD_MEASURE_IAQ)) { + if (!this->write_command(SGP30_CMD_MEASURE_IAQ)) { this->status_set_warning(); return; } this->seconds_since_last_store_ += this->update_interval_ / 1000; this->set_timeout(50, [this]() { uint16_t raw_data[2]; - if (!this->read_data_(raw_data, 2)) { + if (!this->read_data(raw_data, 2)) { this->status_set_warning(); return; } @@ -305,56 +295,5 @@ void SGP30Component::update() { }); } -bool SGP30Component::write_command_(uint16_t command) { - // Warning ugly, trick the I2Ccomponent base by setting register to the first 8 bit. - return this->write_byte(command >> 8, command & 0xFF); -} - -uint8_t SGP30Component::sht_crc_(uint8_t data1, uint8_t data2) { - uint8_t bit; - uint8_t crc = 0xFF; - - crc ^= data1; - for (bit = 8; bit > 0; --bit) { - if (crc & 0x80) { - crc = (crc << 1) ^ 0x131; - } else { - crc = (crc << 1); - } - } - - crc ^= data2; - for (bit = 8; bit > 0; --bit) { - if (crc & 0x80) { - crc = (crc << 1) ^ 0x131; - } else { - crc = (crc << 1); - } - } - - return crc; -} - -bool SGP30Component::read_data_(uint16_t *data, uint8_t len) { - const uint8_t num_bytes = len * 3; - std::vector buf(num_bytes); - - if (this->read(buf.data(), num_bytes) != i2c::ERROR_OK) { - return false; - } - - for (uint8_t i = 0; i < len; i++) { - const uint8_t j = 3 * i; - uint8_t crc = sht_crc_(buf[j], buf[j + 1]); - if (crc != buf[j + 2]) { - ESP_LOGE(TAG, "CRC8 Checksum invalid! 0x%02X != 0x%02X", buf[j + 2], crc); - return false; - } - data[i] = (buf[j] << 8) | buf[j + 1]; - } - - return true; -} - } // namespace sgp30 } // namespace esphome diff --git a/esphome/components/sgp30/sgp30.h b/esphome/components/sgp30/sgp30.h index 91a1c1e9c7..d61eee00db 100644 --- a/esphome/components/sgp30/sgp30.h +++ b/esphome/components/sgp30/sgp30.h @@ -2,7 +2,7 @@ #include "esphome/core/component.h" #include "esphome/components/sensor/sensor.h" -#include "esphome/components/i2c/i2c.h" +#include "esphome/components/sensirion_common/i2c_sensirion.h" #include "esphome/core/preferences.h" #include @@ -15,7 +15,7 @@ struct SGP30Baselines { } PACKED; /// This class implements support for the Sensirion SGP30 i2c GAS (VOC and CO2eq) sensors. -class SGP30Component : public PollingComponent, public i2c::I2CDevice { +class SGP30Component : public PollingComponent, public sensirion_common::SensirionI2CDevice { public: void set_eco2_sensor(sensor::Sensor *eco2) { eco2_sensor_ = eco2; } void set_tvoc_sensor(sensor::Sensor *tvoc) { tvoc_sensor_ = tvoc; } @@ -33,13 +33,10 @@ class SGP30Component : public PollingComponent, public i2c::I2CDevice { float get_setup_priority() const override { return setup_priority::DATA; } protected: - bool write_command_(uint16_t command); - bool read_data_(uint16_t *data, uint8_t len); void send_env_data_(); void read_iaq_baseline_(); bool is_sensor_baseline_reliable_(); void write_iaq_baseline_(uint16_t eco2_baseline, uint16_t tvoc_baseline); - uint8_t sht_crc_(uint8_t data1, uint8_t data2); uint64_t serial_number_; uint16_t featureset_; uint32_t required_warm_up_time_; diff --git a/esphome/components/sgp40/sensor.py b/esphome/components/sgp40/sensor.py index 6f27b54fb0..ee267d6062 100644 --- a/esphome/components/sgp40/sensor.py +++ b/esphome/components/sgp40/sensor.py @@ -1,25 +1,30 @@ import esphome.codegen as cg import esphome.config_validation as cv -from esphome.components import i2c, sensor +from esphome.components import i2c, sensor, sensirion_common + from esphome.const import ( + CONF_STORE_BASELINE, + CONF_TEMPERATURE_SOURCE, ICON_RADIATOR, DEVICE_CLASS_VOLATILE_ORGANIC_COMPOUNDS, STATE_CLASS_MEASUREMENT, ) DEPENDENCIES = ["i2c"] +AUTO_LOAD = ["sensirion_common"] CODEOWNERS = ["@SenexCrenshaw"] sgp40_ns = cg.esphome_ns.namespace("sgp40") SGP40Component = sgp40_ns.class_( - "SGP40Component", sensor.Sensor, cg.PollingComponent, i2c.I2CDevice + "SGP40Component", + sensor.Sensor, + cg.PollingComponent, + sensirion_common.SensirionI2CDevice, ) CONF_COMPENSATION = "compensation" CONF_HUMIDITY_SOURCE = "humidity_source" -CONF_TEMPERATURE_SOURCE = "temperature_source" -CONF_STORE_BASELINE = "store_baseline" CONF_VOC_BASELINE = "voc_baseline" CONFIG_SCHEMA = ( diff --git a/esphome/components/sgp40/sgp40.cpp b/esphome/components/sgp40/sgp40.cpp index 829c00a218..9d78572b50 100644 --- a/esphome/components/sgp40/sgp40.cpp +++ b/esphome/components/sgp40/sgp40.cpp @@ -12,14 +12,14 @@ void SGP40Component::setup() { ESP_LOGCONFIG(TAG, "Setting up SGP40..."); // Serial Number identification - if (!this->write_command_(SGP40_CMD_GET_SERIAL_ID)) { + if (!this->write_command(SGP40_CMD_GET_SERIAL_ID)) { this->error_code_ = COMMUNICATION_FAILED; this->mark_failed(); return; } uint16_t raw_serial_number[3]; - if (!this->read_data_(raw_serial_number, 3)) { + if (!this->read_data(raw_serial_number, 3)) { this->mark_failed(); return; } @@ -28,19 +28,19 @@ void SGP40Component::setup() { ESP_LOGD(TAG, "Serial Number: %" PRIu64, this->serial_number_); // Featureset identification for future use - if (!this->write_command_(SGP40_CMD_GET_FEATURESET)) { + if (!this->write_command(SGP40_CMD_GET_FEATURESET)) { ESP_LOGD(TAG, "raw_featureset write_command_ failed"); this->mark_failed(); return; } - uint16_t raw_featureset[1]; - if (!this->read_data_(raw_featureset, 1)) { + uint16_t raw_featureset; + if (!this->read_data(raw_featureset)) { ESP_LOGD(TAG, "raw_featureset read_data_ failed"); this->mark_failed(); return; } - this->featureset_ = raw_featureset[0]; + this->featureset_ = raw_featureset; if ((this->featureset_ & 0x1FF) != SGP40_FEATURESET) { ESP_LOGD(TAG, "Product feature set failed 0x%0X , expecting 0x%0X", uint16_t(this->featureset_ & 0x1FF), SGP40_FEATURESET); @@ -95,21 +95,21 @@ void SGP40Component::setup() { void SGP40Component::self_test_() { ESP_LOGD(TAG, "Self-test started"); - if (!this->write_command_(SGP40_CMD_SELF_TEST)) { + if (!this->write_command(SGP40_CMD_SELF_TEST)) { this->error_code_ = COMMUNICATION_FAILED; ESP_LOGD(TAG, "Self-test communication failed"); this->mark_failed(); } this->set_timeout(250, [this]() { - uint16_t reply[1]; - if (!this->read_data_(reply, 1)) { + uint16_t reply; + if (!this->read_data(reply)) { ESP_LOGD(TAG, "Self-test read_data_ failed"); this->mark_failed(); return; } - if (reply[0] == 0xD400) { + if (reply == 0xD400) { this->self_test_complete_ = true; ESP_LOGD(TAG, "Self-test completed"); return; @@ -192,51 +192,28 @@ uint16_t SGP40Component::measure_raw_() { temperature = 25; } - uint8_t command[8]; - - command[0] = 0x26; - command[1] = 0x0F; - + uint16_t data[2]; uint16_t rhticks = llround((uint16_t)((humidity * 65535) / 100)); - command[2] = rhticks >> 8; - command[3] = rhticks & 0xFF; - command[4] = generate_crc_(command + 2, 2); uint16_t tempticks = (uint16_t)(((temperature + 45) * 65535) / 175); - command[5] = tempticks >> 8; - command[6] = tempticks & 0xFF; - command[7] = generate_crc_(command + 5, 2); + // first paramater is the relative humidity ticks + data[0] = rhticks; + // second paramater is the temperature ticks + data[1] = tempticks; - if (this->write(command, 8) != i2c::ERROR_OK) { + if (!this->write_command(SGP40_CMD_MEASURE_RAW, data, 2)) { this->status_set_warning(); - ESP_LOGD(TAG, "write error"); - return UINT16_MAX; + ESP_LOGD(TAG, "write error (%d)", this->last_error_); + return false; } delay(30); - uint16_t raw_data[1]; - if (!this->read_data_(raw_data, 1)) { + uint16_t raw_data; + if (!this->read_data(raw_data)) { this->status_set_warning(); ESP_LOGD(TAG, "read_data_ error"); return UINT16_MAX; } - return raw_data[0]; -} - -uint8_t SGP40Component::generate_crc_(const uint8_t *data, uint8_t datalen) { - // calculates 8-Bit checksum with given polynomial - uint8_t crc = SGP40_CRC8_INIT; - - for (uint8_t i = 0; i < datalen; i++) { - crc ^= data[i]; - for (uint8_t b = 0; b < 8; b++) { - if (crc & 0x80) { - crc = (crc << 1) ^ SGP40_CRC8_POLYNOMIAL; - } else { - crc <<= 1; - } - } - } - return crc; + return raw_data; } void SGP40Component::update_voc_index() { @@ -293,56 +270,5 @@ void SGP40Component::dump_config() { } } -bool SGP40Component::write_command_(uint16_t command) { - // Warning ugly, trick the I2Ccomponent base by setting register to the first 8 bit. - return this->write_byte(command >> 8, command & 0xFF); -} - -uint8_t SGP40Component::sht_crc_(uint8_t data1, uint8_t data2) { - uint8_t bit; - uint8_t crc = 0xFF; - - crc ^= data1; - for (bit = 8; bit > 0; --bit) { - if (crc & 0x80) { - crc = (crc << 1) ^ 0x131; - } else { - crc = (crc << 1); - } - } - - crc ^= data2; - for (bit = 8; bit > 0; --bit) { - if (crc & 0x80) { - crc = (crc << 1) ^ 0x131; - } else { - crc = (crc << 1); - } - } - - return crc; -} - -bool SGP40Component::read_data_(uint16_t *data, uint8_t len) { - const uint8_t num_bytes = len * 3; - std::vector buf(num_bytes); - - if (this->read(buf.data(), num_bytes) != i2c::ERROR_OK) { - return false; - } - - for (uint8_t i = 0; i < len; i++) { - const uint8_t j = 3 * i; - uint8_t crc = sht_crc_(buf[j], buf[j + 1]); - if (crc != buf[j + 2]) { - ESP_LOGE(TAG, "CRC8 Checksum invalid! 0x%02X != 0x%02X", buf[j + 2], crc); - return false; - } - data[i] = (buf[j] << 8) | buf[j + 1]; - } - - return true; -} - } // namespace sgp40 } // namespace esphome diff --git a/esphome/components/sgp40/sgp40.h b/esphome/components/sgp40/sgp40.h index c854b21060..c5b7d2dfa0 100644 --- a/esphome/components/sgp40/sgp40.h +++ b/esphome/components/sgp40/sgp40.h @@ -2,7 +2,7 @@ #include "esphome/core/component.h" #include "esphome/components/sensor/sensor.h" -#include "esphome/components/i2c/i2c.h" +#include "esphome/components/sensirion_common/i2c_sensirion.h" #include "esphome/core/application.h" #include "esphome/core/preferences.h" #include "sensirion_voc_algorithm.h" @@ -28,6 +28,7 @@ static const uint8_t SGP40_WORD_LEN = 2; ///< 2 bytes per word static const uint16_t SGP40_CMD_GET_SERIAL_ID = 0x3682; static const uint16_t SGP40_CMD_GET_FEATURESET = 0x202f; static const uint16_t SGP40_CMD_SELF_TEST = 0x280e; +static const uint16_t SGP40_CMD_MEASURE_RAW = 0x260F; // Shortest time interval of 3H for storing baseline values. // Prevents wear of the flash because of too many write operations @@ -39,7 +40,7 @@ const uint32_t MAXIMUM_STORAGE_DIFF = 50; class SGP40Component; /// This class implements support for the Sensirion sgp40 i2c GAS (VOC) sensors. -class SGP40Component : public PollingComponent, public sensor::Sensor, public i2c::I2CDevice { +class SGP40Component : public PollingComponent, public sensor::Sensor, public sensirion_common::SensirionI2CDevice { public: void set_humidity_sensor(sensor::Sensor *humidity) { humidity_sensor_ = humidity; } void set_temperature_sensor(sensor::Sensor *temperature) { temperature_sensor_ = temperature; } @@ -55,11 +56,8 @@ class SGP40Component : public PollingComponent, public sensor::Sensor, public i2 /// Input sensor for humidity and temperature compensation. sensor::Sensor *humidity_sensor_{nullptr}; sensor::Sensor *temperature_sensor_{nullptr}; - bool write_command_(uint16_t command); - bool read_data_(uint16_t *data, uint8_t len); int16_t sensirion_init_sensors_(); int16_t sgp40_probe_(); - uint8_t sht_crc_(uint8_t data1, uint8_t data2); uint64_t serial_number_; uint16_t featureset_; int32_t measure_voc_index_(); diff --git a/esphome/components/sht3xd/sensor.py b/esphome/components/sht3xd/sensor.py index b9e7bce733..8e1ef426ad 100644 --- a/esphome/components/sht3xd/sensor.py +++ b/esphome/components/sht3xd/sensor.py @@ -1,6 +1,6 @@ import esphome.codegen as cg import esphome.config_validation as cv -from esphome.components import i2c, sensor +from esphome.components import i2c, sensor, sensirion_common from esphome.const import ( CONF_HUMIDITY, CONF_ID, @@ -13,10 +13,11 @@ from esphome.const import ( ) DEPENDENCIES = ["i2c"] +AUTO_LOAD = ["sensirion_common"] sht3xd_ns = cg.esphome_ns.namespace("sht3xd") SHT3XDComponent = sht3xd_ns.class_( - "SHT3XDComponent", cg.PollingComponent, i2c.I2CDevice + "SHT3XDComponent", cg.PollingComponent, sensirion_common.SensirionI2CDevice ) CONFIG_SCHEMA = ( diff --git a/esphome/components/sht3xd/sht3xd.cpp b/esphome/components/sht3xd/sht3xd.cpp index e7981b64cf..4e1c9742bc 100644 --- a/esphome/components/sht3xd/sht3xd.cpp +++ b/esphome/components/sht3xd/sht3xd.cpp @@ -17,13 +17,8 @@ static const uint16_t SHT3XD_COMMAND_FETCH_DATA = 0xE000; void SHT3XDComponent::setup() { ESP_LOGCONFIG(TAG, "Setting up SHT3xD..."); - if (!this->write_command_(SHT3XD_COMMAND_READ_SERIAL_NUMBER)) { - this->mark_failed(); - return; - } - uint16_t raw_serial_number[2]; - if (!this->read_data_(raw_serial_number, 2)) { + if (!this->get_register(SHT3XD_COMMAND_READ_SERIAL_NUMBER, raw_serial_number, 2)) { this->mark_failed(); return; } @@ -45,16 +40,16 @@ float SHT3XDComponent::get_setup_priority() const { return setup_priority::DATA; void SHT3XDComponent::update() { if (this->status_has_warning()) { ESP_LOGD(TAG, "Retrying to reconnect the sensor."); - this->write_command_(SHT3XD_COMMAND_SOFT_RESET); + this->write_command(SHT3XD_COMMAND_SOFT_RESET); } - if (!this->write_command_(SHT3XD_COMMAND_POLLING_H)) { + if (!this->write_command(SHT3XD_COMMAND_POLLING_H)) { this->status_set_warning(); return; } this->set_timeout(50, [this]() { uint16_t raw_data[2]; - if (!this->read_data_(raw_data, 2)) { + if (!this->read_data(raw_data, 2)) { this->status_set_warning(); return; } @@ -71,56 +66,5 @@ void SHT3XDComponent::update() { }); } -bool SHT3XDComponent::write_command_(uint16_t command) { - // Warning ugly, trick the I2Ccomponent base by setting register to the first 8 bit. - return this->write_byte(command >> 8, command & 0xFF); -} - -uint8_t sht_crc(uint8_t data1, uint8_t data2) { - uint8_t bit; - uint8_t crc = 0xFF; - - crc ^= data1; - for (bit = 8; bit > 0; --bit) { - if (crc & 0x80) { - crc = (crc << 1) ^ 0x131; - } else { - crc = (crc << 1); - } - } - - crc ^= data2; - for (bit = 8; bit > 0; --bit) { - if (crc & 0x80) { - crc = (crc << 1) ^ 0x131; - } else { - crc = (crc << 1); - } - } - - return crc; -} - -bool SHT3XDComponent::read_data_(uint16_t *data, uint8_t len) { - const uint8_t num_bytes = len * 3; - std::vector buf(num_bytes); - - if (this->read(buf.data(), num_bytes) != i2c::ERROR_OK) { - return false; - } - - for (uint8_t i = 0; i < len; i++) { - const uint8_t j = 3 * i; - uint8_t crc = sht_crc(buf[j], buf[j + 1]); - if (crc != buf[j + 2]) { - ESP_LOGE(TAG, "CRC8 Checksum invalid! 0x%02X != 0x%02X", buf[j + 2], crc); - return false; - } - data[i] = (buf[j] << 8) | buf[j + 1]; - } - - return true; -} - } // namespace sht3xd } // namespace esphome diff --git a/esphome/components/sht3xd/sht3xd.h b/esphome/components/sht3xd/sht3xd.h index 709f8aebe7..3164aa0687 100644 --- a/esphome/components/sht3xd/sht3xd.h +++ b/esphome/components/sht3xd/sht3xd.h @@ -2,13 +2,13 @@ #include "esphome/core/component.h" #include "esphome/components/sensor/sensor.h" -#include "esphome/components/i2c/i2c.h" +#include "esphome/components/sensirion_common/i2c_sensirion.h" namespace esphome { namespace sht3xd { /// This class implements support for the SHT3x-DIS family of temperature+humidity i2c sensors. -class SHT3XDComponent : public PollingComponent, public i2c::I2CDevice { +class SHT3XDComponent : public PollingComponent, public sensirion_common::SensirionI2CDevice { public: void set_temperature_sensor(sensor::Sensor *temperature_sensor) { temperature_sensor_ = temperature_sensor; } void set_humidity_sensor(sensor::Sensor *humidity_sensor) { humidity_sensor_ = humidity_sensor; } @@ -19,9 +19,6 @@ class SHT3XDComponent : public PollingComponent, public i2c::I2CDevice { void update() override; protected: - bool write_command_(uint16_t command); - bool read_data_(uint16_t *data, uint8_t len); - sensor::Sensor *temperature_sensor_; sensor::Sensor *humidity_sensor_; }; diff --git a/esphome/components/sht4x/sensor.py b/esphome/components/sht4x/sensor.py index a66ca1a526..9fb8fc969e 100644 --- a/esphome/components/sht4x/sensor.py +++ b/esphome/components/sht4x/sensor.py @@ -1,6 +1,6 @@ import esphome.codegen as cg import esphome.config_validation as cv -from esphome.components import i2c, sensor +from esphome.components import i2c, sensor, sensirion_common from esphome.const import ( CONF_ID, CONF_TEMPERATURE, @@ -16,10 +16,13 @@ from esphome.const import ( CODEOWNERS = ["@sjtrny"] DEPENDENCIES = ["i2c"] +AUTO_LOAD = ["sensirion_common"] sht4x_ns = cg.esphome_ns.namespace("sht4x") -SHT4XComponent = sht4x_ns.class_("SHT4XComponent", cg.PollingComponent, i2c.I2CDevice) +SHT4XComponent = sht4x_ns.class_( + "SHT4XComponent", cg.PollingComponent, sensirion_common.SensirionI2CDevice +) CONF_PRECISION = "precision" SHT4XPRECISION = sht4x_ns.enum("SHT4XPRECISION") diff --git a/esphome/components/sht4x/sht4x.cpp b/esphome/components/sht4x/sht4x.cpp index 248f32c4de..bdc3e62d2f 100644 --- a/esphome/components/sht4x/sht4x.cpp +++ b/esphome/components/sht4x/sht4x.cpp @@ -50,31 +50,28 @@ void SHT4XComponent::setup() { void SHT4XComponent::dump_config() { LOG_I2C_DEVICE(this); } void SHT4XComponent::update() { - uint8_t cmd[] = {MEASURECOMMANDS[this->precision_]}; - // Send command - this->write(cmd, 1); + this->write_command(MEASURECOMMANDS[this->precision_]); this->set_timeout(10, [this]() { - const uint8_t num_bytes = 6; - uint8_t buffer[num_bytes]; + uint16_t buffer[2]; // Read measurement - bool read_status = this->read_bytes_raw(buffer, num_bytes); + bool read_status = this->read_data(buffer, 2); if (read_status) { // Evaluate and publish measurements if (this->temp_sensor_ != nullptr) { - // Temp is contained in the first 16 bits - float sensor_value_temp = (buffer[0] << 8) + buffer[1]; + // Temp is contained in the first result word + float sensor_value_temp = buffer[0]; float temp = -45 + 175 * sensor_value_temp / 65535; this->temp_sensor_->publish_state(temp); } if (this->humidity_sensor_ != nullptr) { - // Relative humidity is in the last 16 bits - float sensor_value_rh = (buffer[3] << 8) + buffer[4]; + // Relative humidity is in the second result word + float sensor_value_rh = buffer[1]; float rh = -6 + 125 * sensor_value_rh / 65535; this->humidity_sensor_->publish_state(rh); diff --git a/esphome/components/sht4x/sht4x.h b/esphome/components/sht4x/sht4x.h index 8694bd9879..01553d5c15 100644 --- a/esphome/components/sht4x/sht4x.h +++ b/esphome/components/sht4x/sht4x.h @@ -2,7 +2,7 @@ #include "esphome/core/component.h" #include "esphome/components/sensor/sensor.h" -#include "esphome/components/i2c/i2c.h" +#include "esphome/components/sensirion_common/i2c_sensirion.h" namespace esphome { namespace sht4x { @@ -13,7 +13,7 @@ enum SHT4XHEATERPOWER { SHT4X_HEATERPOWER_HIGH, SHT4X_HEATERPOWER_MED, SHT4X_HEA enum SHT4XHEATERTIME { SHT4X_HEATERTIME_LONG = 1100, SHT4X_HEATERTIME_SHORT = 110 }; -class SHT4XComponent : public PollingComponent, public i2c::I2CDevice { +class SHT4XComponent : public PollingComponent, public sensirion_common::SensirionI2CDevice { public: float get_setup_priority() const override { return setup_priority::DATA; } void setup() override; diff --git a/esphome/components/shtcx/sensor.py b/esphome/components/shtcx/sensor.py index ba2283a9b4..c8b56cfe30 100644 --- a/esphome/components/shtcx/sensor.py +++ b/esphome/components/shtcx/sensor.py @@ -1,6 +1,6 @@ import esphome.codegen as cg import esphome.config_validation as cv -from esphome.components import i2c, sensor +from esphome.components import i2c, sensor, sensirion_common from esphome.const import ( CONF_HUMIDITY, CONF_ID, @@ -13,9 +13,12 @@ from esphome.const import ( ) DEPENDENCIES = ["i2c"] +AUTO_LOAD = ["sensirion_common"] shtcx_ns = cg.esphome_ns.namespace("shtcx") -SHTCXComponent = shtcx_ns.class_("SHTCXComponent", cg.PollingComponent, i2c.I2CDevice) +SHTCXComponent = shtcx_ns.class_( + "SHTCXComponent", cg.PollingComponent, sensirion_common.SensirionI2CDevice +) SHTCXType = shtcx_ns.enum("SHTCXType") diff --git a/esphome/components/shtcx/shtcx.cpp b/esphome/components/shtcx/shtcx.cpp index 4112270c02..0de56a8044 100644 --- a/esphome/components/shtcx/shtcx.cpp +++ b/esphome/components/shtcx/shtcx.cpp @@ -29,14 +29,14 @@ void SHTCXComponent::setup() { this->wake_up(); this->soft_reset(); - if (!this->write_command_(SHTCX_COMMAND_READ_ID_REGISTER)) { + if (!this->write_command(SHTCX_COMMAND_READ_ID_REGISTER)) { ESP_LOGE(TAG, "Error requesting Device ID"); this->mark_failed(); return; } uint16_t device_id_register; - if (!this->read_data_(&device_id_register, 1)) { + if (!this->read_data(&device_id_register, 1)) { ESP_LOGE(TAG, "Error reading Device ID"); this->mark_failed(); return; @@ -76,7 +76,7 @@ void SHTCXComponent::update() { if (this->type_ != SHTCX_TYPE_SHTC1) { this->wake_up(); } - if (!this->write_command_(SHTCX_COMMAND_POLLING_H)) { + if (!this->write_command(SHTCX_COMMAND_POLLING_H)) { ESP_LOGE(TAG, "sensor polling failed"); if (this->temperature_sensor_ != nullptr) this->temperature_sensor_->publish_state(NAN); @@ -90,7 +90,7 @@ void SHTCXComponent::update() { float temperature = NAN; float humidity = NAN; uint16_t raw_data[2]; - if (!this->read_data_(raw_data, 2)) { + if (!this->read_data(raw_data, 2)) { ESP_LOGE(TAG, "sensor read failed"); this->status_set_warning(); } else { @@ -110,65 +110,14 @@ void SHTCXComponent::update() { }); } -bool SHTCXComponent::write_command_(uint16_t command) { - // Warning ugly, trick the I2Ccomponent base by setting register to the first 8 bit. - return this->write_byte(command >> 8, command & 0xFF); -} - -uint8_t sht_crc(uint8_t data1, uint8_t data2) { - uint8_t bit; - uint8_t crc = 0xFF; - - crc ^= data1; - for (bit = 8; bit > 0; --bit) { - if (crc & 0x80) { - crc = (crc << 1) ^ 0x131; - } else { - crc = (crc << 1); - } - } - - crc ^= data2; - for (bit = 8; bit > 0; --bit) { - if (crc & 0x80) { - crc = (crc << 1) ^ 0x131; - } else { - crc = (crc << 1); - } - } - - return crc; -} - -bool SHTCXComponent::read_data_(uint16_t *data, uint8_t len) { - const uint8_t num_bytes = len * 3; - std::vector buf(num_bytes); - - if (this->read(buf.data(), num_bytes) != i2c::ERROR_OK) { - return false; - } - - for (uint8_t i = 0; i < len; i++) { - const uint8_t j = 3 * i; - uint8_t crc = sht_crc(buf[j], buf[j + 1]); - if (crc != buf[j + 2]) { - ESP_LOGE(TAG, "CRC8 Checksum invalid! 0x%02X != 0x%02X", buf[j + 2], crc); - return false; - } - data[i] = (buf[j] << 8) | buf[j + 1]; - } - - return true; -} - void SHTCXComponent::soft_reset() { - this->write_command_(SHTCX_COMMAND_SOFT_RESET); + this->write_command(SHTCX_COMMAND_SOFT_RESET); delayMicroseconds(200); } -void SHTCXComponent::sleep() { this->write_command_(SHTCX_COMMAND_SLEEP); } +void SHTCXComponent::sleep() { this->write_command(SHTCX_COMMAND_SLEEP); } void SHTCXComponent::wake_up() { - this->write_command_(SHTCX_COMMAND_WAKEUP); + this->write_command(SHTCX_COMMAND_WAKEUP); delayMicroseconds(200); } diff --git a/esphome/components/shtcx/shtcx.h b/esphome/components/shtcx/shtcx.h index cb2b46d348..c44fb9d9c1 100644 --- a/esphome/components/shtcx/shtcx.h +++ b/esphome/components/shtcx/shtcx.h @@ -2,7 +2,7 @@ #include "esphome/core/component.h" #include "esphome/components/sensor/sensor.h" -#include "esphome/components/i2c/i2c.h" +#include "esphome/components/sensirion_common/i2c_sensirion.h" namespace esphome { namespace shtcx { @@ -10,7 +10,7 @@ namespace shtcx { enum SHTCXType { SHTCX_TYPE_SHTC3 = 0, SHTCX_TYPE_SHTC1, SHTCX_TYPE_UNKNOWN }; /// This class implements support for the SHT3x-DIS family of temperature+humidity i2c sensors. -class SHTCXComponent : public PollingComponent, public i2c::I2CDevice { +class SHTCXComponent : public PollingComponent, public sensirion_common::SensirionI2CDevice { public: void set_temperature_sensor(sensor::Sensor *temperature_sensor) { temperature_sensor_ = temperature_sensor; } void set_humidity_sensor(sensor::Sensor *humidity_sensor) { humidity_sensor_ = humidity_sensor; } @@ -24,8 +24,6 @@ class SHTCXComponent : public PollingComponent, public i2c::I2CDevice { void wake_up(); protected: - bool write_command_(uint16_t command); - bool read_data_(uint16_t *data, uint8_t len); SHTCXType type_; uint16_t sensor_id_; sensor::Sensor *temperature_sensor_; diff --git a/esphome/components/sps30/sensor.py b/esphome/components/sps30/sensor.py index 27264cf942..89cb25c24f 100644 --- a/esphome/components/sps30/sensor.py +++ b/esphome/components/sps30/sensor.py @@ -1,6 +1,6 @@ import esphome.codegen as cg import esphome.config_validation as cv -from esphome.components import i2c, sensor +from esphome.components import i2c, sensor, sensirion_common from esphome.const import ( CONF_ID, CONF_PM_1_0, @@ -26,9 +26,12 @@ from esphome.const import ( ) DEPENDENCIES = ["i2c"] +AUTO_LOAD = ["sensirion_common"] sps30_ns = cg.esphome_ns.namespace("sps30") -SPS30Component = sps30_ns.class_("SPS30Component", cg.PollingComponent, i2c.I2CDevice) +SPS30Component = sps30_ns.class_( + "SPS30Component", cg.PollingComponent, sensirion_common.SensirionI2CDevice +) CONFIG_SCHEMA = ( cv.Schema( diff --git a/esphome/components/sps30/sps30.cpp b/esphome/components/sps30/sps30.cpp index 2bd7bcb458..2885125a8a 100644 --- a/esphome/components/sps30/sps30.cpp +++ b/esphome/components/sps30/sps30.cpp @@ -22,30 +22,18 @@ static const uint8_t MAX_SKIPPED_DATA_CYCLES_BEFORE_ERROR = 5; void SPS30Component::setup() { ESP_LOGCONFIG(TAG, "Setting up sps30..."); - this->write_command_(SPS30_CMD_SOFT_RESET); + this->write_command(SPS30_CMD_SOFT_RESET); /// Deferred Sensor initialization this->set_timeout(500, [this]() { /// Firmware version identification - if (!this->write_command_(SPS30_CMD_GET_FIRMWARE_VERSION)) { - this->error_code_ = FIRMWARE_VERSION_REQUEST_FAILED; - this->mark_failed(); - return; - } - - if (!this->read_data_(&raw_firmware_version_, 1)) { + if (!this->get_register(SPS30_CMD_GET_FIRMWARE_VERSION, raw_firmware_version_, 1)) { this->error_code_ = FIRMWARE_VERSION_READ_FAILED; this->mark_failed(); return; } /// Serial number identification - if (!this->write_command_(SPS30_CMD_GET_SERIAL_NUMBER)) { - this->error_code_ = SERIAL_NUMBER_REQUEST_FAILED; - this->mark_failed(); - return; - } - uint16_t raw_serial_number[8]; - if (!this->read_data_(raw_serial_number, 8)) { + if (!this->get_register(SPS30_CMD_GET_SERIAL_NUMBER, raw_serial_number, 8, 1)) { this->error_code_ = SERIAL_NUMBER_READ_FAILED; this->mark_failed(); return; @@ -109,7 +97,7 @@ void SPS30Component::update() { /// Check if warning flag active (sensor reconnected?) if (this->status_has_warning()) { ESP_LOGD(TAG, "Trying to reconnect the sensor..."); - if (this->write_command_(SPS30_CMD_SOFT_RESET)) { + if (this->write_command(SPS30_CMD_SOFT_RESET)) { ESP_LOGD(TAG, "Sensor has soft-reset successfully. Waiting for reconnection in 500ms..."); this->set_timeout(500, [this]() { this->start_continuous_measurement_(); @@ -124,13 +112,13 @@ void SPS30Component::update() { return; } /// Check if measurement is ready before reading the value - if (!this->write_command_(SPS30_CMD_GET_DATA_READY_STATUS)) { + if (!this->write_command(SPS30_CMD_GET_DATA_READY_STATUS)) { this->status_set_warning(); return; } uint16_t raw_read_status; - if (!this->read_data_(&raw_read_status, 1) || raw_read_status == 0x00) { + if (!this->read_data(&raw_read_status, 1) || raw_read_status == 0x00) { ESP_LOGD(TAG, "Sensor measurement not ready yet."); this->skipped_data_read_cycles_++; /// The following logic is required to address the cases when a sensor is quickly replaced before it's marked @@ -142,7 +130,7 @@ void SPS30Component::update() { return; } - if (!this->write_command_(SPS30_CMD_READ_MEASUREMENT)) { + if (!this->write_command(SPS30_CMD_READ_MEASUREMENT)) { ESP_LOGW(TAG, "Error reading measurement status!"); this->status_set_warning(); return; @@ -150,7 +138,7 @@ void SPS30Component::update() { this->set_timeout(50, [this]() { uint16_t raw_data[20]; - if (!this->read_data_(raw_data, 20)) { + if (!this->read_data(raw_data, 20)) { ESP_LOGW(TAG, "Error reading measurement data!"); this->status_set_warning(); return; @@ -205,69 +193,18 @@ void SPS30Component::update() { }); } -bool SPS30Component::write_command_(uint16_t command) { - // Warning ugly, trick the I2Ccomponent base by setting register to the first 8 bit. - return this->write_byte(command >> 8, command & 0xFF); -} - -uint8_t SPS30Component::sht_crc_(uint8_t data1, uint8_t data2) { - uint8_t bit; - uint8_t crc = 0xFF; - - crc ^= data1; - for (bit = 8; bit > 0; --bit) { - if (crc & 0x80) { - crc = (crc << 1) ^ 0x131; - } else { - crc = (crc << 1); - } - } - - crc ^= data2; - for (bit = 8; bit > 0; --bit) { - if (crc & 0x80) { - crc = (crc << 1) ^ 0x131; - } else { - crc = (crc << 1); - } - } - - return crc; -} - bool SPS30Component::start_continuous_measurement_() { uint8_t data[4]; data[0] = SPS30_CMD_START_CONTINUOUS_MEASUREMENTS & 0xFF; data[1] = 0x03; data[2] = 0x00; data[3] = sht_crc_(0x03, 0x00); - if (!this->write_bytes(SPS30_CMD_START_CONTINUOUS_MEASUREMENTS >> 8, data, 4)) { + if (!this->write_command(SPS30_CMD_START_CONTINUOUS_MEASUREMENTS, SPS30_CMD_START_CONTINUOUS_MEASUREMENTS_ARG)) { ESP_LOGE(TAG, "Error initiating measurements"); return false; } return true; } -bool SPS30Component::read_data_(uint16_t *data, uint8_t len) { - const uint8_t num_bytes = len * 3; - std::vector buf(num_bytes); - - if (this->read(buf.data(), num_bytes) != i2c::ERROR_OK) { - return false; - } - - for (uint8_t i = 0; i < len; i++) { - const uint8_t j = 3 * i; - uint8_t crc = sht_crc_(buf[j], buf[j + 1]); - if (crc != buf[j + 2]) { - ESP_LOGE(TAG, "CRC8 Checksum invalid! 0x%02X != 0x%02X", buf[j + 2], crc); - return false; - } - data[i] = (buf[j] << 8) | buf[j + 1]; - } - - return true; -} - } // namespace sps30 } // namespace esphome diff --git a/esphome/components/sps30/sps30.h b/esphome/components/sps30/sps30.h index bae33a46e1..9a93df8597 100644 --- a/esphome/components/sps30/sps30.h +++ b/esphome/components/sps30/sps30.h @@ -2,14 +2,14 @@ #include "esphome/core/component.h" #include "esphome/components/sensor/sensor.h" -#include "esphome/components/i2c/i2c.h" +#include "esphome/components/sensirion_common/i2c_sensirion.h" namespace esphome { namespace sps30 { /// This class implements support for the Sensirion SPS30 i2c/UART Particulate Matter /// PM1.0, PM2.5, PM4, PM10 Air Quality sensors. -class SPS30Component : public PollingComponent, public i2c::I2CDevice { +class SPS30Component : public PollingComponent, public sensirion_common::SensirionI2CDevice { public: void set_pm_1_0_sensor(sensor::Sensor *pm_1_0) { pm_1_0_sensor_ = pm_1_0; } void set_pm_2_5_sensor(sensor::Sensor *pm_2_5) { pm_2_5_sensor_ = pm_2_5; } @@ -29,9 +29,6 @@ class SPS30Component : public PollingComponent, public i2c::I2CDevice { float get_setup_priority() const override { return setup_priority::DATA; } protected: - bool write_command_(uint16_t command); - bool read_data_(uint16_t *data, uint8_t len); - uint8_t sht_crc_(uint8_t data1, uint8_t data2); char serial_number_[17] = {0}; /// Terminating NULL character uint16_t raw_firmware_version_; bool start_continuous_measurement_(); diff --git a/esphome/components/sts3x/sensor.py b/esphome/components/sts3x/sensor.py index aa7573aaf2..a99503a2b8 100644 --- a/esphome/components/sts3x/sensor.py +++ b/esphome/components/sts3x/sensor.py @@ -8,6 +8,7 @@ from esphome.const import ( ) DEPENDENCIES = ["i2c"] +AUTO_LOAD = ["sensirion_common"] sts3x_ns = cg.esphome_ns.namespace("sts3x") diff --git a/esphome/components/sts3x/sts3x.cpp b/esphome/components/sts3x/sts3x.cpp index ce166f2055..5af808b6e7 100644 --- a/esphome/components/sts3x/sts3x.cpp +++ b/esphome/components/sts3x/sts3x.cpp @@ -19,13 +19,13 @@ static const uint16_t STS3X_COMMAND_FETCH_DATA = 0xE000; void STS3XComponent::setup() { ESP_LOGCONFIG(TAG, "Setting up STS3x..."); - if (!this->write_command_(STS3X_COMMAND_READ_SERIAL_NUMBER)) { + if (!this->write_command(STS3X_COMMAND_READ_SERIAL_NUMBER)) { this->mark_failed(); return; } uint16_t raw_serial_number[2]; - if (!this->read_data_(raw_serial_number, 1)) { + if (!this->read_data(raw_serial_number, 1)) { this->mark_failed(); return; } @@ -46,16 +46,16 @@ float STS3XComponent::get_setup_priority() const { return setup_priority::DATA; void STS3XComponent::update() { if (this->status_has_warning()) { ESP_LOGD(TAG, "Retrying to reconnect the sensor."); - this->write_command_(STS3X_COMMAND_SOFT_RESET); + this->write_command(STS3X_COMMAND_SOFT_RESET); } - if (!this->write_command_(STS3X_COMMAND_POLLING_H)) { + if (!this->write_command(STS3X_COMMAND_POLLING_H)) { this->status_set_warning(); return; } this->set_timeout(50, [this]() { uint16_t raw_data[1]; - if (!this->read_data_(raw_data, 1)) { + if (!this->read_data(raw_data, 1)) { this->status_set_warning(); return; } @@ -67,56 +67,5 @@ void STS3XComponent::update() { }); } -bool STS3XComponent::write_command_(uint16_t command) { - // Warning ugly, trick the I2Ccomponent base by setting register to the first 8 bit. - return this->write_byte(command >> 8, command & 0xFF); -} - -uint8_t sts3x_crc(uint8_t data1, uint8_t data2) { - uint8_t bit; - uint8_t crc = 0xFF; - - crc ^= data1; - for (bit = 8; bit > 0; --bit) { - if (crc & 0x80) { - crc = (crc << 1) ^ 0x131; - } else { - crc = (crc << 1); - } - } - - crc ^= data2; - for (bit = 8; bit > 0; --bit) { - if (crc & 0x80) { - crc = (crc << 1) ^ 0x131; - } else { - crc = (crc << 1); - } - } - - return crc; -} - -bool STS3XComponent::read_data_(uint16_t *data, uint8_t len) { - const uint8_t num_bytes = len * 3; - std::vector buf(num_bytes); - - if (this->read(buf.data(), num_bytes) != i2c::ERROR_OK) { - return false; - } - - for (uint8_t i = 0; i < len; i++) { - const uint8_t j = 3 * i; - uint8_t crc = sts3x_crc(buf[j], buf[j + 1]); - if (crc != buf[j + 2]) { - ESP_LOGE(TAG, "CRC8 Checksum invalid! 0x%02X != 0x%02X", buf[j + 2], crc); - return false; - } - data[i] = (buf[j] << 8) | buf[j + 1]; - } - - return true; -} - } // namespace sts3x } // namespace esphome diff --git a/esphome/components/sts3x/sts3x.h b/esphome/components/sts3x/sts3x.h index 436cf938d8..261033efad 100644 --- a/esphome/components/sts3x/sts3x.h +++ b/esphome/components/sts3x/sts3x.h @@ -2,22 +2,18 @@ #include "esphome/core/component.h" #include "esphome/components/sensor/sensor.h" -#include "esphome/components/i2c/i2c.h" +#include "esphome/components/sensirion_common/i2c_sensirion.h" namespace esphome { namespace sts3x { /// This class implements support for the ST3x-DIS family of temperature i2c sensors. -class STS3XComponent : public sensor::Sensor, public PollingComponent, public i2c::I2CDevice { +class STS3XComponent : public sensor::Sensor, public PollingComponent, public sensirion_common::SensirionI2CDevice { public: void setup() override; void dump_config() override; float get_setup_priority() const override; void update() override; - - protected: - bool write_command_(uint16_t command); - bool read_data_(uint16_t *data, uint8_t len); }; } // namespace sts3x diff --git a/esphome/const.py b/esphome/const.py index 46c9a659be..9096e66f4e 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -644,6 +644,7 @@ CONF_STEP_MODE = "step_mode" CONF_STEP_PIN = "step_pin" CONF_STOP = "stop" CONF_STOP_ACTION = "stop_action" +CONF_STORE_BASELINE = "store_baseline" CONF_SUBNET = "subnet" CONF_SUBSTITUTIONS = "substitutions" CONF_SUPPLEMENTAL_COOLING_ACTION = "supplemental_cooling_action" @@ -680,6 +681,7 @@ CONF_TARGET_TEMPERATURE_LOW_COMMAND_TOPIC = "target_temperature_low_command_topi CONF_TARGET_TEMPERATURE_LOW_STATE_TOPIC = "target_temperature_low_state_topic" CONF_TARGET_TEMPERATURE_STATE_TOPIC = "target_temperature_state_topic" CONF_TEMPERATURE = "temperature" +CONF_TEMPERATURE_SOURCE = "temperature_source" CONF_TEMPERATURE_STEP = "temperature_step" CONF_TEXT_SENSORS = "text_sensors" CONF_THEN = "then"