diff options
Diffstat (limited to '')
-rw-r--r-- | src/onboard/iceportal.rs (renamed from src/iceportal.rs) | 41 | ||||
-rw-r--r-- | src/onboard/mod.rs | 76 | ||||
-rw-r--r-- | src/onboard/zugportal.rs | 109 |
3 files changed, 219 insertions, 7 deletions
diff --git a/src/iceportal.rs b/src/onboard/iceportal.rs index 3cb5e6f..04ec291 100644 --- a/src/iceportal.rs +++ b/src/onboard/iceportal.rs @@ -1,8 +1,14 @@ +/// implementation of traits to query the iceportal.de +/// (available in high speed trains in DE) use chrono::{DateTime, Utc}; use serde::Deserialize; use serde_json::Value; -use crate::{serde::*, travelynx::TrainRef, types::IsStation}; +use crate::onboard; +use crate::onboard::{OnBoardAPI, OnBoardInfo}; +use crate::{serde::*, traits::*, travelynx::TrainRef}; + +pub struct Iceportal {} #[derive(Deserialize, Debug)] #[serde(rename_all = "camelCase")] @@ -72,8 +78,8 @@ impl IsStation for Stop { } } -impl TripInfo { - pub fn guess_last_station(&self) -> Option<String> { +impl OnBoardInfo for TripInfo { + fn guess_last_station(&self) -> Option<&dyn IsStation> { let current_pos = self.trip.actual_position; self .trip @@ -83,17 +89,38 @@ impl TripInfo { .map(|stop| (stop.info.distance_from_start, stop)) .filter(|(dist, _)| dist <= ¤t_pos) .next() - .map(|(_, stop)| stop.station.name.clone()) + .map(|(_, stop)| stop as &dyn IsStation) } - pub fn get_train_ref(&self) -> TrainRef { + fn get_train_ref(&self) -> TrainRef { TrainRef { _type: self.trip.train_type.clone(), no: self.trip.vzn.clone() } } - pub fn trip(&self) -> crate::types::Trip<'_, Stop> { - crate::types::Trip(&self.trip.stops) + fn stops<'a>( + &'a self + ) -> Box<dyn std::iter::Iterator<Item = &'a dyn IsStation> + 'a> { + Box::new(self.trip.stops.iter().map(|s| s as &dyn IsStation)) + } +} + +impl std::fmt::Display for Stop { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self.station.name) + } +} + +impl OnBoardAPI for Iceportal { + fn apiurl(&self) -> &'static str { + "https://iceportal.de/api1/rs/tripInfo/trip" + } + + fn request( + &self, + debug: bool + ) -> Result<Box<dyn OnBoardInfo>, serde_json::Error> { + onboard::request::<_, TripInfo>(self, debug) } } diff --git a/src/onboard/mod.rs b/src/onboard/mod.rs new file mode 100644 index 0000000..56bd144 --- /dev/null +++ b/src/onboard/mod.rs @@ -0,0 +1,76 @@ +use crate::traits::IsStation; +use crate::travelynx::TrainRef; +use serde::de::DeserializeOwned; + +pub mod iceportal; +pub mod zugportal; + +pub fn choose_api(name: &str) -> &dyn OnBoardAPI { + match name { + "iceportal" => &iceportal::Iceportal {} as &dyn OnBoardAPI, + "zugportal" => &zugportal::Zugportal {} as &dyn OnBoardAPI, + _ => panic!("no such API known") + } +} + +pub trait OnBoardAPI { + fn apiurl(&self) -> &'static str; + + fn request( + &self, + debug: bool + ) -> Result<Box<dyn OnBoardInfo>, serde_json::Error>; +} + +pub fn request<Api, I>( + api: &Api, + debug: bool +) -> Result<Box<dyn OnBoardInfo>, serde_json::Error> +where + Api: OnBoardAPI, + I: OnBoardInfo + DeserializeOwned + 'static +{ + let url: &'static str = api.apiurl(); + match get_request::<I>(url) { + Ok(resp) => Ok(Box::new(resp)), + Err((err, resp)) => { + if debug { + eprintln!("{:?}\n\nError was:{:?}", resp, err); + } + Err(err) + } + } +} + +pub trait OnBoardInfo { + fn guess_last_station(&self) -> Option<&dyn IsStation>; + + fn get_train_ref(&self) -> TrainRef; + + fn stops<'a>( + &'a self + ) -> Box<dyn std::iter::Iterator<Item = &'a dyn IsStation> + 'a>; +} + +fn get_request<R>(uri: &str) -> Result<R, (serde_json::Error, String)> +where + R: serde::de::DeserializeOwned +{ + let resp: String = ureq::get(uri) + .call() + .unwrap_or_else(|err| exit_err(&err.to_string(), "get request failed")) + .into_string() + .unwrap_or_else(|err| { + exit_err(&err.to_string(), "get request response failed") + }); + + match serde_json::from_str::<R>(&resp) { + Ok(obj) => Ok(obj), + Err(err) => Err((err, resp)) + } +} + +fn exit_err(msg: &str, scope: &str) -> ! { + eprintln!("{}: {}", scope, msg); + std::process::exit(1) +} diff --git a/src/onboard/zugportal.rs b/src/onboard/zugportal.rs new file mode 100644 index 0000000..0ad6cfd --- /dev/null +++ b/src/onboard/zugportal.rs @@ -0,0 +1,109 @@ +/// implementation of traits to query zugportal.de +/// (available at least in the Munich S-Bahn, maybe other trains) +use chrono::{DateTime, Utc}; +use serde::Deserialize; + +use crate::onboard; +use crate::onboard::{OnBoardAPI, OnBoardInfo}; +use crate::{traits::*, travelynx::TrainRef}; + +pub struct Zugportal {} + +#[derive(Deserialize, Debug)] +#[serde(rename_all = "camelCase")] +pub struct Journey { + name: String, // the line's name, e.g. S 8 + no: i64, + stops: Vec<Stop> +} + +#[derive(Deserialize, Debug)] +#[serde(rename_all = "camelCase")] +pub struct Stop { + station: Station, + status: String, // one of "Normal", ...? + track: Track, + messages: Vec<String>, + arrival_time: Option<DepartureTime>, + departure_time: Option<DepartureTime> +} + +#[derive(Deserialize, Debug)] +#[serde(rename_all = "camelCase")] +struct Station { + eva_no: String, + name: String +} + +#[derive(Deserialize, Debug)] +#[serde(rename_all = "camelCase")] +struct Track { + target: String, + prediction: String +} + +#[derive(Deserialize, Debug)] +#[serde(rename_all = "camelCase")] +struct DepartureTime { + target: DateTime<Utc>, + predicted: DateTime<Utc>, + time_type: String, // one of REAL, PREVIEW, ..? + diff: i64 // diff in minutes? + // NOTE: also sends predictedTimeInMs and targetTimeInMs; these might be unix times +} + +impl IsStation for Stop { + fn name(&self) -> &str { + &self.station.name + } + + fn scheduled_arrival(&self) -> Option<&chrono::DateTime<Utc>> { + self.arrival_time.as_ref().map(|t| &t.target) + } + + fn real_arrival(&self) -> Option<&chrono::DateTime<Utc>> { + self.arrival_time.as_ref().map(|t| &t.predicted) + } + + fn ds100(&self) -> &str { + "??" + } +} + +impl std::fmt::Display for Stop { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self.station.name) + } +} + +impl OnBoardInfo for Journey { + fn guess_last_station(&self) -> Option<&dyn IsStation> { + todo!() + } + + fn get_train_ref(&self) -> TrainRef { + TrainRef { + _type: self.name.clone(), + no: self.no.to_string().clone() + } + } + + fn stops<'a>( + &'a self + ) -> Box<dyn std::iter::Iterator<Item = &'a dyn IsStation> + 'a> { + Box::new(self.stops.iter().map(|s| s as &dyn IsStation)) + } +} + +impl OnBoardAPI for Zugportal { + fn apiurl(&self) -> &'static str { + "https://zugportal.de/prd/zupo-travel-information/api/public/ri/journey" + } + + fn request( + &self, + debug: bool + ) -> Result<Box<dyn OnBoardInfo>, serde_json::Error> { + onboard::request::<_, Journey>(self, debug) + } +} |