use crate::decode::cpr::CPRFormat;
use crate::decode::{decode_id13, gray2alt};
use deku::prelude::*;
use serde::{Deserialize, Serialize};
use std::fmt;
#[derive(Debug, PartialEq, Serialize, Deserialize, DekuRead, Copy, Clone)]
#[deku(ctx = "tc: u8")]
pub struct AirbornePosition {
#[deku(
skip,
default = "
match tc {
n if n < 19 => 18 - tc,
20 | 21 => 29 - tc,
_ => 0
}
"
)]
#[serde(rename = "NUCp")]
pub nuc_p: u8,
#[serde(skip)] pub ss: SurveillanceStatus,
#[deku(
bits = "1",
map = "|v| -> Result<_, DekuError> {
if tc < 19 { Ok(Some(v)) } else { Ok(None) }
}"
)]
#[serde(rename = "NICb", skip_serializing_if = "Option::is_none")]
pub saf_or_nicb: Option<u8>,
#[deku(reader = "decode_ac12(deku::reader)")]
#[serde(rename = "altitude")]
pub alt: Option<i32>,
#[deku(reader = "read_source(tc)")]
pub source: Source,
#[deku(bits = "1")]
pub time_sync: bool,
pub parity: CPRFormat,
#[deku(bits = "17", endian = "big")]
pub lat_cpr: u32,
#[deku(bits = "17", endian = "big")]
pub lon_cpr: u32,
#[deku(skip, default = "None")]
#[serde(skip_serializing_if = "Option::is_none")]
pub latitude: Option<f64>,
#[deku(skip, default = "None")]
#[serde(skip_serializing_if = "Option::is_none")]
pub longitude: Option<f64>,
}
fn decode_ac12<R: deku::no_std_io::Read + deku::no_std_io::Seek>(
reader: &mut Reader<R>,
) -> Result<Option<i32>, DekuError> {
let num = u16::from_reader_with_ctx(
reader,
(deku::ctx::Endian::Big, deku::ctx::BitSize(12)),
)?;
if num == 0 {
return Ok(None);
}
let q = num & 0x10;
if q > 0 {
let n = ((num & 0x0fe0) >> 1) | (num & 0x000f);
let altitude = i32::from(n) * 25 - 1000;
Ok(Some(altitude))
} else {
let mut n = ((num & 0x0fc0) << 1) | (num & 0x003f);
n = decode_id13(n);
if let Ok(n) = gray2alt(n) {
let altitude = n * 100;
Ok(Some(altitude))
} else {
Ok(None)
}
}
}
fn read_source(tc: u8) -> Result<Source, DekuError> {
let source = if tc < 19 {
Source::Barometric
} else {
Source::Gnss
};
Ok(source)
}
impl fmt::Display for AirbornePosition {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
writeln!(f, " AirbornePosition (BDS 0,5)")?;
let altitude = self.alt.map_or_else(
|| "None".to_string(),
|altitude| format!("{altitude} ft"),
);
writeln!(f, " Altitude: {} {}", altitude, self.source)?;
writeln!(f, " CPR type: Airborne")?;
writeln!(f, " CPR parity: {}", self.parity)?;
writeln!(f, " CPR latitude: ({})", self.lat_cpr)?;
writeln!(f, " CPR longitude: ({})", self.lon_cpr)?;
Ok(())
}
}
#[derive(
Debug, PartialEq, Eq, Serialize, Deserialize, DekuRead, Copy, Clone, Default,
)]
#[repr(u8)]
#[deku(id_type = "u8", bits = "2")]
pub enum SurveillanceStatus {
#[default]
NoCondition = 0,
PermanentAlert = 1,
TemporaryAlert = 2,
SPICondition = 3,
}
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Copy, Clone)]
pub enum Source {
#[serde(rename = "barometric")]
Barometric = 0,
#[serde(rename = "GNSS")]
Gnss = 1,
}
impl fmt::Display for Source {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"{}",
match self {
Self::Barometric => "barometric",
Self::Gnss => "GNSS",
}
)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::decode::adsb::{ADSB, ME};
use crate::decode::{Message, DF};
use hexlit::hex;
#[test]
fn test_negative_altitude_325ft() {
let bytes = hex!("8d484fde5803b647ecec4fcdd74f");
let (_, msg) = Message::from_bytes((&bytes, 0)).unwrap();
if let DF::ExtendedSquitterADSB(ADSB {
message: ME::BDS05 { inner: pos, .. },
..
}) = msg.df
{
assert_eq!(pos.alt, Some(-325));
} else {
panic!("Expected AirbornePosition message, got {:?}", msg.df);
}
}
#[test]
fn test_negative_altitude_300ft() {
let bytes = hex!("8d4845575803c647bcec2a980abc");
let (_, msg) = Message::from_bytes((&bytes, 0)).unwrap();
if let DF::ExtendedSquitterADSB(ADSB {
message: ME::BDS05 { inner: pos, .. },
..
}) = msg.df
{
assert_eq!(pos.alt, Some(-300));
} else {
panic!("Expected AirbornePosition message");
}
}
#[test]
fn test_negative_altitude_275ft() {
let bytes = hex!("8d3424d25803d64c18ee03351f89");
let (_, msg) = Message::from_bytes((&bytes, 0)).unwrap();
if let DF::ExtendedSquitterADSB(ADSB {
message: ME::BDS05 { inner: pos, .. },
..
}) = msg.df
{
assert_eq!(pos.alt, Some(-275));
} else {
panic!("Expected AirbornePosition message");
}
}
#[test]
fn test_zero_altitude() {
let bytes = hex!("8d4401e458058645a8ea90496290");
let (_, msg) = Message::from_bytes((&bytes, 0)).unwrap();
if let DF::ExtendedSquitterADSB(ADSB {
message: ME::BDS05 { inner: pos, .. },
..
}) = msg.df
{
assert_eq!(pos.alt, Some(0));
} else {
panic!("Expected AirbornePosition message");
}
}
#[test]
fn test_small_positive_altitude_25ft() {
let bytes = hex!("8d346355580596459cea86756acc");
let (_, msg) = Message::from_bytes((&bytes, 0)).unwrap();
if let DF::ExtendedSquitterADSB(ADSB {
message: ME::BDS05 { inner: pos, .. },
..
}) = msg.df
{
assert_eq!(pos.alt, Some(25));
} else {
panic!("Expected AirbornePosition message");
}
}
#[test]
fn test_small_positive_altitude_50ft() {
let bytes = hex!("8d3463555805a64584ea756d352e");
let (_, msg) = Message::from_bytes((&bytes, 0)).unwrap();
if let DF::ExtendedSquitterADSB(ADSB {
message: ME::BDS05 { inner: pos, .. },
..
}) = msg.df
{
assert_eq!(pos.alt, Some(50));
} else {
panic!("Expected AirbornePosition message");
}
}
#[test]
fn test_small_positive_altitude_100ft() {
let bytes = hex!("8d3463555805c2d9f6f0f3f1b6c3");
let (_, msg) = Message::from_bytes((&bytes, 0)).unwrap();
if let DF::ExtendedSquitterADSB(ADSB {
message: ME::BDS05 { inner: pos, .. },
..
}) = msg.df
{
assert_eq!(pos.alt, Some(100));
} else {
panic!("Expected AirbornePosition message");
}
}
#[test]
fn test_positive_altitude_1000ft() {
let bytes = hex!("8d346355580b064116e70a269f97");
let (_, msg) = Message::from_bytes((&bytes, 0)).unwrap();
if let DF::ExtendedSquitterADSB(ADSB {
message: ME::BDS05 { inner: pos, .. },
..
}) = msg.df
{
assert_eq!(pos.alt, Some(1000));
} else {
panic!("Expected AirbornePosition message");
}
}
#[test]
fn test_positive_altitude_5000ft() {
let bytes = hex!("8d343386581f06318ad4fecab734");
let (_, msg) = Message::from_bytes((&bytes, 0)).unwrap();
if let DF::ExtendedSquitterADSB(ADSB {
message: ME::BDS05 { inner: pos, .. },
..
}) = msg.df
{
assert_eq!(pos.alt, Some(5000));
} else {
panic!("Expected AirbornePosition message");
}
}
#[test]
fn test_altitude_decoding_formula() {
let test_cases = vec![
(0x03a, -350), (0x03b, -325), (0x03e, -250), (0x050, -200), (0x058, 0), (0x070, 200), (0x0b0, 1000), (0x1f0, 5000), ];
for (alt_field, expected_alt) in test_cases {
let q_bit = alt_field & 0x10;
assert!(
q_bit > 0,
"Q-bit should be set for field 0x{:03x}",
alt_field
);
let n = ((alt_field & 0x0fe0) >> 1) | (alt_field & 0x000f);
let altitude = n * 25 - 1000;
assert_eq!(
altitude, expected_alt,
"Altitude field 0x{:03x} (n={}) should decode to {} ft, got {} ft",
alt_field, n, expected_alt, altitude
);
}
}
#[test]
fn test_altitude_all_zeros() {
let bytes = hex!("8d1234564800000000000000000000");
match Message::from_bytes((&bytes, 0)) {
Ok((_, msg)) => {
if let DF::ExtendedSquitterADSB(ADSB {
message: ME::BDS05 { inner: pos, .. },
..
}) = msg.df
{
assert_eq!(
pos.alt, None,
"Altitude 0x000 should decode to None, not Some(-1000)"
);
} else {
}
}
Err(_) => {
}
}
}
#[test]
fn test_altitude_minus_1000_valid() {
let alt_field: u16 = 0x010;
let q_bit = alt_field & 0x10;
assert!(q_bit > 0, "Q-bit should be set");
let n = ((alt_field & 0x0fe0) >> 1) | (alt_field & 0x000f);
assert_eq!(n, 0, "n should be 0 for altitude field 0x010");
let altitude = i32::from(n) * 25 - 1000;
assert_eq!(
altitude, -1000,
"Altitude field 0x010 should decode to -1000 ft"
);
}
}