Home Assistant Unofficial Reference 2024.12.1
coordinator.py
Go to the documentation of this file.
1 """Teslemetry Data Coordinator."""
2 
3 from datetime import datetime, timedelta
4 from typing import Any
5 
6 from tesla_fleet_api import EnergySpecific, VehicleSpecific
7 from tesla_fleet_api.const import TeslaEnergyPeriod, VehicleDataEndpoint
8 from tesla_fleet_api.exceptions import (
9  Forbidden,
10  InvalidToken,
11  SubscriptionRequired,
12  TeslaFleetError,
13  VehicleOffline,
14 )
15 
16 from homeassistant.core import HomeAssistant
17 from homeassistant.exceptions import ConfigEntryAuthFailed
18 from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
19 
20 from .const import ENERGY_HISTORY_FIELDS, LOGGER, TeslemetryState
21 from .helpers import flatten
22 
23 VEHICLE_INTERVAL = timedelta(seconds=30)
24 VEHICLE_WAIT = timedelta(minutes=15)
25 ENERGY_LIVE_INTERVAL = timedelta(seconds=30)
26 ENERGY_INFO_INTERVAL = timedelta(seconds=30)
27 ENERGY_HISTORY_INTERVAL = timedelta(seconds=60)
28 
29 ENDPOINTS = [
30  VehicleDataEndpoint.CHARGE_STATE,
31  VehicleDataEndpoint.CLIMATE_STATE,
32  VehicleDataEndpoint.DRIVE_STATE,
33  VehicleDataEndpoint.LOCATION_DATA,
34  VehicleDataEndpoint.VEHICLE_STATE,
35  VehicleDataEndpoint.VEHICLE_CONFIG,
36 ]
37 
38 
40  """Class to manage fetching data from the Teslemetry API."""
41 
42  updated_once: bool
43  last_active: datetime
44 
45  def __init__(
46  self, hass: HomeAssistant, api: VehicleSpecific, product: dict
47  ) -> None:
48  """Initialize Teslemetry Vehicle Update Coordinator."""
49  super().__init__(
50  hass,
51  LOGGER,
52  name="Teslemetry Vehicle",
53  update_interval=VEHICLE_INTERVAL,
54  )
55  self.apiapi = api
56  self.datadatadata = flatten(product)
57  self.updated_onceupdated_once = False
58  self.last_activelast_active = datetime.now()
59 
60  async def _async_update_data(self) -> dict[str, Any]:
61  """Update vehicle data using Teslemetry API."""
62 
63  self.update_intervalupdate_intervalupdate_intervalupdate_intervalupdate_interval = VEHICLE_INTERVAL
64 
65  try:
66  if self.datadatadata["state"] != TeslemetryState.ONLINE:
67  response = await self.apiapi.vehicle()
68  self.datadatadata["state"] = response["response"]["state"]
69 
70  if self.datadatadata["state"] != TeslemetryState.ONLINE:
71  return self.datadatadata
72 
73  response = await self.apiapi.vehicle_data(endpoints=ENDPOINTS)
74  data = response["response"]
75 
76  except VehicleOffline:
77  self.datadatadata["state"] = TeslemetryState.OFFLINE
78  return self.datadatadata
79  except InvalidToken as e:
80  raise ConfigEntryAuthFailed from e
81  except SubscriptionRequired as e:
82  raise ConfigEntryAuthFailed from e
83  except TeslaFleetError as e:
84  raise UpdateFailed(e.message) from e
85 
86  self.updated_onceupdated_once = True
87 
88  if self.apiapi.pre2021 and data["state"] == TeslemetryState.ONLINE:
89  # Handle pre-2021 vehicles which cannot sleep by themselves
90  if (
91  data["charge_state"].get("charging_state") == "Charging"
92  or data["vehicle_state"].get("is_user_present")
93  or data["vehicle_state"].get("sentry_mode")
94  ):
95  # Vehicle is active, reset timer
96  self.last_activelast_active = datetime.now()
97  else:
98  elapsed = datetime.now() - self.last_activelast_active
99  if elapsed > timedelta(minutes=20):
100  # Vehicle didn't sleep, try again in 15 minutes
101  self.last_activelast_active = datetime.now()
102  elif elapsed > timedelta(minutes=15):
103  # Let vehicle go to sleep now
104  self.update_intervalupdate_intervalupdate_intervalupdate_intervalupdate_interval = VEHICLE_WAIT
105 
106  return flatten(data)
107 
108 
110  """Class to manage fetching energy site live status from the Teslemetry API."""
111 
112  updated_once: bool
113 
114  def __init__(self, hass: HomeAssistant, api: EnergySpecific) -> None:
115  """Initialize Teslemetry Energy Site Live coordinator."""
116  super().__init__(
117  hass,
118  LOGGER,
119  name="Teslemetry Energy Site Live",
120  update_interval=ENERGY_LIVE_INTERVAL,
121  )
122  self.apiapi = api
123 
124  async def _async_update_data(self) -> dict[str, Any]:
125  """Update energy site data using Teslemetry API."""
126 
127  try:
128  data = (await self.apiapi.live_status())["response"]
129  except (InvalidToken, Forbidden, SubscriptionRequired) as e:
130  raise ConfigEntryAuthFailed from e
131  except TeslaFleetError as e:
132  raise UpdateFailed(e.message) from e
133 
134  # Convert Wall Connectors from array to dict
135  data["wall_connectors"] = {
136  wc["din"]: wc for wc in (data.get("wall_connectors") or [])
137  }
138 
139  return data
140 
141 
143  """Class to manage fetching energy site info from the Teslemetry API."""
144 
145  updated_once: bool
146 
147  def __init__(self, hass: HomeAssistant, api: EnergySpecific, product: dict) -> None:
148  """Initialize Teslemetry Energy Info coordinator."""
149  super().__init__(
150  hass,
151  LOGGER,
152  name="Teslemetry Energy Site Info",
153  update_interval=ENERGY_INFO_INTERVAL,
154  )
155  self.apiapi = api
156  self.datadatadata = product
157 
158  async def _async_update_data(self) -> dict[str, Any]:
159  """Update energy site data using Teslemetry API."""
160 
161  try:
162  data = (await self.apiapi.site_info())["response"]
163  except (InvalidToken, Forbidden, SubscriptionRequired) as e:
164  raise ConfigEntryAuthFailed from e
165  except TeslaFleetError as e:
166  raise UpdateFailed(e.message) from e
167 
168  return flatten(data)
169 
170 
172  """Class to manage fetching energy site info from the Teslemetry API."""
173 
174  updated_once: bool
175 
176  def __init__(self, hass: HomeAssistant, api: EnergySpecific) -> None:
177  """Initialize Teslemetry Energy Info coordinator."""
178  super().__init__(
179  hass,
180  LOGGER,
181  name=f"Teslemetry Energy History {api.energy_site_id}",
182  update_interval=ENERGY_HISTORY_INTERVAL,
183  )
184  self.apiapi = api
185 
186  async def _async_update_data(self) -> dict[str, Any]:
187  """Update energy site data using Teslemetry API."""
188 
189  try:
190  data = (await self.apiapi.energy_history(TeslaEnergyPeriod.DAY))["response"]
191  except (InvalidToken, Forbidden, SubscriptionRequired) as e:
192  raise ConfigEntryAuthFailed from e
193  except TeslaFleetError as e:
194  raise UpdateFailed(e.message) from e
195 
196  self.updated_onceupdated_once = True
197 
198  # Add all time periods together
199  output = {key: 0 for key in ENERGY_HISTORY_FIELDS}
200  for period in data.get("time_series", []):
201  for key in ENERGY_HISTORY_FIELDS:
202  output[key] += period.get(key, 0)
203 
204  return output
None __init__(self, HomeAssistant hass, EnergySpecific api, dict product)
Definition: coordinator.py:147
None __init__(self, HomeAssistant hass, VehicleSpecific api, dict product)
Definition: coordinator.py:47
web.Response get(self, web.Request request, str config_key)
Definition: view.py:88
dict[str, Any] flatten(dict[str, Any] data, str|None parent=None)
Definition: coordinator.py:27