use super::fec::{conv_encode, crc24q};
use super::{get, int, p2, pack, pow2, push_bits, read_bits, sc, unpack, Decoded, Field};
use crate::gps_lnav::GPS_PI;
use crate::rinex::RinexEphemeris;
pub const PREAMBLE: u64 = 0x8B;
pub const MESSAGE_BITS: usize = 300;
pub const A_REF_M: f64 = 26_559_710.0;
pub const OMEGA_DOT_REF_SC: f64 = -2.6e-9;
pub const GROUP_DELAY_NA: u64 = 0x1000;
pub const HEADER: &[Field] = &[
int("preamble", 8),
int("prn", 6),
int("msg_type", 6),
int("tow_count", 17),
int("alert", 1),
];
pub const TYPE10: &[Field] = &[
int("wn", 13),
int("l1_health", 1),
int("l2_health", 1),
int("l5_health", 1),
p2("top", 11, false, 300.0),
p2("ura_ed", 5, true, 1.0),
p2("toe", 11, false, 300.0),
p2("delta_a", 26, true, pow2(-9)),
p2("a_dot", 25, true, pow2(-21)),
sc("delta_n0", 17, true, pow2(-44)),
sc("delta_n0_dot", 23, true, pow2(-57)),
sc("m0", 33, true, pow2(-32)),
p2("e", 33, false, pow2(-34)),
sc("omega", 33, true, pow2(-32)),
int("integrity", 1),
int("l2c_phasing", 1),
int("reserved", 3),
];
pub const TYPE11: &[Field] = &[
p2("toe", 11, false, 300.0),
sc("omega0", 33, true, pow2(-32)),
sc("i0", 33, true, pow2(-32)),
sc("delta_omega_dot", 17, true, pow2(-44)),
sc("i0_dot", 15, true, pow2(-44)),
p2("cis", 16, true, pow2(-30)),
p2("cic", 16, true, pow2(-30)),
p2("crs", 24, true, pow2(-8)),
p2("crc", 24, true, pow2(-8)),
p2("cus", 21, true, pow2(-30)),
p2("cuc", 21, true, pow2(-30)),
int("reserved", 7),
];
pub const CLOCK: &[Field] = &[
p2("top", 11, false, 300.0),
p2("ura_ned0", 5, true, 1.0),
int("ura_ned1", 3),
int("ura_ned2", 3),
p2("toc", 11, false, 300.0),
p2("af0", 26, true, pow2(-35)),
p2("af1", 20, true, pow2(-48)),
p2("af2", 10, true, pow2(-60)),
];
pub const TYPE30_REST: &[Field] = &[
p2("tgd", 13, true, pow2(-35)),
p2("isc_l1ca", 13, true, pow2(-35)),
p2("isc_l2c", 13, true, pow2(-35)),
p2("isc_l5i5", 13, true, pow2(-35)),
p2("isc_l5q5", 13, true, pow2(-35)),
p2("alpha0", 8, true, pow2(-30)),
p2("alpha1", 8, true, pow2(-27)),
p2("alpha2", 8, true, pow2(-24)),
p2("alpha3", 8, true, pow2(-24)),
p2("beta0", 8, true, pow2(11)),
p2("beta1", 8, true, pow2(14)),
p2("beta2", 8, true, pow2(16)),
p2("beta3", 8, true, pow2(16)),
int("wnop", 8),
int("reserved", 12),
];
pub fn type30_layout() -> Vec<Field> {
CLOCK.iter().chain(TYPE30_REST).copied().collect()
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub struct CnavHeader {
pub prn: u32,
pub tow_count: u32,
pub alert: bool,
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct CnavEphemeris {
pub week: u32,
pub toe_s: f64,
pub top_s: f64,
pub ura_ed: i32,
pub health: [bool; 3],
pub a_m: f64,
pub a_dot: f64,
pub delta_n0: f64,
pub delta_n0_dot: f64,
pub m0: f64,
pub e: f64,
pub omega: f64,
pub omega0: f64,
pub i0: f64,
pub omega_dot: f64,
pub i0_dot: f64,
pub cis: f64,
pub cic: f64,
pub crs: f64,
pub crc: f64,
pub cus: f64,
pub cuc: f64,
pub integrity: bool,
pub l2c_phasing: bool,
}
#[derive(Clone, Copy, Debug, Default, PartialEq)]
pub struct CnavClock {
pub top_s: f64,
pub ura_ned0: i32,
pub ura_ned1: u32,
pub ura_ned2: u32,
pub toc_s: f64,
pub af0: f64,
pub af1: f64,
pub af2: f64,
}
#[derive(Clone, Copy, Debug, Default, PartialEq)]
pub struct CnavGroupDelays {
pub tgd: Option<f64>,
pub isc_l1ca: Option<f64>,
pub isc_l2c: Option<f64>,
pub isc_l5i5: Option<f64>,
pub isc_l5q5: Option<f64>,
}
#[derive(Clone, Copy, Debug, Default, PartialEq)]
pub struct Klobuchar {
pub alpha: [f64; 4],
pub beta: [f64; 4],
}
fn b(x: bool) -> f64 {
f64::from(u8::from(x))
}
pub fn type10_value(e: &CnavEphemeris, name: &str) -> Result<f64, String> {
Ok(match name {
"wn" => f64::from(e.week % 8192),
"l1_health" => b(e.health[0]),
"l2_health" => b(e.health[1]),
"l5_health" => b(e.health[2]),
"top" => e.top_s,
"ura_ed" => f64::from(e.ura_ed),
"toe" => e.toe_s,
"delta_a" => e.a_m - A_REF_M,
"a_dot" => e.a_dot,
"delta_n0" => e.delta_n0,
"delta_n0_dot" => e.delta_n0_dot,
"m0" => e.m0,
"e" => e.e,
"omega" => e.omega,
"integrity" => b(e.integrity),
"l2c_phasing" => b(e.l2c_phasing),
"reserved" => 0.0,
_ => return Err(format!("type 10 has no field {name}")),
})
}
pub fn type11_value(e: &CnavEphemeris, name: &str) -> Result<f64, String> {
Ok(match name {
"toe" => e.toe_s,
"omega0" => e.omega0,
"i0" => e.i0,
"delta_omega_dot" => e.omega_dot - OMEGA_DOT_REF_SC * GPS_PI,
"i0_dot" => e.i0_dot,
"cis" => e.cis,
"cic" => e.cic,
"crs" => e.crs,
"crc" => e.crc,
"cus" => e.cus,
"cuc" => e.cuc,
"reserved" => 0.0,
_ => return Err(format!("type 11 has no field {name}")),
})
}
pub fn type30_value(
c: &CnavClock,
g: &CnavGroupDelays,
k: &Klobuchar,
wnop: u32,
name: &str,
) -> Result<f64, String> {
let gd = |x: Option<f64>| x.unwrap_or(f64::NAN);
Ok(match name {
"top" => c.top_s,
"ura_ned0" => f64::from(c.ura_ned0),
"ura_ned1" => f64::from(c.ura_ned1),
"ura_ned2" => f64::from(c.ura_ned2),
"toc" => c.toc_s,
"af0" => c.af0,
"af1" => c.af1,
"af2" => c.af2,
"tgd" => gd(g.tgd),
"isc_l1ca" => gd(g.isc_l1ca),
"isc_l2c" => gd(g.isc_l2c),
"isc_l5i5" => gd(g.isc_l5i5),
"isc_l5q5" => gd(g.isc_l5q5),
"alpha0" => k.alpha[0],
"alpha1" => k.alpha[1],
"alpha2" => k.alpha[2],
"alpha3" => k.alpha[3],
"beta0" => k.beta[0],
"beta1" => k.beta[1],
"beta2" => k.beta[2],
"beta3" => k.beta[3],
"wnop" => f64::from(wnop & 0xFF),
"reserved" => 0.0,
_ => return Err(format!("type 30 has no field {name}")),
})
}
pub fn assemble(h: &CnavHeader, msg_type: u32, body: &[u8]) -> Result<Vec<u8>, String> {
if body.len() != 238 {
return Err(format!("CNAV body has {} bits, expected 238", body.len()));
}
if h.prn > 63 || h.tow_count >= 100_800 {
return Err(format!(
"PRN {} or TOW count {} out of range",
h.prn, h.tow_count
));
}
let mut m = Vec::with_capacity(MESSAGE_BITS);
push_bits(&mut m, PREAMBLE, 8);
push_bits(&mut m, u64::from(h.prn), 6);
push_bits(&mut m, u64::from(msg_type), 6);
push_bits(&mut m, u64::from(h.tow_count), 17);
push_bits(&mut m, u64::from(h.alert), 1);
m.extend_from_slice(body);
let crc = crc24q(&m);
push_bits(&mut m, u64::from(crc), 24);
Ok(m)
}
pub fn encode_type10(h: &CnavHeader, e: &CnavEphemeris) -> Result<Vec<u8>, String> {
assemble(h, 10, &pack(TYPE10, |n| type10_value(e, n))?)
}
pub fn encode_type11(h: &CnavHeader, e: &CnavEphemeris) -> Result<Vec<u8>, String> {
assemble(h, 11, &pack(TYPE11, |n| type11_value(e, n))?)
}
pub fn encode_type30(
h: &CnavHeader,
c: &CnavClock,
g: &CnavGroupDelays,
k: &Klobuchar,
wnop: u32,
) -> Result<Vec<u8>, String> {
let mut body = Vec::with_capacity(238);
for f in type30_layout() {
let v = type30_value(c, g, k, wnop, f.name)?;
let raw = if v.is_nan() {
GROUP_DELAY_NA
} else {
f.quantise(v)?
};
push_bits(&mut body, raw, f.bits);
}
assemble(h, 30, &body)
}
pub fn fec_encode(bits: &[u8], state: &mut u8) -> Vec<u8> {
conv_encode(bits, state, false)
}
#[derive(Clone, Debug, PartialEq)]
pub struct CnavMessage {
pub header: CnavHeader,
pub msg_type: u32,
pub fields: Vec<Decoded>,
}
pub fn decode_message(bits: &[u8]) -> Result<CnavMessage, String> {
if bits.len() != MESSAGE_BITS {
return Err(format!("{} bits, expected {MESSAGE_BITS}", bits.len()));
}
if read_bits(bits, 0, 8) != PREAMBLE {
return Err("preamble is not 10001011".into());
}
let crc = read_bits(bits, 276, 24) as u32;
if crc24q(&bits[..276]) != crc {
return Err("CRC-24Q mismatch".into());
}
let h = unpack(HEADER, bits)?;
let header = CnavHeader {
prn: get(&h, "prn")?.raw as u32,
tow_count: get(&h, "tow_count")?.raw as u32,
alert: get(&h, "alert")?.raw == 1,
};
let msg_type = get(&h, "msg_type")?.raw as u32;
let body = &bits[38..276];
let fields = match msg_type {
10 => unpack(TYPE10, body)?,
11 => unpack(TYPE11, body)?,
30 => unpack(&type30_layout(), body)?,
t => return Err(format!("message type {t} is not decoded here")),
};
Ok(CnavMessage {
header,
msg_type,
fields,
})
}
pub fn ephemeris_from_messages(
m10: &CnavMessage,
m11: &CnavMessage,
) -> Result<CnavEphemeris, String> {
if m10.msg_type != 10 || m11.msg_type != 11 {
return Err("need a type 10 and a type 11 message".into());
}
let a = |n: &str| get(&m10.fields, n).map(|d| d.value);
let c = |n: &str| get(&m11.fields, n).map(|d| d.value);
Ok(CnavEphemeris {
week: a("wn")? as u32,
toe_s: a("toe")?,
top_s: a("top")?,
ura_ed: a("ura_ed")? as i32,
health: [
a("l1_health")? == 1.0,
a("l2_health")? == 1.0,
a("l5_health")? == 1.0,
],
a_m: A_REF_M + a("delta_a")?,
a_dot: a("a_dot")?,
delta_n0: a("delta_n0")?,
delta_n0_dot: a("delta_n0_dot")?,
m0: a("m0")?,
e: a("e")?,
omega: a("omega")?,
omega0: c("omega0")?,
i0: c("i0")?,
omega_dot: OMEGA_DOT_REF_SC * GPS_PI + c("delta_omega_dot")?,
i0_dot: c("i0_dot")?,
cis: c("cis")?,
cic: c("cic")?,
crs: c("crs")?,
crc: c("crc")?,
cus: c("cus")?,
cuc: c("cuc")?,
integrity: a("integrity")? == 1.0,
l2c_phasing: a("l2c_phasing")? == 1.0,
})
}
pub fn ephemeris_from_rinex(r: &RinexEphemeris) -> CnavEphemeris {
let bad = r.sv_health != 0.0;
CnavEphemeris {
week: r.gps_week.max(0.0) as u32,
toe_s: r.toe,
top_s: r.toe,
ura_ed: 0,
health: [bad; 3],
a_m: r.sqrt_a * r.sqrt_a,
a_dot: 0.0,
delta_n0: r.delta_n,
delta_n0_dot: 0.0,
m0: r.m0,
e: r.e,
omega: r.omega,
omega0: r.omega0,
i0: r.i0,
omega_dot: r.omega_dot,
i0_dot: r.idot,
cis: r.cis,
cic: r.cic,
crs: r.crs,
crc: r.crc,
cus: r.cus,
cuc: r.cuc,
integrity: false,
l2c_phasing: false,
}
}
pub fn clock_from_rinex(r: &RinexEphemeris) -> CnavClock {
let toc = r.toc.gps_time_of_week();
CnavClock {
top_s: toc,
ura_ned0: 0,
ura_ned1: 0,
ura_ned2: 0,
toc_s: toc,
af0: r.af0,
af1: r.af1,
af2: r.af2,
}
}
#[cfg(test)]
mod tests {
use super::super::layout_bits;
use super::*;
#[test]
fn bodies_fill_bits_39_to_276() {
assert_eq!(layout_bits(HEADER), 38);
assert_eq!(layout_bits(TYPE10), 238);
assert_eq!(layout_bits(TYPE11), 238);
assert_eq!(layout_bits(&type30_layout()), 238);
}
}