1use crate::parse::TEXT_PARAMETER_MAX_LEN;
2
3use arrayvec::ArrayString;
4use nom::{
5 bytes::complete::is_not,
6 character::complete::{char, one_of},
7 combinator::opt,
8 number::complete::float,
9};
10
11#[cfg(feature = "serde")]
12use serde::{Deserialize, Serialize};
13
14use crate::{parse::NmeaSentence, sentences::utils::array_string, Error, SentenceType};
15
16#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
37#[cfg_attr(feature = "defmt-03", derive(defmt::Format))]
38#[derive(Debug, PartialEq)]
39pub struct AamData {
40 pub arrival_circle_entered: Option<bool>,
41 pub perpendicular_passed: Option<bool>,
42 pub arrival_circle_radius: Option<f32>,
43 pub radius_units: Option<char>,
44 #[cfg_attr(feature = "defmt-03", defmt(Debug2Format))]
45 pub waypoint_id: Option<ArrayString<TEXT_PARAMETER_MAX_LEN>>,
46}
47
48pub fn parse_aam(sentence: NmeaSentence) -> Result<AamData, Error> {
50 if sentence.message_id != SentenceType::AAM {
51 Err(Error::WrongSentenceHeader {
52 expected: SentenceType::AAM,
53 found: sentence.message_id,
54 })
55 } else {
56 Ok(do_parse_aam(sentence.data)?)
57 }
58}
59
60fn do_parse_aam(i: &str) -> Result<AamData, Error> {
61 let (i, arrival_circle_entered) = one_of("AV")(i)?;
62 let arrival_circle_entered = match arrival_circle_entered {
63 'A' => Some(true),
64 'V' => Some(false),
65 _ => unreachable!(),
66 };
67 let (i, _) = char(',')(i)?;
68
69 let (i, perpendicular_passed) = one_of("AV")(i)?;
70 let perpendicular_passed = match perpendicular_passed {
71 'A' => Some(true),
72 'V' => Some(false),
73 _ => unreachable!(),
74 };
75 let (i, _) = char(',')(i)?;
76
77 let (i, arrival_circle_radius) = opt(float)(i)?;
78 let (i, _) = char(',')(i)?;
79
80 let (i, radius_units) = opt(char('N'))(i)?;
81 let (i, _) = char(',')(i)?;
82
83 let (_i, waypoint_id) = opt(is_not("*"))(i)?;
84
85 Ok(AamData {
86 arrival_circle_entered,
87 perpendicular_passed,
88 arrival_circle_radius,
89 radius_units,
90 waypoint_id: waypoint_id
91 .map(array_string::<TEXT_PARAMETER_MAX_LEN>)
92 .transpose()?,
93 })
94}
95
96#[cfg(test)]
97mod tests {
98 use approx::assert_relative_eq;
99
100 use super::*;
101 use crate::{parse::parse_nmea_sentence, SentenceType};
102
103 #[test]
104 fn parse_aam_with_nmea_sentence_struct() {
105 let data = parse_aam(NmeaSentence {
106 talker_id: "GP",
107 message_id: SentenceType::AAM,
108 data: "A,V,0.10,N,WPTNME",
109 checksum: 0x0,
110 })
111 .unwrap();
112
113 assert!(data.arrival_circle_entered.unwrap());
114 assert!(!data.perpendicular_passed.unwrap());
115 assert_relative_eq!(data.arrival_circle_radius.unwrap(), 0.10);
116 assert_eq!(data.radius_units.unwrap(), 'N');
117 assert_eq!(&data.waypoint_id.unwrap(), "WPTNME");
118 }
119
120 #[test]
121 #[should_panic]
122 fn parse_aam_with_invalid_arrival_circle_entered_value() {
123 parse_aam(NmeaSentence {
124 talker_id: "GP",
125 message_id: SentenceType::AAM,
126 data: "G,V,0.10,N,WPTNME",
127 checksum: 0x0,
128 })
129 .unwrap();
130 }
131
132 #[test]
133 #[should_panic]
134 fn parse_aam_with_invalid_perpendicular_passed_value() {
135 parse_aam(NmeaSentence {
136 talker_id: "GP",
137 message_id: SentenceType::AAM,
138 data: "V,X,0.10,N,WPTNME",
139 checksum: 0x0,
140 })
141 .unwrap();
142 }
143
144 #[test]
145 #[should_panic]
146 fn parse_aam_with_invalid_radius_units_value() {
147 parse_aam(NmeaSentence {
148 talker_id: "GP",
149 message_id: SentenceType::AAM,
150 data: "V,A,0.10,P,WPTNME",
151 checksum: 0x0,
152 })
153 .unwrap();
154 }
155
156 #[test]
157 fn parse_aam_full_sentence() {
158 let sentence = parse_nmea_sentence("$GPAAM,A,A,0.10,N,WPTNME*32").unwrap();
159 assert_eq!(sentence.checksum, 0x32);
160 assert_eq!(sentence.calc_checksum(), 0x32);
161
162 let data = parse_aam(sentence).unwrap();
163 assert!(data.arrival_circle_entered.unwrap());
164 assert!(data.perpendicular_passed.unwrap());
165 assert_relative_eq!(data.arrival_circle_radius.unwrap(), 0.10);
166 assert_eq!(data.radius_units.unwrap(), 'N');
167 assert_eq!(&data.waypoint_id.unwrap(), "WPTNME");
168 }
169
170 #[test]
171 fn parse_aam_with_wrong_message_id() {
172 let error = parse_aam(NmeaSentence {
173 talker_id: "GP",
174 message_id: SentenceType::ABK,
175 data: "A,V,0.10,N,WPTNME",
176 checksum: 0x43,
177 })
178 .unwrap_err();
179
180 if let Error::WrongSentenceHeader { expected, found } = error {
181 assert_eq!(expected, SentenceType::AAM);
182 assert_eq!(found, SentenceType::ABK);
183 }
184 }
185}