6#define M_PI 3.1415926535897932384626433
12static const char *
const TAG =
"pid.autotune";
81 ESP_LOGW(TAG,
"%s: Setpoint changed during autotune! The result will not be accurate!", this->
id_.c_str());
85 float error = setpoint - process_variable;
86 const uint32_t now =
millis();
95 ESP_LOGV(TAG,
"%s: Not enough data yet for autotuner", this->
id_.c_str());
101 if (!zc_symmetrical || !amplitude_convergent) {
104 if (zc_symmetrical) {
105 ESP_LOGVV(TAG,
"%s: ZC is not symmetrical", this->
id_.c_str());
107 if (amplitude_convergent) {
108 ESP_LOGVV(TAG,
"%s: Amplitude is not convergent", this->
id_.c_str());
111 ESP_LOGVV(TAG,
"%s: >", this->
id_.c_str());
125 ESP_LOGI(TAG,
"%s: PID Autotune finished!", this->
id_.c_str());
129 ESP_LOGVV(TAG,
" Relay magnitude: %f", d);
130 this->
ku_ = 4.0f * d / float(M_PI * osc_ampl);
142 "%s: PID Autotune:\n"
143 " State: Succeeded!",
145 bool has_issue =
false;
147 ESP_LOGW(TAG,
" Could not reliably determine oscillation amplitude, PID parameters may be inaccurate!\n"
148 " Please make sure you eliminate all outside influences on the measured temperature.");
153 " Oscillation Frequency is not symmetrical. PID parameters may be inaccurate!\n"
154 " This is usually because the heat and cool processes do not change the temperature at the same "
156 " Please try reducing the positive_output value (or increase negative_output in case of a cooler)");
160 ESP_LOGI(TAG,
" All checks passed!");
165 " Calculated PID parameters (\"Ziegler-Nichols PID\" rule):\n"
167 " control_parameters:\n"
172 " Please copy these values into your YAML configuration! They will reset on the next reboot.",
173 fac.kp, fac.ki, fac.kd);
176 " Oscillation Period: %f\n"
177 " Oscillation Amplitude: %f\n"
180 this->amplitude_detector_.get_mean_oscillation_amplitude(), this->ku_, this->pu_);
182 ESP_LOGD(TAG,
" Alternative Rules:");
184 print_rule_(
"Ziegler-Nichols PI", 0.45f, 0.54f, 0.0f);
185 print_rule_(
"Pessen Integral PID", 0.7f, 1.75f, 0.105f);
186 print_rule_(
"Some Overshoot PID", 0.333f, 0.667f, 0.111f);
187 print_rule_(
"No Overshoot PID", 0.2f, 0.4f, 0.0625f);
188 ESP_LOGI(TAG,
"%s: Autotune completed", this->
id_.c_str());
193 "%s: PID Autotune:\n"
194 " Autotune is still running!\n"
195 " Status: Trying to reach %.2f °C\n"
197 " Phases: %" PRIu32
"\n"
198 " Detected %zu zero-crossings\n"
199 " Current Phase Min: %.2f, Max: %.2f",
206 float kp = kp_factor *
ku_;
207 float ki = ki_factor *
ku_ /
pu_;
208 float kd = kd_factor *
ku_ *
pu_;
219 " kp: %.5f, ki: %.5f, kd: %.5f",
220 name, fac.kp, fac.ki, fac.kd);
250 if (this->
state == FREQUENCY_DETECTOR_INIT) {
251 bool pos = error > this->noiseband;
252 state = pos ? FREQUENCY_DETECTOR_POSITIVE : FREQUENCY_DETECTOR_NEGATIVE;
255 bool had_crossing =
false;
256 if (this->
state == FREQUENCY_DETECTOR_POSITIVE && error < -this->noiseband) {
257 this->
state = FREQUENCY_DETECTOR_NEGATIVE;
259 }
else if (this->
state == FREQUENCY_DETECTOR_NEGATIVE && error > this->noiseband) {
260 this->
state = FREQUENCY_DETECTOR_POSITIVE;
266 if (this->last_zerocross != 0) {
267 uint32_t dt = now - this->last_zerocross;
268 this->zerocrossing_intervals.push_back(dt);
270 this->last_zerocross = now;
275 return this->zerocrossing_intervals.size() >= 2;
281 for (uint32_t v : this->zerocrossing_intervals)
284 float mean_value = sum / this->zerocrossing_intervals.size();
286 float mean_period = mean_value / 1000 * 2;
295 if (zerocrossing_intervals.empty())
297 uint32_t max_interval = zerocrossing_intervals[0];
298 uint32_t min_interval = zerocrossing_intervals[0];
299 for (uint32_t interval : zerocrossing_intervals) {
300 max_interval = std::max(max_interval, interval);
301 min_interval = std::min(min_interval, interval);
303 float ratio = min_interval / float(max_interval);
304 return ratio >= 0.66;
310 if (relay_state != last_relay_state) {
315 this->phase_maxs.push_back(phase_max);
320 this->phase_mins.push_back(phase_min);
323 this->phase_min = error;
324 this->phase_max = error;
326 this->last_relay_state = relay_state;
328 this->phase_min = std::min(this->phase_min, error);
329 this->phase_max = std::max(this->phase_max, error);
333 if (this->phase_maxs.size() > 7)
334 this->phase_maxs.erase(this->phase_maxs.begin());
335 if (this->phase_mins.size() > 7)
336 this->phase_mins.erase(this->phase_mins.begin());
342 return std::min(phase_mins.size(), phase_maxs.size()) >= 3;
345 float total_amplitudes = 0;
346 size_t total_amplitudes_n = 0;
347 for (
size_t i = 1; i < std::min(phase_mins.size(), phase_maxs.size()) - 1; i++) {
348 total_amplitudes += std::abs(phase_maxs[i] - phase_mins[i + 1]);
349 total_amplitudes_n++;
351 float mean_amplitude = total_amplitudes / total_amplitudes_n;
353 return mean_amplitude / 2.0f;
358 if (this->phase_mins.empty() || this->phase_maxs.empty())
361 float global_max = phase_maxs[0], global_min = phase_mins[0];
362 for (
auto v : this->phase_mins)
363 global_min = std::min(global_min, v);
364 for (
auto v : this->phase_maxs)
365 global_max = std::min(global_max, v);
366 float global_amplitude = (global_max - global_min) / 2.0f;
367 float mean_amplitude = this->get_mean_oscillation_amplitude();
368 return (mean_amplitude - global_amplitude) / (global_amplitude) < 0.05f;
PIDAutotuneResult update(float setpoint, float process_variable)
struct esphome::pid::PIDAutotuner::OscillationAmplitudeDetector amplitude_detector_
enum esphome::pid::PIDAutotuner::State state_
uint32_t enough_data_phase_
struct esphome::pid::PIDAutotuner::RelayFunction relay_function_
PIDResult calculate_pid_(float kp_factor, float ki_factor, float kd_factor)
void print_rule_(const char *name, float kp_factor, float ki_factor, float kd_factor)
PIDResult get_ziegler_nichols_pid_()
struct esphome::pid::PIDAutotuner::OscillationFrequencyDetector frequency_detector_
Providing packet encoding functions for exchanging data with a remote host.
uint32_t IRAM_ATTR HOT millis()
float get_mean_oscillation_amplitude() const
void update(float error, RelayFunction::RelayFunctionState relay_state)
bool has_enough_data() const
bool is_amplitude_convergent() const
bool is_increase_decrease_symmetrical() const
void update(uint32_t now, float error)
std::vector< uint32_t > zerocrossing_intervals
float get_mean_oscillation_period() const
bool has_enough_data() const
optional< PIDResult > result_params
enum esphome::pid::PIDAutotuner::RelayFunction::RelayFunctionState state
@ RELAY_FUNCTION_NEGATIVE
@ RELAY_FUNCTION_POSITIVE
float update(float error)
float current_target_error() const