heliwatch: switch from json to beast

This commit is contained in:
Astro 2021-11-09 00:56:50 +01:00
parent afca212069
commit 873f0b639f
7 changed files with 89 additions and 273 deletions

162
Cargo.lock generated
View File

@ -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"

View File

@ -54,7 +54,7 @@ impl Entry {
self.last_update = Some(Instant::now());
}
pub fn location(&self) -> Option<adsb::Position> {
pub fn position(&self) -> Option<adsb::Position> {
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<f64> {
self.altitude.map(|altitude| altitude as f64 * 0.3048)
}
}
#[derive(Clone)]

View File

@ -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;

View File

@ -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" }

View File

@ -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<String> {
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<String>,
pub altitude_m: Option<f64>,
}
struct State {
info: Option<Info>,
info: Info,
position: Option<adsb::Position>,
location: Option<Arc<String>>,
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<Event> {
pub fn update(&mut self, entry: &beast::aircrafts::Entry, locations: &Locations) -> Option<Event> {
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<T> = std::result::Result<T, Box<dyn std::error::Error + Send + Sync>>;
async fn fetch(url: &str) -> Result<Vec<Info>> {
let values: Vec<serde_json::Value> = 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<Event> {
pub fn run(host: &'static str, port: u16, locations: Locations) -> Receiver<Event> {
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<Event> {
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

View File

@ -21,7 +21,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
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<String> = args().collect();
if args.len() != 4 {
@ -36,13 +36,13 @@ async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
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<dyn std::error::Error + Send + Sync>> {
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<dyn std::error::Error + Send + Sync>> {
adsb::Action::Ignored =>
format!("{} wird ab {:.0}m ignoriert",
identifier,
event.info.get_altitude_m()
event.info.altitude_m.unwrap_or(0.0),
),
};

View File

@ -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(),