#[cfg(feature = "python")]
use pyo3::prelude::*;
use super::*;
use crate::{calc_config, calc_input_names, calc_output_names, py_json_methods};
#[cfg_attr(feature = "python", pyclass)]
#[derive(Serialize, Deserialize, Default, Debug)]
pub struct Pid {
measurement_name: String,
setpoint_name: String,
kp: f64,
ki: f64,
kd: f64,
max_integral: f64,
save_outputs: bool,
err: f64,
integral: f64,
dt_s: f64,
#[serde(skip)]
input_indices: Vec<usize>,
#[serde(skip)]
output_index: usize,
}
impl Pid {
pub fn new(
measurement_name: String,
setpoint_name: String,
kp: f64,
ki: f64,
kd: f64,
max_integral: f64,
save_outputs: bool,
) -> Box<Self> {
let err = 0.0;
let integral = 0.0;
let dt_s = 1.0;
let input_indices = vec![];
let output_index = usize::MAX;
Box::new(Self {
measurement_name,
setpoint_name,
kp,
ki,
kd,
max_integral,
save_outputs,
err,
integral,
dt_s,
input_indices,
output_index,
})
}
}
py_json_methods!(
Pid,
Calc,
#[new]
fn py_new(
measurement_name: String,
setpoint_name: String,
kp: f64,
ki: f64,
kd: f64,
max_integral: f64,
save_outputs: bool,
) -> Self {
*Self::new(
measurement_name,
setpoint_name,
kp,
ki,
kd,
max_integral,
save_outputs,
)
}
);
#[typetag::serde]
impl Calc for Pid {
fn init(
&mut self,
ctx: ControllerCtx,
input_indices: Vec<usize>,
output_range: Range<usize>,
) -> Result<(), String> {
assert!(
ctx.dt_ns > 0,
"dt_ns value of {} provided. dt_ns must be > 0",
ctx.dt_ns
);
self.dt_s = (ctx.dt_ns as f64) / 1e9;
self.input_indices = input_indices;
self.output_index = output_range.clone().next().unwrap();
Ok(())
}
fn terminate(&mut self) -> Result<(), String> {
self.err = 0.0;
self.dt_s = 1.0;
self.integral = 0.0;
self.input_indices.clear();
self.output_index = usize::MAX;
Ok(())
}
fn eval(&mut self, tape: &mut [f64]) -> Result<(), String> {
let meas = tape[self.input_indices[0]];
let setpoint = tape[self.input_indices[1]];
let new_error = meas - setpoint;
let derivative = (new_error - self.err) / self.dt_s;
self.err = new_error;
self.integral += self.err * self.dt_s;
self.integral = self.integral.min(self.max_integral).max(-self.max_integral);
let y = self.kp * self.err + self.ki * self.integral + self.kd * derivative;
tape[self.output_index] = y;
Ok(())
}
fn get_input_map(&self) -> BTreeMap<CalcInputName, FieldName> {
let mut map = BTreeMap::new();
map.insert("measurement".to_owned(), self.measurement_name.clone());
map.insert("setpoint".to_owned(), self.setpoint_name.clone());
map
}
fn update_input_map(&mut self, field: &str, source: &str) -> Result<(), String> {
match field {
"measurement" => self.measurement_name = source.to_owned(),
"setpoint" => self.setpoint_name = source.to_owned(),
_ => return Err(format!("Unrecognized field {field}")),
}
Ok(())
}
calc_config!(kp, ki, kd, max_integral);
calc_input_names!(measurement, setpoint);
calc_output_names!(y);
fn get_output_units(&self) -> Vec<Option<String>> {
vec![None]
}
}