2021-11-08 23:32:39 +01:00
|
|
|
use std::collections::HashMap;
|
|
|
|
use std::sync::{Arc, RwLock};
|
|
|
|
use std::time::{Duration, Instant};
|
|
|
|
use futures::stream::StreamExt;
|
|
|
|
use tokio::sync::mpsc::channel;
|
2021-12-17 01:13:44 +01:00
|
|
|
use adsb_deku::ICAO;
|
2021-11-08 23:32:39 +01:00
|
|
|
use super::beast;
|
|
|
|
|
2021-11-10 20:25:20 +01:00
|
|
|
const JITTER_WINDOW: usize = 5;
|
|
|
|
|
2021-11-08 23:32:39 +01:00
|
|
|
#[derive(Default)]
|
|
|
|
pub struct Entry {
|
2021-12-17 01:13:44 +01:00
|
|
|
pub category: Option<(adsb_deku::adsb::TypeCoding, u8)>,
|
2021-11-08 23:32:39 +01:00
|
|
|
pub callsign: Option<String>,
|
2021-12-17 01:13:44 +01:00
|
|
|
pub altitude: Option<u32>,
|
|
|
|
cpr1: Option<adsb_deku::Altitude>,
|
|
|
|
cpr2: Option<adsb_deku::Altitude>,
|
|
|
|
// jitter buffer
|
|
|
|
positions: [Option<adsb_deku::cpr::Position>; JITTER_WINDOW],
|
2021-11-08 23:32:39 +01:00
|
|
|
pub heading: Option<f64>,
|
2021-12-17 01:13:44 +01:00
|
|
|
pub speed: Option<f64>,
|
2021-11-08 23:32:39 +01:00
|
|
|
pub vertical_rate: Option<i16>,
|
|
|
|
last_update: Option<Instant>,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Entry {
|
|
|
|
const MAX_AGE: u64 = 300;
|
|
|
|
|
2021-12-17 01:13:44 +01:00
|
|
|
fn update(&mut self, kind: adsb_deku::adsb::ME) {
|
2021-11-08 23:32:39 +01:00
|
|
|
match kind {
|
2021-12-17 01:13:44 +01:00
|
|
|
adsb_deku::adsb::ME::AirbornePositionBaroAltitude(altitude) |
|
|
|
|
adsb_deku::adsb::ME::AirbornePositionGNSSAltitude(altitude) => {
|
|
|
|
self.altitude = Some(altitude.alt);
|
|
|
|
if self.cpr2.map_or(false, |cpr2| cpr2.odd_flag != altitude.odd_flag) {
|
|
|
|
// if last altitude had a different odd flag,
|
|
|
|
// shift the entries
|
2021-11-08 23:32:39 +01:00
|
|
|
self.cpr1 = self.cpr2.take();
|
|
|
|
}
|
2021-12-17 01:13:44 +01:00
|
|
|
// add the new entry
|
|
|
|
self.cpr2 = Some(altitude);
|
2021-11-10 18:59:59 +01:00
|
|
|
|
|
|
|
match (&self.cpr1, &self.cpr2) {
|
|
|
|
(Some(cpr1), Some(cpr2)) => {
|
2021-12-17 01:13:44 +01:00
|
|
|
if let Some(pos) = adsb_deku::cpr::get_position((cpr1, cpr2)) {
|
|
|
|
if pos.latitude < -90. || pos.latitude > 90. ||
|
|
|
|
pos.longitude < -180. || pos.longitude > 180.
|
2021-11-10 18:59:59 +01:00
|
|
|
{
|
2021-12-17 01:13:44 +01:00
|
|
|
eprintln!("invalid position: {:?}", pos);
|
|
|
|
} else {
|
|
|
|
// shift previous positions in jitter buffer
|
2021-11-10 18:59:59 +01:00
|
|
|
for i in 1..self.positions.len() {
|
|
|
|
self.positions[i - 1] = self.positions[i].take();
|
|
|
|
}
|
2021-12-17 01:13:44 +01:00
|
|
|
// add position to jitter buffer
|
2021-11-10 18:59:59 +01:00
|
|
|
self.positions[self.positions.len() - 1] = Some(pos);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
_ => {}
|
|
|
|
}
|
2021-11-08 23:32:39 +01:00
|
|
|
}
|
2021-12-17 01:13:44 +01:00
|
|
|
adsb_deku::adsb::ME::AirborneVelocity(velocity) => {
|
|
|
|
if let Some((heading, speed, vrate)) = velocity.calculate() {
|
|
|
|
self.heading = Some(heading);
|
|
|
|
self.speed = Some(speed);
|
|
|
|
self.vertical_rate = Some(vrate);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
adsb_deku::adsb::ME::AircraftIdentification(ident) => {
|
|
|
|
self.category = Some((ident.tc, ident.ca));
|
|
|
|
self.callsign = Some(ident.cn);
|
2021-11-08 23:32:39 +01:00
|
|
|
}
|
2021-12-17 01:13:44 +01:00
|
|
|
adsb_deku::adsb::ME::TargetStateAndStatusInformation(_) => {}
|
|
|
|
adsb_deku::adsb::ME::AircraftStatus(_) => {}
|
|
|
|
adsb_deku::adsb::ME::AircraftOperationStatus(_) => {}
|
|
|
|
msg => {
|
|
|
|
eprintln!("unhandled adsb msg: {:?}", msg);
|
2021-11-08 23:32:39 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
self.last_update = Some(Instant::now());
|
|
|
|
}
|
|
|
|
|
2021-12-17 01:13:44 +01:00
|
|
|
pub fn position(&self) -> Option<&adsb_deku::cpr::Position> {
|
2021-11-10 18:59:59 +01:00
|
|
|
let mut prev_lat = self.positions[0].as_ref()?.latitude;
|
|
|
|
let mut prev_lon = self.positions[0].as_ref()?.longitude;
|
2021-11-10 19:24:30 +01:00
|
|
|
for i in 1..self.positions.len() {
|
2021-11-10 18:59:59 +01:00
|
|
|
let lat = self.positions[i].as_ref()?.latitude;
|
|
|
|
let lon = self.positions[i].as_ref()?.longitude;
|
|
|
|
if lat < prev_lat - 1.0 ||
|
|
|
|
lat > prev_lat + 1.0 ||
|
|
|
|
lon < prev_lon - 1.0 ||
|
|
|
|
lon > prev_lon + 1.0
|
|
|
|
{
|
|
|
|
// erroneous jitter detected
|
2021-12-17 01:13:44 +01:00
|
|
|
eprintln!("{:?} error in: {:?}", self.callsign, self.positions.iter().filter_map(|pos| {
|
|
|
|
pos.as_ref().map(|pos| (pos.latitude as i32, pos.longitude as i32))
|
|
|
|
}).collect::<Vec<_>>());
|
2021-11-10 18:59:59 +01:00
|
|
|
return None;
|
2021-11-10 18:44:12 +01:00
|
|
|
}
|
2021-11-10 18:59:59 +01:00
|
|
|
prev_lat = lat;
|
|
|
|
prev_lon = lon;
|
2021-11-10 18:44:12 +01:00
|
|
|
}
|
2021-11-10 18:59:59 +01:00
|
|
|
|
|
|
|
self.positions[self.positions.len() - 1].as_ref()
|
2021-11-10 18:44:12 +01:00
|
|
|
}
|
|
|
|
|
2021-11-09 00:56:50 +01:00
|
|
|
pub fn flight(&self) -> Option<&str> {
|
|
|
|
self.callsign.as_ref().map(|callsign| {
|
|
|
|
callsign.split(char::is_whitespace)
|
|
|
|
.next().unwrap()
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn altitude_m(&self) -> Option<f64> {
|
|
|
|
self.altitude.map(|altitude| altitude as f64 * 0.3048)
|
|
|
|
}
|
2021-11-08 23:32:39 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Clone)]
|
|
|
|
pub struct Aircrafts {
|
2021-12-17 01:13:44 +01:00
|
|
|
state: Arc<RwLock<HashMap<ICAO, RwLock<Entry>>>>,
|
2021-11-08 23:32:39 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
impl Aircrafts {
|
|
|
|
pub fn new() -> Self {
|
|
|
|
Aircrafts {
|
|
|
|
state: Arc::new(RwLock::new(HashMap::new())),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-12-17 01:13:44 +01:00
|
|
|
pub fn read(&self) -> std::sync::RwLockReadGuard<HashMap<ICAO, RwLock<Entry>>> {
|
2021-11-08 23:32:39 +01:00
|
|
|
self.state.read().unwrap()
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn connect(&self, host: &'static str, port: u16) {
|
|
|
|
// buffering channel because readsb is very sensitive
|
|
|
|
let (tx, mut rx) = channel(16 * 1024);
|
|
|
|
|
|
|
|
// network input
|
|
|
|
tokio::spawn(async move {
|
|
|
|
loop {
|
|
|
|
let mut stream;
|
|
|
|
if let Ok(stream_) = beast::connect(host, port).await {
|
|
|
|
stream = Box::pin(stream_);
|
|
|
|
} else {
|
|
|
|
tokio::time::sleep(Duration::from_secs(1)).await;
|
|
|
|
// Retry
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
while let Some(frame) = stream.next().await {
|
|
|
|
let _ = tx.send(frame).await;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
// state update
|
|
|
|
let state = self.state.clone();
|
|
|
|
tokio::spawn(async move {
|
|
|
|
while let Some(frame) = rx.recv().await {
|
|
|
|
match frame.parse_adsb() {
|
2021-12-17 01:13:44 +01:00
|
|
|
Some(adsb_deku::Frame { df: adsb_deku::DF::ADSB(adsb), crc }) => {
|
|
|
|
if crc != 0 {
|
|
|
|
eprintln!("crc: {:02X}", crc);
|
|
|
|
}
|
2021-11-08 23:32:39 +01:00
|
|
|
state.write().unwrap()
|
2021-12-17 01:13:44 +01:00
|
|
|
.entry(adsb.icao)
|
2021-11-08 23:32:39 +01:00
|
|
|
.or_default()
|
|
|
|
.write().unwrap()
|
2021-12-17 01:13:44 +01:00
|
|
|
.update(adsb.me);
|
2021-11-08 23:32:39 +01:00
|
|
|
}
|
|
|
|
_ => {}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
// discard old states
|
|
|
|
let state = self.state.clone();
|
|
|
|
tokio::spawn(async move {
|
|
|
|
loop {
|
|
|
|
state.write().unwrap().
|
|
|
|
retain(|_, entry| {
|
|
|
|
entry.read().unwrap()
|
|
|
|
.last_update.map(|last_update| {
|
|
|
|
last_update + Duration::from_secs(Entry::MAX_AGE) > Instant::now()
|
|
|
|
})
|
|
|
|
.unwrap_or(false)
|
|
|
|
});
|
|
|
|
tokio::time::sleep(Duration::from_secs(1)).await;
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|