8static const char *
const TAG =
"pid.autotune";
77 ESP_LOGW(TAG,
"%s: Setpoint changed during autotune! The result will not be accurate!", this->
id_.c_str());
81 float error = setpoint - process_variable;
91 ESP_LOGV(TAG,
"%s: Not enough data yet for autotuner", this->
id_.c_str());
97 if (!zc_symmetrical || !amplitude_convergent) {
100 if (!zc_symmetrical) {
101 ESP_LOGVV(TAG,
"%s: ZC is not symmetrical", this->
id_.c_str());
103 if (!amplitude_convergent) {
104 ESP_LOGVV(TAG,
"%s: Amplitude is not convergent", this->
id_.c_str());
107 ESP_LOGVV(TAG,
"%s: >", this->
id_.c_str());
121 ESP_LOGI(TAG,
"%s: PID Autotune finished!", this->
id_.c_str());
125 ESP_LOGVV(TAG,
" Relay magnitude: %f", d);
126 this->
ku_ = 4.0f * d / (std::numbers::pi_v<float> * osc_ampl);
138 "%s: PID Autotune:\n"
139 " State: Succeeded!",
141 bool has_issue =
false;
143 ESP_LOGW(TAG,
" Could not reliably determine oscillation amplitude, PID parameters may be inaccurate!\n"
144 " Please make sure you eliminate all outside influences on the measured temperature.");
149 " Oscillation Frequency is not symmetrical. PID parameters may be inaccurate!\n"
150 " This is usually because the heat and cool processes do not change the temperature at the same "
152 " Please try reducing the positive_output value (or increase negative_output in case of a cooler)");
156 ESP_LOGI(TAG,
" All checks passed!");
161 " Calculated PID parameters (\"Ziegler-Nichols PID\" rule):\n"
163 " control_parameters:\n"
168 " Please copy these values into your YAML configuration! They will reset on the next reboot.",
169 fac.kp, fac.ki, fac.kd);
172 " Oscillation Period: %f\n"
173 " Oscillation Amplitude: %f\n"
176 this->amplitude_detector_.get_mean_oscillation_amplitude(), this->ku_, this->pu_);
178 ESP_LOGD(TAG,
" Alternative Rules:");
180 print_rule_(
"Ziegler-Nichols PI", 0.45f, 0.54f, 0.0f);
181 print_rule_(
"Pessen Integral PID", 0.7f, 1.75f, 0.105f);
182 print_rule_(
"Some Overshoot PID", 0.333f, 0.667f, 0.111f);
183 print_rule_(
"No Overshoot PID", 0.2f, 0.4f, 0.0625f);
184 ESP_LOGI(TAG,
"%s: Autotune completed", this->
id_.c_str());
189 "%s: PID Autotune:\n"
190 " Autotune is still running!\n"
191 " Status: Trying to reach %.2f °C\n"
193 " Phases: %" PRIu32
"\n"
194 " Detected %zu zero-crossings\n"
195 " Current Phase Min: %.2f, Max: %.2f",
202 float kp = kp_factor *
ku_;
203 float ki = ki_factor *
ku_ /
pu_;
204 float kd = kd_factor *
ku_ *
pu_;
215 " kp: %.5f, ki: %.5f, kd: %.5f",
216 name, fac.kp, fac.ki, fac.kd);
246 if (this->
state == FREQUENCY_DETECTOR_INIT) {
247 bool pos = error > this->noiseband;
248 state =
pos ? FREQUENCY_DETECTOR_POSITIVE : FREQUENCY_DETECTOR_NEGATIVE;
251 bool had_crossing =
false;
252 if (this->
state == FREQUENCY_DETECTOR_POSITIVE && error < -this->noiseband) {
253 this->
state = FREQUENCY_DETECTOR_NEGATIVE;
255 }
else if (this->
state == FREQUENCY_DETECTOR_NEGATIVE && error > this->noiseband) {
256 this->
state = FREQUENCY_DETECTOR_POSITIVE;
262 if (this->last_zerocross != 0) {
263 uint32_t dt = now - this->last_zerocross;
264 this->zerocrossing_intervals.push_back(dt);
266 this->last_zerocross = now;
271 return this->zerocrossing_intervals.size() >= 2;
277 for (
uint32_t v : this->zerocrossing_intervals)
280 float mean_value = sum / this->zerocrossing_intervals.size();
282 float mean_period = mean_value / 1000 * 2;
291 if (zerocrossing_intervals.empty())
293 uint32_t max_interval = zerocrossing_intervals[0];
294 uint32_t min_interval = zerocrossing_intervals[0];
295 for (
uint32_t interval : zerocrossing_intervals) {
296 max_interval = std::max(max_interval, interval);
297 min_interval = std::min(min_interval, interval);
299 float ratio = min_interval / float(max_interval);
300 return ratio >= 0.66f;
306 if (relay_state != last_relay_state) {
311 this->phase_maxs.push_back(phase_max);
316 this->phase_mins.push_back(phase_min);
319 this->phase_min = error;
320 this->phase_max = error;
322 this->last_relay_state = relay_state;
324 this->phase_min = std::min(this->phase_min, error);
325 this->phase_max = std::max(this->phase_max, error);
329 if (this->phase_maxs.size() > 7)
330 this->phase_maxs.erase(this->phase_maxs.begin());
331 if (this->phase_mins.size() > 7)
332 this->phase_mins.erase(this->phase_mins.begin());
338 return std::min(phase_mins.size(), phase_maxs.size()) >= 3;
341 float total_amplitudes = 0;
342 size_t total_amplitudes_n = 0;
343 for (
size_t i = 1; i < std::min(phase_mins.size(), phase_maxs.size()) - 1; i++) {
344 total_amplitudes += std::abs(phase_maxs[i] - phase_mins[i + 1]);
345 total_amplitudes_n++;
347 float mean_amplitude = total_amplitudes / total_amplitudes_n;
349 return mean_amplitude / 2.0f;
354 if (this->phase_mins.empty() || this->phase_maxs.empty())
357 float global_max = phase_maxs[0], global_min = phase_mins[0];
358 for (
auto v : this->phase_mins)
359 global_min = std::min(global_min, v);
360 for (
auto v : this->phase_maxs)
361 global_max = std::max(global_max, v);
362 float global_amplitude = (global_max - global_min) / 2.0f;
363 float mean_amplitude = this->get_mean_oscillation_amplitude();
364 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_
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