Skip to content

Commit b73418a

Browse files
authored
Merge pull request #62 from bahelms/aam-sentence-parsing
Add AAM sentence parser
2 parents d5f1209 + 08ccbbb commit b73418a

File tree

5 files changed

+189
-8
lines changed

5 files changed

+189
-8
lines changed

src/parse.rs

+1
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,7 @@ pub fn parse_nmea_sentence(sentence: &str) -> core::result::Result<NmeaSentence,
9797
/// The result of parsing a single NMEA message.
9898
#[derive(Debug, PartialEq)]
9999
pub enum ParseResult {
100+
AAM(AamData),
100101
BOD(BodData),
101102
BWC(BwcData),
102103
GBS(GbsData),

src/parser.rs

+1
Original file line numberDiff line numberDiff line change
@@ -356,6 +356,7 @@ impl<'a> Nmea {
356356
ParseResult::BWC(_)
357357
| ParseResult::BOD(_)
358358
| ParseResult::GBS(_)
359+
| ParseResult::AAM(_)
359360
| ParseResult::PGRMZ(_) => return Ok(FixType::Invalid),
360361

361362
ParseResult::Unsupported(_) => {

src/sentences/aam.rs

+177
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,177 @@
1+
use arrayvec::ArrayString;
2+
use nom::{
3+
bytes::complete::is_not,
4+
character::complete::{char, one_of},
5+
combinator::opt,
6+
number::complete::float,
7+
};
8+
9+
use crate::{parse::NmeaSentence, sentences::utils::array_string, Error, SentenceType};
10+
11+
const MAX_LEN: usize = 64;
12+
13+
/// AAM - Waypoint Arrival Alarm
14+
///
15+
/// <https://gpsd.gitlab.io/gpsd/NMEA.html#_aam_waypoint_arrival_alarm>
16+
///
17+
/// ```text
18+
/// 1 2 3 4 5 6
19+
/// | | | | | |
20+
/// $--AAM,A,A,x.x,N,c--c*hh<CR><LF>
21+
///
22+
/// Field Number:
23+
/// 1. Status, BOOLEAN, A = Arrival circle entered, V = not passed
24+
/// 2. Status, BOOLEAN, A = perpendicular passed at waypoint, V = not passed
25+
/// 3. Arrival circle radius
26+
/// 4. Units of radiuos, nautical miles
27+
/// 5. Waypoint ID
28+
/// 6. Checksum
29+
///
30+
/// Example: $GPAAM,A,A,0.10,N,WPTNME*43
31+
/// WPTNME is the waypoint name.
32+
/// ```
33+
#[derive(Debug, PartialEq)]
34+
pub struct AamData {
35+
pub arrival_circle_entered: Option<bool>,
36+
pub perpendicular_passed: Option<bool>,
37+
pub arrival_circle_radius: Option<f32>,
38+
pub radius_units: Option<char>,
39+
pub waypoint_id: Option<ArrayString<MAX_LEN>>,
40+
}
41+
42+
/// Parse AAM message
43+
pub fn parse_aam(sentence: NmeaSentence) -> Result<AamData, Error> {
44+
if sentence.message_id != SentenceType::AAM {
45+
Err(Error::WrongSentenceHeader {
46+
expected: SentenceType::AAM,
47+
found: sentence.message_id,
48+
})
49+
} else {
50+
Ok(do_parse_aam(sentence.data)?)
51+
}
52+
}
53+
54+
fn do_parse_aam(i: &str) -> Result<AamData, Error> {
55+
let (i, arrival_circle_entered) = one_of("AV")(i)?;
56+
let arrival_circle_entered = match arrival_circle_entered {
57+
'A' => Some(true),
58+
'V' => Some(false),
59+
_ => unreachable!(),
60+
};
61+
let (i, _) = char(',')(i)?;
62+
63+
let (i, perpendicular_passed) = one_of("AV")(i)?;
64+
let perpendicular_passed = match perpendicular_passed {
65+
'A' => Some(true),
66+
'V' => Some(false),
67+
_ => unreachable!(),
68+
};
69+
let (i, _) = char(',')(i)?;
70+
71+
let (i, arrival_circle_radius) = opt(float)(i)?;
72+
let (i, _) = char(',')(i)?;
73+
74+
let (i, radius_units) = opt(char('N'))(i)?;
75+
let (i, _) = char(',')(i)?;
76+
77+
let (_i, waypoint_id) = opt(is_not("*"))(i)?;
78+
79+
Ok(AamData {
80+
arrival_circle_entered,
81+
perpendicular_passed,
82+
arrival_circle_radius,
83+
radius_units,
84+
waypoint_id: waypoint_id.map(array_string::<MAX_LEN>).transpose()?,
85+
})
86+
}
87+
88+
#[cfg(test)]
89+
mod tests {
90+
use approx::assert_relative_eq;
91+
92+
use super::*;
93+
use crate::{parse::parse_nmea_sentence, SentenceType};
94+
95+
#[test]
96+
fn parse_aam_with_nmea_sentence_struct() {
97+
let data = parse_aam(NmeaSentence {
98+
talker_id: "GP",
99+
message_id: SentenceType::AAM,
100+
data: "A,V,0.10,N,WPTNME",
101+
checksum: 0x0,
102+
})
103+
.unwrap();
104+
105+
assert!(data.arrival_circle_entered.unwrap());
106+
assert!(!data.perpendicular_passed.unwrap());
107+
assert_relative_eq!(data.arrival_circle_radius.unwrap(), 0.10);
108+
assert_eq!(data.radius_units.unwrap(), 'N');
109+
assert_eq!(&data.waypoint_id.unwrap(), "WPTNME");
110+
}
111+
112+
#[test]
113+
#[should_panic]
114+
fn parse_aam_with_invalid_arrival_circle_entered_value() {
115+
parse_aam(NmeaSentence {
116+
talker_id: "GP",
117+
message_id: SentenceType::AAM,
118+
data: "G,V,0.10,N,WPTNME",
119+
checksum: 0x0,
120+
})
121+
.unwrap();
122+
}
123+
124+
#[test]
125+
#[should_panic]
126+
fn parse_aam_with_invalid_perpendicular_passed_value() {
127+
parse_aam(NmeaSentence {
128+
talker_id: "GP",
129+
message_id: SentenceType::AAM,
130+
data: "V,X,0.10,N,WPTNME",
131+
checksum: 0x0,
132+
})
133+
.unwrap();
134+
}
135+
136+
#[test]
137+
#[should_panic]
138+
fn parse_aam_with_invalid_radius_units_value() {
139+
parse_aam(NmeaSentence {
140+
talker_id: "GP",
141+
message_id: SentenceType::AAM,
142+
data: "V,A,0.10,P,WPTNME",
143+
checksum: 0x0,
144+
})
145+
.unwrap();
146+
}
147+
148+
#[test]
149+
fn parse_aam_full_sentence() {
150+
let sentence = parse_nmea_sentence("$GPAAM,A,A,0.10,N,WPTNME*32").unwrap();
151+
assert_eq!(sentence.checksum, 0x32);
152+
assert_eq!(sentence.calc_checksum(), 0x32);
153+
154+
let data = parse_aam(sentence).unwrap();
155+
assert!(data.arrival_circle_entered.unwrap());
156+
assert!(data.perpendicular_passed.unwrap());
157+
assert_relative_eq!(data.arrival_circle_radius.unwrap(), 0.10);
158+
assert_eq!(data.radius_units.unwrap(), 'N');
159+
assert_eq!(&data.waypoint_id.unwrap(), "WPTNME");
160+
}
161+
162+
#[test]
163+
fn parse_aam_with_wrong_message_id() {
164+
let error = parse_aam(NmeaSentence {
165+
talker_id: "GP",
166+
message_id: SentenceType::ABK,
167+
data: "A,V,0.10,N,WPTNME",
168+
checksum: 0x43,
169+
})
170+
.unwrap_err();
171+
172+
if let Error::WrongSentenceHeader { expected, found } = error {
173+
assert_eq!(expected, SentenceType::AAM);
174+
assert_eq!(found, SentenceType::ABK);
175+
}
176+
}
177+
}

src/sentences/mod.rs

+2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
//! All the supported sentence type data and parsers.
22
3+
mod aam;
34
mod bod;
45
mod bwc;
56
mod gbs;
@@ -19,6 +20,7 @@ mod fix_type;
1920
mod gnss_type;
2021

2122
pub use {
23+
aam::{parse_aam, AamData},
2224
bod::{parse_bod, BodData},
2325
bwc::{parse_bwc, BwcData},
2426
faa_mode::{FaaMode, FaaModes},

tests/all_supported_messages.rs

+8-8
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ use nmea::{parse_str, Error, Nmea, SentenceType};
55
#[test]
66
fn test_all_supported_messages() {
77
let sentences = [
8+
// AAM
9+
(SentenceType::AAM, "$GPAAM,A,A,0.10,N,WPTNME*32"),
810
// BWC
911
(SentenceType::BWC, "$GPBWC,220516,5130.02,N,00046.34,W,213.8,T,218.0,M,0004.6,N,EGLM*21"),
1012
// GGA
@@ -23,7 +25,9 @@ fn test_all_supported_messages() {
2325
(SentenceType::TXT, "$GNTXT,01,01,02,u-blox AG - www.u-blox.com*4E"),
2426
// VTG
2527
(SentenceType::VTG, "$GPVTG,360.0,T,348.7,M,000.0,N,000.0,K*43"),
26-
].into_iter().collect::<HashMap<_, _>>();
28+
]
29+
.into_iter()
30+
.collect::<HashMap<_, _>>();
2731

2832
// `parse_str()` test
2933
{
@@ -57,14 +61,10 @@ fn test_all_supported_messages() {
5761
let errors = parse_results
5862
.into_iter()
5963
.filter_map(|result| result.err())
64+
.map(|(_sentence, error_type)| error_type)
6065
.collect::<Vec<_>>();
6166

62-
assert_eq!(
63-
vec![(
64-
&sentences[&SentenceType::BWC],
65-
Error::Unsupported(SentenceType::BWC)
66-
)],
67-
errors,
68-
);
67+
assert!(errors.contains(&Error::Unsupported(SentenceType::BWC)));
68+
assert!(errors.contains(&Error::Unsupported(SentenceType::AAM)));
6969
}
7070
}

0 commit comments

Comments
 (0)