Home Assistant Unofficial Reference 2024.12.1
sensor.py
Go to the documentation of this file.
1 """Sensor platform for hvv."""
2 
3 from datetime import timedelta
4 import logging
5 from typing import Any
6 
7 from aiohttp import ClientConnectorError
8 from pygti.exceptions import InvalidAuth
9 
10 from homeassistant.components.sensor import SensorDeviceClass, SensorEntity
11 from homeassistant.config_entries import ConfigEntry
12 from homeassistant.const import ATTR_ID, CONF_OFFSET
13 from homeassistant.core import HomeAssistant
14 from homeassistant.helpers import aiohttp_client
15 from homeassistant.helpers.device_registry import DeviceEntryType, DeviceInfo
16 from homeassistant.helpers.entity_platform import AddEntitiesCallback
17 from homeassistant.util import Throttle
18 from homeassistant.util.dt import get_time_zone, utcnow
19 
20 from .const import ATTRIBUTION, CONF_REAL_TIME, CONF_STATION, DOMAIN, MANUFACTURER
21 
22 MIN_TIME_BETWEEN_UPDATES = timedelta(minutes=1)
23 MAX_LIST = 20
24 MAX_TIME_OFFSET = 360
25 
26 ATTR_DEPARTURE = "departure"
27 ATTR_LINE = "line"
28 ATTR_ORIGIN = "origin"
29 ATTR_DIRECTION = "direction"
30 ATTR_TYPE = "type"
31 ATTR_DELAY = "delay"
32 ATTR_NEXT = "next"
33 ATTR_CANCELLED = "cancelled"
34 ATTR_EXTRA = "extra"
35 
36 PARALLEL_UPDATES = 0
37 BERLIN_TIME_ZONE = get_time_zone("Europe/Berlin")
38 
39 _LOGGER = logging.getLogger(__name__)
40 
41 
43  hass: HomeAssistant,
44  config_entry: ConfigEntry,
45  async_add_entities: AddEntitiesCallback,
46 ) -> None:
47  """Set up the sensor platform."""
48  hub = hass.data[DOMAIN][config_entry.entry_id]
49 
50  session = aiohttp_client.async_get_clientsession(hass)
51 
52  sensor = HVVDepartureSensor(hass, config_entry, session, hub)
53  async_add_entities([sensor], True)
54 
55 
57  """HVVDepartureSensor class."""
58 
59  _attr_attribution = ATTRIBUTION
60  _attr_device_class = SensorDeviceClass.TIMESTAMP
61  _attr_translation_key = "departures"
62  _attr_has_entity_name = True
63  _attr_available = False
64 
65  def __init__(self, hass, config_entry, session, hub):
66  """Initialize."""
67  self.config_entryconfig_entry = config_entry
68  self.station_namestation_name = self.config_entryconfig_entry.data[CONF_STATION]["name"]
69  self._last_error_last_error = None
70  self._attr_extra_state_attributes_attr_extra_state_attributes = {}
71 
72  self.gtigti = hub.gti
73 
74  station_id = config_entry.data[CONF_STATION]["id"]
75  station_type = config_entry.data[CONF_STATION]["type"]
76  self._attr_unique_id_attr_unique_id = f"{config_entry.entry_id}-{station_id}-{station_type}"
77  self._attr_device_info_attr_device_info = DeviceInfo(
78  entry_type=DeviceEntryType.SERVICE,
79  identifiers={
80  (
81  DOMAIN,
82  config_entry.entry_id,
83  config_entry.data[CONF_STATION]["id"],
84  config_entry.data[CONF_STATION]["type"],
85  )
86  },
87  manufacturer=MANUFACTURER,
88  name=config_entry.data[CONF_STATION]["name"],
89  )
90 
91  @Throttle(MIN_TIME_BETWEEN_UPDATES)
92  async def async_update(self, **kwargs: Any) -> None:
93  """Update the sensor."""
94  departure_time = utcnow() + timedelta(
95  minutes=self.config_entryconfig_entry.options.get(CONF_OFFSET, 0)
96  )
97 
98  departure_time_tz_berlin = departure_time.astimezone(BERLIN_TIME_ZONE)
99 
100  station = self.config_entryconfig_entry.data[CONF_STATION]
101 
102  payload = {
103  "station": {"id": station["id"], "type": station["type"]},
104  "time": {
105  "date": departure_time_tz_berlin.strftime("%d.%m.%Y"),
106  "time": departure_time_tz_berlin.strftime("%H:%M"),
107  },
108  "maxList": MAX_LIST,
109  "maxTimeOffset": MAX_TIME_OFFSET,
110  "useRealtime": self.config_entryconfig_entry.options.get(CONF_REAL_TIME, False),
111  }
112 
113  if "filter" in self.config_entryconfig_entry.options:
114  payload.update({"filter": self.config_entryconfig_entry.options["filter"]})
115 
116  try:
117  data = await self.gtigti.departureList(payload)
118  except InvalidAuth as error:
119  if self._last_error_last_error != InvalidAuth:
120  _LOGGER.error("Authentication failed: %r", error)
121  self._last_error_last_error = InvalidAuth
122  self._attr_available_attr_available_attr_available = False
123  except ClientConnectorError as error:
124  if self._last_error_last_error != ClientConnectorError:
125  _LOGGER.warning("Network unavailable: %r", error)
126  self._last_error_last_error = ClientConnectorError
127  self._attr_available_attr_available_attr_available = False
128  except Exception as error: # noqa: BLE001
129  if self._last_error_last_error != error:
130  _LOGGER.error("Error occurred while fetching data: %r", error)
131  self._last_error_last_error = error
132  self._attr_available_attr_available_attr_available = False
133 
134  if not (data["returnCode"] == "OK" and data.get("departures")):
135  self._attr_available_attr_available_attr_available = False
136  return
137 
138  if self._last_error_last_error == ClientConnectorError:
139  _LOGGER.debug("Network available again")
140 
141  self._last_error_last_error = None
142 
143  departure = data["departures"][0]
144  line = departure["line"]
145  delay = departure.get("delay", 0)
146  cancelled = departure.get("cancelled", False)
147  extra = departure.get("extra", False)
148  self._attr_available_attr_available_attr_available = True
149  self._attr_native_value_attr_native_value = (
150  departure_time
151  + timedelta(minutes=departure["timeOffset"])
152  + timedelta(seconds=delay)
153  )
154 
155  self._attr_extra_state_attributes_attr_extra_state_attributes.update(
156  {
157  ATTR_LINE: line["name"],
158  ATTR_ORIGIN: line["origin"],
159  ATTR_DIRECTION: line["direction"],
160  ATTR_TYPE: line["type"]["shortInfo"],
161  ATTR_ID: line["id"],
162  ATTR_DELAY: delay,
163  ATTR_CANCELLED: cancelled,
164  ATTR_EXTRA: extra,
165  }
166  )
167 
168  departures = []
169  for departure in data["departures"]:
170  line = departure["line"]
171  delay = departure.get("delay", 0)
172  cancelled = departure.get("cancelled", False)
173  extra = departure.get("extra", False)
174  departures.append(
175  {
176  ATTR_DEPARTURE: departure_time
177  + timedelta(minutes=departure["timeOffset"])
178  + timedelta(seconds=delay),
179  ATTR_LINE: line["name"],
180  ATTR_ORIGIN: line["origin"],
181  ATTR_DIRECTION: line["direction"],
182  ATTR_TYPE: line["type"]["shortInfo"],
183  ATTR_ID: line["id"],
184  ATTR_DELAY: delay,
185  ATTR_CANCELLED: cancelled,
186  ATTR_EXTRA: extra,
187  }
188  )
189  self._attr_extra_state_attributes_attr_extra_state_attributes[ATTR_NEXT] = departures
def __init__(self, hass, config_entry, session, hub)
Definition: sensor.py:65
None async_setup_entry(HomeAssistant hass, ConfigEntry config_entry, AddEntitiesCallback async_add_entities)
Definition: sensor.py:46
IssData update(pyiss.ISS iss)
Definition: __init__.py:33
zoneinfo.ZoneInfo|None get_time_zone(str time_zone_str)
Definition: dt.py:98