1use crate::error::{SbfError, SbfResult};
4use crate::header::SbfHeader;
5use crate::types::{SatelliteId, SignalType};
6
7use super::block_ids;
8use super::dnu::{u16_or_none, u8_or_none, I32_DNU, U16_DNU};
9use super::SbfBlockParse;
10
11#[derive(Debug, Clone)]
17pub struct MeasEpochType1Raw {
18 pub rx_channel: u8,
19 pub signal_type: u8,
20 pub svid: u8,
21 pub misc: u8,
22 pub code_lsb: u32,
23 pub doppler: i32,
24 pub carrier_lsb: u16,
25 pub carrier_msb: i8,
26 pub cn0: u8,
27 pub lock_time: u16,
28 pub obs_info: u8,
29 pub n2: u8,
30}
31
32#[derive(Debug, Clone)]
38pub struct SatelliteMeasurement {
39 pub sat_id: SatelliteId,
41 pub signal_type: SignalType,
43 cn0_raw: u8,
45 doppler_raw: i32,
47 doppler_valid: bool,
49 lock_time_raw: u16,
51 lock_time_valid: bool,
53 pub obs_info: u8,
55}
56
57impl SatelliteMeasurement {
58 pub fn cn0_dbhz(&self) -> f64 {
60 self.cn0_dbhz_opt().unwrap_or(0.0)
61 }
62
63 pub fn cn0_dbhz_opt(&self) -> Option<f64> {
65 let cn0_raw = u8_or_none(self.cn0_raw)?;
66 let base = cn0_raw as f64 * 0.25;
67 match self.signal_type {
68 SignalType::L1PY | SignalType::L2P => Some(base),
70 _ => Some(base + 10.0),
71 }
72 }
73
74 pub fn cn0_raw(&self) -> u8 {
76 self.cn0_raw
77 }
78
79 pub fn cn0_valid(&self) -> bool {
81 u8_or_none(self.cn0_raw).is_some()
82 }
83
84 pub fn doppler_hz(&self) -> f64 {
89 self.doppler_hz_opt().unwrap_or(0.0)
90 }
91
92 pub fn doppler_hz_opt(&self) -> Option<f64> {
94 if self.doppler_valid {
95 Some(self.doppler_raw as f64 * 0.0001)
96 } else {
97 None
98 }
99 }
100
101 pub fn doppler_raw(&self) -> i32 {
103 self.doppler_raw
104 }
105
106 pub fn lock_time_seconds(&self) -> f64 {
111 self.lock_time_seconds_opt().unwrap_or(0.0)
112 }
113
114 pub fn lock_time_seconds_opt(&self) -> Option<f64> {
116 if self.lock_time_valid {
117 Some(self.lock_time_raw as f64)
118 } else {
119 None
120 }
121 }
122
123 pub fn lock_time_raw(&self) -> u16 {
125 self.lock_time_raw
126 }
127
128 pub fn half_cycle_resolved(&self) -> bool {
132 (self.obs_info & 0x04) == 0
133 }
134
135 pub fn smoothing_active(&self) -> bool {
139 (self.obs_info & 0x01) != 0
140 }
141}
142
143#[derive(Debug, Clone)]
151pub struct MeasEpochBlock {
152 tow_ms: u32,
154 wnc: u16,
156 pub n1: u8,
158 pub sb1_length: u8,
160 pub sb2_length: u8,
162 pub common_flags: u8,
164 pub cum_clk_jumps: u8,
166 pub measurements: Vec<SatelliteMeasurement>,
168}
169
170impl MeasEpochBlock {
171 pub fn tow_seconds(&self) -> f64 {
173 self.tow_ms as f64 * 0.001
174 }
175
176 pub fn tow_ms(&self) -> u32 {
178 self.tow_ms
179 }
180
181 pub fn wnc(&self) -> u16 {
183 self.wnc
184 }
185
186 pub fn num_satellites(&self) -> usize {
188 self.measurements.len()
189 }
190
191 pub fn measurements_for_sat(&self, sat_id: &SatelliteId) -> Vec<&SatelliteMeasurement> {
193 self.measurements
194 .iter()
195 .filter(|m| &m.sat_id == sat_id)
196 .collect()
197 }
198
199 pub fn valid_cn0_measurements(&self) -> Vec<&SatelliteMeasurement> {
201 self.measurements.iter().filter(|m| m.cn0_valid()).collect()
202 }
203}
204
205impl SbfBlockParse for MeasEpochBlock {
206 const BLOCK_ID: u16 = block_ids::MEAS_EPOCH;
207
208 fn parse(header: &SbfHeader, data: &[u8]) -> SbfResult<Self> {
209 let full_len = header.length as usize;
210 if data.len() < full_len - 2 {
211 return Err(SbfError::IncompleteBlock {
213 needed: full_len,
214 have: data.len() + 2,
215 });
216 }
217
218 if data.len() < 17 {
227 return Err(SbfError::ParseError("MeasEpoch too short".into()));
228 }
229
230 let n1 = data[12];
231 let sb1_length = data[13];
232 let sb2_length = data[14];
233 let common_flags = data[15];
234 let cum_clk_jumps = data[16];
235
236 if sb1_length == 0 {
237 return Err(SbfError::ParseError("MeasEpoch SB1Length is zero".into()));
238 }
239
240 let sb1_length_usize = sb1_length as usize;
241 let sb2_length_usize = sb2_length as usize;
242
243 let mut offset = 17;
245 if header.block_rev >= 1 {
246 offset += 1; }
248
249 let mut measurements = Vec::new();
250
251 let signal_number = |type_field: u8, obs_info: u8| -> u8 {
253 let sig_idx = type_field & 0x1F;
254 if sig_idx == 31 {
255 32 + ((obs_info >> 3) & 0x1F)
256 } else {
257 sig_idx
258 }
259 };
260
261 for _ in 0..n1 {
262 if offset + sb1_length_usize > data.len() {
263 return Err(SbfError::ParseError(
264 "MeasEpoch SB1 exceeds block length".into(),
265 ));
266 }
267
268 let svid = data[offset + 2];
275 let type_field = data[offset + 1];
276
277 let (doppler, doppler_valid) = if sb1_length_usize > 11 {
278 let raw = i32::from_le_bytes([
279 data[offset + 8],
280 data[offset + 9],
281 data[offset + 10],
282 data[offset + 11],
283 ]);
284 (raw, raw != I32_DNU)
285 } else {
286 (0, false)
287 };
288
289 let cn0_raw = if sb1_length_usize > 15 {
290 data[offset + 15]
291 } else {
292 255
293 };
294
295 let (lock_time, lock_time_valid) = if sb1_length_usize > 17 {
296 let raw = u16::from_le_bytes([data[offset + 16], data[offset + 17]]);
297 (raw, raw != U16_DNU)
298 } else {
299 (0, false)
300 };
301
302 let obs_info = if sb1_length_usize > 18 {
303 data[offset + 18]
304 } else {
305 0
306 };
307
308 let n2 = if sb1_length_usize > 19 {
309 data[offset + 19]
310 } else {
311 0
312 };
313
314 if let Some(sat_id) = SatelliteId::from_svid(svid) {
316 let sig_num = signal_number(type_field, obs_info);
317 let signal_type = SignalType::from_signal_number(sig_num);
318
319 measurements.push(SatelliteMeasurement {
320 sat_id: sat_id.clone(),
321 signal_type,
322 cn0_raw,
323 doppler_raw: doppler,
324 doppler_valid,
325 lock_time_raw: lock_time,
326 lock_time_valid,
327 obs_info,
328 });
329
330 offset += sb1_length_usize;
331
332 if sb2_length_usize > 0 {
334 for _ in 0..n2 {
335 if offset + sb2_length_usize > data.len() {
336 return Err(SbfError::ParseError(
337 "MeasEpoch SB2 exceeds block length".into(),
338 ));
339 }
340
341 let type2_field = data[offset];
347 let cn0_raw_2 = if sb2_length_usize > 2 {
348 data[offset + 2]
349 } else {
350 255
351 };
352 let (lock_time_2, lock_time_valid_2) = if sb2_length_usize > 1 {
353 let raw = data[offset + 1];
354 (raw as u16, u8_or_none(raw).is_some())
355 } else {
356 (0, false)
357 };
358 let obs_info_2 = if sb2_length_usize > 5 {
359 data[offset + 5]
360 } else {
361 0
362 };
363
364 let sig_num_2 = signal_number(type2_field, obs_info_2);
365 let signal_type_2 = SignalType::from_signal_number(sig_num_2);
366
367 measurements.push(SatelliteMeasurement {
368 sat_id: sat_id.clone(),
369 signal_type: signal_type_2,
370 cn0_raw: cn0_raw_2,
371 doppler_raw: 0, doppler_valid: false,
373 lock_time_raw: lock_time_2,
374 lock_time_valid: lock_time_valid_2,
375 obs_info: obs_info_2,
376 });
377
378 offset += sb2_length_usize;
379 }
380 }
381 } else {
382 offset += sb1_length_usize;
384 if sb2_length_usize > 0 {
385 let n2_skip = if sb1_length_usize > 19 {
386 data[offset - sb1_length_usize + 19]
387 } else {
388 0
389 };
390 let skip_bytes =
391 sb2_length_usize
392 .checked_mul(n2_skip as usize)
393 .ok_or_else(|| {
394 SbfError::ParseError("MeasEpoch SB2 length overflow".into())
395 })?;
396 if offset + skip_bytes > data.len() {
397 return Err(SbfError::ParseError(
398 "MeasEpoch SB2 exceeds block length".into(),
399 ));
400 }
401 offset += skip_bytes;
402 }
403 }
404 }
405
406 Ok(Self {
407 tow_ms: header.tow_ms,
408 wnc: header.wnc,
409 n1,
410 sb1_length,
411 sb2_length,
412 common_flags,
413 cum_clk_jumps,
414 measurements,
415 })
416 }
417}
418
419#[derive(Debug, Clone)]
425pub struct MeasExtraChannel {
426 pub rx_channel: u8,
428 pub signal_type: SignalType,
430 signal_type_raw: u8,
432 signal_number: u8,
434 mp_correction_raw: i16,
436 smoothing_correction_raw: i16,
438 code_var_raw: u16,
440 carrier_var_raw: u16,
442 lock_time_raw: u16,
444 pub cum_loss_cont: u8,
446 car_mp_correction_raw: Option<i8>,
448 pub info: u8,
450 misc_raw: Option<u8>,
452}
453
454impl MeasExtraChannel {
455 pub fn signal_type_raw(&self) -> u8 {
457 self.signal_type_raw
458 }
459
460 pub fn signal_number(&self) -> u8 {
462 self.signal_number
463 }
464
465 pub fn antenna_id(&self) -> u8 {
467 (self.signal_type_raw >> 5) & 0x07
468 }
469
470 pub fn mp_correction_m(&self) -> f64 {
472 self.mp_correction_raw as f64 * 0.001
473 }
474
475 pub fn smoothing_correction_m(&self) -> f64 {
477 self.smoothing_correction_raw as f64 * 0.001
478 }
479
480 pub fn code_var_m2(&self) -> f64 {
482 self.code_var_m2_opt().unwrap_or(0.0)
483 }
484
485 pub fn code_var_m2_opt(&self) -> Option<f64> {
487 u16_or_none(self.code_var_raw).map(|raw| raw as f64 * 0.0001)
488 }
489
490 pub fn code_var_raw(&self) -> u16 {
492 self.code_var_raw
493 }
494
495 pub fn carrier_var_cycles2(&self) -> f64 {
497 self.carrier_var_cycles2_opt().unwrap_or(0.0)
498 }
499
500 pub fn carrier_var_cycles2_opt(&self) -> Option<f64> {
502 u16_or_none(self.carrier_var_raw).map(|raw| raw as f64 * 0.000001)
503 }
504
505 pub fn carrier_var_raw(&self) -> u16 {
507 self.carrier_var_raw
508 }
509
510 pub fn lock_time_seconds(&self) -> f64 {
512 self.lock_time_seconds_opt().unwrap_or(0.0)
513 }
514
515 pub fn lock_time_seconds_opt(&self) -> Option<f64> {
517 u16_or_none(self.lock_time_raw).map(|raw| raw as f64)
518 }
519
520 pub fn lock_time_raw(&self) -> u16 {
522 self.lock_time_raw
523 }
524
525 pub fn car_mp_correction_raw(&self) -> Option<i8> {
527 self.car_mp_correction_raw
528 }
529
530 pub fn car_mp_correction_cycles(&self) -> Option<f64> {
532 self.car_mp_correction_raw.map(|v| v as f64 / 512.0)
533 }
534
535 pub fn misc_raw(&self) -> Option<u8> {
537 self.misc_raw
538 }
539
540 pub fn cn0_high_res_dbhz_offset(&self) -> Option<f64> {
542 self.misc_raw.map(|misc| (misc & 0x07) as f64 * 0.03125)
543 }
544}
545
546#[derive(Debug, Clone)]
550pub struct MeasExtraBlock {
551 tow_ms: u32,
553 wnc: u16,
555 pub n: u8,
557 pub sb_length: u8,
559 doppler_var_factor: f32,
561 pub channels: Vec<MeasExtraChannel>,
563}
564
565impl MeasExtraBlock {
566 pub fn tow_seconds(&self) -> f64 {
567 self.tow_ms as f64 * 0.001
568 }
569 pub fn tow_ms(&self) -> u32 {
570 self.tow_ms
571 }
572 pub fn wnc(&self) -> u16 {
573 self.wnc
574 }
575
576 pub fn doppler_var_factor(&self) -> f32 {
578 self.doppler_var_factor
579 }
580
581 pub fn num_channels(&self) -> usize {
583 self.channels.len()
584 }
585}
586
587impl SbfBlockParse for MeasExtraBlock {
588 const BLOCK_ID: u16 = block_ids::MEAS_EXTRA;
589
590 fn parse(header: &SbfHeader, data: &[u8]) -> SbfResult<Self> {
591 if data.len() < 18 {
592 return Err(SbfError::ParseError("MeasExtra too short".into()));
593 }
594
595 let n = data[12];
600 let sb_length = data[13];
601
602 if sb_length < 14 {
603 return Err(SbfError::ParseError("MeasExtra SBLength too small".into()));
604 }
605
606 let doppler_var_factor = f32::from_le_bytes(data[14..18].try_into().unwrap());
607
608 let sb_length_usize = sb_length as usize;
609 let mut channels = Vec::new();
610 let mut offset = 18;
611
612 for _ in 0..n {
613 if offset + sb_length_usize > data.len() {
614 return Err(SbfError::ParseError(
615 "MeasExtra sub-block exceeds block length".into(),
616 ));
617 }
618
619 let rx_channel = data[offset];
620 let signal_type_raw = data[offset + 1];
621 let mp_correction_raw = i16::from_le_bytes([data[offset + 2], data[offset + 3]]);
622 let smoothing_correction_raw = i16::from_le_bytes([data[offset + 4], data[offset + 5]]);
623 let code_var_raw = u16::from_le_bytes([data[offset + 6], data[offset + 7]]);
624 let carrier_var_raw = u16::from_le_bytes([data[offset + 8], data[offset + 9]]);
625 let lock_time_raw = u16::from_le_bytes([data[offset + 10], data[offset + 11]]);
626 let cum_loss_cont = data[offset + 12];
627 let (car_mp_correction_raw, info, misc_raw) = if sb_length_usize >= 16 {
628 (
630 Some(data[offset + 13] as i8),
631 data[offset + 14],
632 Some(data[offset + 15]),
633 )
634 } else if sb_length_usize >= 15 {
635 (Some(data[offset + 13] as i8), data[offset + 14], None)
637 } else {
638 (None, data[offset + 13], None)
640 };
641
642 let sig_idx_lo = signal_type_raw & 0x1F;
643 let signal_number = if sig_idx_lo == 31 {
644 misc_raw
645 .map(|misc| 32 + ((misc >> 3) & 0x1F))
646 .unwrap_or(sig_idx_lo)
647 } else {
648 sig_idx_lo
649 };
650
651 channels.push(MeasExtraChannel {
652 rx_channel,
653 signal_type: SignalType::from_signal_number(signal_number),
654 signal_type_raw,
655 signal_number,
656 mp_correction_raw,
657 smoothing_correction_raw,
658 code_var_raw,
659 carrier_var_raw,
660 lock_time_raw,
661 cum_loss_cont,
662 car_mp_correction_raw,
663 info,
664 misc_raw,
665 });
666
667 offset += sb_length_usize;
668 }
669
670 Ok(Self {
671 tow_ms: header.tow_ms,
672 wnc: header.wnc,
673 n,
674 sb_length,
675 doppler_var_factor,
676 channels,
677 })
678 }
679}
680
681#[derive(Debug, Clone)]
687pub struct IqCorrChannel {
688 pub rx_channel: u8,
689 pub signal_type: u8,
690 pub svid: u8,
691 pub corr_iq_msb: u8,
692 pub corr_i_lsb: u8,
693 pub corr_q_lsb: u8,
694 pub carrier_phase_lsb: u16,
695}
696
697#[derive(Debug, Clone)]
701pub struct IqCorrBlock {
702 tow_ms: u32,
703 wnc: u16,
704 pub n: u8,
705 pub sb_length: u8,
706 pub corr_duration: u8,
708 pub cum_clk_jumps: i8,
709 pub channels: Vec<IqCorrChannel>,
710}
711
712impl IqCorrBlock {
713 pub fn tow_seconds(&self) -> f64 {
714 self.tow_ms as f64 * 0.001
715 }
716 pub fn tow_ms(&self) -> u32 {
717 self.tow_ms
718 }
719 pub fn wnc(&self) -> u16 {
720 self.wnc
721 }
722 pub fn num_channels(&self) -> usize {
723 self.channels.len()
724 }
725}
726
727impl SbfBlockParse for IqCorrBlock {
728 const BLOCK_ID: u16 = block_ids::IQ_CORR;
729
730 fn parse(header: &SbfHeader, data: &[u8]) -> SbfResult<Self> {
731 const MIN_HEADER: usize = 16;
733 if data.len() < MIN_HEADER {
734 return Err(SbfError::ParseError("IQCorr too short".into()));
735 }
736
737 let n = data[12];
738 let sb_length = data[13];
739 let corr_duration = data[14];
740 let cum_clk_jumps = data[15] as i8;
741
742 if sb_length < 8 {
743 return Err(SbfError::ParseError("IQCorr SBLength too small".into()));
744 }
745
746 let sb_length_usize = sb_length as usize;
747 let mut channels = Vec::new();
748 let mut offset = 16;
749
750 for _ in 0..n {
751 if offset + sb_length_usize > data.len() {
752 return Err(SbfError::ParseError(
753 "IQCorr sub-block exceeds block length".into(),
754 ));
755 }
756
757 channels.push(IqCorrChannel {
758 rx_channel: data[offset],
759 signal_type: data[offset + 1],
760 svid: data[offset + 2],
761 corr_iq_msb: data[offset + 3],
762 corr_i_lsb: data[offset + 4],
763 corr_q_lsb: data[offset + 5],
764 carrier_phase_lsb: u16::from_le_bytes([data[offset + 6], data[offset + 7]]),
765 });
766
767 offset += sb_length_usize;
768 }
769
770 Ok(Self {
771 tow_ms: header.tow_ms,
772 wnc: header.wnc,
773 n,
774 sb_length,
775 corr_duration,
776 cum_clk_jumps,
777 channels,
778 })
779 }
780}
781
782#[derive(Debug, Clone)]
790pub struct EndOfMeasBlock {
791 tow_ms: u32,
792 wnc: u16,
793}
794
795impl EndOfMeasBlock {
796 pub fn tow_seconds(&self) -> f64 {
797 self.tow_ms as f64 * 0.001
798 }
799 pub fn tow_ms(&self) -> u32 {
800 self.tow_ms
801 }
802 pub fn wnc(&self) -> u16 {
803 self.wnc
804 }
805}
806
807impl SbfBlockParse for EndOfMeasBlock {
808 const BLOCK_ID: u16 = block_ids::END_OF_MEAS;
809
810 fn parse(header: &SbfHeader, data: &[u8]) -> SbfResult<Self> {
811 if data.len() < 12 {
812 return Err(SbfError::ParseError("EndOfMeas too short".into()));
813 }
814
815 Ok(Self {
816 tow_ms: header.tow_ms,
817 wnc: header.wnc,
818 })
819 }
820}
821
822#[cfg(test)]
823mod tests {
824 use super::*;
825 use crate::header::SbfHeader;
826 use crate::types::Constellation;
827
828 #[test]
829 fn test_satellite_measurement_cn0() {
830 let meas = SatelliteMeasurement {
831 sat_id: SatelliteId::new(Constellation::GPS, 1),
832 signal_type: SignalType::L1CA,
833 cn0_raw: 160, doppler_raw: 1000,
835 doppler_valid: true,
836 lock_time_raw: 10,
837 lock_time_valid: true,
838 obs_info: 0,
839 };
840
841 assert_eq!(meas.cn0_dbhz(), 50.0);
842 assert_eq!(meas.cn0_dbhz_opt(), Some(50.0));
843 assert!(meas.cn0_valid());
844 assert_eq!(meas.doppler_hz_opt(), Some(0.1));
845 assert_eq!(meas.lock_time_seconds_opt(), Some(10.0));
846 }
847
848 #[test]
849 fn test_satellite_measurement_cn0_gps_p_no_offset() {
850 let meas = SatelliteMeasurement {
851 sat_id: SatelliteId::new(Constellation::GPS, 1),
852 signal_type: SignalType::L1PY, cn0_raw: 160, doppler_raw: 1000,
855 doppler_valid: true,
856 lock_time_raw: 10,
857 lock_time_valid: true,
858 obs_info: 0,
859 };
860
861 assert_eq!(meas.cn0_dbhz(), 40.0);
862 }
863
864 #[test]
865 fn test_satellite_measurement_invalid_cn0() {
866 let meas = SatelliteMeasurement {
867 sat_id: SatelliteId::new(Constellation::GPS, 1),
868 signal_type: SignalType::L1CA,
869 cn0_raw: 255,
870 doppler_raw: 0,
871 doppler_valid: false,
872 lock_time_raw: 0,
873 lock_time_valid: false,
874 obs_info: 0,
875 };
876
877 assert!(!meas.cn0_valid());
878 assert_eq!(meas.cn0_dbhz_opt(), None);
879 assert_eq!(meas.cn0_dbhz(), 0.0);
880 }
881
882 #[test]
883 fn test_lock_time_encoding() {
884 let meas = SatelliteMeasurement {
886 sat_id: SatelliteId::new(Constellation::GPS, 1),
887 signal_type: SignalType::L1CA,
888 cn0_raw: 160,
889 doppler_raw: 0,
890 doppler_valid: true,
891 lock_time_raw: 30,
892 lock_time_valid: true,
893 obs_info: 0,
894 };
895 assert_eq!(meas.lock_time_seconds(), 30.0);
896 assert_eq!(meas.lock_time_seconds_opt(), Some(30.0));
897
898 let meas2 = SatelliteMeasurement {
900 lock_time_raw: 96,
901 ..meas
902 };
903 assert_eq!(meas2.lock_time_seconds(), 96.0);
904 }
905
906 #[test]
907 fn test_satellite_measurement_doppler_and_lock_time_dnu() {
908 let meas = SatelliteMeasurement {
909 sat_id: SatelliteId::new(Constellation::GPS, 1),
910 signal_type: SignalType::L1CA,
911 cn0_raw: 160,
912 doppler_raw: I32_DNU,
913 doppler_valid: false,
914 lock_time_raw: U16_DNU,
915 lock_time_valid: false,
916 obs_info: 0,
917 };
918
919 assert_eq!(meas.doppler_raw(), I32_DNU);
920 assert_eq!(meas.doppler_hz_opt(), None);
921 assert_eq!(meas.doppler_hz(), 0.0);
922 assert_eq!(meas.lock_time_raw(), U16_DNU);
923 assert_eq!(meas.lock_time_seconds_opt(), None);
924 assert_eq!(meas.lock_time_seconds(), 0.0);
925 }
926
927 #[test]
928 fn test_meas_extra_scaling() {
929 let channel = MeasExtraChannel {
930 rx_channel: 3,
931 signal_type: SignalType::L1CA,
932 signal_type_raw: 0,
933 signal_number: 0,
934 mp_correction_raw: 1234,
935 smoothing_correction_raw: -500,
936 code_var_raw: 200,
937 carrier_var_raw: 150,
938 lock_time_raw: 45,
939 cum_loss_cont: 2,
940 car_mp_correction_raw: None,
941 info: 1,
942 misc_raw: None,
943 };
944
945 assert!((channel.mp_correction_m() - 1.234).abs() < 1e-6);
946 assert!((channel.smoothing_correction_m() + 0.5).abs() < 1e-6);
947 assert!((channel.code_var_m2() - 0.02).abs() < 1e-6);
948 assert!((channel.code_var_m2_opt().unwrap() - 0.02).abs() < 1e-6);
949 assert_eq!(channel.code_var_raw(), 200);
950 assert!((channel.carrier_var_cycles2() - 0.00015).abs() < 1e-9);
951 assert!((channel.carrier_var_cycles2_opt().unwrap() - 0.00015).abs() < 1e-9);
952 assert_eq!(channel.carrier_var_raw(), 150);
953 assert_eq!(channel.lock_time_seconds_opt(), Some(45.0));
954 assert_eq!(channel.lock_time_raw(), 45);
955 assert_eq!(channel.signal_type_raw(), 0);
956 assert_eq!(channel.signal_number(), 0);
957 assert_eq!(channel.antenna_id(), 0);
958 assert_eq!(channel.car_mp_correction_raw(), None);
959 assert_eq!(channel.misc_raw(), None);
960 }
961
962 #[test]
963 fn test_meas_extra_channel_dnu_handling() {
964 let channel = MeasExtraChannel {
965 rx_channel: 3,
966 signal_type: SignalType::L1CA,
967 signal_type_raw: 0,
968 signal_number: 0,
969 mp_correction_raw: 0,
970 smoothing_correction_raw: 0,
971 code_var_raw: U16_DNU,
972 carrier_var_raw: U16_DNU,
973 lock_time_raw: U16_DNU,
974 cum_loss_cont: 0,
975 car_mp_correction_raw: None,
976 info: 0,
977 misc_raw: None,
978 };
979
980 assert_eq!(channel.code_var_raw(), U16_DNU);
981 assert_eq!(channel.code_var_m2_opt(), None);
982 assert_eq!(channel.code_var_m2(), 0.0);
983 assert_eq!(channel.carrier_var_raw(), U16_DNU);
984 assert_eq!(channel.carrier_var_cycles2_opt(), None);
985 assert_eq!(channel.carrier_var_cycles2(), 0.0);
986 assert_eq!(channel.lock_time_seconds_opt(), None);
987 assert_eq!(channel.lock_time_seconds(), 0.0);
988 }
989
990 #[test]
991 fn test_meas_extra_doppler_factor() {
992 let block = MeasExtraBlock {
993 tow_ms: 1000,
994 wnc: 2000,
995 n: 0,
996 sb_length: 14,
997 doppler_var_factor: 1.5,
998 channels: Vec::new(),
999 };
1000
1001 assert_eq!(block.tow_seconds(), 1.0);
1002 assert_eq!(block.wnc(), 2000);
1003 assert!((block.doppler_var_factor() - 1.5).abs() < 1e-6);
1004 assert_eq!(block.num_channels(), 0);
1005 }
1006
1007 #[test]
1008 fn test_iq_corr_parse() {
1009 let mut data = vec![0u8; 32];
1010 data[6..10].copy_from_slice(&5000u32.to_le_bytes());
1011 data[10..12].copy_from_slice(&2100u16.to_le_bytes());
1012 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 {
1025 crc: 0,
1026 block_id: block_ids::IQ_CORR,
1027 block_rev: 0,
1028 length: 32,
1029 tow_ms: 5000,
1030 wnc: 2100,
1031 };
1032 let block = IqCorrBlock::parse(&header, &data).unwrap();
1033 assert_eq!(block.tow_seconds(), 5.0);
1034 assert_eq!(block.wnc(), 2100);
1035 assert_eq!(block.n, 1);
1036 assert_eq!(block.corr_duration, 20);
1037 assert_eq!(block.num_channels(), 1);
1038 assert_eq!(block.channels[0].rx_channel, 2);
1039 assert_eq!(block.channels[0].svid, 7);
1040 assert_eq!(block.channels[0].carrier_phase_lsb, 1000);
1041 }
1042
1043 #[test]
1044 fn test_end_of_meas_accessors() {
1045 let end = EndOfMeasBlock {
1046 tow_ms: 2500,
1047 wnc: 123,
1048 };
1049 assert_eq!(end.tow_ms(), 2500);
1050 assert_eq!(end.wnc(), 123);
1051 assert_eq!(end.tow_seconds(), 2.5);
1052 }
1053
1054 #[test]
1055 fn test_meas_extra_parse() {
1056 let mut data = vec![0u8; 18 + 14];
1057 data[12] = 1; data[13] = 14; data[14..18].copy_from_slice(&1.25_f32.to_le_bytes());
1060
1061 let offset = 18;
1062 data[offset] = 5; data[offset + 1] = 0; data[offset + 2..offset + 4].copy_from_slice(&1000_i16.to_le_bytes());
1065 data[offset + 4..offset + 6].copy_from_slice(&(-200_i16).to_le_bytes());
1066 data[offset + 6..offset + 8].copy_from_slice(&500_u16.to_le_bytes());
1067 data[offset + 8..offset + 10].copy_from_slice(&250_u16.to_le_bytes());
1068 data[offset + 10..offset + 12].copy_from_slice(&60_u16.to_le_bytes());
1069 data[offset + 12] = 3;
1070 data[offset + 13] = 0xA5;
1071
1072 let header = SbfHeader {
1073 crc: 0,
1074 block_id: block_ids::MEAS_EXTRA,
1075 block_rev: 0,
1076 length: (data.len() + 2) as u16,
1077 tow_ms: 123456,
1078 wnc: 321,
1079 };
1080
1081 let parsed = MeasExtraBlock::parse(&header, &data).expect("parse");
1082 assert_eq!(parsed.tow_ms(), 123456);
1083 assert_eq!(parsed.wnc(), 321);
1084 assert_eq!(parsed.n, 1);
1085 assert_eq!(parsed.sb_length, 14);
1086 assert!((parsed.doppler_var_factor() - 1.25).abs() < 1e-6);
1087 assert_eq!(parsed.num_channels(), 1);
1088
1089 let ch = &parsed.channels[0];
1090 assert_eq!(ch.rx_channel, 5);
1091 assert_eq!(ch.signal_type, SignalType::L1CA);
1092 assert_eq!(ch.signal_type_raw(), 0);
1093 assert_eq!(ch.signal_number(), 0);
1094 assert_eq!(ch.antenna_id(), 0);
1095 assert!((ch.mp_correction_m() - 1.0).abs() < 1e-6);
1096 assert!((ch.smoothing_correction_m() + 0.2).abs() < 1e-6);
1097 assert!((ch.code_var_m2() - 0.05).abs() < 1e-6);
1098 assert!((ch.carrier_var_cycles2() - 0.00025).abs() < 1e-9);
1099 assert_eq!(ch.lock_time_raw(), 60);
1100 assert_eq!(ch.cum_loss_cont, 3);
1101 assert_eq!(ch.car_mp_correction_raw(), None);
1102 assert_eq!(ch.info, 0xA5);
1103 assert_eq!(ch.misc_raw(), None);
1104 }
1105
1106 #[test]
1107 fn test_meas_extra_parse_extended_type_and_misc() {
1108 let mut data = vec![0u8; 18 + 16];
1109 data[12] = 1; data[13] = 16; data[14..18].copy_from_slice(&2.0_f32.to_le_bytes());
1112
1113 let offset = 18;
1114 data[offset] = 7; data[offset + 1] = 0x5F; data[offset + 2..offset + 4].copy_from_slice(&0_i16.to_le_bytes());
1117 data[offset + 4..offset + 6].copy_from_slice(&0_i16.to_le_bytes());
1118 data[offset + 6..offset + 8].copy_from_slice(&100_u16.to_le_bytes());
1119 data[offset + 8..offset + 10].copy_from_slice(&1024_u16.to_le_bytes());
1120 data[offset + 10..offset + 12].copy_from_slice(&11_u16.to_le_bytes());
1121 data[offset + 12] = 9; data[offset + 13] = (-64_i8) as u8; data[offset + 14] = 0xB4; data[offset + 15] = 0x33; let header = SbfHeader {
1127 crc: 0,
1128 block_id: block_ids::MEAS_EXTRA,
1129 block_rev: 3,
1130 length: (data.len() + 2) as u16,
1131 tow_ms: 500,
1132 wnc: 2222,
1133 };
1134
1135 let parsed = MeasExtraBlock::parse(&header, &data).expect("parse");
1136 assert_eq!(parsed.num_channels(), 1);
1137
1138 let ch = &parsed.channels[0];
1139 assert_eq!(ch.rx_channel, 7);
1140 assert_eq!(ch.signal_type_raw(), 0x5F);
1141 assert_eq!(ch.antenna_id(), 2);
1142 assert_eq!(ch.signal_number(), 38);
1143 assert_eq!(ch.signal_type, SignalType::QZSSL1CB);
1144 assert_eq!(ch.cum_loss_cont, 9);
1145 assert_eq!(ch.info, 0xB4);
1146 assert_eq!(ch.car_mp_correction_raw(), Some(-64));
1147 assert_eq!(ch.misc_raw(), Some(0x33));
1148 assert!((ch.car_mp_correction_cycles().expect("carmp") + 0.125).abs() < 1e-9);
1149 assert!((ch.cn0_high_res_dbhz_offset().expect("cn0 hi-res") - 0.09375).abs() < 1e-9);
1150 }
1151
1152 #[test]
1153 fn test_end_of_meas_parse() {
1154 let data = vec![0u8; 12];
1155 let header = SbfHeader {
1156 crc: 0,
1157 block_id: block_ids::END_OF_MEAS,
1158 block_rev: 0,
1159 length: (data.len() + 2) as u16,
1160 tow_ms: 1000,
1161 wnc: 45,
1162 };
1163
1164 let parsed = EndOfMeasBlock::parse(&header, &data).expect("parse");
1165 assert_eq!(parsed.tow_ms(), 1000);
1166 assert_eq!(parsed.wnc(), 45);
1167 }
1168}