diff --git a/CODEOWNERS b/CODEOWNERS index 24b4bd362d..08189a13b3 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -80,6 +80,7 @@ esphome/components/rtttl/* @glmnet esphome/components/script/* @esphome/core esphome/components/sensor/* @esphome/core esphome/components/sgp40/* @SenexCrenshaw +esphome/components/sht4x/* @sjtrny esphome/components/shutdown/* @esphome/core esphome/components/sim800l/* @glmnet esphome/components/spi/* @esphome/core diff --git a/esphome/components/sht4x/__init__.py b/esphome/components/sht4x/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/esphome/components/sht4x/sensor.py b/esphome/components/sht4x/sensor.py new file mode 100644 index 0000000000..bd3e5c108c --- /dev/null +++ b/esphome/components/sht4x/sensor.py @@ -0,0 +1,92 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import i2c, sensor +from esphome.const import ( + CONF_ID, + CONF_TEMPERATURE, + CONF_HUMIDITY, + UNIT_CELSIUS, + UNIT_PERCENT, + ICON_THERMOMETER, + ICON_WATER_PERCENT, + DEVICE_CLASS_TEMPERATURE, + DEVICE_CLASS_HUMIDITY, +) + +CODEOWNERS = ["@sjtrny"] +DEPENDENCIES = ["i2c"] + +sht4x_ns = cg.esphome_ns.namespace("sht4x") + +SHT4XComponent = sht4x_ns.class_("SHT4XComponent", cg.PollingComponent, i2c.I2CDevice) + +CONF_PRECISION = "precision" +SHT4XPRECISION = sht4x_ns.enum("SHT4XPRECISION") +PRECISION_OPTIONS = { + "High": SHT4XPRECISION.SHT4X_PRECISION_HIGH, + "Med": SHT4XPRECISION.SHT4X_PRECISION_MED, + "Low": SHT4XPRECISION.SHT4X_PRECISION_LOW, +} + +CONF_HEATER_POWER = "heater_power" +SHT4XHEATERPOWER = sht4x_ns.enum("SHT4XHEATERPOWER") +HEATER_POWER_OPTIONS = { + "High": SHT4XHEATERPOWER.SHT4X_HEATERPOWER_HIGH, + "Med": SHT4XHEATERPOWER.SHT4X_HEATERPOWER_MED, + "Low": SHT4XHEATERPOWER.SHT4X_HEATERPOWER_LOW, +} + +CONF_HEATER_TIME = "heater_time" +SHT4XHEATERTIME = sht4x_ns.enum("SHT4XHEATERTIME") +HEATER_TIME_OPTIONS = { + "Long": SHT4XHEATERTIME.SHT4X_HEATERTIME_LONG, + "Short": SHT4XHEATERTIME.SHT4X_HEATERTIME_SHORT, +} + +CONF_HEATER_MAX_DUTY = "heater_max_duty" + +CONFIG_SCHEMA = ( + cv.Schema( + { + cv.GenerateID(): cv.declare_id(SHT4XComponent), + cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema( + UNIT_CELSIUS, ICON_THERMOMETER, 2, DEVICE_CLASS_TEMPERATURE + ), + cv.Optional(CONF_HUMIDITY): sensor.sensor_schema( + UNIT_PERCENT, ICON_WATER_PERCENT, 2, DEVICE_CLASS_HUMIDITY + ), + cv.Optional(CONF_PRECISION, default="High"): cv.enum(PRECISION_OPTIONS), + cv.Optional(CONF_HEATER_POWER, default="High"): cv.enum( + HEATER_POWER_OPTIONS + ), + cv.Optional(CONF_HEATER_TIME, default="Long"): cv.enum(HEATER_TIME_OPTIONS), + cv.Optional(CONF_HEATER_MAX_DUTY, default=0.0): cv.float_range( + min=0.0, max=0.05 + ), + } + ) + .extend(cv.polling_component_schema("60s")) + .extend(i2c.i2c_device_schema(0x44)) +) + +TYPES = { + CONF_TEMPERATURE: "set_temp_sensor", + CONF_HUMIDITY: "set_humidity_sensor", +} + + +def to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + yield cg.register_component(var, config) + yield i2c.register_i2c_device(var, config) + + cg.add(var.set_precision_value(config[CONF_PRECISION])) + cg.add(var.set_heater_power_value(config[CONF_HEATER_POWER])) + cg.add(var.set_heater_time_value(config[CONF_HEATER_TIME])) + cg.add(var.set_heater_duty_value(config[CONF_HEATER_MAX_DUTY])) + + for key, funcName in TYPES.items(): + + if key in config: + sens = yield sensor.new_sensor(config[key]) + cg.add(getattr(var, funcName)(sens)) diff --git a/esphome/components/sht4x/sht4x.cpp b/esphome/components/sht4x/sht4x.cpp new file mode 100644 index 0000000000..a4b315940d --- /dev/null +++ b/esphome/components/sht4x/sht4x.cpp @@ -0,0 +1,89 @@ +#include "sht4x.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace sht4x { + +static const char *TAG = "sht4x"; + +static const uint8_t MEASURECOMMANDS[] = {0xFD, 0xF6, 0xE0}; + +void SHT4XComponent::start_heater_() { + uint8_t cmd[] = {MEASURECOMMANDS[this->heater_command_]}; + + ESP_LOGD(TAG, "Heater turning on"); + this->write_bytes_raw(cmd, 1); +} + +void SHT4XComponent::setup() { + ESP_LOGCONFIG(TAG, "Setting up sht4x..."); + + if (this->duty_cycle_ > 0.0) { + uint32_t heater_interval = (uint32_t)(this->heater_time_ / this->duty_cycle_); + ESP_LOGD(TAG, "Heater interval: %i", heater_interval); + + if (this->heater_power_ == SHT4X_HEATERPOWER_HIGH) { + if (this->heater_time_ == SHT4X_HEATERTIME_LONG) { + this->heater_command_ = 0x39; + } else { + this->heater_command_ = 0x32; + } + } else if (this->heater_power_ == SHT4X_HEATERPOWER_MED) { + if (this->heater_time_ == SHT4X_HEATERTIME_LONG) { + this->heater_command_ = 0x2F; + } else { + this->heater_command_ = 0x24; + } + } else { + if (this->heater_time_ == SHT4X_HEATERTIME_LONG) { + this->heater_command_ = 0x1E; + } else { + this->heater_command_ = 0x15; + } + } + ESP_LOGD(TAG, "Heater command: %x", this->heater_command_); + + this->set_interval(heater_interval, std::bind(&SHT4XComponent::start_heater_, this)); + } +} + +void SHT4XComponent::dump_config() { LOG_I2C_DEVICE(this); } + +void SHT4XComponent::update() { + uint8_t cmd[] = {MEASURECOMMANDS[this->precision_]}; + + // Send command + this->write_bytes_raw(cmd, 1); + + this->set_timeout(10, [this]() { + const uint8_t num_bytes = 6; + uint8_t buffer[num_bytes]; + + // Read measurement + bool read_status = this->read_bytes_raw(buffer, num_bytes); + + 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]; + 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]; + float rh = -6 + 125 * sensor_value_rh / 65535; + + this->humidity_sensor_->publish_state(rh); + } + } else { + ESP_LOGD(TAG, "Sensor read failed"); + } + }); +} + +} // namespace sht4x +} // namespace esphome diff --git a/esphome/components/sht4x/sht4x.h b/esphome/components/sht4x/sht4x.h new file mode 100644 index 0000000000..8694bd9879 --- /dev/null +++ b/esphome/components/sht4x/sht4x.h @@ -0,0 +1,45 @@ +#pragma once + +#include "esphome/core/component.h" +#include "esphome/components/sensor/sensor.h" +#include "esphome/components/i2c/i2c.h" + +namespace esphome { +namespace sht4x { + +enum SHT4XPRECISION { SHT4X_PRECISION_HIGH = 0, SHT4X_PRECISION_MED, SHT4X_PRECISION_LOW }; + +enum SHT4XHEATERPOWER { SHT4X_HEATERPOWER_HIGH, SHT4X_HEATERPOWER_MED, SHT4X_HEATERPOWER_LOW }; + +enum SHT4XHEATERTIME { SHT4X_HEATERTIME_LONG = 1100, SHT4X_HEATERTIME_SHORT = 110 }; + +class SHT4XComponent : public PollingComponent, public i2c::I2CDevice { + public: + float get_setup_priority() const override { return setup_priority::DATA; } + void setup() override; + void dump_config() override; + void update() override; + + void set_precision_value(SHT4XPRECISION precision) { this->precision_ = precision; }; + void set_heater_power_value(SHT4XHEATERPOWER heater_power) { this->heater_power_ = heater_power; }; + void set_heater_time_value(SHT4XHEATERTIME heater_time) { this->heater_time_ = heater_time; }; + void set_heater_duty_value(float duty_cycle) { this->duty_cycle_ = duty_cycle; }; + + void set_temp_sensor(sensor::Sensor *temp_sensor) { this->temp_sensor_ = temp_sensor; } + void set_humidity_sensor(sensor::Sensor *humidity_sensor) { this->humidity_sensor_ = humidity_sensor; } + + protected: + SHT4XPRECISION precision_; + SHT4XHEATERPOWER heater_power_; + SHT4XHEATERTIME heater_time_; + float duty_cycle_; + + void start_heater_(); + uint8_t heater_command_; + + sensor::Sensor *temp_sensor_{nullptr}; + sensor::Sensor *humidity_sensor_{nullptr}; +}; + +} // namespace sht4x +} // namespace esphome diff --git a/tests/test1.yaml b/tests/test1.yaml index 8742387a7d..8a93de09b1 100644 --- a/tests/test1.yaml +++ b/tests/test1.yaml @@ -741,6 +741,13 @@ sensor: id: 'workshop_PMC_10_0' address: 0x69 update_interval: 10s + - platform: sht4x + temperature: + name: 'SHT4X Temperature' + humidity: + name: 'SHT4X Humidity' + address: 0x44 + update_interval: 15s - platform: shtcx temperature: name: 'Living Room Temperature 10'