1use crate::message::ParsedSentence;
49use crate::types::{MessageType, TalkerId};
50
51#[derive(Debug, Clone)]
53pub struct GgaData {
54 pub talker_id: TalkerId,
55 time_data: [u8; 16],
56 time_len: u8,
57 pub latitude: f64,
58 pub lat_direction: char,
59 pub longitude: f64,
60 pub lon_direction: char,
61 pub fix_quality: u8,
62 pub num_satellites: Option<u8>,
63 pub hdop: Option<f32>,
64 pub altitude: Option<f32>,
65 pub altitude_units: Option<char>,
66 pub geoid_separation: Option<f32>,
67 pub geoid_units: Option<char>,
68 pub age_of_diff: Option<f32>,
69 diff_station_id_data: [u8; 8],
70 diff_station_id_len: u8,
71}
72
73impl GgaData {
74 pub fn time(&self) -> &str {
76 core::str::from_utf8(&self.time_data[..self.time_len as usize]).unwrap_or("")
77 }
78
79 pub fn diff_station_id(&self) -> Option<&str> {
81 if self.diff_station_id_len > 0 {
82 core::str::from_utf8(&self.diff_station_id_data[..self.diff_station_id_len as usize])
83 .ok()
84 } else {
85 None
86 }
87 }
88}
89
90impl ParsedSentence {
91 pub fn as_gga(&self) -> Option<GgaData> {
134 if self.message_type != MessageType::GGA {
135 return None;
136 }
137
138 let time_str = self.get_field_str(1)?;
140 let latitude: f64 = self.parse_field(2)?;
141 let lat_direction = self.parse_field_char(3)?;
142 let longitude: f64 = self.parse_field(4)?;
143 let lon_direction = self.parse_field_char(5)?;
144 let fix_quality: u8 = self.parse_field(6)?;
145
146 let mut time_data = [0u8; 16];
148 let time_bytes = time_str.as_bytes();
149 let time_len = time_bytes.len().min(16) as u8;
150 time_data[..time_len as usize].copy_from_slice(&time_bytes[..time_len as usize]);
151
152 let mut diff_station_id_data = [0u8; 8];
154 let diff_station_id_len = if let Some(id_str) = self.get_field_str(14) {
155 let id_bytes = id_str.as_bytes();
156 let len = id_bytes.len().min(8) as u8;
157 diff_station_id_data[..len as usize].copy_from_slice(&id_bytes[..len as usize]);
158 len
159 } else {
160 0
161 };
162
163 Some(GgaData {
164 talker_id: self.talker_id,
165 time_data,
166 time_len,
167 latitude,
168 lat_direction,
169 longitude,
170 lon_direction,
171 fix_quality,
172 num_satellites: self.parse_field(7),
173 hdop: self.parse_field(8),
174 altitude: self.parse_field(9),
175 altitude_units: self.parse_field_char(10),
176 geoid_separation: self.parse_field(11),
177 geoid_units: self.parse_field_char(12),
178 age_of_diff: self.parse_field(13),
179 diff_station_id_data,
180 diff_station_id_len,
181 })
182 }
183}
184
185#[cfg(test)]
186mod tests {
187 use crate::NmeaParser;
188
189 #[test]
190 fn test_gga_complete_message() {
191 let parser = NmeaParser::new();
192 let sentence = b"$GPGGA,123519,4807.038,N,01131.000,E,1,08,0.9,545.4,M,46.9,M,,*47\r\n";
193
194 let result = parser.parse_sentence_complete(sentence);
195
196 assert!(result.is_some());
197 let msg = result.unwrap();
198 let gga = msg.as_gga();
199 assert!(gga.is_some());
200
201 let gga_data = gga.unwrap();
202 assert_eq!(gga_data.time(), "123519");
203 assert_eq!(gga_data.latitude, 4807.038);
204 assert_eq!(gga_data.lat_direction, 'N');
205 assert_eq!(gga_data.longitude, 1131.000);
206 assert_eq!(gga_data.lon_direction, 'E');
207 assert_eq!(gga_data.fix_quality, 1);
208 assert_eq!(gga_data.num_satellites, Some(8));
209 assert_eq!(gga_data.hdop, Some(0.9));
210 assert_eq!(gga_data.altitude, Some(545.4));
211 assert_eq!(gga_data.altitude_units, Some('M'));
212 assert_eq!(gga_data.geoid_separation, Some(46.9));
213 assert_eq!(gga_data.geoid_units, Some('M'));
214 assert_eq!(gga_data.age_of_diff, None);
215 assert_eq!(gga_data.diff_station_id(), None);
216 }
217
218 #[test]
219 fn test_gga_with_empty_optional_fields() {
220 let parser = NmeaParser::new();
221 let sentence = b"$GPGGA,123519,4807.038,N,01131.000,E,1,,,,,M,,M,,*47\r\n";
222
223 let result = parser.parse_sentence_complete(sentence);
224
225 assert!(result.is_some());
226 let msg = result.unwrap();
227 let gga = msg.as_gga();
228 assert!(gga.is_some());
229
230 let gga_data = gga.unwrap();
231 assert_eq!(gga_data.time(), "123519");
232 assert_eq!(gga_data.latitude, 4807.038);
233 assert_eq!(gga_data.fix_quality, 1);
234 assert_eq!(gga_data.num_satellites, None);
235 assert_eq!(gga_data.hdop, None);
236 assert_eq!(gga_data.altitude, None);
237 }
238
239 #[test]
240 fn test_gga_missing_time() {
241 let parser = NmeaParser::new();
242 let sentence = b"$GPGGA,,4807.038,N,01131.000,E,1,08,0.9,545.4,M,46.9,M,,*47\r\n";
243
244 let result = parser.parse_sentence_complete(sentence);
245
246 assert!(result.is_none());
248 }
249
250 #[test]
251 fn test_gga_missing_latitude() {
252 let parser = NmeaParser::new();
253 let sentence = b"$GPGGA,123519,,N,01131.000,E,1,08,0.9,545.4,M,46.9,M,,*47\r\n";
254
255 let result = parser.parse_sentence_complete(sentence);
256
257 assert!(result.is_none());
259 }
260
261 #[test]
262 fn test_gga_missing_longitude() {
263 let parser = NmeaParser::new();
264 let sentence = b"$GPGGA,123519,4807.038,N,,E,1,08,0.9,545.4,M,46.9,M,,*47\r\n";
265
266 let result = parser.parse_sentence_complete(sentence);
267
268 assert!(result.is_none());
270 }
271
272 #[test]
273 fn test_gga_missing_fix_quality() {
274 let parser = NmeaParser::new();
275 let sentence = b"$GPGGA,123519,4807.038,N,01131.000,E,,08,0.9,545.4,M,46.9,M,,*47\r\n";
276
277 let result = parser.parse_sentence_complete(sentence);
278
279 assert!(result.is_none());
281 }
282
283 #[test]
284 fn test_gga_invalid_latitude_format() {
285 let parser = NmeaParser::new();
286 let sentence = b"$GPGGA,123519,INVALID,N,01131.000,E,1,08,0.9,545.4,M,46.9,M,,*47\r\n";
287
288 let result = parser.parse_sentence_complete(sentence);
289
290 assert!(result.is_none());
292 }
293
294 #[test]
295 fn test_gga_with_differential_data() {
296 let parser = NmeaParser::new();
297 let sentence =
298 b"$GPGGA,123519,4807.038,N,01131.000,E,2,08,0.9,545.4,M,46.9,M,3.2,0120*47\r\n";
299
300 let result = parser.parse_sentence_complete(sentence);
301
302 assert!(result.is_some());
303 let msg = result.unwrap();
304 let gga = msg.as_gga();
305 assert!(gga.is_some());
306
307 let gga_data = gga.unwrap();
308 assert_eq!(gga_data.fix_quality, 2);
309 assert_eq!(gga_data.age_of_diff, Some(3.2));
310 assert_eq!(gga_data.diff_station_id(), Some("0120"));
311 }
312
313 #[test]
314 fn test_gga_numeric_precision() {
315 let parser = NmeaParser::new();
316 let sentence = b"$GPGGA,123519,4807.038,N,01131.000,E,1,08,0.9,545.4,M,46.9,M,,*47\r\n";
317
318 let result = parser.parse_sentence_complete(sentence);
319
320 assert!(result.is_some());
321 let msg = result.unwrap();
322 let gga = msg.as_gga();
323 assert!(gga.is_some());
324
325 let gga_data = gga.unwrap();
326 assert!((gga_data.latitude - 4807.038).abs() < 0.001);
327 assert!((gga_data.longitude - 1131.000).abs() < 0.001);
328
329 if let Some(hdop) = gga_data.hdop {
330 assert!((hdop - 0.9).abs() < 0.01);
331 }
332
333 if let Some(alt) = gga_data.altitude {
334 assert!((alt - 545.4).abs() < 0.1);
335 }
336 }
337
338 #[test]
339 fn test_gga_different_talker_id() {
340 let parser = NmeaParser::new();
341 let sentence = b"$GNGGA,123519,4807.038,N,01131.000,E,1,08,0.9,545.4,M,46.9,M,,*47\r\n";
343
344 let result = parser.parse_sentence_complete(sentence);
345
346 assert!(result.is_some());
347 let msg = result.unwrap();
348 let gga = msg.as_gga();
349 assert!(gga.is_some());
350
351 let gga_data = gga.unwrap();
352 assert_eq!(gga_data.talker_id, crate::types::TalkerId::GN);
353 }
354
355 #[test]
356 fn test_gga_gps_talker_id() {
357 let parser = NmeaParser::new();
358 let sentence = b"$GPGGA,123519,4807.038,N,01131.000,E,1,08,0.9,545.4,M,46.9,M,,*47\r\n";
359
360 let result = parser.parse_sentence_complete(sentence);
361
362 assert!(result.is_some());
363 let msg = result.unwrap();
364 let gga = msg.as_gga();
365 assert!(gga.is_some());
366
367 let gga_data = gga.unwrap();
368 assert_eq!(gga_data.talker_id, crate::types::TalkerId::GP);
369 }
370
371 #[test]
372 fn test_gga_glonass_talker_id() {
373 let parser = NmeaParser::new();
374 let sentence = b"$GLGGA,123519,4807.038,N,01131.000,E,1,08,0.9,545.4,M,46.9,M,,*47\r\n";
375
376 let result = parser.parse_sentence_complete(sentence);
377
378 assert!(result.is_some());
379 let msg = result.unwrap();
380 let gga = msg.as_gga();
381 assert!(gga.is_some());
382
383 let gga_data = gga.unwrap();
384 assert_eq!(gga_data.talker_id, crate::types::TalkerId::GL);
385 }
386
387 #[test]
388 fn test_gga_galileo_talker_id() {
389 let parser = NmeaParser::new();
390 let sentence = b"$GAGGA,123519,4807.038,N,01131.000,E,1,08,0.9,545.4,M,46.9,M,,*47\r\n";
391
392 let result = parser.parse_sentence_complete(sentence);
393
394 assert!(result.is_some());
395 let msg = result.unwrap();
396 let gga = msg.as_gga();
397 assert!(gga.is_some());
398
399 let gga_data = gga.unwrap();
400 assert_eq!(gga_data.talker_id, crate::types::TalkerId::GA);
401 }
402
403 #[test]
404 fn test_gga_beidou_talker_id() {
405 let parser = NmeaParser::new();
406 let sentence = b"$GBGGA,123519,4807.038,N,01131.000,E,1,08,0.9,545.4,M,46.9,M,,*47\r\n";
407
408 let result = parser.parse_sentence_complete(sentence);
409
410 assert!(result.is_some());
411 let msg = result.unwrap();
412 let gga = msg.as_gga();
413 assert!(gga.is_some());
414
415 let gga_data = gga.unwrap();
416 assert_eq!(gga_data.talker_id, crate::types::TalkerId::GB);
417 }
418}