pub use crate::sdr::Cf64;
pub mod acq;
pub mod acq_surface;
#[cfg(not(target_arch = "wasm32"))]
pub mod campaign;
pub mod channel;
#[cfg(not(target_arch = "wasm32"))]
pub mod cli;
pub mod frontend;
pub mod io;
pub mod labfit;
pub mod monitor;
pub mod scene;
pub mod signals;
pub(crate) mod simrng;
pub mod track;
pub const C_M_PER_S: f64 = 299_792_458.0;
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct SampleSpec {
pub fs_hz: f64,
pub center_hz: f64,
pub if_hz: f64,
}
impl SampleSpec {
pub fn baseband_hz(&self, carrier_hz: f64) -> f64 {
if self.center_hz > 0.0 {
self.if_hz + (carrier_hz - self.center_hz)
} else {
self.if_hz
}
}
pub fn samples_in(&self, seconds: f64) -> usize {
(seconds * self.fs_hz).round().max(0.0) as usize
}
pub fn time_of(&self, k: u64) -> f64 {
k as f64 / self.fs_hz
}
}
pub trait SpreadingCode {
fn name(&self) -> String;
fn chip_rate_hz(&self) -> f64;
fn len_chips(&self) -> usize;
fn carrier_hz(&self) -> f64;
fn value_at(&self, code_phase_chips: f64) -> f64;
fn period_s(&self) -> f64 {
self.len_chips() as f64 / self.chip_rate_hz()
}
fn data_modulation(&self) -> DataModulation {
DataModulation::Lnav
}
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum DataModulation {
Pilot,
Lnav,
Symbols {
symbol_s: f64,
},
Meander {
bit_s: f64,
},
NotModelled,
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct PathState {
pub group_delay_s: f64,
pub carrier_phase_rad: f64,
pub amplitude: f64,
pub extra_doppler_hz: f64,
}
#[derive(Clone, Debug, Default, PartialEq)]
pub struct ChannelSnapshot {
pub t_s: f64,
pub paths: Vec<PathState>,
}
impl ChannelSnapshot {
pub fn is_nlos(&self) -> bool {
match self.paths.split_first() {
Some((direct, rest)) => {
direct.amplitude == 0.0 && rest.iter().any(|p| p.amplitude > 0.0)
}
None => false,
}
}
}
#[derive(Debug)]
pub enum IqError {
Io(String),
Format(String),
}
impl std::fmt::Display for IqError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
IqError::Io(m) => write!(f, "I/O error: {m}"),
IqError::Format(m) => write!(f, "format error: {m}"),
}
}
}
impl std::error::Error for IqError {}
pub trait IqSource {
fn spec(&self) -> SampleSpec;
fn read(&mut self, buf: &mut [Cf64]) -> Result<usize, IqError>;
}
pub trait IqSink {
fn write(&mut self, block: &[Cf64]) -> Result<(), IqError>;
fn finish(&mut self) -> Result<(), IqError> {
Ok(())
}
}
#[derive(Clone, Debug, Default)]
pub struct VecSink {
pub samples: Vec<Cf64>,
}
impl IqSink for VecSink {
fn write(&mut self, block: &[Cf64]) -> Result<(), IqError> {
self.samples.extend_from_slice(block);
Ok(())
}
}
#[derive(Clone, Debug)]
pub struct VecSource {
spec: SampleSpec,
samples: Vec<Cf64>,
pos: usize,
}
impl VecSource {
pub fn new(spec: SampleSpec, samples: Vec<Cf64>) -> Self {
Self {
spec,
samples,
pos: 0,
}
}
}
impl IqSource for VecSource {
fn spec(&self) -> SampleSpec {
self.spec
}
fn read(&mut self, buf: &mut [Cf64]) -> Result<usize, IqError> {
let n = buf.len().min(self.samples.len() - self.pos);
buf[..n].copy_from_slice(&self.samples[self.pos..self.pos + n]);
self.pos += n;
Ok(n)
}
}
#[cfg(test)]
mod tests {
use super::*;
fn spec() -> SampleSpec {
SampleSpec {
fs_hz: 5.0e6,
center_hz: 1_575_420_000.0,
if_hz: 0.0,
}
}
#[test]
fn sample_spec_counts_and_times() {
let s = spec();
assert_eq!(s.samples_in(0.001), 5000);
assert!((s.time_of(5000) - 0.001).abs() < 1e-15);
}
#[test]
fn vec_source_reads_in_chunks_then_ends() {
let data: Vec<Cf64> = (0..10).map(|k| Cf64::new(k as f64, 0.0)).collect();
let mut src = VecSource::new(spec(), data.clone());
let mut buf = [Cf64::default(); 4];
let mut sink = VecSink::default();
loop {
let n = src.read(&mut buf).unwrap();
if n == 0 {
break;
}
sink.write(&buf[..n]).unwrap();
}
assert_eq!(sink.samples, data);
}
#[test]
fn nlos_needs_a_blocked_direct_path_and_a_live_reflection() {
let p = |a: f64| PathState {
group_delay_s: 0.0,
carrier_phase_rad: 0.0,
amplitude: a,
extra_doppler_hz: 0.0,
};
assert!(ChannelSnapshot {
t_s: 0.0,
paths: vec![p(0.0), p(0.3)]
}
.is_nlos());
assert!(!ChannelSnapshot {
t_s: 0.0,
paths: vec![p(1.0), p(0.3)]
}
.is_nlos());
assert!(!ChannelSnapshot {
t_s: 0.0,
paths: vec![p(0.0)]
}
.is_nlos());
assert!(!ChannelSnapshot::default().is_nlos());
}
}