use pidsk_controller::{PControllerf32, PidskControllerf32};
use vqm::Quaternionf32;
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct MultirotorAltitudeDualRingPid {
altitude_pid: PControllerf32,
speed_pid: PidskControllerf32,
max_vertical_speed_mps: f32,
max_throttle_adjustment: f32,
hover_throttle: f32,
}
impl Default for MultirotorAltitudeDualRingPid {
fn default() -> Self {
Self::new(0.0)
}
}
impl MultirotorAltitudeDualRingPid {
pub fn new(hover_throttle: f32) -> Self {
Self {
altitude_pid: PControllerf32::new(),
speed_pid: PidskControllerf32::new().with_kp(2.5).with_ki(0.05).with_kd(0.05),
max_vertical_speed_mps: 10.0, max_throttle_adjustment: 1.0, hover_throttle,
}
}
}
#[allow(unused)]
impl MultirotorAltitudeDualRingPid {
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(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 test_traits {
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::<MultirotorAltitudeDualRingPid>();
}
}
#[cfg(test)]
mod tests {
#![allow(clippy::float_cmp)]
use super::*;
use crate::autopilot::MockMultirotorZ;
use pidsk_controller::{PGainsf32, PidskGainsf32};
#[test]
fn test_new() {
let _altitude_hold = MultirotorAltitudeDualRingPid::new(0.0);
}
#[test]
fn test_altitude_hold_convergence() {
let hover_throttle = 0.5; let mut controller = MultirotorAltitudeDualRingPid::new(hover_throttle);
let mut multirotor = MockMultirotorZ::new(hover_throttle);
controller.altitude_pid.set_gains(PGainsf32 { kp: 0.29 });
let gains = PidskGainsf32::new().with_kp(1.0).with_ki(0.05).with_kd(0.1);
controller.speed_pid.set_gains(gains);
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
);
}
}