1use crate::message::ParsedSentence;
58use crate::types::{MessageType, TalkerId};
59
60#[derive(Debug, Clone)]
62pub struct VtgData {
63 pub talker_id: TalkerId,
64 pub track_true: Option<f32>,
65 pub track_true_indicator: Option<char>,
66 pub track_magnetic: Option<f32>,
67 pub track_magnetic_indicator: Option<char>,
68 pub speed_knots: Option<f32>,
69 pub speed_knots_indicator: Option<char>,
70 pub speed_kph: Option<f32>,
71 pub speed_kph_indicator: Option<char>,
72}
73
74impl ParsedSentence {
75 pub fn as_vtg(&self) -> Option<VtgData> {
115 if self.message_type != MessageType::VTG {
116 return None;
117 }
118
119 Some(VtgData {
120 talker_id: self.talker_id,
121 track_true: self.parse_field(1),
122 track_true_indicator: self.parse_field_char(2),
123 track_magnetic: self.parse_field(3),
124 track_magnetic_indicator: self.parse_field_char(4),
125 speed_knots: self.parse_field(5),
126 speed_knots_indicator: self.parse_field_char(6),
127 speed_kph: self.parse_field(7),
128 speed_kph_indicator: self.parse_field_char(8),
129 })
130 }
131}
132
133#[cfg(test)]
134mod tests {
135 use crate::NmeaParser;
136
137 #[test]
138 fn test_vtg_complete_message() {
139 let parser = NmeaParser::new();
140 let sentence = b"$GPVTG,054.7,T,034.4,M,005.5,N,010.2,K*48\r\n";
141
142 let result = parser.parse_sentence_complete(sentence);
143
144 assert!(result.is_some());
145 let msg = result.unwrap();
146 let vtg = msg.as_vtg();
147 assert!(vtg.is_some());
148
149 let vtg_data = vtg.unwrap();
150 assert_eq!(vtg_data.track_true, Some(54.7));
151 assert_eq!(vtg_data.track_true_indicator, Some('T'));
152 assert_eq!(vtg_data.track_magnetic, Some(34.4));
153 assert_eq!(vtg_data.track_magnetic_indicator, Some('M'));
154 assert_eq!(vtg_data.speed_knots, Some(5.5));
155 assert_eq!(vtg_data.speed_knots_indicator, Some('N'));
156 assert_eq!(vtg_data.speed_kph, Some(10.2));
157 assert_eq!(vtg_data.speed_kph_indicator, Some('K'));
158 }
159
160 #[test]
161 fn test_vtg_with_empty_fields() {
162 let parser = NmeaParser::new();
163 let sentence = b"$GPVTG,,T,,M,,N,,K*48\r\n";
164
165 let result = parser.parse_sentence_complete(sentence);
166
167 assert!(result.is_some());
168 let msg = result.unwrap();
169 let vtg = msg.as_vtg();
170 assert!(vtg.is_some());
171
172 let vtg_data = vtg.unwrap();
173 assert_eq!(vtg_data.track_true, None);
174 assert_eq!(vtg_data.track_magnetic, None);
175 assert_eq!(vtg_data.speed_knots, None);
176 assert_eq!(vtg_data.speed_kph, None);
177 }
178
179 #[test]
180 fn test_vtg_zero_speed() {
181 let parser = NmeaParser::new();
182 let sentence = b"$GPVTG,0.0,T,0.0,M,0.0,N,0.0,K*48\r\n";
183
184 let result = parser.parse_sentence_complete(sentence);
185
186 assert!(result.is_some());
187 let msg = result.unwrap();
188 let vtg = msg.as_vtg();
189 assert!(vtg.is_some());
190
191 let vtg_data = vtg.unwrap();
192 assert_eq!(vtg_data.speed_knots, Some(0.0));
193 assert_eq!(vtg_data.speed_kph, Some(0.0));
194 }
195
196 #[test]
197 fn test_vtg_high_speed() {
198 let parser = NmeaParser::new();
199 let sentence = b"$GPVTG,270.5,T,250.3,M,125.8,N,233.0,K*48\r\n";
200
201 let result = parser.parse_sentence_complete(sentence);
202
203 assert!(result.is_some());
204 let msg = result.unwrap();
205 let vtg = msg.as_vtg();
206 assert!(vtg.is_some());
207
208 let vtg_data = vtg.unwrap();
209 assert!((vtg_data.speed_knots.unwrap() - 125.8).abs() < 0.1);
210 assert!((vtg_data.speed_kph.unwrap() - 233.0).abs() < 0.1);
211 }
212
213 #[test]
214 fn test_vtg_only_true_track() {
215 let parser = NmeaParser::new();
216 let sentence = b"$GPVTG,054.7,T,,M,,N,,K*48\r\n";
217
218 let result = parser.parse_sentence_complete(sentence);
219
220 assert!(result.is_some());
221 let msg = result.unwrap();
222 let vtg = msg.as_vtg();
223 assert!(vtg.is_some());
224
225 let vtg_data = vtg.unwrap();
226 assert_eq!(vtg_data.track_true, Some(54.7));
227 assert_eq!(vtg_data.track_magnetic, None);
228 }
229
230 #[test]
231 fn test_vtg_only_knots() {
232 let parser = NmeaParser::new();
233 let sentence = b"$GPVTG,,T,,M,5.5,N,,K*48\r\n";
234
235 let result = parser.parse_sentence_complete(sentence);
236
237 assert!(result.is_some());
238 let msg = result.unwrap();
239 let vtg = msg.as_vtg();
240 assert!(vtg.is_some());
241
242 let vtg_data = vtg.unwrap();
243 assert_eq!(vtg_data.speed_knots, Some(5.5));
244 assert_eq!(vtg_data.speed_kph, None);
245 }
246
247 #[test]
248 fn test_vtg_only_kph() {
249 let parser = NmeaParser::new();
250 let sentence = b"$GPVTG,,T,,M,,N,10.2,K*48\r\n";
251
252 let result = parser.parse_sentence_complete(sentence);
253
254 assert!(result.is_some());
255 let msg = result.unwrap();
256 let vtg = msg.as_vtg();
257 assert!(vtg.is_some());
258
259 let vtg_data = vtg.unwrap();
260 assert_eq!(vtg_data.speed_knots, None);
261 assert_eq!(vtg_data.speed_kph, Some(10.2));
262 }
263
264 #[test]
265 fn test_vtg_track_angle_ranges() {
266 let parser = NmeaParser::new();
267
268 let sentence = b"$GPVTG,0.0,T,0.0,M,5.5,N,10.2,K*48\r\n";
270 let result = parser.parse_sentence_complete(sentence);
271 assert!(result.is_some());
272 let msg = result.unwrap();
273 let vtg = msg.as_vtg().unwrap();
274 assert_eq!(vtg.track_true, Some(0.0));
275
276 let sentence = b"$GPVTG,359.9,T,359.9,M,5.5,N,10.2,K*48\r\n";
278 let result = parser.parse_sentence_complete(sentence);
279 assert!(result.is_some());
280 let msg = result.unwrap();
281 let vtg = msg.as_vtg().unwrap();
282 assert!((vtg.track_true.unwrap() - 359.9).abs() < 0.1);
283 }
284
285 #[test]
286 fn test_vtg_speed_conversion_accuracy() {
287 let parser = NmeaParser::new();
288 let sentence = b"$GPVTG,054.7,T,034.4,M,005.5,N,010.2,K*48\r\n";
291
292 let result = parser.parse_sentence_complete(sentence);
293
294 assert!(result.is_some());
295 let msg = result.unwrap();
296 let vtg = msg.as_vtg();
297 assert!(vtg.is_some());
298
299 let vtg_data = vtg.unwrap();
300 let knots = vtg_data.speed_knots.unwrap();
301 let kph = vtg_data.speed_kph.unwrap();
302
303 let expected_kph = knots * 1.852;
305 assert!((kph - expected_kph).abs() < 0.2);
306 }
307
308 #[test]
309 fn test_vtg_numeric_precision() {
310 let parser = NmeaParser::new();
311 let sentence = b"$GPVTG,054.7,T,034.4,M,005.5,N,010.2,K*48\r\n";
312
313 let result = parser.parse_sentence_complete(sentence);
314
315 assert!(result.is_some());
316 let msg = result.unwrap();
317 let vtg = msg.as_vtg();
318 assert!(vtg.is_some());
319
320 let vtg_data = vtg.unwrap();
321 assert!((vtg_data.track_true.unwrap() - 54.7).abs() < 0.1);
322 assert!((vtg_data.track_magnetic.unwrap() - 34.4).abs() < 0.1);
323 assert!((vtg_data.speed_knots.unwrap() - 5.5).abs() < 0.1);
324 assert!((vtg_data.speed_kph.unwrap() - 10.2).abs() < 0.1);
325 }
326
327 #[test]
328 fn test_vtg_different_talker_id() {
329 let parser = NmeaParser::new();
330 let sentence = b"$GNVTG,054.7,T,034.4,M,005.5,N,010.2,K*48\r\n";
332
333 let result = parser.parse_sentence_complete(sentence);
334
335 assert!(result.is_some());
336 let msg = result.unwrap();
337 let vtg = msg.as_vtg();
338 assert!(vtg.is_some());
339
340 let vtg_data = vtg.unwrap();
341 assert_eq!(vtg_data.talker_id, crate::types::TalkerId::GN);
342 }
343
344 #[test]
345 fn test_vtg_mixed_constellation_data() {
346 let parser = NmeaParser::new();
347
348 let gp_sentence = b"$GPVTG,054.7,T,034.4,M,005.5,N,010.2,K*48\r\n";
350 let gp_result = parser.parse_sentence_complete(gp_sentence);
351 assert!(gp_result.is_some());
352 let gp_msg = gp_result.unwrap();
353 let gp_vtg = gp_msg.as_vtg().unwrap();
354 assert_eq!(gp_vtg.talker_id, crate::types::TalkerId::GP);
355 assert_eq!(gp_vtg.track_true, Some(54.7));
356
357 let gl_sentence = b"$GLVTG,154.7,T,134.4,M,015.5,N,028.7,K*48\r\n";
359 let gl_result = parser.parse_sentence_complete(gl_sentence);
360 assert!(gl_result.is_some());
361 let gl_msg = gl_result.unwrap();
362 let gl_vtg = gl_msg.as_vtg().unwrap();
363 assert_eq!(gl_vtg.talker_id, crate::types::TalkerId::GL);
364 assert_eq!(gl_vtg.track_true, Some(154.7));
365 }
366
367 #[test]
368 fn test_vtg_easterly_heading() {
369 let parser = NmeaParser::new();
370 let sentence = b"$GPVTG,090.0,T,085.0,M,10.0,N,18.5,K*48\r\n";
371
372 let result = parser.parse_sentence_complete(sentence);
373
374 assert!(result.is_some());
375 let msg = result.unwrap();
376 let vtg = msg.as_vtg();
377 assert!(vtg.is_some());
378
379 let vtg_data = vtg.unwrap();
380 assert_eq!(vtg_data.track_true, Some(90.0));
381 }
382
383 #[test]
384 fn test_vtg_westerly_heading() {
385 let parser = NmeaParser::new();
386 let sentence = b"$GPVTG,270.0,T,265.0,M,10.0,N,18.5,K*48\r\n";
387
388 let result = parser.parse_sentence_complete(sentence);
389
390 assert!(result.is_some());
391 let msg = result.unwrap();
392 let vtg = msg.as_vtg();
393 assert!(vtg.is_some());
394
395 let vtg_data = vtg.unwrap();
396 assert_eq!(vtg_data.track_true, Some(270.0));
397 }
398}