#![allow(clippy::suspicious_else_formatting)]
use super::super::cpr::CPRFormat;
use deku::prelude::*;
use serde::{Deserialize, Serialize};
use std::fmt;
use tracing::debug;
#[derive(Debug, PartialEq, DekuRead, Serialize, Deserialize, Copy, Clone)]
#[deku(ctx = "tc: u8")]
pub struct SurfacePosition {
#[deku(skip, default = "14 - tc")]
#[serde(rename = "NUCp")]
pub nuc_p: u8,
#[deku(reader = "read_groundspeed(deku::reader)")]
pub groundspeed: Option<f64>,
#[deku(bits = "1")] #[serde(skip)]
pub track_status: bool,
#[deku(
bits = "7",
map = "|value: u8| -> Result<_, DekuError> {
if *track_status {
Ok(Some(value as f64 * 360. / 128.))
} else {
Ok(None)
}
}"
)]
pub track: Option<f64>,
#[deku(bits = "1")]
#[serde(skip)]
pub t: 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 read_groundspeed<R: deku::no_std_io::Read + deku::no_std_io::Seek>(
reader: &mut Reader<R>,
) -> Result<Option<f64>, DekuError> {
let mov = u8::from_reader_with_ctx(
reader,
(deku::ctx::Endian::Big, deku::ctx::BitSize(7)),
)?;
let value = match mov {
0 => None,
1 => Some(0.),
2..=8 => Some(0.125 + (mov - 2) as f64 * 0.125),
9..=12 => Some(1. + (mov - 9) as f64 * 0.25),
13..=38 => Some(2. + (mov - 13) as f64 * 0.5),
39..=93 => Some(15. + (mov - 39) as f64 * 1.),
94..=108 => Some(70. + (mov - 94) as f64 * 2.),
109..=123 => Some(100. + (mov - 109) as f64 * 5.),
124 => Some(175.),
125..=u8::MAX => None, };
debug!("Groundspeed value: {:?}", value);
Ok(value)
}
#[derive(Debug, PartialEq, DekuRead, Copy, Clone)]
#[repr(u8)]
#[deku(id_type = "u8", bits = "1")]
pub enum StatusForGroundTrack {
Invalid = 0,
Valid = 1,
}
impl fmt::Display for SurfacePosition {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
writeln!(f, " Surface position (BDS 0,6)")?;
let groundspeed = self
.groundspeed
.map_or_else(|| "None".to_string(), |gs| format!("{gs} kts"));
let track = self
.track
.map_or_else(|| "None".to_string(), |track| format!("{track}°"));
writeln!(f, " Groundspeed: {groundspeed}")?;
writeln!(f, " Track angle: {track}")?;
writeln!(f, " CPR parity: {}", self.parity)?;
writeln!(f, " CPR latitude: ({})", self.lat_cpr)?;
writeln!(f, " CPR longitude: ({})", self.lon_cpr)?;
Ok(())
}
}
#[cfg(test)]
mod tests {
use crate::prelude::*;
use hexlit::hex;
#[test]
fn test_surface_position() {
tracing_subscriber::fmt::init();
let bytes = hex!("8c4841753a9a153237aef0f275be");
let (_, msg) = Message::from_bytes((&bytes, 0)).unwrap();
if let ExtendedSquitterADSB(adsb_msg) = msg.df {
if let ME::BDS06 {
inner:
SurfacePosition {
track, groundspeed, ..
},
..
} = adsb_msg.message
{
assert_eq!(track, Some(92.8125));
assert_eq!(groundspeed, Some(17.));
return;
}
}
unreachable!();
}
#[test]
fn test_format() {
let bytes = hex!("8c4841753a9a153237aef0f275be");
let (_, msg) = Message::from_bytes((&bytes, 0)).unwrap();
assert_eq!(
format!("{msg}"),
r#" DF17. Extended Squitter
Address: 484175
Air/Ground: ground
Surface position (BDS 0,6)
Groundspeed: 17 kts
Track angle: 92.8125°
CPR parity: odd
CPR latitude: (39195)
CPR longitude: (110320)
"#
)
}
#[test]
fn test_movement_2_15kt_range() {
let bytes = hex!("8c3461cf399d6059814ea81483a9");
let (_, msg) = Message::from_bytes((&bytes, 0)).unwrap();
if let ExtendedSquitterADSB(adsb_msg) = msg.df {
if let ME::BDS06 {
inner: SurfacePosition { groundspeed, .. },
..
} = adsb_msg.message
{
assert_eq!(groundspeed, Some(8.0));
return;
}
}
unreachable!();
}
#[test]
fn test_movement_4_75kt() {
let bytes = hex!("8c3461cf398d60597b4ea434c4d7");
let (_, msg) = Message::from_bytes((&bytes, 0)).unwrap();
if let ExtendedSquitterADSB(adsb_msg) = msg.df {
if let ME::BDS06 {
inner: SurfacePosition { groundspeed, .. },
..
} = adsb_msg.message
{
assert_eq!(groundspeed, Some(7.5));
return;
}
}
unreachable!();
}
#[test]
fn test_movement_stopped() {
let bytes = hex!("903a33ff40100858d34ff3cce976");
let (_, msg) = Message::from_bytes((&bytes, 0)).unwrap();
if let ExtendedSquitterTisB { cf, .. } = msg.df {
if let ME::BDS06 {
inner: SurfacePosition { groundspeed, .. },
..
} = cf.me
{
assert_eq!(groundspeed, Some(0.0));
return;
}
}
unreachable!();
}
#[test]
fn test_movement_no_info() {
let bytes = hex!("8c3944f8400002acb23cda192b95");
let (_, msg) = Message::from_bytes((&bytes, 0)).unwrap();
if let ExtendedSquitterADSB(adsb_msg) = msg.df {
if let ME::BDS06 {
inner: SurfacePosition { groundspeed, .. },
..
} = adsb_msg.message
{
assert_eq!(groundspeed, None);
return;
}
}
unreachable!();
}
#[test]
fn test_movement_1_2kt_range() {
let bytes = hex!("8c394c0f389b1667e947db7bb8bc");
let (_, msg) = Message::from_bytes((&bytes, 0)).unwrap();
if let ExtendedSquitterADSB(adsb_msg) = msg.df {
if let ME::BDS06 {
inner: SurfacePosition { groundspeed, .. },
..
} = adsb_msg.message
{
assert_eq!(groundspeed, Some(1.0));
return;
}
}
unreachable!();
}
#[test]
fn test_movement_15_70kt_range() {
let bytes = hex!("8c3461cf3a7f3059c94e5bf4e169");
let (_, msg) = Message::from_bytes((&bytes, 0)).unwrap();
if let ExtendedSquitterADSB(adsb_msg) = msg.df {
if let ME::BDS06 {
inner: SurfacePosition { groundspeed, .. },
..
} = adsb_msg.message
{
assert_eq!(groundspeed, Some(15.0));
return;
}
}
unreachable!();
}
#[test]
fn test_movement_70_100kt_range() {
let bytes = hex!("8c3950cf3dede47bac304d3b5122");
let (_, msg) = Message::from_bytes((&bytes, 0)).unwrap();
if let ExtendedSquitterADSB(adsb_msg) = msg.df {
if let ME::BDS06 {
inner: SurfacePosition { groundspeed, .. },
..
} = adsb_msg.message
{
assert_eq!(groundspeed, Some(70.0));
return;
}
}
unreachable!();
}
#[test]
fn test_movement_100_175kt_range() {
let bytes = hex!("8c3933203edde47b9e2ffa5e77b8");
let (_, msg) = Message::from_bytes((&bytes, 0)).unwrap();
if let ExtendedSquitterADSB(adsb_msg) = msg.df {
if let ME::BDS06 {
inner: SurfacePosition { groundspeed, .. },
..
} = adsb_msg.message
{
assert_eq!(groundspeed, Some(100.0));
return;
}
}
unreachable!();
}
#[test]
fn test_movement_175kt_plus() {
let bytes = hex!("8d3933203fcde2a84e39e1c6c5bc");
let (_, msg) = Message::from_bytes((&bytes, 0)).unwrap();
if let ExtendedSquitterADSB(adsb_msg) = msg.df {
if let ME::BDS06 {
inner: SurfacePosition { groundspeed, .. },
..
} = adsb_msg.message
{
assert_eq!(groundspeed, Some(175.0));
return;
}
}
unreachable!();
}
#[test]
fn test_track_invalid() {
let bytes = hex!("903a33ff40100858d34ff3cce976");
let (_, msg) = Message::from_bytes((&bytes, 0)).unwrap();
if let ExtendedSquitterTisB { cf, .. } = msg.df {
if let ME::BDS06 {
inner: SurfacePosition { track, .. },
..
} = cf.me
{
assert_eq!(track, None);
return;
}
}
unreachable!();
}
}