From 424c04d8baf031832c8ed2cb0c27dfd7bc3ff6c0 Mon Sep 17 00:00:00 2001 From: wrfz <77174685+wrfz@users.noreply.github.com> Date: Sat, 11 Jan 2025 11:40:26 +0100 Subject: [PATCH] Improve thermal power calculation (remove noise) --- components/daikin_rotex_can/__init__.py | 10 ++-- .../daikin_rotex_can/daikin_rotex_can.cpp | 46 +++++++++++-------- .../daikin_rotex_can/daikin_rotex_can.h | 5 +- components/daikin_rotex_can/pid.h | 46 +++++++++++++++---- 4 files changed, 71 insertions(+), 36 deletions(-) diff --git a/components/daikin_rotex_can/__init__.py b/components/daikin_rotex_can/__init__.py index e96b67f..d625396 100644 --- a/components/daikin_rotex_can/__init__.py +++ b/components/daikin_rotex_can/__init__.py @@ -1352,7 +1352,7 @@ ########## Sensors ########## CONF_THERMAL_POWER = "thermal_power" -CONF_THERMAL_POWER_SMOOTH = "thermal_power_smooth" +CONF_THERMAL_POWER_RAW = "thermal_power_raw" CONF_DUMP = "dump" CONF_DHW_RUN = "dhw_run" @@ -1441,8 +1441,8 @@ unit_of_measurement=UNIT_KILOWATT, accuracy_decimals=2, state_class=STATE_CLASS_MEASUREMENT - ).extend(), - cv.Optional(CONF_THERMAL_POWER_SMOOTH): sensor.sensor_schema( + ), + cv.Optional(CONF_THERMAL_POWER_RAW): sensor.sensor_schema( device_class=DEVICE_CLASS_POWER, unit_of_measurement=UNIT_KILOWATT, accuracy_decimals=2, @@ -1637,9 +1637,9 @@ async def set_lambda(): if yaml_sensor_conf := entities.get(CONF_THERMAL_POWER): sens = await sensor.new_sensor(yaml_sensor_conf) cg.add(var.set_thermal_power_sensor(sens)) - if yaml_sensor_conf := entities.get(CONF_THERMAL_POWER_SMOOTH): + if yaml_sensor_conf := entities.get(CONF_THERMAL_POWER_RAW): sens = await sensor.new_sensor(yaml_sensor_conf) - cg.add(var.set_thermal_power_sensor_smooth(sens)) + cg.add(var.set_thermal_power_sensor_raw(sens)) ########## Buttons ########## diff --git a/components/daikin_rotex_can/daikin_rotex_can.cpp b/components/daikin_rotex_can/daikin_rotex_can.cpp index 9ba809c..9f9fe37 100644 --- a/components/daikin_rotex_can/daikin_rotex_can.cpp +++ b/components/daikin_rotex_can/daikin_rotex_can.cpp @@ -32,8 +32,9 @@ DaikinRotexCanComponent::DaikinRotexCanComponent() , m_project_git_hash_sensor(nullptr) , m_project_git_hash() , m_thermal_power_sensor(nullptr) -, m_thermal_power_smooth_sensor(nullptr) -, m_pid(0.1, 0.01, 0.001) +, m_thermal_power_sensor_raw(nullptr) +, m_thermal_power_raw(std::numeric_limits::quiet_NaN()) +, m_pid(0.2, 0.05f, 0.05f, 0.2, 0.2, 0.1f) { } @@ -119,10 +120,6 @@ void DaikinRotexCanComponent::update_thermal_power() { CanSensor const* tv = m_entity_manager.get_sensor("tv"); CanSensor const* tr = m_entity_manager.get_sensor("tr"); - if (m_thermal_power_sensor == nullptr) { - ESP_LOGE(TAG, "thermal_power is not configured!"); - return; - } if (flow_rate == nullptr) { ESP_LOGE(TAG, "flow_rate is not configured!"); return; @@ -136,8 +133,11 @@ void DaikinRotexCanComponent::update_thermal_power() { return; } - const float value = (tv->state - tr->state) * (4.19 * flow_rate->state) / 3600.0f; - m_thermal_power_sensor->publish_state(value); + m_thermal_power_raw = (tv->state - tr->state) * (4.19 * flow_rate->state) / 3600.0f; + + if (m_thermal_power_sensor_raw != nullptr) { + m_thermal_power_sensor_raw->publish_state(m_thermal_power_raw); + } } bool DaikinRotexCanComponent::on_custom_select(std::string const& id, uint8_t value) { @@ -271,7 +271,7 @@ void DaikinRotexCanComponent::run_dhw_lambdas() { void DaikinRotexCanComponent::loop() { m_entity_manager.sendNextPendingGet(); for (auto it = m_later_calls.begin(); it != m_later_calls.end(); ) { - if (millis() > it->second) { + if (millis() > it->second) { // checking millis() here is important for callLater! it->first(); it = m_later_calls.erase(it); } else { @@ -279,20 +279,26 @@ void DaikinRotexCanComponent::loop() { } } - if (m_thermal_power_sensor != nullptr && m_thermal_power_smooth_sensor != nullptr && millis() > m_pid.get_last_update() + 10 * 1000) { - float current_tp = m_thermal_power_sensor->get_state(); + if (m_thermal_power_sensor != nullptr) { + const float dt = (millis() - m_pid.get_last_update()) / 1000.0f; // seconds + if (dt > 10.0f) { + if (!std::isnan(m_thermal_power_raw)) { + float smoothed_tp = m_thermal_power_sensor->get_state(); - const float smoothing_factor = 0.1; - float smoothed_tp = m_thermal_power_smooth_sensor->get_state(); - if (std::isnan(smoothed_tp)) { - smoothed_tp = current_tp; - } - smoothed_tp += 0.1 * (current_tp - smoothed_tp); - smoothed_tp = std::ceil(smoothed_tp * 100.0) / 100.0; + if (std::isnan(smoothed_tp)) { + smoothed_tp = m_thermal_power_raw; + } - m_thermal_power_smooth_sensor->publish_state(smoothed_tp); + const float pid_output = m_pid.compute(m_thermal_power_raw, smoothed_tp, dt); + smoothed_tp += pid_output; - m_pid.set_last_update(millis()); + float smoothed_tp_rounded = std::ceil(smoothed_tp * 100.0) / 100.0; + m_thermal_power_sensor->publish_state(smoothed_tp_rounded); + } else { + m_thermal_power_sensor->publish_state(m_thermal_power_raw); + } + m_pid.set_last_update(millis()); + } } } diff --git a/components/daikin_rotex_can/daikin_rotex_can.h b/components/daikin_rotex_can/daikin_rotex_can.h index 37e7300..5e45c55 100644 --- a/components/daikin_rotex_can/daikin_rotex_can.h +++ b/components/daikin_rotex_can/daikin_rotex_can.h @@ -28,7 +28,7 @@ class DaikinRotexCanComponent: public Component, public SensorAccessor { void set_update_interval(uint16_t seconds) {} // dummy void set_project_git_hash(text_sensor::TextSensor* pSensor, std::string const& hash) { m_project_git_hash_sensor = pSensor; m_project_git_hash = hash; } void set_thermal_power_sensor(sensor::Sensor* pSensor) { m_thermal_power_sensor = pSensor; } - void set_thermal_power_sensor_smooth(sensor::Sensor* pSensor) { m_thermal_power_smooth_sensor = pSensor; } + void set_thermal_power_sensor_raw(sensor::Sensor* pSensor) { m_thermal_power_sensor_raw = pSensor; } void set_max_spread(float tvbh_tv, float tvbh_tr) { m_max_spread = { tvbh_tv, tvbh_tr };} void add_entity(EntityBase* pEntity) { if (TEntity* pRequest = dynamic_cast(pEntity)) { @@ -93,8 +93,9 @@ class DaikinRotexCanComponent: public Component, public SensorAccessor { esphome::esp32_can::ESP32Can* m_pCanbus; sensor::Sensor* m_thermal_power_sensor; - sensor::Sensor* m_thermal_power_smooth_sensor; + sensor::Sensor* m_thermal_power_sensor_raw; MaxSpread m_max_spread; + float m_thermal_power_raw; PID m_pid; }; diff --git a/components/daikin_rotex_can/pid.h b/components/daikin_rotex_can/pid.h index 99abf4d..e68586b 100644 --- a/components/daikin_rotex_can/pid.h +++ b/components/daikin_rotex_can/pid.h @@ -1,23 +1,46 @@ #pragma once +#include "esphome/components/daikin_rotex_can/utils.h" +#include + +namespace esphome { +namespace daikin_rotex_can { + class PID { public: - PID(float p, float i, float d, float dt = 0.1) + PID(float p, float i, float d, float max_integral, float alpha_p, float alpha_d) : m_p(p) , m_i(i) , m_d(d) - , m_dt(dt) , m_previous_error(0.0f) , m_integral(0.0f) + , m_max_integral(max_integral) + , m_alpha_p(alpha_p) + , m_alpha_d(alpha_d) , m_last_update(0) { } - float compute(float setpoint, float current_value) { - float error = setpoint - current_value; - m_integral += error * m_dt; - float derivative = (error - m_previous_error) / m_dt; - float output = (m_p * error) + (m_i * m_integral) + (m_d * derivative); + float compute(float setpoint, float current_value, float dt) { + const float error = setpoint - current_value; + + // P-Anteil filtern + m_filtered_p = m_alpha_p * error + (1 - m_alpha_p) * m_filtered_p; + float p_term = m_p * m_filtered_p; + + // I-Anteil (mit Anti-Windup) + m_integral += error * dt; + if (m_integral > m_max_integral) m_integral = m_max_integral; + if (m_integral < -m_max_integral) m_integral = -m_max_integral; + float i_term = m_i * m_integral; + + // D-Anteil filtern + float derivative = (error - m_previous_error) / dt; + m_filtered_d = m_alpha_d * derivative + (1 - m_alpha_d) * m_filtered_d; + float d_term = m_d * m_filtered_d; + + // Gesamtausgang + float output = p_term + i_term + d_term; m_previous_error = error; return output; } @@ -29,8 +52,13 @@ class PID { float m_p; float m_i; float m_d; - float m_dt; float m_previous_error; float m_integral; + float m_max_integral; + float m_alpha_p, m_alpha_d; + float m_filtered_p, m_filtered_d; uint32_t m_last_update; -}; \ No newline at end of file +}; + +} +} \ No newline at end of file