use pidsk_controller::{PidControllerf32, PidGainsf32};
use vqm::Quaternionf32;
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct AltitudeDualRingPid {
altitude_pid: PidControllerf32,
speed_pid: PidControllerf32,
max_vertical_speed_mps: f32,
max_throttle_adjustment: f32,
hover_throttle: f32,
}
impl Default for AltitudeDualRingPid {
fn default() -> Self {
Self::new(0.0)
}
}
impl AltitudeDualRingPid {
pub const fn new(hover_throttle: f32) -> Self {
Self {
altitude_pid: PidControllerf32::new(1.0),
speed_pid: PidControllerf32::with_gains(PidGainsf32 { kp: 2.5, ki: 0.05, kd: 0.05, ks: 0.0, kk: 0.0 }),
max_vertical_speed_mps: 10.0, max_throttle_adjustment: 1.0, hover_throttle,
}
}
}
#[allow(unused)]
impl AltitudeDualRingPid {
pub fn set_altitude_setpoint(&mut self, altitude_setpoint: f32) {
self.altitude_pid.set_setpoint(altitude_setpoint);
}
pub fn update(&mut self, altitude: f32, vertical_speed: f32, orientation: Quaternionf32, delta_t: f32) -> f32 {
self.hover_throttle + self.calculate_throttle_offset(altitude, vertical_speed, orientation, delta_t)
}
pub fn calculate_throttle_offset(
&mut self,
altitude: f32,
vertical_speed: f32,
orientation: Quaternionf32,
delta_t: f32,
) -> f32 {
let cos_tilt = orientation.cos_tilt();
if cos_tilt < 0.0 {
return 0.0;
}
let vertical_speed_setpoint = self
.altitude_pid
.update_sp(altitude) .clamp(-self.max_vertical_speed_mps, self.max_vertical_speed_mps);
self.speed_pid.set_setpoint(vertical_speed_setpoint);
let cos_tilt_reciprocal = 1.0 / cos_tilt.clamp(0.1, 1.0);
let throttle_offset =
self.speed_pid.update(vertical_speed, delta_t) + self.hover_throttle * (cos_tilt_reciprocal - 1.0);
throttle_offset.clamp(-self.max_throttle_adjustment, self.max_throttle_adjustment)
}
pub fn reset(&mut self) {
self.altitude_pid.reset();
self.speed_pid.reset();
}
}
#[cfg(test)]
mod tests {
#![allow(clippy::float_cmp)]
use crate::autopilot::MockMultirotorZ;
use super::*;
fn _is_normal<T: Sized + Send + Sync + Unpin>() {}
fn is_full<T: Sized + Send + Sync + Unpin + Copy + Clone + Default + PartialEq>() {}
#[test]
fn normal_types() {
is_full::<AltitudeDualRingPid>();
}
#[test]
fn test_new() {
let _altitude_hold = AltitudeDualRingPid::new(0.0);
}
#[test]
fn test_altitude_hold_convergence() {
let hover_throttle = 0.5; let mut controller = AltitudeDualRingPid::new(hover_throttle);
let mut multirotor = MockMultirotorZ::new(hover_throttle);
controller.altitude_pid.set_gains(PidGainsf32 { kp: 0.29, ki: 0.0, kd: 0.0, ks: 0.0, kk: 0.0 });
controller.speed_pid.set_gains(PidGainsf32 { kp: 1.0, ki: 0.05, kd: 0.1, ks: 0.0, kk: 0.0 });
let altitude_setpoint = 5.0; controller.set_altitude_setpoint(altitude_setpoint);
let delta_t = 0.001; let orientation = Quaternionf32::default();
let mut converged = false;
for _ in 0..15_000 {
let throttle = controller.update(multirotor.altitude, multirotor.vertical_speed, orientation, delta_t);
multirotor.step(throttle, delta_t);
if (multirotor.altitude - altitude_setpoint).abs() < 0.05 && multirotor.vertical_speed.abs() < 0.01 {
converged = true;
break;
}
}
assert!(
converged,
"Multirotor failed to settle at target altitude. Final Alt: {}, Vel: {}",
multirotor.altitude, multirotor.vertical_speed
);
}
}