11"""Support for Tesla cars."""
22import asyncio
33from datetime import timedelta
4+ from functools import partial
45from http import HTTPStatus
56import logging
67
@@ -215,9 +216,6 @@ def _async_create_close_task():
215216 config_entry .async_on_unload (_async_create_close_task )
216217
217218 _async_save_tokens (hass , config_entry , access_token , refresh_token , expiration )
218- coordinator = TeslaDataUpdateCoordinator (
219- hass , config_entry = config_entry , controller = controller
220- )
221219
222220 try :
223221 if config_entry .data .get ("initial_setup" ):
@@ -268,15 +266,38 @@ def _async_create_close_task():
268266
269267 return False
270268
269+ reload_lock = asyncio .Lock ()
270+ _partial_coordinator = partial (
271+ TeslaDataUpdateCoordinator ,
272+ hass ,
273+ config_entry = config_entry ,
274+ controller = controller ,
275+ reload_lock = reload_lock ,
276+ energy_site_ids = set (),
277+ vins = set (),
278+ update_vehicles = False ,
279+ )
280+ coordinators = {
281+ "update_vehicles" : _partial_coordinator (update_vehicles = True ),
282+ ** {
283+ energy_site_id : _partial_coordinator (energy_site_ids = {energy_site_id })
284+ for energy_site_id in energysites
285+ },
286+ ** {vin : _partial_coordinator (vins = {vin }) for vin in cars },
287+ }
288+
271289 hass .data [DOMAIN ][config_entry .entry_id ] = {
272- "coordinator" : coordinator ,
290+ "controller" : controller ,
291+ "coordinators" : coordinators ,
273292 "cars" : cars ,
274293 "energysites" : energysites ,
275294 DATA_LISTENER : [config_entry .add_update_listener (update_listener )],
276295 }
277296 _LOGGER .debug ("Connected to the Tesla API" )
278297
279- await coordinator .async_config_entry_first_refresh ()
298+ # We do not do a first refresh as we already know the API is working
299+ # from above. Each platform will schedule a refresh via update_before_add
300+ # for the sites/vehicles they are interested in.
280301
281302 await hass .config_entries .async_forward_entry_setups (config_entry , PLATFORMS )
282303
@@ -288,11 +309,11 @@ async def async_unload_entry(hass, config_entry) -> bool:
288309 unload_ok = await hass .config_entries .async_unload_platforms (
289310 config_entry , PLATFORMS
290311 )
291- await hass .data [DOMAIN ]. get ( config_entry .entry_id )[
292- "coordinator"
293- ]. controller .disconnect ()
312+ entry_data = hass .data [DOMAIN ][ config_entry .entry_id ]
313+ controller : TeslaAPI = entry_data [ "controller" ]
314+ await controller .disconnect ()
294315
295- for listener in hass . data [ DOMAIN ][ config_entry . entry_id ] [DATA_LISTENER ]:
316+ for listener in entry_data [DATA_LISTENER ]:
296317 listener ()
297318 username = config_entry .title
298319
@@ -310,7 +331,8 @@ async def async_unload_entry(hass, config_entry) -> bool:
310331
311332async def update_listener (hass , config_entry ):
312333 """Update when config_entry options update."""
313- controller = hass .data [DOMAIN ][config_entry .entry_id ]["coordinator" ].controller
334+ entry_data = hass .data [DOMAIN ][config_entry .entry_id ]
335+ controller : TeslaAPI = entry_data ["controller" ]
314336 old_update_interval = controller .update_interval
315337 controller .update_interval = config_entry .options .get (
316338 CONF_SCAN_INTERVAL , DEFAULT_SCAN_INTERVAL
@@ -326,10 +348,24 @@ async def update_listener(hass, config_entry):
326348class TeslaDataUpdateCoordinator (DataUpdateCoordinator ):
327349 """Class to manage fetching Tesla data."""
328350
329- def __init__ (self , hass , * , config_entry , controller : TeslaAPI ):
351+ def __init__ (
352+ self ,
353+ hass ,
354+ * ,
355+ config_entry ,
356+ controller : TeslaAPI ,
357+ reload_lock : asyncio .Lock ,
358+ vins : set [str ],
359+ energy_site_ids : set [str ],
360+ update_vehicles : bool ,
361+ ):
330362 """Initialize global Tesla data updater."""
331363 self .controller = controller
332364 self .config_entry = config_entry
365+ self .reload_lock = reload_lock
366+ self .vins = vins
367+ self .energy_site_ids = energy_site_ids
368+ self .update_vehicles = update_vehicles
333369
334370 update_interval = timedelta (seconds = MIN_SCAN_INTERVAL )
335371
@@ -343,6 +379,8 @@ def __init__(self, hass, *, config_entry, controller: TeslaAPI):
343379 async def _async_update_data (self ):
344380 """Fetch data from API endpoint."""
345381 if self .controller .is_token_refreshed ():
382+ # It doesn't matter which coordinator calls this, as long as there
383+ # are no awaits in the below code, it will be called only once.
346384 result = self .controller .get_tokens ()
347385 refresh_token = result ["refresh_token" ]
348386 access_token = result ["access_token" ]
@@ -357,8 +395,19 @@ async def _async_update_data(self):
357395 # handled by the data update coordinator.
358396 async with async_timeout .timeout (30 ):
359397 _LOGGER .debug ("Running controller.update()" )
360- return await self .controller .update ()
398+ return await self .controller .update (
399+ vins = self .vins ,
400+ energy_site_ids = self .energy_site_ids ,
401+ update_vehicles = self .update_vehicles ,
402+ )
361403 except IncompleteCredentials :
362- await self .hass .config_entries .async_reload (self .config_entry .entry_id )
404+ if self .reload_lock .locked ():
405+ # Any of the coordinators can trigger a reload, but we only
406+ # want to do it once. If the lock is already locked, we know
407+ # another coordinator is already reloading.
408+ _LOGGER .debug ("Config entry is already being reloaded" )
409+ return
410+ async with self .reload_lock :
411+ await self .hass .config_entries .async_reload (self .config_entry .entry_id )
363412 except TeslaException as err :
364413 raise UpdateFailed (f"Error communicating with API: { err } " ) from err
0 commit comments