pub mod beidou;
pub mod by_name;
pub mod galileo;
pub mod glonass;
pub mod gps;
pub mod tables;
use super::{Cf64, DataModulation, SpreadingCode};
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum SignalError {
UnknownPrn {
signal: &'static str,
prn: u16,
},
FrequencyChannel(i8),
Table(String),
}
impl std::fmt::Display for SignalError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
SignalError::UnknownPrn { signal, prn } => {
write!(f, "{signal}: PRN {prn} is not defined by the ICD")
}
SignalError::FrequencyChannel(k) => {
write!(f, "GLONASS frequency channel {k} is outside -7..=6")
}
SignalError::Table(m) => write!(f, "code table: {m}"),
}
}
}
impl std::error::Error for SignalError {}
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum Modulation {
Bpsk,
SineBoc {
n: u32,
},
Cboc {
plus: bool,
},
Tmboc,
}
pub const TMBOC_BLOCK: usize = 33;
pub const TMBOC_BOC61_SLOTS: [usize; 4] = [0, 4, 6, 29];
pub fn tmboc_is_boc61(t: usize) -> bool {
TMBOC_BOC61_SLOTS.contains(&(t % TMBOC_BLOCK))
}
pub fn square_subcarrier(n: u32, frac: f64) -> f64 {
let half_periods = (frac * 2.0 * n as f64).floor() as i64;
if half_periods.rem_euclid(2) == 0 {
1.0
} else {
-1.0
}
}
impl Modulation {
pub fn subcarrier(&self, frac: f64) -> f64 {
match *self {
Modulation::Bpsk => 1.0,
Modulation::SineBoc { n } => square_subcarrier(n, frac),
Modulation::Cboc { plus } => {
let alpha = (10.0f64 / 11.0).sqrt();
let beta = (1.0f64 / 11.0).sqrt();
let a = square_subcarrier(1, frac);
let b = square_subcarrier(6, frac);
if plus {
alpha * a + beta * b
} else {
alpha * a - beta * b
}
}
Modulation::Tmboc => square_subcarrier(1, frac),
}
}
pub fn subcarrier_at(&self, chip: usize, frac: f64) -> f64 {
match *self {
Modulation::Tmboc if tmboc_is_boc61(chip) => square_subcarrier(6, frac),
_ => self.subcarrier(frac),
}
}
}
pub fn bipolar(bits: &[u8]) -> Vec<i8> {
bits.iter().map(|&b| if b == 0 { 1 } else { -1 }).collect()
}
pub fn hex_to_bits(hex: &str, len: usize) -> Result<Vec<u8>, SignalError> {
let mut bits = Vec::with_capacity(hex.len() * 4);
for c in hex.trim().chars() {
let v = c
.to_digit(16)
.ok_or_else(|| SignalError::Table(format!("non-hex character {c:?}")))?;
for k in (0..4).rev() {
bits.push(((v >> k) & 1) as u8);
}
}
if bits.len() < len || bits.len() >= len + 4 {
return Err(SignalError::Table(format!(
"{} hex symbols cannot hold exactly {len} chips",
hex.trim().len()
)));
}
bits.truncate(len);
Ok(bits)
}
pub fn pack_bits(bits: &[u8]) -> u32 {
bits.iter()
.fold(0u32, |acc, &b| (acc << 1) | (b & 1) as u32)
}
#[derive(Clone, Debug, PartialEq)]
pub struct SignalCode {
name: String,
chip_rate_hz: f64,
carrier_hz: f64,
primary: Vec<i8>,
secondary: Vec<i8>,
modulation: Modulation,
data: DataModulation,
}
impl SignalCode {
pub fn new(
name: impl Into<String>,
chip_rate_hz: f64,
carrier_hz: f64,
primary: Vec<i8>,
secondary: Vec<i8>,
modulation: Modulation,
) -> Self {
assert!(
!primary.is_empty(),
"a spreading code needs at least one chip"
);
let secondary = if secondary.is_empty() {
vec![1]
} else {
secondary
};
Self {
name: name.into(),
chip_rate_hz,
carrier_hz,
primary,
secondary,
modulation,
data: DataModulation::Lnav,
}
}
pub fn with_data_modulation(mut self, data: DataModulation) -> Self {
self.data = data;
self
}
pub fn primary(&self) -> &[i8] {
&self.primary
}
pub fn secondary(&self) -> &[i8] {
&self.secondary
}
pub fn primary_bits(&self) -> Vec<u8> {
self.primary.iter().map(|&c| u8::from(c < 0)).collect()
}
pub fn modulation(&self) -> Modulation {
self.modulation
}
pub fn primary_len(&self) -> usize {
self.primary.len()
}
pub fn primary_only(&self) -> Self {
Self {
name: format!("{} (primary only)", self.name),
secondary: vec![1],
..self.clone()
}
}
pub fn with_carrier_hz(mut self, carrier_hz: f64) -> Self {
self.carrier_hz = carrier_hz;
self
}
pub fn chip(&self, k: i64) -> f64 {
let total = (self.primary.len() * self.secondary.len()) as i64;
let k = k.rem_euclid(total) as usize;
let n = self.primary.len();
f64::from(self.primary[k % n] * self.secondary[k / n])
}
}
fn split_phase(phase: f64, len: usize) -> (usize, f64) {
let p = phase.rem_euclid(len as f64);
let i = p.floor();
let idx = (i as usize).min(len - 1);
(idx, (p - i).clamp(0.0, 1.0 - f64::EPSILON))
}
impl SpreadingCode for SignalCode {
fn name(&self) -> String {
self.name.clone()
}
fn chip_rate_hz(&self) -> f64 {
self.chip_rate_hz
}
fn len_chips(&self) -> usize {
self.primary.len() * self.secondary.len()
}
fn carrier_hz(&self) -> f64 {
self.carrier_hz
}
fn data_modulation(&self) -> DataModulation {
self.data
}
fn value_at(&self, code_phase_chips: f64) -> f64 {
let (k, frac) = split_phase(code_phase_chips, self.len_chips());
self.chip(k as i64) * self.modulation.subcarrier_at(k % self.primary.len(), frac)
}
}
pub trait ComplexSpreadingCode: SpreadingCode {
fn complex_at(&self, code_phase_chips: f64) -> Cf64;
}
impl ComplexSpreadingCode for SignalCode {
fn complex_at(&self, code_phase_chips: f64) -> Cf64 {
Cf64::new(self.value_at(code_phase_chips), 0.0)
}
}
pub fn periodic_autocorrelation(chips: &[i8], lag: usize) -> i64 {
let n = chips.len();
(0..n)
.map(|k| i64::from(chips[k]) * i64::from(chips[(k + lag) % n]))
.sum()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn hex_expansion_follows_galileo_annex_c2_example() {
let bits = hex_to_bits("EC4", 10).unwrap();
assert_eq!(bits, vec![1, 1, 1, 0, 1, 1, 0, 0, 0, 1]);
assert!(hex_to_bits("EC4", 13).is_err());
assert!(hex_to_bits("EG4", 10).is_err());
}
#[test]
fn secondary_cs25_matches_icd_binary_example() {
let bits = hex_to_bits("380AD90", 25).unwrap();
let want = "0011100000001010110110010";
let got: String = bits.iter().map(|b| char::from(b'0' + b)).collect();
assert_eq!(got, want);
}
#[test]
fn phase_wraps_and_fraction_is_in_chip() {
let c = SignalCode::new("t", 1.0, 1.0, vec![1, -1, 1], vec![], Modulation::Bpsk);
assert_eq!(c.value_at(1.5), -1.0);
assert_eq!(c.value_at(-1.5), -1.0);
assert_eq!(c.value_at(3.0 + 0.25), 1.0);
assert_eq!(c.value_at(-1e-18), 1.0);
}
#[test]
fn boc_subcarrier_halves() {
assert_eq!(square_subcarrier(1, 0.0), 1.0);
assert_eq!(square_subcarrier(1, 0.49), 1.0);
assert_eq!(square_subcarrier(1, 0.5), -1.0);
assert_eq!(square_subcarrier(6, 1.0 / 12.0 + 1e-9), -1.0);
assert_eq!(square_subcarrier(6, 2.0 / 12.0 + 1e-9), 1.0);
}
}