melvin_ob/flight_control/
flight_state.rs

1use crate::{DT_0, fatal};
2use chrono::TimeDelta;
3use fixed::types::I32F32;
4use num::Zero;
5use std::{collections::HashMap, sync::LazyLock, time::Duration};
6use strum_macros::Display;
7
8/// Represents the various states of MELVINs flight system.
9///
10/// Each state corresponds to a specific operational phase of the flight
11/// system and has unique characteristics, such as distinct charge rates and
12/// transition times to and from other states.
13///
14/// # Variants
15/// - `Deployment`: The initial deployment phase of the flight system.
16/// - `Transition`: A transitional state between two operational modes.
17/// - `Acquisition`: State where the system is actively acquiring images and changing orbit.
18/// - `Charge`: State where the system is primarily charging its batteries.
19/// - `Comms`: State where the system is communicating through the high-gain antenna to receive beacon pings.
20/// - `Safe`: A safe mode, typically activated in the event of an anomaly or low power.
21#[derive(Debug, Display, PartialEq, Eq, Clone, Copy, Hash)]
22pub enum FlightState {
23    Charge = 0,
24    Acquisition = 1,
25    Deployment,
26    Transition,
27    Comms,
28    Safe,
29}
30
31impl FlightState {
32    /// Additional discharge per second when accelerating in acquisition mode
33    pub const ACQ_ACC_ADDITION: I32F32 = I32F32::lit("-0.05");
34
35    /// Returns the charge rate for the given flight state.
36    ///
37    /// Each state has an associated charge rate that indicates the system's power consumption
38    /// or charging rate in that state. A negative value implies power consumption,
39    /// while a positive value indicates charging.
40    ///
41    /// # Returns
42    /// A `I32F32` value representing the charge rate for the flight state.
43    pub fn get_charge_rate(self) -> I32F32 {
44        match self {
45            FlightState::Deployment => I32F32::lit("-0.025"),
46            FlightState::Transition => I32F32::zero(),
47            FlightState::Acquisition => I32F32::lit("-0.1"),
48            FlightState::Charge => I32F32::lit("0.1"),
49            FlightState::Comms => I32F32::lit("-0.008"),
50            FlightState::Safe => I32F32::lit("0.025"),
51        }
52    }
53
54    /// Maps a usize from the dynamic scheduling program to a [`FlightState`].
55    pub fn from_dp_usize(i: usize) -> Self {
56        match i {
57            1 => FlightState::Acquisition,
58            0 => FlightState::Charge,
59            _ => panic!("Invalid state"),
60        }
61    }
62
63    /// Maps a [`FlightState`] to a usize from the dynamic scheduling program.
64    pub fn to_dp_usize(self) -> usize {
65        match self {
66            FlightState::Charge => 0,
67            FlightState::Acquisition => 1,
68            FlightState::Comms => 2,
69            _ => panic!("Invalid state"),
70        }
71    }
72
73    /// Returns the transition time to another mode
74    pub fn dt_to(self, other: Self) -> Duration {
75        *TRANS_DEL
76            .get(&(self, other))
77            .unwrap_or_else(|| fatal!("({self}, {other}) not in TRANSITION_DELAY_LOOKUP"))
78    }
79
80    pub fn td_dt_to(self, other: Self) -> TimeDelta {
81        TimeDelta::from_std(*TRANS_DEL.get(&(self, other)).unwrap_or_else(|| {
82            fatal!("({self}, {other}) not in TRANSITION_DELAY_LOOKUP")
83        }))
84        .unwrap_or(DT_0)
85    }
86}
87
88impl From<&str> for FlightState {
89    /// Converts a string value into a `FlightState` enum.
90    ///
91    /// # Arguments
92    /// - `value`: A string slice representing the flight state (`"deployment"`, `"transition"`,
93    ///   `"acquisition"`, `"charge"`, `"comms"` or `"safe"`).
94    ///
95    /// # Returns
96    /// A `FlightState` converted from the input string.
97    /// If the input is an unknown string this defaults to `safe` and logs the error.
98    fn from(value: &str) -> Self {
99        match value.to_lowercase().as_str() {
100            "deployment" => FlightState::Deployment,
101            "transition" => FlightState::Transition,
102            "acquisition" => FlightState::Acquisition,
103            "charge" => FlightState::Charge,
104            "communication" => FlightState::Comms,
105            "safe" => FlightState::Safe,
106            _ => panic!("Couldn't convert flight_state string"),
107        }
108    }
109}
110
111impl From<FlightState> for &'static str {
112    /// Converts a `FlightState` variant back into its string representation.
113    ///
114    /// # Arguments
115    /// - `value`: A `FlightState` variant to be converted.
116    ///
117    /// # Returns
118    /// A string slice (`"deployment"`, `"transition"`, `"acquisition"`, `"charge"`, `"comms"`, `"safe"`)
119    /// corresponding to the given `FlightState`.
120    fn from(value: FlightState) -> Self {
121        match value {
122            FlightState::Deployment => "deployment",
123            FlightState::Transition => "transition",
124            FlightState::Acquisition => "acquisition",
125            FlightState::Charge => "charge",
126            FlightState::Comms => "communication",
127            FlightState::Safe => "safe",
128        }
129    }
130}
131
132/// A static lookup table defining the delays needed
133/// for transitioning between different flight states.
134///
135/// The delay for each transition is represented as a `Duration`.
136static TRANS_DEL: LazyLock<HashMap<(FlightState, FlightState), Duration>> = LazyLock::new(|| {
137    let mut lookup = HashMap::new();
138    let transition_times = vec![
139        // Deployment transitions
140        (
141            FlightState::Deployment,
142            FlightState::Acquisition,
143            Duration::from_secs(180),
144        ),
145        (
146            FlightState::Deployment,
147            FlightState::Charge,
148            Duration::from_secs(180),
149        ),
150        (
151            FlightState::Deployment,
152            FlightState::Comms,
153            Duration::from_secs(180),
154        ),
155        // Acquisition transitions
156        (
157            FlightState::Acquisition,
158            FlightState::Deployment,
159            Duration::from_secs(180),
160        ),
161        (
162            FlightState::Acquisition,
163            FlightState::Charge,
164            Duration::from_secs(180),
165        ),
166        (
167            FlightState::Acquisition,
168            FlightState::Comms,
169            Duration::from_secs(180),
170        ),
171        // Charge transitions
172        (
173            FlightState::Charge,
174            FlightState::Deployment,
175            Duration::from_secs(180),
176        ),
177        (
178            FlightState::Charge,
179            FlightState::Acquisition,
180            Duration::from_secs(180),
181        ),
182        (
183            FlightState::Charge,
184            FlightState::Comms,
185            Duration::from_secs(180),
186        ),
187        // Comms transitions
188        (
189            FlightState::Comms,
190            FlightState::Deployment,
191            Duration::from_secs(180),
192        ),
193        (
194            FlightState::Comms,
195            FlightState::Acquisition,
196            Duration::from_secs(180),
197        ),
198        (
199            FlightState::Comms,
200            FlightState::Charge,
201            Duration::from_secs(180),
202        ),
203        // Safe transitions
204        (
205            FlightState::Safe,
206            FlightState::Deployment,
207            Duration::from_secs(1200),
208        ),
209        (
210            FlightState::Safe,
211            FlightState::Acquisition,
212            Duration::from_secs(1200),
213        ),
214        (
215            FlightState::Safe,
216            FlightState::Charge,
217            Duration::from_secs(1200),
218        ),
219    ];
220
221    for (from, to, duration) in transition_times {
222        lookup.insert((from, to), duration);
223    }
224    lookup
225});