6#define M_PI 3.1415926535897932384626433
11static const char *
const TAG =
"pid.autotune";
80 ESP_LOGW(TAG,
"%s: Setpoint changed during autotune! The result will not be accurate!", this->
id_.c_str());
84 float error = setpoint - process_variable;
94 ESP_LOGV(TAG,
"%s: Not enough data yet for autotuner", this->
id_.c_str());
100 if (!zc_symmetrical || !amplitude_convergent) {
103 if (!zc_symmetrical) {
104 ESP_LOGVV(TAG,
"%s: ZC is not symmetrical", this->
id_.c_str());
106 if (!amplitude_convergent) {
107 ESP_LOGVV(TAG,
"%s: Amplitude is not convergent", this->
id_.c_str());
110 ESP_LOGVV(TAG,
"%s: >", this->
id_.c_str());
124 ESP_LOGI(TAG,
"%s: PID Autotune finished!", this->
id_.c_str());
128 ESP_LOGVV(TAG,
" Relay magnitude: %f", d);
129 this->
ku_ = 4.0f * d / float(M_PI * osc_ampl);
141 "%s: PID Autotune:\n"
142 " State: Succeeded!",
144 bool has_issue =
false;
146 ESP_LOGW(TAG,
" Could not reliably determine oscillation amplitude, PID parameters may be inaccurate!\n"
147 " Please make sure you eliminate all outside influences on the measured temperature.");
152 " Oscillation Frequency is not symmetrical. PID parameters may be inaccurate!\n"
153 " This is usually because the heat and cool processes do not change the temperature at the same "
155 " Please try reducing the positive_output value (or increase negative_output in case of a cooler)");
159 ESP_LOGI(TAG,
" All checks passed!");
164 " Calculated PID parameters (\"Ziegler-Nichols PID\" rule):\n"
166 " control_parameters:\n"
171 " Please copy these values into your YAML configuration! They will reset on the next reboot.",
172 fac.kp, fac.ki, fac.kd);
175 " Oscillation Period: %f\n"
176 " Oscillation Amplitude: %f\n"
179 this->amplitude_detector_.get_mean_oscillation_amplitude(), this->ku_, this->pu_);
181 ESP_LOGD(TAG,
" Alternative Rules:");
183 print_rule_(
"Ziegler-Nichols PI", 0.45f, 0.54f, 0.0f);
184 print_rule_(
"Pessen Integral PID", 0.7f, 1.75f, 0.105f);
185 print_rule_(
"Some Overshoot PID", 0.333f, 0.667f, 0.111f);
186 print_rule_(
"No Overshoot PID", 0.2f, 0.4f, 0.0625f);
187 ESP_LOGI(TAG,
"%s: Autotune completed", this->
id_.c_str());
192 "%s: PID Autotune:\n"
193 " Autotune is still running!\n"
194 " Status: Trying to reach %.2f °C\n"
196 " Phases: %" PRIu32
"\n"
197 " Detected %zu zero-crossings\n"
198 " Current Phase Min: %.2f, Max: %.2f",
205 float kp = kp_factor *
ku_;
206 float ki = ki_factor *
ku_ /
pu_;
207 float kd = kd_factor *
ku_ *
pu_;
218 " kp: %.5f, ki: %.5f, kd: %.5f",
219 name, fac.kp, fac.ki, fac.kd);
249 if (this->
state == FREQUENCY_DETECTOR_INIT) {
250 bool pos = error > this->noiseband;
251 state =
pos ? FREQUENCY_DETECTOR_POSITIVE : FREQUENCY_DETECTOR_NEGATIVE;
254 bool had_crossing =
false;
255 if (this->
state == FREQUENCY_DETECTOR_POSITIVE && error < -this->noiseband) {
256 this->
state = FREQUENCY_DETECTOR_NEGATIVE;
258 }
else if (this->
state == FREQUENCY_DETECTOR_NEGATIVE && error > this->noiseband) {
259 this->
state = FREQUENCY_DETECTOR_POSITIVE;
265 if (this->last_zerocross != 0) {
266 uint32_t dt = now - this->last_zerocross;
267 this->zerocrossing_intervals.push_back(dt);
269 this->last_zerocross = now;
274 return this->zerocrossing_intervals.size() >= 2;
280 for (
uint32_t v : this->zerocrossing_intervals)
283 float mean_value = sum / this->zerocrossing_intervals.size();
285 float mean_period = mean_value / 1000 * 2;
294 if (zerocrossing_intervals.empty())
296 uint32_t max_interval = zerocrossing_intervals[0];
297 uint32_t min_interval = zerocrossing_intervals[0];
298 for (
uint32_t interval : zerocrossing_intervals) {
299 max_interval = std::max(max_interval, interval);
300 min_interval = std::min(min_interval, interval);
302 float ratio = min_interval / float(max_interval);
303 return ratio >= 0.66;
309 if (relay_state != last_relay_state) {
314 this->phase_maxs.push_back(phase_max);
319 this->phase_mins.push_back(phase_min);
322 this->phase_min = error;
323 this->phase_max = error;
325 this->last_relay_state = relay_state;
327 this->phase_min = std::min(this->phase_min, error);
328 this->phase_max = std::max(this->phase_max, error);
332 if (this->phase_maxs.size() > 7)
333 this->phase_maxs.erase(this->phase_maxs.begin());
334 if (this->phase_mins.size() > 7)
335 this->phase_mins.erase(this->phase_mins.begin());
341 return std::min(phase_mins.size(), phase_maxs.size()) >= 3;
344 float total_amplitudes = 0;
345 size_t total_amplitudes_n = 0;
346 for (
size_t i = 1; i < std::min(phase_mins.size(), phase_maxs.size()) - 1; i++) {
347 total_amplitudes += std::abs(phase_maxs[i] - phase_mins[i + 1]);
348 total_amplitudes_n++;
350 float mean_amplitude = total_amplitudes / total_amplitudes_n;
352 return mean_amplitude / 2.0f;
357 if (this->phase_mins.empty() || this->phase_maxs.empty())
360 float global_max = phase_maxs[0], global_min = phase_mins[0];
361 for (
auto v : this->phase_mins)
362 global_min = std::min(global_min, v);
363 for (
auto v : this->phase_maxs)
364 global_max = std::max(global_max, v);
365 float global_amplitude = (global_max - global_min) / 2.0f;
366 float mean_amplitude = this->get_mean_oscillation_amplitude();
367 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