Skip to content

Commit

Permalink
Merge pull request #554 from alandtse/dev
Browse files Browse the repository at this point in the history
chore: release 2023-03-26
  • Loading branch information
alandtse authored Mar 26, 2023
2 parents 1ec6092 + 41dfbcc commit 648fe39
Show file tree
Hide file tree
Showing 18 changed files with 182 additions and 79 deletions.
93 changes: 78 additions & 15 deletions custom_components/tesla_custom/__init__.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
"""Support for Tesla cars."""
import asyncio
from datetime import timedelta
from functools import partial
from http import HTTPStatus
import logging

Expand Down Expand Up @@ -39,6 +40,7 @@
PLATFORMS,
)
from .services import async_setup_services, async_unload_services
from .util import SSL_CONTEXT

_LOGGER = logging.getLogger(__name__)

Expand Down Expand Up @@ -126,7 +128,9 @@ async def async_setup_entry(hass, config_entry):
config = config_entry.data
# Because users can have multiple accounts, we always
# create a new session so they have separate cookies
async_client = httpx.AsyncClient(headers={USER_AGENT: SERVER_SOFTWARE}, timeout=60)
async_client = httpx.AsyncClient(
headers={USER_AGENT: SERVER_SOFTWARE}, timeout=60, verify=SSL_CONTEXT
)
email = config_entry.title

if not hass.data[DOMAIN]:
Expand Down Expand Up @@ -193,17 +197,25 @@ async def _async_close_client(*_):

@callback
def _async_create_close_task():
asyncio.create_task(_async_close_client())
# Background tasks are tracked in HA to prevent them from
# being garbage collected in the middle of the task since
# asyncio only holds a weak reference to them.
#
# https://docs.python.org/3/library/asyncio-task.html#creating-tasks

if hasattr(hass, "async_create_background_task"):
hass.async_create_background_task(
_async_close_client(), "tesla_close_client"
)
else:
asyncio.create_task(_async_close_client())

config_entry.async_on_unload(
hass.bus.async_listen_once(EVENT_HOMEASSISTANT_CLOSE, _async_close_client)
)
config_entry.async_on_unload(_async_create_close_task)

_async_save_tokens(hass, config_entry, access_token, refresh_token, expiration)
coordinator = TeslaDataUpdateCoordinator(
hass, config_entry=config_entry, controller=controller
)

try:
if config_entry.data.get("initial_setup"):
Expand Down Expand Up @@ -254,15 +266,38 @@ def _async_create_close_task():

return False

reload_lock = asyncio.Lock()
_partial_coordinator = partial(
TeslaDataUpdateCoordinator,
hass,
config_entry=config_entry,
controller=controller,
reload_lock=reload_lock,
energy_site_ids=set(),
vins=set(),
update_vehicles=False,
)
coordinators = {
"update_vehicles": _partial_coordinator(update_vehicles=True),
**{
energy_site_id: _partial_coordinator(energy_site_ids={energy_site_id})
for energy_site_id in energysites
},
**{vin: _partial_coordinator(vins={vin}) for vin in cars},
}

hass.data[DOMAIN][config_entry.entry_id] = {
"coordinator": coordinator,
"controller": controller,
"coordinators": coordinators,
"cars": cars,
"energysites": energysites,
DATA_LISTENER: [config_entry.add_update_listener(update_listener)],
}
_LOGGER.debug("Connected to the Tesla API")

await coordinator.async_config_entry_first_refresh()
# We do not do a first refresh as we already know the API is working
# from above. Each platform will schedule a refresh via update_before_add
# for the sites/vehicles they are interested in.

await hass.config_entries.async_forward_entry_setups(config_entry, PLATFORMS)

Expand All @@ -274,11 +309,11 @@ async def async_unload_entry(hass, config_entry) -> bool:
unload_ok = await hass.config_entries.async_unload_platforms(
config_entry, PLATFORMS
)
await hass.data[DOMAIN].get(config_entry.entry_id)[
"coordinator"
].controller.disconnect()
entry_data = hass.data[DOMAIN][config_entry.entry_id]
controller: TeslaAPI = entry_data["controller"]
await controller.disconnect()

for listener in hass.data[DOMAIN][config_entry.entry_id][DATA_LISTENER]:
for listener in entry_data[DATA_LISTENER]:
listener()
username = config_entry.title

Expand All @@ -296,7 +331,8 @@ async def async_unload_entry(hass, config_entry) -> bool:

async def update_listener(hass, config_entry):
"""Update when config_entry options update."""
controller = hass.data[DOMAIN][config_entry.entry_id]["coordinator"].controller
entry_data = hass.data[DOMAIN][config_entry.entry_id]
controller: TeslaAPI = entry_data["controller"]
old_update_interval = controller.update_interval
controller.update_interval = config_entry.options.get(
CONF_SCAN_INTERVAL, DEFAULT_SCAN_INTERVAL
Expand All @@ -312,10 +348,24 @@ async def update_listener(hass, config_entry):
class TeslaDataUpdateCoordinator(DataUpdateCoordinator):
"""Class to manage fetching Tesla data."""

def __init__(self, hass, *, config_entry, controller: TeslaAPI):
def __init__(
self,
hass,
*,
config_entry,
controller: TeslaAPI,
reload_lock: asyncio.Lock,
vins: set[str],
energy_site_ids: set[str],
update_vehicles: bool,
):
"""Initialize global Tesla data updater."""
self.controller = controller
self.config_entry = config_entry
self.reload_lock = reload_lock
self.vins = vins
self.energy_site_ids = energy_site_ids
self.update_vehicles = update_vehicles

update_interval = timedelta(seconds=MIN_SCAN_INTERVAL)

Expand All @@ -329,6 +379,8 @@ def __init__(self, hass, *, config_entry, controller: TeslaAPI):
async def _async_update_data(self):
"""Fetch data from API endpoint."""
if self.controller.is_token_refreshed():
# It doesn't matter which coordinator calls this, as long as there
# are no awaits in the below code, it will be called only once.
result = self.controller.get_tokens()
refresh_token = result["refresh_token"]
access_token = result["access_token"]
Expand All @@ -343,8 +395,19 @@ async def _async_update_data(self):
# handled by the data update coordinator.
async with async_timeout.timeout(30):
_LOGGER.debug("Running controller.update()")
return await self.controller.update()
return await self.controller.update(
vins=self.vins,
energy_site_ids=self.energy_site_ids,
update_vehicles=self.update_vehicles,
)
except IncompleteCredentials:
await self.hass.config_entries.async_reload(self.config_entry.entry_id)
if self.reload_lock.locked():
# Any of the coordinators can trigger a reload, but we only
# want to do it once. If the lock is already locked, we know
# another coordinator is already reloading.
_LOGGER.debug("Config entry is already being reloaded")
return
async with self.reload_lock:
await self.hass.config_entries.async_reload(self.config_entry.entry_id)
except TeslaException as err:
raise UpdateFailed(f"Error communicating with API: {err}") from err
15 changes: 9 additions & 6 deletions custom_components/tesla_custom/binary_sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,14 @@

async def async_setup_entry(hass: HomeAssistant, config_entry, async_add_entities):
"""Set up the Tesla selects by config_entry."""
coordinator = hass.data[DOMAIN][config_entry.entry_id]["coordinator"]
cars = hass.data[DOMAIN][config_entry.entry_id]["cars"]
energysites = hass.data[DOMAIN][config_entry.entry_id]["energysites"]
entry_data = hass.data[DOMAIN][config_entry.entry_id]
coordinators = entry_data["coordinators"]
cars = entry_data["cars"]
energysites = entry_data["energysites"]
entities = []

for car in cars.values():
for vin, car in cars.items():
coordinator = coordinators[vin]
entities.append(TeslaCarParkingBrake(hass, car, coordinator))
entities.append(TeslaCarOnline(hass, car, coordinator))
entities.append(TeslaCarAsleep(hass, car, coordinator))
Expand All @@ -35,12 +37,13 @@ async def async_setup_entry(hass: HomeAssistant, config_entry, async_add_entitie
entities.append(TeslaCarScheduledDeparture(hass, car, coordinator))
entities.append(TeslaCarUserPresent(hass, car, coordinator))

for energysite in energysites.values():
for energy_site_id, energysite in energysites.items():
coordinator = coordinators[energy_site_id]
if energysite.resource_type == RESOURCE_TYPE_BATTERY:
entities.append(TeslaEnergyBatteryCharging(hass, energysite, coordinator))
entities.append(TeslaEnergyGridStatus(hass, energysite, coordinator))

async_add_entities(entities, True)
async_add_entities(entities, update_before_add=True)


class TeslaCarParkingBrake(TeslaCarEntity, BinarySensorEntity):
Expand Down
10 changes: 6 additions & 4 deletions custom_components/tesla_custom/button.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,13 @@

async def async_setup_entry(hass: HomeAssistant, config_entry, async_add_entities):
"""Set up the Tesla selects by config_entry."""
coordinator = hass.data[DOMAIN][config_entry.entry_id]["coordinator"]
cars = hass.data[DOMAIN][config_entry.entry_id]["cars"]
entry_data = hass.data[DOMAIN][config_entry.entry_id]
coordinators = entry_data["coordinators"]
cars = entry_data["cars"]
entities = []

for car in cars.values():
for vin, car in cars.items():
coordinator = coordinators[vin]
entities.append(TeslaCarHorn(hass, car, coordinator))
entities.append(TeslaCarFlashLights(hass, car, coordinator))
entities.append(TeslaCarWakeUp(hass, car, coordinator))
Expand All @@ -28,7 +30,7 @@ async def async_setup_entry(hass: HomeAssistant, config_entry, async_add_entitie
entities.append(TeslaCarRemoteStart(hass, car, coordinator))
entities.append(TeslaCarEmissionsTest(hass, car, coordinator))

async_add_entities(entities, True)
async_add_entities(entities, update_before_add=True)


class TeslaCarHorn(TeslaCarEntity, ButtonEntity):
Expand Down
11 changes: 6 additions & 5 deletions custom_components/tesla_custom/climate.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,18 +34,19 @@ async def async_setup_entry(
hass: HomeAssistant, config_entry, async_add_entities
) -> None:
"""Set up the Tesla climate by config_entry."""
coordinator = hass.data[DOMAIN][config_entry.entry_id]["coordinator"]
cars = hass.data[DOMAIN][config_entry.entry_id]["cars"]
entry_data = hass.data[DOMAIN][config_entry.entry_id]
coordinators = entry_data["coordinators"]
cars = entry_data["cars"]

entities = [
TeslaCarClimate(
hass,
car,
coordinator,
coordinators[vin],
)
for car in cars.values()
for vin, car in cars.items()
]
async_add_entities(entities, True)
async_add_entities(entities, update_before_add=True)


class TeslaCarClimate(TeslaCarEntity, ClimateEntity):
Expand Down
5 changes: 4 additions & 1 deletion custom_components/tesla_custom/config_flow.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
DOMAIN,
MIN_SCAN_INTERVAL,
)
from .util import SSL_CONTEXT

_LOGGER = logging.getLogger(__name__)

Expand Down Expand Up @@ -173,7 +174,9 @@ async def validate_input(hass: core.HomeAssistant, data) -> dict:
"""

config = {}
async_client = httpx.AsyncClient(headers={USER_AGENT: SERVER_SOFTWARE}, timeout=60)
async_client = httpx.AsyncClient(
headers={USER_AGENT: SERVER_SOFTWARE}, timeout=60, verify=SSL_CONTEXT
)

try:
controller = TeslaAPI(
Expand Down
10 changes: 6 additions & 4 deletions custom_components/tesla_custom/cover.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,17 +18,19 @@

async def async_setup_entry(hass: HomeAssistant, config_entry, async_add_entities):
"""Set up the Tesla locks by config_entry."""
coordinator = hass.data[DOMAIN][config_entry.entry_id]["coordinator"]
cars = hass.data[DOMAIN][config_entry.entry_id]["cars"]
entry_data = hass.data[DOMAIN][config_entry.entry_id]
coordinators = entry_data["coordinators"]
cars = entry_data["cars"]
entities = []

for car in cars.values():
for vin, car in cars.items():
coordinator = coordinators[vin]
entities.append(TeslaCarChargerDoor(hass, car, coordinator))
entities.append(TeslaCarFrunk(hass, car, coordinator))
entities.append(TeslaCarTrunk(hass, car, coordinator))
entities.append(TeslaCarWindows(hass, car, coordinator))

async_add_entities(entities, True)
async_add_entities(entities, update_before_add=True)


class TeslaCarChargerDoor(TeslaCarEntity, CoverEntity):
Expand Down
10 changes: 6 additions & 4 deletions custom_components/tesla_custom/device_tracker.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,15 +15,17 @@

async def async_setup_entry(hass: HomeAssistant, config_entry, async_add_entities):
"""Set up the Tesla device trackers by config_entry."""
coordinator = hass.data[DOMAIN][config_entry.entry_id]["coordinator"]
cars = hass.data[DOMAIN][config_entry.entry_id]["cars"]
entry_data = hass.data[DOMAIN][config_entry.entry_id]
coordinators = entry_data["coordinators"]
cars = entry_data["cars"]
entities = []

for car in cars.values():
for vin, car in cars.items():
coordinator = coordinators[vin]
entities.append(TeslaCarLocation(hass, car, coordinator))
entities.append(TeslaCarDestinationLocation(hass, car, coordinator))

async_add_entities(entities, True)
async_add_entities(entities, update_before_add=True)


class TeslaCarLocation(TeslaCarEntity, TrackerEntity):
Expand Down
10 changes: 6 additions & 4 deletions custom_components/tesla_custom/lock.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,15 +14,17 @@

async def async_setup_entry(hass: HomeAssistant, config_entry, async_add_entities):
"""Set up the Tesla locks by config_entry."""
coordinator = hass.data[DOMAIN][config_entry.entry_id]["coordinator"]
cars = hass.data[DOMAIN][config_entry.entry_id]["cars"]
entry_data = hass.data[DOMAIN][config_entry.entry_id]
coordinators = entry_data["coordinators"]
cars = entry_data["cars"]
entities = []

for car in cars.values():
for vin, car in cars.items():
coordinator = coordinators[vin]
entities.append(TeslaCarDoors(hass, car, coordinator))
entities.append(TeslaCarChargePortLatch(hass, car, coordinator))

async_add_entities(entities, True)
async_add_entities(entities, update_before_add=True)


class TeslaCarDoors(TeslaCarEntity, LockEntity):
Expand Down
2 changes: 1 addition & 1 deletion custom_components/tesla_custom/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,6 @@
"iot_class": "cloud_polling",
"issue_tracker": "https://github.com/alandtse/tesla/issues",
"loggers": ["teslajsonpy"],
"requirements": ["teslajsonpy==3.7.5"],
"requirements": ["teslajsonpy==3.8.0"],
"version": "3.10.4"
}
15 changes: 9 additions & 6 deletions custom_components/tesla_custom/number.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,20 +19,23 @@

async def async_setup_entry(hass: HomeAssistant, config_entry, async_add_entities):
"""Set up the Tesla numbers by config_entry."""
coordinator = hass.data[DOMAIN][config_entry.entry_id]["coordinator"]
cars = hass.data[DOMAIN][config_entry.entry_id]["cars"]
energysites = hass.data[DOMAIN][config_entry.entry_id]["energysites"]
entry_data = hass.data[DOMAIN][config_entry.entry_id]
coordinators = entry_data["coordinators"]
cars = entry_data["cars"]
energysites = entry_data["energysites"]
entities = []

for car in cars.values():
for vin, car in cars.items():
coordinator = coordinators[vin]
entities.append(TeslaCarChargeLimit(hass, car, coordinator))
entities.append(TeslaCarChargingAmps(hass, car, coordinator))

for energysite in energysites.values():
for energy_site_id, energysite in energysites.items():
coordinator = coordinators[energy_site_id]
if energysite.resource_type == RESOURCE_TYPE_BATTERY:
entities.append(TeslaEnergyBackupReserve(hass, energysite, coordinator))

async_add_entities(entities, True)
async_add_entities(entities, update_before_add=True)


class TeslaCarChargeLimit(TeslaCarEntity, NumberEntity):
Expand Down
Loading

0 comments on commit 648fe39

Please sign in to comment.