nmea/parser.rs
1//! The [`Nmea`] parser.
2
3use core::{fmt, mem, ops::BitOr};
4
5use chrono::{NaiveDate, NaiveTime};
6use heapless::{Deque, Vec};
7
8use crate::{
9 parse_str,
10 sentences::{rmc::RmcStatusOfFix, *},
11 Error, ParseResult,
12};
13
14#[cfg(feature = "serde")]
15use serde::{de::Visitor, ser::SerializeSeq, Deserialize, Serialize};
16
17/// NMEA parser
18///
19/// This struct parses NMEA sentences, including checksum checks and sentence
20/// validation.
21///
22/// # Examples
23///
24/// ```
25/// use nmea::Nmea;
26///
27/// let mut nmea = Nmea::default();
28/// let gga = "$GPGGA,092750.000,5321.6802,N,00630.3372,W,1,8,1.03,61.7,M,55.2,M,,*76";
29/// # #[cfg(feature = "GGA")]
30/// # {
31/// // feature `GGA` should be enabled to parse this sentence.
32/// nmea.parse(gga).unwrap();
33/// println!("{}", nmea);
34/// # }
35///
36/// ```
37#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
38#[cfg_attr(feature = "defmt-03", derive(defmt::Format))]
39#[derive(Debug, Clone, Default)]
40pub struct Nmea {
41 #[cfg_attr(feature = "defmt-03", defmt(Debug2Format))]
42 pub fix_time: Option<NaiveTime>,
43 #[cfg_attr(feature = "defmt-03", defmt(Debug2Format))]
44 pub fix_date: Option<NaiveDate>,
45 pub fix_type: Option<FixType>,
46 pub latitude: Option<f64>,
47 pub longitude: Option<f64>,
48 /// MSL Altitude in meters
49 pub altitude: Option<f32>,
50 pub speed_over_ground: Option<f32>,
51 pub true_course: Option<f32>,
52 pub num_of_fix_satellites: Option<u32>,
53 pub hdop: Option<f32>,
54 pub vdop: Option<f32>,
55 pub pdop: Option<f32>,
56 /// Geoid separation in meters
57 pub geoid_separation: Option<f32>,
58 pub fix_satellites_prns: Option<Vec<u32, 18>>,
59 satellites_scan: [SatsPack; GnssType::COUNT],
60 required_sentences_for_nav: SentenceMask,
61 #[cfg_attr(feature = "defmt-03", defmt(Debug2Format))]
62 last_fix_time: Option<NaiveTime>,
63 last_txt: Option<TxtData>,
64 sentences_for_this_time: SentenceMask,
65}
66
67impl<'a> Nmea {
68 /// Constructs a new `Nmea` for navigation purposes.
69 ///
70 /// # Examples
71 ///
72 /// ```
73 /// use nmea::{Nmea, SentenceType};
74 ///
75 /// let mut nmea = Nmea::create_for_navigation(&[SentenceType::RMC, SentenceType::GGA]).unwrap();
76 /// let gga = "$GPGGA,092750.000,5321.6802,N,00630.3372,W,1,8,1.03,61.7,M,55.2,M,,*76";
77 /// # #[cfg(feature = "GGA")]
78 /// # {
79 /// // feature `GGA` should be enabled to parse this sentence.
80 /// nmea.parse(gga).unwrap();
81 /// println!("{}", nmea);
82 /// # }
83 /// ```
84 pub fn create_for_navigation(
85 required_sentences_for_nav: &[SentenceType],
86 ) -> Result<Nmea, Error<'a>> {
87 if required_sentences_for_nav.is_empty() {
88 return Err(Error::EmptyNavConfig);
89 }
90 let mut n = Self::default();
91 for sentence in required_sentences_for_nav.iter() {
92 n.required_sentences_for_nav.insert(*sentence);
93 }
94 Ok(n)
95 }
96
97 /// Returns fix type
98 pub fn fix_timestamp(&self) -> Option<NaiveTime> {
99 self.fix_time
100 }
101
102 /// Returns fix type
103 pub fn fix_type(&self) -> Option<FixType> {
104 self.fix_type
105 }
106
107 /// Returns last fixed latitude in degrees. None if not fixed.
108 pub fn latitude(&self) -> Option<f64> {
109 self.latitude
110 }
111
112 /// Returns last fixed longitude in degrees. None if not fixed.
113 pub fn longitude(&self) -> Option<f64> {
114 self.longitude
115 }
116
117 /// Returns altitude above WGS-84 ellipsoid, meters.
118 pub fn altitude(&self) -> Option<f32> {
119 self.altitude
120 }
121
122 /// Returns the number of satellites use for fix.
123 pub fn fix_satellites(&self) -> Option<u32> {
124 self.num_of_fix_satellites
125 }
126
127 /// Returns the number fix HDOP
128 pub fn hdop(&self) -> Option<f32> {
129 self.hdop
130 }
131
132 /// Returns the altitude above MSL (geoid), meters.
133 pub fn geoid_altitude(&self) -> Option<f32> {
134 match (self.altitude, self.geoid_separation) {
135 (Some(alt), Some(geoid_diff)) => Some(alt + geoid_diff),
136 _ => None,
137 }
138 }
139
140 /// Returns used satellites
141 pub fn satellites(&self) -> Vec<Satellite, 58> {
142 let mut ret = Vec::<Satellite, 58>::new();
143 let sat_key = |sat: &Satellite| (sat.gnss_type() as u8, sat.prn());
144 for sns in &self.satellites_scan {
145 // for sat_pack in sns.data.iter().rev() {
146 for sat_pack in sns.data.iter().rev().flatten() {
147 for sat in sat_pack.iter() {
148 match ret.binary_search_by_key(&sat_key(sat), sat_key) {
149 //already set
150 Ok(_pos) => {}
151 Err(pos) => ret.insert(pos, sat.clone()).unwrap(),
152 }
153 }
154 }
155 }
156 ret
157 }
158
159 fn merge_gga_data(&mut self, gga_data: GgaData) {
160 self.fix_time = gga_data.fix_time;
161 self.latitude = gga_data.latitude;
162 self.longitude = gga_data.longitude;
163 self.fix_type = gga_data.fix_type;
164 self.num_of_fix_satellites = gga_data.fix_satellites;
165 self.hdop = gga_data.hdop;
166 self.altitude = gga_data.altitude;
167 self.geoid_separation = gga_data.geoid_separation;
168 }
169
170 fn merge_gsv_data(&mut self, data: GsvData) -> Result<(), Error<'a>> {
171 {
172 let d = &mut self.satellites_scan[data.gnss_type as usize];
173 let full_pack_size: usize = data.sentence_num.into();
174 d.max_len = full_pack_size.max(d.max_len);
175 d.data
176 .push_back(data.sats_info)
177 .expect("Should not get the more than expected number of satellites");
178 if d.data.len() > d.max_len {
179 d.data.pop_front();
180 }
181 }
182
183 Ok(())
184 }
185
186 fn merge_rmc_data(&mut self, rmc_data: RmcData) {
187 self.fix_time = rmc_data.fix_time;
188 self.fix_date = rmc_data.fix_date;
189 self.fix_type = Some(match rmc_data.status_of_fix {
190 RmcStatusOfFix::Autonomous => FixType::Gps,
191 RmcStatusOfFix::Differential => FixType::DGps,
192 RmcStatusOfFix::Invalid => FixType::Invalid,
193 });
194 self.latitude = rmc_data.lat;
195 self.longitude = rmc_data.lon;
196 self.speed_over_ground = rmc_data.speed_over_ground;
197 self.true_course = rmc_data.true_course;
198 }
199
200 fn merge_gns_data(&mut self, gns_data: GnsData) {
201 self.fix_time = gns_data.fix_time;
202 self.fix_type = Some(gns_data.faa_modes.into());
203 self.latitude = gns_data.lat;
204 self.longitude = gns_data.lon;
205 self.altitude = gns_data.alt;
206 self.hdop = gns_data.hdop;
207 self.geoid_separation = gns_data.geoid_separation;
208 }
209
210 fn merge_gsa_data(&mut self, gsa: GsaData) {
211 self.fix_satellites_prns = Some(gsa.fix_sats_prn);
212 self.hdop = gsa.hdop;
213 self.vdop = gsa.vdop;
214 self.pdop = gsa.pdop;
215 }
216
217 fn merge_vtg_data(&mut self, vtg: VtgData) {
218 self.speed_over_ground = vtg.speed_over_ground;
219 self.true_course = vtg.true_course;
220 }
221
222 fn merge_gll_data(&mut self, gll: GllData) {
223 self.latitude = gll.latitude;
224 self.longitude = gll.longitude;
225 self.fix_time = Some(gll.fix_time);
226 if let Some(faa_mode) = gll.faa_mode {
227 self.fix_type = Some(faa_mode.into());
228 } else {
229 self.fix_type = Some(if gll.valid {
230 FixType::Gps
231 } else {
232 FixType::Invalid
233 });
234 }
235 }
236
237 fn merge_txt_data(&mut self, txt: TxtData) {
238 self.last_txt = Some(txt);
239 }
240
241 /// Parse any NMEA sentence and stores the result of sentences that include:
242 /// - altitude
243 /// - latitude and longitude
244 /// - speed_over_ground
245 /// - and other
246 ///
247 /// The type of sentence is returned if implemented and valid.
248 pub fn parse(&mut self, sentence: &'a str) -> Result<SentenceType, Error<'a>> {
249 match parse_str(sentence)? {
250 ParseResult::VTG(vtg) => {
251 self.merge_vtg_data(vtg);
252 Ok(SentenceType::VTG)
253 }
254 ParseResult::GGA(gga) => {
255 self.merge_gga_data(gga);
256 Ok(SentenceType::GGA)
257 }
258 ParseResult::GSV(gsv) => {
259 self.merge_gsv_data(gsv)?;
260 Ok(SentenceType::GSV)
261 }
262 ParseResult::RMC(rmc) => {
263 self.merge_rmc_data(rmc);
264 Ok(SentenceType::RMC)
265 }
266 ParseResult::GNS(gns) => {
267 self.merge_gns_data(gns);
268 Ok(SentenceType::GNS)
269 }
270 ParseResult::GSA(gsa) => {
271 self.merge_gsa_data(gsa);
272 Ok(SentenceType::GSA)
273 }
274 ParseResult::GLL(gll) => {
275 self.merge_gll_data(gll);
276 Ok(SentenceType::GLL)
277 }
278 ParseResult::TXT(txt) => {
279 self.merge_txt_data(txt);
280 Ok(SentenceType::TXT)
281 }
282 ParseResult::Unsupported(sentence_type) => Err(Error::Unsupported(sentence_type)),
283 // any other implemented sentence which is not part of the `Nmea` parsing is unsupported
284 // at this time being
285 ref parse_result => Err(Error::Unsupported(parse_result.into())),
286 }
287 }
288
289 fn new_tick(&mut self) {
290 let old = mem::take(self);
291 self.satellites_scan = old.satellites_scan;
292 self.required_sentences_for_nav = old.required_sentences_for_nav;
293 self.last_fix_time = old.last_fix_time;
294 }
295
296 fn clear_position_info(&mut self) {
297 self.last_fix_time = None;
298 self.new_tick();
299 }
300
301 pub fn parse_for_fix(&mut self, xs: &'a str) -> Result<FixType, Error<'a>> {
302 match parse_str(xs)? {
303 ParseResult::GSA(gsa) => {
304 self.merge_gsa_data(gsa);
305 return Ok(FixType::Invalid);
306 }
307 ParseResult::GSV(gsv_data) => {
308 self.merge_gsv_data(gsv_data)?;
309 return Ok(FixType::Invalid);
310 }
311 ParseResult::VTG(vtg) => {
312 //have no time field, so only if user explicitly mention it
313 if self.required_sentences_for_nav.contains(&SentenceType::VTG) {
314 if vtg.true_course.is_none() || vtg.speed_over_ground.is_none() {
315 self.clear_position_info();
316 return Ok(FixType::Invalid);
317 }
318 self.merge_vtg_data(vtg);
319 self.sentences_for_this_time.insert(SentenceType::VTG);
320 } else {
321 return Ok(FixType::Invalid);
322 }
323 }
324 ParseResult::RMC(rmc_data) => {
325 if rmc_data.status_of_fix == RmcStatusOfFix::Invalid {
326 self.clear_position_info();
327 return Ok(FixType::Invalid);
328 }
329 if !self.update_fix_time(rmc_data.fix_time) {
330 return Ok(FixType::Invalid);
331 }
332 self.merge_rmc_data(rmc_data);
333 self.sentences_for_this_time.insert(SentenceType::RMC);
334 }
335 ParseResult::GNS(gns_data) => {
336 let fix_type: FixType = gns_data.faa_modes.into();
337 if !fix_type.is_valid() {
338 self.clear_position_info();
339 return Ok(FixType::Invalid);
340 }
341 if !self.update_fix_time(gns_data.fix_time) {
342 return Ok(FixType::Invalid);
343 }
344 self.merge_gns_data(gns_data);
345 self.sentences_for_this_time.insert(SentenceType::GNS);
346 }
347 ParseResult::GGA(gga_data) => {
348 match gga_data.fix_type {
349 Some(FixType::Invalid) | None => {
350 self.clear_position_info();
351 return Ok(FixType::Invalid);
352 }
353 _ => { /*nothing*/ }
354 }
355 if !self.update_fix_time(gga_data.fix_time) {
356 return Ok(FixType::Invalid);
357 }
358 self.merge_gga_data(gga_data);
359 self.sentences_for_this_time.insert(SentenceType::GGA);
360 }
361 ParseResult::GLL(gll_data) => {
362 if !self.update_fix_time(Some(gll_data.fix_time)) {
363 return Ok(FixType::Invalid);
364 }
365 self.merge_gll_data(gll_data);
366 return Ok(FixType::Invalid);
367 }
368 ParseResult::TXT(txt_data) => {
369 self.merge_txt_data(txt_data);
370 return Ok(FixType::Invalid);
371 }
372 ParseResult::BWC(_)
373 | ParseResult::BWW(_)
374 | ParseResult::BOD(_)
375 | ParseResult::DBK(_)
376 | ParseResult::DPT(_)
377 | ParseResult::GBS(_)
378 | ParseResult::GST(_)
379 | ParseResult::AAM(_)
380 | ParseResult::APA(_)
381 | ParseResult::ALM(_)
382 | ParseResult::HDT(_)
383 | ParseResult::PGRMZ(_)
384 | ParseResult::MTW(_)
385 | ParseResult::MWV(_)
386 | ParseResult::MDA(_)
387 | ParseResult::VHW(_)
388 | ParseResult::TTM(_)
389 | ParseResult::ZDA(_)
390 | ParseResult::ZFO(_)
391 | ParseResult::WNC(_)
392 | ParseResult::ZTG(_) => return Ok(FixType::Invalid),
393
394 ParseResult::Unsupported(_) => {
395 return Ok(FixType::Invalid);
396 }
397 }
398 match self.fix_type {
399 Some(FixType::Invalid) | None => Ok(FixType::Invalid),
400 Some(ref fix_type)
401 if self
402 .required_sentences_for_nav
403 .is_subset(&self.sentences_for_this_time) =>
404 {
405 Ok(*fix_type)
406 }
407 _ => Ok(FixType::Invalid),
408 }
409 }
410
411 pub fn last_txt(&self) -> Option<&TxtData> {
412 self.last_txt.as_ref()
413 }
414
415 fn update_fix_time(&mut self, fix_time: Option<NaiveTime>) -> bool {
416 match (self.last_fix_time, fix_time) {
417 (Some(ref last_fix_time), Some(ref new_fix_time)) => {
418 if *last_fix_time != *new_fix_time {
419 self.new_tick();
420 self.last_fix_time = Some(*new_fix_time);
421 }
422 }
423 (None, Some(ref new_fix_time)) => self.last_fix_time = Some(*new_fix_time),
424 (Some(_), None) | (None, None) => {
425 self.clear_position_info();
426 return false;
427 }
428 }
429 true
430 }
431}
432
433impl fmt::Display for Nmea {
434 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
435 write!(
436 f,
437 "{}: lat: {} lon: {} alt: {} {:?}",
438 format_args!("{:?}", self.fix_time),
439 format_args!("{:?}", self.latitude),
440 format_args!("{:?}", self.longitude),
441 format_args!("{:?}", self.altitude),
442 self.satellites()
443 )
444 }
445}
446
447#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
448#[cfg_attr(feature = "defmt-03", derive(defmt::Format))]
449#[derive(Debug, Clone, Default)]
450struct SatsPack {
451 /// max number of visible GNSS satellites per hemisphere, assuming global coverage
452 /// GPS: 16
453 /// GLONASS: 12
454 /// BeiDou: 12 + 3 IGSO + 3 GEO
455 /// Galileo: 12
456 /// => 58 total Satellites => max 15 rows of data
457 #[cfg_attr(feature = "serde", serde(with = "serde_deq"))]
458 #[cfg_attr(feature = "defmt-03", defmt(Debug2Format))]
459 data: Deque<Vec<Option<Satellite>, 4>, 15>,
460 max_len: usize,
461}
462
463#[cfg(feature = "serde")]
464mod serde_deq {
465 use super::*;
466
467 pub fn serialize<S>(v: &Deque<Vec<Option<Satellite>, 4>, 15>, s: S) -> Result<S::Ok, S::Error>
468 where
469 S: serde::Serializer,
470 {
471 let mut seq = s.serialize_seq(Some(15))?;
472 for e in v.iter() {
473 seq.serialize_element(e)?;
474 }
475 seq.end()
476 }
477
478 struct DequeVisitor;
479
480 impl<'de> Visitor<'de> for DequeVisitor {
481 type Value = Deque<Vec<Option<Satellite>, 4>, 15>;
482
483 fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
484 formatter.write_str("deque of vectors containing optional Satellite structs")
485 }
486
487 fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>
488 where
489 A: serde::de::SeqAccess<'de>,
490 {
491 let mut deq: Deque<Vec<Option<Satellite>, 4>, 15> = Deque::new();
492
493 while let Some(v) = seq.next_element()? {
494 deq.push_back(v)
495 .map_err(|_| serde::de::Error::invalid_length(deq.capacity() + 1, &self))?;
496 }
497
498 Ok(deq)
499 }
500 }
501
502 pub fn deserialize<'de, D>(d: D) -> Result<Deque<Vec<Option<Satellite>, 4>, 15>, D::Error>
503 where
504 D: serde::Deserializer<'de>,
505 {
506 d.deserialize_seq(DequeVisitor)
507 }
508}
509
510#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
511#[cfg_attr(feature = "defmt-03", derive(defmt::Format))]
512#[derive(Clone, PartialEq)]
513/// Satellite information
514pub struct Satellite {
515 pub(crate) gnss_type: GnssType,
516 pub(crate) prn: u32,
517 pub(crate) elevation: Option<f32>,
518 pub(crate) azimuth: Option<f32>,
519 pub(crate) snr: Option<f32>,
520}
521
522impl Satellite {
523 #[inline]
524 pub fn gnss_type(&self) -> GnssType {
525 self.gnss_type
526 }
527 #[inline]
528 pub fn prn(&self) -> u32 {
529 self.prn
530 }
531 #[inline]
532 pub fn elevation(&self) -> Option<f32> {
533 self.elevation
534 }
535 #[inline]
536 pub fn azimuth(&self) -> Option<f32> {
537 self.azimuth
538 }
539 #[inline]
540 pub fn snr(&self) -> Option<f32> {
541 self.snr
542 }
543}
544
545impl fmt::Display for Satellite {
546 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
547 write!(
548 f,
549 "{}: {} elv: {} ath: {} snr: {}",
550 self.gnss_type,
551 self.prn,
552 format_args!("{:?}", self.elevation),
553 format_args!("{:?}", self.azimuth),
554 format_args!("{:?}", self.snr),
555 )
556 }
557}
558
559impl fmt::Debug for Satellite {
560 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
561 write!(
562 f,
563 "[{:?},{:?},{:?},{:?},{:?}]",
564 self.gnss_type, self.prn, self.elevation, self.azimuth, self.snr
565 )
566 }
567}
568
569macro_rules! count_tts {
570 () => {0usize};
571 ($_head:tt , $($tail:tt)*) => {1usize + count_tts!($($tail)*)};
572 ($item:tt) => {1usize};
573}
574pub(crate) use count_tts;
575
576macro_rules! define_sentence_type_enum {
577 (
578 $(#[$outer:meta])*
579 pub enum $Name:ident {
580 $(
581 $(#[$variant:meta])*
582 $Variant:ident
583 ),* $(,)* }
584 ) => {
585 $(#[$outer])*
586 pub enum $Name {
587 $(
588 $(#[$variant])*
589 $Variant
590 ),*,
591 }
592
593 impl<'a> TryFrom<&'a str> for SentenceType {
594 type Error = crate::Error<'a>;
595
596 fn try_from(s: &'a str) -> Result<$Name, Self::Error> {
597 match s {
598 $(stringify!($Variant) => Ok($Name::$Variant),)*
599 _ => Err(Error::Unknown(s)),
600 }
601 }
602 }
603
604 impl $Name {
605 const COUNT: usize = count_tts!($($Variant),*);
606 pub const TYPES: [$Name; $Name::COUNT] = [$($Name::$Variant,)*];
607
608 pub fn to_mask_value(self) -> u128 {
609 1 << self as u32
610 }
611
612 pub fn as_str(&self) -> &str {
613 match self {
614 $($Name::$Variant => stringify!($Variant),)*
615 }
616 }
617 }
618
619 // impl core::str::FromStr for $Name {
620 // type Err = crate::Error;
621
622 // fn from_str(s: &'a str) -> Result<Self, Self::Err> {
623 // match s {
624 // $(stringify!($Variant) => Ok($Name::$Variant),)*
625 // _ => Err(crate::Error::Unknown(s)),
626 // }
627 // }
628 // }
629
630 impl core::fmt::Display for $Name {
631 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> fmt::Result {
632 f.write_str(self.as_str())
633 }
634 }
635 }
636}
637
638define_sentence_type_enum! {
639 /// NMEA sentence type
640 ///
641 /// ## Types
642 ///
643 /// ### General
644 ///
645 /// - [`SentenceType::OSD`]
646 ///
647 /// ### Autopilot:
648 ///
649 /// - [`SentenceType::APA`]
650 /// - [`SentenceType::APB`]
651 /// - [`SentenceType::ASD`]
652 ///
653 /// ### Decca
654 ///
655 /// - [`SentenceType::DCN`]
656 ///
657 /// ### D-GPS
658 ///
659 /// - [`SentenceType::MSK`]
660 ///
661 /// ### Echo
662 /// - [`SentenceType::DBK`]
663 /// - [`SentenceType::DBS`]
664 /// - [`SentenceType::DBT`]
665 ///
666 /// ### Radio
667 ///
668 /// - [`SentenceType::FSI`]
669 /// - [`SentenceType::SFI`]
670 /// - [`SentenceType::TLL`]
671 ///
672 /// ### Speed
673 ///
674 /// - [`SentenceType::VBW`]
675 /// - [`SentenceType::VHW`]
676 /// - [`SentenceType::VLW`]
677 ///
678 /// ### GPS
679 ///
680 /// - [`SentenceType::ALM`]
681 /// - [`SentenceType::GBS`]
682 /// - [`SentenceType::GGA`]
683 /// - [`SentenceType::GNS`]
684 /// - [`SentenceType::GSA`]
685 /// - [`SentenceType::GST`]
686 /// - [`SentenceType::GSV`]
687 ///
688 /// ### Course
689 ///
690 /// - [`SentenceType::DPT`]
691 /// - [`SentenceType::HDG`]
692 /// - [`SentenceType::HDM`]
693 /// - [`SentenceType::HDT`]
694 /// - [`SentenceType::HSC`]
695 /// - [`SentenceType::ROT`]
696 /// - [`SentenceType::VDR`]
697 ///
698 /// ### Loran-C
699 ///
700 /// - [`SentenceType::GLC`]
701 /// - [`SentenceType::LCD`]
702 ///
703 /// ### Machine
704 ///
705 /// - [`SentenceType::RPM`]
706 ///
707 /// ### Navigation
708 ///
709 /// - [`SentenceType::RMA`]
710 /// - [`SentenceType::RMB`]
711 /// - [`SentenceType::RMC`]
712 ///
713 /// ### Omega
714 ///
715 /// - [`SentenceType::OLN`]
716 ///
717 /// ### Position
718 ///
719 /// - [`SentenceType::GLL`]
720 /// - [`SentenceType::DTM`]
721 ///
722 /// ### Radar
723 ///
724 /// - [`SentenceType::RSD`]
725 /// - [`SentenceType::TLL`]
726 /// - [`SentenceType::TTM`]
727 ///
728 /// ### Rudder
729 ///
730 /// - [`SentenceType::RSA`]
731 ///
732 /// ### Temperature
733 ///
734 /// - [`SentenceType::MTW`]
735 ///
736 /// ### Transit
737 ///
738 /// - [`SentenceType::GXA`]
739 /// - `SentenceType::RTF` (missing?!)
740 ///
741 /// ### Waypoints and tacks
742 ///
743 /// - [`SentenceType::AAM`]
744 /// - [`SentenceType::BEC`]
745 /// - [`SentenceType::BOD`]
746 /// - [`SentenceType::BWC`]
747 /// - [`SentenceType::BWR`]
748 /// - [`SentenceType::BWW`]
749 /// - [`SentenceType::ROO`]
750 /// - [`SentenceType::RTE`]
751 /// - [`SentenceType::VTG`]
752 /// - [`SentenceType::WCV`]
753 /// - [`SentenceType::WNC`]
754 /// - [`SentenceType::WPL`]
755 /// - [`SentenceType::XDR`]
756 /// - [`SentenceType::XTE`]
757 /// - [`SentenceType::XTR`]
758 ///
759 /// ### Wind
760 ///
761 /// - [`SentenceType::MWV`]
762 /// - [`SentenceType::VPW`]
763 /// - [`SentenceType::VWR`]
764 ///
765 /// ### Date and Time
766 ///
767 /// - [`SentenceType::GTD`]
768 /// - [`SentenceType::ZDA`]
769 /// - [`SentenceType::ZFO`]
770 /// - [`SentenceType::ZTG`]
771 ///
772 /// ### Vendor extensions
773 ///
774 /// - [`SentenceType::RMZ`]
775 #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
776#[cfg_attr(feature = "defmt-03", derive(defmt::Format))]
777 #[derive(Debug, Hash, PartialEq, Eq, Clone, Copy)]
778 #[repr(u32)]
779 #[allow(rustdoc::bare_urls)]
780 pub enum SentenceType {
781 /// AAM - Waypoint Arrival Alarm
782 ///
783 /// <https://gpsd.gitlab.io/gpsd/NMEA.html#_aam_waypoint_arrival_alarm>
784 ///
785 /// Type: `Waypoints and tacks`
786 AAM,
787 ABK,
788 ACA,
789 ACK,
790 ACS,
791 AIR,
792 /// ALM - GPS Almanac Data
793 ///
794 /// <https://gpsd.gitlab.io/gpsd/NMEA.html#_alm_gps_almanac_data>
795 ///
796 /// Type: `GPS`
797 ALM,
798 ALR,
799 /// APA - Autopilot Sentence "A"
800 ///
801 /// <https://gpsd.gitlab.io/gpsd/NMEA.html#_apa_autopilot_sentence_a>
802 ///
803 /// Type: `Autopilot`
804 APA,
805 /// APB - Autopilot Sentence "B"
806 ///
807 /// <https://gpsd.gitlab.io/gpsd/NMEA.html#_apb_autopilot_sentence_b>
808 ///
809 /// Type: `Autopilot`
810 APB,
811 /// Type: `Autopilot`
812 ASD,
813 /// Type: `Waypoints and tacks`
814 BEC,
815 /// BOD - Bearing - Waypoint to Waypoint
816 ///
817 /// <https://gpsd.gitlab.io/gpsd/NMEA.html#_bod_bearing_waypoint_to_waypoint>
818 ///
819 /// Type: `Waypoints and tacks`
820 BOD,
821 /// BWC - Bearing & Distance to Waypoint - Great Circle
822 ///
823 /// <https://gpsd.gitlab.io/gpsd/NMEA.html#_bwc_bearing_distance_to_waypoint_great_circle>
824 ///
825 /// Type: `Waypoints and tacks`
826 BWC,
827 /// BWR - Bearing and Distance to Waypoint - Rhumb Line
828 ///
829 /// <https://gpsd.gitlab.io/gpsd/NMEA.html#_bwr_bearing_and_distance_to_waypoint_rhumb_line>
830 ///
831 /// Type: `Waypoints and tacks`
832 BWR,
833 /// BWW - Bearing - Waypoint to Waypoint
834 ///
835 /// https://gpsd.gitlab.io/gpsd/NMEA.html#_bww_bearing_waypoint_to_waypoint
836 ///
837 /// Type: `Waypoints and tacks`
838 BWW,
839 CUR,
840 /// DBK - Depth Below Keel
841 ///
842 /// <https://gpsd.gitlab.io/gpsd/NMEA.html#_dbk_depth_below_keel>
843 ///
844 /// Type: `Echo`
845 DBK,
846 /// DBS - Depth Below Surface
847 ///
848 /// <https://gpsd.gitlab.io/gpsd/NMEA.html#_dbs_depth_below_surface>
849 ///
850 /// Type: `Echo`
851 DBS,
852 /// DBT - Depth below transducer
853 ///
854 /// <https://gpsd.gitlab.io/gpsd/NMEA.html#_dbt_depth_below_transducer>
855 ///
856 /// Type: `Echo`
857 DBT,
858 /// DCN - Decca Position
859 ///
860 /// <https://gpsd.gitlab.io/gpsd/NMEA.html#_dcn_decca_position>
861 ///
862 /// Type: `Decca`
863 DCN,
864 /// DPT - Depth of Water
865 ///
866 /// <https://gpsd.gitlab.io/gpsd/NMEA.html#_dpt_depth_of_water>
867 ///
868 /// Type: `Course`
869 DPT,
870 DSC,
871 DSE,
872 DSI,
873 /// Type: `Radar`
874 DSR,
875 /// DTM - Datum Reference
876 ///
877 /// <https://gpsd.gitlab.io/gpsd/NMEA.html#_dtm_datum_reference>
878 ///
879 /// Type: `Position`
880 DTM,
881 /// FSI - Frequency Set Information
882 ///
883 /// <https://gpsd.gitlab.io/gpsd/NMEA.html#_fsi_frequency_set_information>
884 ///
885 /// Type: `Radio`
886 FSI,
887 /// GBS - GPS Satellite Fault Detection
888 ///
889 /// <https://gpsd.gitlab.io/gpsd/NMEA.html#_gbs_gps_satellite_fault_detection>
890 ///
891 /// Type: `GPS`
892 GBS,
893 /// GGA - Global Positioning System Fix Data
894 ///
895 /// <https://gpsd.gitlab.io/gpsd/NMEA.html#_gga_global_positioning_system_fix_data>
896 ///
897 /// Type: `GPS`
898 GGA,
899 /// GLC - Geographic Position, Loran-C
900 ///
901 /// <https://gpsd.gitlab.io/gpsd/NMEA.html#_glc_geographic_position_loran_c>
902 ///
903 /// Type: `Loran-C`
904 GLC,
905 /// GLL - Geographic Position - Latitude/Longitude
906 ///
907 /// <https://gpsd.gitlab.io/gpsd/NMEA.html#_gll_geographic_position_latitudelongitude>
908 ///
909 /// Type: `Position`
910 GLL,
911 GMP,
912 /// GNS - Fix data
913 ///
914 /// <https://gpsd.gitlab.io/gpsd/NMEA.html#_gns_fix_data>
915 ///
916 /// Type: `GPS`
917 GNS,
918 /// GRS - GPS Range Residuals
919 ///
920 /// <https://gpsd.gitlab.io/gpsd/NMEA.html#_grs_gps_range_residuals>
921 GRS,
922 /// GSA - GPS DOP and active satellites
923 ///
924 /// <https://gpsd.gitlab.io/gpsd/NMEA.html#_gsa_gps_dop_and_active_satellites>
925 ///
926 /// Type: `GPS`
927 GSA,
928 /// GST - GPS Pseudorange Noise Statistics
929 ///
930 /// https://gpsd.gitlab.io/gpsd/NMEA.html#_gst_gps_pseudorange_noise_statistics
931 GST,
932 /// GSV - Satellites in view
933 ///
934 /// <https://gpsd.gitlab.io/gpsd/NMEA.html#_gsv_satellites_in_view>
935 ///
936 /// Type: `GPS`
937 GSV,
938 /// GTD - Geographic Location in Time Differences
939 ///
940 /// <https://gpsd.gitlab.io/gpsd/NMEA.html#_gtd_geographic_location_in_time_differences>
941 ///
942 /// Type: `Date and Time`
943 GTD,
944 /// GXA - TRANSIT Position - Latitude/Longitude
945 ///
946 /// <https://gpsd.gitlab.io/gpsd/NMEA.html#_gxa_transit_position_latitudelongitude>
947 ///
948 /// Type: `Transit`
949 GXA,
950 /// HDG - Heading - Deviation & Variation
951 ///
952 /// <https://gpsd.gitlab.io/gpsd/NMEA.html#_hdg_heading_deviation_variation>
953 ///
954 /// Type: `Course`
955 HDG,
956 /// HDM - Heading - Magnetic
957 ///
958 /// <https://gpsd.gitlab.io/gpsd/NMEA.html#_hdm_heading_magnetic>
959 ///
960 /// Type: `Course`
961 HDM,
962 ///
963 /// HDT - Heading - True
964 ///
965 /// <https://gpsd.gitlab.io/gpsd/NMEA.html#_hdt_heading_true>
966 ///
967 /// Type: `Course`
968 HDT,
969 /// HFB - Trawl Headrope to Footrope and Bottom
970 ///
971 /// <https://gpsd.gitlab.io/gpsd/NMEA.html#_hfb_trawl_headrope_to_footrope_and_bottom>
972 HFB,
973 HMR,
974 HMS,
975 /// HSC - Heading Steering Command
976 ///
977 /// <https://gpsd.gitlab.io/gpsd/NMEA.html#_hsc_heading_steering_command>
978 ///
979 /// Type: `Course`
980 HSC,
981 /// HWBIAS - Unknown
982 ///
983 /// <https://gpsd.gitlab.io/gpsd/NMEA.html#_hwbias_unknown>
984 HWBIAS,
985 HTC,
986 HTD,
987 /// ITS - Trawl Door Spread 2 Distance
988 ///
989 /// <https://gpsd.gitlab.io/gpsd/NMEA.html#_its_trawl_door_spread_2_distance>
990 ITS,
991 /// LCD - Loran-C Signal Data
992 ///
993 /// <https://gpsd.gitlab.io/gpsd/NMEA.html#_lcd_loran_c_signal_data>
994 ///
995 /// Type: `Loran-C`
996 LCD,
997 LRF,
998 LRI,
999 LR1,
1000 LR2,
1001 LR3,
1002 /// MDA - Meteorological Composite
1003 ///
1004 /// <https://gpsd.gitlab.io/gpsd/NMEA.html#_mda_meteorological_composite>
1005 MDA,
1006 MLA,
1007 /// MSK - Control for a Beacon Receiver
1008 ///
1009 /// <https://gpsd.gitlab.io/gpsd/NMEA.html#_msk_control_for_a_beacon_receiver>
1010 ///
1011 /// Type: `D-GPS`
1012 MSK,
1013 /// MSS - Beacon Receiver Status
1014 ///
1015 /// <https://gpsd.gitlab.io/gpsd/NMEA.html#_mss_beacon_receiver_status>
1016 MSS,
1017 MWD,
1018 /// MTW - Mean Temperature of Water
1019 ///
1020 /// <https://gpsd.gitlab.io/gpsd/NMEA.html#_mtw_mean_temperature_of_water>
1021 ///
1022 /// Type: `Temperature`
1023 MTW,
1024 /// MWV - Wind Speed and Angle
1025 ///
1026 /// <https://gpsd.gitlab.io/gpsd/NMEA.html#_mwv_wind_speed_and_angle>
1027 ///
1028 /// Type: `Wind`
1029 MWV,
1030 /// OLN - Omega Lane Numbers
1031 ///
1032 /// <https://gpsd.gitlab.io/gpsd/NMEA.html#_oln_omega_lane_numbers>
1033 /// Type: `Omega`
1034 OLN,
1035 /// OSD - Own Ship Data
1036 ///
1037 /// <https://gpsd.gitlab.io/gpsd/NMEA.html#_osd_own_ship_data>
1038 ///
1039 /// Type: `General`
1040 OSD,
1041 /// R00 - Waypoints in active route
1042 ///
1043 /// <https://gpsd.gitlab.io/gpsd/NMEA.html#_r00_waypoints_in_active_route>
1044 ///
1045 /// Type: `Waypoints and tacks`
1046 ROO,
1047 /// RLM – Return Link Message
1048 ///
1049 /// <https://gpsd.gitlab.io/gpsd/NMEA.html#_rlm_return_link_message>
1050 RLM,
1051 /// RMA - Recommended Minimum Navigation Information
1052 ///
1053 /// <https://gpsd.gitlab.io/gpsd/NMEA.html#_rma_recommended_minimum_navigation_information>
1054 ///
1055 /// Type: `Navigation`
1056 RMA,
1057 /// RMB - Recommended Minimum Navigation Information
1058 ///
1059 /// <https://gpsd.gitlab.io/gpsd/NMEA.html#_rmb_recommended_minimum_navigation_information>
1060 ///
1061 /// Type: `Navigation`
1062 RMB,
1063 /// RMC - Recommended Minimum Navigation Information
1064 ///
1065 /// <https://gpsd.gitlab.io/gpsd/NMEA.html#_rmc_recommended_minimum_navigation_information>
1066 ///
1067 /// Type: `Navigation`
1068 RMC,
1069 /// PGRMZ - Garmin Altitude
1070 ///
1071 /// <https://gpsd.gitlab.io/gpsd/NMEA.html#_pgrmz_garmin_altitude>
1072 ///
1073 /// Type: `Vendor extensions`
1074 RMZ,
1075 /// ROT - Rate Of Turn
1076 ///
1077 /// <https://gpsd.gitlab.io/gpsd/NMEA.html#_rot_rate_of_turn>
1078 ///
1079 /// Type: `Course`
1080 ROT,
1081 /// RPM - Revolutions
1082 ///
1083 /// <https://gpsd.gitlab.io/gpsd/NMEA.html#_rpm_revolutions>
1084 ///
1085 /// Type: `Machine`
1086 RPM,
1087 /// RSA - Rudder Sensor Angle
1088 ///
1089 /// <https://gpsd.gitlab.io/gpsd/NMEA.html#_rsa_rudder_sensor_angle>
1090 ///
1091 /// Type: `Rudder`
1092 RSA,
1093 /// RSD - RADAR System Data
1094 ///
1095 /// <https://gpsd.gitlab.io/gpsd/NMEA.html#_rsd_radar_system_data>
1096 ///
1097 /// Type: `Radar`
1098 RSD,
1099 /// RTE - Routes
1100 ///
1101 /// <https://gpsd.gitlab.io/gpsd/NMEA.html#_rte_routes>
1102 ///
1103 /// Type: `Waypoints and tacks`
1104 RTE,
1105 /// SFI - Scanning Frequency Information
1106 ///
1107 /// <https://gpsd.gitlab.io/gpsd/NMEA.html#_sfi_scanning_frequency_information>
1108 ///
1109 /// Type: `Radio`
1110 SFI,
1111 SSD,
1112 /// STN - Multiple Data ID
1113 ///
1114 /// <https://gpsd.gitlab.io/gpsd/NMEA.html#_stn_multiple_data_id>
1115 STN,
1116 /// TDS - Trawl Door Spread Distance
1117 ///
1118 /// <https://gpsd.gitlab.io/gpsd/NMEA.html#_tds_trawl_door_spread_distance>
1119 TDS,
1120 /// TFI - Trawl Filling Indicator
1121 ///
1122 /// <https://gpsd.gitlab.io/gpsd/NMEA.html#_tfi_trawl_filling_indicator>
1123 TFI,
1124 /// TLB - Target Label
1125 ///
1126 /// <https://gpsd.gitlab.io/gpsd/NMEA.html#_tlb_target_label>
1127 TLB,
1128 /// TLL - Target Latitude and Longitude
1129 ///
1130 /// <https://gpsd.gitlab.io/gpsd/NMEA.html#_tll_target_latitude_and_longitude>
1131 /// Type: `Radio`
1132 TLL,
1133 /// TPC - Trawl Position Cartesian Coordinates
1134 ///
1135 /// <https://gpsd.gitlab.io/gpsd/NMEA.html#_tpc_trawl_position_cartesian_coordinates>
1136 TPC,
1137 /// TPR - Trawl Position Relative Vessel
1138 ///
1139 /// <https://gpsd.gitlab.io/gpsd/NMEA.html#_tpr_trawl_position_relative_vessel>
1140 TPR,
1141 /// TPT - Trawl Position True
1142 ///
1143 /// <https://gpsd.gitlab.io/gpsd/NMEA.html#_tpt_trawl_position_true>
1144 TPT,
1145 /// TRF - TRANSIT Fix Data
1146 ///
1147 /// <https://gpsd.gitlab.io/gpsd/NMEA.html#_trf_transit_fix_data>
1148 TRF,
1149 /// TTM - Tracked Target Message
1150 ///
1151 /// <https://gpsd.gitlab.io/gpsd/NMEA.html#_ttm_tracked_target_message>
1152 ///
1153 /// Type: `Radar`
1154 TTM,
1155 TUT,
1156 TXT,
1157 /// VBW - Dual Ground/Water Speed
1158 ///
1159 /// <https://gpsd.gitlab.io/gpsd/NMEA.html#_vbw_dual_groundwater_speed>
1160 ///
1161 /// Type: `Speed`
1162 VBW,
1163 VDM,
1164 VDO,
1165 /// VDR - Set and Drift
1166 ///
1167 /// <https://gpsd.gitlab.io/gpsd/NMEA.html#_vdr_set_and_drift>
1168 ///
1169 /// Type: `Course`
1170 VDR,
1171 /// VHW - Water speed and heading
1172 ///
1173 /// <https://gpsd.gitlab.io/gpsd/NMEA.html#_vhw_water_speed_and_heading>
1174 ///
1175 /// Type: `Speed`
1176 VHW,
1177 /// VLW - Distance Traveled through Water
1178 ///
1179 /// <https://gpsd.gitlab.io/gpsd/NMEA.html#_vlw_distance_traveled_through_water>
1180 ///
1181 /// Type: `Speed`
1182 VLW,
1183 /// VPW - Speed - Measured Parallel to Wind
1184 ///
1185 /// <https://gpsd.gitlab.io/gpsd/NMEA.html#_vpw_speed_measured_parallel_to_wind>
1186 ///
1187 /// Type: `Wind`
1188 VPW,
1189 VSD,
1190 /// VTG - Track made good and Ground speed
1191 ///
1192 /// <https://gpsd.gitlab.io/gpsd/NMEA.html#_vtg_track_made_good_and_ground_speed>
1193 ///
1194 /// Type: `Waypoints and tacks`
1195 VTG,
1196 /// VWR - Relative Wind Speed and Angle
1197 ///
1198 /// <https://gpsd.gitlab.io/gpsd/NMEA.html#_vwr_relative_wind_speed_and_angle>
1199 ///
1200 /// Type: `Wind`
1201 VWR,
1202 /// WCV - Waypoint Closure Velocity
1203 ///
1204 /// <https://gpsd.gitlab.io/gpsd/NMEA.html#_wcv_waypoint_closure_velocity>
1205 ///
1206 /// Type: `Waypoints and tacks`
1207 WCV,
1208 /// WNC - Distance - Waypoint to Waypoint
1209 ///
1210 /// <https://gpsd.gitlab.io/gpsd/NMEA.html#_wnc_distance_waypoint_to_waypoint>
1211 ///
1212 /// Type: `Waypoints and tacks`
1213 WNC,
1214 /// WPL - Waypoint Location
1215 ///
1216 /// <https://gpsd.gitlab.io/gpsd/NMEA.html#_wpl_waypoint_location>
1217 ///
1218 /// Type: `Waypoints and tacks`
1219 WPL,
1220 /// XDR - Transducer Measurement
1221 ///
1222 /// <https://gpsd.gitlab.io/gpsd/NMEA.html#_xdr_transducer_measurement>
1223 ///
1224 /// Type: `Waypoints and tacks`
1225 XDR,
1226 /// XTE - Cross-Track Error, Measured
1227 ///
1228 /// <https://gpsd.gitlab.io/gpsd/NMEA.html#_xte_cross_track_error_measured>
1229 ///
1230 /// Type: `Waypoints and tacks`
1231 XTE,
1232 /// XTR - Cross Track Error - Dead Reckoning
1233 ///
1234 /// <https://gpsd.gitlab.io/gpsd/NMEA.html#_xtr_cross_track_error_dead_reckoning>
1235 ///
1236 /// Type: `Waypoints and tacks`
1237 XTR,
1238 /// ZDA - Time & Date - UTC, day, month, year and local time zone
1239 ///
1240 /// <https://gpsd.gitlab.io/gpsd/NMEA.html#_zda_time_date_utc_day_month_year_and_local_time_zone>
1241 ///
1242 /// Type: `Date and Time`
1243 ZDA,
1244 ZDL,
1245 /// ZFO - UTC & Time from origin Waypoint
1246 ///
1247 /// <https://gpsd.gitlab.io/gpsd/NMEA.html#_zfo_utc_time_from_origin_waypoint>
1248 ///
1249 /// Type: `Date and Time`
1250 ZFO,
1251 /// ZTG - UTC & Time to Destination Waypoint
1252 ///
1253 /// <https://gpsd.gitlab.io/gpsd/NMEA.html#_ztg_utc_time_to_destination_waypoint>
1254 ///
1255 /// Type: `Date and Time`
1256 ZTG,
1257 }
1258}
1259
1260#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
1261#[cfg_attr(feature = "defmt-03", derive(defmt::Format))]
1262#[derive(Copy, Clone, PartialEq, Eq, Debug, Default)]
1263pub struct SentenceMask {
1264 mask: u128,
1265}
1266
1267impl SentenceMask {
1268 fn contains(&self, sentence_type: &SentenceType) -> bool {
1269 sentence_type.to_mask_value() & self.mask != 0
1270 }
1271
1272 fn is_subset(&self, mask: &Self) -> bool {
1273 (mask.mask | self.mask) == mask.mask
1274 }
1275
1276 fn insert(&mut self, sentence_type: SentenceType) {
1277 self.mask |= sentence_type.to_mask_value()
1278 }
1279}
1280
1281impl BitOr for SentenceType {
1282 type Output = SentenceMask;
1283 fn bitor(self, rhs: Self) -> Self::Output {
1284 SentenceMask {
1285 mask: self.to_mask_value() | rhs.to_mask_value(),
1286 }
1287 }
1288}
1289
1290impl BitOr<SentenceType> for SentenceMask {
1291 type Output = Self;
1292 fn bitor(self, rhs: SentenceType) -> Self {
1293 SentenceMask {
1294 mask: self.mask | rhs.to_mask_value(),
1295 }
1296 }
1297}
1298
1299#[cfg(test)]
1300mod tests {
1301 use core::convert::TryFrom;
1302
1303 use quickcheck::{QuickCheck, TestResult};
1304
1305 use crate::{parse::checksum, sentences::FixType, Error, Nmea, SentenceType};
1306
1307 #[cfg(feature = "GGA")]
1308 fn check_parsing_lat_lon_in_gga(lat: f64, lon: f64) -> TestResult {
1309 fn scale(val: f64, max: f64) -> f64 {
1310 val % max
1311 }
1312 if !lat.is_finite() || !lon.is_finite() {
1313 return TestResult::discard();
1314 }
1315 let lat = scale(lat, 90.0);
1316 let lon = scale(lon, 180.0);
1317 let lat_min = (lat.abs() * 60.0) % 60.0;
1318 let lon_min = (lon.abs() * 60.0) % 60.0;
1319 let mut nmea = Nmea::default();
1320 let mut s = format!(
1321 "$GPGGA,092750.000,{lat_deg:02}{lat_min:09.6},{lat_dir},\
1322 {lon_deg:03}{lon_min:09.6},{lon_dir},1,8,1.03,61.7,M,55.2,M,,*",
1323 lat_deg = lat.abs().floor() as u8,
1324 lon_deg = lon.abs().floor() as u8,
1325 lat_min = lat_min,
1326 lon_min = lon_min,
1327 lat_dir = if lat.is_sign_positive() { 'N' } else { 'S' },
1328 lon_dir = if lon.is_sign_positive() { 'E' } else { 'W' },
1329 );
1330 let cs = checksum(s.as_bytes()[1..s.len() - 1].iter());
1331 s.push_str(&format!("{:02X}", cs));
1332 nmea.parse(&s).unwrap();
1333
1334 let (new_lat, new_lon) = (nmea.latitude.unwrap(), nmea.longitude.unwrap());
1335 const MAX_COOR_DIFF: f64 = 1e-7;
1336 TestResult::from_bool(
1337 (new_lat - lat).abs() < MAX_COOR_DIFF && (new_lon - lon).abs() < MAX_COOR_DIFF,
1338 )
1339 }
1340
1341 #[test]
1342 fn test_fix_type() {
1343 assert_eq!(FixType::from('A'), FixType::Invalid);
1344 assert_eq!(FixType::from('0'), FixType::Invalid);
1345 assert_eq!(FixType::from('1'), FixType::Gps);
1346 assert_eq!(FixType::from('2'), FixType::DGps);
1347 assert_eq!(FixType::from('3'), FixType::Pps);
1348 assert_eq!(FixType::from('4'), FixType::Rtk);
1349 assert_eq!(FixType::from('5'), FixType::FloatRtk);
1350 assert_eq!(FixType::from('6'), FixType::Estimated);
1351 assert_eq!(FixType::from('7'), FixType::Manual);
1352 assert_eq!(FixType::from('8'), FixType::Simulation);
1353 }
1354
1355 #[test]
1356 fn test_checksum() {
1357 let valid = "$GNGSA,A,1,,,,,,,,,,,,,99.99,99.99,99.99*2E";
1358 let invalid = "$GNZDA,165118.00,13,05,2016,00,00*71";
1359 assert_eq!(checksum(valid[1..valid.len() - 3].as_bytes().iter()), 0x2E);
1360 assert_ne!(
1361 checksum(invalid[1..invalid.len() - 3].as_bytes().iter()),
1362 0x71
1363 );
1364 }
1365
1366 #[test]
1367 fn test_message_type() {
1368 assert_eq!(SentenceType::try_from("GGA"), Ok(SentenceType::GGA));
1369 let parse_err = SentenceType::try_from("XXX").expect_err("Should trigger parsing error");
1370
1371 assert_eq!(Error::Unknown("XXX"), parse_err);
1372 }
1373
1374 #[test]
1375 // FIXME: remove dependency on GGA and instead use quickcheck for `do_parse_lat_lon` parser
1376 #[cfg(feature = "GGA")]
1377 fn test_parsing_lat_lon_in_gga() {
1378 // regressions found by quickcheck,
1379 // explicit because of quickcheck use random gen
1380 assert!(!check_parsing_lat_lon_in_gga(0., 57.89528).is_failure());
1381 assert!(!check_parsing_lat_lon_in_gga(0., -43.33031).is_failure());
1382 QuickCheck::new()
1383 .tests(10_000_000_000)
1384 .quickcheck(check_parsing_lat_lon_in_gga as fn(f64, f64) -> TestResult);
1385 }
1386
1387 #[test]
1388 fn test_sentence_type_enum() {
1389 // So we don't trip over the max value of u128 when shifting it with
1390 // SentenceType as u32
1391 for sentence_type in SentenceType::TYPES {
1392 assert!((sentence_type as u32) < 127);
1393 }
1394 }
1395}