1 """Support for Nederlandse Spoorwegen public transport."""
3 from __future__
import annotations
5 from datetime
import datetime, timedelta
9 from ns_api
import RequestParametersError
11 import voluptuous
as vol
14 PLATFORM_SCHEMA
as SENSOR_PLATFORM_SCHEMA,
25 _LOGGER = logging.getLogger(__name__)
27 CONF_ROUTES =
"routes"
36 ROUTE_SCHEMA = vol.Schema(
38 vol.Required(CONF_NAME): cv.string,
39 vol.Required(CONF_FROM): cv.string,
40 vol.Required(CONF_TO): cv.string,
41 vol.Optional(CONF_VIA): cv.string,
42 vol.Optional(CONF_TIME): cv.time,
46 ROUTES_SCHEMA = vol.All(cv.ensure_list, [ROUTE_SCHEMA])
48 PLATFORM_SCHEMA = SENSOR_PLATFORM_SCHEMA.extend(
49 {vol.Required(CONF_API_KEY): cv.string, vol.Optional(CONF_ROUTES): ROUTES_SCHEMA}
56 add_entities: AddEntitiesCallback,
57 discovery_info: DiscoveryInfoType |
None =
None,
59 """Set up the departure sensor."""
61 nsapi = ns_api.NSAPI(config[CONF_API_KEY])
64 stations = nsapi.get_stations()
66 requests.exceptions.ConnectionError,
67 requests.exceptions.HTTPError,
69 _LOGGER.error(
"Could not connect to the internet: %s", error)
70 raise PlatformNotReady
from error
71 except RequestParametersError
as error:
72 _LOGGER.error(
"Could not fetch stations, please check configuration: %s", error)
76 for departure
in config.get(CONF_ROUTES, {}):
79 [departure.get(CONF_FROM), departure.get(CONF_VIA), departure.get(CONF_TO)],
85 departure.get(CONF_NAME),
86 departure.get(CONF_FROM),
87 departure.get(CONF_TO),
88 departure.get(CONF_VIA),
89 departure.get(CONF_TIME),
96 """Verify the existence of the given station codes."""
97 for station
in given_stations:
100 if not any(s.code == station.upper()
for s
in stations):
101 _LOGGER.warning(
"Station '%s' is not a valid station", station)
107 """Implementation of a NS Departure Sensor."""
109 _attr_attribution =
"Data provided by NS"
110 _attr_icon =
"mdi:train"
112 def __init__(self, nsapi, name, departure, heading, via, time):
113 """Initialize the sensor."""
125 """Return the name of the sensor."""
126 return self.
_name_name
130 """Return the next departure time."""
135 """Return the state attributes."""
139 if self.
_trips_trips[0].trip_parts:
140 route = [self.
_trips_trips[0].departure]
141 route.extend(k.destination
for k
in self.
_trips_trips[0].trip_parts)
145 "going": self.
_trips_trips[0].going,
146 "departure_time_planned":
None,
147 "departure_time_actual":
None,
148 "departure_delay":
False,
149 "departure_platform_planned": self.
_trips_trips[0].departure_platform_planned,
150 "departure_platform_actual": self.
_trips_trips[0].departure_platform_actual,
151 "arrival_time_planned":
None,
152 "arrival_time_actual":
None,
153 "arrival_delay":
False,
154 "arrival_platform_planned": self.
_trips_trips[0].arrival_platform_planned,
155 "arrival_platform_actual": self.
_trips_trips[0].arrival_platform_actual,
157 "status": self.
_trips_trips[0].status.lower(),
158 "transfers": self.
_trips_trips[0].nr_transfers,
164 if self.
_trips_trips[0].departure_time_planned
is not None:
165 attributes[
"departure_time_planned"] = self.
_trips_trips[
167 ].departure_time_planned.strftime(
"%H:%M")
170 if self.
_trips_trips[0].departure_time_actual
is not None:
171 attributes[
"departure_time_actual"] = self.
_trips_trips[
173 ].departure_time_actual.strftime(
"%H:%M")
177 attributes[
"departure_time_planned"]
178 and attributes[
"departure_time_actual"]
179 and attributes[
"departure_time_planned"]
180 != attributes[
"departure_time_actual"]
182 attributes[
"departure_delay"] =
True
185 if self.
_trips_trips[0].arrival_time_planned
is not None:
186 attributes[
"arrival_time_planned"] = self.
_trips_trips[
188 ].arrival_time_planned.strftime(
"%H:%M")
191 if self.
_trips_trips[0].arrival_time_actual
is not None:
192 attributes[
"arrival_time_actual"] = self.
_trips_trips[
194 ].arrival_time_actual.strftime(
"%H:%M")
198 attributes[
"arrival_time_planned"]
199 and attributes[
"arrival_time_actual"]
200 and attributes[
"arrival_time_planned"] != attributes[
"arrival_time_actual"]
202 attributes[
"arrival_delay"] =
True
205 if len(self.
_trips_trips) > 1:
206 if self.
_trips_trips[1].departure_time_actual
is not None:
207 attributes[
"next"] = self.
_trips_trips[1].departure_time_actual.strftime(
210 elif self.
_trips_trips[1].departure_time_planned
is not None:
211 attributes[
"next"] = self.
_trips_trips[1].departure_time_planned.strftime(
217 @Throttle(MIN_TIME_BETWEEN_UPDATES)
219 """Get the trip information."""
222 if self.
_time_time
and (
235 .replace(hour=self.
_time_time.hour, minute=self.
_time_time.minute)
236 .strftime(
"%d-%m-%Y %H:%M")
239 trip_time = datetime.now().strftime(
"%d-%m-%Y %H:%M")
246 if self.
_trips_trips[0].departure_time_actual
is None:
247 planned_time = self.
_trips_trips[0].departure_time_planned
248 self.
_state_state = planned_time.strftime(
"%H:%M")
250 actual_time = self.
_trips_trips[0].departure_time_actual
251 self.
_state_state = actual_time.strftime(
"%H:%M")
253 requests.exceptions.ConnectionError,
254 requests.exceptions.HTTPError,
256 _LOGGER.error(
"Couldn't fetch trip info: %s", error)
def extra_state_attributes(self)
def __init__(self, nsapi, name, departure, heading, via, time)
def add_entities(account, async_add_entities, tracked)
None setup_platform(HomeAssistant hass, ConfigType config, AddEntitiesCallback add_entities, DiscoveryInfoType|None discovery_info=None)
def valid_stations(stations, given_stations)
bool time(HomeAssistant hass, dt_time|str|None before=None, dt_time|str|None after=None, str|Container[str]|None weekday=None)