1use crate::error::{SbfError, SbfResult};
4use crate::header::SbfHeader;
5use crate::types::{PvtError, PvtMode};
6
7use super::block_ids;
8use super::dnu::{u8_or_none, F32_DNU, F64_DNU, I16_DNU, I32_DNU, U16_DNU};
9use super::SbfBlockParse;
10
11#[derive(Debug, Clone)]
19#[allow(dead_code)]
20pub struct IntPvCartBlock {
21 tow_ms: u32,
22 wnc: u16,
23 mode: u8,
24 error: u8,
25 info: u16,
26 nr_sv: u8,
27 nr_ant: u8,
28 gnss_pvt_mode: u8,
29 datum: u8,
30 gnss_age_raw: u16,
31 x_m: f64,
32 y_m: f64,
33 z_m: f64,
34 vx_mps: f32,
35 vy_mps: f32,
36 vz_mps: f32,
37 cog_deg: f32,
38}
39
40impl IntPvCartBlock {
41 pub fn tow_seconds(&self) -> f64 {
42 self.tow_ms as f64 * 0.001
43 }
44 pub fn tow_ms(&self) -> u32 {
45 self.tow_ms
46 }
47 pub fn wnc(&self) -> u16 {
48 self.wnc
49 }
50 pub fn mode(&self) -> PvtMode {
51 PvtMode::from_mode_byte(self.mode)
52 }
53 pub fn mode_raw(&self) -> u8 {
54 self.mode
55 }
56 pub fn error(&self) -> PvtError {
57 PvtError::from_error_byte(self.error)
58 }
59 pub fn error_raw(&self) -> u8 {
60 self.error
61 }
62 pub fn info(&self) -> u16 {
63 self.info
64 }
65 pub fn nr_sv(&self) -> u8 {
66 u8_or_none(self.nr_sv).unwrap_or(0)
67 }
68 pub fn nr_sv_opt(&self) -> Option<u8> {
69 u8_or_none(self.nr_sv)
70 }
71 pub fn nr_sv_raw(&self) -> u8 {
72 self.nr_sv
73 }
74 pub fn nr_ant(&self) -> u8 {
75 self.nr_ant
76 }
77 pub fn gnss_pvt_mode(&self) -> u8 {
78 self.gnss_pvt_mode
79 }
80 pub fn datum(&self) -> u8 {
81 self.datum
82 }
83 pub fn gnss_age_seconds(&self) -> Option<f32> {
85 if self.gnss_age_raw == U16_DNU {
86 None
87 } else {
88 Some(self.gnss_age_raw as f32 * 0.01)
89 }
90 }
91 pub fn gnss_age_raw(&self) -> u16 {
92 self.gnss_age_raw
93 }
94 pub fn x_m(&self) -> Option<f64> {
95 if self.x_m == F64_DNU {
96 None
97 } else {
98 Some(self.x_m)
99 }
100 }
101 pub fn y_m(&self) -> Option<f64> {
102 if self.y_m == F64_DNU {
103 None
104 } else {
105 Some(self.y_m)
106 }
107 }
108 pub fn z_m(&self) -> Option<f64> {
109 if self.z_m == F64_DNU {
110 None
111 } else {
112 Some(self.z_m)
113 }
114 }
115 pub fn velocity_x_mps(&self) -> Option<f32> {
116 if self.vx_mps == F32_DNU {
117 None
118 } else {
119 Some(self.vx_mps)
120 }
121 }
122 pub fn velocity_y_mps(&self) -> Option<f32> {
123 if self.vy_mps == F32_DNU {
124 None
125 } else {
126 Some(self.vy_mps)
127 }
128 }
129 pub fn velocity_z_mps(&self) -> Option<f32> {
130 if self.vz_mps == F32_DNU {
131 None
132 } else {
133 Some(self.vz_mps)
134 }
135 }
136 pub fn course_over_ground_deg(&self) -> Option<f32> {
137 if self.cog_deg == F32_DNU {
138 None
139 } else {
140 Some(self.cog_deg)
141 }
142 }
143}
144
145impl SbfBlockParse for IntPvCartBlock {
146 const BLOCK_ID: u16 = block_ids::INT_PV_CART;
147
148 fn parse(header: &SbfHeader, data: &[u8]) -> SbfResult<Self> {
149 const MIN_LEN: usize = 62;
153 if data.len() < MIN_LEN {
154 return Err(SbfError::ParseError("IntPVCart too short".into()));
155 }
156
157 let mode = data[12];
158 let error = data[13];
159 let info = u16::from_le_bytes([data[14], data[15]]);
160 let nr_sv = data[16];
161 let nr_ant = data[17];
162 let gnss_pvt_mode = data[18];
163 let datum = data[19];
164 let gnss_age_raw = u16::from_le_bytes([data[20], data[21]]);
165 let x_m = f64::from_le_bytes(data[22..30].try_into().unwrap());
166 let y_m = f64::from_le_bytes(data[30..38].try_into().unwrap());
167 let z_m = f64::from_le_bytes(data[38..46].try_into().unwrap());
168 let vx_mps = f32::from_le_bytes(data[46..50].try_into().unwrap());
169 let vy_mps = f32::from_le_bytes(data[50..54].try_into().unwrap());
170 let vz_mps = f32::from_le_bytes(data[54..58].try_into().unwrap());
171 let cog_deg = f32::from_le_bytes(data[58..62].try_into().unwrap());
172
173 Ok(Self {
174 tow_ms: header.tow_ms,
175 wnc: header.wnc,
176 mode,
177 error,
178 info,
179 nr_sv,
180 nr_ant,
181 gnss_pvt_mode,
182 datum,
183 gnss_age_raw,
184 x_m,
185 y_m,
186 z_m,
187 vx_mps,
188 vy_mps,
189 vz_mps,
190 cog_deg,
191 })
192 }
193}
194
195#[derive(Debug, Clone)]
203#[allow(dead_code)]
204pub struct IntPvGeodBlock {
205 tow_ms: u32,
206 wnc: u16,
207 mode: u8,
208 error: u8,
209 info: u16,
210 nr_sv: u8,
211 nr_ant: u8,
212 gnss_pvt_mode: u8,
213 datum: u8,
214 gnss_age_raw: u16,
215 lat_rad: f64,
216 long_rad: f64,
217 alt_m: f64,
218 vn_mps: f32,
219 ve_mps: f32,
220 vu_mps: f32,
221 cog_deg: f32,
222}
223
224impl IntPvGeodBlock {
225 pub fn tow_seconds(&self) -> f64 {
226 self.tow_ms as f64 * 0.001
227 }
228 pub fn tow_ms(&self) -> u32 {
229 self.tow_ms
230 }
231 pub fn wnc(&self) -> u16 {
232 self.wnc
233 }
234 pub fn mode(&self) -> PvtMode {
235 PvtMode::from_mode_byte(self.mode)
236 }
237 pub fn mode_raw(&self) -> u8 {
238 self.mode
239 }
240 pub fn error(&self) -> PvtError {
241 PvtError::from_error_byte(self.error)
242 }
243 pub fn error_raw(&self) -> u8 {
244 self.error
245 }
246 pub fn info(&self) -> u16 {
247 self.info
248 }
249 pub fn nr_sv(&self) -> u8 {
250 u8_or_none(self.nr_sv).unwrap_or(0)
251 }
252 pub fn nr_sv_opt(&self) -> Option<u8> {
253 u8_or_none(self.nr_sv)
254 }
255 pub fn nr_sv_raw(&self) -> u8 {
256 self.nr_sv
257 }
258 pub fn nr_ant(&self) -> u8 {
259 self.nr_ant
260 }
261 pub fn gnss_pvt_mode(&self) -> u8 {
262 self.gnss_pvt_mode
263 }
264 pub fn datum(&self) -> u8 {
265 self.datum
266 }
267 pub fn gnss_age_seconds(&self) -> Option<f32> {
268 if self.gnss_age_raw == U16_DNU {
269 None
270 } else {
271 Some(self.gnss_age_raw as f32 * 0.01)
272 }
273 }
274 pub fn gnss_age_raw(&self) -> u16 {
275 self.gnss_age_raw
276 }
277 pub fn latitude_deg(&self) -> Option<f64> {
278 if self.lat_rad == F64_DNU {
279 None
280 } else {
281 Some(self.lat_rad.to_degrees())
282 }
283 }
284 pub fn longitude_deg(&self) -> Option<f64> {
285 if self.long_rad == F64_DNU {
286 None
287 } else {
288 Some(self.long_rad.to_degrees())
289 }
290 }
291 pub fn altitude_m(&self) -> Option<f64> {
292 if self.alt_m == F64_DNU {
293 None
294 } else {
295 Some(self.alt_m)
296 }
297 }
298 pub fn velocity_north_mps(&self) -> Option<f32> {
299 if self.vn_mps == F32_DNU {
300 None
301 } else {
302 Some(self.vn_mps)
303 }
304 }
305 pub fn velocity_east_mps(&self) -> Option<f32> {
306 if self.ve_mps == F32_DNU {
307 None
308 } else {
309 Some(self.ve_mps)
310 }
311 }
312 pub fn velocity_up_mps(&self) -> Option<f32> {
313 if self.vu_mps == F32_DNU {
314 None
315 } else {
316 Some(self.vu_mps)
317 }
318 }
319 pub fn course_over_ground_deg(&self) -> Option<f32> {
320 if self.cog_deg == F32_DNU {
321 None
322 } else {
323 Some(self.cog_deg)
324 }
325 }
326}
327
328impl SbfBlockParse for IntPvGeodBlock {
329 const BLOCK_ID: u16 = block_ids::INT_PV_GEOD;
330
331 fn parse(header: &SbfHeader, data: &[u8]) -> SbfResult<Self> {
332 const MIN_LEN: usize = 62;
333 if data.len() < MIN_LEN {
334 return Err(SbfError::ParseError("IntPVGeod too short".into()));
335 }
336
337 let mode = data[12];
338 let error = data[13];
339 let info = u16::from_le_bytes([data[14], data[15]]);
340 let nr_sv = data[16];
341 let nr_ant = data[17];
342 let gnss_pvt_mode = data[18];
343 let datum = data[19];
344 let gnss_age_raw = u16::from_le_bytes([data[20], data[21]]);
345 let lat_rad = f64::from_le_bytes(data[22..30].try_into().unwrap());
346 let long_rad = f64::from_le_bytes(data[30..38].try_into().unwrap());
347 let alt_m = f64::from_le_bytes(data[38..46].try_into().unwrap());
348 let vn_mps = f32::from_le_bytes(data[46..50].try_into().unwrap());
349 let ve_mps = f32::from_le_bytes(data[50..54].try_into().unwrap());
350 let vu_mps = f32::from_le_bytes(data[54..58].try_into().unwrap());
351 let cog_deg = f32::from_le_bytes(data[58..62].try_into().unwrap());
352
353 Ok(Self {
354 tow_ms: header.tow_ms,
355 wnc: header.wnc,
356 mode,
357 error,
358 info,
359 nr_sv,
360 nr_ant,
361 gnss_pvt_mode,
362 datum,
363 gnss_age_raw,
364 lat_rad,
365 long_rad,
366 alt_m,
367 vn_mps,
368 ve_mps,
369 vu_mps,
370 cog_deg,
371 })
372 }
373}
374
375#[derive(Debug, Clone)]
384#[allow(dead_code)]
385pub struct IntPvaaGeodBlock {
386 tow_ms: u32,
387 wnc: u16,
388 mode: u8,
389 error: u8,
390 info: u16,
391 gnss_pvt_mode: u8,
392 datum: u8,
393 gnss_age_raw: u8,
394 nr_sv_ant: u8,
395 pos_fine: u8,
396 lat_raw: i32,
397 long_raw: i32,
398 alt_raw: i32,
399 vn_raw: i32,
400 ve_raw: i32,
401 vu_raw: i32,
402 ax_raw: i16,
403 ay_raw: i16,
404 az_raw: i16,
405 heading_raw: u16,
406 pitch_raw: i16,
407 roll_raw: i16,
408}
409
410impl IntPvaaGeodBlock {
411 pub fn tow_seconds(&self) -> f64 {
412 self.tow_ms as f64 * 0.001
413 }
414 pub fn tow_ms(&self) -> u32 {
415 self.tow_ms
416 }
417 pub fn wnc(&self) -> u16 {
418 self.wnc
419 }
420 pub fn mode(&self) -> PvtMode {
421 PvtMode::from_mode_byte(self.mode)
422 }
423 pub fn mode_raw(&self) -> u8 {
424 self.mode
425 }
426 pub fn error(&self) -> PvtError {
427 PvtError::from_error_byte(self.error)
428 }
429 pub fn error_raw(&self) -> u8 {
430 self.error
431 }
432 pub fn info(&self) -> u16 {
433 self.info
434 }
435 pub fn gnss_pvt_mode(&self) -> u8 {
436 self.gnss_pvt_mode
437 }
438 pub fn datum(&self) -> u8 {
439 self.datum
440 }
441 pub fn gnss_age_seconds(&self) -> Option<f32> {
443 if self.gnss_age_raw == 255 {
444 None
445 } else {
446 Some(self.gnss_age_raw as f32 * 0.1)
447 }
448 }
449 pub fn gnss_age_raw(&self) -> u8 {
450 self.gnss_age_raw
451 }
452 pub fn nr_sv_ant(&self) -> u8 {
453 self.nr_sv_ant
454 }
455 pub fn pos_fine(&self) -> u8 {
456 self.pos_fine
457 }
458 pub fn nr_sv(&self) -> u8 {
460 self.nr_sv_ant >> 4
461 }
462 pub fn nr_ant(&self) -> u8 {
464 self.nr_sv_ant & 0x0F
465 }
466 pub fn latitude_deg(&self) -> Option<f64> {
468 if self.lat_raw == I32_DNU {
469 None
470 } else {
471 Some(self.lat_raw as f64 * 1e-7)
472 }
473 }
474 pub fn longitude_deg(&self) -> Option<f64> {
476 if self.long_raw == I32_DNU {
477 None
478 } else {
479 Some(self.long_raw as f64 * 1e-7)
480 }
481 }
482 pub fn altitude_m(&self) -> Option<f64> {
484 if self.alt_raw == I32_DNU {
485 None
486 } else {
487 Some(self.alt_raw as f64 * 1e-3)
488 }
489 }
490 pub fn velocity_north_mps(&self) -> Option<f64> {
492 if self.vn_raw == I32_DNU {
493 None
494 } else {
495 Some(self.vn_raw as f64 * 1e-3)
496 }
497 }
498 pub fn velocity_east_mps(&self) -> Option<f64> {
500 if self.ve_raw == I32_DNU {
501 None
502 } else {
503 Some(self.ve_raw as f64 * 1e-3)
504 }
505 }
506 pub fn velocity_up_mps(&self) -> Option<f64> {
508 if self.vu_raw == I32_DNU {
509 None
510 } else {
511 Some(self.vu_raw as f64 * 1e-3)
512 }
513 }
514 pub fn acceleration_x_mps2(&self) -> Option<f64> {
516 if self.ax_raw == I16_DNU {
517 None
518 } else {
519 Some(self.ax_raw as f64 * 0.01)
520 }
521 }
522 pub fn acceleration_y_mps2(&self) -> Option<f64> {
524 if self.ay_raw == I16_DNU {
525 None
526 } else {
527 Some(self.ay_raw as f64 * 0.01)
528 }
529 }
530 pub fn acceleration_z_mps2(&self) -> Option<f64> {
532 if self.az_raw == I16_DNU {
533 None
534 } else {
535 Some(self.az_raw as f64 * 0.01)
536 }
537 }
538 pub fn heading_deg(&self) -> Option<f64> {
540 if self.heading_raw == U16_DNU {
541 None
542 } else {
543 Some(self.heading_raw as f64 * 0.01)
544 }
545 }
546 pub fn pitch_deg(&self) -> Option<f64> {
548 if self.pitch_raw == I16_DNU {
549 None
550 } else {
551 Some(self.pitch_raw as f64 * 0.01)
552 }
553 }
554 pub fn roll_deg(&self) -> Option<f64> {
556 if self.roll_raw == I16_DNU {
557 None
558 } else {
559 Some(self.roll_raw as f64 * 0.01)
560 }
561 }
562}
563
564impl SbfBlockParse for IntPvaaGeodBlock {
565 const BLOCK_ID: u16 = block_ids::INT_PVA_AGEOD;
566
567 fn parse(header: &SbfHeader, data: &[u8]) -> SbfResult<Self> {
568 const MIN_LEN: usize = 57;
571 if data.len() < MIN_LEN {
572 return Err(SbfError::ParseError("IntPVAAGeod too short".into()));
573 }
574
575 let mode = data[12];
576 let error = data[13];
577 let info = u16::from_le_bytes([data[14], data[15]]);
578 let gnss_pvt_mode = data[16];
579 let datum = data[17];
580 let gnss_age_raw = data[18];
581 let nr_sv_ant = data[19];
582 let pos_fine = data[20];
583 let lat_raw = i32::from_le_bytes(data[21..25].try_into().unwrap());
584 let long_raw = i32::from_le_bytes(data[25..29].try_into().unwrap());
585 let alt_raw = i32::from_le_bytes(data[29..33].try_into().unwrap());
586 let vn_raw = i32::from_le_bytes(data[33..37].try_into().unwrap());
587 let ve_raw = i32::from_le_bytes(data[37..41].try_into().unwrap());
588 let vu_raw = i32::from_le_bytes(data[41..45].try_into().unwrap());
589 let ax_raw = i16::from_le_bytes(data[45..47].try_into().unwrap());
590 let ay_raw = i16::from_le_bytes(data[47..49].try_into().unwrap());
591 let az_raw = i16::from_le_bytes(data[49..51].try_into().unwrap());
592 let heading_raw = u16::from_le_bytes([data[51], data[52]]);
593 let pitch_raw = i16::from_le_bytes(data[53..55].try_into().unwrap());
594 let roll_raw = i16::from_le_bytes(data[55..57].try_into().unwrap());
595
596 Ok(Self {
597 tow_ms: header.tow_ms,
598 wnc: header.wnc,
599 mode,
600 error,
601 info,
602 gnss_pvt_mode,
603 datum,
604 gnss_age_raw,
605 nr_sv_ant,
606 pos_fine,
607 lat_raw,
608 long_raw,
609 alt_raw,
610 vn_raw,
611 ve_raw,
612 vu_raw,
613 ax_raw,
614 ay_raw,
615 az_raw,
616 heading_raw,
617 pitch_raw,
618 roll_raw,
619 })
620 }
621}
622
623#[derive(Debug, Clone)]
631#[allow(dead_code)]
632pub struct IntAttEulerBlock {
633 tow_ms: u32,
634 wnc: u16,
635 mode: u8,
636 error: u8,
637 info: u16,
638 nr_sv: u8,
639 nr_ant: u8,
640 datum: u8,
641 gnss_age_raw: u16,
642 heading_deg: f32,
643 pitch_deg: f32,
644 roll_deg: f32,
645 pitch_dot_dps: f32,
646 roll_dot_dps: f32,
647 heading_dot_dps: f32,
648}
649
650impl IntAttEulerBlock {
651 pub fn tow_seconds(&self) -> f64 {
652 self.tow_ms as f64 * 0.001
653 }
654 pub fn tow_ms(&self) -> u32 {
655 self.tow_ms
656 }
657 pub fn wnc(&self) -> u16 {
658 self.wnc
659 }
660 pub fn mode(&self) -> PvtMode {
661 PvtMode::from_mode_byte(self.mode)
662 }
663 pub fn mode_raw(&self) -> u8 {
664 self.mode
665 }
666 pub fn error(&self) -> PvtError {
667 PvtError::from_error_byte(self.error)
668 }
669 pub fn error_raw(&self) -> u8 {
670 self.error
671 }
672 pub fn info(&self) -> u16 {
673 self.info
674 }
675 pub fn nr_sv(&self) -> u8 {
676 u8_or_none(self.nr_sv).unwrap_or(0)
677 }
678 pub fn nr_sv_opt(&self) -> Option<u8> {
679 u8_or_none(self.nr_sv)
680 }
681 pub fn nr_sv_raw(&self) -> u8 {
682 self.nr_sv
683 }
684 pub fn nr_ant(&self) -> u8 {
685 self.nr_ant
686 }
687 pub fn datum(&self) -> u8 {
688 self.datum
689 }
690 pub fn gnss_age_seconds(&self) -> Option<f32> {
691 if self.gnss_age_raw == U16_DNU {
692 None
693 } else {
694 Some(self.gnss_age_raw as f32 * 0.01)
695 }
696 }
697 pub fn gnss_age_raw(&self) -> u16 {
698 self.gnss_age_raw
699 }
700 pub fn heading_deg(&self) -> Option<f32> {
701 if self.heading_deg == F32_DNU {
702 None
703 } else {
704 Some(self.heading_deg)
705 }
706 }
707 pub fn pitch_deg(&self) -> Option<f32> {
708 if self.pitch_deg == F32_DNU {
709 None
710 } else {
711 Some(self.pitch_deg)
712 }
713 }
714 pub fn roll_deg(&self) -> Option<f32> {
715 if self.roll_deg == F32_DNU {
716 None
717 } else {
718 Some(self.roll_deg)
719 }
720 }
721 pub fn pitch_rate_dps(&self) -> Option<f32> {
722 if self.pitch_dot_dps == F32_DNU {
723 None
724 } else {
725 Some(self.pitch_dot_dps)
726 }
727 }
728 pub fn roll_rate_dps(&self) -> Option<f32> {
729 if self.roll_dot_dps == F32_DNU {
730 None
731 } else {
732 Some(self.roll_dot_dps)
733 }
734 }
735 pub fn heading_rate_dps(&self) -> Option<f32> {
736 if self.heading_dot_dps == F32_DNU {
737 None
738 } else {
739 Some(self.heading_dot_dps)
740 }
741 }
742}
743
744impl SbfBlockParse for IntAttEulerBlock {
745 const BLOCK_ID: u16 = block_ids::INT_ATT_EULER;
746
747 fn parse(header: &SbfHeader, data: &[u8]) -> SbfResult<Self> {
748 const MIN_LEN: usize = 45;
749 if data.len() < MIN_LEN {
750 return Err(SbfError::ParseError("IntAttEuler too short".into()));
751 }
752
753 let mode = data[12];
754 let error = data[13];
755 let info = u16::from_le_bytes([data[14], data[15]]);
756 let nr_sv = data[16];
757 let nr_ant = data[17];
758 let datum = data[18];
759 let gnss_age_raw = u16::from_le_bytes([data[19], data[20]]);
760 let heading_deg = f32::from_le_bytes(data[21..25].try_into().unwrap());
761 let pitch_deg = f32::from_le_bytes(data[25..29].try_into().unwrap());
762 let roll_deg = f32::from_le_bytes(data[29..33].try_into().unwrap());
763 let pitch_dot_dps = f32::from_le_bytes(data[33..37].try_into().unwrap());
764 let roll_dot_dps = f32::from_le_bytes(data[37..41].try_into().unwrap());
765 let heading_dot_dps = f32::from_le_bytes(data[41..45].try_into().unwrap());
766
767 Ok(Self {
768 tow_ms: header.tow_ms,
769 wnc: header.wnc,
770 mode,
771 error,
772 info,
773 nr_sv,
774 nr_ant,
775 datum,
776 gnss_age_raw,
777 heading_deg,
778 pitch_deg,
779 roll_deg,
780 pitch_dot_dps,
781 roll_dot_dps,
782 heading_dot_dps,
783 })
784 }
785}
786
787#[derive(Debug, Clone)]
795#[allow(dead_code)]
796pub struct IntPosCovCartBlock {
797 tow_ms: u32,
798 wnc: u16,
799 mode: u8,
800 error: u8,
801 cov_xx: f32,
802 cov_yy: f32,
803 cov_zz: f32,
804 cov_xy: f32,
805 cov_xz: f32,
806 cov_yz: f32,
807}
808
809impl IntPosCovCartBlock {
810 pub fn tow_seconds(&self) -> f64 {
811 self.tow_ms as f64 * 0.001
812 }
813 pub fn tow_ms(&self) -> u32 {
814 self.tow_ms
815 }
816 pub fn wnc(&self) -> u16 {
817 self.wnc
818 }
819 pub fn mode(&self) -> PvtMode {
820 PvtMode::from_mode_byte(self.mode)
821 }
822 pub fn mode_raw(&self) -> u8 {
823 self.mode
824 }
825 pub fn error(&self) -> PvtError {
826 PvtError::from_error_byte(self.error)
827 }
828 pub fn error_raw(&self) -> u8 {
829 self.error
830 }
831
832 pub fn cov_xx(&self) -> Option<f32> {
833 if self.cov_xx == F32_DNU {
834 None
835 } else {
836 Some(self.cov_xx)
837 }
838 }
839 pub fn cov_yy(&self) -> Option<f32> {
840 if self.cov_yy == F32_DNU {
841 None
842 } else {
843 Some(self.cov_yy)
844 }
845 }
846 pub fn cov_zz(&self) -> Option<f32> {
847 if self.cov_zz == F32_DNU {
848 None
849 } else {
850 Some(self.cov_zz)
851 }
852 }
853 pub fn cov_xy(&self) -> Option<f32> {
854 if self.cov_xy == F32_DNU {
855 None
856 } else {
857 Some(self.cov_xy)
858 }
859 }
860 pub fn cov_xz(&self) -> Option<f32> {
861 if self.cov_xz == F32_DNU {
862 None
863 } else {
864 Some(self.cov_xz)
865 }
866 }
867 pub fn cov_yz(&self) -> Option<f32> {
868 if self.cov_yz == F32_DNU {
869 None
870 } else {
871 Some(self.cov_yz)
872 }
873 }
874
875 pub fn x_std_m(&self) -> Option<f32> {
876 if self.cov_xx == F32_DNU || self.cov_xx < 0.0 {
877 None
878 } else {
879 Some(self.cov_xx.sqrt())
880 }
881 }
882 pub fn y_std_m(&self) -> Option<f32> {
883 if self.cov_yy == F32_DNU || self.cov_yy < 0.0 {
884 None
885 } else {
886 Some(self.cov_yy.sqrt())
887 }
888 }
889 pub fn z_std_m(&self) -> Option<f32> {
890 if self.cov_zz == F32_DNU || self.cov_zz < 0.0 {
891 None
892 } else {
893 Some(self.cov_zz.sqrt())
894 }
895 }
896}
897
898#[derive(Debug, Clone)]
906#[allow(dead_code)]
907pub struct IntVelCovCartBlock {
908 tow_ms: u32,
909 wnc: u16,
910 mode: u8,
911 error: u8,
912 cov_vx_vx: f32,
913 cov_vy_vy: f32,
914 cov_vz_vz: f32,
915 cov_vx_vy: f32,
916 cov_vx_vz: f32,
917 cov_vy_vz: f32,
918}
919
920impl IntVelCovCartBlock {
921 pub fn tow_seconds(&self) -> f64 {
922 self.tow_ms as f64 * 0.001
923 }
924 pub fn tow_ms(&self) -> u32 {
925 self.tow_ms
926 }
927 pub fn wnc(&self) -> u16 {
928 self.wnc
929 }
930 pub fn mode(&self) -> PvtMode {
931 PvtMode::from_mode_byte(self.mode)
932 }
933 pub fn mode_raw(&self) -> u8 {
934 self.mode
935 }
936 pub fn error(&self) -> PvtError {
937 PvtError::from_error_byte(self.error)
938 }
939
940 pub fn cov_vx_vx(&self) -> Option<f32> {
941 if self.cov_vx_vx == F32_DNU {
942 None
943 } else {
944 Some(self.cov_vx_vx)
945 }
946 }
947 pub fn cov_vy_vy(&self) -> Option<f32> {
948 if self.cov_vy_vy == F32_DNU {
949 None
950 } else {
951 Some(self.cov_vy_vy)
952 }
953 }
954 pub fn cov_vz_vz(&self) -> Option<f32> {
955 if self.cov_vz_vz == F32_DNU {
956 None
957 } else {
958 Some(self.cov_vz_vz)
959 }
960 }
961 pub fn cov_vx_vy(&self) -> Option<f32> {
962 if self.cov_vx_vy == F32_DNU {
963 None
964 } else {
965 Some(self.cov_vx_vy)
966 }
967 }
968 pub fn cov_vx_vz(&self) -> Option<f32> {
969 if self.cov_vx_vz == F32_DNU {
970 None
971 } else {
972 Some(self.cov_vx_vz)
973 }
974 }
975 pub fn cov_vy_vz(&self) -> Option<f32> {
976 if self.cov_vy_vz == F32_DNU {
977 None
978 } else {
979 Some(self.cov_vy_vz)
980 }
981 }
982
983 pub fn vx_std_mps(&self) -> Option<f32> {
984 if self.cov_vx_vx == F32_DNU || self.cov_vx_vx < 0.0 {
985 None
986 } else {
987 Some(self.cov_vx_vx.sqrt())
988 }
989 }
990 pub fn vy_std_mps(&self) -> Option<f32> {
991 if self.cov_vy_vy == F32_DNU || self.cov_vy_vy < 0.0 {
992 None
993 } else {
994 Some(self.cov_vy_vy.sqrt())
995 }
996 }
997 pub fn vz_std_mps(&self) -> Option<f32> {
998 if self.cov_vz_vz == F32_DNU || self.cov_vz_vz < 0.0 {
999 None
1000 } else {
1001 Some(self.cov_vz_vz.sqrt())
1002 }
1003 }
1004}
1005
1006impl SbfBlockParse for IntVelCovCartBlock {
1007 const BLOCK_ID: u16 = block_ids::INT_VEL_COV_CART;
1008
1009 fn parse(header: &SbfHeader, data: &[u8]) -> SbfResult<Self> {
1010 const MIN_LEN: usize = 38;
1011 if data.len() < MIN_LEN {
1012 return Err(SbfError::ParseError("IntVelCovCart too short".into()));
1013 }
1014
1015 let mode = data[12];
1016 let error = data[13];
1017 let cov_vx_vx = f32::from_le_bytes(data[14..18].try_into().unwrap());
1018 let cov_vy_vy = f32::from_le_bytes(data[18..22].try_into().unwrap());
1019 let cov_vz_vz = f32::from_le_bytes(data[22..26].try_into().unwrap());
1020 let cov_vx_vy = f32::from_le_bytes(data[26..30].try_into().unwrap());
1021 let cov_vx_vz = f32::from_le_bytes(data[30..34].try_into().unwrap());
1022 let cov_vy_vz = f32::from_le_bytes(data[34..38].try_into().unwrap());
1023
1024 Ok(Self {
1025 tow_ms: header.tow_ms,
1026 wnc: header.wnc,
1027 mode,
1028 error,
1029 cov_vx_vx,
1030 cov_vy_vy,
1031 cov_vz_vz,
1032 cov_vx_vy,
1033 cov_vx_vz,
1034 cov_vy_vz,
1035 })
1036 }
1037}
1038
1039#[derive(Debug, Clone)]
1047#[allow(dead_code)]
1048pub struct IntPosCovGeodBlock {
1049 tow_ms: u32,
1050 wnc: u16,
1051 mode: u8,
1052 error: u8,
1053 cov_lat_lat: f32,
1054 cov_lon_lon: f32,
1055 cov_alt_alt: f32,
1056 cov_lat_lon: f32,
1057 cov_lat_alt: f32,
1058 cov_lon_alt: f32,
1059}
1060
1061impl IntPosCovGeodBlock {
1062 pub fn tow_seconds(&self) -> f64 {
1063 self.tow_ms as f64 * 0.001
1064 }
1065 pub fn tow_ms(&self) -> u32 {
1066 self.tow_ms
1067 }
1068 pub fn wnc(&self) -> u16 {
1069 self.wnc
1070 }
1071 pub fn mode(&self) -> PvtMode {
1072 PvtMode::from_mode_byte(self.mode)
1073 }
1074 pub fn mode_raw(&self) -> u8 {
1075 self.mode
1076 }
1077 pub fn error(&self) -> PvtError {
1078 PvtError::from_error_byte(self.error)
1079 }
1080
1081 pub fn cov_lat_lat(&self) -> Option<f32> {
1082 if self.cov_lat_lat == F32_DNU {
1083 None
1084 } else {
1085 Some(self.cov_lat_lat)
1086 }
1087 }
1088 pub fn cov_lon_lon(&self) -> Option<f32> {
1089 if self.cov_lon_lon == F32_DNU {
1090 None
1091 } else {
1092 Some(self.cov_lon_lon)
1093 }
1094 }
1095 pub fn cov_alt_alt(&self) -> Option<f32> {
1096 if self.cov_alt_alt == F32_DNU {
1097 None
1098 } else {
1099 Some(self.cov_alt_alt)
1100 }
1101 }
1102 pub fn cov_lat_lon(&self) -> Option<f32> {
1103 if self.cov_lat_lon == F32_DNU {
1104 None
1105 } else {
1106 Some(self.cov_lat_lon)
1107 }
1108 }
1109 pub fn cov_lat_alt(&self) -> Option<f32> {
1110 if self.cov_lat_alt == F32_DNU {
1111 None
1112 } else {
1113 Some(self.cov_lat_alt)
1114 }
1115 }
1116 pub fn cov_lon_alt(&self) -> Option<f32> {
1117 if self.cov_lon_alt == F32_DNU {
1118 None
1119 } else {
1120 Some(self.cov_lon_alt)
1121 }
1122 }
1123
1124 pub fn lat_std_m(&self) -> Option<f32> {
1125 if self.cov_lat_lat == F32_DNU || self.cov_lat_lat < 0.0 {
1126 None
1127 } else {
1128 Some(self.cov_lat_lat.sqrt())
1129 }
1130 }
1131 pub fn lon_std_m(&self) -> Option<f32> {
1132 if self.cov_lon_lon == F32_DNU || self.cov_lon_lon < 0.0 {
1133 None
1134 } else {
1135 Some(self.cov_lon_lon.sqrt())
1136 }
1137 }
1138 pub fn alt_std_m(&self) -> Option<f32> {
1139 if self.cov_alt_alt == F32_DNU || self.cov_alt_alt < 0.0 {
1140 None
1141 } else {
1142 Some(self.cov_alt_alt.sqrt())
1143 }
1144 }
1145}
1146
1147impl SbfBlockParse for IntPosCovGeodBlock {
1148 const BLOCK_ID: u16 = block_ids::INT_POS_COV_GEOD;
1149
1150 fn parse(header: &SbfHeader, data: &[u8]) -> SbfResult<Self> {
1151 const MIN_LEN: usize = 38;
1152 if data.len() < MIN_LEN {
1153 return Err(SbfError::ParseError("IntPosCovGeod too short".into()));
1154 }
1155
1156 let mode = data[12];
1157 let error = data[13];
1158 let cov_lat_lat = f32::from_le_bytes(data[14..18].try_into().unwrap());
1159 let cov_lon_lon = f32::from_le_bytes(data[18..22].try_into().unwrap());
1160 let cov_alt_alt = f32::from_le_bytes(data[22..26].try_into().unwrap());
1161 let cov_lat_lon = f32::from_le_bytes(data[26..30].try_into().unwrap());
1162 let cov_lat_alt = f32::from_le_bytes(data[30..34].try_into().unwrap());
1163 let cov_lon_alt = f32::from_le_bytes(data[34..38].try_into().unwrap());
1164
1165 Ok(Self {
1166 tow_ms: header.tow_ms,
1167 wnc: header.wnc,
1168 mode,
1169 error,
1170 cov_lat_lat,
1171 cov_lon_lon,
1172 cov_alt_alt,
1173 cov_lat_lon,
1174 cov_lat_alt,
1175 cov_lon_alt,
1176 })
1177 }
1178}
1179
1180#[derive(Debug, Clone)]
1188#[allow(dead_code)]
1189pub struct IntVelCovGeodBlock {
1190 tow_ms: u32,
1191 wnc: u16,
1192 mode: u8,
1193 error: u8,
1194 cov_vn_vn: f32,
1195 cov_ve_ve: f32,
1196 cov_vu_vu: f32,
1197 cov_vn_ve: f32,
1198 cov_vn_vu: f32,
1199 cov_ve_vu: f32,
1200}
1201
1202impl IntVelCovGeodBlock {
1203 pub fn tow_seconds(&self) -> f64 {
1204 self.tow_ms as f64 * 0.001
1205 }
1206 pub fn tow_ms(&self) -> u32 {
1207 self.tow_ms
1208 }
1209 pub fn wnc(&self) -> u16 {
1210 self.wnc
1211 }
1212 pub fn mode(&self) -> PvtMode {
1213 PvtMode::from_mode_byte(self.mode)
1214 }
1215 pub fn mode_raw(&self) -> u8 {
1216 self.mode
1217 }
1218 pub fn error(&self) -> PvtError {
1219 PvtError::from_error_byte(self.error)
1220 }
1221
1222 pub fn cov_vn_vn(&self) -> Option<f32> {
1223 if self.cov_vn_vn == F32_DNU {
1224 None
1225 } else {
1226 Some(self.cov_vn_vn)
1227 }
1228 }
1229 pub fn cov_ve_ve(&self) -> Option<f32> {
1230 if self.cov_ve_ve == F32_DNU {
1231 None
1232 } else {
1233 Some(self.cov_ve_ve)
1234 }
1235 }
1236 pub fn cov_vu_vu(&self) -> Option<f32> {
1237 if self.cov_vu_vu == F32_DNU {
1238 None
1239 } else {
1240 Some(self.cov_vu_vu)
1241 }
1242 }
1243 pub fn cov_vn_ve(&self) -> Option<f32> {
1244 if self.cov_vn_ve == F32_DNU {
1245 None
1246 } else {
1247 Some(self.cov_vn_ve)
1248 }
1249 }
1250 pub fn cov_vn_vu(&self) -> Option<f32> {
1251 if self.cov_vn_vu == F32_DNU {
1252 None
1253 } else {
1254 Some(self.cov_vn_vu)
1255 }
1256 }
1257 pub fn cov_ve_vu(&self) -> Option<f32> {
1258 if self.cov_ve_vu == F32_DNU {
1259 None
1260 } else {
1261 Some(self.cov_ve_vu)
1262 }
1263 }
1264
1265 pub fn vn_std_mps(&self) -> Option<f32> {
1266 if self.cov_vn_vn == F32_DNU || self.cov_vn_vn < 0.0 {
1267 None
1268 } else {
1269 Some(self.cov_vn_vn.sqrt())
1270 }
1271 }
1272 pub fn ve_std_mps(&self) -> Option<f32> {
1273 if self.cov_ve_ve == F32_DNU || self.cov_ve_ve < 0.0 {
1274 None
1275 } else {
1276 Some(self.cov_ve_ve.sqrt())
1277 }
1278 }
1279 pub fn vu_std_mps(&self) -> Option<f32> {
1280 if self.cov_vu_vu == F32_DNU || self.cov_vu_vu < 0.0 {
1281 None
1282 } else {
1283 Some(self.cov_vu_vu.sqrt())
1284 }
1285 }
1286}
1287
1288impl SbfBlockParse for IntVelCovGeodBlock {
1289 const BLOCK_ID: u16 = block_ids::INT_VEL_COV_GEOD;
1290
1291 fn parse(header: &SbfHeader, data: &[u8]) -> SbfResult<Self> {
1292 const MIN_LEN: usize = 38;
1293 if data.len() < MIN_LEN {
1294 return Err(SbfError::ParseError("IntVelCovGeod too short".into()));
1295 }
1296
1297 let mode = data[12];
1298 let error = data[13];
1299 let cov_vn_vn = f32::from_le_bytes(data[14..18].try_into().unwrap());
1300 let cov_ve_ve = f32::from_le_bytes(data[18..22].try_into().unwrap());
1301 let cov_vu_vu = f32::from_le_bytes(data[22..26].try_into().unwrap());
1302 let cov_vn_ve = f32::from_le_bytes(data[26..30].try_into().unwrap());
1303 let cov_vn_vu = f32::from_le_bytes(data[30..34].try_into().unwrap());
1304 let cov_ve_vu = f32::from_le_bytes(data[34..38].try_into().unwrap());
1305
1306 Ok(Self {
1307 tow_ms: header.tow_ms,
1308 wnc: header.wnc,
1309 mode,
1310 error,
1311 cov_vn_vn,
1312 cov_ve_ve,
1313 cov_vu_vu,
1314 cov_vn_ve,
1315 cov_vn_vu,
1316 cov_ve_vu,
1317 })
1318 }
1319}
1320
1321#[derive(Debug, Clone)]
1329#[allow(dead_code)]
1330pub struct IntAttCovEulerBlock {
1331 tow_ms: u32,
1332 wnc: u16,
1333 mode: u8,
1334 error: u8,
1335 cov_head_head: f32,
1336 cov_pitch_pitch: f32,
1337 cov_roll_roll: f32,
1338 cov_head_pitch: f32,
1339 cov_head_roll: f32,
1340 cov_pitch_roll: f32,
1341}
1342
1343impl IntAttCovEulerBlock {
1344 pub fn tow_seconds(&self) -> f64 {
1345 self.tow_ms as f64 * 0.001
1346 }
1347 pub fn tow_ms(&self) -> u32 {
1348 self.tow_ms
1349 }
1350 pub fn wnc(&self) -> u16 {
1351 self.wnc
1352 }
1353 pub fn mode(&self) -> PvtMode {
1354 PvtMode::from_mode_byte(self.mode)
1355 }
1356 pub fn mode_raw(&self) -> u8 {
1357 self.mode
1358 }
1359 pub fn error(&self) -> PvtError {
1360 PvtError::from_error_byte(self.error)
1361 }
1362
1363 pub fn cov_head_head(&self) -> Option<f32> {
1364 if self.cov_head_head == F32_DNU {
1365 None
1366 } else {
1367 Some(self.cov_head_head)
1368 }
1369 }
1370 pub fn cov_pitch_pitch(&self) -> Option<f32> {
1371 if self.cov_pitch_pitch == F32_DNU {
1372 None
1373 } else {
1374 Some(self.cov_pitch_pitch)
1375 }
1376 }
1377 pub fn cov_roll_roll(&self) -> Option<f32> {
1378 if self.cov_roll_roll == F32_DNU {
1379 None
1380 } else {
1381 Some(self.cov_roll_roll)
1382 }
1383 }
1384 pub fn cov_head_pitch(&self) -> Option<f32> {
1385 if self.cov_head_pitch == F32_DNU {
1386 None
1387 } else {
1388 Some(self.cov_head_pitch)
1389 }
1390 }
1391 pub fn cov_head_roll(&self) -> Option<f32> {
1392 if self.cov_head_roll == F32_DNU {
1393 None
1394 } else {
1395 Some(self.cov_head_roll)
1396 }
1397 }
1398 pub fn cov_pitch_roll(&self) -> Option<f32> {
1399 if self.cov_pitch_roll == F32_DNU {
1400 None
1401 } else {
1402 Some(self.cov_pitch_roll)
1403 }
1404 }
1405
1406 pub fn heading_std_deg(&self) -> Option<f32> {
1407 if self.cov_head_head == F32_DNU || self.cov_head_head < 0.0 {
1408 None
1409 } else {
1410 Some(self.cov_head_head.sqrt())
1411 }
1412 }
1413 pub fn pitch_std_deg(&self) -> Option<f32> {
1414 if self.cov_pitch_pitch == F32_DNU || self.cov_pitch_pitch < 0.0 {
1415 None
1416 } else {
1417 Some(self.cov_pitch_pitch.sqrt())
1418 }
1419 }
1420 pub fn roll_std_deg(&self) -> Option<f32> {
1421 if self.cov_roll_roll == F32_DNU || self.cov_roll_roll < 0.0 {
1422 None
1423 } else {
1424 Some(self.cov_roll_roll.sqrt())
1425 }
1426 }
1427}
1428
1429impl SbfBlockParse for IntAttCovEulerBlock {
1430 const BLOCK_ID: u16 = block_ids::INT_ATT_COV_EULER;
1431
1432 fn parse(header: &SbfHeader, data: &[u8]) -> SbfResult<Self> {
1433 const MIN_LEN: usize = 38;
1434 if data.len() < MIN_LEN {
1435 return Err(SbfError::ParseError("IntAttCovEuler too short".into()));
1436 }
1437
1438 let mode = data[12];
1439 let error = data[13];
1440 let cov_head_head = f32::from_le_bytes(data[14..18].try_into().unwrap());
1441 let cov_pitch_pitch = f32::from_le_bytes(data[18..22].try_into().unwrap());
1442 let cov_roll_roll = f32::from_le_bytes(data[22..26].try_into().unwrap());
1443 let cov_head_pitch = f32::from_le_bytes(data[26..30].try_into().unwrap());
1444 let cov_head_roll = f32::from_le_bytes(data[30..34].try_into().unwrap());
1445 let cov_pitch_roll = f32::from_le_bytes(data[34..38].try_into().unwrap());
1446
1447 Ok(Self {
1448 tow_ms: header.tow_ms,
1449 wnc: header.wnc,
1450 mode,
1451 error,
1452 cov_head_head,
1453 cov_pitch_pitch,
1454 cov_roll_roll,
1455 cov_head_pitch,
1456 cov_head_roll,
1457 cov_pitch_roll,
1458 })
1459 }
1460}
1461
1462impl SbfBlockParse for IntPosCovCartBlock {
1463 const BLOCK_ID: u16 = block_ids::INT_POS_COV_CART;
1464
1465 fn parse(header: &SbfHeader, data: &[u8]) -> SbfResult<Self> {
1466 const MIN_LEN: usize = 38;
1467 if data.len() < MIN_LEN {
1468 return Err(SbfError::ParseError("IntPosCovCart too short".into()));
1469 }
1470
1471 let mode = data[12];
1472 let error = data[13];
1473 let cov_xx = f32::from_le_bytes(data[14..18].try_into().unwrap());
1474 let cov_yy = f32::from_le_bytes(data[18..22].try_into().unwrap());
1475 let cov_zz = f32::from_le_bytes(data[22..26].try_into().unwrap());
1476 let cov_xy = f32::from_le_bytes(data[26..30].try_into().unwrap());
1477 let cov_xz = f32::from_le_bytes(data[30..34].try_into().unwrap());
1478 let cov_yz = f32::from_le_bytes(data[34..38].try_into().unwrap());
1479
1480 Ok(Self {
1481 tow_ms: header.tow_ms,
1482 wnc: header.wnc,
1483 mode,
1484 error,
1485 cov_xx,
1486 cov_yy,
1487 cov_zz,
1488 cov_xy,
1489 cov_xz,
1490 cov_yz,
1491 })
1492 }
1493}
1494
1495#[cfg(test)]
1496mod tests {
1497 use super::*;
1498 use crate::header::SbfHeader;
1499
1500 fn header_for(block_id: u16, tow_ms: u32, wnc: u16) -> SbfHeader {
1501 SbfHeader {
1502 crc: 0,
1503 block_id,
1504 block_rev: 0,
1505 length: 70,
1506 tow_ms,
1507 wnc,
1508 }
1509 }
1510
1511 #[test]
1512 fn test_int_pv_cart_parse() {
1513 let mut data = vec![0u8; 70];
1514 data[0..6].copy_from_slice(&[0, 0, 0, 0, 0, 0]);
1515 data[6..10].copy_from_slice(&1000u32.to_le_bytes());
1516 data[10..12].copy_from_slice(&2000u16.to_le_bytes());
1517 data[12] = 4; data[13] = 0; data[14..16].copy_from_slice(&0u16.to_le_bytes());
1520 data[16] = 12; data[17] = 1; data[18] = 4;
1523 data[19] = 0;
1524 data[20..22].copy_from_slice(&100u16.to_le_bytes()); data[22..30].copy_from_slice(&1234567.0f64.to_le_bytes());
1527 data[30..38].copy_from_slice(&2345678.0f64.to_le_bytes());
1528 data[38..46].copy_from_slice(&3456789.0f64.to_le_bytes());
1529 data[46..50].copy_from_slice(&0.1f32.to_le_bytes());
1530 data[50..54].copy_from_slice(&0.2f32.to_le_bytes());
1531 data[54..58].copy_from_slice(&0.3f32.to_le_bytes());
1532 data[58..62].copy_from_slice(&45.0f32.to_le_bytes());
1533
1534 let header = header_for(block_ids::INT_PV_CART, 1000, 2000);
1535 let block = IntPvCartBlock::parse(&header, &data).unwrap();
1536 assert_eq!(block.tow_seconds(), 1.0);
1537 assert_eq!(block.wnc(), 2000);
1538 assert_eq!(block.nr_sv(), 12);
1539 assert_eq!(block.nr_sv_opt(), Some(12));
1540 assert_eq!(block.nr_sv_raw(), 12);
1541 assert_eq!(block.gnss_age_seconds(), Some(1.0));
1542 }
1543
1544 #[test]
1545 fn test_int_pv_cart_dnu() {
1546 let mut data = vec![0u8; 70];
1547 data[6..10].copy_from_slice(&1000u32.to_le_bytes());
1548 data[10..12].copy_from_slice(&2000u16.to_le_bytes());
1549 data[16] = 255;
1550 data[20..22].copy_from_slice(&U16_DNU.to_le_bytes());
1551 data[22..30].copy_from_slice(&F64_DNU.to_le_bytes());
1552 data[46..50].copy_from_slice(&F32_DNU.to_le_bytes());
1553
1554 let header = header_for(block_ids::INT_PV_CART, 1000, 2000);
1555 let block = IntPvCartBlock::parse(&header, &data).unwrap();
1556 assert_eq!(block.nr_sv_raw(), 255);
1557 assert_eq!(block.nr_sv_opt(), None);
1558 assert_eq!(block.nr_sv(), 0);
1559 assert!(block.gnss_age_seconds().is_none());
1560 assert!(block.x_m().is_none());
1561 assert!(block.velocity_x_mps().is_none());
1562 }
1563
1564 #[test]
1565 fn test_int_pv_geod_parse() {
1566 let mut data = vec![0u8; 70];
1567 data[6..10].copy_from_slice(&2000u32.to_le_bytes());
1568 data[10..12].copy_from_slice(&2100u16.to_le_bytes());
1569 data[12] = 4;
1570 data[13] = 0;
1571 data[14..16].copy_from_slice(&0u16.to_le_bytes());
1572 data[16] = 10;
1573 data[17] = 1;
1574 data[18] = 4;
1575 data[19] = 0;
1576 data[20..22].copy_from_slice(&50u16.to_le_bytes());
1577 let lat_rad = 0.5f64;
1578 let long_rad = 0.3f64;
1579 data[22..30].copy_from_slice(&lat_rad.to_le_bytes());
1580 data[30..38].copy_from_slice(&long_rad.to_le_bytes());
1581 data[38..46].copy_from_slice(&100.0f64.to_le_bytes());
1582 data[46..50].copy_from_slice(&0.5f32.to_le_bytes());
1583 data[50..54].copy_from_slice(&0.2f32.to_le_bytes());
1584 data[54..58].copy_from_slice(&0.1f32.to_le_bytes());
1585 data[58..62].copy_from_slice(&90.0f32.to_le_bytes());
1586
1587 let header = header_for(block_ids::INT_PV_GEOD, 2000, 2100);
1588 let block = IntPvGeodBlock::parse(&header, &data).unwrap();
1589 assert_eq!(block.tow_seconds(), 2.0);
1590 assert_eq!(block.wnc(), 2100);
1591 assert_eq!(block.nr_sv(), 10);
1592 assert_eq!(block.nr_sv_opt(), Some(10));
1593 assert_eq!(block.nr_sv_raw(), 10);
1594 assert_eq!(block.gnss_age_seconds(), Some(0.5));
1595 assert!((block.latitude_deg().unwrap() - lat_rad.to_degrees()).abs() < 1e-6);
1596 assert!((block.longitude_deg().unwrap() - long_rad.to_degrees()).abs() < 1e-6);
1597 assert_eq!(block.altitude_m(), Some(100.0));
1598 assert_eq!(block.course_over_ground_deg(), Some(90.0));
1599 }
1600
1601 #[test]
1602 fn test_int_pvaa_geod_parse() {
1603 let mut data = vec![0u8; 72];
1604 data[6..10].copy_from_slice(&3000u32.to_le_bytes());
1605 data[10..12].copy_from_slice(&2200u16.to_le_bytes());
1606 data[12] = 4; data[13] = 0; data[14..16].copy_from_slice(&0u16.to_le_bytes());
1609 data[16] = 4;
1610 data[17] = 0;
1611 data[18] = 5; data[19] = 0x81; data[20] = 0; data[21..25].copy_from_slice(&(-338700000i32).to_le_bytes());
1616 data[25..29].copy_from_slice(&1512100000i32.to_le_bytes());
1618 data[29..33].copy_from_slice(&50500i32.to_le_bytes());
1620 data[33..37].copy_from_slice(&1500i32.to_le_bytes());
1622 data[37..41].copy_from_slice(&200i32.to_le_bytes());
1623 data[41..45].copy_from_slice(&100i32.to_le_bytes());
1624 data[45..47].copy_from_slice(&10i16.to_le_bytes());
1626 data[47..49].copy_from_slice(&0i16.to_le_bytes());
1627 data[49..51].copy_from_slice(&980i16.to_le_bytes());
1628 data[51..53].copy_from_slice(&4500u16.to_le_bytes());
1630 data[53..55].copy_from_slice(&200i16.to_le_bytes());
1632 data[55..57].copy_from_slice(&(-100i16).to_le_bytes());
1633
1634 let header = SbfHeader {
1635 crc: 0,
1636 block_id: block_ids::INT_PVA_AGEOD,
1637 block_rev: 0,
1638 length: 72,
1639 tow_ms: 3000,
1640 wnc: 2200,
1641 };
1642 let block = IntPvaaGeodBlock::parse(&header, &data).unwrap();
1643 assert_eq!(block.tow_seconds(), 3.0);
1644 assert_eq!(block.wnc(), 2200);
1645 assert_eq!(block.nr_sv(), 8);
1646 assert_eq!(block.nr_ant(), 1);
1647 assert_eq!(block.gnss_age_seconds(), Some(0.5));
1648 assert!((block.latitude_deg().unwrap() - (-33.87)).abs() < 1e-6);
1649 assert!((block.longitude_deg().unwrap() - 151.21).abs() < 1e-6);
1650 assert!((block.altitude_m().unwrap() - 50.5).abs() < 1e-6);
1651 assert!((block.velocity_north_mps().unwrap() - 1.5).abs() < 1e-6);
1652 assert!((block.velocity_east_mps().unwrap() - 0.2).abs() < 1e-6);
1653 assert!((block.velocity_up_mps().unwrap() - 0.1).abs() < 1e-6);
1654 assert!((block.acceleration_z_mps2().unwrap() - 9.8).abs() < 0.01);
1655 assert!((block.heading_deg().unwrap() - 45.0).abs() < 1e-6);
1656 assert!((block.pitch_deg().unwrap() - 2.0).abs() < 1e-6);
1657 assert!((block.roll_deg().unwrap() - (-1.0)).abs() < 1e-6);
1658 }
1659
1660 #[test]
1661 fn test_int_pvaa_geod_dnu() {
1662 let mut data = vec![0u8; 72];
1663 data[6..10].copy_from_slice(&1000u32.to_le_bytes());
1664 data[10..12].copy_from_slice(&2000u16.to_le_bytes());
1665 data[18] = 255; data[21..25].copy_from_slice(&I32_DNU.to_le_bytes());
1667 data[45..47].copy_from_slice(&I16_DNU.to_le_bytes());
1668 data[51..53].copy_from_slice(&U16_DNU.to_le_bytes());
1669
1670 let header = SbfHeader {
1671 crc: 0,
1672 block_id: block_ids::INT_PVA_AGEOD,
1673 block_rev: 0,
1674 length: 72,
1675 tow_ms: 1000,
1676 wnc: 2000,
1677 };
1678 let block = IntPvaaGeodBlock::parse(&header, &data).unwrap();
1679 assert!(block.gnss_age_seconds().is_none());
1680 assert!(block.latitude_deg().is_none());
1681 assert!(block.acceleration_x_mps2().is_none());
1682 assert!(block.heading_deg().is_none());
1683 }
1684
1685 #[test]
1686 fn test_int_pv_geod_dnu() {
1687 let mut data = vec![0u8; 70];
1688 data[6..10].copy_from_slice(&2000u32.to_le_bytes());
1689 data[10..12].copy_from_slice(&2100u16.to_le_bytes());
1690 data[16] = 255;
1691 data[20..22].copy_from_slice(&U16_DNU.to_le_bytes());
1692 data[22..30].copy_from_slice(&F64_DNU.to_le_bytes());
1693 data[46..50].copy_from_slice(&F32_DNU.to_le_bytes());
1694
1695 let header = header_for(block_ids::INT_PV_GEOD, 2000, 2100);
1696 let block = IntPvGeodBlock::parse(&header, &data).unwrap();
1697 assert_eq!(block.nr_sv_raw(), 255);
1698 assert_eq!(block.nr_sv_opt(), None);
1699 assert_eq!(block.nr_sv(), 0);
1700 assert!(block.gnss_age_seconds().is_none());
1701 assert!(block.latitude_deg().is_none());
1702 assert!(block.velocity_north_mps().is_none());
1703 }
1704
1705 #[test]
1706 fn test_int_att_euler_parse() {
1707 let mut data = vec![0u8; 60];
1708 data[6..10].copy_from_slice(&3000u32.to_le_bytes());
1709 data[10..12].copy_from_slice(&2200u16.to_le_bytes());
1710 data[12] = 4;
1711 data[13] = 0;
1712 data[14..16].copy_from_slice(&0u16.to_le_bytes());
1713 data[16] = 8;
1714 data[17] = 1;
1715 data[18] = 0;
1716 data[19..21].copy_from_slice(&25u16.to_le_bytes());
1717 data[21..25].copy_from_slice(&180.0f32.to_le_bytes());
1718 data[25..29].copy_from_slice(&5.0f32.to_le_bytes());
1719 data[29..33].copy_from_slice(&(-2.0f32).to_le_bytes());
1720 data[33..37].copy_from_slice(&0.1f32.to_le_bytes());
1721 data[37..41].copy_from_slice(&0.05f32.to_le_bytes());
1722 data[41..45].copy_from_slice(&1.5f32.to_le_bytes());
1723
1724 let header = header_for(block_ids::INT_ATT_EULER, 3000, 2200);
1725 let block = IntAttEulerBlock::parse(&header, &data).unwrap();
1726 assert_eq!(block.tow_seconds(), 3.0);
1727 assert_eq!(block.nr_sv(), 8);
1728 assert_eq!(block.nr_sv_opt(), Some(8));
1729 assert_eq!(block.nr_sv_raw(), 8);
1730 assert_eq!(block.gnss_age_raw(), 25);
1731 assert_eq!(block.gnss_age_seconds(), Some(0.25));
1732 assert_eq!(block.heading_deg(), Some(180.0));
1733 assert_eq!(block.pitch_deg(), Some(5.0));
1734 assert_eq!(block.roll_deg(), Some(-2.0));
1735 assert_eq!(block.heading_rate_dps(), Some(1.5));
1736 }
1737
1738 #[test]
1739 fn test_int_att_euler_dnu() {
1740 let mut data = vec![0u8; 60];
1741 data[6..10].copy_from_slice(&3000u32.to_le_bytes());
1742 data[10..12].copy_from_slice(&2200u16.to_le_bytes());
1743 data[16] = 255;
1744 data[19..21].copy_from_slice(&U16_DNU.to_le_bytes());
1745 data[21..25].copy_from_slice(&F32_DNU.to_le_bytes());
1746
1747 let header = header_for(block_ids::INT_ATT_EULER, 3000, 2200);
1748 let block = IntAttEulerBlock::parse(&header, &data).unwrap();
1749 assert_eq!(block.nr_sv_raw(), 255);
1750 assert_eq!(block.nr_sv_opt(), None);
1751 assert_eq!(block.nr_sv(), 0);
1752 assert_eq!(block.gnss_age_raw(), U16_DNU);
1753 assert!(block.gnss_age_seconds().is_none());
1754 assert!(block.heading_deg().is_none());
1755 }
1756
1757 #[test]
1758 fn test_int_pos_cov_cart_parse() {
1759 let mut data = vec![0u8; 50];
1760 data[6..10].copy_from_slice(&4000u32.to_le_bytes());
1761 data[10..12].copy_from_slice(&2300u16.to_le_bytes());
1762 data[12] = 4;
1763 data[13] = 0;
1764 data[14..18].copy_from_slice(&1.0f32.to_le_bytes());
1765 data[18..22].copy_from_slice(&2.0f32.to_le_bytes());
1766 data[22..26].copy_from_slice(&3.0f32.to_le_bytes());
1767 data[26..30].copy_from_slice(&0.1f32.to_le_bytes());
1768 data[30..34].copy_from_slice(&0.2f32.to_le_bytes());
1769 data[34..38].copy_from_slice(&0.3f32.to_le_bytes());
1770
1771 let header = header_for(block_ids::INT_POS_COV_CART, 4000, 2300);
1772 let block = IntPosCovCartBlock::parse(&header, &data).unwrap();
1773 assert_eq!(block.tow_seconds(), 4.0);
1774 assert_eq!(block.cov_xx(), Some(1.0));
1775 assert_eq!(block.cov_yy(), Some(2.0));
1776 assert_eq!(block.x_std_m(), Some(1.0));
1777 assert!((block.y_std_m().unwrap() - 2.0_f32.sqrt()).abs() < 1e-5);
1778 }
1779
1780 #[test]
1781 fn test_int_pos_cov_cart_dnu() {
1782 let mut data = vec![0u8; 50];
1783 data[6..10].copy_from_slice(&4000u32.to_le_bytes());
1784 data[10..12].copy_from_slice(&2300u16.to_le_bytes());
1785 data[14..18].copy_from_slice(&F32_DNU.to_le_bytes());
1786
1787 let header = header_for(block_ids::INT_POS_COV_CART, 4000, 2300);
1788 let block = IntPosCovCartBlock::parse(&header, &data).unwrap();
1789 assert!(block.cov_xx().is_none());
1790 assert!(block.x_std_m().is_none());
1791 }
1792
1793 #[test]
1794 fn test_int_vel_cov_cart_parse() {
1795 let mut data = vec![0u8; 50];
1796 data[6..10].copy_from_slice(&4100u32.to_le_bytes());
1797 data[10..12].copy_from_slice(&2300u16.to_le_bytes());
1798 data[12] = 4;
1799 data[13] = 0;
1800 data[14..18].copy_from_slice(&0.01f32.to_le_bytes());
1801 data[18..22].copy_from_slice(&0.02f32.to_le_bytes());
1802 data[22..26].copy_from_slice(&0.03f32.to_le_bytes());
1803 data[26..30].copy_from_slice(&0.001f32.to_le_bytes());
1804 data[30..34].copy_from_slice(&0.002f32.to_le_bytes());
1805 data[34..38].copy_from_slice(&0.003f32.to_le_bytes());
1806
1807 let header = header_for(block_ids::INT_VEL_COV_CART, 4100, 2300);
1808 let block = IntVelCovCartBlock::parse(&header, &data).unwrap();
1809 assert_eq!(block.tow_seconds(), 4.1);
1810 assert_eq!(block.cov_vx_vx(), Some(0.01));
1811 assert_eq!(block.vx_std_mps(), Some(0.1));
1812 }
1813
1814 #[test]
1815 fn test_int_vel_cov_cart_dnu() {
1816 let mut data = vec![0u8; 50];
1817 data[6..10].copy_from_slice(&4100u32.to_le_bytes());
1818 data[10..12].copy_from_slice(&2300u16.to_le_bytes());
1819 data[14..18].copy_from_slice(&F32_DNU.to_le_bytes());
1820
1821 let header = header_for(block_ids::INT_VEL_COV_CART, 4100, 2300);
1822 let block = IntVelCovCartBlock::parse(&header, &data).unwrap();
1823 assert!(block.cov_vx_vx().is_none());
1824 assert!(block.vx_std_mps().is_none());
1825 }
1826
1827 #[test]
1828 fn test_int_pos_cov_geod_parse() {
1829 let mut data = vec![0u8; 50];
1830 data[6..10].copy_from_slice(&4200u32.to_le_bytes());
1831 data[10..12].copy_from_slice(&2300u16.to_le_bytes());
1832 data[12] = 4;
1833 data[13] = 0;
1834 data[14..18].copy_from_slice(&1.0f32.to_le_bytes());
1835 data[18..22].copy_from_slice(&2.0f32.to_le_bytes());
1836 data[22..26].copy_from_slice(&3.0f32.to_le_bytes());
1837 data[26..30].copy_from_slice(&0.1f32.to_le_bytes());
1838 data[30..34].copy_from_slice(&0.2f32.to_le_bytes());
1839 data[34..38].copy_from_slice(&0.3f32.to_le_bytes());
1840
1841 let header = header_for(block_ids::INT_POS_COV_GEOD, 4200, 2300);
1842 let block = IntPosCovGeodBlock::parse(&header, &data).unwrap();
1843 assert_eq!(block.tow_seconds(), 4.2);
1844 assert_eq!(block.cov_lat_lat(), Some(1.0));
1845 assert_eq!(block.lat_std_m(), Some(1.0));
1846 }
1847
1848 #[test]
1849 fn test_int_vel_cov_geod_parse() {
1850 let mut data = vec![0u8; 50];
1851 data[6..10].copy_from_slice(&4300u32.to_le_bytes());
1852 data[10..12].copy_from_slice(&2300u16.to_le_bytes());
1853 data[12] = 4;
1854 data[13] = 0;
1855 data[14..18].copy_from_slice(&0.04f32.to_le_bytes());
1856 data[18..22].copy_from_slice(&0.09f32.to_le_bytes());
1857 data[22..26].copy_from_slice(&0.16f32.to_le_bytes());
1858 data[26..30].copy_from_slice(&0.01f32.to_le_bytes());
1859 data[30..34].copy_from_slice(&0.02f32.to_le_bytes());
1860 data[34..38].copy_from_slice(&0.03f32.to_le_bytes());
1861
1862 let header = header_for(block_ids::INT_VEL_COV_GEOD, 4300, 2300);
1863 let block = IntVelCovGeodBlock::parse(&header, &data).unwrap();
1864 assert_eq!(block.tow_seconds(), 4.3);
1865 assert_eq!(block.cov_vn_vn(), Some(0.04));
1866 assert_eq!(block.vn_std_mps(), Some(0.2));
1867 }
1868
1869 #[test]
1870 fn test_int_att_cov_euler_parse() {
1871 let mut data = vec![0u8; 50];
1872 data[6..10].copy_from_slice(&4400u32.to_le_bytes());
1873 data[10..12].copy_from_slice(&2300u16.to_le_bytes());
1874 data[12] = 4;
1875 data[13] = 0;
1876 data[14..18].copy_from_slice(&4.0f32.to_le_bytes());
1877 data[18..22].copy_from_slice(&1.0f32.to_le_bytes());
1878 data[22..26].copy_from_slice(&1.0f32.to_le_bytes());
1879 data[26..30].copy_from_slice(&0.5f32.to_le_bytes());
1880 data[30..34].copy_from_slice(&0.5f32.to_le_bytes());
1881 data[34..38].copy_from_slice(&0.25f32.to_le_bytes());
1882
1883 let header = header_for(block_ids::INT_ATT_COV_EULER, 4400, 2300);
1884 let block = IntAttCovEulerBlock::parse(&header, &data).unwrap();
1885 assert_eq!(block.tow_seconds(), 4.4);
1886 assert_eq!(block.cov_head_head(), Some(4.0));
1887 assert_eq!(block.heading_std_deg(), Some(2.0));
1888 }
1889
1890 #[test]
1891 fn test_int_att_cov_euler_dnu() {
1892 let mut data = vec![0u8; 50];
1893 data[6..10].copy_from_slice(&4400u32.to_le_bytes());
1894 data[10..12].copy_from_slice(&2300u16.to_le_bytes());
1895 data[14..18].copy_from_slice(&F32_DNU.to_le_bytes());
1896
1897 let header = header_for(block_ids::INT_ATT_COV_EULER, 4400, 2300);
1898 let block = IntAttCovEulerBlock::parse(&header, &data).unwrap();
1899 assert!(block.cov_head_head().is_none());
1900 assert!(block.heading_std_deg().is_none());
1901 }
1902}