mirror of
https://github.com/esphome/esphome.git
synced 2026-06-04 17:43:05 +08:00
[pid] Replace std::deque with FixedRingBuffer (#14733)
Co-authored-by: J. Nick Koston <nick@home-assistant.io>
This commit is contained in:
@@ -57,7 +57,7 @@ CONFIG_SCHEMA = cv.All(
|
||||
cv.Optional(CONF_KD_MULTIPLIER, default=0.0): cv.float_,
|
||||
cv.Optional(
|
||||
CONF_DEADBAND_OUTPUT_AVERAGING_SAMPLES, default=1
|
||||
): cv.int_,
|
||||
): cv.positive_not_null_int,
|
||||
}
|
||||
),
|
||||
cv.Required(CONF_CONTROL_PARAMETERS): cv.Schema(
|
||||
@@ -68,8 +68,12 @@ CONFIG_SCHEMA = cv.All(
|
||||
cv.Optional(CONF_STARTING_INTEGRAL_TERM, default=0.0): cv.float_,
|
||||
cv.Optional(CONF_MIN_INTEGRAL, default=-1): cv.float_,
|
||||
cv.Optional(CONF_MAX_INTEGRAL, default=1): cv.float_,
|
||||
cv.Optional(CONF_DERIVATIVE_AVERAGING_SAMPLES, default=1): cv.int_,
|
||||
cv.Optional(CONF_OUTPUT_AVERAGING_SAMPLES, default=1): cv.int_,
|
||||
cv.Optional(
|
||||
CONF_DERIVATIVE_AVERAGING_SAMPLES, default=1
|
||||
): cv.positive_not_null_int,
|
||||
cv.Optional(
|
||||
CONF_OUTPUT_AVERAGING_SAMPLES, default=1
|
||||
): cv.positive_not_null_int,
|
||||
}
|
||||
),
|
||||
}
|
||||
@@ -102,13 +106,15 @@ async def to_code(config):
|
||||
cg.add(var.set_starting_integral_term(params[CONF_STARTING_INTEGRAL_TERM]))
|
||||
cg.add(var.set_derivative_samples(params[CONF_DERIVATIVE_AVERAGING_SAMPLES]))
|
||||
|
||||
cg.add(var.set_output_samples(params[CONF_OUTPUT_AVERAGING_SAMPLES]))
|
||||
output_samples = params[CONF_OUTPUT_AVERAGING_SAMPLES]
|
||||
cg.add(var.set_output_samples(output_samples))
|
||||
|
||||
if CONF_MIN_INTEGRAL in params:
|
||||
cg.add(var.set_min_integral(params[CONF_MIN_INTEGRAL]))
|
||||
if CONF_MAX_INTEGRAL in params:
|
||||
cg.add(var.set_max_integral(params[CONF_MAX_INTEGRAL]))
|
||||
|
||||
deadband_output_samples = 1
|
||||
if CONF_DEADBAND_PARAMETERS in config:
|
||||
params = config[CONF_DEADBAND_PARAMETERS]
|
||||
cg.add(var.set_threshold_low(params[CONF_THRESHOLD_LOW]))
|
||||
@@ -116,11 +122,11 @@ async def to_code(config):
|
||||
cg.add(var.set_kp_multiplier(params[CONF_KP_MULTIPLIER]))
|
||||
cg.add(var.set_ki_multiplier(params[CONF_KI_MULTIPLIER]))
|
||||
cg.add(var.set_kd_multiplier(params[CONF_KD_MULTIPLIER]))
|
||||
cg.add(
|
||||
var.set_deadband_output_samples(
|
||||
params[CONF_DEADBAND_OUTPUT_AVERAGING_SAMPLES]
|
||||
)
|
||||
)
|
||||
deadband_output_samples = params[CONF_DEADBAND_OUTPUT_AVERAGING_SAMPLES]
|
||||
cg.add(var.set_deadband_output_samples(deadband_output_samples))
|
||||
|
||||
# Single shared output buffer sized to max of both modes
|
||||
cg.add(var.init_output_buffer(max(output_samples, deadband_output_samples)))
|
||||
|
||||
cg.add(var.set_default_target_temperature(config[CONF_DEFAULT_TARGET_TEMPERATURE]))
|
||||
|
||||
|
||||
@@ -28,7 +28,11 @@ class PIDClimate : public climate::Climate, public Component {
|
||||
void set_min_integral(float min_integral) { controller_.min_integral_ = min_integral; }
|
||||
void set_max_integral(float max_integral) { controller_.max_integral_ = max_integral; }
|
||||
void set_output_samples(int in) { controller_.output_samples_ = in; }
|
||||
void set_derivative_samples(int in) { controller_.derivative_samples_ = in; }
|
||||
void set_derivative_samples(int in) {
|
||||
controller_.derivative_samples_ = in;
|
||||
if (in > 1) // No allocation needed when samples=1 (ring_buffer_average_ short-circuits)
|
||||
controller_.derivative_window_.init(in);
|
||||
}
|
||||
|
||||
void set_threshold_low(float in) { controller_.threshold_low_ = in; }
|
||||
void set_threshold_high(float in) { controller_.threshold_high_ = in; }
|
||||
@@ -38,6 +42,10 @@ class PIDClimate : public climate::Climate, public Component {
|
||||
void set_starting_integral_term(float in) { controller_.set_starting_integral_term(in); }
|
||||
|
||||
void set_deadband_output_samples(int in) { controller_.deadband_output_samples_ = in; }
|
||||
void init_output_buffer(int size) {
|
||||
if (size > 1) // No allocation needed when samples=1 (ring_buffer_average_ short-circuits)
|
||||
controller_.output_window_.init(size);
|
||||
}
|
||||
|
||||
float get_output_value() const { return output_value_; }
|
||||
float get_error_value() const { return controller_.error_; }
|
||||
|
||||
@@ -21,9 +21,9 @@ float PIDController::update(float setpoint, float process_value) {
|
||||
// u(t) := p(t) + i(t) + d(t)
|
||||
float output = proportional_term_ + integral_term_ + derivative_term_;
|
||||
|
||||
// smooth/sample the output
|
||||
// smooth/sample the output using shared buffer with mode-appropriate sample count
|
||||
int samples = in_deadband() ? deadband_output_samples_ : output_samples_;
|
||||
return weighted_average_(output_list_, output, samples);
|
||||
return ring_buffer_average_(output_window_, output, samples);
|
||||
}
|
||||
|
||||
bool PIDController::in_deadband() {
|
||||
@@ -83,7 +83,7 @@ void PIDController::calculate_derivative_term_(float setpoint) {
|
||||
previous_setpoint_ = setpoint;
|
||||
|
||||
// smooth the derivative samples
|
||||
derivative = weighted_average_(derivative_list_, derivative, derivative_samples_);
|
||||
derivative = ring_buffer_average_(derivative_window_, derivative, derivative_samples_);
|
||||
|
||||
derivative_term_ = kd_ * derivative;
|
||||
|
||||
@@ -93,25 +93,23 @@ void PIDController::calculate_derivative_term_(float setpoint) {
|
||||
}
|
||||
}
|
||||
|
||||
float PIDController::weighted_average_(std::deque<float> &list, float new_value, int samples) {
|
||||
// if only 1 sample needed, clear the list and return
|
||||
if (samples == 1) {
|
||||
list.clear();
|
||||
float PIDController::ring_buffer_average_(FixedRingBuffer<float> &buf, float new_value, int max_samples) {
|
||||
// if only 1 sample needed (or invalid), clear the buffer and return
|
||||
if (max_samples <= 1) {
|
||||
buf.clear();
|
||||
return new_value;
|
||||
}
|
||||
|
||||
// add the new item to the list
|
||||
list.push_front(new_value);
|
||||
// Trim oldest entries to make room (handles mode-switching where buffer
|
||||
// may have more entries than the current mode needs)
|
||||
while (buf.size() >= static_cast<size_t>(max_samples))
|
||||
buf.pop();
|
||||
buf.push(new_value);
|
||||
|
||||
// keep only 'samples' readings, by popping off the back of the list
|
||||
while (samples > 0 && list.size() > static_cast<size_t>(samples))
|
||||
list.pop_back();
|
||||
|
||||
// calculate and return the average of all values in the list
|
||||
float sum = 0;
|
||||
for (auto &elem : list)
|
||||
sum += elem;
|
||||
return sum / list.size();
|
||||
for (auto val : buf)
|
||||
sum += val;
|
||||
return sum / buf.size();
|
||||
}
|
||||
|
||||
float PIDController::calculate_relative_time_() {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/core/hal.h"
|
||||
#include <deque>
|
||||
#include "esphome/core/helpers.h"
|
||||
#include <cmath>
|
||||
|
||||
namespace esphome {
|
||||
@@ -24,10 +25,10 @@ struct PIDController {
|
||||
/// Differential gain K_d.
|
||||
float kd_ = 0;
|
||||
|
||||
// smooth the derivative value using a weighted average over X samples
|
||||
int derivative_samples_ = 8;
|
||||
// smooth the derivative value using an average over X samples
|
||||
int derivative_samples_ = 1;
|
||||
|
||||
/// smooth the output value using a weighted average over X values
|
||||
/// smooth the output value using an average over X values
|
||||
int output_samples_ = 1;
|
||||
|
||||
float threshold_low_ = 0.0f;
|
||||
@@ -50,7 +51,10 @@ struct PIDController {
|
||||
void calculate_proportional_term_();
|
||||
void calculate_integral_term_();
|
||||
void calculate_derivative_term_(float setpoint);
|
||||
float weighted_average_(std::deque<float> &list, float new_value, int samples);
|
||||
|
||||
/// Ring buffer smoothing using FixedRingBuffer (single allocation at setup)
|
||||
float ring_buffer_average_(FixedRingBuffer<float> &buf, float new_value, int max_samples);
|
||||
|
||||
float calculate_relative_time_();
|
||||
|
||||
/// Error from previous update used for derivative term
|
||||
@@ -60,12 +64,12 @@ struct PIDController {
|
||||
float accumulated_integral_ = 0;
|
||||
uint32_t last_time_ = 0;
|
||||
|
||||
// this is a list of derivative values for smoothing.
|
||||
std::deque<float> derivative_list_;
|
||||
// Ring buffer for derivative smoothing
|
||||
FixedRingBuffer<float> derivative_window_;
|
||||
|
||||
// this is a list of output values for smoothing.
|
||||
std::deque<float> output_list_;
|
||||
// Ring buffer for output smoothing (shared between normal and deadband modes)
|
||||
FixedRingBuffer<float> output_window_;
|
||||
|
||||
}; // Struct PID Controller
|
||||
}; // Struct PIDController
|
||||
} // namespace pid
|
||||
} // namespace esphome
|
||||
|
||||
Reference in New Issue
Block a user