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