use crate::config::ControllerConfig;
use crate::enums::{AntiWindupMode, DerivativeMode};
use crate::error::PidError;
use crate::state::PidState;
pub fn pid_compute(
config: &ControllerConfig,
state: &PidState,
process_value: f64,
dt: f64,
) -> Result<(f64, PidState), PidError> {
if !dt.is_finite() || dt <= 0.0 {
return Err(PidError::InvalidParameter(
"dt must be a finite positive number",
));
}
if !process_value.is_finite() {
return Err(PidError::InvalidParameter(
"process_value must be a finite number",
));
}
let error = config.setpoint - process_value;
let working_error = if error.abs() <= config.deadband {
0.0
} else {
error - config.deadband * error.signum()
};
let n = config.derivative_filter_coeff;
if state.first_run {
let p_term = config.kp * working_error;
let mut integral_contribution =
state.integral_contribution + config.ki * working_error * dt;
let d_term = 0.0;
let unclamped = p_term + integral_contribution + d_term;
let output = unclamped.clamp(config.min_output, config.max_output);
if (output - unclamped).abs() > f64::EPSILON {
match config.anti_windup_mode {
AntiWindupMode::None => {}
AntiWindupMode::Conditional => {
integral_contribution -= config.ki * working_error * dt;
}
AntiWindupMode::BackCalculation { tracking_time } => {
integral_contribution += (output - unclamped) * dt / tracking_time;
}
}
}
let new_state = PidState {
integral_contribution,
prev_error: working_error,
prev_measurement: process_value,
prev_filtered_derivative: 0.0,
last_output: output,
first_run: false,
};
return Ok((output, new_state));
}
let p_term = config.kp * working_error;
let mut integral_contribution = state.integral_contribution + config.ki * working_error * dt;
let raw_derivative = match config.derivative_mode {
DerivativeMode::OnMeasurement => -(process_value - state.prev_measurement) / dt,
DerivativeMode::OnError => (working_error - state.prev_error) / dt,
};
let alpha = n * dt / (1.0 + n * dt);
let filtered =
state.prev_filtered_derivative + alpha * (raw_derivative - state.prev_filtered_derivative);
let d_term = config.kd * filtered;
let unclamped = p_term + integral_contribution + d_term;
let output = unclamped.clamp(config.min_output, config.max_output);
if (output - unclamped).abs() > f64::EPSILON {
match config.anti_windup_mode {
AntiWindupMode::None => {}
AntiWindupMode::Conditional => {
integral_contribution -= config.ki * working_error * dt;
}
AntiWindupMode::BackCalculation { tracking_time } => {
integral_contribution += (output - unclamped) * dt / tracking_time;
}
}
}
let new_state = PidState {
integral_contribution,
prev_error: working_error,
prev_measurement: process_value,
prev_filtered_derivative: filtered,
last_output: output,
first_run: false,
};
Ok((output, new_state))
}