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}