diff --git a/iceportal_apis/__init__.py b/iceportal_apis/__init__.py index a33ee27..b6da293 100644 --- a/iceportal_apis/__init__.py +++ b/iceportal_apis/__init__.py @@ -1,33 +1,33 @@ #!/usr/bin/env python """ Note that this module only works on trains from the Deutsche Bahn -Note that this module only works while connected to the on board network "WIFI@DB" or "WIFIonICE" +Note that this module only works while connected to the on-board network "WIFI@DB" or "WIFIonICE" """ from typing import Union, Any, List, Tuple, Dict from datetime import datetime, timedelta -from .interfaces import (ApiInterface, TestInterface, MediaInterface) -from .mocking import (StaticSimulation, DynamicSimulation) +from onboardapis.utils.conversions import ms_to_kmh +from onboardapis import Vehicle +from onboardapis.trains import Station as OAStation, ConnectingTrain +from onboardapis.trains.germany.db import ICEPortal + from .types import (TrainType, WagonClass, InterfaceStatus, Internet) from .exceptions import (ApiException, NetworkException, NotInFutureException, NotAvailableException, NotOnTrainException, NoneDataException, MissingArgumentError) +import warnings +warnings.warn( + "This package is no longer maintained in favor of the 'onboardapis' package https://pypi.org/projects/onboardapis.", + DeprecationWarning +) + ###################################### __author__ = 'Felix Zenk' __email__ = 'felix.zenk@web.de' -__version__ = '1.1.2a1' +__version__ = '2.0.0' ###################################### -def _data_available(param: str) -> bool: - """ - Tests whether data is available or not - :param param: The data to test - :return: Whether it's available or not - """ - return not (param is None or param == "") - - def _ensure_not_none(param: Any) -> Any: """ Tests if an object is not None then returns it or raises an exception @@ -37,23 +37,12 @@ def _ensure_not_none(param: Any) -> Any: :rtype: Any :raises: NotAvailableException """ - if _data_available(param): + if param not in {None, ""}: return param else: raise NotAvailableException() -def _timestamp_to_datetime(timestamp: Union[str, int, float]) -> datetime: - """ - Converts an int-timestamp with millisecond precision - :param timestamp: A timestamp that describes elapsed milliseconds - :type timestamp: Union[str, int, float] - :return: The datetime object - :rtype: datetime - """ - return datetime.fromtimestamp(float(timestamp) / 1000.0) - - def _convert_to_internet_status(value: str) -> Internet: """ Converts param into an Internet-Status enum @@ -65,10 +54,13 @@ def _convert_to_internet_status(value: str) -> Internet: return Internet.__members__[value.upper()] if value.upper() in Internet.__members__.keys() else Internet.UNKNOWN -class Station: +class Station(OAStation): def __init__(self, eva_number: str, name: str) -> None: - self.eva_number: str = eva_number - self.name: str = name + super(Station, self).__init__(eva_number, name) + + @property + def eva_number(self): + return self.id def __str__(self) -> str: return self.name @@ -77,158 +69,129 @@ def __repr__(self) -> str: return str(self) -class Train: +class Train(Vehicle): def __init__(self, auto_refresh: bool = False, test_mode: bool = False, dynamic_simulation: bool = False) -> None: - self._raw_data = TestInterface(DynamicSimulation if dynamic_simulation else StaticSimulation) \ - if test_mode else ApiInterface() - self._raw_data.set_auto_refresh(auto_refresh=auto_refresh) - self.media = None if test_mode else MediaInterface() - - def __del__(self) -> None: - # Optional, but a clean exit - try: - self._raw_data.set_auto_refresh(False) - if isinstance(self._raw_data, TestInterface): - self._raw_data.simulation.stop() - except AttributeError: - pass + if test_mode or dynamic_simulation: + raise NotImplementedError("Test mode and dynamic simulation are not supported any more.") + super().__init__() + self.__oa = ICEPortal() def __str__(self) -> str: return "<"+self.get_train_type().name+" "+self.get_trip_id()+" -> "+self.get_final_station().name+">" - def __repr__(self) -> str: - return str(self) - - def refresh(self) -> None: - """ - Refreshes all data - """ - self._raw_data.refresh() - - def _station_from_eva_nr(self, eva_nr) -> Station: - """ - Creates a new Station instance from an eva number - :param eva_nr: The ave number of the station - :type eva_nr: str - :return: A new Station instance - :rtype: Station - """ - return Station(eva_number=eva_nr, name=_ensure_not_none(self._raw_data.eva_nr_2_name[eva_nr])) - - def get_speed(self) -> Union[int, str]: + def get_speed(self) -> float: """ Gets the current speed of the train in kilometers/hour. """ - try: - return int(_ensure_not_none(self._raw_data.status['speed'])) - except: - return _ensure_not_none(self._raw_data.status['speed']) + return ms_to_kmh(self.__oa.speed) def get_train_type(self) -> TrainType: """ Gets the train type. """ - return TrainType.__members__[self._raw_data.status['trainType'].upper()] \ - if self._raw_data.status['trainType'].upper() in TrainType.__members__.keys() \ + return TrainType.__members__[self.__oa.type] \ + if self.__oa.type in TrainType.__members__.keys() \ else TrainType.UNKNOWN def get_wagon_class(self) -> WagonClass: """ Gets the wagon class (can be inaccurate for wagons next to another class). """ - return WagonClass.__members__[self._raw_data.status['wagonClass'].upper()] \ - if self._raw_data.status['wagonClass'].upper() in WagonClass.__members__.keys() \ + return WagonClass.__members__[self.__oa.wagon_class()] \ + if self.__oa.wagon_class() in WagonClass.__members__.keys() \ else WagonClass.UNKNOWN def get_internet_status(self) -> Internet: """ Gets the current internet status / (speed) """ - return _convert_to_internet_status(_ensure_not_none(self._raw_data.status['connectivity']['currentState'])) + return _convert_to_internet_status(_ensure_not_none(self.__oa.internet_connection()[0])) def get_next_internet_status(self) -> Internet: """ Gets the next internet status / (speed) """ - return _convert_to_internet_status(_ensure_not_none(self._raw_data.status['connectivity']['nextState'])) + return _convert_to_internet_status(_ensure_not_none(self.__oa.internet_connection()[1])) def get_time_until_internet_change(self) -> timedelta: """ Gets the time until the network status changes """ - return timedelta(seconds=int(_ensure_not_none(self._raw_data.status['connectivity']['remainingTimeSeconds']))) + return timedelta(seconds=int(_ensure_not_none(self.__oa.internet_connection()[2]))) def has_bap_service(self) -> bool: """ Whether this train offers ordering food from the passengers seat :return: Whether this train provides bap service or not """ - return _ensure_not_none(self._raw_data.bap["bapServiceStatus"]).upper() == "ACTIVE" + return self.__oa.has_bap() def get_latitude(self) -> float: """ Gets the current latitude of the train's position in decimal format. """ - return _ensure_not_none(self._raw_data.status['latitude']) + return self.__oa.position[0] def get_longitude(self) -> float: """ Gets the current longitude of the train's position in decimal format. """ - return _ensure_not_none(self._raw_data.status['longitude']) + return self.__oa.position[1] def get_position(self) -> Tuple[float, float]: """ Gets the current position of the train in decimal format. """ - return self.get_longitude(), self.get_latitude() + return self.__oa.position def get_train_id(self) -> str: """ Gets the unique ID of the train """ - return _ensure_not_none(self._raw_data.status['tzn']) + return self.__oa.id def get_trip_id(self) -> str: """ Gets the ID of the trip """ - return _ensure_not_none(self._raw_data.trip['trip']['vzn']) + return self.__oa.number def get_next_station(self) -> Station: """ Gets the next station """ - return self._station_from_eva_nr(_ensure_not_none(self._raw_data.trip['trip']['stopInfo']['actualNext'])) + return Station(self.__oa.current_station.id, self.__oa.current_station.name) def get_last_station(self) -> Station: """ Gets the last station. """ - return self._station_from_eva_nr(_ensure_not_none(self._raw_data.trip['trip']['stopInfo']['actualLast'])) + idx_last_station = list(self.__oa.stations.values()).index(self.__oa.current_station) - 1 + if idx_last_station < 0: + idx_last_station = 0 + id_last_station = list(self.__oa.stations.keys())[idx_last_station] + station = self.__oa.stations.get(id_last_station) + return Station(station.id, station.name) def get_final_station(self) -> Station: """ Gets the destination of the train """ - return self._station_from_eva_nr(_ensure_not_none(self._raw_data.trip['trip']['stopInfo']['finalStationEvaNr'])) + return Station(self.__oa.destination.id, self.__oa.destination.name) def get_all_stations(self) -> List[Station]: """ Gets all stations for this trip. """ - return list([self._station_from_eva_nr(eva_nr=eva_nr) - for eva_nr in _ensure_not_none(self._raw_data.eva_nr_2_name.keys())]) + return list([ + Station(station.id, station.name) for station in self.__oa.stations.values() + ]) def get_arrival_time(self, station: Station) -> datetime: """ Gets the arrival time at a specific station. """ - return _timestamp_to_datetime( - _ensure_not_none(self._raw_data.stations[station.eva_number]['timetable']['actualArrivalTime'] - if _data_available( - self._raw_data.stations[station.eva_number]['timetable']['actualArrivalTime']) - else self._raw_data.stations[station.eva_number]['timetable']['scheduledArrivalTime'])) + return self.__oa.stations.get(station.eva_number).arrival.actual def get_time_until_arrival(self, station: Station) -> timedelta: """Gets the time until the arrival at a specific station. @@ -240,12 +203,7 @@ def get_departure_time(self, station: Station) -> datetime: """ Gets the departure time at a specific station. """ - return _timestamp_to_datetime( - _ensure_not_none(self._raw_data.stations[station.eva_number]['timetable']['actualDepartureTime'] - if _data_available( - self._raw_data.stations[station.eva_number]['timetable']['actualDepartureTime']) - else self._raw_data.stations[station.eva_number]['timetable']['scheduledDepartureTime'] - )) + return self.__oa.stations.get(station.eva_number).departure.actual def get_time_until_departure(self, station: Station) -> timedelta: """ @@ -258,36 +216,34 @@ def get_platform(self, station: Station) -> str: """ Gets the trains arrival platform for a specific station """ - return _ensure_not_none(self._raw_data.stations[station.eva_number]['track']['actual'] - if _data_available( - self._raw_data.stations[station.eva_number]['track']['actual']) - else self._raw_data.stations[station.eva_number]['track']['scheduled']) + return self.__oa.stations.get(station.eva_number).platform.actual def get_delay_at(self, station: Station) -> timedelta: """ Gets the delay at a station """ - delay = _ensure_not_none(self._raw_data.stations[station.eva_number]['timetable']['arrivalDelay']) - return timedelta() if delay == "" else timedelta(minutes=int(delay)) + return ( + self.__oa.stations.get(station.eva_number).arrival.actual + - self.__oa.stations.get(station.eva_number).arrival.scheduled + ) def get_current_delay(self) -> timedelta: """ Gets the current delay """ - return self.get_delay_at(self.get_next_station()) + return timedelta(seconds=self.__oa.delay) def is_delayed(self) -> bool: """ - Whether or not the train is delayed + Whether the train is delayed """ - return self.get_current_delay() > timedelta() + return self.get_current_delay() > timedelta(seconds=0) def get_delay_reasons_at(self, station: Station) -> List[str]: """ Gets the delay reasons for a specific station """ - return list([reason['text'] for reason in self._raw_data.stations[station.eva_number]['delayReasons']]) \ - if self._raw_data.stations[station.eva_number]['delayReasons'] else [] + return self.__oa.all_delay_reasons().get(station.eva_number, []) def get_current_delay_reasons(self) -> List[str]: """ @@ -299,43 +255,51 @@ def get_station_position(self, station: Station) -> Tuple[float, float]: """ Gets the position of a specific station """ - return _ensure_not_none(self._raw_data.stations[station.eva_number]['station']['geocoordinates']['latitude']), \ - _ensure_not_none(self._raw_data.stations[station.eva_number]['station']['geocoordinates']['longitude']) + return self.__oa.stations.get(station.eva_number).position - def get_station_distance(self, station: Station) -> int: + def get_station_distance(self, station: Station) -> float: """ Calculates the distance to a specific station and returns it in meters """ - return int(_ensure_not_none(self._raw_data.stations[station.eva_number]['info']['distanceFromStart'])) \ - - int(_ensure_not_none(self._raw_data.trip['trip']['actualPosition'])) \ - - int(_ensure_not_none(self._raw_data.trip['trip']['distanceFromLastStop'])) + return self.__oa.calculate_distance(self.__oa.stations.get(station.eva_number)) - def get_connections(self, station: Station) -> List: - """Returns the connecting trains from a specific station + def get_connections(self, station: Station) -> List[ConnectingTrain]: + """ + Returns the connecting trains from a specific station """ - return _ensure_not_none(self._raw_data.connections[station.eva_number]['connections']) + return self.__oa.stations.get(station.eva_number).connections - def get_all_connections(self) -> Dict[str, List]: + def get_all_connections(self) -> Dict[str, List[ConnectingTrain]]: """ Gets all connections for every available station (usually every station except for the first one) """ - return _ensure_not_none(self._raw_data.connections) + return { + station_id: self.__oa.stations.get(station.id).connections + for station_id, station in self.__oa.stations.items() + } def get_connections_info(self, station) -> List[Dict[str, Union[str, Station, datetime]]]: """ Processes information for connecting trains from station and returns some useful details:\n {trainName, finalStation, departure, track}\n - example: [{'trainName': 'S 5', 'finalStation': Station(8000148_00, Hameln), + example: [{'trainName': 'S 5', 'finalStation': Hameln, 'departure': datetime.datetime(2020, 12, 26, 15, 25), 'track': '1'}, ...] """ - return list([{ - 'trainName': connection['trainType'] + ' ' + connection['vzn'], - 'finalStation': Station(connection['station']['evaNr'], connection['station']['name']), - 'departure': _timestamp_to_datetime(connection['timetable']['actualDepartureTime'] - if not connection['timetable']['actualDepartureTime'] is None - else connection['timetable']['scheduledDepartureTime']), - 'track': (connection['track']['actual'] - if not connection['track']['actual'] is None - else connection['timetable']['scheduledDepartureTime']) - } for connection in self.get_connections(station)]) + return list([ + { + 'trainName': f"{connection.train_type} {connection.line_number}", + 'finalStation': connection.destination, + 'departure': ( + connection.departure.actual + if connection.departure.actual is not None + else connection.departure.scheduled + ), + 'track': ( + connection.platform.actual + if connection.platform.actual is not None + else connection.platform.scheduled + ) + } + for connection in self.get_connections(station) + ]) diff --git a/iceportal_apis/deprecated.py b/iceportal_apis/deprecated.py deleted file mode 100644 index b1969de..0000000 --- a/iceportal_apis/deprecated.py +++ /dev/null @@ -1,624 +0,0 @@ -#!/usr/bin/env python -""" -Note that this module only works on trains from the Deutsche Bahn -Note that this module only works while connected to the on board network "WIFI@DB" or "WIFIonICE" -""" -import warnings - -from requests import get -from datetime import datetime, timedelta -from iceportal_apis.exceptions import * - -################################################ -__author__ = 'Felix Zenk <..@..>' -__version__ = '1.0.8' -################################################ -warnings.warn("This part of the module is deprecated! Use the new functions from the main module.", DeprecationWarning) -################################################ -# Exceptions moved -################################################ - - -def convert_time_to_string(timedelta_obj: timedelta, locale: str = "en", no_seconds: bool = False) -> str: - """ - Converts a timedelta object into a readable string. - timedelta: timedelta object - locale: The language code for the returned string - supported: ('', 'de', 'en', 'fr', 'nl'), standard: 'en' - """ - if locale.lower() == "de": - strings_one = ["Tag", "Stunde", "Minute", "Sekunde"] - strings_many = ["Tage", "Stunden", "Minuten", "Sekunden"] - elif locale.lower() == "en": - strings_one = ["day", "hour", "minute", "second"] - strings_many = ["days", "hours", "minutes", "seconds"] - elif locale.lower() == "fr": - strings_one = ["jour", "heure", "minute", "seconde"] - strings_many = ["jours", "heures", "minutes", "secondes"] - elif locale.lower() == "nl": - strings_one = ["dag", "uur", "minuut", "seconde"] - strings_many = ["dagen", "uur", "minuten", "seconden"] - else: - strings_one = ["day", "hour", "minute", "second"] - strings_many = ["days", "hours", "minutes", "seconds"] - - string_time = '' - hours = int(timedelta_obj.seconds / 3600) - minutes = int(timedelta_obj.seconds / 60) - hours * 60 - seconds = timedelta_obj.seconds % 60 - - if not timedelta_obj.days == 0: - if timedelta_obj.days == 1: - string_time += f' {timedelta_obj.days} {strings_one[0]}' - else: - string_time += f' {timedelta_obj.days} {strings_many[0]}' - if not hours == 0: - if hours == 1: - string_time += f' {hours} {strings_one[1]}' - else: - string_time += f' {hours} {strings_many[1]}' - if not minutes == 0: - if minutes == 1: - string_time += f' {minutes} {strings_one[2]}' - else: - string_time += f' {minutes} {strings_many[2]}' - if not seconds == 0 and not no_seconds: - if seconds == 1: - string_time += f' {seconds} {strings_one[3]}' - else: - string_time += f' {seconds} {strings_many[3]}' - return string_time.strip() - - -def cut_timestamp(seconds: int) -> int: - """Cuts the values passed by the api to fit the timestamp format - """ - return int(str(seconds)[:10]) - - -def calc_distance(position_start: tuple, position_end: tuple) -> float: - """Calculates the distance between two positions in format (lat, lon) - """ - from math import pi, sin, cos, sqrt, atan, radians - f = 1 / 298.257223563 - a = 6378173 - F = radians((position_start[0] + position_end[0]) / 2.0) - G = radians((position_start[0] - position_end[0]) / 2.0) - l = radians((position_start[1] - position_end[1]) / 2.0) - S = sin(G) ** 2 * cos(l) ** 2 + cos(F) ** 2 * sin(l) ** 2 - C = cos(G) ** 2 * cos(l) ** 2 + sin(F) ** 2 * sin(l) ** 2 - w = atan(sqrt(S / C)) - if float(w) == 0.0: - return 0.0 - D = 2 * w * a - T = sqrt(S * C) / w - H_1 = (3 * T - 1) / (2 * C) - H_2 = (3 * T + 1) / (2 * S) - return D * (1 + f * H_1 * sin(F) ** 2 * cos(G) ** 2 - f * H_2 * cos(F) ** 2 * sin(G) ** 2) - - -def autoupdate(): - """Update the module if outdated - """ - from subprocess import check_output - from sys import executable - from os import system - for row in check_output([executable, "-m", "pip", "list", "--outdated"]).decode('utf-8').split('wheel\r\n')[2:-1]: - module_info = [] - for p in row.split(' '): - if not ' ' == p and not '' == p: - module_info.append(p) - if 'iceportal-apis' == module_info[0]: - system(executable + ' -m pip install --upgrade iceportal-apis') - - -################################################ - -def request_json(url): - """Requests data from 'url' and parses it as a dictionary - """ - try: - data = get(url) - except: - raise NetworkException(url) - try: - return data.json() - except: - raise NotOnTrainException() - - -def get_status(): - """Returns the status data. - """ - return request_json("https://iceportal.de/api1/rs/status") - - -def get_trip(): - """Returns the trip data. - """ - return request_json("https://iceportal.de/api1/rs/tripInfo/trip") - - -def get_all(): - """Pulls data from the api and returns it as dictionaries. - """ - return get_status(), get_trip() - - -def get_connecting_trains(evaNr: str) -> dict: - """Returns the connecting trains from the station - """ - return request_json("https://iceportal.de/api1/rs/tripInfo/connection/" + evaNr) - - -################################################ - -def get_speed(status_call=None): - """Gets the current speed of the train in kilometers/hour. - """ - if status_call is not None: - return status_call['speed'] - return get_status()['speed'] - - -def get_train_type(status_call=None, trip_call=None): - """Gets the train type. - """ - if status_call is not None: - return status_call['trainType'] - if trip_call is not None: - return trip_call['trip']['trainType'] - return get_status()['trainType'] - - -def get_wagon_class(status_call=None): - """Gets the wagon class (can be inaccurate for wagons next to another class). - """ - if status_call is not None: - return status_call['wagonClass'] - return get_status()['wagonClass'] - - -def get_latitude(status_call=None): - """Gets the current latitude of the train's position in decimal format. - """ - if status_call is not None: - return status_call['latitude'] - return get_status()['latitude'] - - -def get_longitude(status_call=None): - """Gets the current longitude of the train's position in decimal format. - """ - if status_call is not None: - return status_call['longitude'] - return get_status()['longitude'] - - -def get_position(status_call=None): - """Gets the current position of the train in decimal format. - """ - if status_call is not None: - return get_latitude(status_call), get_longitude(status_call) - return get_latitude(), get_longitude() - - -def get_train_id(status_call=None): - """Gets the ID of the train - """ - if status_call is not None: - return status_call['tzn'] - return get_status()['tzn'] - - -def get_trip_id(trip_call=None): - """Gets the ID of the trip - """ - if trip_call is not None: - return trip_call['trip']['vzn'] - return get_trip()['trip']['vzn'] - - -def get_station_eva_number(station_name, trip_call=None): - """Gets the evaNr of a specific station. - """ - if trip_call is None: - trip_call = get_trip() - for stop in trip_call['trip']['stops']: - if stop['station']['name'] == station_name: - return stop['station']['evaNr'] - raise NotAvailableException() - - -def get_next_station_eva_number(trip_call=None): - """Gets the evaNr of the next stop. - """ - if trip_call is None: - trip_call = get_trip() - return trip_call['trip']['stopInfo']['actualNext'] - - -def get_last_station_eva_number(trip_call=None): - """Gets the evaNr of a specific station. - """ - if trip_call is None: - trip_call = get_trip() - return trip_call['trip']['stopInfo']['actualLast'] - - -def get_final_station_eva_number(trip_call=None): - """Gets the evaNr of the destination of the train - """ - if trip_call is None: - trip_call = get_trip() - return trip_call['trip']['stopInfo']['finalStationEvaNr'] - - -def get_all_station_eva_numbers(trip_call=None): - """Gets the evaNr of all stations for this trip. - """ - numbers = [] - if trip_call is None: - trip_call = get_trip() - for stop in trip_call['trip']['stops']: - numbers.append(stop['station']['evaNr']) - if len(numbers) != 0: - return numbers - else: - raise NotAvailableException() - - -def get_station_name(evaNr, trip_call=None): - """Gets the name of a specific station. - """ - if trip_call is None: - trip_call = get_trip() - for stop in trip_call['trip']['stops']: - if stop['station']['evaNr'] == evaNr: - return stop['station']['name'] - raise NotAvailableException() - - -def get_next_station_name(trip_call=None): - """Gets the name of the next stop. - """ - if trip_call is None: - trip_call = get_trip() - for stop in trip_call['trip']['stops']: - if stop['station']['evaNr'] == trip_call['trip']['stopInfo']['actualNext']: - return stop['station']['name'] - raise NotAvailableException() - - -def get_last_station_name(trip_call=None): - """Gets the name of the last station - """ - if trip_call is None: - trip_call = get_trip() - next_eva_nr = get_next_station_eva_number(trip_call=trip_call) - for stop in trip_call['trip']['stops']: - if stop['station']['evaNr'] == next_eva_nr: - return trip_call['trip']['stops'][trip_call['trip']['stops'].index(stop) - 1]['station']['name'] - raise NotAvailableException() - - -def get_final_station_name(trip_call=None): - """Gets the destination of the train - """ - if trip_call is None: - trip_call = get_trip() - return trip_call['trip']['stopInfo']['finalStationName'] - - -def get_all_station_names(trip_call=None): - """Gets the names of all stations for this trip. - """ - if trip_call is None: - trip_call = get_trip() - names = [] - for stop in trip_call['trip']['stops']: - names.append(stop['station']['name']) - if len(names) != 0: - return names - else: - raise NotAvailableException() - - -def get_arrival_time(station_name=None, evaNr=None, trip_call=None): - """Gets the arrival time at a specific station. - """ - if trip_call is None: - trip_call = get_trip() - if station_name is not None: - key = 'name' - value = station_name - elif evaNr is not None: - key = 'evaNr' - value = evaNr - else: - raise MissingArgumentError() - - for stop in trip_call['trip']['stops']: - if stop['station'][key] == value: - return datetime.fromtimestamp(cut_timestamp(stop['timetable']['actualArrivalTime'])) - raise NotAvailableException() - - -def get_next_arrival_time(trip_call=None): - """Gets the arrival time at the next station - """ - return get_arrival_time(evaNr=get_next_station_eva_number(trip_call=trip_call), trip_call=trip_call) - - -def get_time_until_arrival(station_name=None, evaNr=None, trip_call=None): - """Gets the time until the arrival at a specific station. - Returns the difference as a timedelta object. - """ - if trip_call is None: - trip_call = get_trip() - return get_arrival_time(station_name=station_name, evaNr=evaNr, trip_call=trip_call) - datetime.now() - - -def get_time_until_next_arrival(trip_call=None): - """Gets the time until the next stop in minutes - """ - if trip_call is None: - trip_call = get_trip() - return get_time_until_arrival(evaNr=get_next_station_eva_number(trip_call=trip_call), trip_call=trip_call) - - -def get_departure_time(station_name=None, evaNr=None, trip_call=None): - """Gets the departure time at a specific station. - """ - if trip_call is None: - trip_call = get_trip() - if station_name is not None: - key = 'name' - value = station_name - elif evaNr is not None: - key = 'evaNr' - value = evaNr - else: - raise MissingArgumentError() - - for stop in trip_call['trip']['stops']: - if stop['station'][key] == value: - return datetime.fromtimestamp(cut_timestamp(stop['timetable']['actualDepartureTime'])) - raise NotAvailableException() - - -def get_next_departure_time(trip_call=None): - """Gets the departure time at the next station - """ - return get_departure_time(evaNr=get_next_station_eva_number(trip_call=trip_call), trip_call=trip_call) - - -def get_time_until_departure(station_name=None, evaNr=None, trip_call=None): - """Gets the time until the departure at a specific station. - Returns the difference as a timedelta object. - """ - if trip_call is None: - trip_call = get_trip() - return get_departure_time(station_name=station_name, evaNr=evaNr, trip_call=trip_call) - datetime.now() - - -def get_time_until_next_departure(trip_call=None): - """Gets the time until the departure from the next station in minutes - """ - if trip_call is None: - trip_call = get_trip() - return get_time_until_departure(evaNr=get_next_station_eva_number(trip_call=trip_call), trip_call=trip_call) - - -def get_track(station_name=None, evaNr=None, trip_call=None): - """Gets the track for a specific station - """ - if trip_call is None: - trip_call = get_trip() - if station_name is not None: - key = 'name' - value = station_name - elif evaNr is not None: - key = 'evaNr' - value = evaNr - else: - raise MissingArgumentError() - - for stop in trip_call['trip']['stops']: - if stop['station'][key] == value: - return int(stop['track']['actual']) - raise NotAvailableException() - - -def get_next_track(trip_call=None): - """Gets the track of the next stop - """ - return get_track(evaNr=get_next_station_eva_number(trip_call=trip_call), trip_call=trip_call) - - -def get_delay(trip_call=None): - """Gets the delay in minutes. - """ - if trip_call is None: - trip_call = get_trip() - evaNr = get_next_station_eva_number(trip_call=trip_call) - for stop in trip_call['trip']['stops']: - if stop['station']['evaNr'] == evaNr: - if stop['timetable']['arrivalDelay'] == '': - return 0 - else: - return int(stop['timetable']['arrivalDelay']) - raise NotAvailableException() - - -def get_all_delay_reasons(trip_call=None): - """Gets all reasons for delays - """ - if trip_call is None: - trip_call = get_trip() - reasons = {} - for stop in trip_call['trip']['stops']: - if stop['delayReasons'] is not None: - this_reasons = [] - for reason in stop['delayReasons']: - this_reasons.append(reason['text']) - if len(this_reasons) != 0: - reasons[stop['station']['evaNr']] = this_reasons - if len(reasons) != 0: - return reasons - raise NoneDataException() # Train is on time - - -def get_delay_reasons(trip_call=None): - """Gets the current delay reasons - """ - if trip_call is None: - trip_call = get_trip() - reasons = [] - - for reason in get_delay_reasons_last_station(trip_call=trip_call), get_delay_reasons_for_station( - evaNr=get_next_station_eva_number(trip_call=trip_call), trip_call=trip_call): - if reason: - for r in reason: - reasons.append(r) - if len(reasons) != 0: - return list(dict.fromkeys(reasons)) - raise NoneDataException() # Train is on time - - -def get_delay_reasons_for_station(station_name=None, evaNr=None, trip_call=None): - """Gets the delay reasons for a specific station - """ - if trip_call is None: - trip_call = get_trip() - if station_name is not None: - key = 'name' - value = station_name - elif evaNr is not None: - key = 'evaNr' - value = evaNr - else: - raise MissingArgumentError() - - for stop in trip_call['trip']['stops']: - if stop['station'][key] == value: - this_reasons = [] - if stop['delayReasons'] is not None: - for reason in stop['delayReasons']: - this_reasons.append(reason['text']) - return this_reasons - raise NotAvailableException() - - -def get_delay_reasons_last_station(trip_call=None): - """Gets the reasons for the current delay - """ - return get_delay_reasons_for_station(evaNr=get_last_station_eva_number(trip_call=trip_call), trip_call=trip_call) - - -def get_delay_status(trip_call=None): - """Gets the status of whether the train is delayed or not. - """ - if get_delay(trip_call=trip_call) > 0: - return True - else: - return False - - -def get_is_delayed(trip_call=None): - """Alias for get_delay_status(). - """ - return get_delay_status(trip_call=trip_call) - - -def get_station_position(station_name=None, evaNr=None, trip_call=None): - """Gets the position of a specific station - """ - if trip_call is None: - trip_call = get_trip() - if station_name is not None: - key = 'name' - value = station_name - elif evaNr is not None: - key = 'evaNr' - value = evaNr - else: - raise MissingArgumentError() - - for stop in trip_call['trip']['stops']: - if stop['station'][key] == value: - return stop['station']['geocoordinates']['latitude'], stop['station']['geocoordinates']['longitude'] - - -def get_station_distance(station_name=None, evaNr=None, trip_call=None): - """Calculates the distance to a specific station and returns it in meters - """ - if trip_call is None: - trip_call = get_trip() - if station_name is not None: - key = 'name' - value = station_name - elif evaNr is not None: - key = 'evaNr' - value = evaNr - else: - raise MissingArgumentError() - if trip_call is None: - trip_call = get_trip() - for stop in trip_call['trip']['stops']: - if stop['station'][key] == value: - return stop['info']['distanceFromStart'] - trip_call['trip']['actualPosition'] - trip_call['trip'][ - 'distanceFromLastStop'] - - -def get_next_station_distance(trip_call=None): - """Gets the distance to the next station - """ - return get_station_distance(evaNr=get_next_station_eva_number(trip_call=trip_call), trip_call=trip_call) - - -def get_connections(evaNr): - """Returns the connecting trains from a specific station - """ - connections = request_json('https://iceportal.de/api1/rs/tripInfo/connection/' + evaNr) - if connections['requestedEvaNr'] is None: - raise NotAvailableException() - else: - return connections - - -def get_all_connections(): - """Gets all connections for every available station - (usually every station except the first one) - """ - all_connections = [] - for evaNr in get_all_station_eva_numbers(): - try: - all_connections.append(get_connections(evaNr)) - except NotAvailableException: - pass - - -def get_connections_info(connections): - """Processes information on a given dict of connections Returns some useful details: {trainName, finalStation, - departure, track} example: [{'trainName': 'S 5', 'finalStation': 'Hameln', 'departureTime': datetime.datetime( - 2020, 12, 26, 15, 25), 'track': '1'}, ...] - """ - connections_info = [] - for connection in connections['connections']: - departure = connection['timetable']['actualDepartureTime'] \ - if not connection['timetable']['actualDepartureTime'] is None \ - else connection['timetable']['scheduledDepartureTime'] - track = connection['track']['actual'] \ - if not connection['track']['actual'] == '' \ - else connection['track']['scheduled'] - connections_info.append({'trainName': connection['trainType'] + ' ' + connection['vzn'], - 'finalStation': connection['station']['name'], - 'departure': datetime.fromtimestamp(cut_timestamp(departure)), 'track': track}) - return connections_info - - -def get_next_station_connections(trip_call=None): - """Gets the connecting trains for the next station - """ - return get_connections_info(get_connections(get_next_station_eva_number(trip_call=trip_call))) diff --git a/iceportal_apis/exceptions.py b/iceportal_apis/exceptions.py index 6638809..cd49dd4 100644 --- a/iceportal_apis/exceptions.py +++ b/iceportal_apis/exceptions.py @@ -3,9 +3,11 @@ Exceptions for iceportal_apis """ +from onboardapis.exceptions import OnboardException + ################################################ -class ApiException(Exception): +class ApiException(OnboardException): def __init__(self, message): """ Base exception for this module diff --git a/iceportal_apis/interfaces.py b/iceportal_apis/interfaces.py deleted file mode 100644 index 1cee7f3..0000000 --- a/iceportal_apis/interfaces.py +++ /dev/null @@ -1,271 +0,0 @@ -import asyncio -import datetime -import json -import threading -import time -import typing -import aiohttp -import requests - -from .constants import (URL_STATUS, URL_TRIP, URL_CONNECTIONS, URL_POIS, URL_BAP, URL_AUDIOBOOKS, BASE_URL) -from .exceptions import (NetworkException, ApiException, NotOnTrainException) -from .mocking import StaticSimulation, DynamicSimulation -from .types import InterfaceStatus - - -headers = {"User-Agent": "python:iceportal_apis"} # Header to inform the api that this request was sent via this module - - -class Requestable: - def __init__(self): - pass - - def get(self, url, **kwargs): - return requests.get(url, headers=headers, **kwargs) - - def get_json(self, url, **kwargs): - try: - response = self.get(url, **kwargs) - print(response.text) - return response.json() - except json.JSONDecodeError: - raise NotOnTrainException() - except requests.exceptions.RequestException: - raise NetworkException() - - -class ApiInterface: - def __init__(self): - self.interface_status: InterfaceStatus = InterfaceStatus.IDLE - self.status: dict = {} - self.trip: dict = {} - self.connections: dict = {} - self.stations: dict = {} - self.bap: dict = {} - self.pois: typing.List = [] - self.eva_nr_2_name: dict = {} - self.name_2_eva_nr: dict = {} - self._event_loop = asyncio.get_event_loop() - self._auto_refresh_thread: threading.Thread = threading.Thread(target=None, daemon=True) - self._auto_refresh_switch: bool = False - self._AUTO_REFRESH_INTERVAL: int = 1 - self._refresh_lock = threading.Lock() - self.refresh() - - async def _request_json(self, url: str) -> dict: - """ - Requests data from 'url' and parses it as a dictionary - """ - self.interface_status = InterfaceStatus.FETCHING - async with aiohttp.ClientSession(loop=self._event_loop) as session: - async with session.get(url, headers=headers) as response: - try: - return await response.json() - except (json.JSONDecodeError, aiohttp.ContentTypeError) as e: - raise NotOnTrainException() from e - except aiohttp.ClientError as e: - raise NetworkException from e - - async def _get_status(self) -> None: - """ - Refreshes the status data. - """ - self.status = await self._request_json(URL_STATUS) - - async def _get_trip(self): - """ - Refreshes the trip data. - """ - self.trip = await self._request_json(URL_TRIP) - - async def _get_connections(self, eva_nr: str): - """ - Refreshes connecting trains for eva_nr - :param eva_nr: The stations evaNr - :type eva_nr: str - """ - self.connections[eva_nr] = await self._request_json(URL_CONNECTIONS.format(eva_nr)) - - async def _get_bap(self) -> None: - """ - Refreshes data for bap service - """ - self.bap = await self._request_json(URL_BAP) \ - if self.status["bapInstalled"] \ - else {"bapServiceStatus": "INACTIVE", "status": False} - - def _get_pois(self, start_pos: typing.Tuple[float, float], end_pos: typing.Tuple[float, float]) -> dict: - """ - Refreshes points of interest in the rectangular area spanned by start_pos and end_pos - :param start_pos: The first corner position - :type start_pos: Tuple - :param end_pos: The second corner position - :type end_pos: Tuple - """ - return { - 'start_pos': start_pos, - 'end_pos': end_pos, - 'data': self._request_json(URL_POIS.format(start_pos[0], start_pos[1], end_pos[0], end_pos[1])) - } - - def refresh(self) -> None: - """ - Refreshes all data - """ - async def refresh_async(): - if self.trip == {}: # First request -> No data for _get_connections yet - await asyncio.gather(self._get_status(), self._get_trip()) - await asyncio.gather(*[self._get_connections(url) for url in list(self.eva_nr_2_name.keys())], - self._get_bap()) - else: - await asyncio.gather(self._get_status(), self._get_trip(), self._get_bap(), - *[self._get_connections(url) for url in list(self.eva_nr_2_name.keys())] - ) - - # Make thread safe - self._refresh_lock.acquire() - # Run async - try: - self._event_loop.run_until_complete(refresh_async()) - # Refresh lookup dicts - try: - for eva_nr, name in [(stop["station"]["evaNr"], stop["station"]["name"]) - for stop in self.trip["trip"]["stops"]]: - self.name_2_eva_nr[name] = eva_nr - self.eva_nr_2_name[eva_nr] = name - except KeyError as e: - print("Key error: ", e) - except (NotOnTrainException, NetworkException): - raise - finally: - self._refresh_lock.release() - - def _auto_refresh(self) -> None: - while self._auto_refresh_switch: - start = datetime.datetime.now() - try: - self.refresh() - except ApiException: - self._auto_refresh_switch = False - raise - delta = datetime.datetime.now() - start - time.sleep(0 if delta.total_seconds() > self._AUTO_REFRESH_INTERVAL - else self._AUTO_REFRESH_INTERVAL - delta.seconds - delta.microseconds / 1000000) - - def set_auto_refresh(self, auto_refresh: bool, interval: int = None): - if interval: - self._AUTO_REFRESH_INTERVAL = interval - if auto_refresh: - self._auto_refresh_switch: bool = True - self._auto_refresh_thread = threading.Thread(target=self._auto_refresh, - name=self.__class__.__name__+"ConnectionThread", - daemon=True) - self._auto_refresh_thread.start() - else: - self._auto_refresh_switch = False - - -class SynchronousApiInterface(ApiInterface, Requestable): - def __init__(self): - super(SynchronousApiInterface, self).__init__() - - def _request_json(self, url: str) -> dict: - return self.get_json(url) - - def _get_status(self) -> None: - self.status = self._request_json(URL_STATUS) - - def _get_trip(self): - self.trip = self._request_json(URL_TRIP) - - def _get_connections(self, eva_nr: str): - self.connections[eva_nr] = self._request_json(URL_CONNECTIONS.format(eva_nr)) - - def refresh(self) -> None: - self._get_status() - self._get_trip() - for eva_nr, name in [(stop["station"]["evaNr"], stop["station"]["name"]) - for stop in self.trip["trip"]["stops"]]: - self.name_2_eva_nr[name] = eva_nr - self.eva_nr_2_name[eva_nr] = name - self._get_connections(eva_nr) - - -class TestInterface(ApiInterface): - def __init__(self, - simulation_type: typing.Type[typing.Union[StaticSimulation, DynamicSimulation]] = StaticSimulation): - self.simulation = simulation_type() - self.simulation.start() - super(TestInterface, self).__init__() - - def __del__(self): - try: - self.simulation.stop() - except AttributeError: - pass - - def _get_status(self) -> dict: - return self.simulation.get_status() - - def _get_trip(self) -> dict: - return self.simulation.get_trip() - - def _get_connections(self, eva_nr: str) -> dict: - return self.simulation.get_connections(eva_nr) - - def refresh(self) -> None: - self.status = self._get_status() - self.trip = self._get_trip() - for stop in self.trip["trip"]["stops"]: - eva_nr = stop["station"]["evaNr"] - name = stop["station"]["name"] - self.name_2_eva_nr[name] = eva_nr - self.eva_nr_2_name[eva_nr] = name - self.stations[eva_nr] = stop - self.connections[eva_nr] = self._get_connections(eva_nr) - - def set_auto_refresh(self, auto_refresh: bool, interval: int = None): - super(TestInterface, self).set_auto_refresh(auto_refresh=auto_refresh, interval=interval) - - -class Part(Requestable): - def __init__(self, filename, url, collect: bool = False): - super().__init__() - self.filename = filename - self.url = url - self.content = self.get(BASE_URL+self.url).content if collect else None - - -class Media(Requestable): - def __init__(self, title, url): - super().__init__() - self.title = title - self.url = url - self.parts = self._get_parts() - - def __str__(self): - return self.title - - def _get_parts(self): - return list( - [Part(file["title"]+"."+file["path"].split(".")[-1], file["path"]) - for file in self.get_json(self.url)["files"] - ]) - - -class PageIndex(Requestable): - def __init__(self, url): - super().__init__() - self.data = self.get_json(url) - - def list(self) -> typing.List[Media]: - # [print("{}{}".format(BASE_URL, media["navigation"]["href"])) for media in self.data["teaserGroups"][0]["items"]] - return list( - [Media(media["title"], "{}{}".format(BASE_URL, media["navigation"]["href"])) - for media in self.data["teaserGroups"][0]["items"]] - ) - - -class MediaInterface: - def __init__(self): - self.audiobooks = PageIndex(URL_AUDIOBOOKS) diff --git a/iceportal_apis/mocking/__init__.py b/iceportal_apis/mocking/__init__.py deleted file mode 100644 index 49dc144..0000000 --- a/iceportal_apis/mocking/__init__.py +++ /dev/null @@ -1,88 +0,0 @@ -from datetime import datetime, timedelta -from threading import Thread -from time import sleep - -from .data import (STATIC_STATUS, STATIC_TRIP, STATIC_CONNECTIONS) - - -class _Simulation: - def __init__(self): - self.simulation_thread = Thread(target=None, daemon=True) - self._running = False - self._interval = timedelta(seconds=1) - self._status = STATIC_STATUS - self._trip = STATIC_TRIP - self._connections = STATIC_CONNECTIONS - - def __del__(self): - try: - if self._running: - self.stop() - except AttributeError: - pass - - def start(self): - self.simulation_thread = Thread(target=self._simulate, name=self.__class__.__name__ + "Thread", daemon=True) - self._running = True - self.simulation_thread.start() - - def stop(self): - self._running = False - # WONT BE NEEDED BECAUSE THREAD IS DAEMON THREAD - # if self.simulation_thread.is_alive(): - # self.simulation_thread.join() - - def get_status(self): - return self._status - - def get_trip(self): - return self._trip - - def get_connections(self, eva_nr): - return self._connections[eva_nr] - - def _simulate(self): - while self._running: - raise NotImplementedError("This is the base class for simulations. Please use a derived class!") - - -class StaticSimulation(_Simulation): - def __init__(self): - """ - Static simulation of the api - """ - super(StaticSimulation, self).__init__() - - def get_connections(self, eva_nr): - return self._connections["8000055_00"] - - def _simulate(self): - pass - - -class DynamicSimulation(_Simulation): - def __init__(self): - """ - Simulates the behaviour of the onboard api dynamically - """ - self.data_server = data.DynamicDataServer() - super(DynamicSimulation, self).__init__() - - def get_connections(self, eva_nr): - return self._connections["8000055_00"] - - def _refresh_values(self): - return - # raise NotImplementedError("The dynamic simulation is not yet implemented!") - - def _simulate(self): - while self._running: - start = datetime.now() - self._refresh_values() - while (datetime.now() - start) < self._interval: # removed := for backwards compatibility <3.8 - delta = (datetime.now() - start) - if not self._running: - break - remaining_time = self._interval - delta - remaining_milliseconds = remaining_time.microseconds / 1000.0 + 1000 * remaining_time.seconds - sleep(0.1 if remaining_milliseconds > 100 else remaining_milliseconds / 1000.0) diff --git a/iceportal_apis/mocking/data.py b/iceportal_apis/mocking/data.py deleted file mode 100644 index f6d9027..0000000 --- a/iceportal_apis/mocking/data.py +++ /dev/null @@ -1,222 +0,0 @@ -from json import loads, dumps -from os.path import join, dirname -from typing import Tuple, Dict, List - -STATIC_STATUS = { - "connection": True, - "servicelevel": "AVAILABLE_SERVICE", - "internet": "HIGH", - "gpsStatus": "VALID", - "tileY": 303, - "tileX": 216, - "series": "011", - "latitude": 52.766562, - "longitude": 10.251847, - "serverTime": 1603913237508, - "speed": 169, - "trainType": "ICE", - "tzn": "Tz1191", - "wagonClass": "SECOND", - "connectivity": { - "currentState": "HIGH", - "nextState": "WEAK", - "remainingTimeSeconds": 58 - } -} - - -STATIC_TRIP = { - "trip": { - "tripDate": "2020-10-31", - "trainType": "ICE", - "vzn": "881", - "actualPosition": 159781, - "distanceFromLastStop": 41632, - "totalDistance": 708799, - "stopInfo": { - "scheduledNext": "8002548_00", - "actualNext": "8002548_00", - "actualLast": "8002553_00", - "actualLastStarted": "8002553", - "finalStationName": "München Hbf", - "finalStationEvaNr": "8000261_00" - }, - "stops": [ - {"station": {"evaNr": "8002553_00", "name": "Hamburg-Altona", "code": None, "geocoordinates": {"latitude": 53.552695, "longitude": 9.935175}}, "timetable": {"scheduledArrivalTime": None, "actualArrivalTime": None, "showActualArrivalTime": None, "arrivalDelay": '', "scheduledDepartureTime": 1604148180000, "actualDepartureTime": 1604148180000, "showActualDepartureTime": True, "departureDelay": ''}, "track": {"scheduled": "11", "actual": "11"}, "info": {"status": 0, "passed": True, "positionStatus": "passed", "distance": 0, "distanceFromStart": 0}, "delayReasons": None}, - {"station": {"evaNr": "8002548_00", "name": "Hamburg Dammtor", "code": None, "geocoordinates": { "latitude": 53.560751, "longitude": 9.989566}}, "timetable": { "scheduledArrivalTime": 1604148720000, "actualArrivalTime": 1604148840000, "showActualArrivalTime": True, "arrivalDelay": "+2", "scheduledDepartureTime": 1604148780000, "actualDepartureTime": 1604148900000, "showActualDepartureTime": True, "departureDelay": "+2"}, "track": {"scheduled": "4", "actual": "4"}, "info": {"status": 0, "passed": True, "positionStatus": "passed", "distance": 3704, "distanceFromStart": 3704}, "delayReasons": None}, - {"station": {"evaNr": "8002549_00", "name": "Hamburg Hbf", "code": None, "geocoordinates": {"latitude": 53.552736, "longitude": 10.006909}}, "timetable": {"scheduledArrivalTime": 1604149080000, "actualArrivalTime": 1604149140000, "showActualArrivalTime": True, "arrivalDelay": "+1", "scheduledDepartureTime": 1604149260000, "actualDepartureTime": 1604149260000, "showActualDepartureTime": True, "departureDelay": ''}, "track": {"scheduled": "14", "actual": "14"}, "info": {"status": 0, "passed": True, "positionStatus": "passed", "distance": 1452, "distanceFromStart": 5156}, "delayReasons": None}, - {"station": {"evaNr": "8000147_00", "name": "Hamburg-Harburg", "code": None, "geocoordinates": {"latitude": 53.455908, "longitude": 9.991701}}, "timetable": {"scheduledArrivalTime": 1604149800000, "actualArrivalTime": 1604149980000, "showActualArrivalTime": True, "arrivalDelay": "+3", "scheduledDepartureTime": 1604149920000, "actualDepartureTime": 1604150100000, "showActualDepartureTime": True, "departureDelay": "+3"}, "track": {"scheduled": "4", "actual": "4"}, "info": {"status": 0, "passed": True, "positionStatus": "passed", "distance": 10817, "distanceFromStart": 15973}, "delayReasons": None}, - {"station": {"evaNr": "8000238_00", "name": "Lüneburg", "code": None, "geocoordinates": {"latitude": 53.249656, "longitude": 10.41989}}, "timetable": {"scheduledArrivalTime": 1604150820000, "actualArrivalTime": 1604151060000, "showActualArrivalTime": True, "arrivalDelay": "+4", "scheduledDepartureTime": 1604150940000, "actualDepartureTime": 1604151240000, "showActualDepartureTime": True, "departureDelay": "+5"}, "track": {"scheduled": "1", "actual": "1"}, "info": {"status": 0, "passed": True, "positionStatus": "passed", "distance": 36529, "distanceFromStart": 52502}, "delayReasons": None}, - {"station": {"evaNr": "8000152_00", "name": "Hannover Hbf", "code": None, "geocoordinates": {"latitude": 52.376761, "longitude": 9.741021}}, "timetable": {"scheduledArrivalTime": 1604154120000, "actualArrivalTime": 1604154120000, "showActualArrivalTime": True, "arrivalDelay": '', "scheduledDepartureTime": 1604154360000, "actualDepartureTime": 1604154360000, "showActualDepartureTime": True, "departureDelay": ''}, "track": {"scheduled": "3", "actual": "3"}, "info": {"status": 0, "passed": True, "positionStatus": "departed", "distance": 107279, "distanceFromStart": 159781}, "delayReasons": [{"code": "38", "text": "Technische Störung an der Strecke"}]}, - {"station": {"evaNr": "8000128_00", "name": "Göttingen", "code": None, "geocoordinates": {"latitude": 51.536815, "longitude": 9.926072}}, "timetable": {"scheduledArrivalTime": 1604156400000, "actualArrivalTime": 1604156400000, "showActualArrivalTime": True, "arrivalDelay": '', "scheduledDepartureTime": 1604156520000, "actualDepartureTime": 1604156520000, "showActualDepartureTime": True, "departureDelay": ''}, "track": {"scheduled": "10", "actual": "10"}, "info": {"status": 0, "passed": False, "positionStatus": "future", "distance": 94281, "distanceFromStart": 254062}, "delayReasons": None}, - {"station": {"evaNr": "8003200_00", "name": "Kassel-Wilhelmshöhe", "code": None, "geocoordinates": {"latitude": 51.313114, "longitude": 9.446898}}, "timetable": {"scheduledArrivalTime": 1604157660000, "actualArrivalTime": 1604157660000, "showActualArrivalTime": True, "arrivalDelay": '', "scheduledDepartureTime": 1604157780000, "actualDepartureTime": 1604157900000, "showActualDepartureTime": True, "departureDelay": "+2"}, "track": {"scheduled": "2", "actual": "2"}, "info": {"status": 0, "passed": False, "positionStatus": "future", "distance": 41515, "distanceFromStart": 295577}, "delayReasons": None}, - {"station": {"evaNr": "8000115_00", "name": "Fulda", "code": None, "geocoordinates": {"latitude": 50.554723, "longitude": 9.683977}}, "timetable": {"scheduledArrivalTime": 1604159640000, "actualArrivalTime": 1604159640000, "showActualArrivalTime": True, "arrivalDelay": '', "scheduledDepartureTime": 1604159760000, "actualDepartureTime": 1604159760000, "showActualDepartureTime": True, "departureDelay": ''}, "track": {"scheduled": "4", "actual": "4"}, "info": {"status": 0, "passed": False, "positionStatus": "future", "distance": 85974, "distanceFromStart": 381551}, "delayReasons": None}, - {"station": {"evaNr": "8000260_00", "name": "Würzburg Hbf", "code": None, "geocoordinates": {"latitude": 49.801796, "longitude": 9.93578}}, "timetable": {"scheduledArrivalTime": 1604161740000, "actualArrivalTime": 1604161740000, "showActualArrivalTime": True, "arrivalDelay": '', "scheduledDepartureTime": 1604161860000, "actualDepartureTime": 1604161860000, "showActualDepartureTime": True, "departureDelay": ''}, "track": {"scheduled": "4", "actual": "4"}, "info": {"status": 0, "passed": False, "positionStatus": "future", "distance": 85644, "distanceFromStart": 467195}, "delayReasons": None}, - {"station": {"evaNr": "8000284_00", "name": "Nürnberg Hbf", "code": None, "geocoordinates": {"latitude": 49.445616, "longitude": 11.082989}}, "timetable": {"scheduledArrivalTime": 1604165040000, "actualArrivalTime": 1604165040000, "showActualArrivalTime": True, "arrivalDelay": '', "scheduledDepartureTime": 1604165220000, "actualDepartureTime": 1604165340000, "showActualDepartureTime": True, "departureDelay": "+2"}, "track": {"scheduled": "8", "actual": "8"}, "info": {"status": 0, "passed": False, "positionStatus": "future", "distance": 91662, "distanceFromStart": 558857}, "delayReasons": None}, - {"station": {"evaNr": "8000183_00", "name": "Ingolstadt Hbf", "code": None, "geocoordinates": {"latitude": 48.744541, "longitude": 11.437337}}, "timetable": {"scheduledArrivalTime": 1604167080000, "actualArrivalTime": 1604167080000, "showActualArrivalTime": True, "arrivalDelay": '', "scheduledDepartureTime": 1604167260000, "actualDepartureTime": 1604167260000, "showActualDepartureTime": True, "departureDelay": ''}, "track": {"scheduled": "3", "actual": "3"}, "info": {"status": 0, "passed": False, "positionStatus": "future", "distance": 82137, "distanceFromStart": 640994}, "delayReasons": None}, - {"station": {"evaNr": "8000261_00", "name": "München Hbf", "code": None, "geocoordinates": {"latitude": 48.140232, "longitude": 11.558335}}, "timetable": {"scheduledArrivalTime": 1604169720000, "actualArrivalTime": 1604169720000, "showActualArrivalTime": True, "arrivalDelay": '', "scheduledDepartureTime": None, "actualDepartureTime": None, "showActualDepartureTime": None, "departureDelay": ''}, "track": {"scheduled": "18", "actual": "18"}, "info": {"status": 0, "passed": False, "positionStatus": "future", "distance": 67805, "distanceFromStart": 708799}, "delayReasons": None} - ] - }, - "connection": None, - "selectedRoute": { - "conflictInfo": { - "status": "NO_CONFLICT", - "text": None - }, - "mobility": None - }, - "active": None -} - - -STATIC_CONNECTIONS = { - "8000055_00": { - "connections": [ - { - "trainType": "S", - "vzn": "3", - "trainNumber": "38334", - "station": { - "evaNr": "8000376_00", - "name": "Germersheim", - "code": None, - "geocoordinates": { - "latitude": 49.225402, - "longitude": 8.365282 - } - }, - "timetable": { - "scheduledArrivalTime": None, - "actualArrivalTime": None, - "showActualArrivalTime": None, - "arrivalDelay": '', - "scheduledDepartureTime": 1611232980000, - "actualDepartureTime": 1611232980000, - "showActualDepartureTime": True, - "departureDelay": '' - }, - "track": { - "scheduled": "3b", - "actual": "3b" - }, - "info": { - "status": 0, - "passed": False, - "positionStatus": None, - "distance": 0, - "distanceFromStart": 0 - }, - "stops": [ - {"station": {"evaNr": "8000055_00", "name": "Bruchsal", "code": None, "geocoordinates": {"latitude": 49.124622, "longitude": 8.589649}}, "timetable": {"scheduledArrivalTime": None, "actualArrivalTime": None, "showActualArrivalTime": None, "arrivalDelay": '', "scheduledDepartureTime": 1611232980000, "actualDepartureTime": 1611232980000, "showActualDepartureTime": True, "departureDelay": ''}, "track": {"scheduled": "3b", "actual": "3b"}, "info": {"status": 0, "passed": False, "positionStatus": None, "distance": 0, "distanceFromStart": 0}, "delayReasons": None}, - {"station": {"evaNr": "8005931_00", "name": "Ubstadt-Weiher", "code": None, "geocoordinates": {"latitude": 49.167021, "longitude": 8.623335}}, "timetable": {"scheduledArrivalTime": 1611233160000, "actualArrivalTime": 1611233220000, "showActualArrivalTime": True, "arrivalDelay": "+1", "scheduledDepartureTime": None, "actualDepartureTime": None, "showActualDepartureTime": None, "departureDelay": ''}, "track": {"scheduled": "2", "actual": "2"}, "info": {"status": 0, "passed": False, "positionStatus": None, "distance": 0, "distanceFromStart": 0}, "delayReasons": None}, - {"station": {"evaNr": "8005933_00", "name": "Stettfeld-Weiher", "code": None, "geocoordinates": {"latitude": 49.183625, "longitude": 8.636928}}, "timetable": {"scheduledArrivalTime": 1611233340000, "actualArrivalTime": 1611233340000, "showActualArrivalTime": True, "arrivalDelay": '', "scheduledDepartureTime": None, "actualDepartureTime": None, "showActualDepartureTime": None, "departureDelay": ''}, "track": {"scheduled": "1", "actual": "1"}, "info": {"status": 0, "passed": False, "positionStatus": None, "distance": 0, "distanceFromStart": 0}, "delayReasons": None}, - {"station": {"evaNr": "8003533_00", "name": "Bad Schönborn Süd", "code": None, "geocoordinates": {"latitude": 49.200001, "longitude": 8.641924}}, "timetable": {"scheduledArrivalTime": 1611233460000, "actualArrivalTime": 1611233460000, "showActualArrivalTime": True, "arrivalDelay": '', "scheduledDepartureTime": None, "actualDepartureTime": None, "showActualDepartureTime": None, "departureDelay": ''}, "track": {"scheduled": "1", "actual": "1"}, "info": {"status": 0, "passed": False, "positionStatus": None, "distance": 0, "distanceFromStart": 0}, "delayReasons": None}, - {"station": {"evaNr": "8004032_00", "name": "Bad Schönborn-Kronau", "code": None, "geocoordinates": {"latitude": 49.219345, "longitude": 8.646821}}, "timetable": {"scheduledArrivalTime": 1611233580000, "actualArrivalTime": 1611233640000, "showActualArrivalTime": True, "arrivalDelay": "+1", "scheduledDepartureTime": None, "actualDepartureTime": None, "showActualDepartureTime": None, "departureDelay": ''}, "track": {"scheduled": "1", "actual": "1"}, "info": {"status": 0, "passed": False, "positionStatus": None, "distance": 0, "distanceFromStart": 0}, "delayReasons": None}, - {"station": {"evaNr": "8005181_00", "name": "Rot-Malsch", "code": None, "geocoordinates": {"latitude": 49.243385, "longitude": 8.65221}}, "timetable": {"scheduledArrivalTime": 1611233760000, "actualArrivalTime": 1611233760000, "showActualArrivalTime": True, "arrivalDelay": '', "scheduledDepartureTime": None, "actualDepartureTime": None, "showActualDepartureTime": None, "departureDelay": ''}, "track": {"scheduled": "2", "actual": "2"}, "info": {"status": 0, "passed": False, "positionStatus": None, "distance": 0, "distanceFromStart": 0}, "delayReasons": None}, - {"station": {"evaNr": "8006421_00", "name": "Wiesloch-Walldorf", "code": None, "geocoordinates": {"latitude": 49.291353, "longitude": 8.664146}}, "timetable": {"scheduledArrivalTime": 1611234000000, "actualArrivalTime": 1611234000000, "showActualArrivalTime": True, "arrivalDelay": '', "scheduledDepartureTime": None, "actualDepartureTime": None, "showActualDepartureTime": None, "departureDelay": ''}, "track": {"scheduled": "1", "actual": "1"}, "info": {"status": 0, "passed": False, "positionStatus": None, "distance": 0, "distanceFromStart": 0}, "delayReasons": None}, - {"station": {"evaNr": "8005648_00", "name": "St Ilgen-Sandhausen", "code": None, "geocoordinates": {"latitude": 49.341268, "longitude": 8.668715}}, "timetable": {"scheduledArrivalTime": 1611234240000, "actualArrivalTime": 1611234240000, "showActualArrivalTime": True, "arrivalDelay": '', "scheduledDepartureTime": None, "actualDepartureTime": None, "showActualDepartureTime": None, "departureDelay": ''}, "track": {"scheduled": "1", "actual": "1"}, "info": {"status": 0, "passed": False, "positionStatus": None, "distance": 0, "distanceFromStart": 0}, "delayReasons": None}, - {"station": {"evaNr": "8002686_00", "name": "Heidelberg-Kirchheim/Rohrbach", "code": None, "geocoordinates": {"latitude": 49.379389, "longitude": 8.675383}}, "timetable": {"scheduledArrivalTime": 1611234420000, "actualArrivalTime": 1611234480000, "showActualArrivalTime": True, "arrivalDelay": "+1", "scheduledDepartureTime": None, "actualDepartureTime": None, "showActualDepartureTime": None, "departureDelay": ''}, "track": {"scheduled": "2", "actual": "2"}, "info": {"status": 0, "passed": False, "positionStatus": None, "distance": 0, "distanceFromStart": 0}, "delayReasons": None}, - {"station": {"evaNr": "8000156_00", "name": "Heidelberg Hbf", "code": None, "geocoordinates": {"latitude": 49.403567, "longitude": 8.675442}}, "timetable": {"scheduledArrivalTime": 1611234720000, "actualArrivalTime": 1611234720000, "showActualArrivalTime": True, "arrivalDelay": '', "scheduledDepartureTime": None, "actualDepartureTime": None, "showActualDepartureTime": None, "departureDelay": ''}, "track": {"scheduled": "4", "actual": "4"}, "info": {"status": 0, "passed": False, "positionStatus": None, "distance": 0, "distanceFromStart": 0}, "delayReasons": None}, - {"station": {"evaNr": "8002687_00", "name": "Heidelberg-Pfaffengrund/Wieblingen", "code": None, "geocoordinates": {"latitude": 49.411929, "longitude": 8.64157}}, "timetable": {"scheduledArrivalTime": 1611234960000, "actualArrivalTime": 1611234960000, "showActualArrivalTime": True, "arrivalDelay": '', "scheduledDepartureTime": None, "actualDepartureTime": None, "showActualDepartureTime": None, "departureDelay": ''}, "track": {"scheduled": "1", "actual": "1"}, "info": {"status": 0, "passed": False, "positionStatus": None, "distance": 0, "distanceFromStart": 0}, "delayReasons": None}, - {"station": {"evaNr": "8003842_00", "name": "Mannheim-Friedrichsfeld Süd", "code": None, "geocoordinates": {"latitude": 49.438144, "longitude": 8.572382}}, "timetable": {"scheduledArrivalTime": 1611235200000, "actualArrivalTime": 1611235200000, "showActualArrivalTime": True, "arrivalDelay": '', "scheduledDepartureTime": None, "actualDepartureTime": None, "showActualDepartureTime": None, "departureDelay": ''}, "track": {"scheduled": "1", "actual": "1"}, "info": {"status": 0, "passed": False, "positionStatus": None, "distance": 0, "distanceFromStart": 0}, "delayReasons": None}, - {"station": {"evaNr": "8000244_00", "name": "Mannheim Hbf", "code": None, "geocoordinates": {"latitude": 49.479354, "longitude": 8.468921}}, "timetable": {"scheduledArrivalTime": 1611235740000, "actualArrivalTime": 1611235740000, "showActualArrivalTime": True, "arrivalDelay": '', "scheduledDepartureTime": None, "actualDepartureTime": None, "showActualDepartureTime": None, "departureDelay": ''}, "track": {"scheduled": "1", "actual": "1"}, "info": {"status": 0, "passed": False, "positionStatus": None, "distance": 0, "distanceFromStart": 0}, "delayReasons": None}, - {"station": {"evaNr": "8003759_00", "name": "Ludwigshafen (Rhein) Mitte", "code": None, "geocoordinates": {"latitude": 49.479005, "longitude": 8.452152}}, "timetable": {"scheduledArrivalTime": 1611235980000, "actualArrivalTime": 1611235980000, "showActualArrivalTime": True, "arrivalDelay": '', "scheduledDepartureTime": None, "actualDepartureTime": None, "showActualDepartureTime": None, "departureDelay": ''}, "track": {"scheduled": "1", "actual": "1"}, "info": {"status": 0, "passed": False, "positionStatus": None, "distance": 0, "distanceFromStart": 0}, "delayReasons": None}, - {"station": {"evaNr": "8000236_00", "name": "Ludwigshafen (Rh) Hbf", "code": None, "geocoordinates": {"latitude": 49.477987, "longitude": 8.433402}}, "timetable": {"scheduledArrivalTime": 1611236100000, "actualArrivalTime": 1611236160000, "showActualArrivalTime": True, "arrivalDelay": "+1", "scheduledDepartureTime": None, "actualDepartureTime": None, "showActualDepartureTime": None, "departureDelay": ''}, "track": {"scheduled": "4", "actual": "4"}, "info": {"status": 0, "passed": False, "positionStatus": None, "distance": 0, "distanceFromStart": 0}, "delayReasons": None}, - {"station": {"evaNr": "8003687_00", "name": "Limburgerhof", "code": None, "geocoordinates": {"latitude": 49.424273, "longitude": 8.390747}}, "timetable": {"scheduledArrivalTime": 1611236460000, "actualArrivalTime": 1611236580000, "showActualArrivalTime": True, "arrivalDelay": "+2", "scheduledDepartureTime": None, "actualDepartureTime": None, "showActualDepartureTime": None, "departureDelay": ''}, "track": {"scheduled": "3", "actual": "3"}, "info": {"status": 0, "passed": False, "positionStatus": None, "distance": 0, "distanceFromStart": 0}, "delayReasons": None}, - {"station": {"evaNr": "8000326_00", "name": "Schifferstadt", "code": None, "geocoordinates": {"latitude": 49.39291, "longitude": 8.364945}}, "timetable": {"scheduledArrivalTime": 1611236760000, "actualArrivalTime": 1611236820000, "showActualArrivalTime": True, "arrivalDelay": "+1", "scheduledDepartureTime": None, "actualDepartureTime": None, "showActualDepartureTime": None, "departureDelay": ''}, "track": {"scheduled": "3", "actual": "3"}, "info": {"status": 0, "passed": False, "positionStatus": None, "distance": 0, "distanceFromStart": 0}, "delayReasons": None}, - {"station": {"evaNr": "8005345_00", "name": "Schifferstadt Süd", "code": None, "geocoordinates": {"latitude": 49.374089, "longitude": 8.377314}}, "timetable": {"scheduledArrivalTime": 1611237000000, "actualArrivalTime": 1611237000000, "showActualArrivalTime": True, "arrivalDelay": '', "scheduledDepartureTime": None, "actualDepartureTime": None, "showActualDepartureTime": None, "departureDelay": ''}, "track": {"scheduled": "2", "actual": "2"}, "info": {"status": 0, "passed": False, "positionStatus": None, "distance": 0, "distanceFromStart": 0}, "delayReasons": None}, - {"station": {"evaNr": "8005626_00", "name": "Speyer Nord-West", "code": None, "geocoordinates": {"latitude": 49.333592, "longitude": 8.419263}}, "timetable": {"scheduledArrivalTime": 1611237240000, "actualArrivalTime": 1611237240000, "showActualArrivalTime": True, "arrivalDelay": '', "scheduledDepartureTime": None, "actualDepartureTime": None, "showActualDepartureTime": None, "departureDelay": ''}, "track": {"scheduled": "1", "actual": "1"}, "info": {"status": 0, "passed": False, "positionStatus": None, "distance": 0, "distanceFromStart": 0}, "delayReasons": None}, - {"station": {"evaNr": "8005628_00", "name": "Speyer Hbf", "code": None, "geocoordinates": {"latitude": 49.324119, "longitude": 8.427949}}, "timetable": {"scheduledArrivalTime": 1611237360000, "actualArrivalTime": 1611237360000, "showActualArrivalTime": True, "arrivalDelay": '', "scheduledDepartureTime": None, "actualDepartureTime": None, "showActualDepartureTime": None, "departureDelay": ''}, "track": {"scheduled": "3", "actual": "3"}, "info": {"status": 0, "passed": False, "positionStatus": None, "distance": 0, "distanceFromStart": 0}, "delayReasons": None}, - {"station": {"evaNr": "8000894_00", "name": "Berghausen (Pfalz)", "code": None, "geocoordinates": {"latitude": 49.295449, "longitude": 8.406173}}, "timetable": {"scheduledArrivalTime": 1611237600000, "actualArrivalTime": 1611237600000, "showActualArrivalTime": True, "arrivalDelay": '', "scheduledDepartureTime": None, "actualDepartureTime": None, "showActualDepartureTime": None, "departureDelay": ''}, "track": {"scheduled": "2", "actual": "2"}, "info": {"status": 0, "passed": False, "positionStatus": None, "distance": 0, "distanceFromStart": 0}, "delayReasons": None}, - {"station": {"evaNr": "8002704_00", "name": "Heiligenstein (Pfalz)", "code": None, "geocoordinates": {"latitude": 49.28566, "longitude": 8.393726}}, "timetable": {"scheduledArrivalTime": 1611237720000, "actualArrivalTime": 1611237720000, "showActualArrivalTime": True, "arrivalDelay": '', "scheduledDepartureTime": None, "actualDepartureTime": None, "showActualDepartureTime": None, "departureDelay": ''}, "track": {"scheduled": "2", "actual": "2"}, "info": {"status": 0, "passed": False, "positionStatus": None, "distance": 0, "distanceFromStart": 0}, "delayReasons": None}, - {"station": {"evaNr": "8003702_00", "name": "Lingenfeld", "code": None, "geocoordinates": {"latitude": 49.252584, "longitude": 8.349523}}, "timetable": {"scheduledArrivalTime": 1611237900000, "actualArrivalTime": 1611237900000, "showActualArrivalTime": True, "arrivalDelay": '', "scheduledDepartureTime": None, "actualDepartureTime": None, "showActualDepartureTime": None, "departureDelay": ''}, "track": {"scheduled": "1", "actual": "1"}, "info": {"status": 0, "passed": False, "positionStatus": None, "distance": 0, "distanceFromStart": 0}, "delayReasons": None}, - {"station": {"evaNr": "8000376_00", "name": "Germersheim", "code": None, "geocoordinates": {"latitude": 49.225402, "longitude": 8.365282}}, "timetable": {"scheduledArrivalTime": 1611238140000, "actualArrivalTime": 1611238260000, "showActualArrivalTime": True, "arrivalDelay": "+2", "scheduledDepartureTime": None, "actualDepartureTime": None, "showActualDepartureTime": None, "departureDelay": ''}, "track": {"scheduled": "5", "actual": "5"}, "info": {"status": 0, "passed": False, "positionStatus": None, "distance": 0, "distanceFromStart": 0}, "delayReasons": None} - ] - }, - { - "trainType": "S", - "vzn": "32", - "trainNumber": "85073", - "station": { - "evaNr": "8007145_00", - "name": "Menzingen (Baden)", - "code": None, - "geocoordinates": { - "latitude": 49.136233, - "longitude": 8.775067 - } - }, - "timetable": { - "scheduledArrivalTime": None, - "actualArrivalTime": None, - "showActualArrivalTime": None, - "arrivalDelay": '', - "scheduledDepartureTime": 1611234720000, - "actualDepartureTime": 1611234840000, - "showActualDepartureTime": True, - "departureDelay": "+2" - }, - "track": { - "scheduled": "3a", - "actual": "3a" - }, - "info": { - "status": 0, - "passed": False, - "positionStatus": None, - "distance": 0, - "distanceFromStart": 0 - }, - "stops": [ - {"station": {"evaNr": "8000055_00", "name": "Bruchsal", "code": None, "geocoordinates": {"latitude": 49.124622, "longitude": 8.589649}}, "timetable": {"scheduledArrivalTime": None, "actualArrivalTime": None, "showActualArrivalTime": None, "arrivalDelay": '', "scheduledDepartureTime": 1611234720000, "actualDepartureTime": 1611234840000, "showActualDepartureTime": True, "departureDelay": "+2"}, "track": {"scheduled": "3a", "actual": "3a"}, "info": {"status": 0, "passed": False, "positionStatus": None, "distance": 0, "distanceFromStart": 0}, "delayReasons": None}, - {"station": {"evaNr": "8085001_00", "name": "Bruchsal Schloßgarten", "code": None, "geocoordinates": {"latitude": 49.131438, "longitude": 8.59406}}, "timetable": {"scheduledArrivalTime": 1611234780000, "actualArrivalTime": 1611234900000, "showActualArrivalTime": True, "arrivalDelay": "+2", "scheduledDepartureTime": None, "actualDepartureTime": None, "showActualDepartureTime": None, "departureDelay": ''}, "track": None, "info": {"status": 0, "passed": False, "positionStatus": None, "distance": 0, "distanceFromStart": 0}, "delayReasons": None}, - {"station": {"evaNr": "8085002_00", "name": "Bruchsal Stegwiesen", "code": None, "geocoordinates": {"latitude": 49.136435, "longitude": 8.598199}}, "timetable": {"scheduledArrivalTime": 1611234900000, "actualArrivalTime": 1611235020000, "showActualArrivalTime": True, "arrivalDelay": "+2", "scheduledDepartureTime": None, "actualDepartureTime": None, "showActualDepartureTime": None, "departureDelay": ''}, "track": None, "info": {"status": 0, "passed": False, "positionStatus": None, "distance": 0, "distanceFromStart": 0}, "delayReasons": None}, - {"station": {"evaNr": "8007133_00", "name": "Ubstadt Ort", "code": None, "geocoordinates": {"latitude": 49.156748, "longitude": 8.625553}}, "timetable": {"scheduledArrivalTime": 1611235080000, "actualArrivalTime": 1611235200000, "showActualArrivalTime": True, "arrivalDelay": "+2", "scheduledDepartureTime": None, "actualDepartureTime": None, "showActualDepartureTime": None, "departureDelay": ''}, "track": None, "info": {"status": 0, "passed": False, "positionStatus": None, "distance": 0, "distanceFromStart": 0}, "delayReasons": None}, - {"station": {"evaNr": "8085003_00", "name": "Ubstadt Salzbrunnenstr", "code": None, "geocoordinates": {"latitude": 49.15519, "longitude": 8.633025}}, "timetable": {"scheduledArrivalTime": 1611235200000, "actualArrivalTime": 1611235260000, "showActualArrivalTime": True, "arrivalDelay": "+1", "scheduledDepartureTime": None, "actualDepartureTime": None, "showActualDepartureTime": None, "departureDelay": ''}, "track": None, "info": {"status": 0, "passed": False, "positionStatus": None, "distance": 0, "distanceFromStart": 0}, "delayReasons": None}, - {"station": {"evaNr": "8079149_00", "name": "Unteröwisheim M.-Luther-Str.", "code": None, "geocoordinates": {"latitude": 49.146484, "longitude": 8.66202}}, "timetable": {"scheduledArrivalTime": 1611235380000, "actualArrivalTime": 1611235560000, "showActualArrivalTime": True, "arrivalDelay": "+3", "scheduledDepartureTime": None, "actualDepartureTime": None, "showActualDepartureTime": None, "departureDelay": ''}, "track": None, "info": {"status": 0, "passed": False, "positionStatus": None, "distance": 0, "distanceFromStart": 0}, "delayReasons": None}, - {"station": {"evaNr": "8007140_00", "name": "Unteröwisheim Bf", "code": None, "geocoordinates": {"latitude": 49.146342, "longitude": 8.668986}}, "timetable": {"scheduledArrivalTime": 1611235440000, "actualArrivalTime": 1611235620000, "showActualArrivalTime": True, "arrivalDelay": "+3", "scheduledDepartureTime": None, "actualDepartureTime": None, "showActualDepartureTime": None, "departureDelay": ''}, "track": None, "info": {"status": 0, "passed": False, "positionStatus": None, "distance": 0, "distanceFromStart": 0}, "delayReasons": None}, - {"station": {"evaNr": "8007141_00", "name": "Oberöwisheim", "code": None, "geocoordinates": {"latitude": 49.14006, "longitude": 8.686232}}, "timetable": {"scheduledArrivalTime": 1611235560000, "actualArrivalTime": 1611235740000, "showActualArrivalTime": True, "arrivalDelay": "+3", "scheduledDepartureTime": None, "actualDepartureTime": None, "showActualDepartureTime": None, "departureDelay": ''}, "track": None, "info": {"status": 0, "passed": False, "positionStatus": None, "distance": 0, "distanceFromStart": 0}, "delayReasons": None}, - {"station": {"evaNr": "8007142_00", "name": "Münzesheim", "code": None, "geocoordinates": {"latitude": 49.12608, "longitude": 8.716007}}, "timetable": {"scheduledArrivalTime": 1611235740000, "actualArrivalTime": 1611235920000, "showActualArrivalTime": True, "arrivalDelay": "+3", "scheduledDepartureTime": None, "actualDepartureTime": None, "showActualDepartureTime": None, "departureDelay": ''}, "track": None, "info": {"status": 0, "passed": False, "positionStatus": None, "distance": 0, "distanceFromStart": 0}, "delayReasons": None}, - {"station": {"evaNr": "8007146_00", "name": "Münzesheim Ost", "code": None, "geocoordinates": {"latitude": 49.121492, "longitude": 8.726215}}, "timetable": {"scheduledArrivalTime": 1611235860000, "actualArrivalTime": 1611236100000, "showActualArrivalTime": True, "arrivalDelay": "+4", "scheduledDepartureTime": None, "actualDepartureTime": None, "showActualDepartureTime": None, "departureDelay": ''}, "track": None, "info": {"status": 0, "passed": False, "positionStatus": None, "distance": 0, "distanceFromStart": 0}, "delayReasons": None}, - {"station": {"evaNr": "8007143_00", "name": "Gochsheim (Baden)", "code": None, "geocoordinates": {"latitude": 49.109411, "longitude": 8.744695}}, "timetable": {"scheduledArrivalTime": 1611235980000, "actualArrivalTime": 1611236160000, "showActualArrivalTime": True, "arrivalDelay": "+3", "scheduledDepartureTime": None, "actualDepartureTime": None, "showActualDepartureTime": None, "departureDelay": ''}, "track": None, "info": {"status": 0, "passed": False, "positionStatus": None, "distance": 0, "distanceFromStart": 0}, "delayReasons": None}, - {"station": {"evaNr": "8007144_00", "name": "Bahnbrücken", "code": None, "geocoordinates": {"latitude": 49.119447, "longitude": 8.764854}}, "timetable": {"scheduledArrivalTime": 1611236160000, "actualArrivalTime": 1611236340000, "showActualArrivalTime": True, "arrivalDelay": "+3", "scheduledDepartureTime": None, "actualDepartureTime": None, "showActualDepartureTime": None, "departureDelay": ''}, "track": None, "info": {"status": 0, "passed": False, "positionStatus": None, "distance": 0, "distanceFromStart": 0}, "delayReasons": None}, - {"station": {"evaNr": "8007145_00", "name": "Menzingen (Baden)", "code": None, "geocoordinates": {"latitude": 49.136233, "longitude": 8.775067}}, "timetable": {"scheduledArrivalTime": 1611236280000, "actualArrivalTime": 1611236460000, "showActualArrivalTime": True, "arrivalDelay": "+3", "scheduledDepartureTime": None, "actualDepartureTime": None, "showActualDepartureTime": None, "departureDelay": ''}, "track": None, "info": {"status": 0, "passed": False, "positionStatus": None, "distance": 0, "distanceFromStart": 0}, "delayReasons": None} - ] - } - ], - "requestedEvaNr": "8000055_00" - } -} - - -DATA_ROOT = join(dirname(__file__), "sample_data") -SAMPLE_FILE_STATUS = join(DATA_ROOT, "status.json") -SAMPLE_FILE_TRIP = join(DATA_ROOT, "trip.json") - - -def load_from_record(filename): - with open(filename, "r", encoding="utf-8") as file: - return loads(file.read()) - - -def save_record(filename, content): - with open(filename, "w", encoding="utf-8") as f: - f.write(dumps(content, indent=4)) - - -class DynamicDataServer: - def __init__(self): - self._data_status = load_from_record(SAMPLE_FILE_STATUS) - self._data_trip = load_from_record(SAMPLE_FILE_TRIP) - # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # - self.eva_nrs: List[str] = [] - self.stations: Dict[str, Dict] = {} - self.velocity: int = 0 - self.position: int = 0 - self.geo_position: Tuple[float, float] = (0.0, 0.0) - # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # - self.init_data() - - def init_data(self): - pass - diff --git a/iceportal_apis/mocking/sample_data/status.json b/iceportal_apis/mocking/sample_data/status.json deleted file mode 100644 index f10af89..0000000 --- a/iceportal_apis/mocking/sample_data/status.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "connection": true, - "serviceLevel": "AVAILABLE_SERVICE", - "gpsStatus": "VALID", - "internet": "HIGH", - "latitude": 50.920622, - "longitude": 10.640545, - "tileY": 102, - "tileX": 74, - "series": "411", - "serverTime": 1634107110427, - "speed": 133, - "trainType": "ICE", - "tzn": "Tz1130", - "wagonClass": "FIRST", - "connectivity": { - "currentState": "WEAK", - "nextState": "HIGH", - "remainingTimeSeconds": 625 - }, - "bapInstalled": true -} \ No newline at end of file diff --git a/iceportal_apis/mocking/sample_data/trip.json b/iceportal_apis/mocking/sample_data/trip.json deleted file mode 100644 index b66427c..0000000 --- a/iceportal_apis/mocking/sample_data/trip.json +++ /dev/null @@ -1,463 +0,0 @@ -{ - "trip": { - "tripDate": "2020-10-31", - "trainType": "ICE", - "vzn": "881", - "actualPosition": 159781, - "distanceFromLastStop": 41632, - "totalDistance": 708799, - "stopInfo": { - "scheduledNext": "8002548_00", - "actualNext": "8002548_00", - "actualLast": "8002553_00", - "actualLastStarted": "8002553", - "finalStationName": "M\u00fcnchen Hbf", - "finalStationEvaNr": "8000261_00" - }, - "stops": [ - { - "station": { - "evaNr": "8002553_00", - "name": "Hamburg-Altona", - "code": null, - "geocoordinates": { - "latitude": 53.552695, - "longitude": 9.935175 - } - }, - "timetable": { - "scheduledArrivalTime": null, - "actualArrivalTime": null, - "showActualArrivalTime": null, - "arrivalDelay": "", - "scheduledDepartureTime": 1604148180000, - "actualDepartureTime": 1604148180000, - "showActualDepartureTime": true, - "departureDelay": "" - }, - "track": { - "scheduled": "11", - "actual": "11" - }, - "info": { - "status": 0, - "passed": true, - "positionStatus": "passed", - "distance": 0, - "distanceFromStart": 0 - }, - "delayReasons": null - }, - { - "station": { - "evaNr": "8002548_00", - "name": "Hamburg Dammtor", - "code": null, - "geocoordinates": { - "latitude": 53.560751, - "longitude": 9.989566 - } - }, - "timetable": { - "scheduledArrivalTime": 1604148720000, - "actualArrivalTime": 1604148840000, - "showActualArrivalTime": true, - "arrivalDelay": "+2", - "scheduledDepartureTime": 1604148780000, - "actualDepartureTime": 1604148900000, - "showActualDepartureTime": true, - "departureDelay": "+2" - }, - "track": { - "scheduled": "4", - "actual": "4" - }, - "info": { - "status": 0, - "passed": true, - "positionStatus": "passed", - "distance": 3704, - "distanceFromStart": 3704 - }, - "delayReasons": null - }, - { - "station": { - "evaNr": "8002549_00", - "name": "Hamburg Hbf", - "code": null, - "geocoordinates": { - "latitude": 53.552736, - "longitude": 10.006909 - } - }, - "timetable": { - "scheduledArrivalTime": 1604149080000, - "actualArrivalTime": 1604149140000, - "showActualArrivalTime": true, - "arrivalDelay": "+1", - "scheduledDepartureTime": 1604149260000, - "actualDepartureTime": 1604149260000, - "showActualDepartureTime": true, - "departureDelay": "" - }, - "track": { - "scheduled": "14", - "actual": "14" - }, - "info": { - "status": 0, - "passed": true, - "positionStatus": "passed", - "distance": 1452, - "distanceFromStart": 5156 - }, - "delayReasons": null - }, - { - "station": { - "evaNr": "8000147_00", - "name": "Hamburg-Harburg", - "code": null, - "geocoordinates": { - "latitude": 53.455908, - "longitude": 9.991701 - } - }, - "timetable": { - "scheduledArrivalTime": 1604149800000, - "actualArrivalTime": 1604149980000, - "showActualArrivalTime": true, - "arrivalDelay": "+3", - "scheduledDepartureTime": 1604149920000, - "actualDepartureTime": 1604150100000, - "showActualDepartureTime": true, - "departureDelay": "+3" - }, - "track": { - "scheduled": "4", - "actual": "4" - }, - "info": { - "status": 0, - "passed": true, - "positionStatus": "passed", - "distance": 10817, - "distanceFromStart": 15973 - }, - "delayReasons": null - }, - { - "station": { - "evaNr": "8000238_00", - "name": "L\u00fcneburg", - "code": null, - "geocoordinates": { - "latitude": 53.249656, - "longitude": 10.41989 - } - }, - "timetable": { - "scheduledArrivalTime": 1604150820000, - "actualArrivalTime": 1604151060000, - "showActualArrivalTime": true, - "arrivalDelay": "+4", - "scheduledDepartureTime": 1604150940000, - "actualDepartureTime": 1604151240000, - "showActualDepartureTime": true, - "departureDelay": "+5" - }, - "track": { - "scheduled": "1", - "actual": "1" - }, - "info": { - "status": 0, - "passed": true, - "positionStatus": "passed", - "distance": 36529, - "distanceFromStart": 52502 - }, - "delayReasons": null - }, - { - "station": { - "evaNr": "8000152_00", - "name": "Hannover Hbf", - "code": null, - "geocoordinates": { - "latitude": 52.376761, - "longitude": 9.741021 - } - }, - "timetable": { - "scheduledArrivalTime": 1604154120000, - "actualArrivalTime": 1604154120000, - "showActualArrivalTime": true, - "arrivalDelay": "", - "scheduledDepartureTime": 1604154360000, - "actualDepartureTime": 1604154360000, - "showActualDepartureTime": true, - "departureDelay": "" - }, - "track": { - "scheduled": "3", - "actual": "3" - }, - "info": { - "status": 0, - "passed": true, - "positionStatus": "departed", - "distance": 107279, - "distanceFromStart": 159781 - }, - "delayReasons": [ - { - "code": "38", - "text": "Technische St\u00f6rung an der Strecke" - } - ] - }, - { - "station": { - "evaNr": "8000128_00", - "name": "G\u00f6ttingen", - "code": null, - "geocoordinates": { - "latitude": 51.536815, - "longitude": 9.926072 - } - }, - "timetable": { - "scheduledArrivalTime": 1604156400000, - "actualArrivalTime": 1604156400000, - "showActualArrivalTime": true, - "arrivalDelay": "", - "scheduledDepartureTime": 1604156520000, - "actualDepartureTime": 1604156520000, - "showActualDepartureTime": true, - "departureDelay": "" - }, - "track": { - "scheduled": "10", - "actual": "10" - }, - "info": { - "status": 0, - "passed": false, - "positionStatus": "future", - "distance": 94281, - "distanceFromStart": 254062 - }, - "delayReasons": null - }, - { - "station": { - "evaNr": "8003200_00", - "name": "Kassel-Wilhelmsh\u00f6he", - "code": null, - "geocoordinates": { - "latitude": 51.313114, - "longitude": 9.446898 - } - }, - "timetable": { - "scheduledArrivalTime": 1604157660000, - "actualArrivalTime": 1604157660000, - "showActualArrivalTime": true, - "arrivalDelay": "", - "scheduledDepartureTime": 1604157780000, - "actualDepartureTime": 1604157900000, - "showActualDepartureTime": true, - "departureDelay": "+2" - }, - "track": { - "scheduled": "2", - "actual": "2" - }, - "info": { - "status": 0, - "passed": false, - "positionStatus": "future", - "distance": 41515, - "distanceFromStart": 295577 - }, - "delayReasons": null - }, - { - "station": { - "evaNr": "8000115_00", - "name": "Fulda", - "code": null, - "geocoordinates": { - "latitude": 50.554723, - "longitude": 9.683977 - } - }, - "timetable": { - "scheduledArrivalTime": 1604159640000, - "actualArrivalTime": 1604159640000, - "showActualArrivalTime": true, - "arrivalDelay": "", - "scheduledDepartureTime": 1604159760000, - "actualDepartureTime": 1604159760000, - "showActualDepartureTime": true, - "departureDelay": "" - }, - "track": { - "scheduled": "4", - "actual": "4" - }, - "info": { - "status": 0, - "passed": false, - "positionStatus": "future", - "distance": 85974, - "distanceFromStart": 381551 - }, - "delayReasons": null - }, - { - "station": { - "evaNr": "8000260_00", - "name": "W\u00fcrzburg Hbf", - "code": null, - "geocoordinates": { - "latitude": 49.801796, - "longitude": 9.93578 - } - }, - "timetable": { - "scheduledArrivalTime": 1604161740000, - "actualArrivalTime": 1604161740000, - "showActualArrivalTime": true, - "arrivalDelay": "", - "scheduledDepartureTime": 1604161860000, - "actualDepartureTime": 1604161860000, - "showActualDepartureTime": true, - "departureDelay": "" - }, - "track": { - "scheduled": "4", - "actual": "4" - }, - "info": { - "status": 0, - "passed": false, - "positionStatus": "future", - "distance": 85644, - "distanceFromStart": 467195 - }, - "delayReasons": null - }, - { - "station": { - "evaNr": "8000284_00", - "name": "N\u00fcrnberg Hbf", - "code": null, - "geocoordinates": { - "latitude": 49.445616, - "longitude": 11.082989 - } - }, - "timetable": { - "scheduledArrivalTime": 1604165040000, - "actualArrivalTime": 1604165040000, - "showActualArrivalTime": true, - "arrivalDelay": "", - "scheduledDepartureTime": 1604165220000, - "actualDepartureTime": 1604165340000, - "showActualDepartureTime": true, - "departureDelay": "+2" - }, - "track": { - "scheduled": "8", - "actual": "8" - }, - "info": { - "status": 0, - "passed": false, - "positionStatus": "future", - "distance": 91662, - "distanceFromStart": 558857 - }, - "delayReasons": null - }, - { - "station": { - "evaNr": "8000183_00", - "name": "Ingolstadt Hbf", - "code": null, - "geocoordinates": { - "latitude": 48.744541, - "longitude": 11.437337 - } - }, - "timetable": { - "scheduledArrivalTime": 1604167080000, - "actualArrivalTime": 1604167080000, - "showActualArrivalTime": true, - "arrivalDelay": "", - "scheduledDepartureTime": 1604167260000, - "actualDepartureTime": 1604167260000, - "showActualDepartureTime": true, - "departureDelay": "" - }, - "track": { - "scheduled": "3", - "actual": "3" - }, - "info": { - "status": 0, - "passed": false, - "positionStatus": "future", - "distance": 82137, - "distanceFromStart": 640994 - }, - "delayReasons": null - }, - { - "station": { - "evaNr": "8000261_00", - "name": "M\u00fcnchen Hbf", - "code": null, - "geocoordinates": { - "latitude": 48.140232, - "longitude": 11.558335 - } - }, - "timetable": { - "scheduledArrivalTime": 1604169720000, - "actualArrivalTime": 1604169720000, - "showActualArrivalTime": true, - "arrivalDelay": "", - "scheduledDepartureTime": null, - "actualDepartureTime": null, - "showActualDepartureTime": null, - "departureDelay": "" - }, - "track": { - "scheduled": "18", - "actual": "18" - }, - "info": { - "status": 0, - "passed": false, - "positionStatus": "future", - "distance": 67805, - "distanceFromStart": 708799 - }, - "delayReasons": null - } - ] - }, - "connection": null, - "selectedRoute": { - "conflictInfo": { - "status": "NO_CONFLICT", - "text": null - }, - "mobility": null - }, - "active": null -} \ No newline at end of file diff --git a/iceportal_apis/types.py b/iceportal_apis/types.py index 6985ef1..30df5e6 100644 --- a/iceportal_apis/types.py +++ b/iceportal_apis/types.py @@ -25,7 +25,8 @@ class TrainType(Enum): @unique class Internet(Enum): - UNKNOWN = None # NO_INFO + UNKNOWN = None # Client can't tell + NO_INFO = -1 # Train can't tell NO_INTERNET = 0 UNSTABLE = 1 WEAK = 2