1use serde::{Deserialize, Serialize};
4
5#[derive(Serialize, Deserialize, PartialEq, Debug, Clone)]
7#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
8pub enum FixStatus {
9 Unknown,
11
12 NotFix,
14
15 Fix2D,
17
18 Fix3D,
20}
21impl TryFrom<&str> for FixStatus {
22 type Error = String;
23
24 fn try_from(value: &str) -> Result<Self, Self::Error> {
25 match value.trim() {
26 "Location Unknown" | "Unknown" => Ok(FixStatus::Unknown),
27 "Location Not Fix" | "Not Fix" => Ok(FixStatus::NotFix),
28 "Location 2D Fix" | "2D Fix" => Ok(FixStatus::Fix2D),
29 "Location 3D Fix" | "3D Fix" => Ok(FixStatus::Fix3D),
30 _ => Err(format!("Invalid GNSS fix status: '{value}'")),
31 }
32 }
33}
34impl From<u8> for FixStatus {
35 fn from(value: u8) -> Self {
36 match value {
37 0 => FixStatus::NotFix,
38 1 => FixStatus::Fix2D,
39 2 => FixStatus::Fix3D,
40 _ => FixStatus::Unknown,
41 }
42 }
43}
44
45#[derive(Serialize, Deserialize, PartialEq, Debug, Clone)]
47#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
48pub struct PositionReport {
49 pub run_status: bool,
51
52 pub fix_status: bool,
54
55 pub utc_time: String,
57
58 pub latitude: Option<f64>,
60
61 pub longitude: Option<f64>,
63
64 pub msl_altitude: Option<f64>,
66
67 pub ground_speed: Option<f32>,
69
70 pub ground_course: Option<f32>,
72
73 pub fix_mode: FixStatus,
75
76 pub hdop: Option<f32>,
78
79 pub pdop: Option<f32>,
81
82 pub vdop: Option<f32>,
84
85 pub gps_in_view: Option<u8>,
87
88 pub gnss_used: Option<u8>,
90
91 pub glonass_in_view: Option<u8>,
93}
94impl TryFrom<Vec<&str>> for PositionReport {
95 type Error = String;
96
97 fn try_from(fields: Vec<&str>) -> Result<Self, Self::Error> {
98 if fields.len() < 15 {
99 return Err(format!(
100 "Insufficient GNSS data fields got {}",
101 fields.len()
102 ));
103 }
104
105 Ok(Self {
107 run_status: fields[0] == "1",
108 fix_status: fields[1] == "1",
109 utc_time: fields[2].to_string(),
110 latitude: fields[3].parse().ok(),
111 longitude: fields[4].parse().ok(),
112 msl_altitude: fields[5].parse().ok(),
113 ground_speed: fields[6].parse().ok(),
114 ground_course: fields[7].parse().ok(),
115 fix_mode: FixStatus::from(fields[8].parse::<u8>().unwrap_or(0)),
116 hdop: fields[10].parse().ok(),
118 pdop: fields[11].parse().ok(),
119 vdop: fields[12].parse().ok(),
120 gps_in_view: fields[14].parse().ok(),
122 gnss_used: fields[15].parse().ok(),
123 glonass_in_view: fields[16].parse().ok(),
124 })
125 }
126}
127impl std::fmt::Display for PositionReport {
128 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
129 fn convert_opt<T: std::fmt::Display>(opt: Option<&T>) -> String {
130 match opt {
131 Some(value) => value.to_string(),
132 None => "None".to_string(),
133 }
134 }
135
136 write!(
137 f,
138 "Lat: {}, Lon: {}, Alt: {}, Speed: {}, Course: {}",
139 convert_opt(self.latitude.as_ref()),
140 convert_opt(self.longitude.as_ref()),
141 convert_opt(self.msl_altitude.as_ref()),
142 convert_opt(self.ground_speed.as_ref()),
143 convert_opt(self.ground_course.as_ref())
144 )
145 }
146}