use core::time::Duration;
use evian_math::Angle;
use super::ControlLoop;
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct Pid {
kp: f64,
ki: f64,
kd: f64,
integral: f64,
integration_range: Option<f64>,
output_limit: Option<f64>,
prev_error: f64,
}
impl Pid {
#[must_use]
pub const fn new(kp: f64, ki: f64, kd: f64, integration_range: Option<f64>) -> Self {
Self {
kp,
ki,
kd,
integration_range,
output_limit: None,
integral: 0.0,
prev_error: 0.0,
}
}
#[must_use]
pub const fn gains(&self) -> (f64, f64, f64) {
(self.kp, self.ki, self.kd)
}
#[must_use]
pub const fn kp(&self) -> f64 {
self.kp
}
#[must_use]
pub const fn ki(&self) -> f64 {
self.ki
}
#[must_use]
pub const fn kd(&self) -> f64 {
self.kd
}
#[must_use]
pub const fn integration_range(&self) -> Option<f64> {
self.integration_range
}
#[must_use]
pub const fn output_limit(&self) -> Option<f64> {
self.output_limit
}
pub const fn set_gains(&mut self, kp: f64, ki: f64, kd: f64) {
self.kp = kp;
self.ki = ki;
self.kd = kd;
}
pub const fn set_kp(&mut self, kp: f64) {
self.kp = kp;
}
pub const fn set_ki(&mut self, ki: f64) {
self.ki = ki;
}
pub const fn set_kd(&mut self, kd: f64) {
self.kd = kd;
}
pub const fn set_integration_range(&mut self, range: Option<f64>) {
self.integration_range = range;
}
pub const fn set_output_limit(&mut self, range: Option<f64>) {
self.output_limit = range;
}
}
impl ControlLoop for Pid {
type Input = f64;
type Output = f64;
fn update(&mut self, measurement: f64, setpoint: f64, dt: Duration) -> f64 {
let error = setpoint - measurement;
if self
.integration_range
.is_none_or(|range| error.abs() < range)
&& error.signum() == self.prev_error.signum()
{
self.integral += error * dt.as_secs_f64();
} else {
self.integral = 0.0;
}
let derivative = (error - self.prev_error) / dt.as_secs_f64();
self.prev_error = error;
let mut output = (error * self.kp) + (self.integral * self.ki) + (derivative * self.kd);
if let Some(range) = self.output_limit {
output = output.clamp(-range, range);
}
output
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct AngularPid {
kp: f64,
ki: f64,
kd: f64,
integral: f64,
output_limit: Option<f64>,
integration_range: Option<Angle>,
prev_error: Angle,
}
impl AngularPid {
#[must_use]
pub const fn new(kp: f64, ki: f64, kd: f64, integration_range: Option<Angle>) -> Self {
Self {
kp,
ki,
kd,
integration_range,
integral: 0.0,
output_limit: None,
prev_error: Angle::from_radians(0.0),
}
}
#[must_use]
pub const fn gains(&self) -> (f64, f64, f64) {
(self.kp, self.ki, self.kd)
}
#[must_use]
pub const fn kp(&self) -> f64 {
self.kp
}
#[must_use]
pub const fn ki(&self) -> f64 {
self.ki
}
#[must_use]
pub const fn kd(&self) -> f64 {
self.kd
}
#[must_use]
pub const fn integration_range(&self) -> Option<Angle> {
self.integration_range
}
pub const fn set_gains(&mut self, kp: f64, ki: f64, kd: f64) {
self.kp = kp;
self.ki = ki;
self.kd = kd;
}
pub const fn set_kp(&mut self, kp: f64) {
self.kp = kp;
}
pub const fn set_ki(&mut self, ki: f64) {
self.ki = ki;
}
pub const fn set_kd(&mut self, kd: f64) {
self.kd = kd;
}
pub const fn set_integration_range(&mut self, range: Option<Angle>) {
self.integration_range = range;
}
pub const fn set_output_limit(&mut self, range: Option<f64>) {
self.output_limit = range;
}
}
impl ControlLoop for AngularPid {
type Input = Angle;
type Output = f64;
fn update(&mut self, measurement: Angle, setpoint: Angle, dt: Duration) -> f64 {
let error = (setpoint - measurement).wrapped();
#[allow(clippy::float_cmp)]
if self
.integration_range
.is_none_or(|range| error.as_radians().abs() < range.as_radians())
&& error.signum() == self.prev_error.signum()
{
self.integral += error.as_radians() * dt.as_secs_f64();
} else {
self.integral = 0.0;
}
let derivative = (error - self.prev_error).as_radians() / dt.as_secs_f64();
self.prev_error = error;
let mut output =
(error.as_radians() * self.kp) + (self.integral * self.ki) + (derivative * self.kd);
if let Some(range) = self.output_limit {
output = output.clamp(-range, range);
}
output
}
}