#![no_std]
#![forbid(unsafe_code)]
#![warn(missing_docs)]
pub mod bits;
pub mod crc;
pub mod error;
pub mod frame;
pub mod message;
pub mod string;
pub use error::AdsbError;
pub use frame::Frame;
pub use message::AdsbMessage;
pub fn decode_line(line: &str) -> Result<AdsbMessage, AdsbError> {
Frame::parse(line)?.decode_message()
}
#[allow(
clippy::cast_possible_truncation,
reason = "icao_address is documented as a 24-bit field; only the low 24 bits are used"
)]
pub fn encode_frame(
message: &AdsbMessage,
icao_address: u32,
capability: u8,
) -> Result<Frame, AdsbError> {
let mut me = [0u8; 7];
let mut w = bits::BitWriter::new(&mut me);
message.encode(&mut w)?;
let mut bytes = [0u8; 14];
bytes[0] = (17 << 3) | (capability & 0b111);
bytes[1] = (icao_address >> 16) as u8;
bytes[2] = (icao_address >> 8) as u8;
bytes[3] = icao_address as u8;
bytes[4..11].copy_from_slice(&me);
Ok(Frame::from_data(bytes))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn decodes_a_real_frame() {
let msg = decode_line("8D406B902015A678D4D220AA4BDA").unwrap();
let AdsbMessage::Identification(id) = msg else {
panic!("expected Identification");
};
assert_eq!(id.type_code, 4);
}
#[test]
fn decodes_and_recognizes_the_klm_callsign_fixture() {
let msg = decode_line("8D4840D6202CC371C32CE0576098").unwrap();
let AdsbMessage::Identification(id) = msg else {
panic!("expected Identification");
};
assert_eq!(id.callsign.as_str(), "KLM1023");
}
#[test]
fn decodes_a_real_airborne_position_frame() {
let msg = decode_line("8D40058B58C901375147EFD09357").unwrap();
let AdsbMessage::AirbornePosition(pos) = msg else {
panic!("expected AirbornePosition");
};
assert_eq!(pos.altitude, message::Altitude::BarometricFeet(39_000));
assert_eq!(pos.lat_cpr, 39848);
assert_eq!(pos.lon_cpr, 83951);
}
#[test]
fn encodes_and_decodes_a_surface_position_through_a_df17_frame() {
let original = AdsbMessage::SurfacePosition(message::SurfacePosition {
type_code: 8,
movement_raw: 38,
track_status: true,
track_raw: 35,
utc_synced: true,
cpr_format: message::CprFormat::Even,
lat_cpr: 11052,
lon_cpr: 86083,
});
let frame = encode_frame(&original, 0x003A_23FF, 5).unwrap();
let mut buf = [0u8; 28];
let hex = frame.write_hex(&mut buf);
let re_decoded = decode_line(hex).unwrap();
assert_eq!(re_decoded, original);
}
#[test]
fn decodes_a_real_airborne_velocity_frame() {
let msg = decode_line("8D485020994409940838175B284F").unwrap();
let AdsbMessage::AirborneVelocity(vel) = msg else {
panic!("expected AirborneVelocity");
};
assert_eq!(vel.vertical_rate_fpm, Some(-832));
assert!((vel.ground_speed_knots().unwrap() - 159.2).abs() < 0.1);
}
#[test]
fn encode_frame_round_trips() {
let original = decode_line("8D4840D6202CC371C32CE0576098").unwrap();
let frame = encode_frame(&original, 0x0048_40D6, 5).unwrap();
let mut buf = [0u8; 28];
let hex = frame.write_hex(&mut buf);
let re_decoded = decode_line(hex).unwrap();
assert_eq!(re_decoded, original);
}
}