tiny_nmea/
lib.rs

1#![cfg_attr(not(test), no_std)]
2
3mod message;
4mod gll;
5mod common;
6mod gsv;
7
8use heapless::String;
9use crate::message::{NMEAMessage, Time};
10use heapless::Vec;
11use crate::message::NMEAMessage::{GLL, GSV};
12
13fn validate(sentence: &String<84>) -> Result<(), ()> {
14    let expected_checksum = substring!(sentence, sentence.len() - 4, 2);
15    let expected_checksum = u8::from_str_radix(expected_checksum.as_str(), 16).unwrap();
16    let mut checksum: u8 = 0;
17    sentence.bytes().skip(1).take(sentence.len() - 6).for_each(|byte| {
18        checksum ^= byte;
19    });
20    if checksum != expected_checksum {
21        return Err(());
22    }
23
24    Ok(())
25}
26
27pub fn parse(sentence: &String<84>) -> Result<NMEAMessage, ()> {
28    validate(&sentence)?;
29    let fields: Vec<&str, 41> = sentence.split(|c| c == '$' || c == ',' || c == '*').collect();
30    let message_type = &(fields[1].clone())[2..5];
31    match message_type {
32        "GLL" => gll::parse_gll(fields),
33        "GSV" => gsv::parse_gsv(fields),
34        _ => Err(()),
35    }
36}
37
38#[derive(Debug, Clone)]
39pub struct NMEA {
40    pub latitude: Option<f32>,
41    pub longitude: Option<f32>,
42    pub utc: Option<Time>,
43    pub satellites_visible: Option<u8>,
44}
45
46impl core::fmt::Display for NMEA {
47    fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
48        write!(f, "NMEA {{ latitude: {:?}, longitude: {:?}, utc: {:?}, satellites_visible: {:?} }}", self.latitude, self.longitude, self.utc, self.satellites_visible)
49    }
50}
51
52impl NMEA {
53    pub fn new() -> Self {
54        NMEA {
55            latitude: None,
56            longitude: None,
57            utc: None,
58            satellites_visible: None,
59        }
60    }
61
62    pub fn update(&mut self, sentence: &String<84>) -> Result<(), ()> {
63        match parse(sentence) {
64            Ok(GLL { latitude, longitude, utc, .. }) => {
65                self.latitude = Some(latitude);
66                self.longitude = Some(longitude);
67                self.utc = Some(utc);
68                Ok(())
69            }
70            Ok(GSV { satellites_visible, .. }) => {
71                self.satellites_visible = Some(satellites_visible);
72                Ok(())
73            }
74            _ => Err(())
75        }
76    }
77
78    pub fn has_fix(&self) -> bool {
79        self.latitude.is_some() && self.longitude.is_some()
80    }
81}
82
83
84#[cfg(test)]
85mod tests {
86    extern crate alloc;
87    use alloc::format;
88    use super::*;
89
90    #[test]
91    fn gll() {
92        let result = parse(&String::from("$GNGLL,4315.68533,N,07955.20234,W,080023.000,A,A*5D\r\n")).unwrap();
93        assert_eq!(format!("{:?}", result), "GLL { talker: \"GN\", latitude: 43.26142, longitude: -79.92004, utc: Time { hour: 8, minute: 0, second: 23, millisecond: 0 } }");
94    }
95
96    #[test]
97    fn gsv() {
98        let result = parse(&String::from("$GPGSV,2,2,07,23,62,115,24,24,42,057,20,32,52,272,21*4A\r\n")).unwrap();
99        assert_eq!(format!("{:?}", result), "GSV { talker: \"GP\", satellites_visible: 7 }");
100    }
101}