sbf-tools 0.3.0

Septentrio Binary Format (SBF) parser library
Documentation
use std::collections::HashMap;
use std::env;
use std::error::Error;
use std::fs::File;

use sbf_tools::{Meas3BlockSet, Meas3Decoder, SbfBlock, SbfReader};

fn main() -> Result<(), Box<dyn Error>> {
    let path = env::args()
        .nth(1)
        .ok_or("usage: cargo run --example decode_meas3 -- <path-to-file.sbf>")?;

    let file = File::open(&path)?;
    let reader = SbfReader::new(file);

    let mut decoder = Meas3Decoder::new();
    let mut current_tow: Option<u32> = None;
    let mut bundles: HashMap<u8, Meas3BlockSet> = HashMap::new();

    for block in reader {
        let block = block?;

        if let Some((tow_ms, antenna_id)) = meas3_epoch_key(&block) {
            if current_tow != Some(tow_ms) {
                flush_bundles(&mut bundles, &mut decoder)?;
                current_tow = Some(tow_ms);
            }
            bundles.entry(antenna_id).or_default().insert_block(&block);
            continue;
        }

        if matches!(block, SbfBlock::EndOfMeas(_)) {
            flush_bundles(&mut bundles, &mut decoder)?;
            current_tow = None;
        }
    }

    flush_bundles(&mut bundles, &mut decoder)?;
    Ok(())
}

fn meas3_epoch_key(block: &SbfBlock) -> Option<(u32, u8)> {
    match block {
        SbfBlock::Meas3Ranges(b) => Some((b.tow_ms(), b.antenna_id())),
        SbfBlock::Meas3Cn0HiRes(b) => Some((b.tow_ms(), b.antenna_id())),
        SbfBlock::Meas3Doppler(b) => Some((b.tow_ms(), b.antenna_id())),
        SbfBlock::Meas3Pp(b) => Some((b.tow_ms(), b.antenna_id())),
        SbfBlock::Meas3Mp(b) => Some((b.tow_ms(), b.antenna_id())),
        _ => None,
    }
}

fn flush_bundles(
    bundles: &mut HashMap<u8, Meas3BlockSet>,
    decoder: &mut Meas3Decoder,
) -> Result<(), Box<dyn Error>> {
    let mut antenna_ids: Vec<u8> = bundles.keys().copied().collect();
    antenna_ids.sort_unstable();

    for antenna_id in antenna_ids {
        if let Some(block_set) = bundles.get(&antenna_id) {
            if block_set.ranges.is_none() {
                continue;
            }

            let epoch = decoder.decode_block_set(block_set)?;
            println!(
                "TOW {} WNc {} antenna {}: {} satellites, {} measurements",
                epoch.tow_ms(),
                epoch.wnc(),
                epoch.antenna_id,
                epoch.num_satellites(),
                epoch.num_measurements()
            );

            for satellite in epoch.satellites.iter().take(4) {
                let signal_summary = satellite
                    .measurements
                    .iter()
                    .map(|meas| match meas.cn0_dbhz() {
                        Some(cn0) => format!("{}:{cn0:.2}dB-Hz", meas.signal_type),
                        None => meas.signal_type.to_string(),
                    })
                    .collect::<Vec<_>>()
                    .join(", ");
                println!("  {}  {}", satellite.sat_id, signal_summary);
            }
        }
    }

    bundles.clear();
    Ok(())
}