#![allow(clippy::suspicious_else_formatting)]
use deku::prelude::*;
use serde::{Deserialize, Serialize};
use std::fmt;
#[derive(Debug, PartialEq, Serialize, Deserialize, DekuRead, Clone)]
pub struct AirborneVelocity {
#[deku(bits = "3")]
#[serde(skip)]
pub subtype: u8,
#[deku(bits = "1")]
#[serde(skip)]
pub intent_change: bool,
#[deku(bits = "1")]
#[serde(skip)]
pub ifr_capability: bool,
#[deku(bits = "3")]
#[serde(rename = "NACv")]
pub nac_v: u8,
#[deku(ctx = "*subtype")]
#[serde(flatten)]
pub velocity: AirborneVelocitySubType,
pub vrate_src: VerticalRateSource,
#[serde(skip, default = "Sign::positive")]
pub vrate_sign: Sign,
#[deku(
endian = "big",
bits = "9",
map = "|v: u16| -> Result<_, DekuError> {
if v == 0 { Ok(None) }
else {
Ok(Some(vrate_sign.value() * (v as i16 - 1) * 64))
}
}"
)]
#[serde(skip_serializing_if = "Option::is_none")]
pub vertical_rate: Option<i16>,
#[deku(bits = "2")]
#[serde(skip)]
pub reserved: u8,
#[serde(skip, default = "Sign::positive")]
pub gnss_sign: Sign,
#[deku(reader = "read_geobaro(deku::reader, *gnss_sign)")]
pub geo_minus_baro: Option<i16>,
}
fn read_geobaro<R: deku::no_std_io::Read + deku::no_std_io::Seek>(
reader: &mut Reader<R>,
gnss_sign: Sign,
) -> Result<Option<i16>, DekuError> {
let value = u8::from_reader_with_ctx(
reader,
(deku::ctx::Endian::Big, deku::ctx::BitSize(7)),
)?;
let value = if value > 1 {
match gnss_sign {
Sign::Positive => Some(25 * (value as i16 - 1)),
Sign::Negative => Some(-25 * (value as i16 - 1)),
}
} else {
None
};
Ok(value)
}
#[derive(Debug, PartialEq, Serialize, Deserialize, DekuRead, Clone)]
#[deku(ctx = "subtype: u8", id = "subtype")]
#[serde(untagged)]
pub enum AirborneVelocitySubType {
#[deku(id = "0")]
Reserved0(ReservedVelocityData),
#[deku(id_pat = "1..=2")]
GroundSpeedDecoding(GroundSpeedDecoding),
#[deku(id = "3")]
AirspeedSubsonic(AirspeedSubsonicDecoding),
#[deku(id = "4")]
AirspeedSupersonic(AirspeedSupersonicDecoding),
#[deku(id_pat = "5..=7")]
Reserved1(ReservedVelocityData),
}
#[derive(Debug, PartialEq, Serialize, Deserialize, DekuRead, Clone)]
pub struct ReservedVelocityData {
#[deku(bits = "22")]
pub reserved_data: u32,
}
#[derive(Debug, PartialEq, DekuRead, Copy, Clone)]
#[repr(u8)]
#[deku(id_type = "u8", bits = "1")]
pub enum Sign {
Positive = 0,
Negative = 1,
}
impl Sign {
#[must_use]
pub fn value(&self) -> i16 {
match self {
Self::Positive => 1,
Self::Negative => -1,
}
}
fn positive() -> Self {
Self::Positive
}
}
impl fmt::Display for Sign {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"{}",
match self {
Self::Positive => "",
Self::Negative => "-",
}
)
}
}
#[derive(Debug, PartialEq, Serialize, Deserialize, DekuRead, Copy, Clone)]
pub struct GroundSpeedDecoding {
#[serde(skip, default = "Sign::positive")]
pub ew_sign: Sign,
#[deku(
endian = "big",
bits = "10",
map = "|val: u16| -> Result<_, DekuError> {
Ok(f64::from((val as i16 - 1) * ew_sign.value()))
}"
)]
#[serde(skip)]
pub ew_vel: f64,
#[serde(skip, default = "Sign::positive")]
pub ns_sign: Sign,
#[serde(skip)]
#[deku(
endian = "big",
bits = "10",
map = "|val: u16| -> Result<_, DekuError> {
Ok(f64::from((val as i16 - 1) * ns_sign.value()))
}"
)]
pub ns_vel: f64,
#[deku(
skip,
default = "libm::hypot(f64::abs(*ew_vel), f64::abs(*ns_vel))"
)]
pub groundspeed: f64,
#[deku(
skip,
default = "
let h = libm::atan2(*ew_vel, *ns_vel) *
(360.0 / (2.0 * std::f64::consts::PI));
if h < 0.0 { h + 360. } else { h }
"
)]
pub track: f64,
}
#[derive(Debug, PartialEq, DekuRead, Clone)]
pub struct AirspeedSubsonicDecoding {
#[deku(bits = "1")]
pub status_heading: bool,
#[deku(
endian = "big",
bits = "10",
map = "|val: u16| -> Result<_, DekuError> {
Ok(if *status_heading { Some(val as f64 * 360. / 1024.) } else { None })
}"
)]
pub heading: Option<f64>,
pub airspeed_type: AirspeedType,
#[deku(
endian = "big",
bits = "10",
map = "|value: u16| -> Result<_, DekuError> {
if value == 0 { return Ok(None) }
Ok(Some(value - 1))
}"
)]
pub airspeed: Option<u16>,
}
impl Serialize for AirspeedSubsonicDecoding {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::ser::Serializer,
{
AirspeedSerdeHelper::from(self).serialize(serializer)
}
}
impl<'de> Deserialize<'de> for AirspeedSubsonicDecoding {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::de::Deserializer<'de>,
{
let helper = AirspeedSerdeHelper::deserialize(deserializer)?;
let (status_heading, heading, airspeed_type, airspeed) =
helper.into_components();
Ok(Self {
status_heading,
heading,
airspeed_type,
airspeed,
})
}
}
#[derive(Debug, PartialEq, DekuRead, Clone)]
pub struct AirspeedSupersonicDecoding {
#[deku(bits = "1")]
pub status_heading: bool,
#[deku(
endian = "big",
bits = "10",
map = "|val: u16| -> Result<_, DekuError> {
Ok(if *status_heading { Some(val as f32 * 360. / 1024.) } else { None })
}"
)]
pub heading: Option<f32>,
pub airspeed_type: AirspeedType,
#[deku(
endian = "big",
bits = "10",
map = "|value: u16| -> Result<_, DekuError> {
if value == 0 { return Ok(None) }
Ok(Some(4*(value - 1)))
}"
)]
pub airspeed: Option<u16>,
}
impl Serialize for AirspeedSupersonicDecoding {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::ser::Serializer,
{
AirspeedSerdeHelper::from(self).serialize(serializer)
}
}
impl<'de> Deserialize<'de> for AirspeedSupersonicDecoding {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::de::Deserializer<'de>,
{
let helper = AirspeedSerdeHelper::deserialize(deserializer)?;
let (status_heading, heading, airspeed_type, airspeed) =
helper.into_components();
Ok(Self {
status_heading,
heading: heading.map(|h| h as f32),
airspeed_type,
airspeed,
})
}
}
#[derive(Serialize, Deserialize)]
struct AirspeedSerdeHelper {
#[serde(skip_serializing_if = "Option::is_none")]
heading: Option<f64>,
#[serde(rename = "IAS", skip_serializing_if = "Option::is_none")]
ias: Option<u16>,
#[serde(rename = "TAS", skip_serializing_if = "Option::is_none")]
tas: Option<u16>,
}
impl AirspeedSerdeHelper {
fn into_components(self) -> (bool, Option<f64>, AirspeedType, Option<u16>) {
let (airspeed_type, airspeed) = if let Some(ias) = self.ias {
(AirspeedType::IAS, Some(ias))
} else if let Some(tas) = self.tas {
(AirspeedType::TAS, Some(tas))
} else {
(AirspeedType::IAS, None)
};
(
self.heading.is_some(),
self.heading,
airspeed_type,
airspeed,
)
}
}
impl From<&AirspeedSubsonicDecoding> for AirspeedSerdeHelper {
fn from(v: &AirspeedSubsonicDecoding) -> Self {
let (ias, tas) = match v.airspeed_type {
AirspeedType::IAS => (v.airspeed, None),
AirspeedType::TAS => (None, v.airspeed),
};
Self {
heading: v.heading,
ias,
tas,
}
}
}
impl From<&AirspeedSupersonicDecoding> for AirspeedSerdeHelper {
fn from(v: &AirspeedSupersonicDecoding) -> Self {
let (ias, tas) = match v.airspeed_type {
AirspeedType::IAS => (v.airspeed, None),
AirspeedType::TAS => (None, v.airspeed),
};
Self {
heading: v.heading.map(|h| h as f64),
ias,
tas,
}
}
}
#[derive(Copy, Clone, Debug, PartialEq, DekuRead)]
#[repr(u8)]
#[deku(id_type = "u8", bits = "1")]
pub enum AirspeedType {
IAS = 0,
TAS = 1,
}
impl fmt::Display for AirspeedType {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"{}",
match self {
Self::IAS => "IAS",
Self::TAS => "TAS",
}
)
}
}
#[derive(Copy, Clone, Debug, PartialEq, DekuRead)]
#[repr(u8)]
#[deku(id_type = "u8", bits = "1")]
pub enum DirectionEW {
WestToEast = 0,
EastToWest = 1,
}
#[derive(Copy, Clone, Debug, PartialEq, DekuRead)]
#[repr(u8)]
#[deku(id_type = "u8", bits = "1")]
pub enum DirectionNS {
SouthToNorth = 0,
NorthToSouth = 1,
}
#[derive(Debug, PartialEq, Serialize, Deserialize, DekuRead, Copy, Clone)]
#[repr(u8)]
#[deku(id_type = "u8", bits = "1")]
pub enum VerticalRateSource {
#[serde(rename = "barometric")]
BarometricPressureAltitude = 0,
#[serde(rename = "GNSS")]
GeometricAltitude = 1,
}
impl fmt::Display for VerticalRateSource {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"{}",
match self {
Self::BarometricPressureAltitude => "barometric",
Self::GeometricAltitude => "GNSS",
}
)
}
}
impl fmt::Display for AirborneVelocity {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
writeln!(f, " Airborne velocity over ground (BDS 0,9)")?;
match &self.velocity {
AirborneVelocitySubType::GroundSpeedDecoding(v) => {
writeln!(f, " Track angle: {}°", libm::round(v.track))?;
writeln!(
f,
" Groundspeed: {} kt",
libm::round(v.groundspeed)
)?;
}
AirborneVelocitySubType::AirspeedSubsonic(v) => {
if let Some(value) = v.airspeed {
writeln!(
f,
" {}: {} kt",
v.airspeed_type, value
)?;
}
if let Some(value) = v.heading {
writeln!(f, " Heading: {}°", libm::round(value))?;
}
}
AirborneVelocitySubType::AirspeedSupersonic(v) => {
if let Some(value) = v.airspeed {
writeln!(
f,
" {}: {} kt",
v.airspeed_type, value
)?;
if let Some(value) = v.heading {
writeln!(
f,
" Heading: {}°",
libm::round(value as f64)
)?;
}
}
}
AirborneVelocitySubType::Reserved0(_)
| AirborneVelocitySubType::Reserved1(_) => {}
}
if let Some(vr) = &self.vertical_rate {
writeln!(f, " Vertical rate: {} ft/min {}", vr, &self.vrate_src)?;
}
writeln!(f, " NACv: {}", &self.nac_v)?;
if let Some(value) = &self.geo_minus_baro {
writeln!(f, " GNSS delta: {value} ft")?;
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::prelude::*;
use approx::assert_relative_eq;
use hexlit::hex;
#[test]
fn test_groundspeed_velocity() {
let bytes = hex!("8D485020994409940838175B284F");
let (_, msg) = Message::from_bytes((&bytes, 0)).unwrap();
if let ExtendedSquitterADSB(adsb_msg) = msg.df {
if let ME::BDS09(velocity) = adsb_msg.message {
if let AirborneVelocitySubType::GroundSpeedDecoding(_gsd) =
velocity.velocity
{
assert_relative_eq!(
_gsd.groundspeed,
159.,
max_relative = 1e-2
);
assert_relative_eq!(
_gsd.track,
182.88,
max_relative = 1e-2
);
if let Some(vrate) = velocity.vertical_rate {
assert_eq!(vrate, -832);
}
assert_eq!(velocity.geo_minus_baro, Some(550));
}
return;
}
}
unreachable!();
}
#[test]
fn test_format_groundspeed() {
let bytes = hex!("8D485020994409940838175B284F");
let (_, msg) = Message::from_bytes((&bytes, 0)).unwrap();
assert_eq!(
format!("{msg}"),
r#" DF17. Extended Squitter
Address: 485020
Air/Ground: airborne
Airborne velocity over ground (BDS 0,9)
Track angle: 183°
Groundspeed: 159 kt
Vertical rate: -832 ft/min barometric
NACv: 0
GNSS delta: 550 ft
"#
)
}
#[test]
fn test_airspeed_velocity() {
let bytes = hex!("8DA05F219B06B6AF189400CBC33F");
let (_, msg) = Message::from_bytes((&bytes, 0)).unwrap();
if let ExtendedSquitterADSB(adsb_msg) = msg.df {
if let ME::BDS09(velocity) = adsb_msg.message {
if let AirborneVelocitySubType::AirspeedSubsonic(asd) =
velocity.velocity
{
assert_eq!(asd.airspeed.unwrap(), 375);
assert_relative_eq!(
asd.heading.unwrap(),
244.,
max_relative = 1e-2
);
assert_eq!(velocity.vertical_rate.unwrap(), -2304);
}
return;
}
}
unreachable!();
}
#[test]
fn test_format_airspeed() {
let bytes = hex!("8DA05F219B06B6AF189400CBC33F");
let (_, msg) = Message::from_bytes((&bytes, 0)).unwrap();
assert_eq!(
format!("{msg}"),
r#" DF17. Extended Squitter
Address: a05f21
Air/Ground: airborne
Airborne velocity over ground (BDS 0,9)
TAS: 375 kt
Heading: 244°
Vertical rate: -2304 ft/min GNSS
NACv: 0
"#
)
}
#[test]
fn test_vertical_rate_positive_64() {
let bytes = hex!("8d3461cf9908388930080f948ea1");
let (_, msg) = Message::from_bytes((&bytes, 0)).unwrap();
if let ExtendedSquitterADSB(adsb_msg) = msg.df {
if let ME::BDS09(velocity) = adsb_msg.message {
assert_eq!(velocity.vertical_rate, Some(64));
assert_eq!(
velocity.vrate_src,
VerticalRateSource::GeometricAltitude
);
return;
}
}
unreachable!();
}
#[test]
fn test_vertical_rate_positive_128() {
let bytes = hex!("8d3461cf9908558e100c1071eb67");
let (_, msg) = Message::from_bytes((&bytes, 0)).unwrap();
if let ExtendedSquitterADSB(adsb_msg) = msg.df {
if let ME::BDS09(velocity) = adsb_msg.message {
assert_eq!(velocity.vertical_rate, Some(128));
assert_eq!(
velocity.vrate_src,
VerticalRateSource::GeometricAltitude
);
return;
}
}
unreachable!();
}
#[test]
fn test_vertical_rate_positive_960() {
let bytes = hex!("8d3461cf99085a8f10400f80e6ac");
let (_, msg) = Message::from_bytes((&bytes, 0)).unwrap();
if let ExtendedSquitterADSB(adsb_msg) = msg.df {
if let ME::BDS09(velocity) = adsb_msg.message {
assert_eq!(velocity.vertical_rate, Some(960));
assert_eq!(
velocity.vrate_src,
VerticalRateSource::GeometricAltitude
);
return;
}
}
unreachable!();
}
#[test]
fn test_vertical_rate_level_flight() {
}
#[test]
fn test_vertical_rate_negative_64() {
let bytes = hex!("8d394c0f990c4932780838866883");
let (_, msg) = Message::from_bytes((&bytes, 0)).unwrap();
if let ExtendedSquitterADSB(adsb_msg) = msg.df {
if let ME::BDS09(velocity) = adsb_msg.message {
assert_eq!(velocity.vertical_rate, Some(-64));
assert_eq!(
velocity.vrate_src,
VerticalRateSource::GeometricAltitude
);
return;
}
}
unreachable!();
}
#[test]
fn test_vertical_rate_sign_bit() {
let bytes_positive = hex!("8d3461cf9908388930080f948ea1"); let bytes_negative = hex!("8d394c0f990c4932780838866883");
let (_, msg_pos) = Message::from_bytes((&bytes_positive, 0)).unwrap();
let (_, msg_neg) = Message::from_bytes((&bytes_negative, 0)).unwrap();
if let ExtendedSquitterADSB(adsb_pos) = msg_pos.df {
if let ME::BDS09(vel_pos) = adsb_pos.message {
if let ExtendedSquitterADSB(adsb_neg) = msg_neg.df {
if let ME::BDS09(vel_neg) = adsb_neg.message {
assert_eq!(vel_pos.vertical_rate, Some(64));
assert_eq!(vel_neg.vertical_rate, Some(-64));
assert_eq!(
vel_pos.vertical_rate.unwrap(),
-vel_neg.vertical_rate.unwrap()
);
return;
}
}
}
}
unreachable!();
}
#[test]
fn test_vrate_source_gnss_vs_barometric() {
let bytes_gnss = hex!("8d3461cf9908388930080f948ea1");
let (_, msg_gnss) = Message::from_bytes((&bytes_gnss, 0)).unwrap();
if let ExtendedSquitterADSB(adsb_msg) = msg_gnss.df {
if let ME::BDS09(velocity) = adsb_msg.message {
assert_eq!(
velocity.vrate_src,
VerticalRateSource::GeometricAltitude
);
}
}
let bytes_baro = hex!("8D485020994409940838175B284F");
let (_, msg_baro) = Message::from_bytes((&bytes_baro, 0)).unwrap();
if let ExtendedSquitterADSB(adsb_msg) = msg_baro.df {
if let ME::BDS09(velocity) = adsb_msg.message {
assert_eq!(
velocity.vrate_src,
VerticalRateSource::BarometricPressureAltitude
);
}
}
}
#[test]
fn test_geo_minus_baro_positive() {
let bytes = hex!("8D485020994409940838175B284F");
let (_, msg) = Message::from_bytes((&bytes, 0)).unwrap();
if let ExtendedSquitterADSB(adsb_msg) = msg.df {
if let ME::BDS09(velocity) = adsb_msg.message {
assert_eq!(velocity.geo_minus_baro, Some(550));
return;
}
}
unreachable!();
}
}