Home Assistant Unofficial Reference 2024.12.1
coordinator.py
Go to the documentation of this file.
1 """DataUpdateCoordinator for the Trafikverket Train integration."""
2 
3 from __future__ import annotations
4 
5 from dataclasses import dataclass
6 from datetime import datetime, time, timedelta
7 import logging
8 from typing import TYPE_CHECKING
9 
10 from pytrafikverket import TrafikverketTrain
11 from pytrafikverket.exceptions import (
12  InvalidAuthentication,
13  MultipleTrainStationsFound,
14  NoTrainAnnouncementFound,
15  NoTrainStationFound,
16  UnknownError,
17 )
18 from pytrafikverket.models import StationInfoModel, TrainStopModel
19 
20 from homeassistant.const import CONF_API_KEY, CONF_WEEKDAY
21 from homeassistant.core import HomeAssistant
22 from homeassistant.exceptions import ConfigEntryAuthFailed
23 from homeassistant.helpers.aiohttp_client import async_get_clientsession
24 from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
25 from homeassistant.util import dt as dt_util
26 
27 from .const import CONF_FILTER_PRODUCT, CONF_FROM, CONF_TIME, CONF_TO, DOMAIN
28 from .util import next_departuredate
29 
30 if TYPE_CHECKING:
31  from . import TVTrainConfigEntry
32 
33 
34 @dataclass
35 class TrainData:
36  """Dataclass for Trafikverket Train data."""
37 
38  departure_time: datetime | None
39  departure_state: str
40  cancelled: bool | None
41  delayed_time: int | None
42  planned_time: datetime | None
43  estimated_time: datetime | None
44  actual_time: datetime | None
45  other_info: str | None
46  deviation: str | None
47  product_filter: str | None
48  departure_time_next: datetime | None
49  departure_time_next_next: datetime | None
50 
51 
52 _LOGGER = logging.getLogger(__name__)
53 TIME_BETWEEN_UPDATES = timedelta(minutes=5)
54 
55 
56 def _get_as_utc(date_value: datetime | None) -> datetime | None:
57  """Return utc datetime or None."""
58  if date_value:
59  return dt_util.as_utc(date_value)
60  return None
61 
62 
63 def _get_as_joined(information: list[str] | None) -> str | None:
64  """Return joined information or None."""
65  if information:
66  return ", ".join(information)
67  return None
68 
69 
71  """A Trafikverket Data Update Coordinator."""
72 
73  config_entry: TVTrainConfigEntry
74  from_station: StationInfoModel
75  to_station: StationInfoModel
76 
77  def __init__(self, hass: HomeAssistant) -> None:
78  """Initialize the Trafikverket coordinator."""
79  super().__init__(
80  hass,
81  _LOGGER,
82  name=DOMAIN,
83  update_interval=TIME_BETWEEN_UPDATES,
84  )
85  self._train_api_train_api = TrafikverketTrain(
86  async_get_clientsession(hass), self.config_entryconfig_entry.data[CONF_API_KEY]
87  )
88  self._time: time | None = dt_util.parse_time(self.config_entryconfig_entry.data[CONF_TIME])
89  self._weekdays: list[str] = self.config_entryconfig_entry.data[CONF_WEEKDAY]
90  self._filter_product: str | None = self.config_entryconfig_entry.options.get(
91  CONF_FILTER_PRODUCT
92  )
93 
94  async def _async_setup(self) -> None:
95  """Initiate stations."""
96  try:
97  self.to_stationto_station = await self._train_api_train_api.async_search_train_station(
98  self.config_entryconfig_entry.data[CONF_TO]
99  )
100  self.from_stationfrom_station = await self._train_api_train_api.async_search_train_station(
101  self.config_entryconfig_entry.data[CONF_FROM]
102  )
103  except InvalidAuthentication as error:
104  raise ConfigEntryAuthFailed from error
105  except (NoTrainStationFound, MultipleTrainStationsFound) as error:
106  raise UpdateFailed(
107  f"Problem when trying station {self.config_entry.data[CONF_FROM]} to"
108  f" {self.config_entry.data[CONF_TO]}. Error: {error} "
109  ) from error
110 
111  async def _async_update_data(self) -> TrainData:
112  """Fetch data from Trafikverket."""
113 
114  when = dt_util.now()
115  state: TrainStopModel | None = None
116  states: list[TrainStopModel] | None = None
117  if self._time:
118  departure_day = next_departuredate(self._weekdays)
119  when = datetime.combine(
120  departure_day,
121  self._time,
122  dt_util.get_default_time_zone(),
123  )
124  try:
125  if self._time:
126  state = await self._train_api_train_api.async_get_train_stop(
127  self.from_stationfrom_station, self.to_stationto_station, when, self._filter_product
128  )
129  else:
130  states = await self._train_api_train_api.async_get_next_train_stops(
131  self.from_stationfrom_station,
132  self.to_stationto_station,
133  when,
134  self._filter_product,
135  number_of_stops=3,
136  )
137  except InvalidAuthentication as error:
138  raise ConfigEntryAuthFailed from error
139  except (
140  NoTrainAnnouncementFound,
141  UnknownError,
142  ) as error:
143  raise UpdateFailed(
144  f"Train departure {when} encountered a problem: {error}"
145  ) from error
146 
147  depart_next = None
148  depart_next_next = None
149  if not state and states:
150  state = states[0]
151  depart_next = (
152  states[1].advertised_time_at_location if len(states) > 1 else None
153  )
154  depart_next_next = (
155  states[2].advertised_time_at_location if len(states) > 2 else None
156  )
157 
158  if not state:
159  raise UpdateFailed("Could not find any departures")
160 
161  departure_time = state.advertised_time_at_location
162  if state.estimated_time_at_location:
163  departure_time = state.estimated_time_at_location
164  elif state.time_at_location:
165  departure_time = state.time_at_location
166 
167  delay_time = state.get_delay_time()
168 
169  return TrainData(
170  departure_time=_get_as_utc(departure_time),
171  departure_state=state.get_state().value,
172  cancelled=state.canceled,
173  delayed_time=delay_time.seconds if delay_time else None,
174  planned_time=_get_as_utc(state.advertised_time_at_location),
175  estimated_time=_get_as_utc(state.estimated_time_at_location),
176  actual_time=_get_as_utc(state.time_at_location),
177  other_info=_get_as_joined(state.other_information),
178  deviation=_get_as_joined(state.deviations),
179  product_filter=self._filter_product,
180  departure_time_next=_get_as_utc(depart_next),
181  departure_time_next_next=_get_as_utc(depart_next_next),
182  )
datetime|None _get_as_utc(datetime|None date_value)
Definition: coordinator.py:56
str|None _get_as_joined(list[str]|None information)
Definition: coordinator.py:63
aiohttp.ClientSession async_get_clientsession(HomeAssistant hass, bool verify_ssl=True, socket.AddressFamily family=socket.AF_UNSPEC, ssl_util.SSLCipherList ssl_cipher=ssl_util.SSLCipherList.PYTHON_DEFAULT)