Skip to content

Commit 41dfbcc

Browse files
authored
feat: split coordinator to avoid updating disabled cars and energy sites (#552)
1 parent 7386d43 commit 41dfbcc

File tree

16 files changed

+151
-76
lines changed

16 files changed

+151
-76
lines changed

custom_components/tesla_custom/__init__.py

Lines changed: 62 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
"""Support for Tesla cars."""
22
import asyncio
33
from datetime import timedelta
4+
from functools import partial
45
from http import HTTPStatus
56
import 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

311332
async 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):
326348
class 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

custom_components/tesla_custom/binary_sensor.py

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -19,12 +19,14 @@
1919

2020
async def async_setup_entry(hass: HomeAssistant, config_entry, async_add_entities):
2121
"""Set up the Tesla selects by config_entry."""
22-
coordinator = hass.data[DOMAIN][config_entry.entry_id]["coordinator"]
23-
cars = hass.data[DOMAIN][config_entry.entry_id]["cars"]
24-
energysites = hass.data[DOMAIN][config_entry.entry_id]["energysites"]
22+
entry_data = hass.data[DOMAIN][config_entry.entry_id]
23+
coordinators = entry_data["coordinators"]
24+
cars = entry_data["cars"]
25+
energysites = entry_data["energysites"]
2526
entities = []
2627

27-
for car in cars.values():
28+
for vin, car in cars.items():
29+
coordinator = coordinators[vin]
2830
entities.append(TeslaCarParkingBrake(hass, car, coordinator))
2931
entities.append(TeslaCarOnline(hass, car, coordinator))
3032
entities.append(TeslaCarAsleep(hass, car, coordinator))
@@ -35,12 +37,13 @@ async def async_setup_entry(hass: HomeAssistant, config_entry, async_add_entitie
3537
entities.append(TeslaCarScheduledDeparture(hass, car, coordinator))
3638
entities.append(TeslaCarUserPresent(hass, car, coordinator))
3739

38-
for energysite in energysites.values():
40+
for energy_site_id, energysite in energysites.items():
41+
coordinator = coordinators[energy_site_id]
3942
if energysite.resource_type == RESOURCE_TYPE_BATTERY:
4043
entities.append(TeslaEnergyBatteryCharging(hass, energysite, coordinator))
4144
entities.append(TeslaEnergyGridStatus(hass, energysite, coordinator))
4245

43-
async_add_entities(entities, True)
46+
async_add_entities(entities, update_before_add=True)
4447

4548

4649
class TeslaCarParkingBrake(TeslaCarEntity, BinarySensorEntity):

custom_components/tesla_custom/button.py

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,13 @@
1515

1616
async def async_setup_entry(hass: HomeAssistant, config_entry, async_add_entities):
1717
"""Set up the Tesla selects by config_entry."""
18-
coordinator = hass.data[DOMAIN][config_entry.entry_id]["coordinator"]
19-
cars = hass.data[DOMAIN][config_entry.entry_id]["cars"]
18+
entry_data = hass.data[DOMAIN][config_entry.entry_id]
19+
coordinators = entry_data["coordinators"]
20+
cars = entry_data["cars"]
2021
entities = []
2122

22-
for car in cars.values():
23+
for vin, car in cars.items():
24+
coordinator = coordinators[vin]
2325
entities.append(TeslaCarHorn(hass, car, coordinator))
2426
entities.append(TeslaCarFlashLights(hass, car, coordinator))
2527
entities.append(TeslaCarWakeUp(hass, car, coordinator))
@@ -28,7 +30,7 @@ async def async_setup_entry(hass: HomeAssistant, config_entry, async_add_entitie
2830
entities.append(TeslaCarRemoteStart(hass, car, coordinator))
2931
entities.append(TeslaCarEmissionsTest(hass, car, coordinator))
3032

31-
async_add_entities(entities, True)
33+
async_add_entities(entities, update_before_add=True)
3234

3335

3436
class TeslaCarHorn(TeslaCarEntity, ButtonEntity):

custom_components/tesla_custom/climate.py

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -34,18 +34,19 @@ async def async_setup_entry(
3434
hass: HomeAssistant, config_entry, async_add_entities
3535
) -> None:
3636
"""Set up the Tesla climate by config_entry."""
37-
coordinator = hass.data[DOMAIN][config_entry.entry_id]["coordinator"]
38-
cars = hass.data[DOMAIN][config_entry.entry_id]["cars"]
37+
entry_data = hass.data[DOMAIN][config_entry.entry_id]
38+
coordinators = entry_data["coordinators"]
39+
cars = entry_data["cars"]
3940

4041
entities = [
4142
TeslaCarClimate(
4243
hass,
4344
car,
44-
coordinator,
45+
coordinators[vin],
4546
)
46-
for car in cars.values()
47+
for vin, car in cars.items()
4748
]
48-
async_add_entities(entities, True)
49+
async_add_entities(entities, update_before_add=True)
4950

5051

5152
class TeslaCarClimate(TeslaCarEntity, ClimateEntity):

custom_components/tesla_custom/cover.py

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,17 +18,19 @@
1818

1919
async def async_setup_entry(hass: HomeAssistant, config_entry, async_add_entities):
2020
"""Set up the Tesla locks by config_entry."""
21-
coordinator = hass.data[DOMAIN][config_entry.entry_id]["coordinator"]
22-
cars = hass.data[DOMAIN][config_entry.entry_id]["cars"]
21+
entry_data = hass.data[DOMAIN][config_entry.entry_id]
22+
coordinators = entry_data["coordinators"]
23+
cars = entry_data["cars"]
2324
entities = []
2425

25-
for car in cars.values():
26+
for vin, car in cars.items():
27+
coordinator = coordinators[vin]
2628
entities.append(TeslaCarChargerDoor(hass, car, coordinator))
2729
entities.append(TeslaCarFrunk(hass, car, coordinator))
2830
entities.append(TeslaCarTrunk(hass, car, coordinator))
2931
entities.append(TeslaCarWindows(hass, car, coordinator))
3032

31-
async_add_entities(entities, True)
33+
async_add_entities(entities, update_before_add=True)
3234

3335

3436
class TeslaCarChargerDoor(TeslaCarEntity, CoverEntity):

custom_components/tesla_custom/device_tracker.py

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,15 +15,17 @@
1515

1616
async def async_setup_entry(hass: HomeAssistant, config_entry, async_add_entities):
1717
"""Set up the Tesla device trackers by config_entry."""
18-
coordinator = hass.data[DOMAIN][config_entry.entry_id]["coordinator"]
19-
cars = hass.data[DOMAIN][config_entry.entry_id]["cars"]
18+
entry_data = hass.data[DOMAIN][config_entry.entry_id]
19+
coordinators = entry_data["coordinators"]
20+
cars = entry_data["cars"]
2021
entities = []
2122

22-
for car in cars.values():
23+
for vin, car in cars.items():
24+
coordinator = coordinators[vin]
2325
entities.append(TeslaCarLocation(hass, car, coordinator))
2426
entities.append(TeslaCarDestinationLocation(hass, car, coordinator))
2527

26-
async_add_entities(entities, True)
28+
async_add_entities(entities, update_before_add=True)
2729

2830

2931
class TeslaCarLocation(TeslaCarEntity, TrackerEntity):

custom_components/tesla_custom/lock.py

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,15 +14,17 @@
1414

1515
async def async_setup_entry(hass: HomeAssistant, config_entry, async_add_entities):
1616
"""Set up the Tesla locks by config_entry."""
17-
coordinator = hass.data[DOMAIN][config_entry.entry_id]["coordinator"]
18-
cars = hass.data[DOMAIN][config_entry.entry_id]["cars"]
17+
entry_data = hass.data[DOMAIN][config_entry.entry_id]
18+
coordinators = entry_data["coordinators"]
19+
cars = entry_data["cars"]
1920
entities = []
2021

21-
for car in cars.values():
22+
for vin, car in cars.items():
23+
coordinator = coordinators[vin]
2224
entities.append(TeslaCarDoors(hass, car, coordinator))
2325
entities.append(TeslaCarChargePortLatch(hass, car, coordinator))
2426

25-
async_add_entities(entities, True)
27+
async_add_entities(entities, update_before_add=True)
2628

2729

2830
class TeslaCarDoors(TeslaCarEntity, LockEntity):

custom_components/tesla_custom/manifest.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,6 @@
2222
"iot_class": "cloud_polling",
2323
"issue_tracker": "https://github.com/alandtse/tesla/issues",
2424
"loggers": ["teslajsonpy"],
25-
"requirements": ["teslajsonpy==3.7.5"],
25+
"requirements": ["teslajsonpy==3.8.0"],
2626
"version": "3.10.4"
2727
}

custom_components/tesla_custom/number.py

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -19,20 +19,23 @@
1919

2020
async def async_setup_entry(hass: HomeAssistant, config_entry, async_add_entities):
2121
"""Set up the Tesla numbers by config_entry."""
22-
coordinator = hass.data[DOMAIN][config_entry.entry_id]["coordinator"]
23-
cars = hass.data[DOMAIN][config_entry.entry_id]["cars"]
24-
energysites = hass.data[DOMAIN][config_entry.entry_id]["energysites"]
22+
entry_data = hass.data[DOMAIN][config_entry.entry_id]
23+
coordinators = entry_data["coordinators"]
24+
cars = entry_data["cars"]
25+
energysites = entry_data["energysites"]
2526
entities = []
2627

27-
for car in cars.values():
28+
for vin, car in cars.items():
29+
coordinator = coordinators[vin]
2830
entities.append(TeslaCarChargeLimit(hass, car, coordinator))
2931
entities.append(TeslaCarChargingAmps(hass, car, coordinator))
3032

31-
for energysite in energysites.values():
33+
for energy_site_id, energysite in energysites.items():
34+
coordinator = coordinators[energy_site_id]
3235
if energysite.resource_type == RESOURCE_TYPE_BATTERY:
3336
entities.append(TeslaEnergyBackupReserve(hass, energysite, coordinator))
3437

35-
async_add_entities(entities, True)
38+
async_add_entities(entities, update_before_add=True)
3639

3740

3841
class TeslaCarChargeLimit(TeslaCarEntity, NumberEntity):

custom_components/tesla_custom/select.py

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -69,12 +69,14 @@
6969

7070
async def async_setup_entry(hass: HomeAssistant, config_entry, async_add_entities):
7171
"""Set up the Tesla selects by config_entry."""
72-
coordinator = hass.data[DOMAIN][config_entry.entry_id]["coordinator"]
73-
cars = hass.data[DOMAIN][config_entry.entry_id]["cars"]
74-
energysites = hass.data[DOMAIN][config_entry.entry_id]["energysites"]
72+
entry_data = hass.data[DOMAIN][config_entry.entry_id]
73+
coordinators = entry_data["coordinators"]
74+
cars = entry_data["cars"]
75+
energysites = entry_data["energysites"]
7576
entities = []
7677

77-
for car in cars.values():
78+
for vin, car in cars.items():
79+
coordinator = coordinators[vin]
7880
entities.append(TeslaCarCabinOverheatProtection(hass, car, coordinator))
7981
for seat_name in SEAT_ID_MAP:
8082
if "rear" in seat_name and not car.rear_seat_heaters:
@@ -87,14 +89,15 @@ async def async_setup_entry(hass: HomeAssistant, config_entry, async_add_entitie
8789
continue
8890
entities.append(TeslaCarHeatedSeat(hass, car, coordinator, seat_name))
8991

90-
for energysite in energysites.values():
92+
for energy_site_id, energysite in energysites.items():
93+
coordinator = coordinators[energy_site_id]
9194
if energysite.resource_type == RESOURCE_TYPE_BATTERY:
9295
entities.append(TeslaEnergyOperationMode(hass, energysite, coordinator))
9396
if energysite.resource_type == RESOURCE_TYPE_BATTERY and energysite.has_solar:
9497
entities.append(TeslaEnergyExportRule(hass, energysite, coordinator))
9598
entities.append(TeslaEnergyGridCharging(hass, energysite, coordinator))
9699

97-
async_add_entities(entities, True)
100+
async_add_entities(entities, update_before_add=True)
98101

99102

100103
class TeslaCarHeatedSeat(TeslaCarEntity, SelectEntity):

0 commit comments

Comments
 (0)