use failure::Error; use serde::{Serialize, Deserialize}; use std::collections::HashMap; use std::time::SystemTime; #[macro_use] mod adapter_macro; mod apiservice; pub mod world_geo; pub use self::apiservice::{APIService, FetchTask}; use crate::config::Config; impl APIService { pub fn api_world_geo(&self, config: &Config, callback: F) -> Option where F: FnOnce(Result) -> Message + 'static, { self.api_get(config, format_args!("/geo/world.geojson"), callback) } } pub type DateTime = chrono::DateTime; #[derive(Clone, Debug, Serialize, Deserialize)] pub struct Global { #[serde(rename = "gps-offset-ns")] pub gps_offset_ns: f64, #[serde(rename = "gps-utc-offset-ns")] pub gps_utc_offset_ns: f64, #[serde(rename = "last-seen", with = "chrono::serde::ts_seconds")] pub last_seen: DateTime, #[serde(rename = "leap-second-planned")] pub leap_second_planned: bool, #[serde(rename = "leap-seconds")] pub leap_seconds: f64, #[serde(rename = "utc-offset-ns")] pub utc_offset_ns: f64, } impl APIService { pub fn api_global(&self, config: &Config, callback: F) -> Option where F: FnOnce(Result) -> Message + 'static, { self.api_get(config, format_args!("/global.json"), callback) } } #[derive(Clone, Copy, PartialEq, Eq, Ord, PartialOrd, Hash, Debug, serde_repr::Serialize_repr, serde_repr::Deserialize_repr)] #[repr(u8)] pub enum GNS { GPS = 0, // US Galileo = 2, // EU BeiDou = 3, // CN Glonass = 6, // RU } #[derive(Clone, Debug, Serialize, Deserialize)] pub struct AlmanacEntry { // NOT in Glonass #[serde(rename = "eph-ecefX")] #[serde(default, skip_serializing_if = "Option::is_none")] pub eph_ecef_x: Option, #[serde(rename = "eph-ecefY")] #[serde(default, skip_serializing_if = "Option::is_none")] pub eph_ecef_y: Option, #[serde(rename = "eph-ecefZ")] #[serde(default, skip_serializing_if = "Option::is_none")] pub eph_ecef_z: Option, // optional in Glonass #[serde(rename = "eph-latitude")] #[serde(default, skip_serializing_if = "Option::is_none")] pub eph_latitude: Option, #[serde(rename = "eph-longitude")] #[serde(default, skip_serializing_if = "Option::is_none")] pub eph_longitude: Option, // NOT in Glonass pub t: Option, pub t0e: Option, // all pub gnssid: GNS, pub name: String, pub observed: bool, pub inclination: f64, } pub type Almanac = HashMap; impl APIService { pub fn api_almanac(&self, config: &Config, callback: F) -> Option where F: FnOnce(Result) -> Message + 'static, { self.api_get(config, format_args!("/almanac.json"), callback) } } #[derive(Clone, Debug, Serialize, Deserialize)] pub struct Observer { pub id: u32, #[serde(rename = "last-seen", with = "chrono::serde::ts_seconds")] pub last_seen: DateTime, pub latitude: f64, pub longitude: f64, #[serde(rename = "svs")] pub satellite_vehicles: HashMap, } #[derive(Clone, Debug, Serialize, Deserialize)] pub struct ObservedSatelliteVehicle { #[serde(rename = "age-s")] pub age_s: f32, // u32? pub azi: f64, pub db: u32, pub elev: f64, #[serde(rename = "fullName")] pub full_name: String, pub gnss: GNS, #[serde(rename = "last-seen")] pub last_seen: i64, pub name: String, pub prres: f64, pub sigid: u32, pub sv: u32, } pub type ObserverList = Vec; impl APIService { pub fn api_observers(&self, config: &Config, callback: F) -> Option where F: FnOnce(Result) -> Message + 'static, { self.api_get(config, format_args!("/observers.json"), callback) } } #[derive(Clone, Copy, Debug)] /// A certain point in time in some reference system, in seconds pub struct Instant { pub week_number: u16, // depending on source only might have rolled on 8-bit pub time_of_week: u32, } impl Instant { pub fn system_time(self) -> SystemTime { SystemTime::UNIX_EPOCH + std::time::Duration::from_secs(self.epoch()) } pub fn unroll_weeknumber(self) -> u32 { let current_epoch = SystemTime::now().duration_since(SystemTime::now()).expect("before 1970-01-01").as_secs(); let current_wn = (current_epoch / (86400*7)) as u32; let wn = self.week_number as u32; let wn_bits = (16 - self.week_number.leading_zeros()).min(8); // assume at least 8-bit precision in weeknumber let wn_mask = !0u32 << wn_bits; let round_up = 1 << (wn_bits - 1); // if we already reached "halftime" round up wn + ((current_wn + round_up) & wn_mask) // add bits from current_wn } pub fn epoch(self) -> u64 { self.unroll_weeknumber() as u64 * (86400*7) + self.time_of_week as u64 } } #[derive(Clone, Copy, Debug)] pub struct Vec3 { pub x: f64, pub y: f64, pub z: f64, } #[derive(Clone, Debug)] pub struct ReferenceTimeOffset { /// offset in 2**(-30) seconds at base Instant (close to nanoseconds) pub base_offset: i32, /// correction in 2**(-50) seconds per second since base Instant pub correction: i32, /// time at which constant offset was measured pub base: Instant, /// text describing delta for some "current" (last_seen) Instant in nanoseconds offset and change in nanoseconds per day. pub delta: String, } mod sv { use super::*; adapter!{ LastSeen => Instant { #[serde(rename = "wn")] week_number: u16, #[serde(rename = "tow")] time_of_week: u32, } } adapter!{ Position => Option { x: f64, y: f64, z: f64, } } adapter!{ UtcOffsetInstant => Option { #[serde(rename = "wn0t")] week_number: u16, #[serde(rename = "t0t")] time_of_week: u32, } } adapter!{ // bug https://github.com/ahupowerdns/galmon/issues/8: Glonass sends a0 and a1, but not the other values UtcOffset => Option { #[serde(rename = "a0")] base_offset: i32, #[serde(rename = "a1")] correction: i32, #[serde(flatten, with = "UtcOffsetInstant")] base: Instant, #[serde(rename = "delta-utc")] delta: String, } } adapter!{ GpsOffsetInstant => Option { #[serde(rename = "wn0g")] week_number: u16, #[serde(rename = "t0g")] time_of_week: u32, } } adapter!{ GpsOffset => Option { #[serde(rename = "a0g")] base_offset: i32, #[serde(rename = "a1g")] correction: i32, #[serde(flatten, with = "GpsOffsetInstant")] base: Instant, #[serde(rename = "delta-gps")] delta: String, } } } // TODO: "undo" flatten #[serde(default, skip_serializing_if = "Option::is_none")] #[derive(Clone, Debug, Serialize, Deserialize)] pub struct SatelliteVehicle { /* Identification: */ gnssid: GNS, svid: u32, /* vehicle id; can be moved between satellites */ sigid: u32, /* "signal", also appended as "@{sigid}" to the full name */ /* Data: */ /// Signal In Space Accuracy #[serde(default, skip_serializing_if = "Option::is_none")] sisa: Option, #[serde(rename = "eph-age-m", default, skip_serializing_if = "Option::is_none")] /// Age of ephemeris in minutes eph_age_m: Option, #[serde(rename = "best-tle", default, skip_serializing_if = "Option::is_none")] best_tle: Option, #[serde(rename = "best-tle-dist", default, skip_serializing_if = "Option::is_none")] best_tle_dist: Option, #[serde(rename = "best-tle-int-desig", default, skip_serializing_if = "Option::is_none")] best_tle_int_desig: Option, #[serde(rename = "best-tle-norad", default, skip_serializing_if = "Option::is_none")] best_tle_norad: Option, #[serde(rename = "alma-dist", default, skip_serializing_if = "Option::is_none")] alma_dist: Option, // distance from almanac position in kilometers #[serde(default, skip_serializing_if = "Option::is_none")] aode: Option, #[serde(default, skip_serializing_if = "Option::is_none")] aodc: Option, #[serde(default, skip_serializing_if = "Option::is_none")] iod: Option, // IOD data: #[serde(default, skip_serializing_if = "Option::is_none")] af0: Option, #[serde(default, skip_serializing_if = "Option::is_none")] af1: Option, #[serde(default, skip_serializing_if = "Option::is_none")] af2: Option, #[serde(default, skip_serializing_if = "Option::is_none")] t0c: Option, // clock epoch #[serde(flatten, with = "sv::Position")] position: Option, // utc offset (all but Glonass): combined data #[serde(flatten, with = "sv::UtcOffset")] utc_offset: Option, // GPS offset (only Galileo and BeiDou) #[serde(flatten, with = "sv::GpsOffset")] gpc_offset: Option, #[serde(rename = "dtLS")] dt_ls: i8, #[serde(default, skip_serializing_if = "Option::is_none")] health: Option, #[serde(default, skip_serializing_if = "Option::is_none")] healthissue: Option, // some codes? // Galileo only: Health flags for E1 (common) and E5 (uncommon) frequencies. #[serde(default, skip_serializing_if = "Option::is_none")] e1bhs: Option, #[serde(default, skip_serializing_if = "Option::is_none")] e1bdvs: Option, #[serde(default, skip_serializing_if = "Option::is_none")] e5bhs: Option, #[serde(default, skip_serializing_if = "Option::is_none")] e5bdvs: Option, #[serde(rename = "latest-disco", default, skip_serializing_if = "Option::is_none")] latest_disco: Option, #[serde(rename = "latest-disco-age", default, skip_serializing_if = "Option::is_none")] latest_disco_age: Option, #[serde(rename = "time-disco", default, skip_serializing_if = "Option::is_none")] time_disco: Option, #[serde(flatten, with = "sv::LastSeen")] last_seen: Instant, /// Number of seconds since we've last received from this SV. A satellite can be out of sight for a long time #[serde(rename = "last-seen-s")] last_seen_s: i64, #[serde(rename = "fullName")] full_name: String, // format!("{}@{}", self.name, self.sigid) name: String, perrecv: HashMap, // keys: Observer `id` } #[derive(Clone, Debug, Serialize, Deserialize)] pub struct SatelliteVehiclePerReceiver { #[serde(rename = "elev")] elevation: f64, #[serde(rename = "azi", default, skip_serializing_if = "Option::is_none")] azimuth: Option, db: i32, #[serde(rename = "last-seen-s")] last_seen_s: i64, prres: f64, delta_hz: Option, delta_hz_corr: Option, } pub type SatelliteVehicles = HashMap; impl APIService { pub fn api_satellite_vehicles(&self, config: &Config, callback: F) -> Option where F: FnOnce(Result) -> Message + 'static, { self.api_get(config, format_args!("/svs.json"), callback) } }