diff --git a/Cargo.lock b/Cargo.lock index e4ecb39..3093bd1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -147,12 +147,6 @@ dependencies = [ "safemem", ] -[[package]] -name = "bumpalo" -version = "3.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f1e260c3a9040a7c19a12468758f4c16f31a81a1fe087482be9570ec864bb6c" - [[package]] name = "byteorder" version = "1.4.3" @@ -278,15 +272,6 @@ dependencies = [ "generic-array 0.14.4", ] -[[package]] -name = "encoding_rs" -version = "0.8.29" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a74ea89a0a1b98f6332de42c95baff457ada66d1cb4030f9ff151b2041a1c746" -dependencies = [ - "cfg-if", -] - [[package]] name = "enum-as-inner" version = "0.3.3" @@ -608,10 +593,11 @@ dependencies = [ name = "heliwatch" version = "0.1.0" dependencies = [ + "adsb", + "beast", "csv", "futures", "geo", - "reqwest", "serde", "serde_json", "tokio", @@ -719,19 +705,6 @@ dependencies = [ "want", ] -[[package]] -name = "hyper-tls" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905" -dependencies = [ - "bytes", - "hyper", - "native-tls", - "tokio", - "tokio-native-tls", -] - [[package]] name = "idna" version = "0.2.3" @@ -780,7 +753,7 @@ dependencies = [ "socket2 0.3.19", "widestring", "winapi", - "winreg 0.6.2", + "winreg", ] [[package]] @@ -804,15 +777,6 @@ dependencies = [ "minidom", ] -[[package]] -name = "js-sys" -version = "0.3.55" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7cc9ffccd38c451a86bf13657df244e9c3f37493cce8e5e21e940963777acc84" -dependencies = [ - "wasm-bindgen", -] - [[package]] name = "keccak" version = "0.1.0" @@ -1500,41 +1464,6 @@ dependencies = [ "winapi", ] -[[package]] -name = "reqwest" -version = "0.11.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "66d2927ca2f685faf0fc620ac4834690d29e7abb153add10f5812eef20b5e280" -dependencies = [ - "base64", - "bytes", - "encoding_rs", - "futures-core", - "futures-util", - "http", - "http-body", - "hyper", - "hyper-tls", - "ipnet", - "js-sys", - "lazy_static", - "log", - "mime", - "native-tls", - "percent-encoding", - "pin-project-lite", - "serde", - "serde_json", - "serde_urlencoded", - "tokio", - "tokio-native-tls", - "url", - "wasm-bindgen", - "wasm-bindgen-futures", - "web-sys", - "winreg 0.7.0", -] - [[package]] name = "resolv-conf" version = "0.7.0" @@ -2270,82 +2199,6 @@ version = "0.10.2+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6" -[[package]] -name = "wasm-bindgen" -version = "0.2.78" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "632f73e236b219150ea279196e54e610f5dbafa5d61786303d4da54f84e47fce" -dependencies = [ - "cfg-if", - "wasm-bindgen-macro", -] - -[[package]] -name = "wasm-bindgen-backend" -version = "0.2.78" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a317bf8f9fba2476b4b2c85ef4c4af8ff39c3c7f0cdfeed4f82c34a880aa837b" -dependencies = [ - "bumpalo", - "lazy_static", - "log", - "proc-macro2", - "quote", - "syn", - "wasm-bindgen-shared", -] - -[[package]] -name = "wasm-bindgen-futures" -version = "0.4.28" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e8d7523cb1f2a4c96c1317ca690031b714a51cc14e05f712446691f413f5d39" -dependencies = [ - "cfg-if", - "js-sys", - "wasm-bindgen", - "web-sys", -] - -[[package]] -name = "wasm-bindgen-macro" -version = "0.2.78" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d56146e7c495528bf6587663bea13a8eb588d39b36b679d83972e1a2dbbdacf9" -dependencies = [ - "quote", - "wasm-bindgen-macro-support", -] - -[[package]] -name = "wasm-bindgen-macro-support" -version = "0.2.78" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7803e0eea25835f8abdc585cd3021b3deb11543c6fe226dcd30b228857c5c5ab" -dependencies = [ - "proc-macro2", - "quote", - "syn", - "wasm-bindgen-backend", - "wasm-bindgen-shared", -] - -[[package]] -name = "wasm-bindgen-shared" -version = "0.2.78" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0237232789cf037d5480773fe568aac745bfe2afbc11a863e97901780a6b47cc" - -[[package]] -name = "web-sys" -version = "0.3.55" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38eb105f1c59d9eaa6b5cdc92b859d85b926e82cb2e0945cd0c9259faa6fe9fb" -dependencies = [ - "js-sys", - "wasm-bindgen", -] - [[package]] name = "widestring" version = "0.4.3" @@ -2383,15 +2236,6 @@ dependencies = [ "winapi", ] -[[package]] -name = "winreg" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0120db82e8a1e0b9fb3345a539c478767c0048d842860994d96113d5b667bd69" -dependencies = [ - "winapi", -] - [[package]] name = "wyz" version = "0.2.0" diff --git a/beast/src/aircrafts.rs b/beast/src/aircrafts.rs index f5302c9..0264af4 100644 --- a/beast/src/aircrafts.rs +++ b/beast/src/aircrafts.rs @@ -54,7 +54,7 @@ impl Entry { self.last_update = Some(Instant::now()); } - pub fn location(&self) -> Option { + pub fn position(&self) -> Option { match (&self.cpr1, &self.cpr2) { (Some(cpr1), Some(cpr2)) => { let pos = adsb::cpr::get_position((cpr1, cpr2))?; @@ -70,6 +70,17 @@ impl Entry { None } } + + 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 { + self.altitude.map(|altitude| altitude as f64 * 0.3048) + } } #[derive(Clone)] diff --git a/collectd-stats/src/main.rs b/collectd-stats/src/main.rs index 84fe663..753b8a2 100644 --- a/collectd-stats/src/main.rs +++ b/collectd-stats/src/main.rs @@ -57,7 +57,7 @@ async fn main() { update_min(&entry.ground_speed, &mut min_speed); update_max(&entry.ground_speed, &mut max_speed); - if let Some(pos) = entry.location() { + if let Some(pos) = entry.position() { let altitude_m = entry.altitude.unwrap_or(0) as f64 * 0.3048; let distance_km = WGS84::from_degrees_and_meters(pos.latitude, pos.longitude, altitude_m) .distance(&home) / 1000.0; diff --git a/heliwatch/Cargo.toml b/heliwatch/Cargo.toml index f6ba974..d59f261 100644 --- a/heliwatch/Cargo.toml +++ b/heliwatch/Cargo.toml @@ -6,10 +6,11 @@ edition = "2021" [dependencies] futures = "0.3" tokio = { version = "1", features = ["full"] } -reqwest = { version = "0.11", features = ["json"] } serde = { version = "1", features = ["derive"] } serde_json = "1" geo = "0.18" csv = "1.1" tokio-xmpp = "3" xmpp-parsers = "0.18" +adsb = "0.3" +beast = { path = "../beast" } diff --git a/heliwatch/src/adsb.rs b/heliwatch/src/adsb.rs index a903e64..73f020b 100644 --- a/heliwatch/src/adsb.rs +++ b/heliwatch/src/adsb.rs @@ -1,59 +1,15 @@ use std::collections::{HashMap, HashSet}; use std::sync::Arc; use std::time::{Duration, Instant}; -use serde::Deserialize; use tokio::sync::mpsc::{channel, Receiver}; use super::location::Locations; /// ft -const MAX_ALTITUDE: u32 = 5000; +const MAX_ALTITUDE: u16 = 5000; /// s const STATE_TIMEOUT: u64 = 180; -#[derive(Deserialize, Debug, Clone)] -pub struct Info { - hex: String, - flight: String, - lat: f64, - lon: f64, - /// ft - altitude: u32, - track: u32, - /// kts - speed: u32, -} - -impl Info { - pub fn get_hex(&self) -> &str { - let mut hex = &self.hex[..]; - while hex.len() > 0 && hex.chars().last().unwrap().is_whitespace() { - hex = &hex[..hex.len() - 1]; - } - hex - } - - pub fn get_flight(&self) -> Option { - let mut i = self.flight.len(); - while i > 0 && self.flight.chars().nth(i - 1).unwrap().is_whitespace() { - i -= 1; - } - if i > 0 { - Some(self.flight[0..i].to_string()) - } else { - None - } - } - - pub fn get_altitude_m(&self) -> f64 { - self.altitude as f64 * 0.3048 - } - - // pub fn get_speed_kph(&self) -> f64 { - // self.speed as f64 * 1.852 - // } -} - #[derive(Debug, Clone)] pub struct Event { pub action: Action, @@ -69,41 +25,63 @@ pub enum Action { Ignored, } +#[derive(Debug, Clone)] +pub struct Info { + pub hex: String, + pub flight: Option, + pub altitude_m: Option, +} + struct State { - info: Option, + info: Info, + position: Option, location: Option>, last: Instant, } impl State { - pub fn new() -> Self { + pub fn new(hex: String) -> Self { State { - info: None, + info: Info { + hex, + flight: None, + altitude_m: None, + }, + position: None, location: None, last: Instant::now(), } } - pub fn update(&mut self, info: Info, locations: &Locations) -> Option { + pub fn update(&mut self, entry: &beast::aircrafts::Entry, locations: &Locations) -> Option { self.last = Instant::now(); - let coord = geo::Coordinate { x: info.lon, y: info.lat }; - if let Some(old_info) = self.info.replace(info) { - let info = self.info.as_ref().unwrap(); - if old_info.lon != info.lon || old_info.lat != info.lat { + if let Some(flight) = entry.flight() { + self.info.flight = Some(flight.to_string()); + } + if let Some(altitude_m) = entry.altitude_m() { + self.info.altitude_m = Some(altitude_m); + } + + let pos = if let Some(pos) = entry.position() { + pos + } else { + return None; + }; + + let coord = geo::Coordinate { x: pos.longitude, y: pos.latitude }; + if let Some(old_pos) = self.position.replace(pos.clone()) { + if old_pos.longitude != pos.longitude || old_pos.latitude != pos.latitude { let location = locations.find(&coord); if location != self.location { if let Some(location) = location { self.location = Some(location.clone()); Some(Event { action: Action::Moved, - info: self.info.clone().unwrap(), location, + info: self.info.clone(), }) } else { - if self.location.is_some() { - println!("{}: move to nowhere", info.flight); - } // move to no location self.location = None; None @@ -120,7 +98,7 @@ impl State { self.location = locations.find(&coord); Some(Event { action: Action::Appeared, - info: self.info.clone().unwrap(), + info: self.info.clone(), location: self.location.clone() .unwrap_or_else(|| Arc::new("irgendwo".to_owned())), }) @@ -128,68 +106,49 @@ impl State { } } -type Result = std::result::Result>; - -async fn fetch(url: &str) -> Result> { - let values: Vec = reqwest::get(url) - .await? - .json() - .await?; - // skip invalid entries - let result = values.into_iter() - .filter_map(|value| serde_json::from_value(value).ok()) - .collect(); - Ok(result) -} - -pub fn run(url: &'static str, locations: Locations) -> Receiver { +pub fn run(host: &'static str, port: u16, locations: Locations) -> Receiver { let (tx, rx) = channel(1); tokio::spawn(async move { + let source = beast::aircrafts::Aircrafts::new(); + source.connect(host, port); + let mut states = HashMap::new(); // ignore anything above MAX_ALTITUDE permanently let mut ignored = HashSet::new(); - // cache that lives longer than dump1090's - let mut flights = HashMap::new(); loop { - let infos = match fetch(url).await { - Ok(infos) => infos, - Err(e) => { - eprintln!("{}", e); - continue; - } - }; let mut events = vec![]; - for mut info in infos { - if info.altitude > MAX_ALTITUDE { - if !ignored.contains(&info.hex) { - ignored.insert(info.hex.clone()); - if let Some(_) = states.remove(&info.hex) { + for (icao_address, entry) in source.read().iter() { + let hex = format!("{}", icao_address); + let entry = entry.read().unwrap(); + + if entry.altitude.map(|altitude| altitude > MAX_ALTITUDE) + .unwrap_or(false) + { + if !ignored.contains(&hex) { + ignored.insert(hex.clone()); + if let Some(_) = states.remove(&hex) { events.push(Event { action: Action::Ignored, - info, + info: Info { + hex, + flight: entry.flight().map(|s| s.to_string()), + altitude_m: entry.altitude_m(), + }, location: Arc::new("ignoriert".to_owned()), }); } } continue; } - if ignored.contains(&info.hex) { + if ignored.contains(&hex) { continue; } - if let Some(flight) = info.get_flight() { - flights.insert(info.hex.clone(), flight); - } else if let Some(flight) = flights.get(&info.hex) { - info.flight = flight.to_string(); - } else { - continue; - }; - - if let Some(event) = states.entry(info.hex.clone()) - .or_insert(State::new()) - .update(info, &locations) { - tx.send(event).await.unwrap(); + if let Some(event) = states.entry(hex.clone()) + .or_insert(State::new(hex.clone())) + .update(&entry, &locations) { + events.push(event); } } @@ -197,7 +156,8 @@ pub fn run(url: &'static str, locations: Locations) -> Receiver { if state.last + Duration::from_secs(STATE_TIMEOUT) < Instant::now() { events.push(Event { action: Action::Disappeared, - info: state.info.clone().unwrap(), + info: state.info + .clone(), location: Arc::new("weg".to_owned()), }); false diff --git a/heliwatch/src/main.rs b/heliwatch/src/main.rs index 5f81c95..80de1c2 100644 --- a/heliwatch/src/main.rs +++ b/heliwatch/src/main.rs @@ -21,7 +21,7 @@ async fn main() -> Result<(), Box> { tokio::task::LocalSet::new().run_until(async move { let aircrafts = aircrafts::Aircrafts::load("aircraftDatabase.csv"); let locations = location::Locations::load("locations.json"); - let mut events = adsb::run("http://radiobert.serv.zentralwerk.org:8080/data.json", locations); + let mut events = adsb::run("radiobert.serv.zentralwerk.org", 30005, locations); let args: Vec = args().collect(); if args.len() != 4 { @@ -36,13 +36,13 @@ async fn main() -> Result<(), Box> { while let Some(event) = events.recv().await { println!("event: {:?}", event); - let flight = if let Some(flight) = event.info.get_flight() { - format!("Flug {} [{}]", flight, event.info.get_hex()) + let flight = if let Some(flight) = event.info.flight { + format!("Flug {} [{}]", flight, event.info.hex) } else { - format!("[{}]", event.info.get_hex()) + format!("[{}]", event.info.hex) }; let identifier; - if let Some(aircraft) = aircrafts.find(&event.info.get_hex()) { + if let Some(aircraft) = aircrafts.find(&event.info.hex) { println!("aircraft: {:?}", aircraft); match (&aircraft.owner, &aircraft.registration, &aircraft.model) { (Some(owner), Some(registration), Some(model)) => @@ -70,13 +70,13 @@ async fn main() -> Result<(), Box> { adsb::Action::Appeared => format!("{} ist {:.0}m über {} aufgetaucht", identifier, - event.info.get_altitude_m(), + event.info.altitude_m.unwrap_or(0.0), event.location ), adsb::Action::Moved => format!("{} fliegt jetzt {:.0}m über {}", identifier, - event.info.get_altitude_m(), + event.info.altitude_m.unwrap_or(0.0), event.location ), adsb::Action::Disappeared => @@ -84,7 +84,7 @@ async fn main() -> Result<(), Box> { adsb::Action::Ignored => format!("{} wird ab {:.0}m ignoriert", identifier, - event.info.get_altitude_m() + event.info.altitude_m.unwrap_or(0.0), ), }; diff --git a/http-json/src/main.rs b/http-json/src/main.rs index 9f1e78f..8b04beb 100644 --- a/http-json/src/main.rs +++ b/http-json/src/main.rs @@ -19,7 +19,7 @@ pub struct Aircraft { impl Aircraft { pub fn new(icao_address: &ICAOAddress, entry: &Entry) -> Self { - let pos = entry.location(); + let pos = entry.position(); Aircraft { hex: format!("{}", icao_address), flight: entry.callsign.clone(),