Home Assistant Unofficial Reference 2024.12.1
__init__.py
Go to the documentation of this file.
1 """The waze_travel_time component."""
2 
3 import asyncio
4 from collections.abc import Collection
5 import logging
6 
7 from pywaze.route_calculator import CalcRoutesResponse, WazeRouteCalculator, WRCError
8 import voluptuous as vol
9 
10 from homeassistant.config_entries import ConfigEntry
11 from homeassistant.const import CONF_REGION, Platform
12 from homeassistant.core import (
13  HomeAssistant,
14  ServiceCall,
15  ServiceResponse,
16  SupportsResponse,
17 )
18 from homeassistant.helpers.httpx_client import get_async_client
20  BooleanSelector,
21  SelectSelector,
22  SelectSelectorConfig,
23  SelectSelectorMode,
24  TextSelector,
25 )
26 
27 from .const import (
28  CONF_AVOID_FERRIES,
29  CONF_AVOID_SUBSCRIPTION_ROADS,
30  CONF_AVOID_TOLL_ROADS,
31  CONF_DESTINATION,
32  CONF_EXCL_FILTER,
33  CONF_INCL_FILTER,
34  CONF_ORIGIN,
35  CONF_REALTIME,
36  CONF_UNITS,
37  CONF_VEHICLE_TYPE,
38  DEFAULT_FILTER,
39  DEFAULT_VEHICLE_TYPE,
40  DOMAIN,
41  METRIC_UNITS,
42  REGIONS,
43  SEMAPHORE,
44  UNITS,
45  VEHICLE_TYPES,
46 )
47 
48 PLATFORMS = [Platform.SENSOR]
49 
50 SERVICE_GET_TRAVEL_TIMES = "get_travel_times"
51 SERVICE_GET_TRAVEL_TIMES_SCHEMA = vol.Schema(
52  {
53  vol.Required(CONF_ORIGIN): TextSelector(),
54  vol.Required(CONF_DESTINATION): TextSelector(),
55  vol.Required(CONF_REGION): SelectSelector(
57  options=REGIONS,
58  mode=SelectSelectorMode.DROPDOWN,
59  translation_key=CONF_REGION,
60  sort=True,
61  )
62  ),
63  vol.Optional(CONF_REALTIME, default=False): BooleanSelector(),
64  vol.Optional(CONF_VEHICLE_TYPE, default=DEFAULT_VEHICLE_TYPE): SelectSelector(
66  options=VEHICLE_TYPES,
67  mode=SelectSelectorMode.DROPDOWN,
68  translation_key=CONF_VEHICLE_TYPE,
69  sort=True,
70  )
71  ),
72  vol.Optional(CONF_UNITS, default=METRIC_UNITS): SelectSelector(
74  options=UNITS,
75  mode=SelectSelectorMode.DROPDOWN,
76  translation_key=CONF_UNITS,
77  sort=True,
78  )
79  ),
80  vol.Optional(CONF_AVOID_TOLL_ROADS, default=False): BooleanSelector(),
81  vol.Optional(CONF_AVOID_SUBSCRIPTION_ROADS, default=False): BooleanSelector(),
82  vol.Optional(CONF_AVOID_FERRIES, default=False): BooleanSelector(),
83  }
84 )
85 
86 _LOGGER = logging.getLogger(__name__)
87 
88 
89 async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> bool:
90  """Load the saved entities."""
91  if SEMAPHORE not in hass.data.setdefault(DOMAIN, {}):
92  hass.data.setdefault(DOMAIN, {})[SEMAPHORE] = asyncio.Semaphore(1)
93 
94  await hass.config_entries.async_forward_entry_setups(config_entry, PLATFORMS)
95 
96  async def async_get_travel_times_service(service: ServiceCall) -> ServiceResponse:
97  httpx_client = get_async_client(hass)
98  client = WazeRouteCalculator(
99  region=service.data[CONF_REGION].upper(), client=httpx_client
100  )
101  response = await async_get_travel_times(
102  client=client,
103  origin=service.data[CONF_ORIGIN],
104  destination=service.data[CONF_DESTINATION],
105  vehicle_type=service.data[CONF_VEHICLE_TYPE],
106  avoid_toll_roads=service.data[CONF_AVOID_TOLL_ROADS],
107  avoid_subscription_roads=service.data[CONF_AVOID_SUBSCRIPTION_ROADS],
108  avoid_ferries=service.data[CONF_AVOID_FERRIES],
109  realtime=service.data[CONF_REALTIME],
110  )
111  return {"routes": [vars(route) for route in response]} if response else None
112 
113  hass.services.async_register(
114  DOMAIN,
115  SERVICE_GET_TRAVEL_TIMES,
116  async_get_travel_times_service,
117  SERVICE_GET_TRAVEL_TIMES_SCHEMA,
118  supports_response=SupportsResponse.ONLY,
119  )
120  return True
121 
122 
124  client: WazeRouteCalculator,
125  origin: str,
126  destination: str,
127  vehicle_type: str,
128  avoid_toll_roads: bool,
129  avoid_subscription_roads: bool,
130  avoid_ferries: bool,
131  realtime: bool,
132  incl_filters: Collection[str] | None = None,
133  excl_filters: Collection[str] | None = None,
134 ) -> list[CalcRoutesResponse] | None:
135  """Get all available routes."""
136 
137  incl_filters = incl_filters or ()
138  excl_filters = excl_filters or ()
139 
140  _LOGGER.debug(
141  "Getting update for origin: %s destination: %s",
142  origin,
143  destination,
144  )
145  routes = []
146  vehicle_type = "" if vehicle_type.upper() == "CAR" else vehicle_type.upper()
147  try:
148  routes = await client.calc_routes(
149  origin,
150  destination,
151  vehicle_type=vehicle_type,
152  avoid_toll_roads=avoid_toll_roads,
153  avoid_subscription_roads=avoid_subscription_roads,
154  avoid_ferries=avoid_ferries,
155  real_time=realtime,
156  alternatives=3,
157  )
158  _LOGGER.debug("Got routes: %s", routes)
159 
160  incl_routes: list[CalcRoutesResponse] = []
161 
162  def should_include_route(route: CalcRoutesResponse) -> bool:
163  if len(incl_filters) < 1:
164  return True
165  should_include = any(
166  street_name in incl_filters or "" in incl_filters
167  for street_name in route.street_names
168  )
169  if not should_include:
170  _LOGGER.debug(
171  "Excluding route [%s], because no inclusive filter matched any streetname",
172  route.name,
173  )
174  return False
175  return True
176 
177  incl_routes = [route for route in routes if should_include_route(route)]
178 
179  filtered_routes: list[CalcRoutesResponse] = []
180 
181  def should_exclude_route(route: CalcRoutesResponse) -> bool:
182  for street_name in route.street_names:
183  for excl_filter in excl_filters:
184  if excl_filter == street_name:
185  _LOGGER.debug(
186  "Excluding route, because exclusive filter [%s] matched streetname: %s",
187  excl_filter,
188  route.name,
189  )
190  return True
191  return False
192 
193  filtered_routes = [
194  route for route in incl_routes if not should_exclude_route(route)
195  ]
196 
197  if len(filtered_routes) < 1:
198  _LOGGER.warning("No routes found")
199  return None
200  except WRCError as exp:
201  _LOGGER.warning("Error on retrieving data: %s", exp)
202  return None
203 
204  else:
205  return filtered_routes
206 
207 
208 async def async_unload_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> bool:
209  """Unload a config entry."""
210  return await hass.config_entries.async_unload_platforms(config_entry, PLATFORMS)
211 
212 
213 async def async_migrate_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> bool:
214  """Migrate an old config entry."""
215 
216  if config_entry.version == 1:
217  _LOGGER.debug(
218  "Migrating from version %s.%s",
219  config_entry.version,
220  config_entry.minor_version,
221  )
222  options = dict(config_entry.options)
223  if (incl_filters := options.pop(CONF_INCL_FILTER, None)) not in {None, ""}:
224  options[CONF_INCL_FILTER] = [incl_filters]
225  else:
226  options[CONF_INCL_FILTER] = DEFAULT_FILTER
227  if (excl_filters := options.pop(CONF_EXCL_FILTER, None)) not in {None, ""}:
228  options[CONF_EXCL_FILTER] = [excl_filters]
229  else:
230  options[CONF_EXCL_FILTER] = DEFAULT_FILTER
231  hass.config_entries.async_update_entry(config_entry, options=options, version=2)
232  _LOGGER.debug(
233  "Migration to version %s.%s successful",
234  config_entry.version,
235  config_entry.minor_version,
236  )
237  return True
bool async_unload_entry(HomeAssistant hass, ConfigEntry config_entry)
Definition: __init__.py:208
bool async_migrate_entry(HomeAssistant hass, ConfigEntry config_entry)
Definition: __init__.py:213
bool async_setup_entry(HomeAssistant hass, ConfigEntry config_entry)
Definition: __init__.py:89
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