ESPHome 2026.11.0-dev
Loading...
Searching...
No Matches
pid_climate.cpp
Go to the documentation of this file.
1#include "pid_climate.h"
2#include "esphome/core/log.h"
3
4namespace esphome::pid {
5
6ESPHOME_LOG_TAG(TAG, "pid.climate");
7
8static constexpr uint32_t AUTOTUNE_PROGRESS_INTERVAL_ID = 0;
9
10bool PIDClimate::set_deadband_thresholds(float threshold_low, float threshold_high) {
11 if (threshold_low > threshold_high) {
12 ESP_LOGW(TAG, "Deadband threshold low %.2f must not be greater than high %.2f", threshold_low, threshold_high);
13 return false;
14 }
15
16 this->set_threshold_low(threshold_low);
17 this->set_threshold_high(threshold_high);
18 return true;
19}
20
22 this->sensor_->add_on_state_callback([this](float state) {
23 // only publish if state/current temperature has changed in two digits of precision
24 this->do_publish_ = roundf(state * 100) != roundf(this->current_temperature * 100);
26 this->update_pid_();
27 });
28 this->current_temperature = this->sensor_->state;
29
30 // register for humidity values and get initial state
31 if (this->humidity_sensor_ != nullptr) {
32 this->humidity_sensor_->add_on_state_callback([this](float state) {
33 this->current_humidity = state;
34 this->publish_state();
35 });
37 }
38
39 // restore set points
40 auto restore = this->restore_state_();
41 if (restore.has_value()) {
42 restore->to_call(this).perform();
43 } else {
44 // restore from defaults, change_away handles those for us
45 if (supports_heat_() && supports_cool_()) {
47 } else if (supports_cool_()) {
49 } else if (supports_heat_()) {
51 }
53 }
54}
56 auto call_mode = call.get_mode();
57 if (call_mode.has_value())
58 this->mode = *call_mode;
59 auto call_target = call.get_target_temperature();
60 if (call_target.has_value())
61 this->target_temperature = *call_target;
62
63 // If switching to off mode, set output immediately
65 this->write_output_(0.0f);
66
67 this->publish_state();
68}
87 LOG_CLIMATE("", "PID Climate", this);
88 ESP_LOGCONFIG(TAG,
89 " Control Parameters:\n"
90 " kp: %.5f, ki: %.5f, kd: %.5f, output samples: %d",
91 controller_.kp_, controller_.ki_, controller_.kd_, controller_.output_samples_);
92
93 if (controller_.threshold_low_ == 0 && controller_.threshold_high_ == 0) {
94 ESP_LOGCONFIG(TAG, " Deadband disabled.");
95 } else {
96 ESP_LOGCONFIG(TAG,
97 " Deadband Parameters:\n"
98 " threshold: %0.5f to %0.5f, multipliers(kp: %.5f, ki: %.5f, kd: %.5f), "
99 "output samples: %d",
100 controller_.threshold_low_, controller_.threshold_high_, controller_.kp_multiplier_,
101 controller_.ki_multiplier_, controller_.kd_multiplier_, controller_.deadband_output_samples_);
102 }
103
104 if (this->autotuner_ != nullptr) {
105 this->autotuner_->dump_config();
106 }
107}
108void PIDClimate::write_output_(float value) {
109 this->output_value_ = value;
110
111 // first ensure outputs are off (both outputs not active at the same time)
112 if (this->supports_cool_() && value >= 0)
113 this->cool_output_->set_level(0.0f);
114 if (this->supports_heat_() && value <= 0)
115 this->heat_output_->set_level(0.0f);
116
117 // value < 0 means cool, > 0 means heat
118 if (this->supports_cool_() && value < 0)
119 this->cool_output_->set_level(std::min(1.0f, -value));
120 if (this->supports_heat_() && value > 0)
121 this->heat_output_->set_level(std::min(1.0f, value));
122
123 // Update action variable for user feedback what's happening
124 climate::ClimateAction new_action;
125 if (this->supports_cool_() && value < 0) {
127 } else if (this->supports_heat_() && value > 0) {
129 } else if (this->mode == climate::CLIMATE_MODE_OFF) {
130 new_action = climate::CLIMATE_ACTION_OFF;
131 } else {
132 new_action = climate::CLIMATE_ACTION_IDLE;
133 }
134
135 if (new_action != this->action) {
136 this->action = new_action;
137 this->do_publish_ = true;
138 }
139 this->pid_computed_callback_.call();
140}
142 float value;
143 if (std::isnan(this->current_temperature) || std::isnan(this->target_temperature)) {
144 // if any control parameters are nan, turn off all outputs
145 value = 0.0;
146 } else {
147 // Update PID controller irrespective of current mode, to not mess up D/I terms
148 // In non-auto mode, we just discard the output value
149 value = this->controller_.update(this->target_temperature, this->current_temperature);
150
151 // Check autotuner
152 if (this->autotuner_ != nullptr && !this->autotuner_->is_finished()) {
153 auto res = this->autotuner_->update(this->target_temperature, this->current_temperature);
154 if (res.result_params.has_value()) {
155 this->controller_.kp_ = res.result_params->kp;
156 this->controller_.ki_ = res.result_params->ki;
157 this->controller_.kd_ = res.result_params->kd;
158 // keep autotuner instance so that subsequent dump_configs will print the long result message.
159 } else {
160 value = res.output;
161 }
162 }
163 }
164
165 if (this->mode == climate::CLIMATE_MODE_OFF) {
166 this->write_output_(0.0);
167 } else {
168 this->write_output_(value);
169 }
170
171 if (this->do_publish_)
172 this->publish_state();
173}
174void PIDClimate::start_autotune(float noiseband, float positive_output, float negative_output) {
175 auto tuner = make_unique<PIDAutotuner>();
176 tuner->set_noiseband(noiseband);
177 tuner->set_output_positive(positive_output);
178 tuner->set_output_negative(negative_output);
179 this->start_autotune(std::move(tuner));
180}
181
182void PIDClimate::start_autotune(std::unique_ptr<PIDAutotuner> &&autotune) {
183 this->autotuner_ = std::move(autotune);
184 float min_value = this->supports_cool_() ? -1.0f : 0.0f;
185 float max_value = this->supports_heat_() ? 1.0f : 0.0f;
186 this->autotuner_->config(min_value, max_value);
187 this->autotuner_->set_autotuner_id(this->get_log_name());
188
189 ESP_LOGI(TAG,
190 "%s: Autotune has started. This can take a long time depending on the "
191 "responsiveness of your system. Your system "
192 "output will be altered to deliberately oscillate above and below the setpoint multiple times. "
193 "Until your sensor provides a reading, the autotuner may display \'nan\'",
194 LOG_STR_ARG(this->get_log_name()));
195
196 this->set_interval(AUTOTUNE_PROGRESS_INTERVAL_ID, 10000, [this]() {
197 if (this->autotuner_ != nullptr && !this->autotuner_->is_finished())
198 this->autotuner_->dump_config();
199 });
200
202 ESP_LOGW(TAG, "%s: !!! For PID autotuner you need to set AUTO (also called heat/cool) mode!",
203 LOG_STR_ARG(this->get_log_name()));
204 }
205}
206
208
209} // namespace esphome::pid
void set_interval(const char *name, uint32_t interval, std::function< void()> &&f)
Set an interval function with a const char* name.
Definition component.cpp:88
const LogString * get_log_name() const
Get the name for a "%s" log argument (LOG_STR_ARG).
This class is used to encode all control actions on a climate device.
Definition climate.h:31
ClimateMode mode
The active mode of the climate device.
Definition climate.h:317
float target_temperature
The target temperature of the climate device.
Definition climate.h:298
float current_humidity
The current humidity of the climate device, as reported from the integration.
Definition climate.h:294
float current_temperature
The current temperature of the climate device, as reported from the integration.
Definition climate.h:291
ClimateAction action
The active state of the climate device.
Definition climate.h:320
void publish_state()
Publish the state of the climate device, to be called from integrations.
Definition climate.cpp:432
optional< ClimateDeviceRestoreState > restore_state_()
Restore the state of the climate device, call this from your setup() method.
Definition climate.cpp:365
void add_feature_flags(uint32_t feature_flags)
void set_supported_modes(ClimateModeMask modes)
void add_supported_mode(ClimateMode mode)
void set_level(float state)
Set the level of this float output, this is called from the front-end.
void start_autotune(std::unique_ptr< PIDAutotuner > &&autotune)
PIDController controller_
output::FloatOutput * cool_output_
sensor::Sensor * sensor_
The sensor used for getting the current temperature.
void set_threshold_high(float in)
Definition pid_climate.h:38
void dump_config() override
bool supports_cool_() const
Definition pid_climate.h:94
void write_output_(float value)
std::unique_ptr< PIDAutotuner > autotuner_
void control(const climate::ClimateCall &call) override
Override control to change settings of the climate device.
CallbackManager< void()> pid_computed_callback_
float output_value_
Output value as reported by the PID controller, for PIDClimateSensor.
bool set_deadband_thresholds(float threshold_low, float threshold_high)
sensor::Sensor * humidity_sensor_
The sensor used for getting the current humidity.
climate::ClimateTraits traits() override
Return the traits of this controller.
bool supports_heat_() const
Definition pid_climate.h:95
void set_threshold_low(float in)
Definition pid_climate.h:37
output::FloatOutput * heat_output_
void add_on_state_callback(F &&callback)
Add a callback that will be called every time a filtered value arrives.
Definition sensor.h:121
float state
This member variable stores the last state that has passed through all filters.
Definition sensor.h:140
bool state
Definition fan.h:2
@ CLIMATE_SUPPORTS_CURRENT_HUMIDITY
@ CLIMATE_SUPPORTS_CURRENT_TEMPERATURE
@ CLIMATE_MODE_HEAT
The climate device is set to heat to reach the target temperature.
@ CLIMATE_MODE_COOL
The climate device is set to cool to reach the target temperature.
@ CLIMATE_MODE_HEAT_COOL
The climate device is set to heat/cool to reach the target temperature.
@ CLIMATE_MODE_OFF
The climate device is off.
ClimateAction
Enum for the current action of the climate device. Values match those of ClimateMode.
@ CLIMATE_ACTION_OFF
The climate device is off (inactive or no power)
@ CLIMATE_ACTION_IDLE
The climate device is idle (monitoring climate but no action needed)
@ CLIMATE_ACTION_HEATING
The climate device is actively heating.
@ CLIMATE_ACTION_COOLING
The climate device is actively cooling.
ESPHOME_LOG_TAG(TAG, "pid.autotune")
static void uint32_t
float update(float setpoint, float process_value)