use super::cpr::{self, CprFormat};
use crate::bits::{BitReader, BitWriter};
use crate::error::{BitError, MessageError};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Altitude {
BarometricFeet(i32),
Gillham(u16),
GnssMeters(u16),
NotAvailable,
}
impl Altitude {
fn decode(type_code: u8, raw12: u16) -> Self {
if raw12 == 0 {
return Self::NotAvailable;
}
if type_code >= 20 {
return Self::GnssMeters(raw12);
}
let q_bit = (raw12 >> 4) & 1;
let top7 = raw12 >> 5;
let bottom4 = raw12 & 0xF;
let n = (top7 << 4) | bottom4;
if q_bit == 1 {
Self::BarometricFeet(i32::from(n) * 25 - 1000)
} else {
Self::Gillham(n)
}
}
fn to_raw(self) -> u16 {
match self {
Self::BarometricFeet(feet) => {
#[allow(
clippy::cast_sign_loss,
clippy::cast_possible_truncation,
reason = "feet is (n*25 - 1000) for n in 0..=2047, so >= -1000; \
(feet+1000)/25 recovers n in 0..=2047, always fits u16"
)]
let n = ((feet + 1000) / 25) as u16;
(n & 0b111_1111_0000) << 1 | 0b1_0000 | (n & 0xF)
}
Self::Gillham(n) => (n & 0b111_1111_0000) << 1 | (n & 0xF),
Self::GnssMeters(m) => m,
Self::NotAvailable => 0,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct AirbornePosition {
pub type_code: u8,
pub surveillance_status: u8,
pub single_antenna: bool,
pub altitude: Altitude,
pub utc_synced: bool,
pub cpr_format: CprFormat,
pub lat_cpr: u32,
pub lon_cpr: u32,
}
impl AirbornePosition {
pub(crate) fn decode(type_code: u8, r: &mut BitReader<'_>) -> Result<Self, MessageError> {
let surveillance_status = r.read_u8(2)?;
let single_antenna = r.read_bool()?;
let altitude = Altitude::decode(type_code, r.read_u16(12)?);
let utc_synced = r.read_bool()?;
let cpr_format = CprFormat::from_raw(r.read_u8(1)?);
let lat_cpr = r.read_u32(17)?;
let lon_cpr = r.read_u32(17)?;
Ok(Self {
type_code,
surveillance_status,
single_antenna,
altitude,
utc_synced,
cpr_format,
lat_cpr,
lon_cpr,
})
}
pub(crate) fn encode(&self, w: &mut BitWriter<'_>) -> Result<(), BitError> {
w.write_bits(u64::from(self.type_code), 5)?;
w.write_bits(u64::from(self.surveillance_status), 2)?;
w.write_bool(self.single_antenna)?;
w.write_bits(u64::from(self.altitude.to_raw()), 12)?;
w.write_bool(self.utc_synced)?;
w.write_bits(u64::from(self.cpr_format.to_raw()), 1)?;
w.write_bits(u64::from(self.lat_cpr), 17)?;
w.write_bits(u64::from(self.lon_cpr), 17)?;
Ok(())
}
}
#[derive(Debug, Clone, Copy, Default)]
pub struct PositionPair {
even: Option<AirbornePosition>,
odd: Option<AirbornePosition>,
}
impl PositionPair {
#[must_use]
pub const fn new() -> Self {
Self {
even: None,
odd: None,
}
}
pub fn push(&mut self, msg: AirbornePosition) -> Option<(f64, f64)> {
let even_is_newer = matches!(msg.cpr_format, CprFormat::Even);
match msg.cpr_format {
CprFormat::Even => self.even = Some(msg),
CprFormat::Odd => self.odd = Some(msg),
}
let even = self.even?;
let odd = self.odd?;
cpr::global_decode(
even.lat_cpr,
even.lon_cpr,
odd.lat_cpr,
odd.lon_cpr,
even_is_newer,
)
}
}
#[cfg(test)]
mod tests {
use super::*;
fn round_trip(original: AirbornePosition) -> AirbornePosition {
let mut me = [0u8; 7];
let mut w = BitWriter::new(&mut me);
original.encode(&mut w).unwrap();
let mut r = BitReader::new(&me);
let type_code = r.read_u8(5).unwrap();
AirbornePosition::decode(type_code, &mut r).unwrap()
}
#[test]
fn round_trips_barometric_altitude() {
let original = AirbornePosition {
type_code: 11,
surveillance_status: 0,
single_antenna: false,
altitude: Altitude::BarometricFeet(39_000),
utc_synced: false,
cpr_format: CprFormat::Even,
lat_cpr: 39848,
lon_cpr: 83951,
};
assert_eq!(round_trip(original), original);
}
#[test]
fn round_trips_gnss_height() {
let original = AirbornePosition {
type_code: 20,
surveillance_status: 1,
single_antenna: true,
altitude: Altitude::GnssMeters(1234),
utc_synced: true,
cpr_format: CprFormat::Odd,
lat_cpr: 12345,
lon_cpr: 54321,
};
assert_eq!(round_trip(original), original);
}
#[test]
fn altitude_not_available_round_trips() {
let original = AirbornePosition {
type_code: 9,
surveillance_status: 0,
single_antenna: false,
altitude: Altitude::NotAvailable,
utc_synced: false,
cpr_format: CprFormat::Even,
lat_cpr: 0,
lon_cpr: 0,
};
assert_eq!(round_trip(original), original);
}
#[test]
fn decodes_a_real_captured_message_with_expected_altitude() {
let me: [u8; 7] = [0x58, 0xC9, 0x01, 0x37, 0x51, 0x47, 0xEF];
let mut r = BitReader::new(&me);
let type_code = r.read_u8(5).unwrap();
assert_eq!(type_code, 11);
let msg = AirbornePosition::decode(type_code, &mut r).unwrap();
assert_eq!(msg.altitude, Altitude::BarometricFeet(39_000));
assert_eq!(msg.cpr_format, CprFormat::Even);
assert_eq!(msg.lat_cpr, 39848);
assert_eq!(msg.lon_cpr, 83951);
}
#[test]
fn position_pair_resolves_a_real_captured_pair() {
let even = AirbornePosition {
type_code: 11,
surveillance_status: 0,
single_antenna: false,
altitude: Altitude::BarometricFeet(39_000),
utc_synced: false,
cpr_format: CprFormat::Even,
lat_cpr: 39848,
lon_cpr: 83951,
};
let odd = AirbornePosition {
cpr_format: CprFormat::Odd,
lat_cpr: 21567,
lon_cpr: 81965,
..even
};
let mut pair = PositionPair::new();
assert_eq!(pair.push(even), None);
let (lat, lon) = pair.push(odd).unwrap();
assert!((lat - 49.817_55).abs() < 1e-3);
assert!((lon - 6.084_42).abs() < 1e-3);
}
}