protoflight 0.1.4

Protoflight flight controller.
#![cfg(feature = "battery")]
#![allow(unused)]

use core::ops::{Index, IndexMut};
#[cfg(feature = "storage")]
use sequential_storage::map::PostcardValue;
#[cfg(feature = "serde")]
use {
    postcard::experimental::max_size::MaxSize,
    serde::{Deserialize, Serialize},
};

#[derive(Clone, Copy, Debug, Default, PartialEq)]
pub enum BatteryState {
    #[default]
    Ok,
    Warning,
    Critical,
    NotPresent,
    Init,
}
/// Per-profile battery settings (voltage thresholds, capacity).
#[derive(Clone, Copy, Debug, PartialEq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize, MaxSize))]
pub struct BatteryProfile {
    /// maximum voltage per cell, used for auto-detecting battery voltage in 0.01V units, default is 430 (4.30V).
    pub max_cell_voltage_v_x100: u16,
    /// minimum voltage per cell, this triggers battery critical alarm, in 0.01V units, default is 330 (3.30V).
    pub min_cell_voltage_v_x100: u16,
    /// warning voltage per cell, this triggers battery warning alarm, in 0.01V units, default is 350 (3.50V).
    pub warning_cell_voltage_v_x100: u16,
    // Cell voltage at which the battery is deemed to be "full" 0.01V units, default is 410 (4.1V)
    pub full_cell_voltage_v_x100: u16,
    pub battery_capacity_mah: u16,
    // Number of cells in battery, used for overwriting auto-detected cell count if someone has issues with it.
    pub force_battery_cell_count: u8,
    // Percentage of remaining capacity that should trigger a battery warning
    pub consumption_warning_percentage: u8,
    pub profile_name: [u8; Self::MAX_NAME_LENGTH],
}

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

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

impl BatteryProfile {
    pub const COUNT: usize = 3;
    pub const MAX_NAME_LENGTH: usize = 8;
    pub const CELL_VOLTAGE_RANGE_MIN: u16 = 100;
    pub const CELL_VOLTAGE_RANGE_MAX: u16 = 500;
    pub const CELL_VOLTAGE_DEFAULT_MIN: u16 = 330;

    pub const fn new() -> Self {
        Self {
            max_cell_voltage_v_x100: Self::CELL_VOLTAGE_RANGE_MAX,
            min_cell_voltage_v_x100: Self::CELL_VOLTAGE_RANGE_MIN,
            warning_cell_voltage_v_x100: 350,
            full_cell_voltage_v_x100: 410,
            battery_capacity_mah: 0,
            force_battery_cell_count: 0,
            consumption_warning_percentage: 10,
            profile_name: [0u8; Self::MAX_NAME_LENGTH],
        }
    }
}

#[derive(Clone, Copy, Debug, PartialEq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize, MaxSize))]
pub struct BatteryProfiles {
    pub profiles: [BatteryProfile; Self::COUNT],
}

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

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

impl BatteryProfiles {
    pub const MAX: u8 = 2;
    pub const COUNT: usize = 3;
    pub const fn new() -> Self {
        Self { profiles: [BatteryProfile::new(); Self::COUNT] }
    }
}

impl Index<usize> for BatteryProfiles {
    type Output = BatteryProfile;
    #[inline]
    fn index(&self, index: usize) -> &BatteryProfile {
        &self.profiles[index]
    }
}

impl IndexMut<usize> for BatteryProfiles {
    #[inline]
    fn index_mut(&mut self, index: usize) -> &mut BatteryProfile {
        &mut self.profiles[index]
    }
}
#[derive(Clone, Copy, Debug, PartialEq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize, MaxSize))]
pub struct BatteryConfig {
    pub vbat_not_present_cell_voltage: u16, // Between vbat_max_cell_voltage and 2*this is considered to be USB powered. Below this it is not present
    pub lvc_percentage: u8,                 // Percentage of throttle when lvc is triggered

    pub voltage_meter_source: u8, // source of battery voltage meter used, either ADC or ESC
    pub current_meter_source: u8, // source of battery current meter used, either ADC, Virtual or ESC

    pub use_vbat_alerts: u8,        // Issue alerts based on VBat readings
    pub use_consumption_alerts: u8, // Issue alerts based on total power consumption
    pub vbat_hysteresis: u8,        // hysteresis for alarm in 0.01V units, default 1 = 0.01V

    pub vbat_display_lpf_period: u8, // Period of the cutoff frequency for the Vbat filter for display and startup (in 0.1 s)
    pub vbat_sag_lpf_period: u8, // Period of the cutoff frequency for the Vbat sag and PID compensation filter (in 0.1 s)
    pub ibat_lpf_period: u8,     // Period of the cutoff frequency for the Ibat filter (in 0.1 s)

    pub vbat_duration_for_warning: u8, // Period voltage has to sustain before the battery state is set to BATTERY_WARNING (in 0.1 s)
    pub vbat_duration_for_critical: u8, // Period voltage has to sustain before the battery state is set to BATTERY_CRIT (in 0.1 s)
}

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

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

impl BatteryConfig {
    pub const fn new() -> Self {
        Self {
            vbat_not_present_cell_voltage: 300,
            lvc_percentage: 100, // off by default at 100%
            voltage_meter_source: VoltageMeterReading::SOURCE_NONE,

            current_meter_source: CurrentMeterReading::SOURCE_NONE,

            use_vbat_alerts: 1,
            use_consumption_alerts: 0,
            vbat_hysteresis: 1, // 0.01V

            vbat_display_lpf_period: 30,
            vbat_sag_lpf_period: 2,
            ibat_lpf_period: 10,
            vbat_duration_for_warning: 0,
            vbat_duration_for_critical: 0,
        }
    }
}

#[derive(Clone, Copy, Debug, PartialEq)]
pub struct VoltageMeterReading {
    /// Voltage in 0.01V steps.
    pub display_filtered_x100: u16,
    /// Last filtered voltage sample.
    pub voltage_stable_previous_filtered_x100: u16,
    pub voltage_stable_last_update_ms: u32,
    /// Rolling bitmask, bit 1 if battery difference is within threshold, shifted left.
    pub voltage_stable_bits: u16,
    // Voltage in 0.01V steps.
    pub unfiltered_x100: u16,
    // Voltage in 0.01V steps.
    pub sag_filtered_x100: u16,
    pub low_voltage_cutoff: bool,
}

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

impl VoltageMeterReading {
    pub const SOURCE_NONE: u8 = 0;
    pub const SOURCE_ADC: u8 = 1;
    pub const SOURCE_ESC: u8 = 2;
    pub const SOURCE_MAX: u8 = 2;
    pub const SOURCE_COUNT: usize = 3;
    pub const SOURCE_NAMES: [&str; Self::SOURCE_COUNT] = ["NONE", "ADC", "ESC"];

    pub const fn new() -> Self {
        Self {
            display_filtered_x100: 0,
            voltage_stable_previous_filtered_x100: 0,
            voltage_stable_last_update_ms: 0,
            voltage_stable_bits: 0,
            unfiltered_x100: 0,
            sag_filtered_x100: 0,
            low_voltage_cutoff: false,
        }
    }
}

#[derive(Clone, Copy, Debug, PartialEq)]
pub struct CurrentMeterReading {
    /// Current read by current sensor in centiampere (1/100th A).
    pub amperage_x100: i32,
    /// Current read by current sensor in centiampere (1/100th A) (unfiltered).
    pub amperage_latest_x100: i32,
    /// Milliampere hours drawn from the battery since start.
    pub mah_drawn: i32,
    /// mAh offset.
    pub mah_drawn_offset: i32,
}

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

impl CurrentMeterReading {
    pub const SOURCE_NONE: u8 = 0;
    pub const SOURCE_ADC: u8 = 1;
    pub const SOURCE_VIRTUAL: u8 = 2;
    pub const SOURCE_ESC: u8 = 3;
    pub const SOURCE_MSP: u8 = 4;
    pub const SOURCE_MAX: u8 = 4;
    pub const SOURCE_COUNT: usize = 5;
    pub const SOURCE_NAMES: [&str; Self::SOURCE_COUNT] = ["NONE", "ADC", "VIRTUAL", "ESC", "MSP"];

    pub const fn new() -> Self {
        Self { amperage_x100: 0, amperage_latest_x100: 0, mah_drawn: 0, mah_drawn_offset: 0 }
    }
}

#[derive(Clone, Copy, Debug, PartialEq)]
pub struct BatteryMessage {
    pub voltage: VoltageMeterReading,
    pub current: CurrentMeterReading,
    pub state: BatteryState,
}

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

impl BatteryMessage {
    pub fn new() -> Self {
        Self {
            voltage: VoltageMeterReading::new(),
            current: CurrentMeterReading::new(),
            state: BatteryState::default(),
        }
    }
}

#[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>() {}
    #[cfg(feature = "serde")]
    fn is_serde<T: Serialize + MaxSize + for<'a> Deserialize<'a>>() {}
    #[cfg(feature = "storage")]
    fn is_storage<T: for<'a> PostcardValue<'a>>() {}

    #[test]
    fn normal_types() {
        is_full::<BatteryState>();
        is_full::<BatteryConfig>();
        is_full::<BatteryProfile>();
        is_full::<BatteryProfiles>();
        is_full::<BatteryMessage>();
        #[cfg(feature = "serde")]
        is_serde::<BatteryConfig>();
        #[cfg(feature = "serde")]
        is_serde::<BatteryProfile>();
        #[cfg(feature = "serde")]
        is_serde::<BatteryProfiles>();
    }
}

mod tests {
    use super::*;

    #[test]
    fn test_new() {
        let config = BatteryConfig::new();
        assert_eq!(300, config.vbat_not_present_cell_voltage);
    }
}