1 """Support for Rejseplanen information from rejseplanen.dk.
3 For more info on the API see:
4 https://help.rejseplanen.dk/hc/en-us/articles/214174465-Rejseplanen-s-API
7 from __future__
import annotations
9 from contextlib
import suppress
10 from datetime
import datetime, timedelta
12 from operator
import itemgetter
15 import voluptuous
as vol
18 PLATFORM_SCHEMA
as SENSOR_PLATFORM_SCHEMA,
28 _LOGGER = logging.getLogger(__name__)
30 ATTR_STOP_ID =
"stop_id"
31 ATTR_STOP_NAME =
"stop"
34 ATTR_DIRECTION =
"direction"
35 ATTR_FINAL_STOP =
"final_stop"
36 ATTR_DUE_IN =
"due_in"
37 ATTR_DUE_AT =
"due_at"
38 ATTR_SCHEDULED_AT =
"scheduled_at"
39 ATTR_REAL_TIME_AT =
"real_time_at"
41 ATTR_NEXT_UP =
"next_departures"
43 CONF_STOP_ID =
"stop_id"
45 CONF_DIRECTION =
"direction"
46 CONF_DEPARTURE_TYPE =
"departure_type"
48 DEFAULT_NAME =
"Next departure"
53 BUS_TYPES = [
"BUS",
"EXB",
"TB"]
54 TRAIN_TYPES = [
"LET",
"S",
"REG",
"IC",
"LYN",
"TOG"]
57 PLATFORM_SCHEMA = SENSOR_PLATFORM_SCHEMA.extend(
59 vol.Required(CONF_STOP_ID): cv.string,
60 vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
61 vol.Optional(CONF_ROUTE, default=[]): vol.All(cv.ensure_list, [cv.string]),
62 vol.Optional(CONF_DIRECTION, default=[]): vol.All(cv.ensure_list, [cv.string]),
63 vol.Optional(CONF_DEPARTURE_TYPE, default=[]): vol.All(
64 cv.ensure_list, [vol.In([*BUS_TYPES, *TRAIN_TYPES, *METRO_TYPES])]
71 """Get the time in minutes from a timestamp.
73 The timestamp should be in the format day.month.year hour:minute
75 diff = datetime.strptime(timestamp,
"%d.%m.%y %H:%M") - dt_util.now().replace(
79 return int(diff.total_seconds() // 60)
85 add_devices: AddEntitiesCallback,
86 discovery_info: DiscoveryInfoType |
None =
None,
88 """Set up the Rejseplanen transport sensor."""
89 name = config[CONF_NAME]
90 stop_id = config[CONF_STOP_ID]
91 route = config.get(CONF_ROUTE)
92 direction = config[CONF_DIRECTION]
93 departure_type = config[CONF_DEPARTURE_TYPE]
102 """Implementation of Rejseplanen transport sensor."""
104 _attr_attribution =
"Data provided by rejseplanen.dk"
105 _attr_icon =
"mdi:bus"
107 def __init__(self, data, stop_id, route, direction, name):
108 """Initialize the sensor."""
118 """Return the name of the sensor."""
119 return self.
_name_name
123 """Return the state of the sensor."""
128 """Return the state attributes."""
130 return {ATTR_STOP_ID: self.
_stop_id_stop_id}
133 if len(self.
_times_times) > 1:
134 next_up = self.
_times_times[1:]
137 ATTR_NEXT_UP: next_up,
138 ATTR_STOP_ID: self.
_stop_id_stop_id,
141 if self.
_times_times[0]
is not None:
142 attributes.update(self.
_times_times[0])
148 """Return the unit this state is expressed in."""
149 return UnitOfTime.MINUTES
152 """Get the latest data from rejseplanen.dk and update the states."""
159 with suppress(TypeError):
164 """The Class for handling the data retrieval."""
166 def __init__(self, stop_id, route, direction, departure_type):
167 """Initialize the data object."""
175 """Get the latest data from rejseplanen."""
178 def intersection(lst1, lst2):
179 """Return items contained in both lists."""
180 return list(set(lst1) & set(lst2))
184 use_train = all_types
or bool(intersection(TRAIN_TYPES, self.
departure_typedeparture_type))
185 use_bus = all_types
or bool(intersection(BUS_TYPES, self.
departure_typedeparture_type))
186 use_metro = all_types
or bool(intersection(METRO_TYPES, self.
departure_typedeparture_type))
189 results = rjpl.departureBoard(
196 except rjpl.rjplAPIError
as error:
197 _LOGGER.debug(
"API returned error: %s", error)
199 except (rjpl.rjplConnectionError, rjpl.rjplHTTPError):
200 _LOGGER.debug(
"Error occurred while connecting to the API")
204 results = [d
for d
in results
if "cancelled" not in d]
206 results = [d
for d
in results
if d[
"name"]
in self.
routeroute]
208 results = [d
for d
in results
if d[
"direction"]
in self.
directiondirection]
210 results = [d
for d
in results
if d[
"type"]
in self.
departure_typedeparture_type]
213 route = item.get(
"name")
215 scheduled_date = item.get(
"date")
216 scheduled_time = item.get(
"time")
217 real_time_date = due_at_date = item.get(
"rtDate")
218 real_time_time = due_at_time = item.get(
"rtTime")
220 if due_at_date
is None:
221 due_at_date = scheduled_date
222 if due_at_time
is None:
223 due_at_time = scheduled_time
226 due_at_date
is not None
227 and due_at_time
is not None
228 and route
is not None
230 due_at = f
"{due_at_date} {due_at_time}"
231 scheduled_at = f
"{scheduled_date} {scheduled_time}"
234 ATTR_DIRECTION: item.get(
"direction"),
237 ATTR_FINAL_STOP: item.get(
"finalStop"),
239 ATTR_SCHEDULED_AT: scheduled_at,
240 ATTR_STOP_NAME: item.get(
"stop"),
241 ATTR_TYPE: item.get(
"type"),
244 if real_time_date
is not None and real_time_time
is not None:
245 departure_data[ATTR_REAL_TIME_AT] = (
246 f
"{real_time_date} {real_time_time}"
248 if item.get(
"rtTrack")
is not None:
249 departure_data[ATTR_TRACK] = item.get(
"rtTrack")
251 self.
infoinfo.append(departure_data)
253 if not self.
infoinfo:
254 _LOGGER.debug(
"No departures with given parameters")
257 self.
infoinfo = sorted(self.
infoinfo, key=itemgetter(ATTR_DUE_IN))
def __init__(self, stop_id, route, direction, departure_type)
def native_unit_of_measurement(self)
def extra_state_attributes(self)
def __init__(self, data, stop_id, route, direction, name)
def due_in_minutes(timestamp)
None setup_platform(HomeAssistant hass, ConfigType config, AddEntitiesCallback add_devices, DiscoveryInfoType|None discovery_info=None)