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}