add beast, http-json, collectd-stats

This commit is contained in:
Astro 2021-11-08 23:32:39 +01:00
parent 1fb409758b
commit afca212069
11 changed files with 924 additions and 10 deletions

422
Cargo.lock generated
View File

@ -2,6 +2,25 @@
# It is not intended for manual editing.
version = 3
[[package]]
name = "adsb"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9245ecd631f63f1995269dc9f88fdda8fc8e9a308c4196315cfdedd1ccfccc07"
dependencies = [
"lazy_static",
"nom",
]
[[package]]
name = "approx"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f0e60b75072ecd4168020818c0107f2857bb6c4e64252d8d3983f6263b40a5c3"
dependencies = [
"num-traits",
]
[[package]]
name = "approx"
version = "0.4.0"
@ -11,6 +30,12 @@ dependencies = [
"num-traits",
]
[[package]]
name = "arrayvec"
version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b"
[[package]]
name = "as-slice"
version = "0.1.5"
@ -46,12 +71,33 @@ version = "0.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd"
[[package]]
name = "beast"
version = "0.1.0"
dependencies = [
"adsb",
"futures",
"tokio",
]
[[package]]
name = "bitflags"
version = "1.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
[[package]]
name = "bitvec"
version = "0.19.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8942c8d352ae1838c9dda0b0ca2ab657696ef2232a20147cf1b30ae1a9cb4321"
dependencies = [
"funty",
"radium",
"tap",
"wyz",
]
[[package]]
name = "blake2"
version = "0.9.2"
@ -81,9 +127,9 @@ checksum = "8d696c370c750c948ada61c69a0ee2cbbb9c50b1019ddb86d9317157a99c2cae"
[[package]]
name = "bstr"
version = "0.2.17"
version = "0.2.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ba3569f383e8f1598449f1a423e72e99569137b47740b1da11ef19af3d5c3223"
checksum = "a40b47ad93e1a5404e6c18dec46b628214fee441c70f4ab5d6942142cc268a3d"
dependencies = [
"lazy_static",
"memchr",
@ -91,6 +137,16 @@ dependencies = [
"serde",
]
[[package]]
name = "buf_redux"
version = "0.8.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b953a6887648bb07a535631f2bc00fbdb2a2216f135552cb3f534ed136b9c07f"
dependencies = [
"memchr",
"safemem",
]
[[package]]
name = "bumpalo"
version = "3.8.0"
@ -131,6 +187,15 @@ dependencies = [
"num-traits",
]
[[package]]
name = "collectd-stats"
version = "0.1.0"
dependencies = [
"beast",
"nav-types",
"tokio",
]
[[package]]
name = "core-foundation"
version = "0.9.2"
@ -265,6 +330,12 @@ dependencies = [
"percent-encoding",
]
[[package]]
name = "funty"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fed34cd105917e91daa4da6b3728c47b068749d6a62c59811f06ed2ac71d9da7"
[[package]]
name = "futf"
version = "0.1.4"
@ -417,7 +488,7 @@ version = "0.7.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d8bd2e95dd9f5c8ff74159ed9205ad7fd239a9569173a550863976421b45d2bb"
dependencies = [
"approx",
"approx 0.4.0",
"num-traits",
"rstar",
]
@ -487,6 +558,31 @@ version = "0.11.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e"
[[package]]
name = "headers"
version = "0.3.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a4c4eb0471fcb85846d8b0690695ef354f9afb11cb03cac2e1d7c9253351afb0"
dependencies = [
"base64",
"bitflags",
"bytes",
"headers-core",
"http",
"httpdate",
"mime",
"sha-1",
]
[[package]]
name = "headers-core"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e7f66481bfee273957b1f20485a4ff3362987f85b2c236580d81b4eb7a326429"
dependencies = [
"http",
]
[[package]]
name = "heapless"
version = "0.6.1"
@ -575,6 +671,18 @@ dependencies = [
"pin-project-lite",
]
[[package]]
name = "http-json"
version = "0.1.0"
dependencies = [
"adsb",
"beast",
"serde",
"serde_json",
"tokio",
"warp",
]
[[package]]
name = "httparse"
version = "1.5.1"
@ -645,6 +753,15 @@ dependencies = [
"hashbrown",
]
[[package]]
name = "input_buffer"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f97967975f448f1a7ddb12b0bc41069d09ed6a1c161a92687e057325db35d413"
dependencies = [
"bytes",
]
[[package]]
name = "instant"
version = "0.1.12"
@ -708,6 +825,19 @@ version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
[[package]]
name = "lexical-core"
version = "0.7.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6607c62aa161d23d17a9072cc5da0be67cdfc89d3afb1e8d9c842bebc2525ffe"
dependencies = [
"arrayvec",
"bitflags",
"cfg-if",
"ryu",
"static_assertions",
]
[[package]]
name = "libc"
version = "0.2.105"
@ -780,10 +910,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a3e378b66a060d48947b590737b30a1be76706c8dd7b8ba0f2fe3989c68a853f"
[[package]]
name = "memchr"
version = "2.4.1"
name = "matrixmultiply"
version = "0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a"
checksum = "916806ba0031cd542105d916a97c8572e1fa6dd79c9c51e7eb43a09ec2dd84c1"
dependencies = [
"rawpointer",
]
[[package]]
name = "memchr"
version = "2.3.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0ee1c47aaa256ecabcaea351eae4a9b01ef39ed810004e298d2511ed284b1525"
[[package]]
name = "mime"
@ -791,6 +930,16 @@ version = "0.3.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2a60c7ce501c71e03a9c9c0d35b861413ae925bd979cc7a4e30d060069aaac8d"
[[package]]
name = "mime_guess"
version = "2.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2684d4c2e97d99848d30b324b00c8fcc7e5c897b7cbb5819b09e7c90e8baf212"
dependencies = [
"mime",
"unicase",
]
[[package]]
name = "minidom"
version = "0.13.0"
@ -822,6 +971,42 @@ dependencies = [
"winapi",
]
[[package]]
name = "multipart"
version = "0.17.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d050aeedc89243f5347c3e237e3e13dc76fbe4ae3742a57b94dc14f69acf76d4"
dependencies = [
"buf_redux",
"httparse",
"log",
"mime",
"mime_guess",
"quick-error",
"rand 0.7.3",
"safemem",
"tempfile",
"twoway",
]
[[package]]
name = "nalgebra"
version = "0.22.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "500430fe18b836c0099c22e716139eae37210ecf8e1ae3931b73078e9a2de7a9"
dependencies = [
"approx 0.3.2",
"generic-array 0.13.3",
"matrixmultiply",
"num-complex",
"num-rational",
"num-traits",
"rand 0.7.3",
"rand_distr",
"simba",
"typenum",
]
[[package]]
name = "native-tls"
version = "0.2.8"
@ -840,12 +1025,34 @@ dependencies = [
"tempfile",
]
[[package]]
name = "nav-types"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a761b66b2e27aba4945f8e82ad20aef2dac0f47f9659ecbbdf54095c79828a37"
dependencies = [
"nalgebra",
]
[[package]]
name = "new_debug_unreachable"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e4a24736216ec316047a1fc4252e27dabb04218aa4a3f37c6e7ddbf1f9782b54"
[[package]]
name = "nom"
version = "6.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c5c51b9083a3c620fa67a2a635d1ce7d95b897e957d6b28ff9a5da960a103a6"
dependencies = [
"bitvec",
"funty",
"lexical-core",
"memchr",
"version_check",
]
[[package]]
name = "ntapi"
version = "0.3.6"
@ -855,6 +1062,16 @@ dependencies = [
"winapi",
]
[[package]]
name = "num-complex"
version = "0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b6b19411a9719e753aff12e5187b74d60d3dc449ec3f4dc21e3989c3f554bc95"
dependencies = [
"autocfg",
"num-traits",
]
[[package]]
name = "num-integer"
version = "0.1.44"
@ -865,6 +1082,17 @@ dependencies = [
"num-traits",
]
[[package]]
name = "num-rational"
version = "0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c000134b5dbf44adc5cb772486d335293351644b801551abe8f75c84cfa4aef"
dependencies = [
"autocfg",
"num-integer",
"num-traits",
]
[[package]]
name = "num-traits"
version = "0.2.14"
@ -954,6 +1182,25 @@ dependencies = [
"winapi",
]
[[package]]
name = "paste"
version = "0.1.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "45ca20c77d80be666aef2b45486da86238fabe33e38306bd3118fe4af33fa880"
dependencies = [
"paste-impl",
"proc-macro-hack",
]
[[package]]
name = "paste-impl"
version = "0.1.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d95a7db200b97ef370c8e6de0088252f7e0dfff7d047a28528e47456c0fc98b6"
dependencies = [
"proc-macro-hack",
]
[[package]]
name = "pbkdf2"
version = "0.6.0"
@ -1022,6 +1269,26 @@ dependencies = [
"siphasher",
]
[[package]]
name = "pin-project"
version = "1.0.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "576bc800220cc65dac09e99e97b08b358cfab6e17078de8dc5fee223bd2d0c08"
dependencies = [
"pin-project-internal",
]
[[package]]
name = "pin-project-internal"
version = "1.0.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6e8fe8163d14ce7f0cdac2e040116f22eac817edabff0be91e8aff7e9accf389"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "pin-project-lite"
version = "0.2.7"
@ -1097,6 +1364,12 @@ dependencies = [
"proc-macro2",
]
[[package]]
name = "radium"
version = "0.5.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "941ba9d78d8e2f7ce474c015eea4d9c6d25b6a3327f9832ee29a4de27f91bbb8"
[[package]]
name = "rand"
version = "0.7.3"
@ -1161,6 +1434,15 @@ dependencies = [
"getrandom 0.2.3",
]
[[package]]
name = "rand_distr"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "96977acbdd3a6576fb1d27391900035bf3863d4a16422973a409b488cf29ffb2"
dependencies = [
"rand 0.7.3",
]
[[package]]
name = "rand_hc"
version = "0.2.0"
@ -1188,6 +1470,12 @@ dependencies = [
"rand_core 0.5.1",
]
[[package]]
name = "rawpointer"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "60a357793950651c4ed0f3f52338f53b2f809f32d83a07f72909fa13e4c6c1e3"
[[package]]
name = "redox_syscall"
version = "0.2.10"
@ -1290,6 +1578,12 @@ version = "1.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e"
[[package]]
name = "safemem"
version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ef703b7cb59335eae2eb93ceb664c0eb7ea6bf567079d843e09420219668e072"
[[package]]
name = "sasl"
version = "0.5.0"
@ -1314,6 +1608,12 @@ dependencies = [
"winapi",
]
[[package]]
name = "scoped-tls"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ea6a9290e3c9cf0f18145ef7ffa62d68ee0bf5fcd651017e586dc7fd5da448c2"
[[package]]
name = "scopeguard"
version = "1.1.0"
@ -1451,6 +1751,18 @@ dependencies = [
"libc",
]
[[package]]
name = "simba"
version = "0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "051b345c9ec09734622cbb49e20dfcf72c9b55e473f0ca6a8025409e37c25fb8"
dependencies = [
"approx 0.3.2",
"num-complex",
"num-traits",
"paste",
]
[[package]]
name = "siphasher"
version = "0.3.7"
@ -1496,6 +1808,12 @@ version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3"
[[package]]
name = "static_assertions"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
[[package]]
name = "string_cache"
version = "0.8.2"
@ -1539,6 +1857,12 @@ dependencies = [
"unicode-xid",
]
[[package]]
name = "tap"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369"
[[package]]
name = "tempfile"
version = "3.2.0"
@ -1661,6 +1985,19 @@ dependencies = [
"tokio",
]
[[package]]
name = "tokio-tungstenite"
version = "0.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e1a5f475f1b9d077ea1017ecbc60890fda8e54942d680ca0b1d2b47cfa2d861b"
dependencies = [
"futures-util",
"log",
"pin-project",
"tokio",
"tungstenite",
]
[[package]]
name = "tokio-util"
version = "0.6.8"
@ -1711,6 +2048,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "375a639232caf30edfc78e8d89b2d4c375515393e7af7e16f01cd96917fb2105"
dependencies = [
"cfg-if",
"log",
"pin-project-lite",
"tracing-core",
]
@ -1775,6 +2113,34 @@ version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "59547bce71d9c38b83d9c0e92b6066c4253371f15005def0c30d9657f50c7642"
[[package]]
name = "tungstenite"
version = "0.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ada8297e8d70872fa9a551d93250a9f407beb9f37ef86494eb20012a2ff7c24"
dependencies = [
"base64",
"byteorder",
"bytes",
"http",
"httparse",
"input_buffer",
"log",
"rand 0.8.4",
"sha-1",
"url",
"utf-8",
]
[[package]]
name = "twoway"
version = "0.1.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "59b11b2b5241ba34be09c3cc85a36e56e48f9888862e19cedf23336d35316ed1"
dependencies = [
"memchr",
]
[[package]]
name = "typenum"
version = "1.14.0"
@ -1787,6 +2153,15 @@ version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "56dee185309b50d1f11bfedef0fe6d036842e3fb77413abef29f8f8d1c5d4c1c"
[[package]]
name = "unicase"
version = "2.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "50f37be617794602aabbeee0be4f259dc1778fabe05e2d67ee8f79326d5cb4f6"
dependencies = [
"version_check",
]
[[package]]
name = "unicode-bidi"
version = "0.3.7"
@ -1854,6 +2229,35 @@ dependencies = [
"try-lock",
]
[[package]]
name = "warp"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "332d47745e9a0c38636dbd454729b147d16bd1ed08ae67b3ab281c4506771054"
dependencies = [
"bytes",
"futures",
"headers",
"http",
"hyper",
"log",
"mime",
"mime_guess",
"multipart",
"percent-encoding",
"pin-project",
"scoped-tls",
"serde",
"serde_json",
"serde_urlencoded",
"tokio",
"tokio-stream",
"tokio-tungstenite",
"tokio-util",
"tower-service",
"tracing",
]
[[package]]
name = "wasi"
version = "0.9.0+wasi-snapshot-preview1"
@ -1988,6 +2392,12 @@ dependencies = [
"winapi",
]
[[package]]
name = "wyz"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "85e60b0d1b5f99db2556934e21937020776a5d31520bf169e851ac44e6420214"
[[package]]
name = "xml5ever"
version = "0.16.2"

View File

@ -1,4 +1,7 @@
[workspace]
members = [
"heliwatch",
"beast",
"http-json",
"collectd-stats",
]

10
beast/Cargo.toml Normal file
View File

@ -0,0 +1,10 @@
[package]
name = "beast"
description = "Receive ADS-B data from readsb"
version = "0.1.0"
edition = "2021"
[dependencies]
futures = "0.3"
tokio = { version = "1", features = ["full"] }
adsb = "0.3"

153
beast/src/aircrafts.rs Normal file
View File

@ -0,0 +1,153 @@
use std::collections::HashMap;
use std::sync::{Arc, RwLock};
use std::time::{Duration, Instant};
use futures::stream::StreamExt;
use tokio::sync::mpsc::channel;
use adsb::ICAOAddress;
use super::beast;
#[derive(Default)]
pub struct Entry {
pub emitter_category: Option<u8>,
pub callsign: Option<String>,
pub altitude: Option<u16>,
cpr1: Option<adsb::CPRFrame>,
cpr2: Option<adsb::CPRFrame>,
pub heading: Option<f64>,
pub ground_speed: Option<f64>,
pub vertical_rate: Option<i16>,
pub vertical_rate_source: Option<adsb::VerticalRateSource>,
last_update: Option<Instant>,
}
impl Entry {
const MAX_AGE: u64 = 300;
fn update(&mut self, kind: adsb::ADSBMessageKind) {
match kind {
adsb::ADSBMessageKind::AirbornePosition {
altitude, cpr_frame
} => {
self.altitude = Some(altitude);
if self.cpr2.as_ref().map(|cpr2| cpr2.parity != cpr_frame.parity).unwrap_or(false) {
self.cpr1 = self.cpr2.take();
}
self.cpr2 = Some(cpr_frame);
}
adsb::ADSBMessageKind::AirborneVelocity {
heading, ground_speed,
vertical_rate, vertical_rate_source,
} => {
self.heading = Some(heading);
self.ground_speed = Some(ground_speed);
self.vertical_rate = Some(vertical_rate);
self.vertical_rate_source = Some(vertical_rate_source);
}
adsb::ADSBMessageKind::AircraftIdentification {
emitter_category, callsign
} => {
self.emitter_category = Some(emitter_category);
self.callsign = Some(callsign);
}
}
self.last_update = Some(Instant::now());
}
pub fn location(&self) -> Option<adsb::Position> {
match (&self.cpr1, &self.cpr2) {
(Some(cpr1), Some(cpr2)) => {
let pos = adsb::cpr::get_position((cpr1, cpr2))?;
if pos.latitude >= -90.0 && pos.latitude <= 90.0 &&
pos.longitude >= -180.0 && pos.longitude <= 180.0
{
Some(pos)
} else {
None
}
}
_ =>
None
}
}
}
#[derive(Clone)]
pub struct Aircrafts {
state: Arc<RwLock<HashMap<ICAOAddress, RwLock<Entry>>>>,
}
impl Aircrafts {
pub fn new() -> Self {
Aircrafts {
state: Arc::new(RwLock::new(HashMap::new())),
}
}
pub fn read(&self) -> std::sync::RwLockReadGuard<HashMap<ICAOAddress, RwLock<Entry>>> {
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() {
Some(adsb::Message {
kind: adsb::MessageKind::ADSBMessage {
icao_address,
kind,
crc,
.. },
.. }) if crc =>
{
state.write().unwrap()
.entry(icao_address)
.or_default()
.write().unwrap()
.update(kind);
}
_ => {}
}
}
});
// 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;
}
});
}
}

154
beast/src/beast.rs Normal file
View File

@ -0,0 +1,154 @@
use futures::stream;
use tokio::io::AsyncReadExt;
use tokio::net::TcpStream;
const DEFRAMER_DEFAULT_CAPACITY: usize = 22;
struct Deframer {
buffer: Vec<u8>,
escapes: usize,
}
impl Deframer {
pub fn new() -> Self {
Deframer {
buffer: Vec::with_capacity(DEFRAMER_DEFAULT_CAPACITY),
escapes: 0,
}
}
pub fn push(&mut self, c: u8) -> Option<Frame> {
let mut result = None;
if c == 0x1a {
self.escapes += 1;
} else {
if self.escapes > 0 {
for _ in 0..(self.escapes / 2) {
self.buffer.push(0x1a);
}
if self.escapes & 1 == 1 {
let new_buffer = Vec::with_capacity(DEFRAMER_DEFAULT_CAPACITY);
let buffer = std::mem::replace(&mut self.buffer, new_buffer);
if buffer.len() > 0 {
result = Frame::new(buffer);
}
}
self.escapes = 0;
}
self.buffer.push(c);
}
result
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[repr(u8)]
pub enum FrameType {
ModeAC = '1' as u8,
ModeSShort = '2' as u8,
ModeSLong = '3' as u8,
Config = '4' as u8,
}
#[derive(Debug)]
pub struct Frame {
data: Vec<u8>,
}
impl Frame {
pub fn new(data: Vec<u8>) -> Option<Self> {
if data.len() < 1 {
return None;
}
let this = Frame { data };
match (this.frame_type(), this.data.len()) {
(Some(FrameType::ModeAC), 10) =>
Some(this),
(Some(FrameType::ModeSShort), 15) =>
Some(this),
(Some(FrameType::ModeSLong), 22) =>
Some(this),
_ =>
None // don't care
}
}
pub fn frame_type(&self) -> Option<FrameType> {
match self.data[0] as char {
'1' => Some(FrameType::ModeAC),
'2' => Some(FrameType::ModeSShort),
'3' => Some(FrameType::ModeSLong),
'4' => Some(FrameType::Config),
_ => None,
}
}
pub fn timestamp(&self) -> u64 {
match self.frame_type() {
Some(_) => {
let mut result = 0;
for i in 1..=6 {
result |= u64::from(self.data[i]) << (8 * (6 - i));
}
result
}
None => 0
}
}
pub fn signal(&self) -> u8 {
match self.frame_type() {
Some(FrameType::ModeAC) |
Some(FrameType::ModeSShort) |
Some(FrameType::ModeSLong) => {
self.data[7]
}
_ => 255
}
}
pub fn parse_adsb(&self) -> Option<adsb::Message> {
match self.frame_type() {
Some(FrameType::ModeSShort) | Some(FrameType::ModeSLong) => {
if let Ok((msg, _rest)) = adsb::parse_binary(&self.data[8..]) {
Some(msg)
} else {
eprintln!("adsb decode error");
None
}
}
_ => None
}
}
}
pub async fn connect(host: &str, port: u16) -> Result<impl futures::Stream<Item = Frame>, Box<dyn std::error::Error + Send + Sync>> {
println!("Connecting to {}:{}", host, port);
let conn = TcpStream::connect((host, port)).await?;
println!("Connected to {}:{}", host, port);
let stream = stream::unfold((conn, vec![], Deframer::new()), |(mut conn, mut buf, mut deframe)| async {
loop {
for i in 0..buf.len() {
if let Some(frame) = deframe.push(buf[i]) {
buf = buf.split_off(i + 1);
return Some((frame, (conn, buf, deframe)));
}
} // buf consumed
if buf.len() < DEFRAMER_DEFAULT_CAPACITY {
buf = vec![0; 1024];
}
match conn.read(&mut buf).await {
Ok(0) | Err(_) => {
println!("Disconnected");
return None;
}
Ok(len) =>
buf.truncate(len),
}
}
});
Ok(stream)
}

2
beast/src/lib.rs Normal file
View File

@ -0,0 +1,2 @@
pub mod beast;
pub mod aircrafts;

10
collectd-stats/Cargo.toml Normal file
View File

@ -0,0 +1,10 @@
[package]
name = "collectd-stats"
description = "ADS-B statistics in collectd"
version = "0.1.0"
edition = "2021"
[dependencies]
tokio = { version = "1", features = ["full"] }
nav-types = "0.5"
beast = { path = "../beast" }

View File

@ -0,0 +1,91 @@
use std::time::Duration;
use tokio::time::sleep;
use nav_types::WGS84;
const INTERVAL: u64 = 10;
fn update_min<T: Clone + PartialOrd>(value: &Option<T>, target: &mut Option<T>) {
match (value, target) {
(Some(value), target) if target.is_none() =>
*target = Some(value.clone()),
(Some(value), Some(target)) if value < target =>
*target = value.clone(),
_ => {}
};
}
fn update_max<T: Clone + PartialOrd>(value: &Option<T>, target: &mut Option<T>) {
match (value, target) {
(Some(value), target) if target.is_none() =>
*target = Some(value.clone()),
(Some(value), Some(target)) if value > target =>
*target = value.clone(),
_ => {}
};
}
#[tokio::main]
async fn main() {
let home = WGS84::from_degrees_and_meters(51.081, 13.728, 0.0);
let aircrafts = beast::aircrafts::Aircrafts::new();
aircrafts.connect("radiobert.serv.zentralwerk.org", 30005);
let hostname_line = std::fs::read_to_string("/proc/sys/kernel/hostname")
.unwrap();
let hostname = hostname_line.split(char::is_whitespace)
.next()
.unwrap();
loop {
sleep(Duration::from_secs(INTERVAL)).await;
let mut count = 0;
let mut min_altitude = None;
let mut max_altitude = None;
let mut min_speed = None;
let mut max_speed = None;
let mut min_distance = None;
let mut max_distance = None;
let aircrafts = aircrafts.read();
for (_, entry) in aircrafts.iter() {
let entry = entry.read().unwrap();
count += 1;
update_min(&entry.altitude, &mut min_altitude);
update_max(&entry.altitude, &mut max_altitude);
update_min(&entry.ground_speed, &mut min_speed);
update_max(&entry.ground_speed, &mut max_speed);
if let Some(pos) = entry.location() {
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;
update_min(&Some(distance_km), &mut min_distance);
update_max(&Some(distance_km), &mut max_distance);
}
}
println!("PUTVAL \"{}/beast-aircrafts/records\" interval={} N:{}", hostname, INTERVAL, count);
min_altitude.map(|min_altitude| {
println!("PUTVAL \"{}/beast-altitude/current-min\" interval={} N:{}", hostname, INTERVAL, min_altitude);
});
max_altitude.map(|max_altitude| {
println!("PUTVAL \"{}/beast-altitude/current-max\" interval={} N:{}", hostname, INTERVAL, max_altitude);
});
min_speed.map(|min_speed| {
println!("PUTVAL \"{}/beast-speed/current-min\" interval={} N:{:.3}", hostname, INTERVAL, min_speed);
});
max_speed.map(|max_speed| {
println!("PUTVAL \"{}/beast-speed/current-max\" interval={} N:{:.3}", hostname, INTERVAL, max_speed);
});
min_distance.map(|min_distance| {
println!("PUTVAL \"{}/beast-distance/current-min\" interval={} N:{:.3}", hostname, INTERVAL, min_distance);
});
max_distance.map(|max_distance| {
println!("PUTVAL \"{}/beast-distance/current-max\" interval={} N:{:.3}", hostname, INTERVAL, max_distance);
});
}
}

View File

@ -21,16 +21,26 @@
cargo = rust;
rustc = rust;
};
in rec {
# `nix build`
packages.heliwatch = naersk-lib.buildPackage {
pname = "heliwatch";
buildRustPackage = args: naersk-lib.buildPackage ({
root = ./.;
src = ./heliwatch;
nativeBuildInputs = with pkgs; [ pkg-config ];
buildInputs = with pkgs; [ openssl ];
} // args);
in rec {
# `nix build`
packages.heliwatch = buildRustPackage {
pname = "heliwatch";
};
packages.http-json = buildRustPackage {
pname = "http-json";
};
packages.collectd-stats = buildRustPackage {
pname = "collectd-stats";
};
defaultPackage = packages.heliwatch;
checks = packages;
# `nix run`
apps.heliwatch = utils.lib.mkApp {

13
http-json/Cargo.toml Normal file
View File

@ -0,0 +1,13 @@
[package]
name = "http-json"
description = "Serves http://:8080/data.json for dump1090's HTML map"
version = "0.1.0"
edition = "2021"
[dependencies]
tokio = { version = "1", features = ["full"] }
beast = { path = "../beast" }
warp = "0.3"
serde = { version = "1", features = ["derive"] }
serde_json = "1"
adsb = "0.3"

58
http-json/src/main.rs Normal file
View File

@ -0,0 +1,58 @@
use warp::{http::Response, Filter};
use adsb::ICAOAddress;
use beast::aircrafts::Entry;
use serde::Serialize;
#[derive(Serialize)]
pub struct Aircraft {
hex: String,
flight: Option<String>,
category: Option<u8>,
lat: Option<f64>,
lon: Option<f64>,
/// ft
altitude: Option<u16>,
track: Option<i16>,
/// kts
speed: Option<u16>,
}
impl Aircraft {
pub fn new(icao_address: &ICAOAddress, entry: &Entry) -> Self {
let pos = entry.location();
Aircraft {
hex: format!("{}", icao_address),
flight: entry.callsign.clone(),
category: entry.emitter_category.clone(),
lat: pos.as_ref().map(|pos| pos.latitude),
lon: pos.as_ref().map(|pos| pos.longitude),
altitude: entry.altitude.clone(),
track: entry.heading.map(|heading| heading as i16),
speed: entry.ground_speed.map(|speed| speed as u16),
}
}
}
#[tokio::main]
async fn main() {
let aircrafts = beast::aircrafts::Aircrafts::new();
aircrafts.connect("radiobert.serv.zentralwerk.org", 30005);
let hello = warp::path!("data.json")
.map(move || {
let data = aircrafts.read()
.iter()
.map(|(icao_address, entry)| Aircraft::new(icao_address, &entry.read().unwrap()))
.collect::<Vec<Aircraft>>();
let json = serde_json::ser::to_vec_pretty(&data)
.unwrap_or_else(|_| vec![]);
Response::builder()
.header("Content-Type", "application/json")
.body(json)
});
warp::serve(hello)
.run(([0, 0, 0, 0], 8080))
.await;
}