diff --git a/Cargo.toml b/Cargo.toml index b1e174d..2f3e670 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,11 +12,15 @@ categories = ["embedded", "no-std", "parser-implementations"] readme = "README.md" [dependencies] +defmt = { version = "1", optional = true } +heapless = { version = "0.9", optional = true } [features] default = ["strict"] mtk = [] +txt = ["dep:heapless"] strict = [] +defmt = ["dep:defmt"] [badges] codecov = { repository = "nsforth/nmea0183" } diff --git a/src/coords.rs b/src/coords.rs index 9dd9dda..b6adb29 100644 --- a/src/coords.rs +++ b/src/coords.rs @@ -2,7 +2,8 @@ use core::convert::TryFrom; /// Earth hemisphere -#[derive(Debug, PartialEq, Clone)] +#[derive(Debug, PartialEq, Eq, Clone, Copy)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] pub enum Hemisphere { /// North North, @@ -15,7 +16,8 @@ pub enum Hemisphere { } /// Latitude as reported by receiver. -#[derive(Debug, PartialEq, Clone)] +#[derive(Debug, PartialEq, Clone, Copy)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] pub struct Latitude { /// Degrees pub degrees: u8, @@ -121,7 +123,8 @@ impl Latitude { } /// Longitude as reported by receiver. -#[derive(Debug, PartialEq, Clone)] +#[derive(Debug, PartialEq, Clone, Copy)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] pub struct Longitude { /// Degrees pub degrees: u8, @@ -227,7 +230,8 @@ impl Longitude { } /// Altitude reported by receiver typically in GGA sentence. -#[derive(Debug, PartialEq, Clone)] +#[derive(Debug, PartialEq, Clone, Copy)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] pub struct Altitude { /// Altitude in meters over ground. pub meters: f32, @@ -248,7 +252,8 @@ impl Altitude { } /// Speed reported by receiver typically in RMC and VTG sentences. -#[derive(Debug, PartialEq, Clone)] +#[derive(Debug, PartialEq, Clone, Copy)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] pub struct Speed { knots: f32, } @@ -305,7 +310,8 @@ impl Speed { } /// The course over ground. -#[derive(Debug, PartialEq, Clone)] +#[derive(Debug, PartialEq, Clone, Copy)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] pub struct Course { /// Course in degrees from North rotated clockwise. pub degrees: f32, @@ -331,10 +337,11 @@ impl Course { } /// The course over ground calculated from True course and magnetic variation. -#[derive(Debug, PartialEq, Clone)] +#[derive(Debug, PartialEq, Clone, Copy)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] pub struct MagneticCourse { /// Course in degrees from Magnetic North Pole rotated clockwise. - degrees: f32, + pub degrees: f32, } impl From for MagneticCourse { diff --git a/src/datetime.rs b/src/datetime.rs index fd1afd0..eacc7f2 100644 --- a/src/datetime.rs +++ b/src/datetime.rs @@ -1,6 +1,7 @@ //! NMEA date and time structures. /// NMEA date -#[derive(Debug, PartialEq, Clone)] +#[derive(Debug, PartialEq, Eq, Clone, Copy)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] pub struct Date { /// NMEA day pub day: u8, @@ -47,7 +48,8 @@ impl Date { } /// NMEA time in UTC -#[derive(Debug, PartialEq, Clone)] +#[derive(Debug, PartialEq, Clone, Copy)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] pub struct Time { /// Hours as reported by receiver pub hours: u8, @@ -100,7 +102,8 @@ impl Time { } /// NMEA date and time in UTC -#[derive(Debug, PartialEq, Clone)] +#[derive(Debug, PartialEq, Clone, Copy)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] pub struct DateTime { /// NMEA date pub date: Date, diff --git a/src/gga.rs b/src/gga.rs index 9e63c98..f304312 100644 --- a/src/gga.rs +++ b/src/gga.rs @@ -5,7 +5,8 @@ use crate::Source; use core::time::Duration; /// Geographic coordinates including altitude, GPS solution quality, DGPS usage information. -#[derive(Debug, PartialEq, Clone)] +#[derive(Debug, PartialEq, Clone, Copy)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] pub struct GGA { /// Navigational system. pub source: Source, @@ -78,7 +79,8 @@ impl GGA { } /// Quality of GPS solution -#[derive(Debug, PartialEq, Clone)] +#[derive(Debug, PartialEq, Eq, Clone, Copy)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] pub enum GPSQuality { /// No solution NoFix, diff --git a/src/gll.rs b/src/gll.rs index fd8bfe7..baf13ae 100644 --- a/src/gll.rs +++ b/src/gll.rs @@ -4,7 +4,8 @@ use crate::modes::{Mode, Status}; use crate::Source; /// Geographic latitude ang longitude sentence with time of fix and receiver state. -#[derive(Debug, PartialEq, Clone)] +#[derive(Debug, PartialEq, Clone, Copy)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] pub struct GLL { /// Navigational system. pub source: Source, diff --git a/src/gsa.rs b/src/gsa.rs index 2b4ed4a..05fae99 100644 --- a/src/gsa.rs +++ b/src/gsa.rs @@ -4,7 +4,8 @@ use crate::Source; const MAX_PRNS_PER_MESSAGE: usize = 12; /// GPS DOP and active satellites -#[derive(Debug, PartialEq, Clone)] +#[derive(Debug, PartialEq, Clone, Copy)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] pub struct GSA { /// Navigational system. pub source: Source, @@ -65,7 +66,8 @@ impl GSA { } /// Receiver mode of positioning. -#[derive(Debug, PartialEq, Clone)] +#[derive(Debug, PartialEq, Eq, Clone, Copy)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] pub enum FixType { /// No valid position is available. NoFix, diff --git a/src/gsv.rs b/src/gsv.rs index 5466a5f..c1f8c5d 100644 --- a/src/gsv.rs +++ b/src/gsv.rs @@ -3,7 +3,8 @@ use crate::satellite::Satellite; use crate::Source; const MAX_SATELLITES_PER_MESSAGE: usize = 4; /// Satellites in views including the number of SVs in view, the PRN numbers, elevations, azimuths, and SNR values. -#[derive(Debug, PartialEq, Clone)] +#[derive(Debug, PartialEq, Clone, Copy)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] pub struct GSV { /// Navigational system. pub source: Source, diff --git a/src/lib.rs b/src/lib.rs index 130a079..d87b332 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -96,6 +96,8 @@ pub(crate) mod mtk; pub(crate) mod rmc; pub(crate) mod vtg; pub(crate) mod zda; +#[cfg(feature = "txt")] +pub(crate) mod txt; pub use gga::GPSQuality; pub use gga::GGA; @@ -113,9 +115,12 @@ pub use mtk::PMTKSPF; pub use rmc::RMC; pub use vtg::VTG; pub use zda::ZDA; +#[cfg(feature = "txt")] +pub use txt::{TXT, MessageType}; /// Source of NMEA sentence like GPS, GLONASS or other. -#[derive(Debug, Copy, Clone, PartialEq)] +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] pub enum Source { /// USA Global Positioning System GPS = 0b1, @@ -127,12 +132,16 @@ pub enum Source { Beidou = 0b1000, /// Global Navigation Sattelite System. Some combination of other systems. Depends on receiver model, receiver settings, etc.. GNSS = 0b10000, + /// Quasi-Zenith Satellite System + QZSS = 0b100000, #[cfg(feature = "mtk")] /// MediaTek NMEA packet protocol - MTK = 0b100000, + MTK = 0b1000000, } /// Mask for Source filter in Parser. +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] pub struct SourceMask { mask: u32, } @@ -179,15 +188,21 @@ impl TryFrom<&str> for Source { "GA" => Ok(Source::Gallileo), "BD" => Ok(Source::Beidou), "GN" => Ok(Source::GNSS), + "GQ" => Ok(Source::QZSS), #[cfg(feature = "mtk")] "PM" => Ok(Source::MTK), - _ => Err("Source is not supported!"), + source => { + #[cfg(feature = "defmt")] + defmt::error!("Source is not supported: {}", source); + Err("Source is not supported!") + } } } } /// Various kinds of NMEA sentence like RMC, VTG or other. Used for filter by sentence type in Parser. -#[derive(Debug, Copy, Clone)] +#[derive(Debug, Eq, PartialEq, Copy, Clone)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] pub enum Sentence { /// Recommended minimum sentence. RMC = 0b1, @@ -206,6 +221,9 @@ pub enum Sentence { GSA = 0b1000000, /// Current Date and Time. ZDA = 0b10000000, + #[cfg(feature = "txt")] + /// Text message from receiver. + TXT = 0b100000000, } impl TryFrom<&str> for Sentence { @@ -222,13 +240,21 @@ impl TryFrom<&str> for Sentence { "PMTK" => Ok(Sentence::PMTK), "GSA" => Ok(Sentence::GSA), "ZDA" => Ok(Sentence::ZDA), + #[cfg(feature = "txt")] + "TXT" => Ok(Sentence::TXT), - _ => Err("Unsupported sentence type."), + sentence => { + #[cfg(feature = "defmt")] + defmt::error!("Unsupported NMEA sentence type: {}", sentence); + Err("Unsupported sentence type.") + } } } } /// Mask for Sentence filter in Parser. +#[derive(Debug, Eq, PartialEq, Copy, Clone)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] pub struct SentenceMask { mask: u32, } @@ -269,6 +295,7 @@ impl BitOr for SentenceMask { /// Sentences with many null fields or sentences without valid data is also parsed and returned as None. /// None ParseResult may be interpreted as working receiver but without valid data. #[derive(Debug, PartialEq, Clone)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] pub enum ParseResult { /// The Recommended Minimum Sentence for any GNSS. Typically most used. RMC(Option), @@ -287,6 +314,9 @@ pub enum ParseResult { GSA(Option), /// Current Date and Time. ZDA(Option), + #[cfg(feature = "txt")] + /// Text message from receiver. + TXT(Option), } #[cfg(feature = "strict")] @@ -299,6 +329,8 @@ pub const MAX_SENTENCE_LENGTH: usize = 120usize; /// Parses NMEA sentences and stores intermediate parsing state. /// Parser is tolerant for errors so you should not reinitialize it after errors. +#[derive(Debug, Copy, Clone)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] pub struct Parser { buffer: [u8; MAX_SENTENCE_LENGTH], buflen: usize, @@ -309,7 +341,8 @@ pub struct Parser { sentence_mask: SentenceMask, } -#[derive(Debug)] +#[derive(Debug, PartialEq, Eq, Copy, Clone)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] enum ParserState { WaitStart, ReadUntilChkSum, @@ -319,6 +352,8 @@ enum ParserState { WaitLF, } +#[derive(Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] struct ParserIterator<'a> { parser: &'a mut Parser, input: Iter<'a, u8>, @@ -472,6 +507,8 @@ impl Parser { Sentence::GSV => Ok(Some(ParseResult::GSV(GSV::parse(source, &mut iter)?))), Sentence::GSA => Ok(Some(ParseResult::GSA(GSA::parse(source, &mut iter)?))), Sentence::ZDA => Ok(Some(ParseResult::ZDA(ZDA::parse(source, &mut iter)?))), + #[cfg(feature = "txt")] + Sentence::TXT => Ok(Some(ParseResult::TXT(TXT::parse(source, &mut iter)?))), #[cfg(feature = "mtk")] Sentence::PMTK => { diff --git a/src/modes.rs b/src/modes.rs index ad19bf1..d7f4532 100644 --- a/src/modes.rs +++ b/src/modes.rs @@ -1,4 +1,5 @@ -#[derive(Debug, PartialEq)] +#[derive(Debug, PartialEq, Eq, Copy, Clone)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] pub(crate) enum Status { Valid, NotValid, @@ -15,7 +16,8 @@ impl Status { } /// Receiver mode of operation. -#[derive(Debug, PartialEq, Clone)] +#[derive(Debug, PartialEq, Eq, Copy, Clone)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] pub enum Mode { /// Autonomous mode without any external correction. Autonomous, diff --git a/src/mtk.rs b/src/mtk.rs index acb41a5..179ef2a 100644 --- a/src/mtk.rs +++ b/src/mtk.rs @@ -2,7 +2,8 @@ use crate::Source; use core::convert::TryFrom; /// MTK NMEA packet type -#[derive(Debug, PartialEq, Clone)] +#[derive(Debug, PartialEq, Eq, Copy, Clone)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] pub enum MTKPacketType { /// Related to Spoofing and Jamming detection SPF, @@ -20,7 +21,8 @@ impl TryFrom<&str> for MTKPacketType { } /// Related to Spoofing and Jamming detection . -#[derive(Debug, PartialEq, Clone)] +#[derive(Debug, PartialEq, Copy, Clone)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] pub struct PMTKSPF { /// Navigational system. pub source: Source, @@ -46,7 +48,8 @@ impl PMTKSPF { } /// Status of gps Jamming -#[derive(Debug, PartialEq, Clone,Copy)] +#[derive(Debug, PartialEq, Eq, Copy, Clone)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] pub enum JammingStatus { /// No Jamming Healthy, diff --git a/src/rmc.rs b/src/rmc.rs index 760ed47..f141752 100644 --- a/src/rmc.rs +++ b/src/rmc.rs @@ -4,7 +4,8 @@ use crate::modes::{Mode, Status}; use crate::Source; /// Recommended Minimum Sentence for any GNSS source. -#[derive(Debug, PartialEq, Clone)] +#[derive(Debug, PartialEq, Copy, Clone)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] pub struct RMC { /// Navigational system. pub source: Source, diff --git a/src/satellite.rs b/src/satellite.rs index a13e490..40ddfc0 100644 --- a/src/satellite.rs +++ b/src/satellite.rs @@ -3,7 +3,8 @@ use crate::common; ///Information about satellite in view. -#[derive(Debug, PartialEq, Clone, Default)] +#[derive(Debug, PartialEq, Eq, Clone, Copy, Default)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] pub struct Satellite { /// Satellite pseudo-random noise number. pub prn: u16, diff --git a/src/txt.rs b/src/txt.rs new file mode 100644 index 0000000..7b974df --- /dev/null +++ b/src/txt.rs @@ -0,0 +1,75 @@ +use heapless::String; + +use crate::{common, Source}; + +/// Geographic latitude ang longitude sentence with time of fix and receiver state. +#[derive(Debug, PartialEq, Clone)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct TXT { + /// Navigational system. + pub source: Source, + /// Total number of sentences + pub sentences: u8, + /// Sentence number + pub number: u8, + /// Message type + pub type_: MessageType, + /// The message content + pub text: String<69>, +} + +impl TXT { + pub(crate) fn parse<'a>( + source: Source, + fields: &mut core::str::Split<'a, char>, + ) -> Result, &'static str> { + let mut fields = fields.peekable(); + let sentences = common::parse_u8(fields.next())?; + let number = common::parse_u8(fields.next())?; + let type_ = MessageType::parse(fields.next())?; + let mut text = String::<69>::new(); + + while let Some(part) = fields.next() { + text.push_str(part).map_err(|_| "exceeded string size")?; + + if fields.peek().is_some() { + text.push_str(",").map_err(|_| "exceeded string size")?; + } + } + + if let (Some(sentences), Some(number), Some(type_)) = (sentences, number, type_) { + Ok(Some(TXT { source, sentences, number, type_, text })) + } + else { + Ok(None) + } + } +} + +/// The type of message +#[derive(Debug, PartialEq, Eq, Clone, Copy)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum MessageType { + /// An error message + Error, + /// A warning message + Warning, + /// An informational message or notice + Notice, + /// An application specific message + User, +} + +impl MessageType { + pub(crate) fn parse(input: Option<&str>) -> Result, &'static str> { + match input { + Some("00") => Ok(Some(MessageType::Error)), + Some("01") => Ok(Some(MessageType::Warning)), + Some("02") => Ok(Some(MessageType::Notice)), + Some("07") => Ok(Some(MessageType::User)), + Some("") => Ok(None), + None => Ok(None), + _ => Err("Unsupported message kind!"), + } + } +} diff --git a/src/vtg.rs b/src/vtg.rs index 2ee09ef..7675963 100644 --- a/src/vtg.rs +++ b/src/vtg.rs @@ -3,7 +3,8 @@ use crate::modes::Mode; use crate::Source; /// The actual course and speed relative to the ground. -#[derive(Debug, PartialEq, Clone)] +#[derive(Debug, PartialEq, Clone, Copy)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] pub struct VTG { /// Navigational system. pub source: Source, diff --git a/src/zda.rs b/src/zda.rs index bac0ea0..514b825 100644 --- a/src/zda.rs +++ b/src/zda.rs @@ -2,7 +2,8 @@ use crate::datetime::{Date, DateTime, Time}; use crate::{common, Source}; /// Geographic latitude ang longitude sentence with time of fix and receiver state. -#[derive(Debug, PartialEq, Clone)] +#[derive(Debug, PartialEq, Copy, Clone)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] pub struct ZDA { /// Navigational system. pub source: Source,