1use crate::error::{SbfError, SbfResult};
4use crate::header::SbfHeader;
5use crate::types::{SatelliteId, SignalType};
6
7use super::block_ids;
8use super::SbfBlockParse;
9
10#[derive(Debug, Clone)]
16pub struct MeasEpochType1Raw {
17 pub rx_channel: u8,
18 pub signal_type: u8,
19 pub svid: u8,
20 pub misc: u8,
21 pub code_lsb: u32,
22 pub doppler: i32,
23 pub carrier_lsb: u16,
24 pub carrier_msb: i8,
25 pub cn0: u8,
26 pub lock_time: u16,
27 pub obs_info: u8,
28 pub n2: u8,
29}
30
31#[derive(Debug, Clone)]
37pub struct SatelliteMeasurement {
38 pub sat_id: SatelliteId,
40 pub signal_type: SignalType,
42 cn0_raw: u8,
44 doppler_raw: i32,
46 lock_time_raw: u16,
48 pub obs_info: u8,
50}
51
52impl SatelliteMeasurement {
53 pub fn cn0_dbhz(&self) -> f64 {
55 let base = self.cn0_raw as f64 * 0.25;
56 match self.signal_type {
57 SignalType::L1PY | SignalType::L2P => base,
59 _ => base + 10.0,
60 }
61 }
62
63 pub fn cn0_raw(&self) -> u8 {
65 self.cn0_raw
66 }
67
68 pub fn cn0_valid(&self) -> bool {
70 self.cn0_raw != 255
71 }
72
73 pub fn doppler_hz(&self) -> f64 {
75 self.doppler_raw as f64 * 0.0001
76 }
77
78 pub fn doppler_raw(&self) -> i32 {
80 self.doppler_raw
81 }
82
83 pub fn lock_time_seconds(&self) -> f64 {
87 self.lock_time_raw as f64
88 }
89
90 pub fn lock_time_raw(&self) -> u16 {
92 self.lock_time_raw
93 }
94
95 pub fn half_cycle_resolved(&self) -> bool {
99 (self.obs_info & 0x04) == 0
100 }
101
102 pub fn smoothing_active(&self) -> bool {
106 (self.obs_info & 0x01) != 0
107 }
108}
109
110#[derive(Debug, Clone)]
118pub struct MeasEpochBlock {
119 tow_ms: u32,
121 wnc: u16,
123 pub n1: u8,
125 pub sb1_length: u8,
127 pub sb2_length: u8,
129 pub common_flags: u8,
131 pub cum_clk_jumps: u8,
133 pub measurements: Vec<SatelliteMeasurement>,
135}
136
137impl MeasEpochBlock {
138 pub fn tow_seconds(&self) -> f64 {
140 self.tow_ms as f64 * 0.001
141 }
142
143 pub fn tow_ms(&self) -> u32 {
145 self.tow_ms
146 }
147
148 pub fn wnc(&self) -> u16 {
150 self.wnc
151 }
152
153 pub fn num_satellites(&self) -> usize {
155 self.measurements.len()
156 }
157
158 pub fn measurements_for_sat(&self, sat_id: &SatelliteId) -> Vec<&SatelliteMeasurement> {
160 self.measurements
161 .iter()
162 .filter(|m| &m.sat_id == sat_id)
163 .collect()
164 }
165
166 pub fn valid_cn0_measurements(&self) -> Vec<&SatelliteMeasurement> {
168 self.measurements.iter().filter(|m| m.cn0_valid()).collect()
169 }
170}
171
172impl SbfBlockParse for MeasEpochBlock {
173 const BLOCK_ID: u16 = block_ids::MEAS_EPOCH;
174
175 fn parse(header: &SbfHeader, data: &[u8]) -> SbfResult<Self> {
176 let full_len = header.length as usize;
177 if data.len() < full_len - 2 {
178 return Err(SbfError::IncompleteBlock {
180 needed: full_len,
181 have: data.len() + 2,
182 });
183 }
184
185 if data.len() < 17 {
194 return Err(SbfError::ParseError("MeasEpoch too short".into()));
195 }
196
197 let n1 = data[12];
198 let sb1_length = data[13];
199 let sb2_length = data[14];
200 let common_flags = data[15];
201 let cum_clk_jumps = data[16];
202
203 if sb1_length == 0 {
204 return Err(SbfError::ParseError("MeasEpoch SB1Length is zero".into()));
205 }
206
207 let sb1_length_usize = sb1_length as usize;
208 let sb2_length_usize = sb2_length as usize;
209
210 let mut offset = 17;
212 if header.block_rev >= 1 {
213 offset += 1; }
215
216 let mut measurements = Vec::new();
217
218 let signal_number = |type_field: u8, obs_info: u8| -> u8 {
220 let sig_idx = type_field & 0x1F;
221 if sig_idx == 31 {
222 32 + ((obs_info >> 3) & 0x1F)
223 } else {
224 sig_idx
225 }
226 };
227
228 for _ in 0..n1 {
229 if offset + sb1_length_usize > data.len() {
230 return Err(SbfError::ParseError(
231 "MeasEpoch SB1 exceeds block length".into(),
232 ));
233 }
234
235 let svid = data[offset + 2];
242 let type_field = data[offset + 1];
243
244 let doppler = if sb1_length_usize > 11 {
245 i32::from_le_bytes([
246 data[offset + 8],
247 data[offset + 9],
248 data[offset + 10],
249 data[offset + 11],
250 ])
251 } else {
252 0
253 };
254
255 let cn0_raw = if sb1_length_usize > 15 {
256 data[offset + 15]
257 } else {
258 255
259 };
260
261 let lock_time = if sb1_length_usize > 17 {
262 u16::from_le_bytes([data[offset + 16], data[offset + 17]])
263 } else {
264 0
265 };
266
267 let obs_info = if sb1_length_usize > 18 {
268 data[offset + 18]
269 } else {
270 0
271 };
272
273 let n2 = if sb1_length_usize > 19 {
274 data[offset + 19]
275 } else {
276 0
277 };
278
279 if let Some(sat_id) = SatelliteId::from_svid(svid) {
281 let sig_num = signal_number(type_field, obs_info);
282 let signal_type = SignalType::from_signal_number(sig_num);
283
284 measurements.push(SatelliteMeasurement {
285 sat_id: sat_id.clone(),
286 signal_type,
287 cn0_raw,
288 doppler_raw: doppler,
289 lock_time_raw: lock_time,
290 obs_info,
291 });
292
293 offset += sb1_length_usize;
294
295 if sb2_length_usize > 0 {
297 for _ in 0..n2 {
298 if offset + sb2_length_usize > data.len() {
299 return Err(SbfError::ParseError(
300 "MeasEpoch SB2 exceeds block length".into(),
301 ));
302 }
303
304 let type2_field = data[offset];
310 let cn0_raw_2 = if sb2_length_usize > 2 {
311 data[offset + 2]
312 } else {
313 255
314 };
315 let lock_time_2 = if sb2_length_usize > 1 {
316 data[offset + 1] as u16
317 } else {
318 0
319 };
320 let obs_info_2 = if sb2_length_usize > 5 {
321 data[offset + 5]
322 } else {
323 0
324 };
325
326 let sig_num_2 = signal_number(type2_field, obs_info_2);
327 let signal_type_2 = SignalType::from_signal_number(sig_num_2);
328
329 measurements.push(SatelliteMeasurement {
330 sat_id: sat_id.clone(),
331 signal_type: signal_type_2,
332 cn0_raw: cn0_raw_2,
333 doppler_raw: 0, lock_time_raw: lock_time_2,
335 obs_info: obs_info_2,
336 });
337
338 offset += sb2_length_usize;
339 }
340 }
341 } else {
342 offset += sb1_length_usize;
344 if sb2_length_usize > 0 {
345 let n2_skip = if sb1_length_usize > 19 {
346 data[offset - sb1_length_usize + 19]
347 } else {
348 0
349 };
350 let skip_bytes =
351 sb2_length_usize
352 .checked_mul(n2_skip as usize)
353 .ok_or_else(|| {
354 SbfError::ParseError("MeasEpoch SB2 length overflow".into())
355 })?;
356 if offset + skip_bytes > data.len() {
357 return Err(SbfError::ParseError(
358 "MeasEpoch SB2 exceeds block length".into(),
359 ));
360 }
361 offset += skip_bytes;
362 }
363 }
364 }
365
366 Ok(Self {
367 tow_ms: header.tow_ms,
368 wnc: header.wnc,
369 n1,
370 sb1_length,
371 sb2_length,
372 common_flags,
373 cum_clk_jumps,
374 measurements,
375 })
376 }
377}
378
379#[derive(Debug, Clone)]
385pub struct MeasExtraChannel {
386 pub rx_channel: u8,
388 pub signal_type: SignalType,
390 signal_type_raw: u8,
392 signal_number: u8,
394 mp_correction_raw: i16,
396 smoothing_correction_raw: i16,
398 code_var_raw: u16,
400 carrier_var_raw: u16,
402 lock_time_raw: u16,
404 pub cum_loss_cont: u8,
406 car_mp_correction_raw: Option<i8>,
408 pub info: u8,
410 misc_raw: Option<u8>,
412}
413
414impl MeasExtraChannel {
415 pub fn signal_type_raw(&self) -> u8 {
417 self.signal_type_raw
418 }
419
420 pub fn signal_number(&self) -> u8 {
422 self.signal_number
423 }
424
425 pub fn antenna_id(&self) -> u8 {
427 (self.signal_type_raw >> 5) & 0x07
428 }
429
430 pub fn mp_correction_m(&self) -> f64 {
432 self.mp_correction_raw as f64 * 0.001
433 }
434
435 pub fn smoothing_correction_m(&self) -> f64 {
437 self.smoothing_correction_raw as f64 * 0.001
438 }
439
440 pub fn code_var_m2(&self) -> f64 {
442 self.code_var_raw as f64 * 0.0001
443 }
444
445 pub fn carrier_var_cycles2(&self) -> f64 {
447 self.carrier_var_raw as f64 * 0.000001
448 }
449
450 pub fn lock_time_seconds(&self) -> f64 {
452 self.lock_time_raw as f64
453 }
454
455 pub fn lock_time_raw(&self) -> u16 {
457 self.lock_time_raw
458 }
459
460 pub fn car_mp_correction_raw(&self) -> Option<i8> {
462 self.car_mp_correction_raw
463 }
464
465 pub fn car_mp_correction_cycles(&self) -> Option<f64> {
467 self.car_mp_correction_raw.map(|v| v as f64 / 512.0)
468 }
469
470 pub fn misc_raw(&self) -> Option<u8> {
472 self.misc_raw
473 }
474
475 pub fn cn0_high_res_dbhz_offset(&self) -> Option<f64> {
477 self.misc_raw.map(|misc| (misc & 0x07) as f64 * 0.03125)
478 }
479}
480
481#[derive(Debug, Clone)]
485pub struct MeasExtraBlock {
486 tow_ms: u32,
488 wnc: u16,
490 pub n: u8,
492 pub sb_length: u8,
494 doppler_var_factor: f32,
496 pub channels: Vec<MeasExtraChannel>,
498}
499
500impl MeasExtraBlock {
501 pub fn tow_seconds(&self) -> f64 {
502 self.tow_ms as f64 * 0.001
503 }
504 pub fn tow_ms(&self) -> u32 {
505 self.tow_ms
506 }
507 pub fn wnc(&self) -> u16 {
508 self.wnc
509 }
510
511 pub fn doppler_var_factor(&self) -> f32 {
513 self.doppler_var_factor
514 }
515
516 pub fn num_channels(&self) -> usize {
518 self.channels.len()
519 }
520}
521
522impl SbfBlockParse for MeasExtraBlock {
523 const BLOCK_ID: u16 = block_ids::MEAS_EXTRA;
524
525 fn parse(header: &SbfHeader, data: &[u8]) -> SbfResult<Self> {
526 if data.len() < 18 {
527 return Err(SbfError::ParseError("MeasExtra too short".into()));
528 }
529
530 let n = data[12];
535 let sb_length = data[13];
536
537 if sb_length < 14 {
538 return Err(SbfError::ParseError("MeasExtra SBLength too small".into()));
539 }
540
541 let doppler_var_factor = f32::from_le_bytes(data[14..18].try_into().unwrap());
542
543 let sb_length_usize = sb_length as usize;
544 let mut channels = Vec::new();
545 let mut offset = 18;
546
547 for _ in 0..n {
548 if offset + sb_length_usize > data.len() {
549 return Err(SbfError::ParseError(
550 "MeasExtra sub-block exceeds block length".into(),
551 ));
552 }
553
554 let rx_channel = data[offset];
555 let signal_type_raw = data[offset + 1];
556 let mp_correction_raw = i16::from_le_bytes([data[offset + 2], data[offset + 3]]);
557 let smoothing_correction_raw = i16::from_le_bytes([data[offset + 4], data[offset + 5]]);
558 let code_var_raw = u16::from_le_bytes([data[offset + 6], data[offset + 7]]);
559 let carrier_var_raw = u16::from_le_bytes([data[offset + 8], data[offset + 9]]);
560 let lock_time_raw = u16::from_le_bytes([data[offset + 10], data[offset + 11]]);
561 let cum_loss_cont = data[offset + 12];
562 let (car_mp_correction_raw, info, misc_raw) = if sb_length_usize >= 16 {
563 (
565 Some(data[offset + 13] as i8),
566 data[offset + 14],
567 Some(data[offset + 15]),
568 )
569 } else if sb_length_usize >= 15 {
570 (Some(data[offset + 13] as i8), data[offset + 14], None)
572 } else {
573 (None, data[offset + 13], None)
575 };
576
577 let sig_idx_lo = signal_type_raw & 0x1F;
578 let signal_number = if sig_idx_lo == 31 {
579 misc_raw
580 .map(|misc| 32 + ((misc >> 3) & 0x1F))
581 .unwrap_or(sig_idx_lo)
582 } else {
583 sig_idx_lo
584 };
585
586 channels.push(MeasExtraChannel {
587 rx_channel,
588 signal_type: SignalType::from_signal_number(signal_number),
589 signal_type_raw,
590 signal_number,
591 mp_correction_raw,
592 smoothing_correction_raw,
593 code_var_raw,
594 carrier_var_raw,
595 lock_time_raw,
596 cum_loss_cont,
597 car_mp_correction_raw,
598 info,
599 misc_raw,
600 });
601
602 offset += sb_length_usize;
603 }
604
605 Ok(Self {
606 tow_ms: header.tow_ms,
607 wnc: header.wnc,
608 n,
609 sb_length,
610 doppler_var_factor,
611 channels,
612 })
613 }
614}
615
616#[derive(Debug, Clone)]
622pub struct IqCorrChannel {
623 pub rx_channel: u8,
624 pub signal_type: u8,
625 pub svid: u8,
626 pub corr_iq_msb: u8,
627 pub corr_i_lsb: u8,
628 pub corr_q_lsb: u8,
629 pub carrier_phase_lsb: u16,
630}
631
632#[derive(Debug, Clone)]
636pub struct IqCorrBlock {
637 tow_ms: u32,
638 wnc: u16,
639 pub n: u8,
640 pub sb_length: u8,
641 pub corr_duration: u8,
643 pub cum_clk_jumps: i8,
644 pub channels: Vec<IqCorrChannel>,
645}
646
647impl IqCorrBlock {
648 pub fn tow_seconds(&self) -> f64 {
649 self.tow_ms as f64 * 0.001
650 }
651 pub fn tow_ms(&self) -> u32 {
652 self.tow_ms
653 }
654 pub fn wnc(&self) -> u16 {
655 self.wnc
656 }
657 pub fn num_channels(&self) -> usize {
658 self.channels.len()
659 }
660}
661
662impl SbfBlockParse for IqCorrBlock {
663 const BLOCK_ID: u16 = block_ids::IQ_CORR;
664
665 fn parse(header: &SbfHeader, data: &[u8]) -> SbfResult<Self> {
666 const MIN_HEADER: usize = 16;
668 if data.len() < MIN_HEADER {
669 return Err(SbfError::ParseError("IQCorr too short".into()));
670 }
671
672 let n = data[12];
673 let sb_length = data[13];
674 let corr_duration = data[14];
675 let cum_clk_jumps = data[15] as i8;
676
677 if sb_length < 8 {
678 return Err(SbfError::ParseError("IQCorr SBLength too small".into()));
679 }
680
681 let sb_length_usize = sb_length as usize;
682 let mut channels = Vec::new();
683 let mut offset = 16;
684
685 for _ in 0..n {
686 if offset + sb_length_usize > data.len() {
687 return Err(SbfError::ParseError(
688 "IQCorr sub-block exceeds block length".into(),
689 ));
690 }
691
692 channels.push(IqCorrChannel {
693 rx_channel: data[offset],
694 signal_type: data[offset + 1],
695 svid: data[offset + 2],
696 corr_iq_msb: data[offset + 3],
697 corr_i_lsb: data[offset + 4],
698 corr_q_lsb: data[offset + 5],
699 carrier_phase_lsb: u16::from_le_bytes([data[offset + 6], data[offset + 7]]),
700 });
701
702 offset += sb_length_usize;
703 }
704
705 Ok(Self {
706 tow_ms: header.tow_ms,
707 wnc: header.wnc,
708 n,
709 sb_length,
710 corr_duration,
711 cum_clk_jumps,
712 channels,
713 })
714 }
715}
716
717#[derive(Debug, Clone)]
725pub struct EndOfMeasBlock {
726 tow_ms: u32,
727 wnc: u16,
728}
729
730impl EndOfMeasBlock {
731 pub fn tow_seconds(&self) -> f64 {
732 self.tow_ms as f64 * 0.001
733 }
734 pub fn tow_ms(&self) -> u32 {
735 self.tow_ms
736 }
737 pub fn wnc(&self) -> u16 {
738 self.wnc
739 }
740}
741
742impl SbfBlockParse for EndOfMeasBlock {
743 const BLOCK_ID: u16 = block_ids::END_OF_MEAS;
744
745 fn parse(header: &SbfHeader, data: &[u8]) -> SbfResult<Self> {
746 if data.len() < 12 {
747 return Err(SbfError::ParseError("EndOfMeas too short".into()));
748 }
749
750 Ok(Self {
751 tow_ms: header.tow_ms,
752 wnc: header.wnc,
753 })
754 }
755}
756
757#[cfg(test)]
758mod tests {
759 use super::*;
760 use crate::header::SbfHeader;
761 use crate::types::Constellation;
762
763 #[test]
764 fn test_satellite_measurement_cn0() {
765 let meas = SatelliteMeasurement {
766 sat_id: SatelliteId::new(Constellation::GPS, 1),
767 signal_type: SignalType::L1CA,
768 cn0_raw: 160, doppler_raw: 1000,
770 lock_time_raw: 10,
771 obs_info: 0,
772 };
773
774 assert_eq!(meas.cn0_dbhz(), 50.0);
775 assert!(meas.cn0_valid());
776 }
777
778 #[test]
779 fn test_satellite_measurement_cn0_gps_p_no_offset() {
780 let meas = SatelliteMeasurement {
781 sat_id: SatelliteId::new(Constellation::GPS, 1),
782 signal_type: SignalType::L1PY, cn0_raw: 160, doppler_raw: 1000,
785 lock_time_raw: 10,
786 obs_info: 0,
787 };
788
789 assert_eq!(meas.cn0_dbhz(), 40.0);
790 }
791
792 #[test]
793 fn test_satellite_measurement_invalid_cn0() {
794 let meas = SatelliteMeasurement {
795 sat_id: SatelliteId::new(Constellation::GPS, 1),
796 signal_type: SignalType::L1CA,
797 cn0_raw: 255,
798 doppler_raw: 0,
799 lock_time_raw: 0,
800 obs_info: 0,
801 };
802
803 assert!(!meas.cn0_valid());
804 }
805
806 #[test]
807 fn test_lock_time_encoding() {
808 let meas = SatelliteMeasurement {
810 sat_id: SatelliteId::new(Constellation::GPS, 1),
811 signal_type: SignalType::L1CA,
812 cn0_raw: 160,
813 doppler_raw: 0,
814 lock_time_raw: 30,
815 obs_info: 0,
816 };
817 assert_eq!(meas.lock_time_seconds(), 30.0);
818
819 let meas2 = SatelliteMeasurement {
821 lock_time_raw: 96,
822 ..meas
823 };
824 assert_eq!(meas2.lock_time_seconds(), 96.0);
825 }
826
827 #[test]
828 fn test_meas_extra_scaling() {
829 let channel = MeasExtraChannel {
830 rx_channel: 3,
831 signal_type: SignalType::L1CA,
832 signal_type_raw: 0,
833 signal_number: 0,
834 mp_correction_raw: 1234,
835 smoothing_correction_raw: -500,
836 code_var_raw: 200,
837 carrier_var_raw: 150,
838 lock_time_raw: 45,
839 cum_loss_cont: 2,
840 car_mp_correction_raw: None,
841 info: 1,
842 misc_raw: None,
843 };
844
845 assert!((channel.mp_correction_m() - 1.234).abs() < 1e-6);
846 assert!((channel.smoothing_correction_m() + 0.5).abs() < 1e-6);
847 assert!((channel.code_var_m2() - 0.02).abs() < 1e-6);
848 assert!((channel.carrier_var_cycles2() - 0.00015).abs() < 1e-9);
849 assert_eq!(channel.lock_time_raw(), 45);
850 assert_eq!(channel.signal_type_raw(), 0);
851 assert_eq!(channel.signal_number(), 0);
852 assert_eq!(channel.antenna_id(), 0);
853 assert_eq!(channel.car_mp_correction_raw(), None);
854 assert_eq!(channel.misc_raw(), None);
855 }
856
857 #[test]
858 fn test_meas_extra_doppler_factor() {
859 let block = MeasExtraBlock {
860 tow_ms: 1000,
861 wnc: 2000,
862 n: 0,
863 sb_length: 14,
864 doppler_var_factor: 1.5,
865 channels: Vec::new(),
866 };
867
868 assert_eq!(block.tow_seconds(), 1.0);
869 assert_eq!(block.wnc(), 2000);
870 assert!((block.doppler_var_factor() - 1.5).abs() < 1e-6);
871 assert_eq!(block.num_channels(), 0);
872 }
873
874 #[test]
875 fn test_iq_corr_parse() {
876 let mut data = vec![0u8; 32];
877 data[6..10].copy_from_slice(&5000u32.to_le_bytes());
878 data[10..12].copy_from_slice(&2100u16.to_le_bytes());
879 data[12] = 1; data[13] = 8; data[14] = 20; data[15] = 0; data[16] = 2; data[17] = 0; data[18] = 7; data[19] = 10; data[20] = 5; data[21] = 3; data[22..24].copy_from_slice(&1000u16.to_le_bytes()); let header = SbfHeader {
892 crc: 0,
893 block_id: block_ids::IQ_CORR,
894 block_rev: 0,
895 length: 32,
896 tow_ms: 5000,
897 wnc: 2100,
898 };
899 let block = IqCorrBlock::parse(&header, &data).unwrap();
900 assert_eq!(block.tow_seconds(), 5.0);
901 assert_eq!(block.wnc(), 2100);
902 assert_eq!(block.n, 1);
903 assert_eq!(block.corr_duration, 20);
904 assert_eq!(block.num_channels(), 1);
905 assert_eq!(block.channels[0].rx_channel, 2);
906 assert_eq!(block.channels[0].svid, 7);
907 assert_eq!(block.channels[0].carrier_phase_lsb, 1000);
908 }
909
910 #[test]
911 fn test_end_of_meas_accessors() {
912 let end = EndOfMeasBlock {
913 tow_ms: 2500,
914 wnc: 123,
915 };
916 assert_eq!(end.tow_ms(), 2500);
917 assert_eq!(end.wnc(), 123);
918 assert_eq!(end.tow_seconds(), 2.5);
919 }
920
921 #[test]
922 fn test_meas_extra_parse() {
923 let mut data = vec![0u8; 18 + 14];
924 data[12] = 1; data[13] = 14; data[14..18].copy_from_slice(&1.25_f32.to_le_bytes());
927
928 let offset = 18;
929 data[offset] = 5; data[offset + 1] = 0; data[offset + 2..offset + 4].copy_from_slice(&1000_i16.to_le_bytes());
932 data[offset + 4..offset + 6].copy_from_slice(&(-200_i16).to_le_bytes());
933 data[offset + 6..offset + 8].copy_from_slice(&500_u16.to_le_bytes());
934 data[offset + 8..offset + 10].copy_from_slice(&250_u16.to_le_bytes());
935 data[offset + 10..offset + 12].copy_from_slice(&60_u16.to_le_bytes());
936 data[offset + 12] = 3;
937 data[offset + 13] = 0xA5;
938
939 let header = SbfHeader {
940 crc: 0,
941 block_id: block_ids::MEAS_EXTRA,
942 block_rev: 0,
943 length: (data.len() + 2) as u16,
944 tow_ms: 123456,
945 wnc: 321,
946 };
947
948 let parsed = MeasExtraBlock::parse(&header, &data).expect("parse");
949 assert_eq!(parsed.tow_ms(), 123456);
950 assert_eq!(parsed.wnc(), 321);
951 assert_eq!(parsed.n, 1);
952 assert_eq!(parsed.sb_length, 14);
953 assert!((parsed.doppler_var_factor() - 1.25).abs() < 1e-6);
954 assert_eq!(parsed.num_channels(), 1);
955
956 let ch = &parsed.channels[0];
957 assert_eq!(ch.rx_channel, 5);
958 assert_eq!(ch.signal_type, SignalType::L1CA);
959 assert_eq!(ch.signal_type_raw(), 0);
960 assert_eq!(ch.signal_number(), 0);
961 assert_eq!(ch.antenna_id(), 0);
962 assert!((ch.mp_correction_m() - 1.0).abs() < 1e-6);
963 assert!((ch.smoothing_correction_m() + 0.2).abs() < 1e-6);
964 assert!((ch.code_var_m2() - 0.05).abs() < 1e-6);
965 assert!((ch.carrier_var_cycles2() - 0.00025).abs() < 1e-9);
966 assert_eq!(ch.lock_time_raw(), 60);
967 assert_eq!(ch.cum_loss_cont, 3);
968 assert_eq!(ch.car_mp_correction_raw(), None);
969 assert_eq!(ch.info, 0xA5);
970 assert_eq!(ch.misc_raw(), None);
971 }
972
973 #[test]
974 fn test_meas_extra_parse_extended_type_and_misc() {
975 let mut data = vec![0u8; 18 + 16];
976 data[12] = 1; data[13] = 16; data[14..18].copy_from_slice(&2.0_f32.to_le_bytes());
979
980 let offset = 18;
981 data[offset] = 7; data[offset + 1] = 0x5F; data[offset + 2..offset + 4].copy_from_slice(&0_i16.to_le_bytes());
984 data[offset + 4..offset + 6].copy_from_slice(&0_i16.to_le_bytes());
985 data[offset + 6..offset + 8].copy_from_slice(&100_u16.to_le_bytes());
986 data[offset + 8..offset + 10].copy_from_slice(&1024_u16.to_le_bytes());
987 data[offset + 10..offset + 12].copy_from_slice(&11_u16.to_le_bytes());
988 data[offset + 12] = 9; data[offset + 13] = (-64_i8) as u8; data[offset + 14] = 0xB4; data[offset + 15] = 0x33; let header = SbfHeader {
994 crc: 0,
995 block_id: block_ids::MEAS_EXTRA,
996 block_rev: 3,
997 length: (data.len() + 2) as u16,
998 tow_ms: 500,
999 wnc: 2222,
1000 };
1001
1002 let parsed = MeasExtraBlock::parse(&header, &data).expect("parse");
1003 assert_eq!(parsed.num_channels(), 1);
1004
1005 let ch = &parsed.channels[0];
1006 assert_eq!(ch.rx_channel, 7);
1007 assert_eq!(ch.signal_type_raw(), 0x5F);
1008 assert_eq!(ch.antenna_id(), 2);
1009 assert_eq!(ch.signal_number(), 38);
1010 assert_eq!(ch.signal_type, SignalType::QZSSL1CB);
1011 assert_eq!(ch.cum_loss_cont, 9);
1012 assert_eq!(ch.info, 0xB4);
1013 assert_eq!(ch.car_mp_correction_raw(), Some(-64));
1014 assert_eq!(ch.misc_raw(), Some(0x33));
1015 assert!((ch.car_mp_correction_cycles().expect("carmp") + 0.125).abs() < 1e-9);
1016 assert!((ch.cn0_high_res_dbhz_offset().expect("cn0 hi-res") - 0.09375).abs() < 1e-9);
1017 }
1018
1019 #[test]
1020 fn test_end_of_meas_parse() {
1021 let data = vec![0u8; 12];
1022 let header = SbfHeader {
1023 crc: 0,
1024 block_id: block_ids::END_OF_MEAS,
1025 block_rev: 0,
1026 length: (data.len() + 2) as u16,
1027 tow_ms: 1000,
1028 wnc: 45,
1029 };
1030
1031 let parsed = EndOfMeasBlock::parse(&header, &data).expect("parse");
1032 assert_eq!(parsed.tow_ms(), 1000);
1033 assert_eq!(parsed.wnc(), 45);
1034 }
1035}