summaryrefslogtreecommitdiff
path: root/src/onboard
diff options
context:
space:
mode:
Diffstat (limited to 'src/onboard')
-rw-r--r--src/onboard/iceportal.rs126
-rw-r--r--src/onboard/mod.rs76
-rw-r--r--src/onboard/zugportal.rs109
3 files changed, 311 insertions, 0 deletions
diff --git a/src/onboard/iceportal.rs b/src/onboard/iceportal.rs
new file mode 100644
index 0000000..04ec291
--- /dev/null
+++ b/src/onboard/iceportal.rs
@@ -0,0 +1,126 @@
+/// 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::onboard;
+use crate::onboard::{OnBoardAPI, OnBoardInfo};
+use crate::{serde::*, traits::*, travelynx::TrainRef};
+
+pub struct Iceportal {}
+
+#[derive(Deserialize, Debug)]
+#[serde(rename_all = "camelCase")]
+pub struct TripInfo {
+ trip: Trip,
+ connection: Option<Value>,
+ selected_route: Option<Value>,
+ active: Option<Value>
+}
+
+#[derive(Deserialize, Debug)]
+#[serde(rename_all = "camelCase")]
+struct Trip {
+ train_type: String,
+ vzn: String, // train number
+ // some position info here
+ actual_position: u64, // distance along track, presumably
+ stops: Vec<Stop>
+}
+
+#[derive(Deserialize, Debug)]
+#[serde(rename_all = "camelCase")]
+pub struct Stop {
+ info: StopInfo,
+ station: Station,
+ timetable: Timetable
+}
+
+#[derive(Deserialize, Debug)]
+#[serde(rename_all = "camelCase")]
+struct StopInfo {
+ distance_from_start: u64,
+ position_status: String // one of "departed", "future", ... ?
+}
+
+#[derive(Deserialize, Debug)]
+#[serde(rename_all = "camelCase")]
+struct Station {
+ eva_nr: String,
+ name: String
+}
+
+#[derive(Deserialize, Debug)]
+#[serde(rename_all = "camelCase")]
+struct Timetable {
+ #[serde(deserialize_with = "option_naive_read_unixtime_db")]
+ scheduled_arrival_time: Option<DateTime<Utc>>,
+ #[serde(deserialize_with = "option_naive_read_unixtime_db")]
+ actual_arrival_time: Option<DateTime<Utc>>
+}
+
+impl IsStation for Stop {
+ fn name(&self) -> &str {
+ &self.station.name
+ }
+
+ fn scheduled_arrival(&self) -> Option<&chrono::DateTime<Utc>> {
+ self.timetable.scheduled_arrival_time.as_ref()
+ }
+
+ fn real_arrival(&self) -> Option<&chrono::DateTime<Utc>> {
+ self.timetable.scheduled_arrival_time.as_ref()
+ }
+
+ fn ds100(&self) -> &str {
+ "??"
+ }
+}
+
+impl OnBoardInfo for TripInfo {
+ fn guess_last_station(&self) -> Option<&dyn IsStation> {
+ let current_pos = self.trip.actual_position;
+ self
+ .trip
+ .stops
+ .iter()
+ .rev()
+ .map(|stop| (stop.info.distance_from_start, stop))
+ .filter(|(dist, _)| dist <= &current_pos)
+ .next()
+ .map(|(_, stop)| stop as &dyn IsStation)
+ }
+
+ fn get_train_ref(&self) -> TrainRef {
+ TrainRef {
+ _type: self.trip.train_type.clone(),
+ no: self.trip.vzn.clone()
+ }
+ }
+
+ 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)
+ }
+}