1#![no_std]
2
3mod message;
9mod parser;
10mod types;
11
12pub use message::{Field, GgaData, GllData, GsaData, GsvData, RmcData, SatelliteInfo, VtgData};
14pub use parser::NmeaParser;
15pub use types::*;
16
17pub type ParseResult = Result<(Option<NmeaMessage>, usize), (ParseError, usize)>;
19
20#[cfg(test)]
21mod tests {
22 use super::*;
23
24 fn test_message_parsing(sentence: &[u8], expected_type: MessageType) {
26 let parser = NmeaParser::new();
27 let result = parser.parse_bytes(sentence);
28 assert!(result.is_ok());
29 let (msg, consumed) = result.unwrap();
30 assert_eq!(consumed, sentence.len());
31 let msg = msg.unwrap();
32 assert_eq!(msg.message_type(), expected_type);
33 }
34
35 #[test]
36 fn test_parser_initialization() {
37 let _parser = NmeaParser::new();
38 }
40
41 #[test]
42 fn test_parse_all_message_types() {
43 let test_cases = [
44 (
45 b"$GPGGA,123519,4807.038,N,01131.000,E,1,08,0.9,545.4,M,46.9,M,,*47\r\n".as_slice(),
46 MessageType::GGA,
47 ),
48 (
49 b"$GPRMC,123519,A,4807.038,N,01131.000,E,022.4,084.4,230394,003.1,W*6A\r\n"
50 .as_slice(),
51 MessageType::RMC,
52 ),
53 (
54 b"$GPGSA,A,3,04,05,,09,12,,,24,,,,,2.5,1.3,2.1*39\r\n".as_slice(),
55 MessageType::GSA,
56 ),
57 (
58 b"$GPGSV,2,1,08,01,40,083,46,02,17,308,41,12,07,344,39,14,22,228,45*75\r\n"
59 .as_slice(),
60 MessageType::GSV,
61 ),
62 (
63 b"$GPGLL,4916.45,N,12311.12,W,225444,A,*1D\r\n".as_slice(),
64 MessageType::GLL,
65 ),
66 (
67 b"$GPVTG,054.7,T,034.4,M,005.5,N,010.2,K*48\r\n".as_slice(),
68 MessageType::VTG,
69 ),
70 ];
71
72 for (sentence, expected_type) in test_cases {
73 test_message_parsing(sentence, expected_type);
74 }
75 }
76
77 #[test]
78 fn test_multiple_messages_stream() {
79 let parser = NmeaParser::new();
80 let stream = b"$GPGGA,123519,4807.038,N,01131.000,E,1,08,0.9,545.4,M,46.9,M,,*47\r\n\
81 $GPRMC,123519,A,4807.038,N,01131.000,E,022.4,084.4,230394,003.1,W*6A\r\n\
82 $GPGSA,A,3,04,05,,09,12,,,24,,,,,2.5,1.3,2.1*39\r\n";
83
84 let mut message_count = 0;
85 let mut offset = 0;
86 while offset < stream.len() {
87 let result = parser.parse_bytes(&stream[offset..]);
88 if result.is_err() {
89 break;
90 }
91 let (msg, consumed) = result.unwrap();
92 if consumed == 0 {
93 break;
94 }
95 offset += consumed;
96 if msg.is_some() {
97 message_count += 1;
98 }
99 }
100
101 assert_eq!(message_count, 3);
102 }
103
104 #[test]
105 fn test_field_extraction() {
106 let parser = NmeaParser::new();
107 let sentence = b"$GPGGA,123519,4807.038,N,01131.000,E,1,08,0.9,545.4,M,46.9,M,,*47\r\n";
108
109 let result = parser.parse_bytes(sentence);
110 assert!(result.is_ok());
111 let (msg, consumed) = result.unwrap();
112 assert_eq!(consumed, sentence.len());
113 let msg = msg.unwrap();
114
115 assert_eq!(msg.message_type(), MessageType::GGA);
116 let gga = msg.as_gga().expect("Expected GGA message");
117 assert_eq!(gga.time(), "123519");
118 assert_eq!(gga.latitude, 4807.038);
119 }
120
121 #[test]
122 fn test_invalid_and_partial_sentences() {
123 let parser = NmeaParser::new();
124
125 let invalid = b"INVALID DATA\r\n";
127 let result = parser.parse_bytes(invalid);
128 assert!(result.is_ok());
129 let (msg, consumed) = result.unwrap();
130 assert!(msg.is_none());
131 assert_eq!(consumed, invalid.len());
132
133 let partial = b"$GPGGA,123519,4807.038,N";
135 let result = parser.parse_bytes(partial);
136 assert!(result.is_ok());
137 let (msg, consumed) = result.unwrap();
138 assert!(msg.is_none());
139 assert_eq!(consumed, 0);
140 }
141
142 #[test]
143 fn test_gga_parameters() {
144 let parser = NmeaParser::new();
145 let sentence = b"$GPGGA,123519,4807.038,N,01131.000,E,1,08,0.9,545.4,M,46.9,M,,*47\r\n";
146
147 let result = parser.parse_bytes(sentence);
148 assert!(result.is_ok());
149 let (msg, _) = result.unwrap();
150 let msg = msg.unwrap();
151 let gga = msg.as_gga();
152 assert!(gga.is_some());
153
154 let gga_data = gga.unwrap();
155 assert_eq!(gga_data.time(), "123519");
156 assert_eq!(gga_data.latitude, 4807.038);
157 assert_eq!(gga_data.lat_direction, 'N');
158 assert_eq!(gga_data.longitude, 1131.000);
159 assert_eq!(gga_data.lon_direction, 'E');
160 assert_eq!(gga_data.fix_quality, 1);
161 assert_eq!(gga_data.num_satellites, Some(8));
162 assert_eq!(gga_data.hdop, Some(0.9));
163 assert_eq!(gga_data.altitude, Some(545.4));
164 assert_eq!(gga_data.altitude_units, Some('M'));
165 }
166
167 #[test]
168 fn test_rmc_parameters() {
169 let parser = NmeaParser::new();
170 let sentence = b"$GPRMC,123519,A,4807.038,N,01131.000,E,022.4,084.4,230394,003.1,W*6A\r\n";
171
172 let result = parser.parse_bytes(sentence);
173 assert!(result.is_ok());
174 let (msg, _) = result.unwrap();
175 let msg = msg.unwrap();
176 let rmc = msg.as_rmc();
177 assert!(rmc.is_some());
178
179 let rmc_data = rmc.unwrap();
180 assert_eq!(rmc_data.time(), "123519");
181 assert_eq!(rmc_data.status, 'A');
182 assert_eq!(rmc_data.latitude, 4807.038);
183 assert_eq!(rmc_data.lat_direction, 'N');
184 assert_eq!(rmc_data.longitude, 1131.000);
185 assert_eq!(rmc_data.lon_direction, 'E');
186 assert_eq!(rmc_data.speed_knots, 22.4);
187 assert_eq!(rmc_data.track_angle, 84.4);
188 assert_eq!(rmc_data.date(), "230394");
189 }
190
191 #[test]
192 fn test_gsa_parameters() {
193 let parser = NmeaParser::new();
194 let sentence = b"$GPGSA,A,3,04,05,,09,12,,,24,,,,,2.5,1.3,2.1*39\r\n";
195
196 let result = parser.parse_bytes(sentence);
197 assert!(result.is_ok());
198 let (msg, _) = result.unwrap();
199 let msg = msg.unwrap();
200 let gsa = msg.as_gsa();
201 assert!(gsa.is_some());
202
203 let gsa_data = gsa.unwrap();
204 assert_eq!(gsa_data.mode, 'A');
205 assert_eq!(gsa_data.fix_type, 3);
206 assert_eq!(gsa_data.satellite_ids[0], Some(4));
207 assert_eq!(gsa_data.satellite_ids[1], Some(5));
208 assert_eq!(gsa_data.satellite_ids[3], Some(9));
209 assert_eq!(gsa_data.pdop, Some(2.5));
210 assert_eq!(gsa_data.hdop, Some(1.3));
211 assert_eq!(gsa_data.vdop, Some(2.1));
212 }
213
214 #[test]
215 fn test_gsv_parameters() {
216 let parser = NmeaParser::new();
217 let sentence = b"$GPGSV,2,1,08,01,40,083,46,02,17,308,41,12,07,344,39,14,22,228,45*75\r\n";
218
219 let result = parser.parse_bytes(sentence);
220 assert!(result.is_ok());
221 let (msg, _) = result.unwrap();
222 let msg = msg.unwrap();
223 let gsv = msg.as_gsv();
224 assert!(gsv.is_some());
225
226 let gsv_data = gsv.unwrap();
227 assert_eq!(gsv_data.num_messages, 2);
228 assert_eq!(gsv_data.message_num, 1);
229 assert_eq!(gsv_data.satellites_in_view, 8);
230
231 assert!(gsv_data.satellite_info[0].is_some());
233 let sat1 = gsv_data.satellite_info[0].as_ref().unwrap();
234 assert_eq!(sat1.prn, Some(1));
235 assert_eq!(sat1.elevation, Some(40));
236 assert_eq!(sat1.azimuth, Some(83));
237 assert_eq!(sat1.snr, Some(46));
238 }
239
240 #[test]
241 fn test_gll_parameters() {
242 let parser = NmeaParser::new();
243 let sentence = b"$GPGLL,4916.45,N,12311.12,W,225444,A,*1D\r\n";
244
245 let result = parser.parse_bytes(sentence);
246 assert!(result.is_ok());
247 let (msg, _) = result.unwrap();
248 let msg = msg.unwrap();
249 let gll = msg.as_gll();
250 assert!(gll.is_some());
251
252 let gll_data = gll.unwrap();
253 assert_eq!(gll_data.latitude, 4916.45);
254 assert_eq!(gll_data.lat_direction, 'N');
255 assert_eq!(gll_data.longitude, 12311.12);
256 assert_eq!(gll_data.lon_direction, 'W');
257 assert_eq!(gll_data.time(), "225444");
258 assert_eq!(gll_data.status, 'A');
259 }
260
261 #[test]
262 fn test_vtg_parameters() {
263 let parser = NmeaParser::new();
264 let sentence = b"$GPVTG,054.7,T,034.4,M,005.5,N,010.2,K*48\r\n";
265
266 let result = parser.parse_bytes(sentence);
267 assert!(result.is_ok());
268 let (msg, _) = result.unwrap();
269 let msg = msg.unwrap();
270 let vtg = msg.as_vtg();
271 assert!(vtg.is_some());
272
273 let vtg_data = vtg.unwrap();
274 assert_eq!(vtg_data.track_true, Some(54.7));
275 assert_eq!(vtg_data.track_true_indicator, Some('T'));
276 assert_eq!(vtg_data.track_magnetic, Some(34.4));
277 assert_eq!(vtg_data.track_magnetic_indicator, Some('M'));
278 assert_eq!(vtg_data.speed_knots, Some(5.5));
279 assert_eq!(vtg_data.speed_knots_indicator, Some('N'));
280 assert_eq!(vtg_data.speed_kph, Some(10.2));
281 assert_eq!(vtg_data.speed_kph_indicator, Some('K'));
282 }
283
284 #[test]
285 fn test_wrong_message_type_extraction() {
286 let parser = NmeaParser::new();
287 let sentence = b"$GPGGA,123519,4807.038,N,01131.000,E,1,08,0.9,545.4,M,46.9,M,,*47\r\n";
288
289 let result = parser.parse_bytes(sentence);
290 assert!(result.is_ok());
291 let (msg, _) = result.unwrap();
292 let msg = msg.unwrap();
293
294 let rmc = msg.as_rmc();
296 assert!(rmc.is_none());
297
298 let gga = msg.as_gga();
300 assert!(gga.is_some());
301 }
302
303 #[test]
304 fn test_gga_with_empty_fields() {
305 let parser = NmeaParser::new();
306 let sentence = b"$GPGGA,123519,,N,,E,1,,,,,M,,M,,*47\r\n";
308
309 let result = parser.parse_bytes(sentence);
310 assert!(result.is_err());
312 let (err, _consumed) = result.unwrap_err();
313 assert_eq!(err, ParseError::InvalidMessage);
314 }
315
316 #[test]
317 fn test_rmc_with_empty_status() {
318 let parser = NmeaParser::new();
319 let sentence = b"$GPRMC,123519,V,4807.038,N,01131.000,E,022.4,084.4,230394,003.1,W*6A\r\n";
321
322 let result = parser.parse_bytes(sentence);
323 assert!(result.is_ok());
324 let (msg, _) = result.unwrap();
325 let msg = msg.unwrap();
326 let rmc = msg.as_rmc();
327 assert!(rmc.is_some());
328
329 let rmc_data = rmc.unwrap();
330 assert_eq!(rmc_data.status, 'V');
331 }
332
333 #[test]
334 fn test_gsa_with_partial_satellites() {
335 let parser = NmeaParser::new();
336 let sentence = b"$GPGSA,A,3,01,,,,,,,,,,,,2.5,1.3,2.1*39\r\n";
338
339 let result = parser.parse_bytes(sentence);
340 assert!(result.is_ok());
341 let (msg, _) = result.unwrap();
342 let msg = msg.unwrap();
343 let gsa = msg.as_gsa();
344 assert!(gsa.is_some());
345
346 let gsa_data = gsa.unwrap();
347 assert_eq!(gsa_data.mode, 'A');
348 assert_eq!(gsa_data.fix_type, 3);
349 assert_eq!(gsa_data.satellite_ids[0], Some(1));
350 assert_eq!(gsa_data.satellite_ids[1], None);
351 assert_eq!(gsa_data.pdop, Some(2.5));
352 assert_eq!(gsa_data.hdop, Some(1.3));
353 assert_eq!(gsa_data.vdop, Some(2.1));
354 }
355
356 #[test]
357 fn test_gsv_with_partial_satellite_data() {
358 let parser = NmeaParser::new();
359 let sentence = b"$GPGSV,1,1,02,01,40,083,46,02,17,308,*75\r\n";
361
362 let result = parser.parse_bytes(sentence);
363 assert!(result.is_ok());
364 let (msg, _) = result.unwrap();
365 let msg = msg.unwrap();
366 let gsv = msg.as_gsv();
367 assert!(gsv.is_some());
368
369 let gsv_data = gsv.unwrap();
370 assert_eq!(gsv_data.num_messages, 1);
371 assert_eq!(gsv_data.satellites_in_view, 2);
372
373 assert!(gsv_data.satellite_info[0].is_some());
375 let sat1 = gsv_data.satellite_info[0].as_ref().unwrap();
376 assert_eq!(sat1.prn, Some(1));
377 assert_eq!(sat1.elevation, Some(40));
378 assert_eq!(sat1.azimuth, Some(83));
379 assert_eq!(sat1.snr, Some(46));
380
381 assert!(gsv_data.satellite_info[1].is_some());
383 let sat2 = gsv_data.satellite_info[1].as_ref().unwrap();
384 assert_eq!(sat2.prn, Some(2));
385 assert_eq!(sat2.elevation, Some(17));
386 assert_eq!(sat2.azimuth, Some(308));
387 assert_eq!(sat2.snr, None);
388
389 assert!(gsv_data.satellite_info[2].is_none());
391 assert!(gsv_data.satellite_info[3].is_none());
392 }
393
394 #[test]
395 fn test_numeric_type_parsing() {
396 let parser = NmeaParser::new();
397 let sentence = b"$GPGGA,123519,4807.038,N,01131.000,E,1,08,0.9,545.4,M,46.9,M,,*47\r\n";
398
399 let result = parser.parse_bytes(sentence);
400 assert!(result.is_ok());
401 let (msg, _) = result.unwrap();
402 let msg = msg.unwrap();
403 let gga = msg.as_gga();
404 assert!(gga.is_some());
405
406 let gga_data = gga.unwrap();
407
408 assert!((gga_data.latitude - 4807.038).abs() < 0.001);
410 assert!((gga_data.longitude - 1131.000).abs() < 0.001);
411
412 if let Some(hdop) = gga_data.hdop {
413 assert!((hdop - 0.9).abs() < 0.01);
414 }
415
416 if let Some(alt) = gga_data.altitude {
417 assert!((alt - 545.4).abs() < 0.1);
418 }
419 }
420
421 #[test]
422 fn test_messages_with_missing_mandatory_fields() {
423 let parser = NmeaParser::new();
424 let test_cases = [
425 b"$GPGGA,,4807.038,N,01131.000,E,1,08,0.9,545.4,M,46.9,M,,*47\r\n".as_slice(), b"$GPRMC,123519,A,4807.038,N,01131.000,E,022.4,084.4,,003.1,W*6A\r\n".as_slice(), b"$GPGSA,,3,04,05,,09,12,,,24,,,,,2.5,1.3,2.1*39\r\n".as_slice(), b"$GPGSV,,1,08,01,40,083,46,02,17,308,41,12,07,344,39,14,22,228,45*75\r\n".as_slice(), b"$GPGLL,4916.45,N,12311.12,W,225444,,*1D\r\n".as_slice(), ];
431
432 for sentence in test_cases {
433 let result = parser.parse_bytes(sentence);
434 assert!(result.is_err());
435 let (err, _consumed) = result.unwrap_err();
436 assert_eq!(err, ParseError::InvalidMessage);
437 }
438 }
439
440 #[test]
442 fn test_parse_with_spurious_characters() {
443 let parser = NmeaParser::new();
444 let data = b"GARBAGE$GPGGA,123519,4807.038,N,01131.000,E,1,08,0.9,545.4,M,46.9,M,,*47\r\n";
446
447 let result = parser.parse_bytes(data);
448 assert!(result.is_ok());
449 let (msg, consumed) = result.unwrap();
450 assert!(msg.is_some());
451 assert_eq!(consumed, data.len());
452 }
453
454 #[test]
455 fn test_parse_multiple_messages_in_buffer() {
456 let parser = NmeaParser::new();
457 let data = b"$GPGGA,123519,4807.038,N,01131.000,E,1,08,0.9,545.4,M,46.9,M,,*47\r\n\
458 $GPRMC,123519,A,4807.038,N,01131.000,E,022.4,084.4,230394,003.1,W*6A\r\n";
459
460 let result1 = parser.parse_bytes(data);
462 assert!(result1.is_ok());
463 let (msg1, consumed1) = result1.unwrap();
464 assert!(msg1.is_some());
465 assert_eq!(msg1.unwrap().message_type(), MessageType::GGA);
466
467 let result2 = parser.parse_bytes(&data[consumed1..]);
469 assert!(result2.is_ok());
470 let (msg2, consumed2) = result2.unwrap();
471 assert!(msg2.is_some());
472 assert_eq!(msg2.unwrap().message_type(), MessageType::RMC);
473
474 assert_eq!(consumed1 + consumed2, data.len());
476 }
477
478 #[test]
479 fn test_partial_message_returns_none() {
480 let parser = NmeaParser::new();
481 let partial = b"$GPGGA,123519,4807.038,N,01131.000,E,1";
483
484 let result = parser.parse_bytes(partial);
485 assert!(result.is_ok());
486 let (msg, consumed) = result.unwrap();
487 assert!(msg.is_none());
488 assert_eq!(consumed, 0); }
490
491 #[test]
492 fn test_spurious_only_data() {
493 let parser = NmeaParser::new();
494 let spurious = b"GARBAGE DATA WITHOUT DOLLAR SIGN\r\n";
496
497 let result = parser.parse_bytes(spurious);
498 assert!(result.is_ok());
499 let (msg, consumed) = result.unwrap();
500 assert!(msg.is_none());
501 assert_eq!(consumed, spurious.len()); }
503
504 #[test]
505 fn test_bytes_consumed_tracking() {
506 let parser = NmeaParser::new();
507 let data = b"JUNK$GPGGA,123519,4807.038,N,01131.000,E,1,08,0.9,545.4,M,46.9,M,,*47\r\nMORE";
508
509 let result = parser.parse_bytes(data);
510 assert!(result.is_ok());
511 let (msg, consumed) = result.unwrap();
512 assert!(msg.is_some());
513 assert!(consumed < data.len());
515 assert!(consumed > 0);
516 assert_eq!(&data[consumed..], b"MORE");
517 }
518
519 #[test]
520 fn test_invalid_message_returns_error() {
521 let parser = NmeaParser::new();
522 let invalid = b"$GPGGA,,4807.038,N,01131.000,E,1,08,0.9,545.4,M,46.9,M,,*47\r\n";
524
525 let result = parser.parse_bytes(invalid);
526 assert!(result.is_err());
527 let (err, _consumed) = result.unwrap_err();
528 assert_eq!(err, ParseError::InvalidMessage);
529 }
530
531 #[test]
532 fn test_multiple_messages_with_different_timestamps() {
533 let parser = NmeaParser::new();
534 let data = b"$GPGGA,123519,4807.038,N,01131.000,E,1,08,0.9,545.4,M,46.9,M,,*47\r\n\
535 $GPRMC,123519,A,4807.038,N,01131.000,E,022.4,084.4,230394,003.1,W*6A\r\n";
536
537 let result1 = parser.parse_bytes(data);
539 assert!(result1.is_ok());
540 let (msg1, consumed1) = result1.unwrap();
541 assert!(msg1.is_some());
542 let msg1 = msg1.unwrap();
543 assert_eq!(msg1.message_type(), MessageType::GGA);
544
545 let result2 = parser.parse_bytes(&data[consumed1..]);
547 assert!(result2.is_ok());
548 let (msg2, _consumed2) = result2.unwrap();
549 assert!(msg2.is_some());
550 let msg2 = msg2.unwrap();
551 assert_eq!(msg2.message_type(), MessageType::RMC);
552 }
553}