fsd_interface/
structs.rs

1use std::{fmt::Display, str::FromStr};
2
3use crate::{enums::FlightRules, errors::FsdMessageParseError, util::parse_altitude};
4
5#[derive(Debug, Clone, Copy, PartialEq, Eq)]
6pub struct TransponderCode(u16);
7impl TryFrom<u16> for TransponderCode {
8    type Error = FsdMessageParseError;
9    fn try_from(mut code: u16) -> Result<Self, Self::Error> {
10        let mut digits = [0; 4];
11        digits[0] = code / 1000;
12        code -= digits[0] * 1000;
13        digits[1] = code / 100;
14        code -= digits[1] * 100;
15        digits[2] = code / 10;
16        code -= digits[2] * 10;
17        digits[3] = code;
18
19        if digits.into_iter().any(|x| x > 7) {
20            Err(FsdMessageParseError::InvalidTransponderCode(format!(
21                "{:04}",
22                code
23            )))
24        } else {
25            let code = digits[0] * 1000 + digits[1] * 100 + digits[2] * 10 + digits[3];
26            Ok(TransponderCode(code))
27        }
28    }
29}
30impl TransponderCode {
31    pub fn try_from_bcd_format(
32        bcd: impl AsRef<str>,
33    ) -> Result<TransponderCode, FsdMessageParseError> {
34        let bcd = bcd.as_ref();
35        let num = bcd
36            .parse::<u32>()
37            .map_err(|_| FsdMessageParseError::InvalidTransponderCode(bcd.to_owned()))?;
38        let hex_value = format!("{:X}", num);
39        let result = hex_value
40            .parse::<u16>()
41            .map_err(|_| FsdMessageParseError::InvalidTransponderCode(bcd.to_owned()))?;
42        Self::try_from(result)
43    }
44    pub fn as_bcd_format(&self) -> u32 {
45        let as_dec_str = self.to_string();
46        u32::from_str_radix(&as_dec_str, 16).unwrap()
47    }
48}
49
50impl FromStr for TransponderCode {
51    type Err = FsdMessageParseError;
52    fn from_str(s: &str) -> Result<Self, Self::Err> {
53        let code: u16 = s
54            .parse()
55            .map_err(|_| FsdMessageParseError::InvalidTransponderCode(s.to_string()))?;
56        code.try_into()
57    }
58}
59impl Display for TransponderCode {
60    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
61        write!(f, "{:04}", self.0)
62    }
63}
64
65/// Represents a VHF, airband radio frequency from 118.000 MHz to 137.000 MHz.
66///
67/// Stored internally as the left part and the right part. For example, 118.3MHz is `RadioFrequency(118, 300)`.
68#[derive(Debug, Clone, Copy, PartialEq, Eq)]
69pub struct RadioFrequency(pub(crate) u16, pub(crate) u16);
70impl RadioFrequency {
71    /// Creates a new [`RadioFrequency`] from two parts
72    ///
73    /// # Example
74    /// ```
75    /// use fsd_messages::util::RadioFrequency;
76    /// let freq = RadioFrequency::new(118, 300).unwrap();
77    /// assert_eq!(RadioFrequency(118, 300), freq);
78    /// ```
79    pub fn new(left: u16, right: u16) -> Result<RadioFrequency, FsdMessageParseError> {
80        if !((118..=137).contains(&left)
81            || left == 199 && right == 998
82            || left == 149 && right == 999)
83        {
84            return Err(FsdMessageParseError::InvalidFrequency(format!(
85                "{}.{:03}",
86                left, right
87            )));
88        }
89        Ok(RadioFrequency(left, right))
90    }
91
92    pub fn frequency(&self) -> (u16, u16) {
93        (self.0, self.1)
94    }
95
96    /// Returns the frequency in the form XXX.YYY
97    ///
98    /// # Example
99    /// ```
100    /// use fsd_messages::util::RadioFrequency;
101    /// let freq = RadioFrequency::new(133, 175).unwrap();
102    /// let human_readable = freq.to_human_readable_string();
103    /// assert_eq!(human_readable, String::from("133.175"));
104    /// ```
105    pub fn to_human_readable_string(&self) -> String {
106        format!("{}.{:03}", self.0, self.1)
107    }
108    pub fn try_from_human_readable_string(
109        input: impl AsRef<str>,
110    ) -> Result<RadioFrequency, FsdMessageParseError> {
111        let input = input.as_ref();
112        let mut split = input.split('.');
113
114        let left = split
115            .next()
116            .ok_or_else(|| FsdMessageParseError::InvalidFrequency(input.to_string()))?;
117        let left: u16 = left
118            .parse()
119            .map_err(|_| FsdMessageParseError::InvalidFrequency(input.to_string()))?;
120        let right = split
121            .next()
122            .ok_or_else(|| FsdMessageParseError::InvalidFrequency(input.to_string()))?;
123        let right: u16 = right
124            .parse()
125            .map_err(|_| FsdMessageParseError::InvalidFrequency(input.to_string()))?;
126        RadioFrequency::new(left, right)
127    }
128}
129
130impl FromStr for RadioFrequency {
131    type Err = FsdMessageParseError;
132    fn from_str(short_form: &str) -> Result<Self, Self::Err> {
133        if short_form.len() != 5 {
134            return Err(FsdMessageParseError::InvalidFrequency(
135                short_form.to_string(),
136            ));
137        }
138        let left: u16 = short_form[0..2]
139            .parse()
140            .map_err(|_| FsdMessageParseError::InvalidFrequency(short_form.to_string()))?;
141        let right: u16 = short_form[2..]
142            .parse()
143            .map_err(|_| FsdMessageParseError::InvalidFrequency(short_form.to_string()))?;
144        RadioFrequency::new(left + 100, right)
145    }
146}
147
148impl Display for RadioFrequency {
149    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
150        write!(f, "{}{:03}", self.0 - 100, self.1)
151    }
152}
153
154#[derive(Debug, Default, Clone)]
155pub struct PlaneInfo {
156    pub equipment: Option<String>,
157    pub airline: Option<String>,
158    pub livery: Option<String>,
159}
160impl From<&[&str]> for PlaneInfo {
161    fn from(value: &[&str]) -> Self {
162        let mut plane_info = PlaneInfo::default();
163
164        if value.is_empty() {
165            return plane_info;
166        }
167
168        for entry in value {
169            let mut split = entry.split('=');
170            let k = match split.next() {
171                Some(k) => k,
172                None => continue,
173            };
174
175            let v = match split.next() {
176                Some(v) => v.to_string(),
177                None => continue,
178            };
179
180            match k.to_uppercase().as_str() {
181                "EQUIPMENT" => plane_info.equipment = Some(v),
182                "AIRLINE" => plane_info.airline = Some(v),
183                "LIVERY" => plane_info.livery = Some(v),
184                _ => {}
185            }
186        }
187        plane_info
188    }
189}
190impl Display for PlaneInfo {
191    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
192        let mut need_delimiter = false;
193        if let Some(ref equipment) = self.equipment {
194            write!(f, "EQUIPMENT={}", equipment)?;
195            need_delimiter = true;
196        }
197        if let Some(ref airline) = self.airline {
198            if need_delimiter {
199                write!(f, ":")?;
200            }
201            write!(f, "AIRLINE={}", airline)?;
202            need_delimiter = true;
203        }
204        if let Some(ref livery) = self.livery {
205            if need_delimiter {
206                write!(f, ":")?;
207            }
208            write!(f, "LIVERY={}", livery)?;
209        }
210        Ok(())
211    }
212}
213
214impl PlaneInfo {
215    pub fn new(
216        equipment: Option<impl Into<String>>,
217        airline: Option<impl Into<String>>,
218        livery: Option<impl Into<String>>,
219    ) -> PlaneInfo {
220        let equipment = equipment.map(|x| x.into());
221        let airline = airline.map(|x| x.into());
222        let livery = livery.map(|x| x.into());
223        PlaneInfo {
224            equipment,
225            airline,
226            livery,
227        }
228    }
229}
230
231#[derive(Debug, Clone, PartialEq, Eq)]
232pub struct FlightPlan {
233    pub flight_rules: FlightRules,
234    pub ac_type: String,
235    pub filed_tas: u16,
236    pub origin: String,
237    pub etd: u16,
238    pub atd: u16,
239    pub cruise_level: u32,
240    pub destination: String,
241    pub hours_enroute: u8,
242    pub mins_enroute: u8,
243    pub hours_fuel: u8,
244    pub mins_fuel: u8,
245    pub alternate: String,
246    pub remarks: String,
247    pub route: String,
248}
249
250impl Display for FlightPlan {
251    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
252        write!(
253            f,
254            "{}:{}:{}:{}:{}:{}:{}:{}:{}:{}:{}:{}:{}:{}:{}",
255            self.flight_rules,
256            self.ac_type,
257            self.filed_tas,
258            self.origin,
259            self.etd,
260            self.atd,
261            self.cruise_level,
262            self.destination,
263            self.hours_enroute,
264            self.mins_enroute,
265            self.hours_fuel,
266            self.mins_fuel,
267            self.alternate,
268            self.remarks,
269            self.route
270        )
271    }
272}
273
274impl TryFrom<&[&str]> for FlightPlan {
275    type Error = FsdMessageParseError;
276    fn try_from(fields: &[&str]) -> Result<Self, Self::Error> {
277        if fields.len() != 15 {
278            return Err(FsdMessageParseError::InvalidFieldCount(15, fields.len()));
279        }
280
281        let filed_tas = if fields[2].is_empty() {
282            0
283        } else {
284            fields[2]
285                .parse()
286                .map_err(|_| FsdMessageParseError::InvalidSpeed(fields[2].to_string()))?
287        };
288        let etd = if fields[4].is_empty() {
289            0
290        } else {
291            fields[4]
292                .parse()
293                .map_err(|_| FsdMessageParseError::InvalidTime(fields[4].to_string()))?
294        };
295        let atd = if fields[5].is_empty() {
296            0
297        } else {
298            fields[5]
299                .parse()
300                .map_err(|_| FsdMessageParseError::InvalidTime(fields[5].to_string()))?
301        };
302        let hours_enroute = if fields[8].is_empty() {
303            0
304        } else {
305            fields[8]
306                .parse()
307                .map_err(|_| FsdMessageParseError::InvalidTime(fields[8].to_string()))?
308        };
309        let hours_fuel = if fields[10].is_empty() {
310            0
311        } else {
312            fields[10]
313                .parse()
314                .map_err(|_| FsdMessageParseError::InvalidTime(fields[10].to_string()))?
315        };
316        let mins_enroute = if fields[9].is_empty() {
317            0
318        } else {
319            let mins = fields[9]
320                .parse()
321                .map_err(|_| FsdMessageParseError::InvalidTime(fields[9].to_string()))?;
322            if mins > 59 {
323                return Err(FsdMessageParseError::InvalidMinute(fields[9].to_string()));
324            }
325            mins
326        };
327        let mins_fuel = if fields[11].is_empty() {
328            0
329        } else {
330            let mins = fields[11]
331                .parse()
332                .map_err(|_| FsdMessageParseError::InvalidTime(fields[11].to_string()))?;
333            if mins > 59 {
334                return Err(FsdMessageParseError::InvalidMinute(fields[11].to_string()));
335            }
336            mins
337        };
338
339        Ok(FlightPlan::new(
340            fields[0].parse()?,
341            fields[1],
342            filed_tas,
343            fields[3],
344            etd,
345            atd,
346            parse_altitude(fields[6])? as u32,
347            fields[7],
348            hours_enroute,
349            mins_enroute,
350            hours_fuel,
351            mins_fuel,
352            fields[12],
353            fields[13],
354            fields[14],
355        ))
356    }
357}
358
359impl FlightPlan {
360    pub fn new(
361        flight_rules: FlightRules,
362        ac_type: impl AsRef<str>,
363        filed_tas: u16,
364        origin: impl AsRef<str>,
365        etd: u16,
366        atd: u16,
367        cruise_level: u32,
368        destination: impl AsRef<str>,
369        hours_enroute: u8,
370        mins_enroute: u8,
371        hours_fuel: u8,
372        mins_fuel: u8,
373        alternate: impl AsRef<str>,
374        remarks: impl Into<String>,
375        route: impl AsRef<str>,
376    ) -> FlightPlan {
377        FlightPlan {
378            flight_rules,
379            ac_type: ac_type.as_ref().to_uppercase(),
380            filed_tas,
381            origin: origin.as_ref().to_uppercase(),
382            etd,
383            atd,
384            cruise_level,
385            destination: destination.as_ref().to_uppercase(),
386            hours_enroute,
387            mins_enroute,
388            hours_fuel,
389            mins_fuel,
390            alternate: alternate.as_ref().to_uppercase(),
391            remarks: remarks.into(),
392            route: route.as_ref().to_uppercase(),
393        }
394    }
395}