Home Assistant Unofficial Reference 2024.12.1
sensor.py
Go to the documentation of this file.
1 """Support for Google travel time sensors."""
2 
3 from __future__ import annotations
4 
5 from datetime import datetime, timedelta
6 import logging
7 
8 from googlemaps import Client
9 from googlemaps.distance_matrix import distance_matrix
10 from googlemaps.exceptions import ApiError, Timeout, TransportError
11 
13  SensorDeviceClass,
14  SensorEntity,
15  SensorStateClass,
16 )
17 from homeassistant.config_entries import ConfigEntry
18 from homeassistant.const import (
19  CONF_API_KEY,
20  CONF_NAME,
21  EVENT_HOMEASSISTANT_STARTED,
22  UnitOfTime,
23 )
24 from homeassistant.core import CoreState, HomeAssistant
25 from homeassistant.helpers.device_registry import DeviceEntryType, DeviceInfo
26 from homeassistant.helpers.entity_platform import AddEntitiesCallback
27 from homeassistant.helpers.location import find_coordinates
28 import homeassistant.util.dt as dt_util
29 
30 from .const import (
31  ATTRIBUTION,
32  CONF_ARRIVAL_TIME,
33  CONF_DEPARTURE_TIME,
34  CONF_DESTINATION,
35  CONF_ORIGIN,
36  DEFAULT_NAME,
37  DOMAIN,
38 )
39 
40 _LOGGER = logging.getLogger(__name__)
41 
42 SCAN_INTERVAL = timedelta(minutes=5)
43 
44 
45 def convert_time_to_utc(timestr):
46  """Take a string like 08:00:00 and convert it to a unix timestamp."""
47  combined = datetime.combine(
48  dt_util.start_of_local_day(), dt_util.parse_time(timestr)
49  )
50  if combined < datetime.now():
51  combined = combined + timedelta(days=1)
52  return dt_util.as_timestamp(combined)
53 
54 
56  hass: HomeAssistant,
57  config_entry: ConfigEntry,
58  async_add_entities: AddEntitiesCallback,
59 ) -> None:
60  """Set up a Google travel time sensor entry."""
61  api_key = config_entry.data[CONF_API_KEY]
62  origin = config_entry.data[CONF_ORIGIN]
63  destination = config_entry.data[CONF_DESTINATION]
64  name = config_entry.data.get(CONF_NAME, DEFAULT_NAME)
65 
66  client = Client(api_key, timeout=10)
67 
68  sensor = GoogleTravelTimeSensor(
69  config_entry, name, api_key, origin, destination, client
70  )
71 
72  async_add_entities([sensor], False)
73 
74 
76  """Representation of a Google travel time sensor."""
77 
78  _attr_attribution = ATTRIBUTION
79  _attr_native_unit_of_measurement = UnitOfTime.MINUTES
80  _attr_device_class = SensorDeviceClass.DURATION
81  _attr_state_class = SensorStateClass.MEASUREMENT
82 
83  def __init__(self, config_entry, name, api_key, origin, destination, client):
84  """Initialize the sensor."""
85  self._attr_name_attr_name = name
86  self._attr_unique_id_attr_unique_id = config_entry.entry_id
87  self._attr_device_info_attr_device_info = DeviceInfo(
88  entry_type=DeviceEntryType.SERVICE,
89  identifiers={(DOMAIN, api_key)},
90  name=DOMAIN,
91  )
92 
93  self._config_entry_config_entry = config_entry
94  self._matrix_matrix = None
95  self._api_key_api_key = api_key
96  self._client_client = client
97  self._origin_origin = origin
98  self._destination_destination = destination
99  self._resolved_origin_resolved_origin = None
100  self._resolved_destination_resolved_destination = None
101 
102  async def async_added_to_hass(self) -> None:
103  """Handle when entity is added."""
104  if self.hasshass.state is not CoreState.running:
105  self.hasshass.bus.async_listen_once(
106  EVENT_HOMEASSISTANT_STARTED, self.first_updatefirst_update
107  )
108  else:
109  await self.first_updatefirst_update()
110 
111  @property
112  def native_value(self):
113  """Return the state of the sensor."""
114  if self._matrix_matrix is None:
115  return None
116 
117  _data = self._matrix_matrix["rows"][0]["elements"][0]
118  if "duration_in_traffic" in _data:
119  return round(_data["duration_in_traffic"]["value"] / 60)
120  if "duration" in _data:
121  return round(_data["duration"]["value"] / 60)
122  return None
123 
124  @property
126  """Return the state attributes."""
127  if self._matrix_matrix is None:
128  return None
129 
130  res = self._matrix_matrix.copy()
131  options = self._config_entry_config_entry.options.copy()
132  res.update(options)
133  del res["rows"]
134  _data = self._matrix_matrix["rows"][0]["elements"][0]
135  if "duration_in_traffic" in _data:
136  res["duration_in_traffic"] = _data["duration_in_traffic"]["text"]
137  if "duration" in _data:
138  res["duration"] = _data["duration"]["text"]
139  if "distance" in _data:
140  res["distance"] = _data["distance"]["text"]
141  res["origin"] = self._resolved_origin_resolved_origin
142  res["destination"] = self._resolved_destination_resolved_destination
143  return res
144 
145  async def first_update(self, _=None):
146  """Run the first update and write the state."""
147  await self.hasshass.async_add_executor_job(self.updateupdate)
148  self.async_write_ha_stateasync_write_ha_state()
149 
150  def update(self) -> None:
151  """Get the latest data from Google."""
152  options_copy = self._config_entry_config_entry.options.copy()
153  dtime = options_copy.get(CONF_DEPARTURE_TIME)
154  atime = options_copy.get(CONF_ARRIVAL_TIME)
155  if dtime is not None and ":" in dtime:
156  options_copy[CONF_DEPARTURE_TIME] = convert_time_to_utc(dtime)
157  elif dtime is not None:
158  options_copy[CONF_DEPARTURE_TIME] = dtime
159  elif atime is None:
160  options_copy[CONF_DEPARTURE_TIME] = "now"
161 
162  if atime is not None and ":" in atime:
163  options_copy[CONF_ARRIVAL_TIME] = convert_time_to_utc(atime)
164  elif atime is not None:
165  options_copy[CONF_ARRIVAL_TIME] = atime
166 
167  self._resolved_origin_resolved_origin = find_coordinates(self.hasshass, self._origin_origin)
168  self._resolved_destination_resolved_destination = find_coordinates(self.hasshass, self._destination_destination)
169 
170  _LOGGER.debug(
171  "Getting update for origin: %s destination: %s",
172  self._resolved_origin_resolved_origin,
173  self._resolved_destination_resolved_destination,
174  )
175  if self._resolved_destination_resolved_destination is not None and self._resolved_origin_resolved_origin is not None:
176  try:
177  self._matrix_matrix = distance_matrix(
178  self._client_client,
179  self._resolved_origin_resolved_origin,
180  self._resolved_destination_resolved_destination,
181  **options_copy,
182  )
183  except (ApiError, TransportError, Timeout) as ex:
184  _LOGGER.error("Error getting travel time: %s", ex)
185  self._matrix_matrix = None
def __init__(self, config_entry, name, api_key, origin, destination, client)
Definition: sensor.py:83
None async_setup_entry(HomeAssistant hass, ConfigEntry config_entry, AddEntitiesCallback async_add_entities)
Definition: sensor.py:59
str|None find_coordinates(HomeAssistant hass, str name, list|None recursion_history=None)
Definition: location.py:51