use super::tables::{WeilRow, B1C_DATA, B1C_PILOT, B1C_PILOT_SECONDARY, B1I_G2_TAPS, B1I_GEO_PRNS};
use super::{
bipolar, square_subcarrier, ComplexSpreadingCode, Modulation, SignalCode, SignalError,
};
use crate::iq::{Cf64, DataModulation, SpreadingCode};
pub const B1C_HZ: f64 = 1_575_420_000.0;
pub const B1I_HZ: f64 = 1_561_098_000.0;
pub const B1C_CHIP_RATE_HZ: f64 = 1_023_000.0;
pub const B1C_CODE_LEN: usize = 10_230;
pub const B1C_WEIL_LEN: usize = 10_243;
pub const B1C_SECONDARY_LEN: usize = 1800;
pub const B1C_SECONDARY_WEIL_LEN: usize = 3607;
pub const B1I_CHIP_RATE_HZ: f64 = 2_046_000.0;
pub const B1I_CODE_LEN: usize = 2046;
pub const B1I_NH20: [u8; 20] = [0, 0, 0, 0, 0, 1, 0, 0, 1, 1, 0, 1, 0, 1, 0, 0, 1, 1, 1, 0];
pub fn legendre(n: usize) -> Vec<u8> {
let mut l = vec![0u8; n];
for x in 1..n {
l[(x * x) % n] = 1;
}
l
}
pub fn weil_bits(n: usize, len: usize, w: usize, p: usize) -> Vec<u8> {
let l = legendre(n);
(0..len)
.map(|k| {
let i = (k + p - 1) % n;
l[i] ^ l[(i + w) % n]
})
.collect()
}
fn row(
table: &'static [WeilRow; 63],
signal: &'static str,
prn: u16,
) -> Result<&'static WeilRow, SignalError> {
(prn as usize)
.checked_sub(1)
.and_then(|i| table.get(i))
.ok_or(SignalError::UnknownPrn { signal, prn })
}
pub fn b1c_data_bits(prn: u16) -> Result<Vec<u8>, SignalError> {
let r = row(&B1C_DATA, "BeiDou B1C data", prn)?;
Ok(weil_bits(
B1C_WEIL_LEN,
B1C_CODE_LEN,
r.w as usize,
r.p as usize,
))
}
pub fn b1c_pilot_bits(prn: u16) -> Result<Vec<u8>, SignalError> {
let r = row(&B1C_PILOT, "BeiDou B1C pilot", prn)?;
Ok(weil_bits(
B1C_WEIL_LEN,
B1C_CODE_LEN,
r.w as usize,
r.p as usize,
))
}
pub fn b1c_pilot_secondary_bits(prn: u16) -> Result<Vec<u8>, SignalError> {
let r = row(&B1C_PILOT_SECONDARY, "BeiDou B1C pilot secondary", prn)?;
Ok(weil_bits(
B1C_SECONDARY_WEIL_LEN,
B1C_SECONDARY_LEN,
r.w as usize,
r.p as usize,
))
}
pub fn b1c_data(prn: u16) -> Result<SignalCode, SignalError> {
Ok(SignalCode::new(
format!("BeiDou B1C data PRN {prn}"),
B1C_CHIP_RATE_HZ,
B1C_HZ,
bipolar(&b1c_data_bits(prn)?),
vec![],
Modulation::SineBoc { n: 1 },
)
.with_data_modulation(DataModulation::Symbols { symbol_s: 0.01 }))
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum QmbocPart {
Boc11,
Boc61,
}
#[derive(Clone, Debug, PartialEq)]
pub struct B1cPilot {
code: SignalCode,
part: QmbocPart,
}
impl B1cPilot {
pub fn code(&self) -> &SignalCode {
&self.code
}
pub fn part(&self) -> QmbocPart {
self.part
}
pub fn with_part(mut self, part: QmbocPart) -> Self {
self.part = part;
self
}
pub fn primary_only(&self) -> Self {
Self {
code: self.code.primary_only(),
part: self.part,
}
}
}
fn split(phase: f64, len: usize) -> (i64, f64) {
let p = phase.rem_euclid(len as f64);
let i = p.floor();
(
(i as i64).min(len as i64 - 1),
(p - i).clamp(0.0, 1.0 - f64::EPSILON),
)
}
impl SpreadingCode for B1cPilot {
fn name(&self) -> String {
let part = match self.part {
QmbocPart::Boc11 => "BOC(1,1) part",
QmbocPart::Boc61 => "BOC(6,1) part",
};
format!("{} [{part}]", self.code.name())
}
fn chip_rate_hz(&self) -> f64 {
self.code.chip_rate_hz()
}
fn len_chips(&self) -> usize {
self.code.len_chips()
}
fn carrier_hz(&self) -> f64 {
self.code.carrier_hz()
}
fn data_modulation(&self) -> DataModulation {
self.code.data_modulation()
}
fn value_at(&self, code_phase_chips: f64) -> f64 {
let (k, frac) = split(code_phase_chips, self.len_chips());
let n = match self.part {
QmbocPart::Boc11 => 1,
QmbocPart::Boc61 => 6,
};
self.code.chip(k) * square_subcarrier(n, frac)
}
}
impl ComplexSpreadingCode for B1cPilot {
fn complex_at(&self, code_phase_chips: f64) -> Cf64 {
let (k, frac) = split(code_phase_chips, self.len_chips());
let c = self.code.chip(k);
let a = (29.0f64 / 33.0).sqrt() * square_subcarrier(1, frac);
let b = (4.0f64 / 33.0).sqrt() * square_subcarrier(6, frac);
Cf64::new(c * a, -c * b)
}
}
pub fn b1c_pilot(prn: u16) -> Result<B1cPilot, SignalError> {
Ok(B1cPilot {
code: SignalCode::new(
format!("BeiDou B1C pilot PRN {prn}"),
B1C_CHIP_RATE_HZ,
B1C_HZ,
bipolar(&b1c_pilot_bits(prn)?),
bipolar(&b1c_pilot_secondary_bits(prn)?),
Modulation::SineBoc { n: 1 },
)
.with_data_modulation(DataModulation::Pilot),
part: QmbocPart::Boc11,
})
}
const G1_TAPS: [usize; 6] = [1, 7, 8, 9, 10, 11];
const G2_TAPS: [usize; 8] = [1, 2, 3, 4, 5, 8, 9, 11];
const B1I_INIT: [u8; 11] = [0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0];
fn shift11(r: &mut [u8; 11], taps: &[usize]) {
let fb = taps.iter().fold(0u8, |acc, &t| acc ^ r[t - 1]);
r.copy_within(0..10, 1);
r[0] = fb;
}
pub fn b1i_bits(prn: u16, len: usize) -> Result<Vec<u8>, SignalError> {
let taps = (prn as usize)
.checked_sub(1)
.and_then(|i| B1I_G2_TAPS.get(i))
.ok_or(SignalError::UnknownPrn {
signal: "BeiDou B1I",
prn,
})?;
let (mut g1, mut g2) = (B1I_INIT, B1I_INIT);
let mut out = Vec::with_capacity(len);
for _ in 0..len {
let g2_out = taps
.iter()
.filter(|&&t| t != 0)
.fold(0u8, |acc, &t| acc ^ g2[t as usize - 1]);
out.push(g1[10] ^ g2_out);
shift11(&mut g1, &G1_TAPS);
shift11(&mut g2, &G2_TAPS);
}
Ok(out)
}
pub fn b1i_is_geo(prn: u16) -> bool {
B1I_GEO_PRNS.iter().any(|&g| u16::from(g) == prn)
}
pub fn b1i(prn: u16) -> Result<SignalCode, SignalError> {
let bits = b1i_bits(prn, B1I_CODE_LEN)?;
let secondary = if b1i_is_geo(prn) {
vec![]
} else {
bipolar(&B1I_NH20)
};
Ok(SignalCode::new(
format!("BeiDou B1I PRN {prn}"),
B1I_CHIP_RATE_HZ,
B1I_HZ,
bipolar(&bits),
secondary,
Modulation::Bpsk,
)
.with_data_modulation(DataModulation::Symbols {
symbol_s: if b1i_is_geo(prn) { 0.002 } else { 0.02 },
}))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn legendre_example_length_7() {
assert_eq!(legendre(7), vec![0, 1, 1, 0, 1, 0, 0]);
}
#[test]
fn prn_range_is_enforced() {
assert!(b1c_data(0).is_err());
assert!(b1c_pilot(64).is_err());
assert!(b1i(64).is_err());
assert!(b1i_is_geo(1) && b1i_is_geo(63) && !b1i_is_geo(6));
}
}