diff --git a/CODEOWNERS b/CODEOWNERS index 8d297d7b07..03f41618af 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -217,6 +217,7 @@ esphome/components/hbridge/light/* @DotNetDann esphome/components/hbridge/switch/* @dwmw2 esphome/components/hc8/* @omartijn esphome/components/hdc2010/* @optimusprimespace @ssieb +esphome/components/hdc2080/* @G-Pereira @jesserockz esphome/components/hdc302x/* @joshuasing esphome/components/he60r/* @clydebarrow esphome/components/heatpumpir/* @rob-deutsch diff --git a/esphome/components/hdc2080/__init__.py b/esphome/components/hdc2080/__init__.py new file mode 100644 index 0000000000..341ea61048 --- /dev/null +++ b/esphome/components/hdc2080/__init__.py @@ -0,0 +1 @@ +CODEOWNERS = ["@G-Pereira", "@jesserockz"] diff --git a/esphome/components/hdc2080/hdc2080.cpp b/esphome/components/hdc2080/hdc2080.cpp new file mode 100644 index 0000000000..dcb207e099 --- /dev/null +++ b/esphome/components/hdc2080/hdc2080.cpp @@ -0,0 +1,71 @@ +#include "hdc2080.h" +#include "esphome/core/hal.h" +#include "esphome/core/log.h" + +namespace esphome::hdc2080 { + +static const char *const TAG = "hdc2080"; + +// Register map (Table 8-6) +static constexpr uint8_t REG_TEMPERATURE_LOW = 0x00; // Temperature [7:0] +static constexpr uint8_t REG_TEMPERATURE_HIGH = 0x01; // Temperature [15:8] +static constexpr uint8_t REG_HUMIDITY_LOW = 0x02; // Humidity [7:0] +static constexpr uint8_t REG_HUMIDITY_HIGH = 0x03; // Humidity [15:8] +static constexpr uint8_t REG_RESET_DRDY_INT_CONF = 0x0E; // Soft Reset and Interrupt Configuration +static constexpr uint8_t REG_MEASUREMENT_CONFIGURATION = 0x0F; + +// Measurement register (0x0F) bit fields +static constexpr uint8_t MEAS_TRIG = 0x01; // Bit 0: start measurement +static constexpr uint8_t MEAS_CONF_TEMP = 0x02; // Bits 2:1 = 01: temperature only +static constexpr uint8_t MEAS_CONF_HUM = 0x04; // Bits 2:1 = 10: humidity only + +void HDC2080Component::setup() { + const uint8_t data = 0x00; // automatic measurement mode disabled, heater off + if (this->write_register(REG_RESET_DRDY_INT_CONF, &data, 1) != i2c::ERROR_OK) { + this->mark_failed(ESP_LOG_MSG_COMM_FAIL); + return; + } +} + +void HDC2080Component::dump_config() { + ESP_LOGCONFIG(TAG, "HDC2080:"); + LOG_I2C_DEVICE(this); + LOG_UPDATE_INTERVAL(this); + LOG_SENSOR(" ", "Temperature", this->temperature_sensor_); + LOG_SENSOR(" ", "Humidity", this->humidity_sensor_); + if (this->is_failed()) { + ESP_LOGE(TAG, ESP_LOG_MSG_COMM_FAIL); + } +} + +void HDC2080Component::update() { + uint8_t data = MEAS_TRIG; // 14-bit resolution, measure both, start + if (this->temperature_sensor_ != nullptr && this->humidity_sensor_ == nullptr) { + data = MEAS_TRIG | MEAS_CONF_TEMP; + } else if (this->temperature_sensor_ == nullptr && this->humidity_sensor_ != nullptr) { + data = MEAS_TRIG | MEAS_CONF_HUM; + } + if (this->write_register(REG_MEASUREMENT_CONFIGURATION, &data, 1) != i2c::ERROR_OK) { + this->status_set_warning(ESP_LOG_MSG_COMM_FAIL); + return; + } + // wait for conversion to complete 2ms should be enough, more is fine + this->set_timeout(5, [this]() { + uint8_t raw_data[4]; + if (this->read_register(REG_TEMPERATURE_LOW, raw_data, 4) != i2c::ERROR_OK) { + this->status_set_warning(ESP_LOG_MSG_COMM_FAIL); + return; + } + this->status_clear_warning(); + if (this->temperature_sensor_ != nullptr) { + float temp = encode_uint16(raw_data[1], raw_data[0]) * (165.0f / 65536.0f) - 40.5f; + this->temperature_sensor_->publish_state(temp); + } + if (this->humidity_sensor_ != nullptr) { + float humidity = encode_uint16(raw_data[3], raw_data[2]) * (100.0f / 65536.0f); + this->humidity_sensor_->publish_state(humidity); + } + }); +} + +} // namespace esphome::hdc2080 diff --git a/esphome/components/hdc2080/hdc2080.h b/esphome/components/hdc2080/hdc2080.h new file mode 100644 index 0000000000..daa10d371d --- /dev/null +++ b/esphome/components/hdc2080/hdc2080.h @@ -0,0 +1,24 @@ +#pragma once + +#include "esphome/components/i2c/i2c.h" +#include "esphome/components/sensor/sensor.h" +#include "esphome/core/component.h" + +namespace esphome::hdc2080 { + +class HDC2080Component : public PollingComponent, public i2c::I2CDevice { + public: + void set_temperature(sensor::Sensor *temperature) { this->temperature_sensor_ = temperature; } + void set_humidity(sensor::Sensor *humidity) { this->humidity_sensor_ = humidity; } + + /// Setup the sensor and check for connection. + void setup() override; + void dump_config() override; + void update() override; + + protected: + sensor::Sensor *temperature_sensor_{nullptr}; + sensor::Sensor *humidity_sensor_{nullptr}; +}; + +} // namespace esphome::hdc2080 diff --git a/esphome/components/hdc2080/sensor.py b/esphome/components/hdc2080/sensor.py new file mode 100644 index 0000000000..777fc51cba --- /dev/null +++ b/esphome/components/hdc2080/sensor.py @@ -0,0 +1,57 @@ +import esphome.codegen as cg +from esphome.components import i2c, sensor +import esphome.config_validation as cv +from esphome.const import ( + CONF_HUMIDITY, + CONF_ID, + CONF_TEMPERATURE, + DEVICE_CLASS_HUMIDITY, + DEVICE_CLASS_TEMPERATURE, + STATE_CLASS_MEASUREMENT, + UNIT_CELSIUS, + UNIT_PERCENT, +) + +DEPENDENCIES = ["i2c"] + +hdc2080_ns = cg.esphome_ns.namespace("hdc2080") +HDC2080Component = hdc2080_ns.class_( + "HDC2080Component", cg.PollingComponent, i2c.I2CDevice +) + +CONFIG_SCHEMA = ( + cv.Schema( + { + cv.GenerateID(): cv.declare_id(HDC2080Component), + cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema( + unit_of_measurement=UNIT_CELSIUS, + accuracy_decimals=1, + device_class=DEVICE_CLASS_TEMPERATURE, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional(CONF_HUMIDITY): sensor.sensor_schema( + unit_of_measurement=UNIT_PERCENT, + accuracy_decimals=0, + device_class=DEVICE_CLASS_HUMIDITY, + state_class=STATE_CLASS_MEASUREMENT, + ), + } + ) + .extend(cv.polling_component_schema("60s")) + .extend(i2c.i2c_device_schema(0x40)) + .add_extra(cv.has_at_least_one_key(CONF_TEMPERATURE, CONF_HUMIDITY)) +) + + +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 temperature_config := config.get(CONF_TEMPERATURE): + sens = await sensor.new_sensor(temperature_config) + cg.add(var.set_temperature(sens)) + + if humidity_config := config.get(CONF_HUMIDITY): + sens = await sensor.new_sensor(humidity_config) + cg.add(var.set_humidity(sens)) diff --git a/tests/components/hdc2080/common.yaml b/tests/components/hdc2080/common.yaml new file mode 100644 index 0000000000..cb14cb183b --- /dev/null +++ b/tests/components/hdc2080/common.yaml @@ -0,0 +1,7 @@ +sensor: + - platform: hdc2080 + temperature: + name: Temperature + humidity: + name: Humidity + update_interval: 15s diff --git a/tests/components/hdc2080/test.esp32-idf.yaml b/tests/components/hdc2080/test.esp32-idf.yaml new file mode 100644 index 0000000000..b47e39c389 --- /dev/null +++ b/tests/components/hdc2080/test.esp32-idf.yaml @@ -0,0 +1,4 @@ +packages: + i2c: !include ../../test_build_components/common/i2c/esp32-idf.yaml + +<<: !include common.yaml diff --git a/tests/components/hdc2080/test.esp8266-ard.yaml b/tests/components/hdc2080/test.esp8266-ard.yaml new file mode 100644 index 0000000000..4a98b9388a --- /dev/null +++ b/tests/components/hdc2080/test.esp8266-ard.yaml @@ -0,0 +1,4 @@ +packages: + i2c: !include ../../test_build_components/common/i2c/esp8266-ard.yaml + +<<: !include common.yaml diff --git a/tests/components/hdc2080/test.rp2040-ard.yaml b/tests/components/hdc2080/test.rp2040-ard.yaml new file mode 100644 index 0000000000..319a7c71a6 --- /dev/null +++ b/tests/components/hdc2080/test.rp2040-ard.yaml @@ -0,0 +1,4 @@ +packages: + i2c: !include ../../test_build_components/common/i2c/rp2040-ard.yaml + +<<: !include common.yaml