Home Assistant Unofficial Reference 2024.12.1
sensor.py
Go to the documentation of this file.
1 """NextBus sensor."""
2 
3 from __future__ import annotations
4 
5 import logging
6 from typing import cast
7 
8 from homeassistant.components.sensor import SensorDeviceClass, SensorEntity
9 from homeassistant.config_entries import ConfigEntry
10 from homeassistant.const import CONF_NAME, CONF_STOP
11 from homeassistant.core import HomeAssistant, callback
12 from homeassistant.helpers.entity_platform import AddEntitiesCallback
13 from homeassistant.helpers.update_coordinator import CoordinatorEntity
14 from homeassistant.util.dt import utc_from_timestamp
15 
16 from .const import CONF_AGENCY, CONF_ROUTE, DOMAIN
17 from .coordinator import NextBusDataUpdateCoordinator
18 from .util import maybe_first
19 
20 _LOGGER = logging.getLogger(__name__)
21 
22 
24  hass: HomeAssistant,
25  config: ConfigEntry,
26  async_add_entities: AddEntitiesCallback,
27 ) -> None:
28  """Load values from configuration and initialize the platform."""
29  _LOGGER.debug(config.data)
30  entry_agency = config.data[CONF_AGENCY]
31  entry_stop = config.data[CONF_STOP]
32  coordinator_key = f"{entry_agency}-{entry_stop}"
33 
34  coordinator: NextBusDataUpdateCoordinator = hass.data[DOMAIN].get(coordinator_key)
35 
37  (
39  coordinator,
40  cast(str, config.unique_id),
41  config.data[CONF_AGENCY],
42  config.data[CONF_ROUTE],
43  config.data[CONF_STOP],
44  config.data.get(CONF_NAME) or config.title,
45  ),
46  ),
47  )
48 
49 
51  CoordinatorEntity[NextBusDataUpdateCoordinator], SensorEntity
52 ):
53  """Sensor class that displays upcoming NextBus times.
54 
55  To function, this requires knowing the agency tag as well as the tags for
56  both the route and the stop.
57 
58  This is possibly a little convoluted to provide as it requires making a
59  request to the service to get these values. Perhaps it can be simplified in
60  the future using fuzzy logic and matching.
61  """
62 
63  _attr_device_class = SensorDeviceClass.TIMESTAMP
64  _attr_translation_key = "nextbus"
65 
66  def __init__(
67  self,
68  coordinator: NextBusDataUpdateCoordinator,
69  unique_id: str,
70  agency: str,
71  route: str,
72  stop: str,
73  name: str,
74  ) -> None:
75  """Initialize sensor with all required config."""
76  super().__init__(coordinator)
77  self.agencyagency = agency
78  self.routeroute = route
79  self.stopstop = stop
80  self._attr_extra_state_attributes: dict[str, str] = {
81  "agency": agency,
82  "route": route,
83  "stop": stop,
84  }
85  self._attr_unique_id_attr_unique_id = unique_id
86  self._attr_name_attr_name = name
87 
88  def _log_debug(self, message, *args):
89  """Log debug message with prefix."""
90  msg = f"{self.agency}:{self.route}:{self.stop}:{message}"
91  _LOGGER.debug(msg, *args)
92 
93  def _log_err(self, message, *args):
94  """Log error message with prefix."""
95  msg = f"{self.agency}:{self.route}:{self.stop}:{message}"
96  _LOGGER.error(msg, *args)
97 
98  async def async_added_to_hass(self) -> None:
99  """Read data from coordinator after adding to hass."""
100  self._handle_coordinator_update_handle_coordinator_update()
101  await super().async_added_to_hass()
102 
103  @callback
104  def _handle_coordinator_update(self) -> None:
105  """Update sensor with new departures times."""
106  results = self.coordinator.get_prediction_data(self.stopstop, self.routeroute)
107 
108  self._log_debug_log_debug("Predictions results: %s", results)
109 
110  if not results:
111  self._log_err_log_err("Error getting predictions: %s", str(results))
112  self._attr_native_value_attr_native_value = None
113  self._attr_extra_state_attributes.pop("upcoming", None)
114  return
115 
116  # Set detailed attributes
117  self._attr_extra_state_attributes.update(
118  {
119  "route": str(results["route"]["title"]),
120  "stop": str(results["stop"]["name"]),
121  }
122  )
123 
124  # Chain all predictions together
125  predictions = results["values"]
126 
127  # Short circuit if we don't have any actual bus predictions
128  if not predictions:
129  self._log_debug_log_debug("No upcoming predictions available")
130  self._attr_native_value_attr_native_value = None
131  self._attr_extra_state_attributes["upcoming"] = "No upcoming predictions"
132  else:
133  # Generate list of upcoming times
134  self._attr_extra_state_attributes["upcoming"] = ", ".join(
135  str(p["minutes"]) for p in predictions
136  )
137 
138  latest_prediction = maybe_first(predictions)
139  self._attr_native_value_attr_native_value = utc_from_timestamp(
140  latest_prediction["timestamp"] / 1000
141  )
142 
143  self.async_write_ha_stateasync_write_ha_state()
dict[str, Any]|None get_prediction_data(self, str stop_id, str route_id)
Definition: coordinator.py:44
None __init__(self, NextBusDataUpdateCoordinator coordinator, str unique_id, str agency, str route, str stop, str name)
Definition: sensor.py:74
web.Response get(self, web.Request request, str config_key)
Definition: view.py:88
IssData update(pyiss.ISS iss)
Definition: __init__.py:33
None async_setup_entry(HomeAssistant hass, ConfigEntry config, AddEntitiesCallback async_add_entities)
Definition: sensor.py:27
Any maybe_first(list[Any]|None maybe_list)
Definition: util.py:21