From 8fc28a52f7fb9f6243e042857bd3a976d90e8b60 Mon Sep 17 00:00:00 2001 From: Marcin Krasowski Date: Tue, 7 May 2024 17:06:33 +0200 Subject: [PATCH 1/8] enh(sen5x): add TPS & PM number concentration This change adds a new command, listed as being available in FW >0.7 New entities can be configured: - pm_n_x_x for PM number concentration in #/cm3 - tps for typical particle size --- esphome/components/sen5x/sen5x.cpp | 155 ++++++++++++++---- esphome/components/sen5x/sen5x.h | 22 ++- esphome/components/sen5x/sensor.py | 49 ++++++ tests/components/sen5x/test.esp32-c3-idf.yaml | 31 +++- tests/components/sen5x/test.esp32-c3.yaml | 31 +++- tests/components/sen5x/test.esp32-idf.yaml | 31 +++- tests/components/sen5x/test.esp32.yaml | 31 +++- tests/components/sen5x/test.esp8266.yaml | 31 +++- tests/components/sen5x/test.rp2040.yaml | 31 +++- tests/test11.5.yaml | 31 +++- tests/test5.yaml | 31 +++- 11 files changed, 411 insertions(+), 63 deletions(-) diff --git a/esphome/components/sen5x/sen5x.cpp b/esphome/components/sen5x/sen5x.cpp index 0efc96194386..3a631b4047f2 100644 --- a/esphome/components/sen5x/sen5x.cpp +++ b/esphome/components/sen5x/sen5x.cpp @@ -16,6 +16,7 @@ static const uint16_t SEN5X_CMD_GET_PRODUCT_NAME = 0xD014; static const uint16_t SEN5X_CMD_GET_SERIAL_NUMBER = 0xD033; static const uint16_t SEN5X_CMD_NOX_ALGORITHM_TUNING = 0x60E1; static const uint16_t SEN5X_CMD_READ_MEASUREMENT = 0x03C4; +static const uint16_t SEN5X_CMD_READ_PM_MEASUREMENT = 0x0413; static const uint16_t SEN5X_CMD_RHT_ACCELERATION_MODE = 0x60F7; static const uint16_t SEN5X_CMD_START_CLEANING_FAN = 0x5607; static const uint16_t SEN5X_CMD_START_MEASUREMENTS = 0x0021; @@ -126,14 +127,16 @@ void SEN5XComponent::setup() { this->nox_sensor_ = nullptr; // mark as not used } - if (!this->get_register(SEN5X_CMD_GET_FIRMWARE_VERSION, this->firmware_version_, 20)) { + uint16_t firmware_version; + if (!this->get_register(SEN5X_CMD_GET_FIRMWARE_VERSION, firmware_version, 20)) { ESP_LOGE(TAG, "Failed to read firmware version"); this->error_code_ = FIRMWARE_FAILED; this->mark_failed(); return; } - this->firmware_version_ >>= 8; - ESP_LOGD(TAG, "Firmware version %d", this->firmware_version_); + this->firmware_version_major_ = firmware_version >> 8; + this->firmware_version_minor_ = firmware_version & 0xFF; + ESP_LOGD(TAG, "Firmware version %u.%u", this->firmware_version_major_, this->firmware_version_minor_); if (this->voc_sensor_ && this->store_baseline_) { uint32_t combined_serial = @@ -215,9 +218,28 @@ void SEN5XComponent::setup() { delay(20); } + if ((this->pm_n_0_5_sensor_ || this->pm_n_1_0_sensor_ || this->pm_n_2_5_sensor_ || this->pm_n_4_0_sensor_ || + this->pm_n_10_0_sensor_ || this->pm_tps_sensor_)) { + if (this->firmware_version_major_ > 0 || this->firmware_version_minor_ > 7) { + this->get_pm_number_concentration_and_tps_ = true; + } else { + ESP_LOGE(TAG, "For number concentration and TPS, firmware >0.7 is required. You are using <%u.%u>", + this->firmware_version_major_, this->firmware_version_minor_); + this->pm_n_0_5_sensor_ = nullptr; + this->pm_n_1_0_sensor_ = nullptr; + this->pm_n_2_5_sensor_ = nullptr; + this->pm_n_4_0_sensor_ = nullptr; + this->pm_n_10_0_sensor_ = nullptr; + this->pm_tps_sensor_ = nullptr; + this->get_pm_number_concentration_and_tps_ = false; + } + } + // Finally start sensor measurements auto cmd = SEN5X_CMD_START_MEASUREMENTS_RHT_ONLY; - if (this->pm_1_0_sensor_ || this->pm_2_5_sensor_ || this->pm_4_0_sensor_ || this->pm_10_0_sensor_) { + if (this->pm_1_0_sensor_ || this->pm_2_5_sensor_ || this->pm_4_0_sensor_ || this->pm_10_0_sensor_ || + this->pm_n_0_5_sensor_ || this->pm_n_1_0_sensor_ || this->pm_n_2_5_sensor_ || this->pm_n_4_0_sensor_ || + this->pm_n_10_0_sensor_ || this->pm_tps_sensor_) { // if any of the gas sensors are active we need a full measurement cmd = SEN5X_CMD_START_MEASUREMENTS; } @@ -260,7 +282,7 @@ void SEN5XComponent::dump_config() { } } ESP_LOGCONFIG(TAG, " Productname: %s", this->product_name_.c_str()); - ESP_LOGCONFIG(TAG, " Firmware version: %d", this->firmware_version_); + ESP_LOGCONFIG(TAG, " Firmware version: %u.%u", this->firmware_version_major_, this->firmware_version_minor_); ESP_LOGCONFIG(TAG, " Serial number %02d.%02d.%02d", serial_number_[0], serial_number_[1], serial_number_[2]); if (this->auto_cleaning_interval_.has_value()) { ESP_LOGCONFIG(TAG, " Auto cleaning interval %" PRId32 " seconds", auto_cleaning_interval_.value()); @@ -283,6 +305,12 @@ void SEN5XComponent::dump_config() { LOG_SENSOR(" ", "PM 2.5", this->pm_2_5_sensor_); LOG_SENSOR(" ", "PM 4.0", this->pm_4_0_sensor_); LOG_SENSOR(" ", "PM 10.0", this->pm_10_0_sensor_); + LOG_SENSOR(" ", "PM_N 0.5", this->pm_n_0_5_sensor_); + LOG_SENSOR(" ", "PM_N 1.0", this->pm_n_1_0_sensor_); + LOG_SENSOR(" ", "PM_N 2.5", this->pm_n_2_5_sensor_); + LOG_SENSOR(" ", "PM_N 4.0", this->pm_n_4_0_sensor_); + LOG_SENSOR(" ", "PM_N 10.0", this->pm_n_10_0_sensor_); + LOG_SENSOR(" ", "PM_TPS", this->pm_tps_sensor_); LOG_SENSOR(" ", "Temperature", this->temperature_sensor_); LOG_SENSOR(" ", "Humidity", this->humidity_sensor_); LOG_SENSOR(" ", "VOC", this->voc_sensor_); // SEN54 and SEN55 only @@ -297,38 +325,104 @@ void SEN5XComponent::update() { // Store baselines after defined interval or if the difference between current and stored baseline becomes too // much if (this->store_baseline_ && this->seconds_since_last_store_ > SHORTEST_BASELINE_STORE_INTERVAL) { - if (this->write_command(SEN5X_CMD_VOC_ALGORITHM_STATE)) { - // run it a bit later to avoid adding a delay here - this->set_timeout(550, [this]() { - uint16_t states[4]; - if (this->read_data(states, 4)) { - uint32_t state0 = states[0] << 16 | states[1]; - uint32_t state1 = states[2] << 16 | states[3]; - if ((uint32_t) std::abs(static_cast(this->voc_baselines_storage_.state0 - state0)) > - MAXIMUM_STORAGE_DIFF || - (uint32_t) std::abs(static_cast(this->voc_baselines_storage_.state1 - state1)) > - MAXIMUM_STORAGE_DIFF) { - this->seconds_since_last_store_ = 0; - this->voc_baselines_storage_.state0 = state0; - this->voc_baselines_storage_.state1 = state1; - - if (this->pref_.save(&this->voc_baselines_storage_)) { - ESP_LOGI(TAG, "Stored VOC baseline state0: 0x%04" PRIX32 " ,state1: 0x%04" PRIX32, - this->voc_baselines_storage_.state0, voc_baselines_storage_.state1); - } else { - ESP_LOGW(TAG, "Could not store VOC baselines"); - } + this->write_voc_baseline_(); + } + + this->update_measured_values_(); + + if (this->get_pm_number_concentration_and_tps_) { + // delay the extra reading otherwise the device throws errors or the data is corrupted (CRC errors) + this->set_timeout(200, [this]() { this->update_measured_pm_(); }); + } +} + +void SEN5XComponent::write_voc_baseline_() { + if (this->write_command(SEN5X_CMD_VOC_ALGORITHM_STATE)) { + // run it a bit later to avoid adding a delay here + this->set_timeout(550, [this]() { + uint16_t states[4]; + if (this->read_data(states, 4)) { + uint32_t state0 = states[0] << 16 | states[1]; + uint32_t state1 = states[2] << 16 | states[3]; + if ((uint32_t) std::abs(static_cast(this->voc_baselines_storage_.state0 - state0)) > + MAXIMUM_STORAGE_DIFF || + (uint32_t) std::abs(static_cast(this->voc_baselines_storage_.state1 - state1)) > + MAXIMUM_STORAGE_DIFF) { + this->seconds_since_last_store_ = 0; + this->voc_baselines_storage_.state0 = state0; + this->voc_baselines_storage_.state1 = state1; + + if (this->pref_.save(&this->voc_baselines_storage_)) { + ESP_LOGI(TAG, "Stored VOC baseline state0: 0x%04" PRIX32 " ,state1: 0x%04" PRIX32, + this->voc_baselines_storage_.state0, voc_baselines_storage_.state1); + } else { + ESP_LOGW(TAG, "Could not store VOC baselines"); } } - }); - } + } + }); + } +} + +void SEN5XComponent::update_measured_pm_() { + if (!this->write_command(SEN5X_CMD_READ_PM_MEASUREMENT)) { + this->status_set_warning(); + ESP_LOGD(TAG, "write error read measurement (%d)", this->last_error_); + return; } + this->set_timeout(20, [this]() { + uint16_t measurements[10]; + + if (!this->read_data(measurements, 10)) { + this->status_set_warning(); + ESP_LOGD(TAG, "read data error (%d)", this->last_error_); + return; + } + + float pm_n_0_5 = measurements[4] / 10.0; + if (measurements[4] == 0xFFFF) + pm_n_0_5 = NAN; + float pm_n_1_0 = measurements[5] / 10.0; + if (measurements[5] == 0xFFFF) + pm_n_1_0 = NAN; + float pm_n_2_5 = measurements[6] / 10.0; + if (measurements[6] == 0xFFFF) + pm_n_2_5 = NAN; + float pm_n_4_0 = measurements[7] / 10.0; + if (measurements[7] == 0xFFFF) + pm_n_4_0 = NAN; + float pm_n_10_0 = measurements[8] / 10.0; + if (measurements[8] == 0xFFFF) + pm_n_10_0 = NAN; + float pm_tps = measurements[9] / 1000.0; + if (measurements[9] == 0xFFFF) + pm_tps = NAN; + + if (this->pm_n_0_5_sensor_ != nullptr) + this->pm_n_0_5_sensor_->publish_state(pm_n_0_5); + if (this->pm_n_1_0_sensor_ != nullptr) + this->pm_n_1_0_sensor_->publish_state(pm_n_1_0); + if (this->pm_n_2_5_sensor_ != nullptr) + this->pm_n_2_5_sensor_->publish_state(pm_n_2_5); + if (this->pm_n_4_0_sensor_ != nullptr) + this->pm_n_4_0_sensor_->publish_state(pm_n_4_0); + if (this->pm_n_10_0_sensor_ != nullptr) + this->pm_n_10_0_sensor_->publish_state(pm_n_10_0); + if (this->pm_tps_sensor_ != nullptr) + this->pm_tps_sensor_->publish_state(pm_tps); + + this->status_clear_warning(); + }); +} + +void SEN5XComponent::update_measured_values_() { if (!this->write_command(SEN5X_CMD_READ_MEASUREMENT)) { this->status_set_warning(); ESP_LOGD(TAG, "write error read measurement (%d)", this->last_error_); return; } + this->set_timeout(20, [this]() { uint16_t measurements[8]; @@ -337,6 +431,7 @@ void SEN5XComponent::update() { ESP_LOGD(TAG, "read data error (%d)", this->last_error_); return; } + float pm_1_0 = measurements[0] / 10.0; if (measurements[0] == 0xFFFF) pm_1_0 = NAN; @@ -350,10 +445,10 @@ void SEN5XComponent::update() { if (measurements[3] == 0xFFFF) pm_10_0 = NAN; float humidity = measurements[4] / 100.0; - if (measurements[4] == 0xFFFF) + if (measurements[4] >= 0x7FFF) humidity = NAN; float temperature = (int16_t) measurements[5] / 200.0; - if (measurements[5] == 0xFFFF) + if (measurements[5] >= 0x7FFF) temperature = NAN; float voc = measurements[6] / 10.0; if (measurements[6] == 0xFFFF) diff --git a/esphome/components/sen5x/sen5x.h b/esphome/components/sen5x/sen5x.h index 6d90636a8985..e89abef6b91c 100644 --- a/esphome/components/sen5x/sen5x.h +++ b/esphome/components/sen5x/sen5x.h @@ -60,6 +60,13 @@ class SEN5XComponent : public PollingComponent, public sensirion_common::Sensiri void set_pm_4_0_sensor(sensor::Sensor *pm_4_0) { pm_4_0_sensor_ = pm_4_0; } void set_pm_10_0_sensor(sensor::Sensor *pm_10_0) { pm_10_0_sensor_ = pm_10_0; } + void set_pm_n_0_5_sensor(sensor::Sensor *pm_n_0_5) { pm_n_0_5_sensor_ = pm_n_0_5; } + void set_pm_n_1_0_sensor(sensor::Sensor *pm_n_1_0) { pm_n_1_0_sensor_ = pm_n_1_0; } + void set_pm_n_2_5_sensor(sensor::Sensor *pm_n_2_5) { pm_n_2_5_sensor_ = pm_n_2_5; } + void set_pm_n_4_0_sensor(sensor::Sensor *pm_n_4_0) { pm_n_4_0_sensor_ = pm_n_4_0; } + void set_pm_n_10_0_sensor(sensor::Sensor *pm_n_10_0) { pm_n_10_0_sensor_ = pm_n_10_0; } + void set_pm_tps_sensor(sensor::Sensor *pm_tps) { pm_tps_sensor_ = pm_tps; } + void set_voc_sensor(sensor::Sensor *voc_sensor) { voc_sensor_ = voc_sensor; } void set_nox_sensor(sensor::Sensor *nox_sensor) { nox_sensor_ = nox_sensor; } void set_humidity_sensor(sensor::Sensor *humidity_sensor) { humidity_sensor_ = humidity_sensor; } @@ -103,12 +110,22 @@ class SEN5XComponent : public PollingComponent, public sensirion_common::Sensiri protected: bool write_tuning_parameters_(uint16_t i2c_command, const GasTuning &tuning); bool write_temperature_compensation_(const TemperatureCompensation &compensation); + void write_voc_baseline_(); + void update_measured_values_(); + void update_measured_pm_(); ERRORCODE error_code_; bool initialized_{false}; sensor::Sensor *pm_1_0_sensor_{nullptr}; sensor::Sensor *pm_2_5_sensor_{nullptr}; sensor::Sensor *pm_4_0_sensor_{nullptr}; sensor::Sensor *pm_10_0_sensor_{nullptr}; + // Firmware >0.7 only + sensor::Sensor *pm_n_0_5_sensor_{nullptr}; + sensor::Sensor *pm_n_1_0_sensor_{nullptr}; + sensor::Sensor *pm_n_2_5_sensor_{nullptr}; + sensor::Sensor *pm_n_4_0_sensor_{nullptr}; + sensor::Sensor *pm_n_10_0_sensor_{nullptr}; + sensor::Sensor *pm_tps_sensor_{nullptr}; // SEN54 and SEN55 only sensor::Sensor *temperature_sensor_{nullptr}; sensor::Sensor *humidity_sensor_{nullptr}; @@ -118,7 +135,8 @@ class SEN5XComponent : public PollingComponent, public sensirion_common::Sensiri std::string product_name_; uint8_t serial_number_[4]; - uint16_t firmware_version_; + uint8_t firmware_version_major_; + uint8_t firmware_version_minor_; Sen5xBaselines voc_baselines_storage_; bool store_baseline_; uint32_t seconds_since_last_store_; @@ -128,6 +146,8 @@ class SEN5XComponent : public PollingComponent, public sensirion_common::Sensiri optional voc_tuning_params_; optional nox_tuning_params_; optional temperature_compensation_; + // Driver state variables + bool get_pm_number_concentration_and_tps_; }; } // namespace sen5x diff --git a/esphome/components/sen5x/sensor.py b/esphome/components/sen5x/sensor.py index 67bd627f7f4c..45c6903e0f4d 100644 --- a/esphome/components/sen5x/sensor.py +++ b/esphome/components/sen5x/sensor.py @@ -27,6 +27,7 @@ STATE_CLASS_MEASUREMENT, UNIT_CELSIUS, UNIT_MICROGRAMS_PER_CUBIC_METER, + UNIT_MICROMETER, UNIT_PERCENT, ) @@ -40,6 +41,12 @@ ) RhtAccelerationMode = sen5x_ns.enum("RhtAccelerationMode") +CONF_PM_N_0_5 = "pm_n_0_5" +CONF_PM_N_1_0 = "pm_n_1_0" +CONF_PM_N_2_5 = "pm_n_2_5" +CONF_PM_N_4_0 = "pm_n_4_0" +CONF_PM_N_10_0 = "pm_n_10_0" +CONF_PM_TPS = "pm_tps" CONF_ACCELERATION_MODE = "acceleration_mode" CONF_ALGORITHM_TUNING = "algorithm_tuning" CONF_AUTO_CLEANING_INTERVAL = "auto_cleaning_interval" @@ -127,6 +134,42 @@ def float_previously_pct(value): device_class=DEVICE_CLASS_PM10, state_class=STATE_CLASS_MEASUREMENT, ), + cv.Optional(CONF_PM_N_0_5): sensor.sensor_schema( + unit_of_measurement="#/cm³", + icon=ICON_CHEMICAL_WEAPON, + accuracy_decimals=2, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional(CONF_PM_N_1_0): sensor.sensor_schema( + unit_of_measurement="#/cm³", + icon=ICON_CHEMICAL_WEAPON, + accuracy_decimals=2, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional(CONF_PM_N_2_5): sensor.sensor_schema( + unit_of_measurement="#/cm³", + icon=ICON_CHEMICAL_WEAPON, + accuracy_decimals=2, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional(CONF_PM_N_4_0): sensor.sensor_schema( + unit_of_measurement="#/cm³", + icon=ICON_CHEMICAL_WEAPON, + accuracy_decimals=2, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional(CONF_PM_N_10_0): sensor.sensor_schema( + unit_of_measurement="#/cm³", + icon=ICON_CHEMICAL_WEAPON, + accuracy_decimals=2, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional(CONF_PM_TPS): sensor.sensor_schema( + unit_of_measurement=UNIT_MICROMETER, + icon=ICON_CHEMICAL_WEAPON, + accuracy_decimals=2, + state_class=STATE_CLASS_MEASUREMENT, + ), cv.Optional(CONF_AUTO_CLEANING_INTERVAL): cv.update_interval, cv.Optional(CONF_VOC): sensor.sensor_schema( icon=ICON_RADIATOR, @@ -177,6 +220,12 @@ def float_previously_pct(value): CONF_PM_2_5: "set_pm_2_5_sensor", CONF_PM_4_0: "set_pm_4_0_sensor", CONF_PM_10_0: "set_pm_10_0_sensor", + CONF_PM_N_0_5: "set_pm_n_0_5_sensor", + CONF_PM_N_1_0: "set_pm_n_1_0_sensor", + CONF_PM_N_2_5: "set_pm_n_2_5_sensor", + CONF_PM_N_4_0: "set_pm_n_4_0_sensor", + CONF_PM_N_10_0: "set_pm_n_10_0_sensor", + CONF_PM_TPS: "set_pm_tps_sensor", CONF_VOC: "set_voc_sensor", CONF_NOX: "set_nox_sensor", CONF_TEMPERATURE: "set_temperature_sensor", diff --git a/tests/components/sen5x/test.esp32-c3-idf.yaml b/tests/components/sen5x/test.esp32-c3-idf.yaml index 3352a59b1725..897a2c67232c 100644 --- a/tests/components/sen5x/test.esp32-c3-idf.yaml +++ b/tests/components/sen5x/test.esp32-c3-idf.yaml @@ -13,21 +13,44 @@ sensor: name: Humidity accuracy_decimals: 0 pm_1_0: - name: PM <1µm Weight concentration + name: PM <1µm Mass concentration id: pm_1_0 accuracy_decimals: 1 pm_2_5: - name: PM <2.5µm Weight concentration + name: PM <2.5µm Mass concentration id: pm_2_5 accuracy_decimals: 1 pm_4_0: - name: PM <4µm Weight concentration + name: PM <4µm Mass concentration id: pm_4_0 accuracy_decimals: 1 pm_10_0: - name: PM <10µm Weight concentration + name: PM <10µm Mass concentration id: pm_10_0 accuracy_decimals: 1 + pm_n_0_5: + name: PM <0.5µm Number concentration + id: pm_n_0_5 + accuracy_decimals: 1 + pm_n_1_0: + name: PM <1.0µm Number concentration + id: pm_n_1_0 + accuracy_decimals: 1 + pm_n_2_5: + name: PM <2.5µm Number concentration + id: pm_n_2_5 + accuracy_decimals: 1 + pm_n_4_0: + name: PM <4µm Number concentration + id: pm_n_4_0 + accuracy_decimals: 1 + pm_n_10_0: + name: PM <10µm Number concentration + accuracy_decimals: 1 + pm_tps: + name: Typical Particle size + id: pm_tps + accuracy_decimals: 1 nox: name: NOx voc: diff --git a/tests/components/sen5x/test.esp32-c3.yaml b/tests/components/sen5x/test.esp32-c3.yaml index 3352a59b1725..897a2c67232c 100644 --- a/tests/components/sen5x/test.esp32-c3.yaml +++ b/tests/components/sen5x/test.esp32-c3.yaml @@ -13,21 +13,44 @@ sensor: name: Humidity accuracy_decimals: 0 pm_1_0: - name: PM <1µm Weight concentration + name: PM <1µm Mass concentration id: pm_1_0 accuracy_decimals: 1 pm_2_5: - name: PM <2.5µm Weight concentration + name: PM <2.5µm Mass concentration id: pm_2_5 accuracy_decimals: 1 pm_4_0: - name: PM <4µm Weight concentration + name: PM <4µm Mass concentration id: pm_4_0 accuracy_decimals: 1 pm_10_0: - name: PM <10µm Weight concentration + name: PM <10µm Mass concentration id: pm_10_0 accuracy_decimals: 1 + pm_n_0_5: + name: PM <0.5µm Number concentration + id: pm_n_0_5 + accuracy_decimals: 1 + pm_n_1_0: + name: PM <1.0µm Number concentration + id: pm_n_1_0 + accuracy_decimals: 1 + pm_n_2_5: + name: PM <2.5µm Number concentration + id: pm_n_2_5 + accuracy_decimals: 1 + pm_n_4_0: + name: PM <4µm Number concentration + id: pm_n_4_0 + accuracy_decimals: 1 + pm_n_10_0: + name: PM <10µm Number concentration + accuracy_decimals: 1 + pm_tps: + name: Typical Particle size + id: pm_tps + accuracy_decimals: 1 nox: name: NOx voc: diff --git a/tests/components/sen5x/test.esp32-idf.yaml b/tests/components/sen5x/test.esp32-idf.yaml index b8f89c435f40..edf92792e5ea 100644 --- a/tests/components/sen5x/test.esp32-idf.yaml +++ b/tests/components/sen5x/test.esp32-idf.yaml @@ -13,21 +13,44 @@ sensor: name: Humidity accuracy_decimals: 0 pm_1_0: - name: PM <1µm Weight concentration + name: PM <1µm Mass concentration id: pm_1_0 accuracy_decimals: 1 pm_2_5: - name: PM <2.5µm Weight concentration + name: PM <2.5µm Mass concentration id: pm_2_5 accuracy_decimals: 1 pm_4_0: - name: PM <4µm Weight concentration + name: PM <4µm Mass concentration id: pm_4_0 accuracy_decimals: 1 pm_10_0: - name: PM <10µm Weight concentration + name: PM <10µm Mass concentration id: pm_10_0 accuracy_decimals: 1 + pm_n_0_5: + name: PM <0.5µm Number concentration + id: pm_n_0_5 + accuracy_decimals: 1 + pm_n_1_0: + name: PM <1.0µm Number concentration + id: pm_n_1_0 + accuracy_decimals: 1 + pm_n_2_5: + name: PM <2.5µm Number concentration + id: pm_n_2_5 + accuracy_decimals: 1 + pm_n_4_0: + name: PM <4µm Number concentration + id: pm_n_4_0 + accuracy_decimals: 1 + pm_n_10_0: + name: PM <10µm Number concentration + accuracy_decimals: 1 + pm_tps: + name: Typical Particle size + id: pm_tps + accuracy_decimals: 1 nox: name: NOx voc: diff --git a/tests/components/sen5x/test.esp32.yaml b/tests/components/sen5x/test.esp32.yaml index b8f89c435f40..edf92792e5ea 100644 --- a/tests/components/sen5x/test.esp32.yaml +++ b/tests/components/sen5x/test.esp32.yaml @@ -13,21 +13,44 @@ sensor: name: Humidity accuracy_decimals: 0 pm_1_0: - name: PM <1µm Weight concentration + name: PM <1µm Mass concentration id: pm_1_0 accuracy_decimals: 1 pm_2_5: - name: PM <2.5µm Weight concentration + name: PM <2.5µm Mass concentration id: pm_2_5 accuracy_decimals: 1 pm_4_0: - name: PM <4µm Weight concentration + name: PM <4µm Mass concentration id: pm_4_0 accuracy_decimals: 1 pm_10_0: - name: PM <10µm Weight concentration + name: PM <10µm Mass concentration id: pm_10_0 accuracy_decimals: 1 + pm_n_0_5: + name: PM <0.5µm Number concentration + id: pm_n_0_5 + accuracy_decimals: 1 + pm_n_1_0: + name: PM <1.0µm Number concentration + id: pm_n_1_0 + accuracy_decimals: 1 + pm_n_2_5: + name: PM <2.5µm Number concentration + id: pm_n_2_5 + accuracy_decimals: 1 + pm_n_4_0: + name: PM <4µm Number concentration + id: pm_n_4_0 + accuracy_decimals: 1 + pm_n_10_0: + name: PM <10µm Number concentration + accuracy_decimals: 1 + pm_tps: + name: Typical Particle size + id: pm_tps + accuracy_decimals: 1 nox: name: NOx voc: diff --git a/tests/components/sen5x/test.esp8266.yaml b/tests/components/sen5x/test.esp8266.yaml index 3352a59b1725..897a2c67232c 100644 --- a/tests/components/sen5x/test.esp8266.yaml +++ b/tests/components/sen5x/test.esp8266.yaml @@ -13,21 +13,44 @@ sensor: name: Humidity accuracy_decimals: 0 pm_1_0: - name: PM <1µm Weight concentration + name: PM <1µm Mass concentration id: pm_1_0 accuracy_decimals: 1 pm_2_5: - name: PM <2.5µm Weight concentration + name: PM <2.5µm Mass concentration id: pm_2_5 accuracy_decimals: 1 pm_4_0: - name: PM <4µm Weight concentration + name: PM <4µm Mass concentration id: pm_4_0 accuracy_decimals: 1 pm_10_0: - name: PM <10µm Weight concentration + name: PM <10µm Mass concentration id: pm_10_0 accuracy_decimals: 1 + pm_n_0_5: + name: PM <0.5µm Number concentration + id: pm_n_0_5 + accuracy_decimals: 1 + pm_n_1_0: + name: PM <1.0µm Number concentration + id: pm_n_1_0 + accuracy_decimals: 1 + pm_n_2_5: + name: PM <2.5µm Number concentration + id: pm_n_2_5 + accuracy_decimals: 1 + pm_n_4_0: + name: PM <4µm Number concentration + id: pm_n_4_0 + accuracy_decimals: 1 + pm_n_10_0: + name: PM <10µm Number concentration + accuracy_decimals: 1 + pm_tps: + name: Typical Particle size + id: pm_tps + accuracy_decimals: 1 nox: name: NOx voc: diff --git a/tests/components/sen5x/test.rp2040.yaml b/tests/components/sen5x/test.rp2040.yaml index 3352a59b1725..897a2c67232c 100644 --- a/tests/components/sen5x/test.rp2040.yaml +++ b/tests/components/sen5x/test.rp2040.yaml @@ -13,21 +13,44 @@ sensor: name: Humidity accuracy_decimals: 0 pm_1_0: - name: PM <1µm Weight concentration + name: PM <1µm Mass concentration id: pm_1_0 accuracy_decimals: 1 pm_2_5: - name: PM <2.5µm Weight concentration + name: PM <2.5µm Mass concentration id: pm_2_5 accuracy_decimals: 1 pm_4_0: - name: PM <4µm Weight concentration + name: PM <4µm Mass concentration id: pm_4_0 accuracy_decimals: 1 pm_10_0: - name: PM <10µm Weight concentration + name: PM <10µm Mass concentration id: pm_10_0 accuracy_decimals: 1 + pm_n_0_5: + name: PM <0.5µm Number concentration + id: pm_n_0_5 + accuracy_decimals: 1 + pm_n_1_0: + name: PM <1.0µm Number concentration + id: pm_n_1_0 + accuracy_decimals: 1 + pm_n_2_5: + name: PM <2.5µm Number concentration + id: pm_n_2_5 + accuracy_decimals: 1 + pm_n_4_0: + name: PM <4µm Number concentration + id: pm_n_4_0 + accuracy_decimals: 1 + pm_n_10_0: + name: PM <10µm Number concentration + accuracy_decimals: 1 + pm_tps: + name: Typical Particle size + id: pm_tps + accuracy_decimals: 1 nox: name: NOx voc: diff --git a/tests/test11.5.yaml b/tests/test11.5.yaml index 13de7f1929cb..7074cf5e23a6 100644 --- a/tests/test11.5.yaml +++ b/tests/test11.5.yaml @@ -507,21 +507,44 @@ sensor: name: Humidity accuracy_decimals: 0 pm_1_0: - name: PM <1µm Weight concentration + name: PM <1µm Mass concentration id: pm_1_0 accuracy_decimals: 1 pm_2_5: - name: PM <2.5µm Weight concentration + name: PM <2.5µm Mass concentration id: pm_2_5 accuracy_decimals: 1 pm_4_0: - name: PM <4µm Weight concentration + name: PM <4µm Mass concentration id: pm_4_0 accuracy_decimals: 1 pm_10_0: - name: PM <10µm Weight concentration + name: PM <10µm Mass concentration id: pm_10_0 accuracy_decimals: 1 + pm_n_0_5: + name: PM <0.5µm Number concentration + id: pm_n_0_5 + accuracy_decimals: 1 + pm_n_1_0: + name: PM <1.0µm Number concentration + id: pm_n_1_0 + accuracy_decimals: 1 + pm_n_2_5: + name: PM <2.5µm Number concentration + id: pm_n_2_5 + accuracy_decimals: 1 + pm_n_4_0: + name: PM <4µm Number concentration + id: pm_n_4_0 + accuracy_decimals: 1 + pm_n_10_0: + name: PM <10µm Number concentration + accuracy_decimals: 1 + pm_tps: + name: Typical Particle size + id: pm_tps + accuracy_decimals: 1 nox: name: NOx voc: diff --git a/tests/test5.yaml b/tests/test5.yaml index afd335909863..25ae51f31d98 100644 --- a/tests/test5.yaml +++ b/tests/test5.yaml @@ -506,21 +506,44 @@ sensor: name: Humidity accuracy_decimals: 0 pm_1_0: - name: PM <1µm Weight concentration + name: PM <1µm Mass concentration id: pm_1_0 accuracy_decimals: 1 pm_2_5: - name: PM <2.5µm Weight concentration + name: PM <2.5µm Mass concentration id: pm_2_5 accuracy_decimals: 1 pm_4_0: - name: PM <4µm Weight concentration + name: PM <4µm Mass concentration id: pm_4_0 accuracy_decimals: 1 pm_10_0: - name: PM <10µm Weight concentration + name: PM <10µm Mass concentration id: pm_10_0 accuracy_decimals: 1 + pm_n_0_5: + name: PM <0.5µm Number concentration + id: pm_n_0_5 + accuracy_decimals: 1 + pm_n_1_0: + name: PM <1.0µm Number concentration + id: pm_n_1_0 + accuracy_decimals: 1 + pm_n_2_5: + name: PM <2.5µm Number concentration + id: pm_n_2_5 + accuracy_decimals: 1 + pm_n_4_0: + name: PM <4µm Number concentration + id: pm_n_4_0 + accuracy_decimals: 1 + pm_n_10_0: + name: PM <10µm Number concentration + accuracy_decimals: 1 + pm_tps: + name: Typical Particle size + id: pm_tps + accuracy_decimals: 1 nox: name: NOx voc: From 69534e28101f19722a21d55046fc414d4e3099d3 Mon Sep 17 00:00:00 2001 From: Marcin Krasowski Date: Wed, 8 May 2024 00:18:02 +0200 Subject: [PATCH 2/8] enh(sen5x): reduce timeout of extra PM reading --- esphome/components/sen5x/sen5x.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/esphome/components/sen5x/sen5x.cpp b/esphome/components/sen5x/sen5x.cpp index 3a631b4047f2..ed81f5949b94 100644 --- a/esphome/components/sen5x/sen5x.cpp +++ b/esphome/components/sen5x/sen5x.cpp @@ -331,8 +331,8 @@ void SEN5XComponent::update() { this->update_measured_values_(); if (this->get_pm_number_concentration_and_tps_) { - // delay the extra reading otherwise the device throws errors or the data is corrupted (CRC errors) - this->set_timeout(200, [this]() { this->update_measured_pm_(); }); + // delay the extra reading to allow update_measured_values to complete. + this->set_timeout(30, [this]() { this->update_measured_pm_(); }); } } From 1413c349a8fbb2785c2a9a9448e458c647a1b1a4 Mon Sep 17 00:00:00 2001 From: Marcin Krasowski Date: Wed, 8 May 2024 18:35:16 +0200 Subject: [PATCH 3/8] rev(sen5x): remove new entities from tests5 & tests11.5 --- tests/test11.5.yaml | 31 ++++--------------------------- tests/test5.yaml | 31 ++++--------------------------- 2 files changed, 8 insertions(+), 54 deletions(-) diff --git a/tests/test11.5.yaml b/tests/test11.5.yaml index 7074cf5e23a6..13de7f1929cb 100644 --- a/tests/test11.5.yaml +++ b/tests/test11.5.yaml @@ -507,44 +507,21 @@ sensor: name: Humidity accuracy_decimals: 0 pm_1_0: - name: PM <1µm Mass concentration + name: PM <1µm Weight concentration id: pm_1_0 accuracy_decimals: 1 pm_2_5: - name: PM <2.5µm Mass concentration + name: PM <2.5µm Weight concentration id: pm_2_5 accuracy_decimals: 1 pm_4_0: - name: PM <4µm Mass concentration + name: PM <4µm Weight concentration id: pm_4_0 accuracy_decimals: 1 pm_10_0: - name: PM <10µm Mass concentration + name: PM <10µm Weight concentration id: pm_10_0 accuracy_decimals: 1 - pm_n_0_5: - name: PM <0.5µm Number concentration - id: pm_n_0_5 - accuracy_decimals: 1 - pm_n_1_0: - name: PM <1.0µm Number concentration - id: pm_n_1_0 - accuracy_decimals: 1 - pm_n_2_5: - name: PM <2.5µm Number concentration - id: pm_n_2_5 - accuracy_decimals: 1 - pm_n_4_0: - name: PM <4µm Number concentration - id: pm_n_4_0 - accuracy_decimals: 1 - pm_n_10_0: - name: PM <10µm Number concentration - accuracy_decimals: 1 - pm_tps: - name: Typical Particle size - id: pm_tps - accuracy_decimals: 1 nox: name: NOx voc: diff --git a/tests/test5.yaml b/tests/test5.yaml index 25ae51f31d98..afd335909863 100644 --- a/tests/test5.yaml +++ b/tests/test5.yaml @@ -506,44 +506,21 @@ sensor: name: Humidity accuracy_decimals: 0 pm_1_0: - name: PM <1µm Mass concentration + name: PM <1µm Weight concentration id: pm_1_0 accuracy_decimals: 1 pm_2_5: - name: PM <2.5µm Mass concentration + name: PM <2.5µm Weight concentration id: pm_2_5 accuracy_decimals: 1 pm_4_0: - name: PM <4µm Mass concentration + name: PM <4µm Weight concentration id: pm_4_0 accuracy_decimals: 1 pm_10_0: - name: PM <10µm Mass concentration + name: PM <10µm Weight concentration id: pm_10_0 accuracy_decimals: 1 - pm_n_0_5: - name: PM <0.5µm Number concentration - id: pm_n_0_5 - accuracy_decimals: 1 - pm_n_1_0: - name: PM <1.0µm Number concentration - id: pm_n_1_0 - accuracy_decimals: 1 - pm_n_2_5: - name: PM <2.5µm Number concentration - id: pm_n_2_5 - accuracy_decimals: 1 - pm_n_4_0: - name: PM <4µm Number concentration - id: pm_n_4_0 - accuracy_decimals: 1 - pm_n_10_0: - name: PM <10µm Number concentration - accuracy_decimals: 1 - pm_tps: - name: Typical Particle size - id: pm_tps - accuracy_decimals: 1 nox: name: NOx voc: From 095dedca260f615fe7658cb85f7117fd4a8c5da0 Mon Sep 17 00:00:00 2001 From: Marcin Krasowski Date: Wed, 8 May 2024 18:59:45 +0200 Subject: [PATCH 4/8] ref(sen5x): consolidate test configs --- tests/components/sen5x/common.yaml | 72 ++++++++++++++++++ tests/components/sen5x/test.esp32-c3-idf.yaml | 75 +------------------ tests/components/sen5x/test.esp32-c3.yaml | 75 +------------------ tests/components/sen5x/test.esp32-idf.yaml | 75 +------------------ tests/components/sen5x/test.esp32.yaml | 75 +------------------ tests/components/sen5x/test.esp8266.yaml | 75 +------------------ tests/components/sen5x/test.rp2040.yaml | 75 +------------------ 7 files changed, 96 insertions(+), 426 deletions(-) create mode 100644 tests/components/sen5x/common.yaml diff --git a/tests/components/sen5x/common.yaml b/tests/components/sen5x/common.yaml new file mode 100644 index 000000000000..ed284af938ea --- /dev/null +++ b/tests/components/sen5x/common.yaml @@ -0,0 +1,72 @@ +i2c: + - id: i2c_sen5x + scl: ${i2c_a_scl_pin} + sda: ${i2c_a_sda_pin} + +sensor: + - platform: sen5x + id: sen54 + temperature: + name: Temperature + accuracy_decimals: 1 + humidity: + name: Humidity + accuracy_decimals: 0 + pm_1_0: + name: PM <1µm Mass concentration + id: pm_1_0 + accuracy_decimals: 1 + pm_2_5: + name: PM <2.5µm Mass concentration + id: pm_2_5 + accuracy_decimals: 1 + pm_4_0: + name: PM <4µm Mass concentration + id: pm_4_0 + accuracy_decimals: 1 + pm_10_0: + name: PM <10µm Mass concentration + id: pm_10_0 + accuracy_decimals: 1 + pm_n_0_5: + name: PM <0.5µm Number concentration + id: pm_n_0_5 + accuracy_decimals: 1 + pm_n_1_0: + name: PM <1.0µm Number concentration + id: pm_n_1_0 + accuracy_decimals: 1 + pm_n_2_5: + name: PM <2.5µm Number concentration + id: pm_n_2_5 + accuracy_decimals: 1 + pm_n_4_0: + name: PM <4µm Number concentration + id: pm_n_4_0 + accuracy_decimals: 1 + pm_n_10_0: + name: PM <10µm Number concentration + accuracy_decimals: 1 + pm_tps: + name: Typical Particle size + id: pm_tps + accuracy_decimals: 1 + nox: + name: NOx + voc: + name: VOC + algorithm_tuning: + index_offset: 100 + learning_time_offset_hours: 12 + learning_time_gain_hours: 12 + gating_max_duration_minutes: 180 + std_initial: 50 + gain_factor: 230 + temperature_compensation: + offset: 0 + normalized_offset_slope: 0 + time_constant: 0 + auto_cleaning_interval: 604800s + acceleration_mode: low + store_baseline: true + address: 0x69 diff --git a/tests/components/sen5x/test.esp32-c3-idf.yaml b/tests/components/sen5x/test.esp32-c3-idf.yaml index 897a2c67232c..e2d3b1e1ada7 100644 --- a/tests/components/sen5x/test.esp32-c3-idf.yaml +++ b/tests/components/sen5x/test.esp32-c3-idf.yaml @@ -1,72 +1,5 @@ -i2c: - - id: i2c_sen5x - scl: 5 - sda: 4 +substitutions: + i2c_a_scl_pin: GPIO5 + i2c_a_sda_pin: GPIO4 -sensor: - - platform: sen5x - id: sen54 - temperature: - name: Temperature - accuracy_decimals: 1 - humidity: - name: Humidity - accuracy_decimals: 0 - pm_1_0: - name: PM <1µm Mass concentration - id: pm_1_0 - accuracy_decimals: 1 - pm_2_5: - name: PM <2.5µm Mass concentration - id: pm_2_5 - accuracy_decimals: 1 - pm_4_0: - name: PM <4µm Mass concentration - id: pm_4_0 - accuracy_decimals: 1 - pm_10_0: - name: PM <10µm Mass concentration - id: pm_10_0 - accuracy_decimals: 1 - pm_n_0_5: - name: PM <0.5µm Number concentration - id: pm_n_0_5 - accuracy_decimals: 1 - pm_n_1_0: - name: PM <1.0µm Number concentration - id: pm_n_1_0 - accuracy_decimals: 1 - pm_n_2_5: - name: PM <2.5µm Number concentration - id: pm_n_2_5 - accuracy_decimals: 1 - pm_n_4_0: - name: PM <4µm Number concentration - id: pm_n_4_0 - accuracy_decimals: 1 - pm_n_10_0: - name: PM <10µm Number concentration - accuracy_decimals: 1 - pm_tps: - name: Typical Particle size - id: pm_tps - accuracy_decimals: 1 - nox: - name: NOx - voc: - name: VOC - algorithm_tuning: - index_offset: 100 - learning_time_offset_hours: 12 - learning_time_gain_hours: 12 - gating_max_duration_minutes: 180 - std_initial: 50 - gain_factor: 230 - temperature_compensation: - offset: 0 - normalized_offset_slope: 0 - time_constant: 0 - auto_cleaning_interval: 604800s - acceleration_mode: low - store_baseline: true - address: 0x69 +<<: !include common.yaml diff --git a/tests/components/sen5x/test.esp32-c3.yaml b/tests/components/sen5x/test.esp32-c3.yaml index 897a2c67232c..e2d3b1e1ada7 100644 --- a/tests/components/sen5x/test.esp32-c3.yaml +++ b/tests/components/sen5x/test.esp32-c3.yaml @@ -1,72 +1,5 @@ -i2c: - - id: i2c_sen5x - scl: 5 - sda: 4 +substitutions: + i2c_a_scl_pin: GPIO5 + i2c_a_sda_pin: GPIO4 -sensor: - - platform: sen5x - id: sen54 - temperature: - name: Temperature - accuracy_decimals: 1 - humidity: - name: Humidity - accuracy_decimals: 0 - pm_1_0: - name: PM <1µm Mass concentration - id: pm_1_0 - accuracy_decimals: 1 - pm_2_5: - name: PM <2.5µm Mass concentration - id: pm_2_5 - accuracy_decimals: 1 - pm_4_0: - name: PM <4µm Mass concentration - id: pm_4_0 - accuracy_decimals: 1 - pm_10_0: - name: PM <10µm Mass concentration - id: pm_10_0 - accuracy_decimals: 1 - pm_n_0_5: - name: PM <0.5µm Number concentration - id: pm_n_0_5 - accuracy_decimals: 1 - pm_n_1_0: - name: PM <1.0µm Number concentration - id: pm_n_1_0 - accuracy_decimals: 1 - pm_n_2_5: - name: PM <2.5µm Number concentration - id: pm_n_2_5 - accuracy_decimals: 1 - pm_n_4_0: - name: PM <4µm Number concentration - id: pm_n_4_0 - accuracy_decimals: 1 - pm_n_10_0: - name: PM <10µm Number concentration - accuracy_decimals: 1 - pm_tps: - name: Typical Particle size - id: pm_tps - accuracy_decimals: 1 - nox: - name: NOx - voc: - name: VOC - algorithm_tuning: - index_offset: 100 - learning_time_offset_hours: 12 - learning_time_gain_hours: 12 - gating_max_duration_minutes: 180 - std_initial: 50 - gain_factor: 230 - temperature_compensation: - offset: 0 - normalized_offset_slope: 0 - time_constant: 0 - auto_cleaning_interval: 604800s - acceleration_mode: low - store_baseline: true - address: 0x69 +<<: !include common.yaml diff --git a/tests/components/sen5x/test.esp32-idf.yaml b/tests/components/sen5x/test.esp32-idf.yaml index edf92792e5ea..883155a43fee 100644 --- a/tests/components/sen5x/test.esp32-idf.yaml +++ b/tests/components/sen5x/test.esp32-idf.yaml @@ -1,72 +1,5 @@ -i2c: - - id: i2c_sen5x - scl: 16 - sda: 17 +substitutions: + i2c_a_scl_pin: GPIO16 + i2c_a_sda_pin: GPIO17 -sensor: - - platform: sen5x - id: sen54 - temperature: - name: Temperature - accuracy_decimals: 1 - humidity: - name: Humidity - accuracy_decimals: 0 - pm_1_0: - name: PM <1µm Mass concentration - id: pm_1_0 - accuracy_decimals: 1 - pm_2_5: - name: PM <2.5µm Mass concentration - id: pm_2_5 - accuracy_decimals: 1 - pm_4_0: - name: PM <4µm Mass concentration - id: pm_4_0 - accuracy_decimals: 1 - pm_10_0: - name: PM <10µm Mass concentration - id: pm_10_0 - accuracy_decimals: 1 - pm_n_0_5: - name: PM <0.5µm Number concentration - id: pm_n_0_5 - accuracy_decimals: 1 - pm_n_1_0: - name: PM <1.0µm Number concentration - id: pm_n_1_0 - accuracy_decimals: 1 - pm_n_2_5: - name: PM <2.5µm Number concentration - id: pm_n_2_5 - accuracy_decimals: 1 - pm_n_4_0: - name: PM <4µm Number concentration - id: pm_n_4_0 - accuracy_decimals: 1 - pm_n_10_0: - name: PM <10µm Number concentration - accuracy_decimals: 1 - pm_tps: - name: Typical Particle size - id: pm_tps - accuracy_decimals: 1 - nox: - name: NOx - voc: - name: VOC - algorithm_tuning: - index_offset: 100 - learning_time_offset_hours: 12 - learning_time_gain_hours: 12 - gating_max_duration_minutes: 180 - std_initial: 50 - gain_factor: 230 - temperature_compensation: - offset: 0 - normalized_offset_slope: 0 - time_constant: 0 - auto_cleaning_interval: 604800s - acceleration_mode: low - store_baseline: true - address: 0x69 +<<: !include common.yaml diff --git a/tests/components/sen5x/test.esp32.yaml b/tests/components/sen5x/test.esp32.yaml index edf92792e5ea..883155a43fee 100644 --- a/tests/components/sen5x/test.esp32.yaml +++ b/tests/components/sen5x/test.esp32.yaml @@ -1,72 +1,5 @@ -i2c: - - id: i2c_sen5x - scl: 16 - sda: 17 +substitutions: + i2c_a_scl_pin: GPIO16 + i2c_a_sda_pin: GPIO17 -sensor: - - platform: sen5x - id: sen54 - temperature: - name: Temperature - accuracy_decimals: 1 - humidity: - name: Humidity - accuracy_decimals: 0 - pm_1_0: - name: PM <1µm Mass concentration - id: pm_1_0 - accuracy_decimals: 1 - pm_2_5: - name: PM <2.5µm Mass concentration - id: pm_2_5 - accuracy_decimals: 1 - pm_4_0: - name: PM <4µm Mass concentration - id: pm_4_0 - accuracy_decimals: 1 - pm_10_0: - name: PM <10µm Mass concentration - id: pm_10_0 - accuracy_decimals: 1 - pm_n_0_5: - name: PM <0.5µm Number concentration - id: pm_n_0_5 - accuracy_decimals: 1 - pm_n_1_0: - name: PM <1.0µm Number concentration - id: pm_n_1_0 - accuracy_decimals: 1 - pm_n_2_5: - name: PM <2.5µm Number concentration - id: pm_n_2_5 - accuracy_decimals: 1 - pm_n_4_0: - name: PM <4µm Number concentration - id: pm_n_4_0 - accuracy_decimals: 1 - pm_n_10_0: - name: PM <10µm Number concentration - accuracy_decimals: 1 - pm_tps: - name: Typical Particle size - id: pm_tps - accuracy_decimals: 1 - nox: - name: NOx - voc: - name: VOC - algorithm_tuning: - index_offset: 100 - learning_time_offset_hours: 12 - learning_time_gain_hours: 12 - gating_max_duration_minutes: 180 - std_initial: 50 - gain_factor: 230 - temperature_compensation: - offset: 0 - normalized_offset_slope: 0 - time_constant: 0 - auto_cleaning_interval: 604800s - acceleration_mode: low - store_baseline: true - address: 0x69 +<<: !include common.yaml diff --git a/tests/components/sen5x/test.esp8266.yaml b/tests/components/sen5x/test.esp8266.yaml index 897a2c67232c..e2d3b1e1ada7 100644 --- a/tests/components/sen5x/test.esp8266.yaml +++ b/tests/components/sen5x/test.esp8266.yaml @@ -1,72 +1,5 @@ -i2c: - - id: i2c_sen5x - scl: 5 - sda: 4 +substitutions: + i2c_a_scl_pin: GPIO5 + i2c_a_sda_pin: GPIO4 -sensor: - - platform: sen5x - id: sen54 - temperature: - name: Temperature - accuracy_decimals: 1 - humidity: - name: Humidity - accuracy_decimals: 0 - pm_1_0: - name: PM <1µm Mass concentration - id: pm_1_0 - accuracy_decimals: 1 - pm_2_5: - name: PM <2.5µm Mass concentration - id: pm_2_5 - accuracy_decimals: 1 - pm_4_0: - name: PM <4µm Mass concentration - id: pm_4_0 - accuracy_decimals: 1 - pm_10_0: - name: PM <10µm Mass concentration - id: pm_10_0 - accuracy_decimals: 1 - pm_n_0_5: - name: PM <0.5µm Number concentration - id: pm_n_0_5 - accuracy_decimals: 1 - pm_n_1_0: - name: PM <1.0µm Number concentration - id: pm_n_1_0 - accuracy_decimals: 1 - pm_n_2_5: - name: PM <2.5µm Number concentration - id: pm_n_2_5 - accuracy_decimals: 1 - pm_n_4_0: - name: PM <4µm Number concentration - id: pm_n_4_0 - accuracy_decimals: 1 - pm_n_10_0: - name: PM <10µm Number concentration - accuracy_decimals: 1 - pm_tps: - name: Typical Particle size - id: pm_tps - accuracy_decimals: 1 - nox: - name: NOx - voc: - name: VOC - algorithm_tuning: - index_offset: 100 - learning_time_offset_hours: 12 - learning_time_gain_hours: 12 - gating_max_duration_minutes: 180 - std_initial: 50 - gain_factor: 230 - temperature_compensation: - offset: 0 - normalized_offset_slope: 0 - time_constant: 0 - auto_cleaning_interval: 604800s - acceleration_mode: low - store_baseline: true - address: 0x69 +<<: !include common.yaml diff --git a/tests/components/sen5x/test.rp2040.yaml b/tests/components/sen5x/test.rp2040.yaml index 897a2c67232c..e2d3b1e1ada7 100644 --- a/tests/components/sen5x/test.rp2040.yaml +++ b/tests/components/sen5x/test.rp2040.yaml @@ -1,72 +1,5 @@ -i2c: - - id: i2c_sen5x - scl: 5 - sda: 4 +substitutions: + i2c_a_scl_pin: GPIO5 + i2c_a_sda_pin: GPIO4 -sensor: - - platform: sen5x - id: sen54 - temperature: - name: Temperature - accuracy_decimals: 1 - humidity: - name: Humidity - accuracy_decimals: 0 - pm_1_0: - name: PM <1µm Mass concentration - id: pm_1_0 - accuracy_decimals: 1 - pm_2_5: - name: PM <2.5µm Mass concentration - id: pm_2_5 - accuracy_decimals: 1 - pm_4_0: - name: PM <4µm Mass concentration - id: pm_4_0 - accuracy_decimals: 1 - pm_10_0: - name: PM <10µm Mass concentration - id: pm_10_0 - accuracy_decimals: 1 - pm_n_0_5: - name: PM <0.5µm Number concentration - id: pm_n_0_5 - accuracy_decimals: 1 - pm_n_1_0: - name: PM <1.0µm Number concentration - id: pm_n_1_0 - accuracy_decimals: 1 - pm_n_2_5: - name: PM <2.5µm Number concentration - id: pm_n_2_5 - accuracy_decimals: 1 - pm_n_4_0: - name: PM <4µm Number concentration - id: pm_n_4_0 - accuracy_decimals: 1 - pm_n_10_0: - name: PM <10µm Number concentration - accuracy_decimals: 1 - pm_tps: - name: Typical Particle size - id: pm_tps - accuracy_decimals: 1 - nox: - name: NOx - voc: - name: VOC - algorithm_tuning: - index_offset: 100 - learning_time_offset_hours: 12 - learning_time_gain_hours: 12 - gating_max_duration_minutes: 180 - std_initial: 50 - gain_factor: 230 - temperature_compensation: - offset: 0 - normalized_offset_slope: 0 - time_constant: 0 - auto_cleaning_interval: 604800s - acceleration_mode: low - store_baseline: true - address: 0x69 +<<: !include common.yaml From 5848d0e6ad2d6c00fafc93e18e8665ec737c65dc Mon Sep 17 00:00:00 2001 From: Marcin Krasowski Date: Wed, 8 May 2024 19:32:09 +0200 Subject: [PATCH 5/8] ref(sen5x): check invalid meas before scaling it --- esphome/components/sen5x/sen5x.cpp | 68 ++++++++++++------------------ esphome/components/sen5x/sen5x.h | 1 + 2 files changed, 27 insertions(+), 42 deletions(-) diff --git a/esphome/components/sen5x/sen5x.cpp b/esphome/components/sen5x/sen5x.cpp index ed81f5949b94..8b07555a59d7 100644 --- a/esphome/components/sen5x/sen5x.cpp +++ b/esphome/components/sen5x/sen5x.cpp @@ -336,6 +336,13 @@ void SEN5XComponent::update() { } } +float SEN5XComponent::get_valid_measurement_(uint16_t measurement, uint16_t invalid_value, float scaling) { + if (measurement >= invalid_value) { + return NAN; + } + return measurement / scaling; +} + void SEN5XComponent::write_voc_baseline_() { if (this->write_command(SEN5X_CMD_VOC_ALGORITHM_STATE)) { // run it a bit later to avoid adding a delay here @@ -380,24 +387,12 @@ void SEN5XComponent::update_measured_pm_() { return; } - float pm_n_0_5 = measurements[4] / 10.0; - if (measurements[4] == 0xFFFF) - pm_n_0_5 = NAN; - float pm_n_1_0 = measurements[5] / 10.0; - if (measurements[5] == 0xFFFF) - pm_n_1_0 = NAN; - float pm_n_2_5 = measurements[6] / 10.0; - if (measurements[6] == 0xFFFF) - pm_n_2_5 = NAN; - float pm_n_4_0 = measurements[7] / 10.0; - if (measurements[7] == 0xFFFF) - pm_n_4_0 = NAN; - float pm_n_10_0 = measurements[8] / 10.0; - if (measurements[8] == 0xFFFF) - pm_n_10_0 = NAN; - float pm_tps = measurements[9] / 1000.0; - if (measurements[9] == 0xFFFF) - pm_tps = NAN; + float pm_n_0_5 = this->get_valid_measurement_(measurements[4], 0xFFFF, 10.0f); + float pm_n_1_0 = this->get_valid_measurement_(measurements[5], 0xFFFF, 10.0f); + float pm_n_2_5 = this->get_valid_measurement_(measurements[6], 0xFFFF, 10.0f); + float pm_n_4_0 = this->get_valid_measurement_(measurements[7], 0xFFFF, 10.0f); + float pm_n_10_0 = this->get_valid_measurement_(measurements[8], 0xFFFF, 10.0f); + float pm_tps = this->get_valid_measurement_(measurements[9], 0xFFFF, 10.0f); if (this->pm_n_0_5_sensor_ != nullptr) this->pm_n_0_5_sensor_->publish_state(pm_n_0_5); @@ -432,30 +427,19 @@ void SEN5XComponent::update_measured_values_() { return; } - float pm_1_0 = measurements[0] / 10.0; - if (measurements[0] == 0xFFFF) - pm_1_0 = NAN; - float pm_2_5 = measurements[1] / 10.0; - if (measurements[1] == 0xFFFF) - pm_2_5 = NAN; - float pm_4_0 = measurements[2] / 10.0; - if (measurements[2] == 0xFFFF) - pm_4_0 = NAN; - float pm_10_0 = measurements[3] / 10.0; - if (measurements[3] == 0xFFFF) - pm_10_0 = NAN; - float humidity = measurements[4] / 100.0; - if (measurements[4] >= 0x7FFF) - humidity = NAN; - float temperature = (int16_t) measurements[5] / 200.0; - if (measurements[5] >= 0x7FFF) - temperature = NAN; - float voc = measurements[6] / 10.0; - if (measurements[6] == 0xFFFF) - voc = NAN; - float nox = measurements[7] / 10.0; - if (measurements[7] == 0xFFFF) - nox = NAN; + float pm_1_0 = this->get_valid_measurement_(measurements[0], 0xFFFF, 10.0f); + float pm_2_5 = this->get_valid_measurement_(measurements[1], 0xFFFF, 10.0f); + float pm_4_0 = this->get_valid_measurement_(measurements[2], 0xFFFF, 10.0f); + float pm_10_0 = this->get_valid_measurement_(measurements[3], 0xFFFF, 10.0f); + float humidity = this->get_valid_measurement_(measurements[4], 0x7FFF, 100.0f); + float voc = this->get_valid_measurement_(measurements[6], 0xFFFF, 10.0f); + float nox = this->get_valid_measurement_(measurements[7], 0xFFFF, 10.0f); + + // special case for signed temperature + float temperature = NAN; + if (measurements[5] < 0x7FFF) { + temperature = (int16_t) measurements[5] / 200.0f; + } if (this->pm_1_0_sensor_ != nullptr) this->pm_1_0_sensor_->publish_state(pm_1_0); diff --git a/esphome/components/sen5x/sen5x.h b/esphome/components/sen5x/sen5x.h index e89abef6b91c..4812e7deb57f 100644 --- a/esphome/components/sen5x/sen5x.h +++ b/esphome/components/sen5x/sen5x.h @@ -108,6 +108,7 @@ class SEN5XComponent : public PollingComponent, public sensirion_common::Sensiri bool start_fan_cleaning(); protected: + float get_valid_measurement_(uint16_t measurement, uint16_t invalid_value, float scaling); bool write_tuning_parameters_(uint16_t i2c_command, const GasTuning &tuning); bool write_temperature_compensation_(const TemperatureCompensation &compensation); void write_voc_baseline_(); From 7428b4b309e063f9821e3572e461660d5388efb7 Mon Sep 17 00:00:00 2001 From: Marcin Krasowski Date: Wed, 8 May 2024 19:54:41 +0200 Subject: [PATCH 6/8] ref(sen5x): check humidity, voc & nox are signed ints --- esphome/components/sen5x/sen5x.cpp | 45 ++++++++++++++++-------------- esphome/components/sen5x/sen5x.h | 5 +++- 2 files changed, 28 insertions(+), 22 deletions(-) diff --git a/esphome/components/sen5x/sen5x.cpp b/esphome/components/sen5x/sen5x.cpp index 8b07555a59d7..79ed72bc6a4a 100644 --- a/esphome/components/sen5x/sen5x.cpp +++ b/esphome/components/sen5x/sen5x.cpp @@ -336,13 +336,20 @@ void SEN5XComponent::update() { } } -float SEN5XComponent::get_valid_measurement_(uint16_t measurement, uint16_t invalid_value, float scaling) { - if (measurement >= invalid_value) { +float SEN5XComponent::get_valid_measurement_(uint16_t measurement, float scaling) { + if (measurement == 0xFFFF) { return NAN; } return measurement / scaling; } +float SEN5XComponent::get_valid_measurement_signed_(uint16_t measurement, float scaling) { + if (measurement == 0x7FFF) { + return NAN; + } + return (int16_t) measurement / scaling; +} + void SEN5XComponent::write_voc_baseline_() { if (this->write_command(SEN5X_CMD_VOC_ALGORITHM_STATE)) { // run it a bit later to avoid adding a delay here @@ -387,12 +394,12 @@ void SEN5XComponent::update_measured_pm_() { return; } - float pm_n_0_5 = this->get_valid_measurement_(measurements[4], 0xFFFF, 10.0f); - float pm_n_1_0 = this->get_valid_measurement_(measurements[5], 0xFFFF, 10.0f); - float pm_n_2_5 = this->get_valid_measurement_(measurements[6], 0xFFFF, 10.0f); - float pm_n_4_0 = this->get_valid_measurement_(measurements[7], 0xFFFF, 10.0f); - float pm_n_10_0 = this->get_valid_measurement_(measurements[8], 0xFFFF, 10.0f); - float pm_tps = this->get_valid_measurement_(measurements[9], 0xFFFF, 10.0f); + float pm_n_0_5 = this->get_valid_measurement_(measurements[4], 10.0f); + float pm_n_1_0 = this->get_valid_measurement_(measurements[5], 10.0f); + float pm_n_2_5 = this->get_valid_measurement_(measurements[6], 10.0f); + float pm_n_4_0 = this->get_valid_measurement_(measurements[7], 10.0f); + float pm_n_10_0 = this->get_valid_measurement_(measurements[8], 10.0f); + float pm_tps = this->get_valid_measurement_(measurements[9], 1000.0f); if (this->pm_n_0_5_sensor_ != nullptr) this->pm_n_0_5_sensor_->publish_state(pm_n_0_5); @@ -427,19 +434,15 @@ void SEN5XComponent::update_measured_values_() { return; } - float pm_1_0 = this->get_valid_measurement_(measurements[0], 0xFFFF, 10.0f); - float pm_2_5 = this->get_valid_measurement_(measurements[1], 0xFFFF, 10.0f); - float pm_4_0 = this->get_valid_measurement_(measurements[2], 0xFFFF, 10.0f); - float pm_10_0 = this->get_valid_measurement_(measurements[3], 0xFFFF, 10.0f); - float humidity = this->get_valid_measurement_(measurements[4], 0x7FFF, 100.0f); - float voc = this->get_valid_measurement_(measurements[6], 0xFFFF, 10.0f); - float nox = this->get_valid_measurement_(measurements[7], 0xFFFF, 10.0f); - - // special case for signed temperature - float temperature = NAN; - if (measurements[5] < 0x7FFF) { - temperature = (int16_t) measurements[5] / 200.0f; - } + float pm_1_0 = this->get_valid_measurement_(measurements[0], 10.0f); + float pm_2_5 = this->get_valid_measurement_(measurements[1], 10.0f); + float pm_4_0 = this->get_valid_measurement_(measurements[2], 10.0f); + float pm_10_0 = this->get_valid_measurement_(measurements[3], 10.0f); + + float humidity = this->get_valid_measurement_signed_(measurements[4], 100.0f); + float temperature = this->get_valid_measurement_signed_(measurements[5], 200.0f); + float voc = this->get_valid_measurement_signed_(measurements[6], 10.0f); + float nox = this->get_valid_measurement_signed_(measurements[7], 10.0f); if (this->pm_1_0_sensor_ != nullptr) this->pm_1_0_sensor_->publish_state(pm_1_0); diff --git a/esphome/components/sen5x/sen5x.h b/esphome/components/sen5x/sen5x.h index 4812e7deb57f..4a9a39e50270 100644 --- a/esphome/components/sen5x/sen5x.h +++ b/esphome/components/sen5x/sen5x.h @@ -108,7 +108,10 @@ class SEN5XComponent : public PollingComponent, public sensirion_common::Sensiri bool start_fan_cleaning(); protected: - float get_valid_measurement_(uint16_t measurement, uint16_t invalid_value, float scaling); + // Returns a valid measurement for an unsigned int, NAN otherwise + float get_valid_measurement_(uint16_t measurement, float scaling); + // Returns a valid measurement for a signed int, NAN otherwise + float get_valid_measurement_signed_(uint16_t measurement, float scaling); bool write_tuning_parameters_(uint16_t i2c_command, const GasTuning &tuning); bool write_temperature_compensation_(const TemperatureCompensation &compensation); void write_voc_baseline_(); From c075183c73157e2453beb489cf194f54ec99cf69 Mon Sep 17 00:00:00 2001 From: Marcin Krasowski Date: Wed, 8 May 2024 23:09:49 +0200 Subject: [PATCH 7/8] feat(sen5x): FSM for read commands arbitration --- esphome/components/sen5x/sen5x.cpp | 18 ++++++++++++++++-- esphome/components/sen5x/sen5x.h | 3 +++ 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/esphome/components/sen5x/sen5x.cpp b/esphome/components/sen5x/sen5x.cpp index 79ed72bc6a4a..d231a072c270 100644 --- a/esphome/components/sen5x/sen5x.cpp +++ b/esphome/components/sen5x/sen5x.cpp @@ -29,6 +29,8 @@ static const uint16_t SEN5X_CMD_VOC_ALGORITHM_TUNING = 0x60D0; void SEN5XComponent::setup() { ESP_LOGCONFIG(TAG, "Setting up sen5x..."); + this->fsm_ = IDLE; + // the sensor needs 1000 ms to enter the idle state this->set_timeout(1000, [this]() { // Check if measurement is ready before reading the value @@ -331,8 +333,16 @@ void SEN5XComponent::update() { this->update_measured_values_(); if (this->get_pm_number_concentration_and_tps_) { - // delay the extra reading to allow update_measured_values to complete. - this->set_timeout(30, [this]() { this->update_measured_pm_(); }); + this->set_timeout(10, [this]() { this->trigger_update_measured_pm_(); }); + } +} + +void SEN5XComponent::trigger_update_measured_pm_() { + ESP_LOGD(TAG, "fsm: %d", this->fsm_); + if (this->fsm_ != VALUE_UPDATE_DONE) { + this->set_timeout(20, [this]() { this->trigger_update_measured_pm_(); }); + } else if (this->fsm_ == VALUE_UPDATE_DONE) { + this->update_measured_pm_(); } } @@ -415,10 +425,12 @@ void SEN5XComponent::update_measured_pm_() { this->pm_tps_sensor_->publish_state(pm_tps); this->status_clear_warning(); + this->fsm_ = IDLE; }); } void SEN5XComponent::update_measured_values_() { + this->fsm_ = VALUE_UPDATE_ONGOING; if (!this->write_command(SEN5X_CMD_READ_MEASUREMENT)) { this->status_set_warning(); ESP_LOGD(TAG, "write error read measurement (%d)", this->last_error_); @@ -460,7 +472,9 @@ void SEN5XComponent::update_measured_values_() { this->voc_sensor_->publish_state(voc); if (this->nox_sensor_ != nullptr) this->nox_sensor_->publish_state(nox); + this->status_clear_warning(); + this->fsm_ = VALUE_UPDATE_DONE; }); } diff --git a/esphome/components/sen5x/sen5x.h b/esphome/components/sen5x/sen5x.h index 4a9a39e50270..8aaef48dc612 100644 --- a/esphome/components/sen5x/sen5x.h +++ b/esphome/components/sen5x/sen5x.h @@ -117,6 +117,7 @@ class SEN5XComponent : public PollingComponent, public sensirion_common::Sensiri void write_voc_baseline_(); void update_measured_values_(); void update_measured_pm_(); + void trigger_update_measured_pm_(); ERRORCODE error_code_; bool initialized_{false}; sensor::Sensor *pm_1_0_sensor_{nullptr}; @@ -152,6 +153,8 @@ class SEN5XComponent : public PollingComponent, public sensirion_common::Sensiri optional temperature_compensation_; // Driver state variables bool get_pm_number_concentration_and_tps_; + enum FsmStates { IDLE, VALUE_UPDATE_ONGOING, VALUE_UPDATE_DONE }; + FsmStates fsm_; }; } // namespace sen5x From 9c8eb69400dc909be01c809e376fa58cd0a760d5 Mon Sep 17 00:00:00 2001 From: Marcin Krasowski Date: Thu, 9 May 2024 14:10:51 +0200 Subject: [PATCH 8/8] enh(sen5x): use std units for pm_count --- esphome/components/sen5x/sensor.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/esphome/components/sen5x/sensor.py b/esphome/components/sen5x/sensor.py index 45c6903e0f4d..268270e00855 100644 --- a/esphome/components/sen5x/sensor.py +++ b/esphome/components/sen5x/sensor.py @@ -26,6 +26,7 @@ ICON_WATER_PERCENT, STATE_CLASS_MEASUREMENT, UNIT_CELSIUS, + UNIT_COUNTS_PER_CUBIC_CENTIMETER, UNIT_MICROGRAMS_PER_CUBIC_METER, UNIT_MICROMETER, UNIT_PERCENT, @@ -135,31 +136,31 @@ def float_previously_pct(value): state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional(CONF_PM_N_0_5): sensor.sensor_schema( - unit_of_measurement="#/cm³", + unit_of_measurement=UNIT_COUNTS_PER_CUBIC_CENTIMETER, icon=ICON_CHEMICAL_WEAPON, accuracy_decimals=2, state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional(CONF_PM_N_1_0): sensor.sensor_schema( - unit_of_measurement="#/cm³", + unit_of_measurement=UNIT_COUNTS_PER_CUBIC_CENTIMETER, icon=ICON_CHEMICAL_WEAPON, accuracy_decimals=2, state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional(CONF_PM_N_2_5): sensor.sensor_schema( - unit_of_measurement="#/cm³", + unit_of_measurement=UNIT_COUNTS_PER_CUBIC_CENTIMETER, icon=ICON_CHEMICAL_WEAPON, accuracy_decimals=2, state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional(CONF_PM_N_4_0): sensor.sensor_schema( - unit_of_measurement="#/cm³", + unit_of_measurement=UNIT_COUNTS_PER_CUBIC_CENTIMETER, icon=ICON_CHEMICAL_WEAPON, accuracy_decimals=2, state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional(CONF_PM_N_10_0): sensor.sensor_schema( - unit_of_measurement="#/cm³", + unit_of_measurement=UNIT_COUNTS_PER_CUBIC_CENTIMETER, icon=ICON_CHEMICAL_WEAPON, accuracy_decimals=2, state_class=STATE_CLASS_MEASUREMENT,