#![allow(dead_code)]
use crate::core::scalar::ControlScalar;
use crate::core::signal::{Feedback, Setpoint};
use crate::core::traits::Controller;
use crate::pid::Pid;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ControlMode {
Manual,
Auto,
}
pub struct BumplessTransfer<S: ControlScalar> {
pub pid: Pid<S>,
pub mode: ControlMode,
pub manual_output: S,
pub last_output: S,
preload: S,
preloaded: bool,
}
impl<S: ControlScalar> BumplessTransfer<S> {
pub fn new(pid: Pid<S>) -> Self {
Self {
pid,
mode: ControlMode::Manual,
manual_output: S::ZERO,
last_output: S::ZERO,
preload: S::ZERO,
preloaded: false,
}
}
pub fn set_manual(&mut self, v: S) {
self.manual_output = v;
self.last_output = v;
}
pub fn switch_to_auto(&mut self) {
if self.mode == ControlMode::Auto {
return;
}
self.pid.reset();
self.preload = self.manual_output;
self.preloaded = true;
self.mode = ControlMode::Auto;
}
pub fn switch_to_manual(&mut self) {
if self.mode == ControlMode::Manual {
return;
}
self.manual_output = self.last_output;
self.mode = ControlMode::Manual;
}
pub fn update(&mut self, sp: S, fb: S, dt: S) -> S {
let output = match self.mode {
ControlMode::Manual => self.manual_output,
ControlMode::Auto => {
let sp_sig = Setpoint::new(sp);
let fb_sig = Feedback::new(fb);
let pid_out = self.pid.update(&sp_sig, &fb_sig, dt).value();
if self.preloaded {
self.preloaded = false;
pid_out + self.preload
} else {
pid_out
}
}
};
self.last_output = output;
output
}
pub fn output(&self) -> S {
self.last_output
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::pid::PidConfig;
fn make_bt() -> BumplessTransfer<f64> {
let pid = PidConfig::pi(1.0_f64, 5.0)
.with_limits(-100.0, 100.0)
.build();
BumplessTransfer::new(pid)
}
#[test]
fn manual_mode_returns_manual_output() {
let mut bt = make_bt();
bt.set_manual(core::f64::consts::PI);
let out = bt.update(0.0, 0.0, 0.01);
assert!((out - core::f64::consts::PI).abs() < 1e-12, "out={out}");
}
#[test]
fn bumpless_switch_to_auto() {
let mut bt = make_bt();
bt.set_manual(5.0);
bt.switch_to_auto();
let out = bt.update(0.0, 0.0, 0.01);
assert!((out - 5.0).abs() < 1e-9, "out={out}");
let out2 = bt.update(0.0, 0.0, 0.01);
assert!(out2.abs() < 1e-9, "out2={out2}");
}
#[test]
fn switch_to_manual_latches_output() {
let mut bt = make_bt();
bt.set_manual(0.0);
bt.switch_to_auto();
for _ in 0..5 {
bt.update(10.0, 0.0, 0.01);
}
let auto_out = bt.last_output;
bt.switch_to_manual();
assert!((bt.manual_output - auto_out).abs() < 1e-12);
let manual_out = bt.update(99.0, 99.0, 0.01);
assert!((manual_out - auto_out).abs() < 1e-12);
}
#[test]
fn double_switch_to_auto_is_idempotent() {
let mut bt = make_bt();
bt.set_manual(2.0);
bt.switch_to_auto();
bt.switch_to_auto();
let out = bt.update(0.0, 0.0, 0.01);
assert!((out - 2.0).abs() < 1e-9, "out={out}");
}
}