1use crate::message::ParsedSentence;
58use crate::types::{MessageType, TalkerId};
59
60#[derive(Debug, Clone)]
62pub struct GsvData {
63 pub talker_id: TalkerId,
64 pub num_messages: u8,
65 pub message_num: u8,
66 pub satellites_in_view: u8,
67 pub satellite_info: [Option<SatelliteInfo>; 4],
68}
69
70#[derive(Debug, Clone)]
72pub struct SatelliteInfo {
73 pub prn: Option<u8>,
74 pub elevation: Option<u16>,
75 pub azimuth: Option<u16>,
76 pub snr: Option<u8>,
77}
78
79impl ParsedSentence {
80 pub fn as_gsv(&self) -> Option<GsvData> {
122 if self.message_type != MessageType::GSV {
123 return None;
124 }
125
126 let num_messages: u8 = self.parse_field(1)?;
128 let message_num: u8 = self.parse_field(2)?;
129 let satellites_in_view: u8 = self.parse_field(3)?;
130
131 let sat1 = if self.get_field_str(4).is_some() {
132 Some(SatelliteInfo {
133 prn: self.parse_field(4),
134 elevation: self.parse_field(5),
135 azimuth: self.parse_field(6),
136 snr: self.parse_field(7),
137 })
138 } else {
139 None
140 };
141
142 let sat2 = if self.get_field_str(8).is_some() {
143 Some(SatelliteInfo {
144 prn: self.parse_field(8),
145 elevation: self.parse_field(9),
146 azimuth: self.parse_field(10),
147 snr: self.parse_field(11),
148 })
149 } else {
150 None
151 };
152
153 let sat3 = if self.get_field_str(12).is_some() {
154 Some(SatelliteInfo {
155 prn: self.parse_field(12),
156 elevation: self.parse_field(13),
157 azimuth: self.parse_field(14),
158 snr: self.parse_field(15),
159 })
160 } else {
161 None
162 };
163
164 let sat4 = if self.get_field_str(16).is_some() {
165 Some(SatelliteInfo {
166 prn: self.parse_field(16),
167 elevation: self.parse_field(17),
168 azimuth: self.parse_field(18),
169 snr: self.parse_field(19),
170 })
171 } else {
172 None
173 };
174
175 Some(GsvData {
176 talker_id: self.talker_id,
177 num_messages,
178 message_num,
179 satellites_in_view,
180 satellite_info: [sat1, sat2, sat3, sat4],
181 })
182 }
183}
184
185#[cfg(test)]
186mod tests {
187 use crate::NmeaParser;
188
189 #[test]
190 fn test_gsv_complete_message() {
191 let parser = NmeaParser::new();
192 let sentence = b"$GPGSV,2,1,08,01,40,083,46,02,17,308,41,12,07,344,39,14,22,228,45*75\r\n";
193
194 let result = parser.parse_sentence_complete(sentence);
195
196 assert!(result.is_some());
197 let msg = result.unwrap();
198 let gsv = msg.as_gsv();
199 assert!(gsv.is_some());
200
201 let gsv_data = gsv.unwrap();
202 assert_eq!(gsv_data.num_messages, 2);
203 assert_eq!(gsv_data.message_num, 1);
204 assert_eq!(gsv_data.satellites_in_view, 8);
205
206 assert!(gsv_data.satellite_info[0].is_some());
208 let sat1 = gsv_data.satellite_info[0].as_ref().unwrap();
209 assert_eq!(sat1.prn, Some(1));
210 assert_eq!(sat1.elevation, Some(40));
211 assert_eq!(sat1.azimuth, Some(83));
212 assert_eq!(sat1.snr, Some(46));
213
214 assert!(gsv_data.satellite_info[1].is_some());
216 let sat2 = gsv_data.satellite_info[1].as_ref().unwrap();
217 assert_eq!(sat2.prn, Some(2));
218 assert_eq!(sat2.elevation, Some(17));
219 assert_eq!(sat2.azimuth, Some(308));
220 assert_eq!(sat2.snr, Some(41));
221 }
222
223 #[test]
224 fn test_gsv_partial_satellites() {
225 let parser = NmeaParser::new();
226 let sentence = b"$GPGSV,1,1,02,01,40,083,46,02,17,308,*75\r\n";
227
228 let result = parser.parse_sentence_complete(sentence);
229
230 assert!(result.is_some());
231 let msg = result.unwrap();
232 let gsv = msg.as_gsv();
233 assert!(gsv.is_some());
234
235 let gsv_data = gsv.unwrap();
236 assert_eq!(gsv_data.satellites_in_view, 2);
237
238 assert!(gsv_data.satellite_info[0].is_some());
240 let sat1 = gsv_data.satellite_info[0].as_ref().unwrap();
241 assert_eq!(sat1.prn, Some(1));
242
243 assert!(gsv_data.satellite_info[1].is_some());
245 let sat2 = gsv_data.satellite_info[1].as_ref().unwrap();
246 assert_eq!(sat2.prn, Some(2));
247 assert_eq!(sat2.snr, None);
248
249 assert!(gsv_data.satellite_info[2].is_none());
251 assert!(gsv_data.satellite_info[3].is_none());
252 }
253
254 #[test]
255 fn test_gsv_single_satellite() {
256 let parser = NmeaParser::new();
257 let sentence = b"$GPGSV,1,1,01,01,40,083,46*75\r\n";
258
259 let result = parser.parse_sentence_complete(sentence);
260
261 assert!(result.is_some());
262 let msg = result.unwrap();
263 let gsv = msg.as_gsv();
264 assert!(gsv.is_some());
265
266 let gsv_data = gsv.unwrap();
267 assert_eq!(gsv_data.satellites_in_view, 1);
268 assert!(gsv_data.satellite_info[0].is_some());
269 assert!(gsv_data.satellite_info[1].is_none());
270 }
271
272 #[test]
273 fn test_gsv_missing_num_messages() {
274 let parser = NmeaParser::new();
275 let sentence = b"$GPGSV,,1,08,01,40,083,46,02,17,308,41,12,07,344,39,14,22,228,45*75\r\n";
276
277 let result = parser.parse_sentence_complete(sentence);
278
279 assert!(result.is_none());
281 }
282
283 #[test]
284 fn test_gsv_missing_message_num() {
285 let parser = NmeaParser::new();
286 let sentence = b"$GPGSV,2,,08,01,40,083,46,02,17,308,41,12,07,344,39,14,22,228,45*75\r\n";
287
288 let result = parser.parse_sentence_complete(sentence);
289
290 assert!(result.is_none());
292 }
293
294 #[test]
295 fn test_gsv_missing_satellites_in_view() {
296 let parser = NmeaParser::new();
297 let sentence = b"$GPGSV,2,1,,01,40,083,46,02,17,308,41,12,07,344,39,14,22,228,45*75\r\n";
298
299 let result = parser.parse_sentence_complete(sentence);
300
301 assert!(result.is_none());
303 }
304
305 #[test]
306 fn test_gsv_satellite_partial_info() {
307 let parser = NmeaParser::new();
308 let sentence = b"$GPGSV,1,1,01,01,,,*75\r\n";
310
311 let result = parser.parse_sentence_complete(sentence);
312
313 assert!(result.is_some());
314 let msg = result.unwrap();
315 let gsv = msg.as_gsv();
316 assert!(gsv.is_some());
317
318 let gsv_data = gsv.unwrap();
319 assert!(gsv_data.satellite_info[0].is_some());
320 let sat = gsv_data.satellite_info[0].as_ref().unwrap();
321 assert_eq!(sat.prn, Some(1));
322 assert_eq!(sat.elevation, None);
323 assert_eq!(sat.azimuth, None);
324 assert_eq!(sat.snr, None);
325 }
326
327 #[test]
328 fn test_gsv_zero_elevation_azimuth() {
329 let parser = NmeaParser::new();
330 let sentence = b"$GPGSV,1,1,01,01,0,0,46*75\r\n";
331
332 let result = parser.parse_sentence_complete(sentence);
333
334 assert!(result.is_some());
335 let msg = result.unwrap();
336 let gsv = msg.as_gsv();
337 assert!(gsv.is_some());
338
339 let gsv_data = gsv.unwrap();
340 let sat = gsv_data.satellite_info[0].as_ref().unwrap();
341 assert_eq!(sat.elevation, Some(0));
342 assert_eq!(sat.azimuth, Some(0));
343 }
344
345 #[test]
346 fn test_gsv_max_elevation() {
347 let parser = NmeaParser::new();
348 let sentence = b"$GPGSV,1,1,01,01,90,180,46*75\r\n";
349
350 let result = parser.parse_sentence_complete(sentence);
351
352 assert!(result.is_some());
353 let msg = result.unwrap();
354 let gsv = msg.as_gsv();
355 assert!(gsv.is_some());
356
357 let gsv_data = gsv.unwrap();
358 let sat = gsv_data.satellite_info[0].as_ref().unwrap();
359 assert_eq!(sat.elevation, Some(90));
360 }
361
362 #[test]
363 fn test_gsv_max_azimuth() {
364 let parser = NmeaParser::new();
365 let sentence = b"$GPGSV,1,1,01,01,45,359,46*75\r\n";
366
367 let result = parser.parse_sentence_complete(sentence);
368
369 assert!(result.is_some());
370 let msg = result.unwrap();
371 let gsv = msg.as_gsv();
372 assert!(gsv.is_some());
373
374 let gsv_data = gsv.unwrap();
375 let sat = gsv_data.satellite_info[0].as_ref().unwrap();
376 assert_eq!(sat.azimuth, Some(359));
377 }
378
379 #[test]
380 fn test_gsv_different_talker_id() {
381 let parser = NmeaParser::new();
382 let sentence = b"$GNGSV,2,1,08,01,40,083,46,02,17,308,41,12,07,344,39,14,22,228,45*75\r\n";
384
385 let result = parser.parse_sentence_complete(sentence);
386
387 assert!(result.is_some());
388 let msg = result.unwrap();
389 let gsv = msg.as_gsv();
390 assert!(gsv.is_some());
391
392 let gsv_data = gsv.unwrap();
393 assert_eq!(gsv_data.talker_id, crate::types::TalkerId::GN);
394 }
395
396 #[test]
397 fn test_gsv_satellites_from_different_constellations() {
398 let parser = NmeaParser::new();
399
400 let gp_sentence =
402 b"$GPGSV,2,1,08,01,40,083,46,02,17,308,41,12,07,344,39,14,22,228,45*75\r\n";
403 let gp_result = parser.parse_sentence_complete(gp_sentence);
404 assert!(gp_result.is_some());
405 let gp_msg = gp_result.unwrap();
406 let gp_gsv = gp_msg.as_gsv().unwrap();
407 assert_eq!(gp_gsv.talker_id, crate::types::TalkerId::GP);
408 assert_eq!(gp_gsv.satellites_in_view, 8);
409
410 let gl_sentence =
412 b"$GLGSV,2,1,08,65,40,083,46,66,17,308,41,75,07,344,39,76,22,228,45*75\r\n";
413 let gl_result = parser.parse_sentence_complete(gl_sentence);
414 assert!(gl_result.is_some());
415 let gl_msg = gl_result.unwrap();
416 let gl_gsv = gl_msg.as_gsv().unwrap();
417 assert_eq!(gl_gsv.talker_id, crate::types::TalkerId::GL);
418
419 let ga_sentence =
421 b"$GAGSV,2,1,08,01,40,083,46,02,17,308,41,12,07,344,39,14,22,228,45*75\r\n";
422 let ga_result = parser.parse_sentence_complete(ga_sentence);
423 assert!(ga_result.is_some());
424 let ga_msg = ga_result.unwrap();
425 let ga_gsv = ga_msg.as_gsv().unwrap();
426 assert_eq!(ga_gsv.talker_id, crate::types::TalkerId::GA);
427 }
428
429 #[test]
430 fn test_gsv_multiple_message_sequence() {
431 let parser = NmeaParser::new();
432
433 let sentence1 = b"$GPGSV,2,1,08,01,40,083,46,02,17,308,41,12,07,344,39,14,22,228,45*75\r\n";
435 let result1 = parser.parse_sentence_complete(sentence1);
436 assert!(result1.is_some());
437 let msg1 = result1.unwrap();
438 let gsv1_data = msg1.as_gsv().unwrap();
439 assert_eq!(gsv1_data.message_num, 1);
440 assert_eq!(gsv1_data.num_messages, 2);
441
442 let sentence2 = b"$GPGSV,2,2,08,20,35,073,44,21,25,210,42,25,15,120,40,32,10,045,38*75\r\n";
444 let result2 = parser.parse_sentence_complete(sentence2);
445 assert!(result2.is_some());
446 let msg2 = result2.unwrap();
447 let gsv2_data = msg2.as_gsv().unwrap();
448 assert_eq!(gsv2_data.message_num, 2);
449 assert_eq!(gsv2_data.num_messages, 2);
450 }
451}