use crate::control::OnePoleLowPass;
use crate::error::ControlError;
use crate::scalar::Numeric;
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct Pid<T: Numeric = f64> {
proportional_gain: T,
integral_gain: T,
derivative_gain: T,
dt: T,
output_minimum: T,
output_maximum: T,
integral: T,
derivative_filter: OnePoleLowPass<T>,
previous_error: T,
has_previous_error: bool,
}
impl<T: Numeric> Pid<T> {
pub fn new(
proportional_gain: T,
integral_gain: T,
derivative_gain: T,
dt: T,
) -> Result<Self, ControlError> {
if !proportional_gain.is_finite()
|| !integral_gain.is_finite()
|| !derivative_gain.is_finite()
|| !dt.is_finite()
{
return Err(ControlError::NonFinite);
}
if dt <= T::ZERO {
return Err(ControlError::NonPositiveTimestep);
}
Ok(Self {
proportional_gain,
integral_gain,
derivative_gain,
dt,
output_minimum: T::NEG_INFINITY,
output_maximum: T::INFINITY,
integral: T::ZERO,
derivative_filter: OnePoleLowPass::new(T::ONE)?,
previous_error: T::ZERO,
has_previous_error: false,
})
}
pub fn with_output_limits(mut self, minimum: T, maximum: T) -> Result<Self, ControlError> {
if minimum.is_nan() || maximum.is_nan() {
return Err(ControlError::NonFinite);
}
if minimum > maximum {
return Err(ControlError::InvalidOutputLimits);
}
self.output_minimum = minimum;
self.output_maximum = maximum;
Ok(self)
}
pub fn with_derivative_filter(mut self, smoothing: T) -> Result<Self, ControlError> {
self.derivative_filter = OnePoleLowPass::new(smoothing)?;
Ok(self)
}
pub fn update(&mut self, setpoint: T, measurement: T) -> T {
let error = setpoint - measurement;
let proportional_term = self.proportional_gain * error;
let raw_derivative = if self.has_previous_error {
(error - self.previous_error) / self.dt
} else {
T::ZERO
};
let derivative_term = self.derivative_gain * self.derivative_filter.filter(raw_derivative);
self.previous_error = error;
self.has_previous_error = true;
let candidate_integral = self.integral + self.integral_gain * error * self.dt;
let unsaturated = proportional_term + candidate_integral + derivative_term;
let output = unsaturated
.max(self.output_minimum)
.min(self.output_maximum);
let saturated_high = unsaturated > self.output_maximum;
let saturated_low = unsaturated < self.output_minimum;
let pushing_deeper =
(saturated_high && error > T::ZERO) || (saturated_low && error < T::ZERO);
if !pushing_deeper {
self.integral = candidate_integral;
}
output
}
pub fn reset(&mut self) {
self.integral = T::ZERO;
self.has_previous_error = false;
self.previous_error = T::ZERO;
self.derivative_filter.reset();
}
#[inline]
#[must_use]
pub fn integral(&self) -> T {
self.integral
}
}