From 32a2960d985f45b7bcf1e2cf5beab450700219db Mon Sep 17 00:00:00 2001 From: ekuinox Date: Mon, 13 Mar 2023 23:13:45 +0900 Subject: [PATCH 1/5] WIP: Add VHW sentence parser. --- src/sentences/mod.rs | 2 ++ src/sentences/vhw.rs | 46 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 48 insertions(+) create mode 100644 src/sentences/vhw.rs diff --git a/src/sentences/mod.rs b/src/sentences/mod.rs index 0c94cb9..4fea8af 100644 --- a/src/sentences/mod.rs +++ b/src/sentences/mod.rs @@ -18,6 +18,7 @@ mod rmc; mod rmz; mod txt; mod utils; +mod vhw; mod vtg; pub(crate) mod faa_mode; @@ -45,6 +46,7 @@ pub use { rmc::{parse_rmc, RmcData, RmcStatusOfFix}, rmz::{parse_pgrmz, PgrmzData}, txt::{parse_txt, TxtData}, + vhw::{parse_vhw, VhwData}, vtg::{parse_vtg, VtgData}, }; diff --git a/src/sentences/vhw.rs b/src/sentences/vhw.rs new file mode 100644 index 0000000..ee9976c --- /dev/null +++ b/src/sentences/vhw.rs @@ -0,0 +1,46 @@ +use crate::{Error, NmeaSentence}; + +/// VHW - Water speed and heading +/// +/// +/// +/// ```text +/// 1 2 3 4 5 6 7 8 9 +/// | | | | | | | | | +/// $--VHW,x.x,T,x.x,M,x.x,N,x.x,K*hh +/// ``` +/// 1. Heading degrees, True +/// 2. T = True +/// 3. Heading degrees, Magnetic +/// 4. M = Magnetic +/// 5. Speed of vessel relative to the water, knots +/// 6. N = Knots +/// 7. Speed of vessel relative to the water, km/hr +/// 8. K = Kilometers +/// 9. Checksum +/// +/// Note that this implementation follows the documentation published by `gpsd`, but the GLOBALSAT documentation may have conflicting definitions. +/// > [[GLOBALSAT](https://gpsd.gitlab.io/gpsd/NMEA.html#GLOBALSAT)] describes a different format in which the first three fields are water-temperature measurements. +/// > It’s not clear which is correct. +#[derive(Clone, PartialEq, Debug)] +pub struct VhwData { + /// Heading degrees, True + pub heading_true: Option, + /// Heading degrees, Magnetic + pub heading_magnetic: Option, + /// Speed of vessel relative to the water, knots + pub relative_speed_knots: Option, + /// Speed of vessel relative to the water, km/hr + pub relative_speed_kmph: Option, +} + +/// # Parse VHW message +/// +/// ```text +/// ``` +pub fn parse_vhw(sentence: NmeaSentence) -> Result { + todo!() +} + +#[cfg(test)] +mod tests {} From 7d0b9a4a5998ad11ed5436cb6840a71fc086e76e Mon Sep 17 00:00:00 2001 From: ekuinox Date: Tue, 14 Mar 2023 00:00:52 +0900 Subject: [PATCH 2/5] Add VHW patterns. --- src/parse.rs | 3 +++ src/parser.rs | 3 ++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/src/parse.rs b/src/parse.rs index 293eace..6fb5c52 100644 --- a/src/parse.rs +++ b/src/parse.rs @@ -113,6 +113,7 @@ pub enum ParseResult { MWV(MwvData), RMC(RmcData), TXT(TxtData), + VHW(VhwData), VTG(VtgData), PGRMZ(PgrmzData), /// A message that is not supported by the crate and cannot be parsed. @@ -138,6 +139,7 @@ impl From<&ParseResult> for SentenceType { ParseResult::MWV(_) => SentenceType::MWV, ParseResult::RMC(_) => SentenceType::RMC, ParseResult::TXT(_) => SentenceType::TXT, + ParseResult::VHW(_) => SentenceType::VHW, ParseResult::VTG(_) => SentenceType::VTG, ParseResult::PGRMZ(_) => SentenceType::RMZ, ParseResult::Unsupported(sentence_type) => *sentence_type, @@ -184,6 +186,7 @@ pub fn parse_str(sentence_input: &str) -> Result { SentenceType::RMC => parse_rmc(nmea_sentence).map(ParseResult::RMC), SentenceType::GSA => parse_gsa(nmea_sentence).map(ParseResult::GSA), SentenceType::VTG => parse_vtg(nmea_sentence).map(ParseResult::VTG), + SentenceType::VHW => parse_vhw(nmea_sentence).map(ParseResult::VHW), SentenceType::GLL => parse_gll(nmea_sentence).map(ParseResult::GLL), SentenceType::TXT => parse_txt(nmea_sentence).map(ParseResult::TXT), SentenceType::GNS => parse_gns(nmea_sentence).map(ParseResult::GNS), diff --git a/src/parser.rs b/src/parser.rs index fd0ffe1..4a7ed9c 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -361,7 +361,8 @@ impl<'a> Nmea { | ParseResult::PGRMZ(_) | ParseResult::MTW(_) | ParseResult::MWV(_) - | ParseResult::MDA(_) => return Ok(FixType::Invalid), + | ParseResult::MDA(_) + | ParseResult::VHW(_) => return Ok(FixType::Invalid), ParseResult::Unsupported(_) => { return Ok(FixType::Invalid); From 87b4de8c6d77def84bc4405e8291517623cd99c2 Mon Sep 17 00:00:00 2001 From: ekuinox Date: Tue, 14 Mar 2023 00:01:11 +0900 Subject: [PATCH 3/5] WIP: impl VHW parser --- src/sentences/vhw.rs | 139 ++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 136 insertions(+), 3 deletions(-) diff --git a/src/sentences/vhw.rs b/src/sentences/vhw.rs index ee9976c..d1848d0 100644 --- a/src/sentences/vhw.rs +++ b/src/sentences/vhw.rs @@ -1,4 +1,13 @@ -use crate::{Error, NmeaSentence}; +use nom::{ + bytes::complete::take_until, + character::streaming::char, + combinator::{map_res, opt}, + IResult, +}; + +use crate::{Error, NmeaSentence, SentenceType}; + +use super::utils::parse_float_num; /// VHW - Water speed and heading /// @@ -39,8 +48,132 @@ pub struct VhwData { /// ```text /// ``` pub fn parse_vhw(sentence: NmeaSentence) -> Result { - todo!() + if sentence.message_id == SentenceType::VHW { + Ok(do_parse_vhw(sentence.data)?.1) + } else { + Err(Error::WrongSentenceHeader { + expected: SentenceType::VHW, + found: sentence.message_id, + }) + } +} + +fn do_parse_vhw(i: &str) -> IResult<&str, VhwData> { + let comma = char(','); + + let (i, heading_true) = opt(map_res(take_until(","), parse_float_num))(i)?; + let (i, _) = comma(i)?; + let (i, _) = char('T')(i)?; + let (i, _) = comma(i)?; + + let (i, heading_magnetic) = opt(map_res(take_until(","), parse_float_num))(i)?; + let (i, _) = comma(i)?; + let (i, _) = char('M')(i)?; + let (i, _) = comma(i)?; + + let (i, relative_speed_knots) = opt(map_res(take_until(","), parse_float_num))(i)?; + let (i, _) = comma(i)?; + let (i, _) = char('N')(i)?; + let (i, _) = comma(i)?; + + let (i, relative_speed_kmph) = opt(map_res(take_until(","), parse_float_num))(i)?; + let (i, _) = comma(i)?; + let (i, _) = char('K')(i)?; + + Ok(( + i, + VhwData { + heading_true, + heading_magnetic, + relative_speed_knots, + relative_speed_kmph, + }, + )) } #[cfg(test)] -mod tests {} +mod tests { + use approx::assert_relative_eq; + + use super::*; + use crate::parse::parse_nmea_sentence; + + #[test] + fn test_parse_vhw() { + let s = NmeaSentence { + message_id: SentenceType::VHW, + talker_id: "GP", + data: "100.5,T,105.5,M,10.5,N,19.4,K", + checksum: 0x4f, + }; + let vhw_data = parse_vhw(s).unwrap(); + assert_relative_eq!(vhw_data.heading_true.unwrap(), 100.5); + assert_relative_eq!(vhw_data.heading_magnetic.unwrap(), 105.5); + assert_relative_eq!(vhw_data.relative_speed_knots.unwrap(), 10.5); + assert_relative_eq!(vhw_data.relative_speed_kmph.unwrap(), 19.4); + + let s = parse_nmea_sentence("$GPVHW,100.5,T,105.5,M,10.5,N,19.4,K*4F").unwrap(); + assert_eq!(s.checksum, s.calc_checksum()); + assert_eq!(s.checksum, 0x4F); + + let vhw_data = parse_vhw(s).unwrap(); + assert_relative_eq!(vhw_data.heading_true.unwrap(), 100.5); + assert_relative_eq!(vhw_data.heading_magnetic.unwrap(), 105.5); + assert_relative_eq!(vhw_data.relative_speed_knots.unwrap(), 10.5); + assert_relative_eq!(vhw_data.relative_speed_kmph.unwrap(), 19.4); + } + + #[test] + fn test_parse_incomplete_vhw() { + // Pattern with all single letter alphabetical fields filled, but all numeric fields blank. + let s = NmeaSentence { + message_id: SentenceType::VHW, + talker_id: "GP", + data: ",T,,M,,N,,K", + checksum: 0, + }; + assert_eq!( + parse_vhw(s), + Ok(VhwData { + heading_true: None, + heading_magnetic: None, + relative_speed_knots: None, + relative_speed_kmph: None, + }) + ); + + // Pattern with all single letter alphabetical fields filled and some numerical fields filled. + let s = NmeaSentence { + message_id: SentenceType::VHW, + talker_id: "GP", + data: ",T,,M,10.5,N,20.0,K", + checksum: 0, + }; + assert_eq!( + parse_vhw(s), + Ok(VhwData { + heading_true: None, + heading_magnetic: None, + relative_speed_knots: Some(10.5), + relative_speed_kmph: Some(20.0), + }) + ); + + // Pattern with all fields missing + let s = NmeaSentence { + message_id: SentenceType::VHW, + talker_id: "GP", + data: ",,,,,,,", + checksum: 0, + }; + assert_eq!( + parse_vhw(s), + Ok(VhwData { + heading_true: None, + heading_magnetic: None, + relative_speed_knots: None, + relative_speed_kmph: None + }) + ); + } +} From 363e4cbeb32db2484ad20a426fc9006dec5329c8 Mon Sep 17 00:00:00 2001 From: ekuinox Date: Wed, 15 Mar 2023 00:02:37 +0900 Subject: [PATCH 4/5] Add VHW sentence parser --- README.md | 1 + src/lib.rs | 1 + src/sentences/vhw.rs | 47 ++++++++++++++++++++++++--------- tests/all_supported_messages.rs | 2 ++ 4 files changed, 38 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index 29c38da..aa480e8 100644 --- a/README.md +++ b/README.md @@ -26,6 +26,7 @@ NMEA Standard Sentences - MTW - MWV - RMC * +- VHW - VTG * Other Sentences diff --git a/src/lib.rs b/src/lib.rs index edef100..e8c36a6 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -23,6 +23,7 @@ //! - MDA //! - MWV //! - RMC * +//! - VHW //! - VTG * //! //! Other Sentences diff --git a/src/sentences/vhw.rs b/src/sentences/vhw.rs index d1848d0..d514a0d 100644 --- a/src/sentences/vhw.rs +++ b/src/sentences/vhw.rs @@ -1,6 +1,6 @@ use nom::{ bytes::complete::take_until, - character::streaming::char, + character::complete::char, combinator::{map_res, opt}, IResult, }; @@ -46,7 +46,20 @@ pub struct VhwData { /// # Parse VHW message /// /// ```text +/// $GPVHW,100.5,T,105.5,M,10.5,N,19.4,K*4F /// ``` +/// 1. 100.5 Heading True +/// 2. T +/// 3. 105.5 Heading Magnetic +/// 4. M +/// 5. 10.5 Speed relative to water, knots +/// 6. N +/// 7. 19.4 Speed relative to water, km/hr +/// 8. K +/// +/// Each is considered as a pair of a float value and a single character, +/// and if the float value exists but the single character is not correct, it is treated as `None`. +/// For example, if 1 is "100.5" and 2 is not "T", then heading_true is `None`. pub fn parse_vhw(sentence: NmeaSentence) -> Result { if sentence.message_id == SentenceType::VHW { Ok(do_parse_vhw(sentence.data)?.1) @@ -58,27 +71,28 @@ pub fn parse_vhw(sentence: NmeaSentence) -> Result { } } +/// Parses a float value +/// and returns `None` if the float value can be parsed but the next field does not match the specified character. +fn do_parse_float_with_char(c: char, i: &str) -> IResult<&str, Option> { + let (i, value) = opt(map_res(take_until(","), parse_float_num::))(i)?; + let (i, _) = char(',')(i)?; + let (i, tag) = opt(char(c))(i)?; + Ok((i, tag.and(value))) +} + fn do_parse_vhw(i: &str) -> IResult<&str, VhwData> { let comma = char(','); - let (i, heading_true) = opt(map_res(take_until(","), parse_float_num))(i)?; - let (i, _) = comma(i)?; - let (i, _) = char('T')(i)?; + let (i, heading_true) = do_parse_float_with_char('T', i)?; let (i, _) = comma(i)?; - let (i, heading_magnetic) = opt(map_res(take_until(","), parse_float_num))(i)?; - let (i, _) = comma(i)?; - let (i, _) = char('M')(i)?; + let (i, heading_magnetic) = do_parse_float_with_char('M', i)?; let (i, _) = comma(i)?; - let (i, relative_speed_knots) = opt(map_res(take_until(","), parse_float_num))(i)?; - let (i, _) = comma(i)?; - let (i, _) = char('N')(i)?; + let (i, relative_speed_knots) = do_parse_float_with_char('N', i)?; let (i, _) = comma(i)?; - let (i, relative_speed_kmph) = opt(map_res(take_until(","), parse_float_num))(i)?; - let (i, _) = comma(i)?; - let (i, _) = char('K')(i)?; + let (i, relative_speed_kmph) = do_parse_float_with_char('K', i)?; Ok(( i, @@ -98,6 +112,13 @@ mod tests { use super::*; use crate::parse::parse_nmea_sentence; + #[test] + fn test_do_parse_float_with_char() { + assert_eq!(do_parse_float_with_char('T', "1.5,T"), Ok(("", Some(1.5)))); + assert_eq!(do_parse_float_with_char('T', "1.5,"), Ok(("", None))); + assert_eq!(do_parse_float_with_char('T', ","), Ok(("", None))); + } + #[test] fn test_parse_vhw() { let s = NmeaSentence { diff --git a/tests/all_supported_messages.rs b/tests/all_supported_messages.rs index b665cc9..5d420cf 100644 --- a/tests/all_supported_messages.rs +++ b/tests/all_supported_messages.rs @@ -33,6 +33,8 @@ fn test_all_supported_messages() { (SentenceType::RMZ, "$PGRMZ,2282,f,3*21"), // TXT (SentenceType::TXT, "$GNTXT,01,01,02,u-blox AG - www.u-blox.com*4E"), + // VHW + (SentenceType::VHW, "$GPVHW,100.5,T,105.5,M,10.5,N,19.4,K*4F"), // VTG (SentenceType::VTG, "$GPVTG,360.0,T,348.7,M,000.0,N,000.0,K*43"), ] From 9ea462f0ab9d14672f1003a8be3b34fb23422cc7 Mon Sep 17 00:00:00 2001 From: ekuinox Date: Wed, 15 Mar 2023 20:35:47 +0900 Subject: [PATCH 5/5] Add test --- src/sentences/vhw.rs | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/src/sentences/vhw.rs b/src/sentences/vhw.rs index d514a0d..c0232aa 100644 --- a/src/sentences/vhw.rs +++ b/src/sentences/vhw.rs @@ -119,6 +119,23 @@ mod tests { assert_eq!(do_parse_float_with_char('T', ","), Ok(("", None))); } + #[test] + fn test_wrong_sentence() { + let invalid_aam_sentence = NmeaSentence { + message_id: SentenceType::AAM, + data: "", + talker_id: "GP", + checksum: 0, + }; + assert_eq!( + Err(Error::WrongSentenceHeader { + expected: SentenceType::VHW, + found: SentenceType::AAM + }), + parse_vhw(invalid_aam_sentence) + ); + } + #[test] fn test_parse_vhw() { let s = NmeaSentence {