Skip to content

Commit

Permalink
Improve thermal power calculation (remove noise)
Browse files Browse the repository at this point in the history
  • Loading branch information
wrfz committed Jan 11, 2025
1 parent 600f927 commit 424c04d
Show file tree
Hide file tree
Showing 4 changed files with 71 additions and 36 deletions.
10 changes: 5 additions & 5 deletions components/daikin_rotex_can/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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 ##########

Expand Down
46 changes: 26 additions & 20 deletions components/daikin_rotex_can/daikin_rotex_can.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<float>::quiet_NaN())
, m_pid(0.2, 0.05f, 0.05f, 0.2, 0.2, 0.1f)
{
}

Expand Down Expand Up @@ -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;
Expand All @@ -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) {
Expand Down Expand Up @@ -271,28 +271,34 @@ 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 {
++it;
}
}

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());
}
}
}

Expand Down
5 changes: 3 additions & 2 deletions components/daikin_rotex_can/daikin_rotex_can.h
Original file line number Diff line number Diff line change
Expand Up @@ -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<TEntity*>(pEntity)) {
Expand Down Expand Up @@ -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;
};

Expand Down
46 changes: 37 additions & 9 deletions components/daikin_rotex_can/pid.h
Original file line number Diff line number Diff line change
@@ -1,23 +1,46 @@
#pragma once

#include "esphome/components/daikin_rotex_can/utils.h"
#include <string>

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;
}
Expand All @@ -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;
};
};

}
}

0 comments on commit 424c04d

Please sign in to comment.