Home Assistant Unofficial Reference 2024.12.1
sensor.py
Go to the documentation of this file.
1 """Service for obtaining information about closer bus from Transport Yandex Service."""
2 
3 from __future__ import annotations
4 
5 from datetime import timedelta
6 import logging
7 
8 from aioymaps import CaptchaError, NoSessionError, YandexMapsRequester
9 import voluptuous as vol
10 
12  PLATFORM_SCHEMA as SENSOR_PLATFORM_SCHEMA,
13  SensorDeviceClass,
14  SensorEntity,
15 )
16 from homeassistant.const import CONF_NAME
17 from homeassistant.core import HomeAssistant
18 from homeassistant.helpers.aiohttp_client import async_create_clientsession
20 from homeassistant.helpers.entity_platform import AddEntitiesCallback
21 from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
22 import homeassistant.util.dt as dt_util
23 
24 _LOGGER = logging.getLogger(__name__)
25 
26 STOP_NAME = "stop_name"
27 USER_AGENT = "Home Assistant"
28 
29 CONF_STOP_ID = "stop_id"
30 CONF_ROUTE = "routes"
31 
32 DEFAULT_NAME = "Yandex Transport"
33 
34 
35 SCAN_INTERVAL = timedelta(minutes=1)
36 
37 PLATFORM_SCHEMA = SENSOR_PLATFORM_SCHEMA.extend(
38  {
39  vol.Required(CONF_STOP_ID): cv.string,
40  vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
41  vol.Optional(CONF_ROUTE, default=[]): vol.All(cv.ensure_list, [cv.string]),
42  }
43 )
44 
45 
47  hass: HomeAssistant,
48  config: ConfigType,
49  async_add_entities: AddEntitiesCallback,
50  discovery_info: DiscoveryInfoType | None = None,
51 ) -> None:
52  """Set up the Yandex transport sensor."""
53  stop_id = config[CONF_STOP_ID]
54  name = config[CONF_NAME]
55  routes = config[CONF_ROUTE]
56 
57  client_session = async_create_clientsession(hass, requote_redirect_url=False)
58  ymaps = YandexMapsRequester(user_agent=USER_AGENT, client_session=client_session)
59  try:
60  await ymaps.set_new_session()
61  except CaptchaError as ex:
62  _LOGGER.error(
63  "%s. You may need to disable the integration for some time",
64  ex,
65  )
66  return
67  async_add_entities([DiscoverYandexTransport(ymaps, stop_id, routes, name)], True)
68 
69 
71  """Implementation of yandex_transport sensor."""
72 
73  _attr_attribution = "Data provided by maps.yandex.ru"
74  _attr_icon = "mdi:bus"
75 
76  def __init__(self, requester: YandexMapsRequester, stop_id, routes, name) -> None:
77  """Initialize sensor."""
78  self.requesterrequester = requester
79  self._stop_id_stop_id = stop_id
80  self._routes_routes = routes
81  self._state_state = None
82  self._name_name = name
83  self._attrs_attrs = None
84 
85  async def async_update(self, *, tries=0):
86  """Get the latest data from maps.yandex.ru and update the states."""
87  attrs = {}
88  closer_time = None
89  try:
90  yandex_reply = await self.requesterrequester.get_stop_info(self._stop_id_stop_id)
91  except (CaptchaError, NoSessionError) as ex:
92  _LOGGER.error(
93  "%s. You may need to disable the integration for some time",
94  ex,
95  )
96  return
97  try:
98  data = yandex_reply["data"]
99  except KeyError as key_error:
100  _LOGGER.warning(
101  (
102  "Exception KeyError was captured, missing key is %s. Yandex"
103  " returned: %s"
104  ),
105  key_error,
106  yandex_reply,
107  )
108  if tries > 0:
109  return
110  await self.requesterrequester.set_new_session()
111  await self.async_updateasync_update(tries=tries + 1)
112  return
113 
114  stop_name = data["name"]
115  transport_list = data["transports"]
116  for transport in transport_list:
117  for thread in transport["threads"]:
118  if "Events" not in thread["BriefSchedule"]:
119  continue
120  if thread.get("noBoarding") is True:
121  continue
122  for event in thread["BriefSchedule"]["Events"]:
123  # Railway route depends on the essential stops and
124  # can vary over time.
125  # City transport has the fixed name for the route
126  if "railway" in transport["Types"]:
127  route = " - ".join(
128  [x["name"] for x in thread["EssentialStops"]]
129  )
130  else:
131  route = transport["name"]
132 
133  if self._routes_routes and route not in self._routes_routes:
134  # skip unnecessary route info
135  continue
136  if "Estimated" not in event and "Scheduled" not in event:
137  continue
138 
139  departure = event.get("Estimated") or event["Scheduled"]
140  posix_time_next = int(departure["value"])
141  if closer_time is None or closer_time > posix_time_next:
142  closer_time = posix_time_next
143  if route not in attrs:
144  attrs[route] = []
145  attrs[route].append(departure["text"])
146  attrs[STOP_NAME] = stop_name
147 
148  if closer_time is None:
149  self._state_state = None
150  else:
151  self._state_state = dt_util.utc_from_timestamp(closer_time).replace(microsecond=0)
152  self._attrs_attrs = attrs
153 
154  @property
155  def native_value(self):
156  """Return the state of the sensor."""
157  return self._state_state
158 
159  @property
160  def device_class(self):
161  """Return the device class."""
162  return SensorDeviceClass.TIMESTAMP
163 
164  @property
165  def name(self):
166  """Return the name of the sensor."""
167  return self._name_name
168 
169  @property
171  """Return the state attributes."""
172  return self._attrs_attrs
None __init__(self, YandexMapsRequester requester, stop_id, routes, name)
Definition: sensor.py:76
aiohttp.ClientSession async_create_clientsession()
Definition: coordinator.py:51
None async_setup_platform(HomeAssistant hass, ConfigType config, AddEntitiesCallback async_add_entities, DiscoveryInfoType|None discovery_info=None)
Definition: sensor.py:51