diff --git a/CODEOWNERS b/CODEOWNERS index f50700c502b2..0cea8d48bafa 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -212,6 +212,7 @@ esphome/components/pid/* @OttoWinter esphome/components/pipsolar/* @andreashergert1984 esphome/components/pm1006/* @habbie esphome/components/pmsa003i/* @sjtrny +esphome/components/pmwcs3/* @SeByDocKy esphome/components/pn532/* @OttoWinter @jesserockz esphome/components/pn532_i2c/* @OttoWinter @jesserockz esphome/components/pn532_spi/* @OttoWinter @jesserockz diff --git a/esphome/components/pmwcs3/__init__.py b/esphome/components/pmwcs3/__init__.py new file mode 100644 index 000000000000..b04c30a00530 --- /dev/null +++ b/esphome/components/pmwcs3/__init__.py @@ -0,0 +1 @@ +CODEOWNERS = ["@SeByDocKy"] diff --git a/esphome/components/pmwcs3/pmwcs3.cpp b/esphome/components/pmwcs3/pmwcs3.cpp new file mode 100644 index 000000000000..812018b52e44 --- /dev/null +++ b/esphome/components/pmwcs3/pmwcs3.cpp @@ -0,0 +1,115 @@ +#include "pmwcs3.h" +#include "esphome/core/hal.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace pmwcs3 { + +static const uint8_t PMWCS3_I2C_ADDRESS = 0x63; +static const uint8_t PMWCS3_REG_READ_START = 0x01; +static const uint8_t PMWCS3_REG_READ_E25 = 0x02; +static const uint8_t PMWCS3_REG_READ_EC = 0x03; +static const uint8_t PMWCS3_REG_READ_TEMP = 0x04; +static const uint8_t PMWCS3_REG_READ_VWC = 0x05; +static const uint8_t PMWCS3_REG_CALIBRATE_AIR = 0x06; +static const uint8_t PMWCS3_REG_CALIBRATE_WATER = 0x07; +static const uint8_t PMWCS3_SET_I2C_ADDRESS = 0x08; +static const uint8_t PMWCS3_REG_GET_DATA = 0x09; +static const uint8_t PMWCS3_REG_CALIBRATE_EC = 0x10; +static const uint8_t PMWCS3_REG_CAP = 0x0A; +static const uint8_t PMWCS3_REG_RES = 0x0B; +static const uint8_t PMWCS3_REG_RC = 0x0C; +static const uint8_t PMWCS3_REG_RT = 0x0D; + +static const char *const TAG = "pmwcs3"; + +void PMWCS3Component::new_i2c_address(uint8_t address) { + if (!this->write_byte(PMWCS3_SET_I2C_ADDRESS, address)) { + this->status_set_warning(); + ESP_LOGW(TAG, "couldn't write the new I2C address %d", address); + return; + } + this->set_i2c_address(address); // Allows device to continue working until new firmware is written with new address. + ESP_LOGVV(TAG, "changed I2C address to %d", address); + this->status_clear_warning(); +} + +void PMWCS3Component::air_calibration() { + if (!this->write_bytes(PMWCS3_REG_CALIBRATE_AIR, nullptr, 0)) { + this->status_set_warning(); + ESP_LOGW(TAG, "couldn't start air calibration"); + return; + } + ESP_LOGW(TAG, "Start air calibration during the next 300s"); +} +void PMWCS3Component::water_calibration() { + if (!this->write_bytes(PMWCS3_REG_CALIBRATE_WATER, nullptr, 0)) { + this->status_set_warning(); + ESP_LOGW(TAG, "couldn't start water calibration"); + return; + } + ESP_LOGW(TAG, "Start water calibration during the next 300s"); +} + +void PMWCS3Component::setup() { ESP_LOGCONFIG(TAG, "Setting up PMWCS3..."); } + +void PMWCS3Component::update() { this->read_data_(); } + +float PMWCS3Component::get_setup_priority() const { return setup_priority::DATA; } + +void PMWCS3Component::dump_config() { + ESP_LOGCONFIG(TAG, "PMWCS3"); + LOG_I2C_DEVICE(this); + if (this->is_failed()) { + ESP_LOGE(TAG, "Communication with PMWCS3 failed!"); + } + ESP_LOGI(TAG, "%s", this->is_failed() ? "FAILED" : "OK"); + + LOG_UPDATE_INTERVAL(this); + LOG_SENSOR(" ", "e25", this->e25_sensor_); + LOG_SENSOR(" ", "ec", this->ec_sensor_); + LOG_SENSOR(" ", "temperature", this->temperature_sensor_); + LOG_SENSOR(" ", "vwc", this->vwc_sensor_); +} +void PMWCS3Component::read_data_() { + uint8_t data[8]; + float e25, ec, temperature, vwc; + + /////// Super important !!!! first activate reading PMWCS3_REG_READ_START (if not, return always the same values) //// + + if (!this->write_bytes(PMWCS3_REG_READ_START, nullptr, 0)) { + this->status_set_warning(); + ESP_LOGVV(TAG, "Failed to write into REG_READ_START register !!!"); + return; + } + // NOLINT delay(100); + + if (!this->read_bytes(PMWCS3_REG_GET_DATA, (uint8_t *) &data, 8)) { + ESP_LOGVV(TAG, "Error reading PMWCS3_REG_GET_DATA registers"); + this->mark_failed(); + return; + } + if (this->e25_sensor_ != nullptr) { + e25 = ((data[1] << 8) | data[0]) / 100.0; + this->e25_sensor_->publish_state(e25); + ESP_LOGVV(TAG, "e25: data[0]=%d, data[1]=%d, result=%f", data[0], data[1], e25); + } + if (this->ec_sensor_ != nullptr) { + ec = ((data[3] << 8) | data[2]) / 10.0; + this->ec_sensor_->publish_state(ec); + ESP_LOGVV(TAG, "ec: data[2]=%d, data[3]=%d, result=%f", data[2], data[3], ec); + } + if (this->temperature_sensor_ != nullptr) { + temperature = ((data[5] << 8) | data[4]) / 100.0; + this->temperature_sensor_->publish_state(temperature); + ESP_LOGVV(TAG, "temp: data[4]=%d, data[5]=%d, result=%f", data[4], data[5], temperature); + } + if (this->vwc_sensor_ != nullptr) { + vwc = ((data[7] << 8) | data[6]) / 10.0; + this->vwc_sensor_->publish_state(vwc); + ESP_LOGVV(TAG, "vwc: data[6]=%d, data[7]=%d, result=%f", data[6], data[7], vwc); + } +} + +} // namespace pmwcs3 +} // namespace esphome diff --git a/esphome/components/pmwcs3/pmwcs3.h b/esphome/components/pmwcs3/pmwcs3.h new file mode 100644 index 000000000000..f3916bb8683d --- /dev/null +++ b/esphome/components/pmwcs3/pmwcs3.h @@ -0,0 +1,70 @@ +#pragma once +#include "esphome/core/automation.h" +#include "esphome/core/component.h" +#include "esphome/components/sensor/sensor.h" +#include "esphome/components/i2c/i2c.h" + +// ref: +// https://github.com/tinovi/i2cArduino/blob/master/i2cArduino.h + +namespace esphome { +namespace pmwcs3 { + +class PMWCS3Component : public PollingComponent, public i2c::I2CDevice { + public: + void setup() override; + void update() override; + void dump_config() override; + float get_setup_priority() const override; + + void set_e25_sensor(sensor::Sensor *e25_sensor) { e25_sensor_ = e25_sensor; } + void set_ec_sensor(sensor::Sensor *ec_sensor) { ec_sensor_ = ec_sensor; } + void set_temperature_sensor(sensor::Sensor *temperature_sensor) { temperature_sensor_ = temperature_sensor; } + void set_vwc_sensor(sensor::Sensor *vwc_sensor) { vwc_sensor_ = vwc_sensor; } + + void new_i2c_address(uint8_t newaddress); + void air_calibration(); + void water_calibration(); + + protected: + void read_data_(); + + sensor::Sensor *e25_sensor_{nullptr}; + sensor::Sensor *ec_sensor_{nullptr}; + sensor::Sensor *temperature_sensor_{nullptr}; + sensor::Sensor *vwc_sensor_{nullptr}; +}; + +template class PMWCS3AirCalibrationAction : public Action { + public: + PMWCS3AirCalibrationAction(PMWCS3Component *parent) : parent_(parent) {} + + void play(Ts... x) override { this->parent_->air_calibration(); } + + protected: + PMWCS3Component *parent_; +}; + +template class PMWCS3WaterCalibrationAction : public Action { + public: + PMWCS3WaterCalibrationAction(PMWCS3Component *parent) : parent_(parent) {} + + void play(Ts... x) override { this->parent_->water_calibration(); } + + protected: + PMWCS3Component *parent_; +}; + +template class PMWCS3NewI2cAddressAction : public Action { + public: + PMWCS3NewI2cAddressAction(PMWCS3Component *parent) : parent_(parent) {} + TEMPLATABLE_VALUE(int, new_address) + + void play(Ts... x) override { this->parent_->new_i2c_address(this->new_address_.value(x...)); } + + protected: + PMWCS3Component *parent_; +}; + +} // namespace pmwcs3 +} // namespace esphome diff --git a/esphome/components/pmwcs3/sensor.py b/esphome/components/pmwcs3/sensor.py new file mode 100644 index 000000000000..81be327d14b4 --- /dev/null +++ b/esphome/components/pmwcs3/sensor.py @@ -0,0 +1,140 @@ +import esphome.codegen as cg +from esphome import automation +import esphome.config_validation as cv +from esphome.components import i2c, sensor +from esphome.const import ( + CONF_ID, + CONF_ADDRESS, + CONF_TEMPERATURE, + CONF_EC, + STATE_CLASS_MEASUREMENT, + ICON_THERMOMETER, +) + +CODEOWNERS = ["@SeByDocKy"] +DEPENDENCIES = ["i2c"] + +CONF_E25 = "e25" +CONF_VWC = "vwc" + +ICON_EPSILON = "mdi:epsilon" +ICON_SIGMA = "mdi:sigma-lower" +ICON_ALPHA = "mdi:alpha-h-circle-outline" + +pmwcs3_ns = cg.esphome_ns.namespace("pmwcs3") +PMWCS3Component = pmwcs3_ns.class_( + "PMWCS3Component", cg.PollingComponent, i2c.I2CDevice +) + +# Actions +PMWCS3AirCalibrationAction = pmwcs3_ns.class_( + "PMWCS3AirCalibrationAction", automation.Action +) +PMWCS3WaterCalibrationAction = pmwcs3_ns.class_( + "PMWCS3WaterCalibrationAction", automation.Action +) +PMWCS3NewI2cAddressAction = pmwcs3_ns.class_( + "PMWCS3NewI2cAddressAction", automation.Action +) + +CONFIG_SCHEMA = ( + cv.Schema( + { + cv.GenerateID(): cv.declare_id(PMWCS3Component), + cv.Optional(CONF_E25): sensor.sensor_schema( + icon=ICON_EPSILON, + accuracy_decimals=3, + unit_of_measurement="dS/m", + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional(CONF_EC): sensor.sensor_schema( + icon=ICON_SIGMA, + accuracy_decimals=2, + unit_of_measurement="mS/m", + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema( + icon=ICON_THERMOMETER, + accuracy_decimals=3, + unit_of_measurement="°C", + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional(CONF_VWC): sensor.sensor_schema( + icon=ICON_ALPHA, + accuracy_decimals=3, + unit_of_measurement="cm3cm−3", + state_class=STATE_CLASS_MEASUREMENT, + ), + } + ) + .extend(cv.polling_component_schema("60s")) + .extend(i2c.i2c_device_schema(0x63)) +) + + +async def to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + await cg.register_component(var, config) + await i2c.register_i2c_device(var, config) + + if CONF_E25 in config: + sens = await sensor.new_sensor(config[CONF_E25]) + cg.add(var.set_e25_sensor(sens)) + + if CONF_EC in config: + sens = await sensor.new_sensor(config[CONF_EC]) + cg.add(var.set_ec_sensor(sens)) + + if CONF_TEMPERATURE in config: + sens = await sensor.new_sensor(config[CONF_TEMPERATURE]) + cg.add(var.set_temperature_sensor(sens)) + + if CONF_VWC in config: + sens = await sensor.new_sensor(config[CONF_VWC]) + cg.add(var.set_vwc_sensor(sens)) + + +# Actions +PMWCS3_CALIBRATION_SCHEMA = cv.Schema( + { + cv.GenerateID(): cv.use_id(PMWCS3Component), + } +) + + +@automation.register_action( + "pmwcs3.air_calibration", + PMWCS3AirCalibrationAction, + PMWCS3_CALIBRATION_SCHEMA, +) +@automation.register_action( + "pmwcs3.water_calibration", + PMWCS3WaterCalibrationAction, + PMWCS3_CALIBRATION_SCHEMA, +) +async def pmwcs3_calibration_to_code(config, action_id, template_arg, args): + parent = await cg.get_variable(config[CONF_ID]) + var = cg.new_Pvariable(action_id, template_arg, parent) + return var + + +PMWCS3_NEW_I2C_ADDRESS_SCHEMA = cv.maybe_simple_value( + { + cv.GenerateID(): cv.use_id(PMWCS3Component), + cv.Required(CONF_ADDRESS): cv.templatable(cv.i2c_address), + }, + key=CONF_ADDRESS, +) + + +@automation.register_action( + "pmwcs3.new_i2c_address", + PMWCS3NewI2cAddressAction, + PMWCS3_NEW_I2C_ADDRESS_SCHEMA, +) +async def pmwcs3newi2caddress_to_code(config, action_id, template_arg, args): + parent = await cg.get_variable(config[CONF_ID]) + var = cg.new_Pvariable(action_id, template_arg, parent) + address = await cg.templatable(config[CONF_ADDRESS], args, int) + cg.add(var.set_new_address(address)) + return var diff --git a/tests/test1.yaml b/tests/test1.yaml index 66caad961af3..80cd9e3987fa 100644 --- a/tests/test1.yaml +++ b/tests/test1.yaml @@ -348,6 +348,16 @@ mcp23s17: deviceaddress: 1 sensor: + - platform: pmwcs3 + i2c_id: i2c_bus + e25: + name: pmwcs3_e25 + ec: + name: pmwcs3_ec + temperature: + name: pmwcs3_temperature + vwc: + name: pmwcs3_vwc - platform: gcja5 pm_1_0: name: "Particulate Matter <1.0µm Concentration"