diff --git a/CODEOWNERS b/CODEOWNERS index 12aff01e73..e72b164761 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -132,6 +132,7 @@ esphome/components/dashboard_import/* @esphome/core esphome/components/datetime/* @jesserockz @rfdarter esphome/components/debug/* @esphome/core esphome/components/delonghi/* @grob6000 +esphome/components/dew_point/* @CFlix esphome/components/dfplayer/* @glmnet esphome/components/dfrobot_sen0395/* @niklasweber esphome/components/dht/* @OttoWinter diff --git a/esphome/components/dew_point/__init__.py b/esphome/components/dew_point/__init__.py new file mode 100644 index 0000000000..3b852436c3 --- /dev/null +++ b/esphome/components/dew_point/__init__.py @@ -0,0 +1 @@ +CODEOWNERS = ["@CFlix"] diff --git a/esphome/components/dew_point/dew_point.cpp b/esphome/components/dew_point/dew_point.cpp new file mode 100644 index 0000000000..04ac305e3d --- /dev/null +++ b/esphome/components/dew_point/dew_point.cpp @@ -0,0 +1,82 @@ + +#include "dew_point.h" + +namespace esphome::dew_point { + +static const char *const TAG = "dew_point.sensor"; + +void DewPointComponent::setup() { + // Register callbacks for sensor updates + if (this->temperature_sensor_ != nullptr) { + this->temperature_sensor_->add_on_state_callback([this](float state) { + this->temperature_value_ = state; + this->enable_loop(); + }); + // Get initial value + if (this->temperature_sensor_->has_state()) { + this->temperature_value_ = this->temperature_sensor_->get_state(); + } + } + + if (this->humidity_sensor_ != nullptr) { + this->humidity_sensor_->add_on_state_callback([this](float state) { + this->humidity_value_ = state; + this->enable_loop(); + }); + // Get initial value + if (this->humidity_sensor_->has_state()) { + this->humidity_value_ = this->humidity_sensor_->get_state(); + } + } +} + +void DewPointComponent::dump_config() { + LOG_SENSOR("", "Dew Point", this); + ESP_LOGCONFIG(TAG, + "Sources\n" + " Temperature: '%s'\n" + " Humidity: '%s'", + this->temperature_sensor_->get_name().c_str(), this->humidity_sensor_->get_name().c_str()); +} + +float DewPointComponent::get_setup_priority() const { return setup_priority::DATA; } + +void DewPointComponent::loop() { + // Only run once + this->disable_loop(); + + // Check if we have valid values for both sensors + if (std::isnan(this->temperature_value_) || std::isnan(this->humidity_value_)) { + ESP_LOGW(TAG, "Temperature or humidity value is NaN, skipping calculation"); + this->publish_state(NAN); + return; + } + + // Check for valid humidity range + if (this->humidity_value_ <= 0.0f || this->humidity_value_ > 100.0f) { + ESP_LOGW(TAG, "Humidity value out of range (0-100): %.2f", this->humidity_value_); + this->publish_state(NAN); + return; + } + + // Magnus formula constants + const float a{17.625f}; + const float b{243.04f}; + + // Calculate dew point using Magnus formula + // Td = (b * alpha) / (a - alpha) + // where alpha = ln(RH/100) + (a * T) / (b + T) + + const float alpha{std::log(this->humidity_value_ / 100.0f) + + (a * this->temperature_value_) / (b + this->temperature_value_)}; + + const float dew_point{(b * alpha) / (a - alpha)}; + + // Publish the calculated dew point + this->publish_state(dew_point); + + ESP_LOGD(TAG, "'%s' >> %.1f°C (T: %.1f°C, RH: %.1f%%)", this->get_name().c_str(), dew_point, this->temperature_value_, + this->humidity_value_); +} + +} // namespace esphome::dew_point diff --git a/esphome/components/dew_point/dew_point.h b/esphome/components/dew_point/dew_point.h new file mode 100644 index 0000000000..833c50fba2 --- /dev/null +++ b/esphome/components/dew_point/dew_point.h @@ -0,0 +1,26 @@ +#pragma once + +#include "esphome/core/component.h" +#include "esphome/components/sensor/sensor.h" + +namespace esphome::dew_point { + +class DewPointComponent : public Component, public sensor::Sensor { + public: + void set_temperature_sensor(sensor::Sensor *temperature_sensor) { this->temperature_sensor_ = temperature_sensor; } + void set_humidity_sensor(sensor::Sensor *humidity_sensor) { this->humidity_sensor_ = humidity_sensor; } + + void setup() override; + void dump_config() override; + void loop() override; + + float get_setup_priority() const override; + + protected: + sensor::Sensor *temperature_sensor_{nullptr}; + sensor::Sensor *humidity_sensor_{nullptr}; + float temperature_value_{NAN}; + float humidity_value_{NAN}; +}; + +} // namespace esphome::dew_point diff --git a/esphome/components/dew_point/sensor.py b/esphome/components/dew_point/sensor.py new file mode 100644 index 0000000000..4fee095602 --- /dev/null +++ b/esphome/components/dew_point/sensor.py @@ -0,0 +1,46 @@ +import esphome.codegen as cg +from esphome.components import sensor +import esphome.config_validation as cv +from esphome.const import ( + CONF_HUMIDITY, + CONF_TEMPERATURE, + DEVICE_CLASS_TEMPERATURE, + STATE_CLASS_MEASUREMENT, + UNIT_CELSIUS, +) + +DEPENDENCIES = ["sensor"] + +dew_point_ns = cg.esphome_ns.namespace("dew_point") +DewPointComponent = dew_point_ns.class_( + "DewPointComponent", cg.Component, sensor.Sensor +) + +CONFIG_SCHEMA = ( + sensor.sensor_schema( + DewPointComponent, + unit_of_measurement=UNIT_CELSIUS, + accuracy_decimals=1, + device_class=DEVICE_CLASS_TEMPERATURE, + state_class=STATE_CLASS_MEASUREMENT, + icon="mdi:weather-rainy", + ) + .extend( + { + cv.Required(CONF_TEMPERATURE): cv.use_id(sensor.Sensor), + cv.Required(CONF_HUMIDITY): cv.use_id(sensor.Sensor), + } + ) + .extend(cv.COMPONENT_SCHEMA) +) + + +async def to_code(config): + var = await sensor.new_sensor(config) + await cg.register_component(var, config) + + temperature_sensor = await cg.get_variable(config[CONF_TEMPERATURE]) + cg.add(var.set_temperature_sensor(temperature_sensor)) + + humidity_sensor = await cg.get_variable(config[CONF_HUMIDITY]) + cg.add(var.set_humidity_sensor(humidity_sensor)) diff --git a/tests/components/dew_point/common.yaml b/tests/components/dew_point/common.yaml new file mode 100644 index 0000000000..527eeb2f84 --- /dev/null +++ b/tests/components/dew_point/common.yaml @@ -0,0 +1,19 @@ +sensor: + - platform: dew_point + name: Dew Point + temperature: template_temperature + humidity: template_humidity + - platform: template + id: template_humidity + lambda: |- + if (millis() > 10000) { + return 0.6; + } + return 0.0; + - platform: template + id: template_temperature + lambda: |- + if (millis() > 10000) { + return 42.0; + } + return 0.0; diff --git a/tests/components/dew_point/test.esp32-idf.yaml b/tests/components/dew_point/test.esp32-idf.yaml new file mode 100644 index 0000000000..dade44d145 --- /dev/null +++ b/tests/components/dew_point/test.esp32-idf.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/dew_point/test.esp8266-ard.yaml b/tests/components/dew_point/test.esp8266-ard.yaml new file mode 100644 index 0000000000..dade44d145 --- /dev/null +++ b/tests/components/dew_point/test.esp8266-ard.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/dew_point/test.rp2040-ard.yaml b/tests/components/dew_point/test.rp2040-ard.yaml new file mode 100644 index 0000000000..dade44d145 --- /dev/null +++ b/tests/components/dew_point/test.rp2040-ard.yaml @@ -0,0 +1 @@ +<<: !include common.yaml