Home Assistant Unofficial Reference 2024.12.1
sensor.py
Go to the documentation of this file.
1 """Support for Dublin RTPI information from data.dublinked.ie.
2 
3 For more info on the API see :
4 https://data.gov.ie/dataset/real-time-passenger-information-rtpi-for-dublin-bus-bus-eireann-luas-and-irish-rail/resource/4b9f2c4f-6bf5-4958-a43a-f12dab04cf61
5 """
6 
7 from __future__ import annotations
8 
9 from contextlib import suppress
10 from datetime import datetime, timedelta
11 from http import HTTPStatus
12 from typing import Any
13 
14 import requests
15 import voluptuous as vol
16 
18  PLATFORM_SCHEMA as SENSOR_PLATFORM_SCHEMA,
19  SensorEntity,
20 )
21 from homeassistant.const import CONF_NAME, UnitOfTime
22 from homeassistant.core import HomeAssistant
24 from homeassistant.helpers.entity_platform import AddEntitiesCallback
25 from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
26 import homeassistant.util.dt as dt_util
27 
28 _RESOURCE = "https://data.dublinked.ie/cgi-bin/rtpi/realtimebusinformation"
29 
30 ATTR_STOP_ID = "Stop ID"
31 ATTR_ROUTE = "Route"
32 ATTR_DUE_IN = "Due in"
33 ATTR_DUE_AT = "Due at"
34 ATTR_NEXT_UP = "Later Bus"
35 
36 CONF_STOP_ID = "stopid"
37 CONF_ROUTE = "route"
38 
39 DEFAULT_NAME = "Next Bus"
40 
41 
42 SCAN_INTERVAL = timedelta(minutes=1)
43 TIME_STR_FORMAT = "%H:%M"
44 
45 PLATFORM_SCHEMA = SENSOR_PLATFORM_SCHEMA.extend(
46  {
47  vol.Required(CONF_STOP_ID): cv.string,
48  vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
49  vol.Optional(CONF_ROUTE, default=""): cv.string,
50  }
51 )
52 
53 
54 def due_in_minutes(timestamp):
55  """Get the time in minutes from a timestamp.
56 
57  The timestamp should be in the format day/month/year hour/minute/second
58  """
59  diff = datetime.strptime(timestamp, "%d/%m/%Y %H:%M:%S") - dt_util.now().replace(
60  tzinfo=None
61  )
62 
63  return str(int(diff.total_seconds() / 60))
64 
65 
67  hass: HomeAssistant,
68  config: ConfigType,
69  add_entities: AddEntitiesCallback,
70  discovery_info: DiscoveryInfoType | None = None,
71 ) -> None:
72  """Set up the Dublin public transport sensor."""
73  name = config[CONF_NAME]
74  stop = config[CONF_STOP_ID]
75  route = config[CONF_ROUTE]
76 
77  data = PublicTransportData(stop, route)
78  add_entities([DublinPublicTransportSensor(data, stop, route, name)], True)
79 
80 
82  """Implementation of an Dublin public transport sensor."""
83 
84  _attr_attribution = "Data provided by data.dublinked.ie"
85  _attr_icon = "mdi:bus"
86 
87  def __init__(self, data, stop, route, name):
88  """Initialize the sensor."""
89  self.datadata = data
90  self._name_name = name
91  self._stop_stop = stop
92  self._route_route = route
93  self._times_times = self._state_state = None
94 
95  @property
96  def name(self):
97  """Return the name of the sensor."""
98  return self._name_name
99 
100  @property
101  def native_value(self):
102  """Return the state of the sensor."""
103  return self._state_state
104 
105  @property
106  def extra_state_attributes(self) -> dict[str, Any] | None:
107  """Return the state attributes."""
108  if self._times_times is not None:
109  next_up = "None"
110  if len(self._times_times) > 1:
111  next_up = f"{self._times[1][ATTR_ROUTE]} in "
112  next_up += self._times_times[1][ATTR_DUE_IN]
113 
114  return {
115  ATTR_DUE_IN: self._times_times[0][ATTR_DUE_IN],
116  ATTR_DUE_AT: self._times_times[0][ATTR_DUE_AT],
117  ATTR_STOP_ID: self._stop_stop,
118  ATTR_ROUTE: self._times_times[0][ATTR_ROUTE],
119  ATTR_NEXT_UP: next_up,
120  }
121  return None
122 
123  @property
125  """Return the unit this state is expressed in."""
126  return UnitOfTime.MINUTES
127 
128  def update(self) -> None:
129  """Get the latest data from opendata.ch and update the states."""
130  self.datadata.update()
131  self._times_times = self.datadata.info
132  with suppress(TypeError):
133  self._state_state = self._times_times[0][ATTR_DUE_IN]
134 
135 
137  """The Class for handling the data retrieval."""
138 
139  def __init__(self, stop, route):
140  """Initialize the data object."""
141  self.stopstop = stop
142  self.routeroute = route
143  self.infoinfo = [{ATTR_DUE_AT: "n/a", ATTR_ROUTE: self.routeroute, ATTR_DUE_IN: "n/a"}]
144 
145  def update(self):
146  """Get the latest data from opendata.ch."""
147  params = {}
148  params["stopid"] = self.stopstop
149 
150  if self.routeroute:
151  params["routeid"] = self.routeroute
152 
153  params["maxresults"] = 2
154  params["format"] = "json"
155 
156  response = requests.get(_RESOURCE, params, timeout=10)
157 
158  if response.status_code != HTTPStatus.OK:
159  self.infoinfo = [
160  {ATTR_DUE_AT: "n/a", ATTR_ROUTE: self.routeroute, ATTR_DUE_IN: "n/a"}
161  ]
162  return
163 
164  result = response.json()
165 
166  if str(result["errorcode"]) != "0":
167  self.infoinfo = [
168  {ATTR_DUE_AT: "n/a", ATTR_ROUTE: self.routeroute, ATTR_DUE_IN: "n/a"}
169  ]
170  return
171 
172  self.infoinfo = []
173  for item in result["results"]:
174  due_at = item.get("departuredatetime")
175  route = item.get("route")
176  if due_at is not None and route is not None:
177  bus_data = {
178  ATTR_DUE_AT: due_at,
179  ATTR_ROUTE: route,
180  ATTR_DUE_IN: due_in_minutes(due_at),
181  }
182  self.infoinfo.append(bus_data)
183 
184  if not self.infoinfo:
185  self.infoinfo = [
186  {ATTR_DUE_AT: "n/a", ATTR_ROUTE: self.routeroute, ATTR_DUE_IN: "n/a"}
187  ]
None setup_platform(HomeAssistant hass, ConfigType config, AddEntitiesCallback add_entities, DiscoveryInfoType|None discovery_info=None)
Definition: sensor.py:71
def add_entities(account, async_add_entities, tracked)
Definition: sensor.py:40