diff --git a/src/parse.rs b/src/parse.rs index 4cbf0dc..3f5f1bd 100644 --- a/src/parse.rs +++ b/src/parse.rs @@ -97,6 +97,7 @@ pub fn parse_nmea_sentence(sentence: &str) -> core::result::Result Nmea { ParseResult::BWC(_) | ParseResult::BOD(_) | ParseResult::GBS(_) + | ParseResult::AAM(_) | ParseResult::PGRMZ(_) => return Ok(FixType::Invalid), ParseResult::Unsupported(_) => { diff --git a/src/sentences/aam.rs b/src/sentences/aam.rs new file mode 100644 index 0000000..b437a05 --- /dev/null +++ b/src/sentences/aam.rs @@ -0,0 +1,177 @@ +use arrayvec::ArrayString; +use nom::{ + bytes::complete::is_not, + character::complete::{char, one_of}, + combinator::opt, + number::complete::float, +}; + +use crate::{parse::NmeaSentence, sentences::utils::array_string, Error, SentenceType}; + +const MAX_LEN: usize = 64; + +/// AAM - Waypoint Arrival Alarm +/// +/// +/// +/// ```text +/// 1 2 3 4 5 6 +/// | | | | | | +/// $--AAM,A,A,x.x,N,c--c*hh +/// +/// Field Number: +/// 1. Status, BOOLEAN, A = Arrival circle entered, V = not passed +/// 2. Status, BOOLEAN, A = perpendicular passed at waypoint, V = not passed +/// 3. Arrival circle radius +/// 4. Units of radiuos, nautical miles +/// 5. Waypoint ID +/// 6. Checksum +/// +/// Example: $GPAAM,A,A,0.10,N,WPTNME*43 +/// WPTNME is the waypoint name. +/// ``` +#[derive(Debug, PartialEq)] +pub struct AamData { + pub arrival_circle_entered: Option, + pub perpendicular_passed: Option, + pub arrival_circle_radius: Option, + pub radius_units: Option, + pub waypoint_id: Option>, +} + +/// Parse AAM message +pub fn parse_aam(sentence: NmeaSentence) -> Result { + if sentence.message_id != SentenceType::AAM { + Err(Error::WrongSentenceHeader { + expected: SentenceType::AAM, + found: sentence.message_id, + }) + } else { + Ok(do_parse_aam(sentence.data)?) + } +} + +fn do_parse_aam(i: &str) -> Result { + let (i, arrival_circle_entered) = one_of("AV")(i)?; + let arrival_circle_entered = match arrival_circle_entered { + 'A' => Some(true), + 'V' => Some(false), + _ => unreachable!(), + }; + let (i, _) = char(',')(i)?; + + let (i, perpendicular_passed) = one_of("AV")(i)?; + let perpendicular_passed = match perpendicular_passed { + 'A' => Some(true), + 'V' => Some(false), + _ => unreachable!(), + }; + let (i, _) = char(',')(i)?; + + let (i, arrival_circle_radius) = opt(float)(i)?; + let (i, _) = char(',')(i)?; + + let (i, radius_units) = opt(char('N'))(i)?; + let (i, _) = char(',')(i)?; + + let (_i, waypoint_id) = opt(is_not("*"))(i)?; + + Ok(AamData { + arrival_circle_entered, + perpendicular_passed, + arrival_circle_radius, + radius_units, + waypoint_id: waypoint_id.map(array_string::).transpose()?, + }) +} + +#[cfg(test)] +mod tests { + use approx::assert_relative_eq; + + use super::*; + use crate::{parse::parse_nmea_sentence, SentenceType}; + + #[test] + fn parse_aam_with_nmea_sentence_struct() { + let data = parse_aam(NmeaSentence { + talker_id: "GP", + message_id: SentenceType::AAM, + data: "A,V,0.10,N,WPTNME", + checksum: 0x0, + }) + .unwrap(); + + assert!(data.arrival_circle_entered.unwrap()); + assert!(!data.perpendicular_passed.unwrap()); + assert_relative_eq!(data.arrival_circle_radius.unwrap(), 0.10); + assert_eq!(data.radius_units.unwrap(), 'N'); + assert_eq!(&data.waypoint_id.unwrap(), "WPTNME"); + } + + #[test] + #[should_panic] + fn parse_aam_with_invalid_arrival_circle_entered_value() { + parse_aam(NmeaSentence { + talker_id: "GP", + message_id: SentenceType::AAM, + data: "G,V,0.10,N,WPTNME", + checksum: 0x0, + }) + .unwrap(); + } + + #[test] + #[should_panic] + fn parse_aam_with_invalid_perpendicular_passed_value() { + parse_aam(NmeaSentence { + talker_id: "GP", + message_id: SentenceType::AAM, + data: "V,X,0.10,N,WPTNME", + checksum: 0x0, + }) + .unwrap(); + } + + #[test] + #[should_panic] + fn parse_aam_with_invalid_radius_units_value() { + parse_aam(NmeaSentence { + talker_id: "GP", + message_id: SentenceType::AAM, + data: "V,A,0.10,P,WPTNME", + checksum: 0x0, + }) + .unwrap(); + } + + #[test] + fn parse_aam_full_sentence() { + let sentence = parse_nmea_sentence("$GPAAM,A,A,0.10,N,WPTNME*32").unwrap(); + assert_eq!(sentence.checksum, 0x32); + assert_eq!(sentence.calc_checksum(), 0x32); + + let data = parse_aam(sentence).unwrap(); + assert!(data.arrival_circle_entered.unwrap()); + assert!(data.perpendicular_passed.unwrap()); + assert_relative_eq!(data.arrival_circle_radius.unwrap(), 0.10); + assert_eq!(data.radius_units.unwrap(), 'N'); + assert_eq!(&data.waypoint_id.unwrap(), "WPTNME"); + } + + #[test] + fn parse_aam_with_wrong_message_id() { + let error = parse_aam(NmeaSentence { + talker_id: "GP", + message_id: SentenceType::ABK, + data: "A,V,0.10,N,WPTNME", + checksum: 0x43, + }) + .unwrap_err(); + + if let Error::WrongSentenceHeader { expected, found } = error { + assert_eq!(expected, SentenceType::AAM); + assert_eq!(found, SentenceType::ABK); + } + } +} diff --git a/src/sentences/mod.rs b/src/sentences/mod.rs index 3acee99..9c35b51 100644 --- a/src/sentences/mod.rs +++ b/src/sentences/mod.rs @@ -1,5 +1,6 @@ //! All the supported sentence type data and parsers. +mod aam; mod bod; mod bwc; mod gbs; @@ -19,6 +20,7 @@ mod fix_type; mod gnss_type; pub use { + aam::{parse_aam, AamData}, bod::{parse_bod, BodData}, bwc::{parse_bwc, BwcData}, faa_mode::{FaaMode, FaaModes}, diff --git a/tests/all_supported_messages.rs b/tests/all_supported_messages.rs index 4ef3abc..fd79413 100644 --- a/tests/all_supported_messages.rs +++ b/tests/all_supported_messages.rs @@ -5,6 +5,8 @@ use nmea::{parse_str, Error, Nmea, SentenceType}; #[test] fn test_all_supported_messages() { let sentences = [ + // AAM + (SentenceType::AAM, "$GPAAM,A,A,0.10,N,WPTNME*32"), // BWC (SentenceType::BWC, "$GPBWC,220516,5130.02,N,00046.34,W,213.8,T,218.0,M,0004.6,N,EGLM*21"), // GGA @@ -23,7 +25,9 @@ fn test_all_supported_messages() { (SentenceType::TXT, "$GNTXT,01,01,02,u-blox AG - www.u-blox.com*4E"), // VTG (SentenceType::VTG, "$GPVTG,360.0,T,348.7,M,000.0,N,000.0,K*43"), - ].into_iter().collect::>(); + ] + .into_iter() + .collect::>(); // `parse_str()` test { @@ -57,14 +61,10 @@ fn test_all_supported_messages() { let errors = parse_results .into_iter() .filter_map(|result| result.err()) + .map(|(_sentence, error_type)| error_type) .collect::>(); - assert_eq!( - vec![( - &sentences[&SentenceType::BWC], - Error::Unsupported(SentenceType::BWC) - )], - errors, - ); + assert!(errors.contains(&Error::Unsupported(SentenceType::BWC))); + assert!(errors.contains(&Error::Unsupported(SentenceType::AAM))); } }