use deku::prelude::*;
use serde::{Deserialize, Serialize};
use std::fmt;
#[derive(Debug, PartialEq, Serialize, Deserialize, DekuRead, Copy, Clone)]
#[deku(id_type = "u8", bits = "3")]
#[serde(untagged)]
pub enum AircraftOperationStatus {
#[deku(id = "0")]
Airborne(OperationStatusAirborne),
#[deku(id = "1")]
Surface(OperationStatusSurface),
#[deku(id_pat = "2..=7")]
Reserved { id: u8, status: ReservedStatus },
}
impl fmt::Display for AircraftOperationStatus {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
writeln!(f, " Aircraft Operation Status (BDS 6,5)")?;
match &self {
Self::Airborne(airborne) => write!(f, "{airborne}"),
Self::Surface(surface) => write!(f, "{surface}"),
Self::Reserved { id, status } => {
write!(f, " Reserved: id={}, data={:?}", id, status.data)
}
}
}
}
#[derive(Debug, PartialEq, Serialize, Deserialize, DekuRead, Copy, Clone)]
pub struct OperationStatusAirborne {
#[serde(skip, default = "serde_default_capability_class_airborne")]
pub capability_class: CapabilityClassAirborne,
#[serde(skip, default = "serde_default_operational_mode")]
pub operational_mode: OperationalMode,
#[deku(pad_bytes_before = "1")]
#[serde(flatten)]
pub version: ADSBVersionAirborne,
}
impl fmt::Display for OperationStatusAirborne {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
writeln!(f, " Capability classes: {}", self.capability_class)?;
writeln!(f, " Operational modes: {}", self.operational_mode)?;
write!(f, " {}", self.version)?;
Ok(())
}
}
#[derive(Debug, PartialEq, Serialize, Deserialize, DekuRead, Copy, Clone)]
pub struct CapabilityClassAirborne {
#[deku(bits = "2", assert_eq = "0")]
#[serde(skip, default = "u8::default")]
pub reserved0: u8,
#[deku(bits = "1")]
#[serde(rename = "ACAS")]
pub acas: bool,
#[deku(bits = "1")]
#[serde(rename = "CDTI")]
pub cdti: bool,
#[deku(bits = "2")]
#[serde(skip, default = "u8::default")]
pub reserved1: u8,
#[deku(bits = "1")]
#[serde(rename = "ARV")]
pub arv: bool,
#[deku(bits = "1")]
#[serde(rename = "TS")]
pub ts: bool,
#[deku(bits = "2")]
#[deku(pad_bits_after = "6")] #[serde(rename = "TC")]
pub tc: u8,
}
pub fn serde_default_capability_class_airborne() -> CapabilityClassAirborne {
CapabilityClassAirborne {
reserved0: 0,
acas: false,
cdti: false,
reserved1: 0,
arv: false,
ts: false,
tc: 0,
}
}
impl fmt::Display for CapabilityClassAirborne {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if self.acas {
write!(f, " ACAS")?;
}
if self.cdti {
write!(f, " CDTI")?;
}
if self.arv {
write!(f, " ARV")?;
}
if self.ts {
write!(f, " TS")?;
}
if self.tc == 1 {
write!(f, " TC")?;
}
Ok(())
}
}
#[derive(Debug, PartialEq, Serialize, Deserialize, DekuRead, Copy, Clone)]
pub struct OperationStatusSurface {
#[serde(skip, default = "serde_default_capability_class_surface")]
pub capability_class: CapabilityClassSurface,
#[deku(bits = "4")]
#[serde(skip, default = "u8::default")]
pub lw_codes: u8,
#[serde(skip, default = "serde_default_operational_mode")]
pub operational_mode: OperationalMode,
#[serde(skip, default = "u8::default")]
pub gps_antenna_offset: u8,
#[serde(flatten, default = "serde_default_adsb_version_surface")]
pub version: ADSBVersionSurface,
}
impl fmt::Display for OperationStatusSurface {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.capability_class)?;
writeln!(f, " Operational modes: {}", self.operational_mode)?;
write!(f, " {}", self.version)?;
Ok(())
}
}
#[derive(Debug, PartialEq, Serialize, Deserialize, DekuRead, Copy, Clone)]
pub struct CapabilityClassSurface {
#[deku(bits = "2", assert_eq = "0")]
#[serde(skip, default = "u8::default")]
pub reserved0: u8,
#[deku(bits = "1")]
pub poe: bool,
#[deku(bits = "1")]
#[deku(pad_bits_after = "2")] #[serde(rename = "1090ES")]
pub es1090: bool,
#[deku(bits = "1")]
#[serde(rename = "GRND")]
pub b2_low: bool,
#[deku(bits = "1")]
#[serde(rename = "UATin")]
pub uat_in: bool,
#[deku(bits = "3")] #[serde(rename = "NACv")]
pub nac_v: u8,
#[deku(bits = "1")]
#[serde(rename = "NICc")]
pub nic_c: u8,
}
pub fn serde_default_capability_class_surface() -> CapabilityClassSurface {
CapabilityClassSurface {
reserved0: 0,
poe: false,
es1090: false,
b2_low: false,
uat_in: false,
nac_v: 0,
nic_c: 0,
}
}
impl fmt::Display for CapabilityClassSurface {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
writeln!(f, " NICc: {}", self.nic_c)?;
writeln!(f, " NACv: {}", self.nac_v)?;
Ok(())
}
}
#[derive(Debug, PartialEq, Serialize, Deserialize, DekuRead, Copy, Clone)]
pub struct OperationalMode {
#[deku(bits = "2", assert_eq = "0")]
#[serde(skip, default = "u8::default")]
reserved: u8,
#[deku(bits = "1")]
tcas_ra_active: bool,
#[deku(bits = "1")]
ident_switch_active: bool,
#[deku(bits = "1")]
reserved_recv_atc_service: bool,
#[deku(bits = "1")]
single_antenna_flag: bool,
#[deku(bits = "2")]
system_design_assurance: u8,
}
pub fn serde_default_operational_mode() -> OperationalMode {
OperationalMode {
reserved: 0,
tcas_ra_active: false,
ident_switch_active: false,
reserved_recv_atc_service: false,
single_antenna_flag: false,
system_design_assurance: 0,
}
}
impl fmt::Display for OperationalMode {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if self.tcas_ra_active {
write!(f, " TCAS")?;
}
if self.ident_switch_active {
write!(f, " IDENT_SWITCH_ACTIVE")?;
}
if self.reserved_recv_atc_service {
write!(f, " ATC")?;
}
if self.single_antenna_flag {
write!(f, " SAF")?;
}
if self.system_design_assurance != 0 {
write!(f, " SDA={}", self.system_design_assurance)?;
}
Ok(())
}
}
#[derive(Debug, PartialEq, Serialize, Deserialize, DekuRead, Copy, Clone)]
#[deku(id_type = "u8", bits = "3")]
#[serde(tag = "version")]
pub enum ADSBVersionAirborne {
#[deku(id = "0")]
#[serde(rename = "0")] DOC9871AppendixA(Empty),
#[deku(id = "1")]
#[serde(rename = "1")]
DOC9871AppendixB(AirborneV1),
#[deku(id = "2")]
#[serde(rename = "2")]
DOC9871AppendixC(AirborneV2),
#[deku(id_pat = "3..=7")]
#[serde(rename = "3to7")]
Reserved { id: u8 },
}
impl ADSBVersionAirborne {
pub fn version_number(&self) -> u8 {
match self {
Self::DOC9871AppendixA(_) => 0,
Self::DOC9871AppendixB(_) => 1,
Self::DOC9871AppendixC(_) => 2,
Self::Reserved { id } => *id,
}
}
}
impl fmt::Display for ADSBVersionAirborne {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::DOC9871AppendixA(_) => write!(f, "Version 0 (DO-260)"),
Self::DOC9871AppendixB(v1) => {
writeln!(f, "Version 1 (DO-260A)")?;
writeln!(f, " NIC supplement: {}", v1.nic_s)?;
writeln!(f, " NACp: {}", v1.nac_p)?;
writeln!(
f,
" BAQ: {}",
v1.barometric_altitude_quality
)?;
writeln!(f, " SIL: {}", v1.sil)?;
writeln!(
f,
" BAI: {}",
v1.barometric_altitude_integrity
)?;
write!(
f,
" HRD: {}",
if v1.horizontal_reference_direction == 0 {
"True North"
} else {
"Magnetic North"
}
)
}
Self::DOC9871AppendixC(v2) => {
writeln!(f, "Version 2 (DO-260B)")?;
writeln!(f, " NIC supplement A: {}", v2.nic_a)?;
writeln!(f, " NACp: {}", v2.nac_p)?;
writeln!(
f,
" GVA: {}",
v2.geometry_vertical_accuracy
)?;
writeln!(f, " SIL: {}", v2.sil)?;
writeln!(f, " SIL supplement: {}", v2.sil_s)?;
writeln!(
f,
" BAI: {}",
v2.barometric_altitude_integrity
)?;
write!(
f,
" HRD: {}",
if v2.horizontal_reference_direction == 0 {
"True North"
} else {
"Magnetic North"
}
)
}
Self::Reserved { id } => write!(f, "Version {} (Reserved)", id),
}
}
}
#[derive(Debug, PartialEq, Serialize, Deserialize, DekuRead, Copy, Clone)]
pub struct AirborneV1 {
#[deku(bits = "1")]
#[serde(rename = "NICs")]
pub nic_s: u8,
#[deku(bits = "4")]
#[serde(rename = "NACp")]
pub nac_p: u8,
#[deku(bits = "2")]
#[serde(rename = "BAQ")]
pub barometric_altitude_quality: u8,
#[deku(bits = "2")]
#[serde(rename = "SIL")]
pub sil: u8,
#[deku(bits = "1")]
#[serde(rename = "BAI")]
pub barometric_altitude_integrity: u8,
#[deku(bits = "1")]
#[deku(pad_bits_after = "2")] #[serde(rename = "HRD")]
pub horizontal_reference_direction: u8,
}
#[derive(Debug, PartialEq, Serialize, Deserialize, DekuRead, Copy, Clone)]
pub struct AirborneV2 {
#[deku(bits = "1")]
#[serde(rename = "NICa")]
pub nic_a: u8,
#[deku(bits = "4")]
#[serde(rename = "NACp")]
pub nac_p: u8,
#[deku(bits = "2")]
#[serde(rename = "GVA")]
pub geometry_vertical_accuracy: u8,
#[deku(bits = "2")] #[serde(rename = "SIL")]
pub sil: u8,
#[deku(bits = "1")]
#[serde(rename = "BAI")]
pub barometric_altitude_integrity: u8,
#[deku(bits = "1")]
#[serde(rename = "HRD")]
pub horizontal_reference_direction: u8,
#[deku(bits = "1")]
#[deku(pad_bits_after = "1")]
#[serde(rename = "SILs")]
pub sil_s: u8,
}
#[derive(Debug, PartialEq, Serialize, Deserialize, DekuRead, Copy, Clone)]
#[deku(id_type = "u8", bits = "3")]
#[serde(tag = "version")]
pub enum ADSBVersionSurface {
#[deku(id = "0")]
#[serde(rename = "0")]
DOC9871AppendixA(Empty),
#[deku(id = "1")]
#[serde(rename = "1")]
DOC9871AppendixB(SurfaceV1),
#[deku(id = "2")]
#[serde(rename = "2")]
DOC9871AppendixC(SurfaceV2),
#[deku(id_pat = "3..=7")]
#[serde(rename = "3to7")]
Reserved { id: u8 },
}
pub fn serde_default_adsb_version_surface() -> ADSBVersionSurface {
ADSBVersionSurface::DOC9871AppendixA(Empty {})
}
impl ADSBVersionSurface {
pub fn version_number(&self) -> u8 {
match self {
Self::DOC9871AppendixA(_) => 0,
Self::DOC9871AppendixB(_) => 1,
Self::DOC9871AppendixC(_) => 2,
Self::Reserved { id } => *id,
}
}
}
impl fmt::Display for ADSBVersionSurface {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::DOC9871AppendixA(_) => write!(f, "Version 0 (DO-260)"),
Self::DOC9871AppendixB(v1) => {
writeln!(f, "Version 1 (DO-260A)")?;
writeln!(f, " NIC supplement: {}", v1.nic_s)?;
writeln!(f, " NACp: {}", v1.nac_p)?;
writeln!(f, " SIL: {}", v1.sil)?;
writeln!(
f,
" TAH: {}",
v1.track_angle_or_heading
)?;
write!(
f,
" HRD: {}",
if v1.horizontal_reference_direction == 0 {
"True North"
} else {
"Magnetic North"
}
)
}
Self::DOC9871AppendixC(v2) => {
writeln!(f, "Version 2 (DO-260B)")?;
writeln!(f, " NIC supplement A: {}", v2.nic_a)?;
writeln!(f, " NACp: {}", v2.nac_p)?;
writeln!(f, " SIL: {}", v2.sil)?;
writeln!(f, " SIL supplement: {}", v2.sil_supplement)?;
writeln!(
f,
" TAH: {}",
v2.track_angle_or_heading
)?;
write!(
f,
" HRD: {}",
if v2.horizontal_reference_direction == 0 {
"True North"
} else {
"Magnetic North"
}
)
}
Self::Reserved { id } => write!(f, "Version {} (Reserved)", id),
}
}
}
#[derive(Debug, PartialEq, Serialize, Deserialize, DekuRead, Copy, Clone)]
pub struct SurfaceV1 {
#[deku(bits = "1")]
#[serde(rename = "NICs")]
pub nic_s: u8,
#[deku(bits = "4")]
#[deku(pad_bits_after = "2")] #[serde(rename = "NACp")]
pub nac_p: u8,
#[deku(bits = "2")]
#[serde(rename = "SIL")]
pub sil: u8,
#[deku(bits = "1")]
#[serde(rename = "TAH")]
pub track_angle_or_heading: u8,
#[deku(bits = "1")]
#[deku(pad_bits_after = "2")]
#[serde(rename = "HRD")]
pub horizontal_reference_direction: u8,
}
#[derive(Debug, PartialEq, Serialize, Deserialize, DekuRead, Copy, Clone)]
pub struct SurfaceV2 {
#[deku(bits = "1")]
#[serde(rename = "NICa")]
pub nic_a: u8,
#[deku(bits = "4")]
#[deku(pad_bits_after = "2")]
#[serde(rename = "NACp")]
pub nac_p: u8,
#[deku(bits = "2")]
#[serde(rename = "SIL")]
pub sil: u8,
#[deku(bits = "1")]
#[serde(rename = "TAH")]
pub track_angle_or_heading: u8,
#[deku(bits = "1")]
#[serde(rename = "HRD")]
pub horizontal_reference_direction: u8,
#[deku(bits = "1")] #[deku(pad_bits_after = "1")] #[serde(rename = "SILs")]
pub sil_supplement: u8,
}
#[derive(Debug, PartialEq, Serialize, Deserialize, DekuRead, Copy, Clone)]
pub struct Empty {}
#[derive(Debug, PartialEq, Serialize, Deserialize, DekuRead, Copy, Clone)]
pub struct EmptyU8 {
pub id: u8,
pub unused: u8,
}
#[derive(Debug, PartialEq, Serialize, Deserialize, DekuRead, Copy, Clone)]
pub struct ReservedStatus {
pub data: [u8; 5],
}
#[cfg(test)]
mod tests {
use super::*;
use crate::decode::DF;
use crate::prelude::*;
use hexlit::hex;
#[test]
fn test_bds65_version2_surface() {
let bytes = hex!("903a33fff90200040049001ea8e2");
let (_, msg) = Message::from_bytes((&bytes, 0)).unwrap();
if let DF::ExtendedSquitterTisB { cf, .. } = msg.df {
if let ME::BDS65(AircraftOperationStatus::Surface(surface)) = cf.me
{
assert_eq!(surface.version.version_number(), 2);
if let ADSBVersionSurface::DOC9871AppendixC(v2) =
surface.version
{
assert_eq!(v2.nic_a, 0);
assert_eq!(v2.nac_p, 9);
assert_eq!(v2.sil, 0);
assert_eq!(v2.sil_supplement, 0);
assert_eq!(v2.track_angle_or_heading, 0);
assert_eq!(v2.horizontal_reference_direction, 0);
} else {
panic!("Expected version 2");
}
let display = format!("{}", surface.version);
assert!(display.contains("Version 2 (DO-260B)"));
assert!(display.contains("NIC supplement A:"));
assert!(display.contains("NACp:"));
return;
}
}
unreachable!();
}
#[test]
fn test_adsb_version_display_airborne() {
let v0 = ADSBVersionAirborne::DOC9871AppendixA(Empty {});
let display = format!("{}", v0);
assert!(display.contains("Version 0 (DO-260)"));
assert_eq!(v0.version_number(), 0);
let v1 = ADSBVersionAirborne::DOC9871AppendixB(AirborneV1 {
nic_s: 1,
nac_p: 9,
barometric_altitude_quality: 1,
sil: 2,
barometric_altitude_integrity: 1,
horizontal_reference_direction: 0,
});
let display = format!("{}", v1);
assert!(display.contains("Version 1 (DO-260A)"));
assert!(display.contains("NIC supplement:"));
assert!(display.contains("NACp: 9"));
assert!(display.contains("BAQ: 1"));
assert!(display.contains("SIL: 2"));
assert!(display.contains("BAI: 1"));
assert!(display.contains("HRD: True North"));
assert_eq!(v1.version_number(), 1);
let v2 = ADSBVersionAirborne::DOC9871AppendixC(AirborneV2 {
nic_a: 0,
nac_p: 9,
geometry_vertical_accuracy: 2,
sil: 3,
barometric_altitude_integrity: 1,
horizontal_reference_direction: 0,
sil_s: 0,
});
let display = format!("{}", v2);
assert!(display.contains("Version 2 (DO-260B)"));
assert!(display.contains("NIC supplement A: 0"));
assert!(display.contains("NACp: 9"));
assert!(display.contains("GVA: 2"));
assert!(display.contains("SIL: 3"));
assert!(display.contains("BAI: 1"));
assert!(display.contains("HRD: True North"));
assert!(display.contains("SIL supplement: 0"));
assert_eq!(v2.version_number(), 2);
let vr = ADSBVersionAirborne::Reserved { id: 5 };
let display = format!("{}", vr);
assert!(display.contains("Version 5 (Reserved)"));
assert_eq!(vr.version_number(), 5);
}
#[test]
fn test_version_number_method() {
let v0 = ADSBVersionAirborne::DOC9871AppendixA(Empty {});
assert_eq!(v0.version_number(), 0);
let v1 = ADSBVersionAirborne::DOC9871AppendixB(AirborneV1 {
nic_s: 0,
nac_p: 0,
barometric_altitude_quality: 0,
sil: 0,
barometric_altitude_integrity: 0,
horizontal_reference_direction: 0,
});
assert_eq!(v1.version_number(), 1);
let v2 = ADSBVersionAirborne::DOC9871AppendixC(AirborneV2 {
nic_a: 0,
nac_p: 0,
geometry_vertical_accuracy: 0,
sil: 0,
barometric_altitude_integrity: 0,
horizontal_reference_direction: 0,
sil_s: 0,
});
assert_eq!(v2.version_number(), 2);
for id in 3..=7 {
let vr = ADSBVersionAirborne::Reserved { id };
assert_eq!(vr.version_number(), id);
}
}
}