Convert Tesla to Async (#28748)

* build: bump teslajsonpy to 0.2.0

* feat: add async

* perf: convert unnecessary async calls to sync

* fix: force real login

* Revert change to HVAC_MODE_HEAT

* Remove charging rate sensor

* Remove tests

* Remove tests

* Address requested changes

* Add missing sensors

* Move update to async_setup_scanner

* Align wtih prior update behavior
This commit is contained in:
Alan Tse 2019-11-14 20:15:58 -08:00 committed by Martin Hjelmare
parent 806b96ef73
commit 2aad150419
10 changed files with 109 additions and 85 deletions

View File

@ -2,9 +2,8 @@
from collections import defaultdict
import logging
import voluptuous as vol
from teslajsonpy import Controller as teslaAPI, TeslaException
import voluptuous as vol
from homeassistant.const import (
ATTR_BATTERY_LEVEL,
@ -12,18 +11,14 @@ from homeassistant.const import (
CONF_SCAN_INTERVAL,
CONF_USERNAME,
)
from homeassistant.helpers import config_validation as cv
from homeassistant.helpers import discovery
from homeassistant.helpers import aiohttp_client, config_validation as cv, discovery
from homeassistant.helpers.entity import Entity
from homeassistant.util import slugify
DOMAIN = "tesla"
from .const import DOMAIN, TESLA_COMPONENTS
_LOGGER = logging.getLogger(__name__)
TESLA_ID_FORMAT = "{}_{}"
TESLA_ID_LIST_SCHEMA = vol.Schema([int])
CONFIG_SCHEMA = vol.Schema(
{
DOMAIN: vol.Schema(
@ -42,17 +37,8 @@ CONFIG_SCHEMA = vol.Schema(
NOTIFICATION_ID = "tesla_integration_notification"
NOTIFICATION_TITLE = "Tesla integration setup"
TESLA_COMPONENTS = [
"sensor",
"lock",
"climate",
"binary_sensor",
"device_tracker",
"switch",
]
def setup(hass, base_config):
async def async_setup(hass, base_config):
"""Set up of Tesla component."""
config = base_config.get(DOMAIN)
@ -61,10 +47,15 @@ def setup(hass, base_config):
update_interval = config.get(CONF_SCAN_INTERVAL)
if hass.data.get(DOMAIN) is None:
try:
hass.data[DOMAIN] = {
"controller": teslaAPI(email, password, update_interval),
"devices": defaultdict(list),
}
websession = aiohttp_client.async_get_clientsession(hass)
controller = teslaAPI(
websession,
email=email,
password=password,
update_interval=update_interval,
)
await controller.connect(test_login=False)
hass.data[DOMAIN] = {"controller": controller, "devices": defaultdict(list)}
_LOGGER.debug("Connected to the Tesla API.")
except TeslaException as ex:
if ex.code == 401:
@ -85,9 +76,7 @@ def setup(hass, base_config):
)
_LOGGER.error("Unable to communicate with Tesla API: %s", ex.message)
return False
all_devices = hass.data[DOMAIN]["controller"].list_vehicles()
all_devices = controller.get_homeassistant_components()
if not all_devices:
return False
@ -95,8 +84,9 @@ def setup(hass, base_config):
hass.data[DOMAIN]["devices"][device.hass_type].append(device)
for component in TESLA_COMPONENTS:
discovery.load_platform(hass, component, DOMAIN, {}, base_config)
hass.async_create_task(
discovery.async_load_platform(hass, component, DOMAIN, {}, base_config)
)
return True
@ -104,11 +94,12 @@ class TeslaDevice(Entity):
"""Representation of a Tesla device."""
def __init__(self, tesla_device, controller):
"""Initialise of the Tesla device."""
"""Initialise the Tesla device."""
self.tesla_device = tesla_device
self.controller = controller
self._name = self.tesla_device.name
self.tesla_id = slugify(self.tesla_device.uniq_name)
self._attributes = {}
@property
def name(self):
@ -128,8 +119,19 @@ class TeslaDevice(Entity):
@property
def device_state_attributes(self):
"""Return the state attributes of the device."""
attr = {}
attr = self._attributes
if self.tesla_device.has_battery():
attr[ATTR_BATTERY_LEVEL] = self.tesla_device.battery_level()
return attr
async def async_added_to_hass(self):
"""Register state update callback."""
pass
async def async_will_remove_from_hass(self):
"""Prepare for unload."""
pass
async def async_update(self):
"""Update the state of the device."""
await self.tesla_device.async_update()

View File

@ -8,7 +8,7 @@ from . import DOMAIN as TESLA_DOMAIN, TeslaDevice
_LOGGER = logging.getLogger(__name__)
def setup_platform(hass, config, add_entities, discovery_info=None):
async def async_setup_platform(hass, config, add_entities, discovery_info=None):
"""Set up the Tesla binary sensor."""
devices = [
TeslaBinarySensor(device, hass.data[TESLA_DOMAIN]["controller"], "connectivity")
@ -41,8 +41,8 @@ class TeslaBinarySensor(TeslaDevice, BinarySensorDevice):
"""Return the state of the binary sensor."""
return self._state
def update(self):
async def async_update(self):
"""Update the state of the device."""
_LOGGER.debug("Updating sensor: %s", self._name)
self.tesla_device.update()
await super().async_update()
self._state = self.tesla_device.get_value()

View File

@ -16,7 +16,7 @@ _LOGGER = logging.getLogger(__name__)
SUPPORT_HVAC = [HVAC_MODE_HEAT, HVAC_MODE_OFF]
def setup_platform(hass, config, add_entities, discovery_info=None):
async def async_setup_platform(hass, config, add_entities, discovery_info=None):
"""Set up the Tesla climate platform."""
devices = [
TeslaThermostat(device, hass.data[TESLA_DOMAIN]["controller"])
@ -57,10 +57,10 @@ class TeslaThermostat(TeslaDevice, ClimateDevice):
"""
return SUPPORT_HVAC
def update(self):
async def async_update(self):
"""Call by the Tesla device callback to update state."""
_LOGGER.debug("Updating: %s", self._name)
self.tesla_device.update()
await super().async_update()
self._target_temperature = self.tesla_device.get_goal_temp()
self._temperature = self.tesla_device.get_current_temp()
@ -83,17 +83,17 @@ class TeslaThermostat(TeslaDevice, ClimateDevice):
"""Return the temperature we try to reach."""
return self._target_temperature
def set_temperature(self, **kwargs):
async def async_set_temperature(self, **kwargs):
"""Set new target temperatures."""
_LOGGER.debug("Setting temperature for: %s", self._name)
temperature = kwargs.get(ATTR_TEMPERATURE)
if temperature:
self.tesla_device.set_temperature(temperature)
await self.tesla_device.set_temperature(temperature)
def set_hvac_mode(self, hvac_mode):
async def async_set_hvac_mode(self, hvac_mode):
"""Set new target hvac mode."""
_LOGGER.debug("Setting mode for: %s", self._name)
if hvac_mode == HVAC_MODE_OFF:
self.tesla_device.set_status(False)
await self.tesla_device.set_status(False)
elif hvac_mode == HVAC_MODE_HEAT:
self.tesla_device.set_status(True)
await self.tesla_device.set_status(True)

View File

@ -0,0 +1,24 @@
"""Const file for Tesla cars."""
DOMAIN = "tesla"
DATA_LISTENER = "listener"
TESLA_COMPONENTS = [
"sensor",
"lock",
"climate",
"binary_sensor",
"device_tracker",
"switch",
]
SENSOR_ICONS = {
"battery sensor": "mdi:battery",
"range sensor": "mdi:gauge",
"mileage sensor": "mdi:counter",
"parking brake sensor": "mdi:car-brake-parking",
"charger sensor": "mdi:ev-station",
"charger switch": "mdi:battery-charging",
"update switch": "mdi:update",
"maxrange switch": "mdi:gauge-full",
"temperature sensor": "mdi:thermometer",
"location tracker": "mdi:crosshairs-gps",
"charging rate sensor": "mdi:speedometer",
}

View File

@ -1,7 +1,7 @@
"""Support for tracking Tesla cars."""
import logging
from homeassistant.helpers.event import track_utc_time_change
from homeassistant.helpers.event import async_track_utc_time_change
from homeassistant.util import slugify
from . import DOMAIN as TESLA_DOMAIN
@ -9,11 +9,13 @@ from . import DOMAIN as TESLA_DOMAIN
_LOGGER = logging.getLogger(__name__)
def setup_scanner(hass, config, see, discovery_info=None):
async def async_setup_scanner(hass, config, async_see, discovery_info=None):
"""Set up the Tesla tracker."""
TeslaDeviceTracker(
hass, config, see, hass.data[TESLA_DOMAIN]["devices"]["devices_tracker"]
tracker = TeslaDeviceTracker(
hass, config, async_see, hass.data[TESLA_DOMAIN]["devices"]["devices_tracker"]
)
await tracker.update_info()
async_track_utc_time_change(hass, tracker.update_info, second=range(0, 60, 30))
return True
@ -25,14 +27,11 @@ class TeslaDeviceTracker:
self.hass = hass
self.see = see
self.devices = tesla_devices
self._update_info()
track_utc_time_change(self.hass, self._update_info, second=range(0, 60, 30))
def _update_info(self, now=None):
async def update_info(self, now=None):
"""Update the device info."""
for device in self.devices:
device.update()
await device.async_update()
name = device.name
_LOGGER.debug("Updating device position: %s", name)
dev_id = slugify(device.uniq_name)
@ -41,6 +40,6 @@ class TeslaDeviceTracker:
lat = location["latitude"]
lon = location["longitude"]
attrs = {"trackr_id": dev_id, "id": dev_id, "name": name}
self.see(
await self.see(
dev_id=dev_id, host_name=name, gps=(lat, lon), attributes=attrs
)

View File

@ -9,7 +9,7 @@ from . import DOMAIN as TESLA_DOMAIN, TeslaDevice
_LOGGER = logging.getLogger(__name__)
def setup_platform(hass, config, add_entities, discovery_info=None):
async def async_setup_platform(hass, config, add_entities, discovery_info=None):
"""Set up the Tesla lock platform."""
devices = [
TeslaLock(device, hass.data[TESLA_DOMAIN]["controller"])
@ -26,23 +26,23 @@ class TeslaLock(TeslaDevice, LockDevice):
self._state = None
super().__init__(tesla_device, controller)
def lock(self, **kwargs):
async def async_lock(self, **kwargs):
"""Send the lock command."""
_LOGGER.debug("Locking doors for: %s", self._name)
self.tesla_device.lock()
await self.tesla_device.lock()
def unlock(self, **kwargs):
async def async_unlock(self, **kwargs):
"""Send the unlock command."""
_LOGGER.debug("Unlocking doors for: %s", self._name)
self.tesla_device.unlock()
await self.tesla_device.unlock()
@property
def is_locked(self):
"""Get whether the lock is in locked state."""
return self._state == STATE_LOCKED
def update(self):
async def async_update(self):
"""Update state of the lock."""
_LOGGER.debug("Updating state for: %s", self._name)
self.tesla_device.update()
await super().async_update()
self._state = STATE_LOCKED if self.tesla_device.is_locked() else STATE_UNLOCKED

View File

@ -2,7 +2,7 @@
"domain": "tesla",
"name": "Tesla",
"documentation": "https://www.home-assistant.io/integrations/tesla",
"requirements": ["teslajsonpy==0.0.26"],
"requirements": ["teslajsonpy==0.2.0"],
"dependencies": [],
"codeowners": ["@zabuldon"]
}

View File

@ -1,5 +1,4 @@
"""Support for the Tesla sensors."""
from datetime import timedelta
import logging
from homeassistant.const import (
@ -14,10 +13,8 @@ from . import DOMAIN as TESLA_DOMAIN, TeslaDevice
_LOGGER = logging.getLogger(__name__)
SCAN_INTERVAL = timedelta(minutes=5)
def setup_platform(hass, config, add_entities, discovery_info=None):
async def async_setup_platform(hass, config, add_entities, discovery_info=None):
"""Set up the Tesla sensor platform."""
controller = hass.data[TESLA_DOMAIN]["devices"]["controller"]
devices = []
@ -26,7 +23,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None):
if device.bin_type == 0x4:
devices.append(TeslaSensor(device, controller, "inside"))
devices.append(TeslaSensor(device, controller, "outside"))
else:
elif device.bin_type in [0xA, 0xB, 0x5]:
devices.append(TeslaSensor(device, controller))
add_entities(devices, True)
@ -62,10 +59,10 @@ class TeslaSensor(TeslaDevice, Entity):
"""Return the unit_of_measurement of the device."""
return self._unit
def update(self):
async def async_update(self):
"""Update the state from the sensor."""
_LOGGER.debug("Updating sensor: %s", self._name)
self.tesla_device.update()
await super().async_update()
units = self.tesla_device.measurement
if self.tesla_device.bin_type == 0x4:

View File

@ -9,7 +9,7 @@ from . import DOMAIN as TESLA_DOMAIN, TeslaDevice
_LOGGER = logging.getLogger(__name__)
def setup_platform(hass, config, add_entities, discovery_info=None):
async def async_setup_platform(hass, config, add_entities, discovery_info=None):
"""Set up the Tesla switch platform."""
controller = hass.data[TESLA_DOMAIN]["controller"]
devices = []
@ -30,25 +30,25 @@ class ChargerSwitch(TeslaDevice, SwitchDevice):
self._state = None
super().__init__(tesla_device, controller)
def turn_on(self, **kwargs):
async def async_turn_on(self, **kwargs):
"""Send the on command."""
_LOGGER.debug("Enable charging: %s", self._name)
self.tesla_device.start_charge()
await self.tesla_device.start_charge()
def turn_off(self, **kwargs):
async def async_turn_off(self, **kwargs):
"""Send the off command."""
_LOGGER.debug("Disable charging for: %s", self._name)
self.tesla_device.stop_charge()
await self.tesla_device.stop_charge()
@property
def is_on(self):
"""Get whether the switch is in on state."""
return self._state == STATE_ON
def update(self):
async def async_update(self):
"""Update the state of the switch."""
_LOGGER.debug("Updating state for: %s", self._name)
self.tesla_device.update()
await super().async_update()
self._state = STATE_ON if self.tesla_device.is_charging() else STATE_OFF
@ -56,29 +56,29 @@ class RangeSwitch(TeslaDevice, SwitchDevice):
"""Representation of a Tesla max range charging switch."""
def __init__(self, tesla_device, controller):
"""Initialise of the switch."""
"""Initialise the switch."""
self._state = None
super().__init__(tesla_device, controller)
def turn_on(self, **kwargs):
async def async_turn_on(self, **kwargs):
"""Send the on command."""
_LOGGER.debug("Enable max range charging: %s", self._name)
self.tesla_device.set_max()
await self.tesla_device.set_max()
def turn_off(self, **kwargs):
async def async_turn_off(self, **kwargs):
"""Send the off command."""
_LOGGER.debug("Disable max range charging: %s", self._name)
self.tesla_device.set_standard()
await self.tesla_device.set_standard()
@property
def is_on(self):
"""Get whether the switch is in on state."""
return self._state
def update(self):
async def async_update(self):
"""Update the state of the switch."""
_LOGGER.debug("Updating state for: %s", self._name)
self.tesla_device.update()
await super().async_update()
self._state = bool(self.tesla_device.is_maxrange())
@ -86,18 +86,19 @@ class UpdateSwitch(TeslaDevice, SwitchDevice):
"""Representation of a Tesla update switch."""
def __init__(self, tesla_device, controller):
"""Initialise of the switch."""
"""Initialise the switch."""
self._state = None
tesla_device.type = "update switch"
super().__init__(tesla_device, controller)
self._name = self._name.replace("charger", "update")
self.tesla_id = self.tesla_id.replace("charger", "update")
def turn_on(self, **kwargs):
async def async_turn_on(self, **kwargs):
"""Send the on command."""
_LOGGER.debug("Enable updates: %s %s", self._name, self.tesla_device.id())
self.controller.set_updates(self.tesla_device.id(), True)
def turn_off(self, **kwargs):
async def async_turn_off(self, **kwargs):
"""Send the off command."""
_LOGGER.debug("Disable updates: %s %s", self._name, self.tesla_device.id())
self.controller.set_updates(self.tesla_device.id(), False)
@ -107,8 +108,9 @@ class UpdateSwitch(TeslaDevice, SwitchDevice):
"""Get whether the switch is in on state."""
return self._state
def update(self):
async def async_update(self):
"""Update the state of the switch."""
car_id = self.tesla_device.id()
_LOGGER.debug("Updating state for: %s %s", self._name, car_id)
await super().async_update()
self._state = bool(self.controller.get_updates(car_id))

View File

@ -1898,7 +1898,7 @@ temperusb==1.5.3
# tensorflow==1.13.2
# homeassistant.components.tesla
teslajsonpy==0.0.26
teslajsonpy==0.2.0
# homeassistant.components.thermoworks_smoke
thermoworks_smoke==0.1.8