protoflight 0.1.3

Protoflight flight controller.
#[cfg(feature = "serde")]
use {
    postcard::experimental::max_size::MaxSize,
    sequential_storage::map::PostcardValue,
    serde::{Deserialize, Serialize},
};

#[derive(Clone, Copy, Debug, PartialEq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize, MaxSize))]
pub struct GpsRescueConfig {
    pub max_rescue_angle_degrees: u16,
    pub return_altitude_m: u16,
    pub descent_distance_m: u16,
    pub ground_speed_cmps: u16,
    pub yaw_kp: u8,
    pub min_sats: u8,
    pub vel_kp: u8,
    pub vel_ki: u8,
    pub vel_kd: u8,
    pub min_start_dist_m: u16,
    pub sanity_checks: GpsRescueSanityChecks,
    pub allow_arming_without_fix: u8,
    pub use_mag: u8,
    pub altitude_mode: GpsRescueAltitudeMode,
    pub ascend_rate: u16,
    pub descend_rate: u16,
    pub initial_climb_m: u16,
    pub roll_mix: u8,
    pub disarm_threshold: u8,
    pub pitch_cutoff_hz: u8,
    pub imu_yaw_gain: u8,
}

#[cfg(feature = "serde")]
impl PostcardValue<'_> for GpsRescueConfig {}

impl Default for GpsRescueConfig {
    fn default() -> Self {
        Self::new()
    }
}

#[allow(unused)]
impl GpsRescueConfig {
    pub const fn new() -> Self {
        Self {
            max_rescue_angle_degrees: 45,
            return_altitude_m: 30,
            descent_distance_m: 20,
            ground_speed_cmps: 750,
            yaw_kp: 20,
            min_sats: 8,
            vel_kp: 8,
            vel_ki: 40,
            vel_kd: 12,
            min_start_dist_m: 15,
            sanity_checks: GpsRescueSanityChecks::FsOnly,
            allow_arming_without_fix: 0,
            use_mag: 0,
            altitude_mode: GpsRescueAltitudeMode::Max,
            ascend_rate: 750,
            descend_rate: 150,
            initial_climb_m: 10,
            roll_mix: 150,
            disarm_threshold: 30,
            pitch_cutoff_hz: 75,
            imu_yaw_gain: 10,
        }
    }
}

#[allow(missing_docs)]
#[repr(u8)]
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize, MaxSize))]
pub enum GpsRescueSanityChecks {
    Off = 0,
    On = 1,
    #[default]
    FsOnly = 2,
}

#[allow(unused)]
impl GpsRescueSanityChecks {
    /// Forgiving conversion, converts invalid values to default.
    #[must_use]
    pub fn from_u8(value: u8) -> Self {
        match value {
            0 => Self::Off,
            1 => Self::On,
            2 => Self::FsOnly,
            _ => Self::default(),
        }
    }
}

#[allow(missing_docs)]
#[repr(u8)]
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize, MaxSize))]
pub enum GpsRescueAltitudeMode {
    #[default]
    Max = 0,
    Fixed = 1,
    Current = 2,
}

#[allow(unused)]
impl GpsRescueAltitudeMode {
    /// Forgiving conversion, converts invalid values to default.
    #[must_use]
    pub fn from_u8(value: u8) -> Self {
        match value {
            0 => Self::Max,
            1 => Self::Fixed,
            2 => Self::Current,
            _ => Self::default(),
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    fn is_full<T: Sized + Send + Sync + Unpin + Copy + Clone + Default + PartialEq>() {}
    fn is_full_eq<T: Sized + Send + Sync + Unpin + Copy + Clone + Default + Eq + PartialEq>() {}
    #[cfg(feature = "serde")]
    fn is_config<T: Serialize + MaxSize + for<'a> Deserialize<'a> + for<'a> PostcardValue<'a>>() {}

    #[test]
    fn normal_types() {
        is_full_eq::<GpsRescueSanityChecks>();
        is_full_eq::<GpsRescueAltitudeMode>();
        is_full::<GpsRescueConfig>();
        #[cfg(feature = "serde")]
        is_config::<GpsRescueConfig>();
    }
}