1use super::*;
18
19#[derive(Clone, Debug, PartialEq, Serialize)]
21pub struct GgaData {
22 pub source: NavigationSystem,
24
25 #[serde(with = "json_date_time_utc")]
27 pub timestamp: Option<DateTime<Utc>>,
28
29 pub latitude: Option<f64>,
31
32 pub longitude: Option<f64>,
34
35 pub quality: GgaQualityIndicator,
37
38 pub satellite_count: Option<u8>,
40
41 pub hdop: Option<f64>,
43
44 pub altitude: Option<f64>,
46
47 pub geoid_separation: Option<f64>,
49
50 pub age_of_dgps: Option<f64>,
52
53 pub ref_station_id: Option<u16>,
55}
56
57impl LatLon for GgaData {
58 fn latitude(&self) -> Option<f64> {
59 self.latitude
60 }
61
62 fn longitude(&self) -> Option<f64> {
63 self.longitude
64 }
65}
66
67#[derive(Clone, Copy, Debug, PartialEq, Serialize)]
69pub enum GgaQualityIndicator {
70 Invalid, GpsFix, DGpsFix, PpsFix, RealTimeKinematic, RealTimeKinematicFloat, DeadReckoning, ManualInputMode, SimulationMode, }
80
81impl GgaQualityIndicator {
82 pub fn new(a: u8) -> GgaQualityIndicator {
83 match a {
84 0 => GgaQualityIndicator::Invalid,
85 1 => GgaQualityIndicator::GpsFix,
86 2 => GgaQualityIndicator::DGpsFix,
87 3 => GgaQualityIndicator::PpsFix,
88 4 => GgaQualityIndicator::RealTimeKinematic,
89 5 => GgaQualityIndicator::RealTimeKinematicFloat,
90 6 => GgaQualityIndicator::DeadReckoning,
91 7 => GgaQualityIndicator::ManualInputMode,
92 8 => GgaQualityIndicator::SimulationMode,
93 _ => GgaQualityIndicator::Invalid,
94 }
95 }
96}
97
98impl core::fmt::Display for GgaQualityIndicator {
99 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
100 match self {
101 GgaQualityIndicator::Invalid => write!(f, "invalid"),
102 GgaQualityIndicator::GpsFix => write!(f, "GPS fix"),
103 GgaQualityIndicator::DGpsFix => write!(f, "DGPS fix"),
104 GgaQualityIndicator::PpsFix => write!(f, "PPS fix"),
105 GgaQualityIndicator::RealTimeKinematic => write!(f, "Real-Time Kinematic"),
106 GgaQualityIndicator::RealTimeKinematicFloat => {
107 write!(f, "Real-Time Kinematic (floating point)")
108 }
109 GgaQualityIndicator::DeadReckoning => write!(f, "dead reckoning"),
110 GgaQualityIndicator::ManualInputMode => write!(f, "manual input mode"),
111 GgaQualityIndicator::SimulationMode => write!(f, "simulation mode"),
112 }
113 }
114}
115
116pub(crate) fn handle(
120 sentence: &str,
121 nav_system: NavigationSystem,
122) -> Result<ParsedMessage, ParseError> {
123 let now: DateTime<Utc> = Utc.with_ymd_and_hms(2000, 1, 1, 0, 0, 0).single().unwrap();
124 let split: Vec<&str> = sentence.split(',').collect();
125
126 Ok(ParsedMessage::Gga(GgaData {
127 source: nav_system,
128 timestamp: parse_hhmmss(split.get(1).unwrap_or(&""), now).ok(),
129 latitude: parse_latitude_ddmm_mmm(
130 split.get(2).unwrap_or(&""),
131 split.get(3).unwrap_or(&""),
132 )?,
133 longitude: parse_longitude_dddmm_mmm(
134 split.get(4).unwrap_or(&""),
135 split.get(5).unwrap_or(&""),
136 )?,
137 quality: GgaQualityIndicator::new(pick_number_field(&split, 6)?.unwrap_or(0)),
138 satellite_count: pick_number_field(&split, 7)?,
139 hdop: pick_number_field(&split, 8)?,
140 altitude: pick_number_field(&split, 9)?,
141 geoid_separation: pick_number_field(&split, 11)?,
142 age_of_dgps: pick_number_field(&split, 13)?,
143 ref_station_id: pick_number_field(&split, 14)?,
144 }))
145}
146
147#[cfg(test)]
150mod test {
151 use super::*;
152
153 #[test]
154 fn test_parse_cpgga() {
155 let mut p = NmeaParser::new();
157 match p.parse_sentence("$GPGGA,123519,4807.038,N,01131.000,E,1,08,0.9,545.4,M,46.9,M,,*47")
158 {
159 Ok(ps) => {
160 match ps {
161 ParsedMessage::Gga(gga) => {
163 assert_eq!(gga.timestamp, {
164 Utc.with_ymd_and_hms(2000, 01, 01, 12, 35, 19).single()
165 });
166 assert::close(gga.latitude.unwrap_or(0.0), 48.117, 0.001);
167 assert::close(gga.longitude.unwrap_or(0.0), 11.517, 0.001);
168 assert_eq!(gga.quality, GgaQualityIndicator::GpsFix);
169 assert_eq!(gga.satellite_count.unwrap_or(0), 8);
170 assert::close(gga.hdop.unwrap_or(0.0), 0.9, 0.1);
171 assert::close(gga.altitude.unwrap_or(0.0), 545.4, 0.1);
172 assert::close(gga.geoid_separation.unwrap_or(0.0), 46.9, 0.1);
173 assert_eq!(gga.age_of_dgps, None);
174 assert_eq!(gga.ref_station_id, None);
175 }
176 ParsedMessage::Incomplete => {
177 assert!(false);
178 }
179 _ => {
180 assert!(false);
181 }
182 }
183 }
184 Err(e) => {
185 assert_eq!(e.to_string(), "OK");
186 }
187 }
188
189 let mut p = NmeaParser::new();
191 match p.parse_sentence("$GPGGA,123519,4807.0,S,01131.0,W,1,08,0.9,545.4,M,46.9,M,,") {
192 Ok(ps) => {
193 match ps {
194 ParsedMessage::Gga(gga) => {
196 assert_eq!(
197 (gga.latitude.unwrap_or(0.0) * 1000.0).round() as i32,
198 -48117
199 );
200 assert_eq!(
201 (gga.longitude.unwrap_or(0.0) * 1000.0).round() as i32,
202 -11517
203 );
204 }
205 ParsedMessage::Incomplete => {
206 assert!(false);
207 }
208 _ => {
209 assert!(false);
210 }
211 }
212 }
213 Err(e) => {
214 assert_eq!(e.to_string(), "OK");
215 }
216 }
217
218 let mut p = NmeaParser::new();
220 match p.parse_sentence("$GPGGA,123519,,,,,,,,,,,,,*5B") {
221 Ok(ps) => {
222 match ps {
223 ParsedMessage::Gga(gga) => {
225 assert_eq!(gga.timestamp, {
226 Utc.with_ymd_and_hms(2000, 01, 01, 12, 35, 19).single()
227 });
228 assert_eq!(gga.latitude, None);
229 assert_eq!(gga.longitude, None);
230 assert_eq!(gga.quality, GgaQualityIndicator::Invalid);
231 assert_eq!(gga.satellite_count, None);
232 assert_eq!(gga.hdop, None);
233 assert_eq!(gga.altitude, None);
234 assert_eq!(gga.geoid_separation, None);
235 assert_eq!(gga.age_of_dgps, None);
236 assert_eq!(gga.ref_station_id, None);
237 }
238 ParsedMessage::Incomplete => {
239 assert!(false);
240 }
241 _ => {
242 assert!(false);
243 }
244 }
245 }
246 Err(e) => {
247 assert_eq!(e.to_string(), "OK");
248 }
249 }
250 }
251}