Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add AAM sentence parser #62

Merged
merged 5 commits into from
Oct 27, 2022
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/parse.rs
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,7 @@ pub fn parse_nmea_sentence(sentence: &str) -> core::result::Result<NmeaSentence,
/// The result of parsing a single NMEA message.
#[derive(Debug, PartialEq)]
pub enum ParseResult {
AAM(AamData),
BOD(BodData),
BWC(BwcData),
GBS(GbsData),
Expand Down
1 change: 1 addition & 0 deletions src/parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -356,6 +356,7 @@ impl<'a> Nmea {
ParseResult::BWC(_)
| ParseResult::BOD(_)
| ParseResult::GBS(_)
| ParseResult::AAM(_)
| ParseResult::PGRMZ(_) => return Ok(FixType::Invalid),

ParseResult::Unsupported(_) => {
Expand Down
177 changes: 177 additions & 0 deletions src/sentences/aam.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
use arrayvec::ArrayString;
use nom::{
bytes::complete::is_not,
character::complete::{anychar, char},
combinator::opt,
number::complete::float,
};

use crate::{parse::NmeaSentence, sentences::utils::array_string, Error, SentenceType};

const MAX_LEN: usize = 64;

/// AAM - Waypoint Arrival Alarm
///
/// <https://gpsd.gitlab.io/gpsd/NMEA.html#_aam_waypoint_arrival_alarm>
///
/// ```text
/// 1 2 3 4 5 6
/// | | | | | |
/// $--AAM,A,A,x.x,N,c--c*hh<CR><LF>
///
/// 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.
/// ```
elpiel marked this conversation as resolved.
Show resolved Hide resolved
#[derive(Debug, PartialEq)]
pub struct AamData {
pub arrival_circle_entered: Option<bool>,
pub perpendicular_passed: Option<bool>,
pub arrival_circle_radius: Option<f32>,
pub radius_units: Option<char>,
pub waypoint_id: Option<ArrayString<MAX_LEN>>,
}

/// Parse AAM message
pub fn parse_aam(sentence: NmeaSentence) -> Result<AamData, Error> {
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<AamData, Error> {
let (i, arrival_circle_entered) = anychar(i)?;
let arrival_circle_entered = match arrival_circle_entered {
'A' => Some(true),
'V' => Some(false),
char => unreachable!("{} is not a valid AAM-Arrival Circle Entered value", char),
};
let (i, _) = char(',')(i)?;

let (i, perpendicular_passed) = anychar(i)?;
let perpendicular_passed = match perpendicular_passed {
'A' => Some(true),
'V' => Some(false),
char => unreachable!("{} is not a valid AAM-Perpendicular Passed value", char),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

only when using one_of this will be unreachable.
Right now, because of anychar it will match other values like G and will reach the unreachable! call.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Gotcha.

};
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::<MAX_LEN>).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);
}
}
}
2 changes: 2 additions & 0 deletions src/sentences/mod.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
//! All the supported sentence type data and parsers.

mod aam;
mod bod;
mod bwc;
mod gbs;
Expand All @@ -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},
Expand Down
16 changes: 8 additions & 8 deletions tests/all_supported_messages.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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::<HashMap<_, _>>();
]
.into_iter()
.collect::<HashMap<_, _>>();

// `parse_str()` test
{
Expand Down Expand Up @@ -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::<Vec<_>>();

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)));
}
}