Home Assistant Unofficial Reference 2024.12.1
sensor.py
Go to the documentation of this file.
1 """Support for Waze travel time sensor."""
2 
3 from __future__ import annotations
4 
5 import asyncio
6 from datetime import timedelta
7 import logging
8 from typing import Any
9 
10 import httpx
11 from pywaze.route_calculator import WazeRouteCalculator
12 
14  SensorDeviceClass,
15  SensorEntity,
16  SensorStateClass,
17 )
18 from homeassistant.config_entries import ConfigEntry
19 from homeassistant.const import (
20  CONF_NAME,
21  CONF_REGION,
22  EVENT_HOMEASSISTANT_STARTED,
23  UnitOfLength,
24  UnitOfTime,
25 )
26 from homeassistant.core import CoreState, HomeAssistant
27 from homeassistant.helpers.device_registry import DeviceEntryType, DeviceInfo
28 from homeassistant.helpers.entity_platform import AddEntitiesCallback
29 from homeassistant.helpers.httpx_client import get_async_client
30 from homeassistant.helpers.location import find_coordinates
31 from homeassistant.util.unit_conversion import DistanceConverter
32 
33 from . import async_get_travel_times
34 from .const import (
35  CONF_AVOID_FERRIES,
36  CONF_AVOID_SUBSCRIPTION_ROADS,
37  CONF_AVOID_TOLL_ROADS,
38  CONF_DESTINATION,
39  CONF_EXCL_FILTER,
40  CONF_INCL_FILTER,
41  CONF_ORIGIN,
42  CONF_REALTIME,
43  CONF_UNITS,
44  CONF_VEHICLE_TYPE,
45  DEFAULT_NAME,
46  DOMAIN,
47  IMPERIAL_UNITS,
48  SEMAPHORE,
49 )
50 
51 _LOGGER = logging.getLogger(__name__)
52 
53 SCAN_INTERVAL = timedelta(minutes=5)
54 
55 PARALLEL_UPDATES = 1
56 
57 SECONDS_BETWEEN_API_CALLS = 0.5
58 
59 
61  hass: HomeAssistant,
62  config_entry: ConfigEntry,
63  async_add_entities: AddEntitiesCallback,
64 ) -> None:
65  """Set up a Waze travel time sensor entry."""
66  destination = config_entry.data[CONF_DESTINATION]
67  origin = config_entry.data[CONF_ORIGIN]
68  region = config_entry.data[CONF_REGION]
69  name = config_entry.data.get(CONF_NAME, DEFAULT_NAME)
70 
71  data = WazeTravelTimeData(
72  region,
73  get_async_client(hass),
74  config_entry,
75  )
76 
77  sensor = WazeTravelTime(config_entry.entry_id, name, origin, destination, data)
78 
79  async_add_entities([sensor], False)
80 
81 
83  """Representation of a Waze travel time sensor."""
84 
85  _attr_attribution = "Powered by Waze"
86  _attr_native_unit_of_measurement = UnitOfTime.MINUTES
87  _attr_device_class = SensorDeviceClass.DURATION
88  _attr_state_class = SensorStateClass.MEASUREMENT
89  _attr_device_info = DeviceInfo(
90  entry_type=DeviceEntryType.SERVICE,
91  name="Waze",
92  identifiers={(DOMAIN, DOMAIN)},
93  configuration_url="https://www.waze.com",
94  )
95  _attr_translation_key = "waze_travel_time"
96 
97  def __init__(
98  self,
99  unique_id: str,
100  name: str,
101  origin: str,
102  destination: str,
103  waze_data: WazeTravelTimeData,
104  ) -> None:
105  """Initialize the Waze travel time sensor."""
106  self._attr_unique_id_attr_unique_id = unique_id
107  self._waze_data_waze_data = waze_data
108  self._attr_name_attr_name = name
109  self._origin_origin = origin
110  self._destination_destination = destination
111  self._state_state = None
112 
113  async def async_added_to_hass(self) -> None:
114  """Handle when entity is added."""
115  if self.hasshass.state is not CoreState.running:
116  self.hasshass.bus.async_listen_once(
117  EVENT_HOMEASSISTANT_STARTED, self.first_updatefirst_update
118  )
119  else:
120  await self.first_updatefirst_update()
121 
122  @property
123  def native_value(self) -> float | None:
124  """Return the state of the sensor."""
125  if self._waze_data_waze_data.duration is not None:
126  return round(self._waze_data_waze_data.duration)
127 
128  return None
129 
130  @property
131  def extra_state_attributes(self) -> dict[str, Any] | None:
132  """Return the state attributes of the last update."""
133  if self._waze_data_waze_data.duration is None:
134  return None
135 
136  return {
137  "duration": self._waze_data_waze_data.duration,
138  "distance": self._waze_data_waze_data.distance,
139  "route": self._waze_data_waze_data.route,
140  "origin": self._waze_data_waze_data.origin,
141  "destination": self._waze_data_waze_data.destination,
142  }
143 
144  async def first_update(self, _=None) -> None:
145  """Run first update and write state."""
146  await self.async_updateasync_update()
147  self.async_write_ha_stateasync_write_ha_state()
148 
149  async def async_update(self) -> None:
150  """Fetch new state data for the sensor."""
151  _LOGGER.debug("Fetching Route for %s", self._attr_name_attr_name)
152  self._waze_data_waze_data.origin = find_coordinates(self.hasshass, self._origin_origin)
153  self._waze_data_waze_data.destination = find_coordinates(self.hasshass, self._destination_destination)
154  await self.hasshass.data[DOMAIN][SEMAPHORE].acquire()
155  try:
156  await self._waze_data_waze_data.async_update()
157  await asyncio.sleep(SECONDS_BETWEEN_API_CALLS)
158  finally:
159  self.hasshass.data[DOMAIN][SEMAPHORE].release()
160 
161 
163  """WazeTravelTime Data object."""
164 
165  def __init__(
166  self, region: str, client: httpx.AsyncClient, config_entry: ConfigEntry
167  ) -> None:
168  """Set up WazeRouteCalculator."""
169  self.config_entryconfig_entry = config_entry
170  self.clientclient = WazeRouteCalculator(region=region, client=client)
171  self.origin: str | None = None
172  self.destination: str | None = None
173  self.durationduration = None
174  self.distancedistance = None
175  self.routeroute = None
176 
177  async def async_update(self):
178  """Update WazeRouteCalculator Sensor."""
179  _LOGGER.debug(
180  "Getting update for origin: %s destination: %s",
181  self.origin,
182  self.destination,
183  )
184  if self.origin is not None and self.destination is not None:
185  # Grab options on every update
186  incl_filter = self.config_entryconfig_entry.options[CONF_INCL_FILTER]
187  excl_filter = self.config_entryconfig_entry.options[CONF_EXCL_FILTER]
188  realtime = self.config_entryconfig_entry.options[CONF_REALTIME]
189  vehicle_type = self.config_entryconfig_entry.options[CONF_VEHICLE_TYPE]
190  avoid_toll_roads = self.config_entryconfig_entry.options[CONF_AVOID_TOLL_ROADS]
191  avoid_subscription_roads = self.config_entryconfig_entry.options[
192  CONF_AVOID_SUBSCRIPTION_ROADS
193  ]
194  avoid_ferries = self.config_entryconfig_entry.options[CONF_AVOID_FERRIES]
195  routes = await async_get_travel_times(
196  self.clientclient,
197  self.origin,
198  self.destination,
199  vehicle_type,
200  avoid_toll_roads,
201  avoid_subscription_roads,
202  avoid_ferries,
203  realtime,
204  incl_filter,
205  excl_filter,
206  )
207  if routes:
208  route = routes[0]
209  else:
210  _LOGGER.warning("No routes found")
211  return
212 
213  self.durationduration = route.duration
214  distance = route.distance
215 
216  if self.config_entryconfig_entry.options[CONF_UNITS] == IMPERIAL_UNITS:
217  # Convert to miles.
218  self.distancedistance = DistanceConverter.convert(
219  distance, UnitOfLength.KILOMETERS, UnitOfLength.MILES
220  )
221  else:
222  self.distancedistance = distance
223 
224  self.routeroute = route.name
None __init__(self, str region, httpx.AsyncClient client, ConfigEntry config_entry)
Definition: sensor.py:167
None __init__(self, str unique_id, str name, str origin, str destination, WazeTravelTimeData waze_data)
Definition: sensor.py:104
None async_setup_entry(HomeAssistant hass, ConfigEntry config_entry, AddEntitiesCallback async_add_entities)
Definition: sensor.py:64
list[CalcRoutesResponse]|None async_get_travel_times(WazeRouteCalculator client, str origin, str destination, str vehicle_type, bool avoid_toll_roads, bool avoid_subscription_roads, bool avoid_ferries, bool realtime, Collection[str]|None incl_filters=None, Collection[str]|None excl_filters=None)
Definition: __init__.py:134
httpx.AsyncClient get_async_client(HomeAssistant hass, bool verify_ssl=True)
Definition: httpx_client.py:41
str|None find_coordinates(HomeAssistant hass, str name, list|None recursion_history=None)
Definition: location.py:51