Remove tesla integration (#55988)

Co-authored-by: Paulus Schoutsen <balloob@gmail.com>
This commit is contained in:
Alan Tse 2021-09-08 22:12:03 -07:00 committed by GitHub
parent 675426dc25
commit 98ecf2888c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
51 changed files with 0 additions and 2240 deletions

View File

@ -1049,14 +1049,6 @@ omit =
homeassistant/components/telnet/switch.py
homeassistant/components/temper/sensor.py
homeassistant/components/tensorflow/image_processing.py
homeassistant/components/tesla/__init__.py
homeassistant/components/tesla/binary_sensor.py
homeassistant/components/tesla/climate.py
homeassistant/components/tesla/const.py
homeassistant/components/tesla/device_tracker.py
homeassistant/components/tesla/lock.py
homeassistant/components/tesla/sensor.py
homeassistant/components/tesla/switch.py
homeassistant/components/tfiac/climate.py
homeassistant/components/thermoworks_smoke/sensor.py
homeassistant/components/thethingsnetwork/*

View File

@ -518,7 +518,6 @@ homeassistant/components/tasmota/* @emontnemery
homeassistant/components/tautulli/* @ludeeus
homeassistant/components/tellduslive/* @fredrike
homeassistant/components/template/* @PhracturedBlue @tetienne @home-assistant/core
homeassistant/components/tesla/* @zabuldon @alandtse
homeassistant/components/tfiac/* @fredrike @mellado
homeassistant/components/thethingsnetwork/* @fabaff
homeassistant/components/threshold/* @fabaff

View File

@ -1,357 +0,0 @@
"""Support for Tesla cars."""
import asyncio
from collections import defaultdict
from datetime import timedelta
import logging
import async_timeout
import httpx
from teslajsonpy import Controller as TeslaAPI
from teslajsonpy.exceptions import IncompleteCredentials, TeslaException
import voluptuous as vol
from homeassistant.config_entries import SOURCE_IMPORT
from homeassistant.const import (
ATTR_BATTERY_CHARGING,
ATTR_BATTERY_LEVEL,
CONF_ACCESS_TOKEN,
CONF_PASSWORD,
CONF_SCAN_INTERVAL,
CONF_TOKEN,
CONF_USERNAME,
EVENT_HOMEASSISTANT_CLOSE,
HTTP_UNAUTHORIZED,
)
from homeassistant.core import callback
from homeassistant.exceptions import ConfigEntryAuthFailed, ConfigEntryNotReady
from homeassistant.helpers import config_validation as cv
from homeassistant.helpers.httpx_client import SERVER_SOFTWARE, USER_AGENT
from homeassistant.helpers.update_coordinator import (
CoordinatorEntity,
DataUpdateCoordinator,
UpdateFailed,
)
from homeassistant.util import slugify
from .config_flow import CannotConnect, InvalidAuth, validate_input
from .const import (
CONF_EXPIRATION,
CONF_WAKE_ON_START,
DATA_LISTENER,
DEFAULT_SCAN_INTERVAL,
DEFAULT_WAKE_ON_START,
DOMAIN,
ICONS,
MIN_SCAN_INTERVAL,
PLATFORMS,
)
_LOGGER = logging.getLogger(__name__)
CONFIG_SCHEMA = vol.Schema(
{
DOMAIN: vol.Schema(
{
vol.Required(CONF_USERNAME): cv.string,
vol.Required(CONF_PASSWORD): cv.string,
vol.Optional(
CONF_SCAN_INTERVAL, default=DEFAULT_SCAN_INTERVAL
): vol.All(cv.positive_int, vol.Clamp(min=MIN_SCAN_INTERVAL)),
}
)
},
extra=vol.ALLOW_EXTRA,
)
@callback
def _async_save_tokens(hass, config_entry, access_token, refresh_token):
hass.config_entries.async_update_entry(
config_entry,
data={
**config_entry.data,
CONF_ACCESS_TOKEN: access_token,
CONF_TOKEN: refresh_token,
},
)
@callback
def _async_configured_emails(hass):
"""Return a set of configured Tesla emails."""
return {
entry.data[CONF_USERNAME]
for entry in hass.config_entries.async_entries(DOMAIN)
if CONF_USERNAME in entry.data
}
async def async_setup(hass, base_config):
"""Set up of Tesla component."""
def _update_entry(email, data=None, options=None):
data = data or {}
options = options or {
CONF_SCAN_INTERVAL: DEFAULT_SCAN_INTERVAL,
CONF_WAKE_ON_START: DEFAULT_WAKE_ON_START,
}
for entry in hass.config_entries.async_entries(DOMAIN):
if email != entry.title:
continue
hass.config_entries.async_update_entry(entry, data=data, options=options)
config = base_config.get(DOMAIN)
if not config:
return True
email = config[CONF_USERNAME]
password = config[CONF_PASSWORD]
scan_interval = config[CONF_SCAN_INTERVAL]
if email in _async_configured_emails(hass):
try:
info = await validate_input(hass, config)
except (CannotConnect, InvalidAuth):
return False
_update_entry(
email,
data={
CONF_USERNAME: email,
CONF_PASSWORD: password,
CONF_ACCESS_TOKEN: info[CONF_ACCESS_TOKEN],
CONF_TOKEN: info[CONF_TOKEN],
CONF_EXPIRATION: info[CONF_EXPIRATION],
},
options={CONF_SCAN_INTERVAL: scan_interval},
)
else:
hass.async_create_task(
hass.config_entries.flow.async_init(
DOMAIN,
context={"source": SOURCE_IMPORT},
data={CONF_USERNAME: email, CONF_PASSWORD: password},
)
)
hass.data.setdefault(DOMAIN, {})
hass.data[DOMAIN][email] = {CONF_SCAN_INTERVAL: scan_interval}
return True
async def async_setup_entry(hass, config_entry):
"""Set up Tesla as config entry."""
hass.data.setdefault(DOMAIN, {})
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)
email = config_entry.title
if email in hass.data[DOMAIN] and CONF_SCAN_INTERVAL in hass.data[DOMAIN][email]:
scan_interval = hass.data[DOMAIN][email][CONF_SCAN_INTERVAL]
hass.config_entries.async_update_entry(
config_entry, options={CONF_SCAN_INTERVAL: scan_interval}
)
hass.data[DOMAIN].pop(email)
try:
controller = TeslaAPI(
async_client,
email=config.get(CONF_USERNAME),
password=config.get(CONF_PASSWORD),
refresh_token=config[CONF_TOKEN],
access_token=config[CONF_ACCESS_TOKEN],
expiration=config.get(CONF_EXPIRATION, 0),
update_interval=config_entry.options.get(
CONF_SCAN_INTERVAL, DEFAULT_SCAN_INTERVAL
),
)
result = await controller.connect(
wake_if_asleep=config_entry.options.get(
CONF_WAKE_ON_START, DEFAULT_WAKE_ON_START
)
)
refresh_token = result["refresh_token"]
access_token = result["access_token"]
except IncompleteCredentials as ex:
await async_client.aclose()
raise ConfigEntryAuthFailed from ex
except httpx.ConnectTimeout as ex:
await async_client.aclose()
raise ConfigEntryNotReady from ex
except TeslaException as ex:
await async_client.aclose()
if ex.code == HTTP_UNAUTHORIZED:
raise ConfigEntryAuthFailed from ex
if ex.message in [
"VEHICLE_UNAVAILABLE",
"TOO_MANY_REQUESTS",
"SERVICE_MAINTENANCE",
"UPSTREAM_TIMEOUT",
]:
raise ConfigEntryNotReady(
f"Temporarily unable to communicate with Tesla API: {ex.message}"
) from ex
_LOGGER.error("Unable to communicate with Tesla API: %s", ex.message)
return False
async def _async_close_client(*_):
await async_client.aclose()
@callback
def _async_create_close_task():
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)
coordinator = TeslaDataUpdateCoordinator(
hass, config_entry=config_entry, controller=controller
)
# Fetch initial data so we have data when entities subscribe
entry_data = hass.data[DOMAIN][config_entry.entry_id] = {
"coordinator": coordinator,
"devices": defaultdict(list),
DATA_LISTENER: [config_entry.add_update_listener(update_listener)],
}
_LOGGER.debug("Connected to the Tesla API")
await coordinator.async_config_entry_first_refresh()
all_devices = controller.get_homeassistant_components()
if not all_devices:
return False
for device in all_devices:
entry_data["devices"][device.hass_type].append(device)
hass.config_entries.async_setup_platforms(config_entry, PLATFORMS)
return True
async def async_unload_entry(hass, config_entry) -> bool:
"""Unload a config entry."""
unload_ok = await hass.config_entries.async_unload_platforms(
config_entry, PLATFORMS
)
for listener in hass.data[DOMAIN][config_entry.entry_id][DATA_LISTENER]:
listener()
username = config_entry.title
if unload_ok:
hass.data[DOMAIN].pop(config_entry.entry_id)
_LOGGER.debug("Unloaded entry for %s", username)
return True
return False
async def update_listener(hass, config_entry):
"""Update when config_entry options update."""
controller = hass.data[DOMAIN][config_entry.entry_id]["coordinator"].controller
old_update_interval = controller.update_interval
controller.update_interval = config_entry.options.get(CONF_SCAN_INTERVAL)
if old_update_interval != controller.update_interval:
_LOGGER.debug(
"Changing scan_interval from %s to %s",
old_update_interval,
controller.update_interval,
)
class TeslaDataUpdateCoordinator(DataUpdateCoordinator):
"""Class to manage fetching Tesla data."""
def __init__(self, hass, *, config_entry, controller):
"""Initialize global Tesla data updater."""
self.controller = controller
self.config_entry = config_entry
update_interval = timedelta(seconds=MIN_SCAN_INTERVAL)
super().__init__(
hass,
_LOGGER,
name=DOMAIN,
update_interval=update_interval,
)
async def _async_update_data(self):
"""Fetch data from API endpoint."""
if self.controller.is_token_refreshed():
result = self.controller.get_tokens()
refresh_token = result["refresh_token"]
access_token = result["access_token"]
_async_save_tokens(
self.hass, self.config_entry, access_token, refresh_token
)
_LOGGER.debug("Saving new tokens in config_entry")
try:
# Note: asyncio.TimeoutError and aiohttp.ClientError are already
# handled by the data update coordinator.
async with async_timeout.timeout(30):
return await self.controller.update()
except TeslaException as err:
raise UpdateFailed(f"Error communicating with API: {err}") from err
class TeslaDevice(CoordinatorEntity):
"""Representation of a Tesla device."""
def __init__(self, tesla_device, coordinator):
"""Initialise the Tesla device."""
super().__init__(coordinator)
self.tesla_device = tesla_device
self._name = self.tesla_device.name
self._unique_id = slugify(self.tesla_device.uniq_name)
self._attributes = self.tesla_device.attrs.copy()
@property
def name(self):
"""Return the name of the device."""
return self._name
@property
def unique_id(self) -> str:
"""Return a unique ID."""
return self._unique_id
@property
def icon(self):
"""Return the icon of the sensor."""
if self.device_class:
return None
return ICONS.get(self.tesla_device.type)
@property
def extra_state_attributes(self):
"""Return the state attributes of the device."""
attr = self._attributes
if self.tesla_device.has_battery():
attr[ATTR_BATTERY_LEVEL] = self.tesla_device.battery_level()
attr[ATTR_BATTERY_CHARGING] = self.tesla_device.battery_charging()
return attr
@property
def device_info(self):
"""Return the device_info of the device."""
return {
"identifiers": {(DOMAIN, self.tesla_device.id())},
"name": self.tesla_device.car_name(),
"manufacturer": "Tesla",
"model": self.tesla_device.car_type,
"sw_version": self.tesla_device.car_version,
}
async def async_added_to_hass(self):
"""Register state update callback."""
self.async_on_remove(self.coordinator.async_add_listener(self.refresh))
@callback
def refresh(self) -> None:
"""Refresh the state of the device.
This assumes the coordinator has updated the controller.
"""
self.tesla_device.refresh()
self._attributes = self.tesla_device.attrs.copy()
self.async_write_ha_state()

View File

@ -1,39 +0,0 @@
"""Support for Tesla binary sensor."""
from homeassistant.components.binary_sensor import DEVICE_CLASSES, BinarySensorEntity
from . import DOMAIN as TESLA_DOMAIN, TeslaDevice
async def async_setup_entry(hass, config_entry, async_add_entities):
"""Set up the Tesla binary_sensors by config_entry."""
async_add_entities(
[
TeslaBinarySensor(
device,
hass.data[TESLA_DOMAIN][config_entry.entry_id]["coordinator"],
)
for device in hass.data[TESLA_DOMAIN][config_entry.entry_id]["devices"][
"binary_sensor"
]
],
True,
)
class TeslaBinarySensor(TeslaDevice, BinarySensorEntity):
"""Implement an Tesla binary sensor for parking and charger."""
@property
def device_class(self):
"""Return the class of this binary sensor."""
return (
self.tesla_device.sensor_type
if self.tesla_device.sensor_type in DEVICE_CLASSES
else None
)
@property
def is_on(self):
"""Return the state of the binary sensor."""
return self.tesla_device.get_value()

View File

@ -1,120 +0,0 @@
"""Support for Tesla HVAC system."""
from __future__ import annotations
import logging
from teslajsonpy.exceptions import UnknownPresetMode
from homeassistant.components.climate import ClimateEntity
from homeassistant.components.climate.const import (
HVAC_MODE_HEAT_COOL,
HVAC_MODE_OFF,
SUPPORT_PRESET_MODE,
SUPPORT_TARGET_TEMPERATURE,
)
from homeassistant.const import ATTR_TEMPERATURE, TEMP_CELSIUS, TEMP_FAHRENHEIT
from . import DOMAIN as TESLA_DOMAIN, TeslaDevice
_LOGGER = logging.getLogger(__name__)
SUPPORT_HVAC = [HVAC_MODE_HEAT_COOL, HVAC_MODE_OFF]
async def async_setup_entry(hass, config_entry, async_add_entities):
"""Set up the Tesla binary_sensors by config_entry."""
async_add_entities(
[
TeslaThermostat(
device,
hass.data[TESLA_DOMAIN][config_entry.entry_id]["coordinator"],
)
for device in hass.data[TESLA_DOMAIN][config_entry.entry_id]["devices"][
"climate"
]
],
True,
)
class TeslaThermostat(TeslaDevice, ClimateEntity):
"""Representation of a Tesla climate."""
@property
def supported_features(self):
"""Return the list of supported features."""
return SUPPORT_TARGET_TEMPERATURE | SUPPORT_PRESET_MODE
@property
def hvac_mode(self):
"""Return hvac operation ie. heat, cool mode.
Need to be one of HVAC_MODE_*.
"""
if self.tesla_device.is_hvac_enabled():
return HVAC_MODE_HEAT_COOL
return HVAC_MODE_OFF
@property
def hvac_modes(self):
"""Return the list of available hvac operation modes.
Need to be a subset of HVAC_MODES.
"""
return SUPPORT_HVAC
@property
def temperature_unit(self):
"""Return the unit of measurement."""
if self.tesla_device.measurement == "F":
return TEMP_FAHRENHEIT
return TEMP_CELSIUS
@property
def current_temperature(self):
"""Return the current temperature."""
return self.tesla_device.get_current_temp()
@property
def target_temperature(self):
"""Return the temperature we try to reach."""
return self.tesla_device.get_goal_temp()
async def async_set_temperature(self, **kwargs):
"""Set new target temperatures."""
temperature = kwargs.get(ATTR_TEMPERATURE)
if temperature:
_LOGGER.debug("%s: Setting temperature to %s", self.name, temperature)
await self.tesla_device.set_temperature(temperature)
async def async_set_hvac_mode(self, hvac_mode):
"""Set new target hvac mode."""
_LOGGER.debug("%s: Setting hvac mode to %s", self.name, hvac_mode)
if hvac_mode == HVAC_MODE_OFF:
await self.tesla_device.set_status(False)
elif hvac_mode == HVAC_MODE_HEAT_COOL:
await self.tesla_device.set_status(True)
async def async_set_preset_mode(self, preset_mode: str) -> None:
"""Set new preset mode."""
_LOGGER.debug("%s: Setting preset_mode to: %s", self.name, preset_mode)
try:
await self.tesla_device.set_preset_mode(preset_mode)
except UnknownPresetMode as ex:
_LOGGER.error("%s", ex.message)
@property
def preset_mode(self) -> str | None:
"""Return the current preset mode, e.g., home, away, temp.
Requires SUPPORT_PRESET_MODE.
"""
return self.tesla_device.preset_mode
@property
def preset_modes(self) -> list[str] | None:
"""Return a list of available preset modes.
Requires SUPPORT_PRESET_MODE.
"""
return self.tesla_device.preset_modes

View File

@ -1,191 +0,0 @@
"""Tesla Config Flow."""
import logging
import httpx
from teslajsonpy import Controller as TeslaAPI, TeslaException
from teslajsonpy.exceptions import IncompleteCredentials
import voluptuous as vol
from homeassistant import config_entries, core, exceptions
from homeassistant.const import (
CONF_ACCESS_TOKEN,
CONF_PASSWORD,
CONF_SCAN_INTERVAL,
CONF_TOKEN,
CONF_USERNAME,
HTTP_UNAUTHORIZED,
)
from homeassistant.core import callback
from homeassistant.helpers import config_validation as cv
from homeassistant.helpers.httpx_client import SERVER_SOFTWARE, USER_AGENT
from .const import (
CONF_EXPIRATION,
CONF_MFA,
CONF_WAKE_ON_START,
DEFAULT_SCAN_INTERVAL,
DEFAULT_WAKE_ON_START,
DOMAIN,
MIN_SCAN_INTERVAL,
)
_LOGGER = logging.getLogger(__name__)
class TeslaConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
"""Handle a config flow for Tesla."""
VERSION = 1
def __init__(self) -> None:
"""Initialize the tesla flow."""
self.username = None
self.reauth = False
async def async_step_import(self, import_config):
"""Import a config entry from configuration.yaml."""
return await self.async_step_user(import_config)
async def async_step_user(self, user_input=None):
"""Handle the start of the config flow."""
errors = {}
if user_input is not None:
existing_entry = self._async_entry_for_username(user_input[CONF_USERNAME])
if existing_entry and not self.reauth:
return self.async_abort(reason="already_configured")
try:
info = await validate_input(self.hass, user_input)
except CannotConnect:
errors["base"] = "cannot_connect"
except InvalidAuth:
errors["base"] = "invalid_auth"
if not errors:
if existing_entry:
self.hass.config_entries.async_update_entry(
existing_entry, data=info
)
await self.hass.config_entries.async_reload(existing_entry.entry_id)
return self.async_abort(reason="reauth_successful")
return self.async_create_entry(
title=user_input[CONF_USERNAME], data=info
)
return self.async_show_form(
step_id="user",
data_schema=self._async_schema(),
errors=errors,
description_placeholders={},
)
async def async_step_reauth(self, data):
"""Handle configuration by re-auth."""
self.username = data[CONF_USERNAME]
self.reauth = True
return await self.async_step_user()
@staticmethod
@callback
def async_get_options_flow(config_entry):
"""Get the options flow for this handler."""
return OptionsFlowHandler(config_entry)
@callback
def _async_schema(self):
"""Fetch schema with defaults."""
return vol.Schema(
{
vol.Required(CONF_USERNAME, default=self.username): str,
vol.Required(CONF_PASSWORD): str,
vol.Optional(CONF_MFA): str,
}
)
@callback
def _async_entry_for_username(self, username):
"""Find an existing entry for a username."""
for entry in self._async_current_entries():
if entry.data.get(CONF_USERNAME) == username:
return entry
return None
class OptionsFlowHandler(config_entries.OptionsFlow):
"""Handle a option flow for Tesla."""
def __init__(self, config_entry: config_entries.ConfigEntry) -> None:
"""Initialize options flow."""
self.config_entry = config_entry
async def async_step_init(self, user_input=None):
"""Handle options flow."""
if user_input is not None:
return self.async_create_entry(title="", data=user_input)
data_schema = vol.Schema(
{
vol.Optional(
CONF_SCAN_INTERVAL,
default=self.config_entry.options.get(
CONF_SCAN_INTERVAL, DEFAULT_SCAN_INTERVAL
),
): vol.All(cv.positive_int, vol.Clamp(min=MIN_SCAN_INTERVAL)),
vol.Optional(
CONF_WAKE_ON_START,
default=self.config_entry.options.get(
CONF_WAKE_ON_START, DEFAULT_WAKE_ON_START
),
): bool,
}
)
return self.async_show_form(step_id="init", data_schema=data_schema)
async def validate_input(hass: core.HomeAssistant, data):
"""Validate the user input allows us to connect.
Data has the keys from DATA_SCHEMA with values provided by the user.
"""
config = {}
async_client = httpx.AsyncClient(headers={USER_AGENT: SERVER_SOFTWARE}, timeout=60)
try:
controller = TeslaAPI(
async_client,
email=data[CONF_USERNAME],
password=data[CONF_PASSWORD],
update_interval=DEFAULT_SCAN_INTERVAL,
)
result = await controller.connect(
test_login=True, mfa_code=(data[CONF_MFA] if CONF_MFA in data else "")
)
config[CONF_TOKEN] = result["refresh_token"]
config[CONF_ACCESS_TOKEN] = result["access_token"]
config[CONF_EXPIRATION] = result[CONF_EXPIRATION]
config[CONF_USERNAME] = data[CONF_USERNAME]
config[CONF_PASSWORD] = data[CONF_PASSWORD]
except IncompleteCredentials as ex:
_LOGGER.error("Authentication error: %s %s", ex.message, ex)
raise InvalidAuth() from ex
except TeslaException as ex:
if ex.code == HTTP_UNAUTHORIZED:
_LOGGER.error("Invalid credentials: %s", ex)
raise InvalidAuth() from ex
_LOGGER.error("Unable to communicate with Tesla API: %s", ex.message)
raise CannotConnect() from ex
finally:
await async_client.aclose()
_LOGGER.debug("Credentials successfully connected to the Tesla API")
return config
class CannotConnect(exceptions.HomeAssistantError):
"""Error to indicate we cannot connect."""
class InvalidAuth(exceptions.HomeAssistantError):
"""Error to indicate there is invalid auth."""

View File

@ -1,33 +0,0 @@
"""Const file for Tesla cars."""
CONF_EXPIRATION = "expiration"
CONF_WAKE_ON_START = "enable_wake_on_start"
CONF_MFA = "mfa"
DOMAIN = "tesla"
DATA_LISTENER = "listener"
DEFAULT_SCAN_INTERVAL = 660
DEFAULT_WAKE_ON_START = False
MIN_SCAN_INTERVAL = 60
PLATFORMS = [
"sensor",
"lock",
"climate",
"binary_sensor",
"device_tracker",
"switch",
]
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",
"sentry mode switch": "mdi:shield-car",
}

View File

@ -1,57 +0,0 @@
"""Support for tracking Tesla cars."""
from __future__ import annotations
from homeassistant.components.device_tracker import SOURCE_TYPE_GPS
from homeassistant.components.device_tracker.config_entry import TrackerEntity
from . import DOMAIN as TESLA_DOMAIN, TeslaDevice
async def async_setup_entry(hass, config_entry, async_add_entities):
"""Set up the Tesla binary_sensors by config_entry."""
entities = [
TeslaDeviceEntity(
device,
hass.data[TESLA_DOMAIN][config_entry.entry_id]["coordinator"],
)
for device in hass.data[TESLA_DOMAIN][config_entry.entry_id]["devices"][
"devices_tracker"
]
]
async_add_entities(entities, True)
class TeslaDeviceEntity(TeslaDevice, TrackerEntity):
"""A class representing a Tesla device."""
@property
def latitude(self) -> float | None:
"""Return latitude value of the device."""
location = self.tesla_device.get_location()
return self.tesla_device.get_location().get("latitude") if location else None
@property
def longitude(self) -> float | None:
"""Return longitude value of the device."""
location = self.tesla_device.get_location()
return self.tesla_device.get_location().get("longitude") if location else None
@property
def source_type(self):
"""Return the source type, eg gps or router, of the device."""
return SOURCE_TYPE_GPS
@property
def extra_state_attributes(self):
"""Return the state attributes of the device."""
attr = super().extra_state_attributes.copy()
location = self.tesla_device.get_location()
if location:
attr.update(
{
"trackr_id": self.unique_id,
"heading": location["heading"],
"speed": location["speed"],
}
)
return attr

View File

@ -1,41 +0,0 @@
"""Support for Tesla door locks."""
import logging
from homeassistant.components.lock import LockEntity
from . import DOMAIN as TESLA_DOMAIN, TeslaDevice
_LOGGER = logging.getLogger(__name__)
async def async_setup_entry(hass, config_entry, async_add_entities):
"""Set up the Tesla binary_sensors by config_entry."""
entities = [
TeslaLock(
device,
hass.data[TESLA_DOMAIN][config_entry.entry_id]["coordinator"],
)
for device in hass.data[TESLA_DOMAIN][config_entry.entry_id]["devices"]["lock"]
]
async_add_entities(entities, True)
class TeslaLock(TeslaDevice, LockEntity):
"""Representation of a Tesla door lock."""
async def async_lock(self, **kwargs):
"""Send the lock command."""
_LOGGER.debug("Locking doors for: %s", self.name)
await self.tesla_device.lock()
async def async_unlock(self, **kwargs):
"""Send the unlock command."""
_LOGGER.debug("Unlocking doors for: %s", self.name)
await self.tesla_device.unlock()
@property
def is_locked(self):
"""Get whether the lock is in locked state."""
if self.tesla_device.is_locked() is None:
return None
return self.tesla_device.is_locked()

View File

@ -1,23 +0,0 @@
{
"domain": "tesla",
"name": "Tesla",
"config_flow": true,
"documentation": "https://www.home-assistant.io/integrations/tesla",
"requirements": ["teslajsonpy==0.18.3"],
"codeowners": ["@zabuldon", "@alandtse"],
"dhcp": [
{
"hostname": "tesla_*",
"macaddress": "4CFCAA*"
},
{
"hostname": "tesla_*",
"macaddress": "044EAF*"
},
{
"hostname": "tesla_*",
"macaddress": "98ED5C*"
}
],
"iot_class": "cloud_polling"
}

View File

@ -1,97 +0,0 @@
"""Support for the Tesla sensors."""
from __future__ import annotations
from homeassistant.components.sensor import DEVICE_CLASSES, SensorEntity
from homeassistant.const import (
LENGTH_KILOMETERS,
LENGTH_MILES,
TEMP_CELSIUS,
TEMP_FAHRENHEIT,
)
from homeassistant.util.distance import convert
from . import DOMAIN as TESLA_DOMAIN, TeslaDevice
async def async_setup_entry(hass, config_entry, async_add_entities):
"""Set up the Tesla binary_sensors by config_entry."""
coordinator = hass.data[TESLA_DOMAIN][config_entry.entry_id]["coordinator"]
entities = []
for device in hass.data[TESLA_DOMAIN][config_entry.entry_id]["devices"]["sensor"]:
if device.type == "temperature sensor":
entities.append(TeslaSensor(device, coordinator, "inside"))
entities.append(TeslaSensor(device, coordinator, "outside"))
else:
entities.append(TeslaSensor(device, coordinator))
async_add_entities(entities, True)
class TeslaSensor(TeslaDevice, SensorEntity):
"""Representation of Tesla sensors."""
def __init__(self, tesla_device, coordinator, sensor_type=None):
"""Initialize of the sensor."""
super().__init__(tesla_device, coordinator)
self.type = sensor_type
if self.type:
self._name = f"{super().name} ({self.type})"
self._unique_id = f"{super().unique_id}_{self.type}"
@property
def native_value(self) -> float | None:
"""Return the state of the sensor."""
if self.tesla_device.type == "temperature sensor":
if self.type == "outside":
return self.tesla_device.get_outside_temp()
return self.tesla_device.get_inside_temp()
if self.tesla_device.type in ("range sensor", "mileage sensor"):
units = self.tesla_device.measurement
if units == "LENGTH_MILES":
return self.tesla_device.get_value()
return round(
convert(self.tesla_device.get_value(), LENGTH_MILES, LENGTH_KILOMETERS),
2,
)
if self.tesla_device.type == "charging rate sensor":
return self.tesla_device.charging_rate
return self.tesla_device.get_value()
@property
def native_unit_of_measurement(self) -> str | None:
"""Return the unit_of_measurement of the device."""
units = self.tesla_device.measurement
if units == "F":
return TEMP_FAHRENHEIT
if units == "C":
return TEMP_CELSIUS
if units == "LENGTH_MILES":
return LENGTH_MILES
if units == "LENGTH_KILOMETERS":
return LENGTH_KILOMETERS
return units
@property
def device_class(self) -> str | None:
"""Return the device_class of the device."""
return (
self.tesla_device.device_class
if self.tesla_device.device_class in DEVICE_CLASSES
else None
)
@property
def extra_state_attributes(self):
"""Return the state attributes of the device."""
attr = self._attributes.copy()
if self.tesla_device.type == "charging rate sensor":
attr.update(
{
"time_left": self.tesla_device.time_left,
"added_range": self.tesla_device.added_range,
"charge_energy_added": self.tesla_device.charge_energy_added,
"charge_current_request": self.tesla_device.charge_current_request,
"charger_actual_current": self.tesla_device.charger_actual_current,
"charger_voltage": self.tesla_device.charger_voltage,
}
)
return attr

View File

@ -1,34 +0,0 @@
{
"config": {
"error": {
"cannot_connect": "[%key:common::config_flow::error::cannot_connect%]",
"already_configured": "[%key:common::config_flow::abort::already_configured_account%]",
"invalid_auth": "[%key:common::config_flow::error::invalid_auth%]"
},
"abort": {
"reauth_successful": "[%key:common::config_flow::abort::reauth_successful%]",
"already_configured": "[%key:common::config_flow::abort::already_configured_account%]"
},
"step": {
"user": {
"data": {
"username": "[%key:common::config_flow::data::email%]",
"password": "[%key:common::config_flow::data::password%]",
"mfa": "MFA Code (optional)"
},
"description": "Please enter your information.",
"title": "Tesla - Configuration"
}
}
},
"options": {
"step": {
"init": {
"data": {
"scan_interval": "Seconds between scans",
"enable_wake_on_start": "Force cars awake on startup"
}
}
}
}
}

View File

@ -1,130 +0,0 @@
"""Support for Tesla charger switches."""
import logging
from homeassistant.components.switch import SwitchEntity
from . import DOMAIN as TESLA_DOMAIN, TeslaDevice
_LOGGER = logging.getLogger(__name__)
async def async_setup_entry(hass, config_entry, async_add_entities):
"""Set up the Tesla binary_sensors by config_entry."""
coordinator = hass.data[TESLA_DOMAIN][config_entry.entry_id]["coordinator"]
entities = []
for device in hass.data[TESLA_DOMAIN][config_entry.entry_id]["devices"]["switch"]:
if device.type == "charger switch":
entities.append(ChargerSwitch(device, coordinator))
entities.append(UpdateSwitch(device, coordinator))
elif device.type == "maxrange switch":
entities.append(RangeSwitch(device, coordinator))
elif device.type == "sentry mode switch":
entities.append(SentryModeSwitch(device, coordinator))
async_add_entities(entities, True)
class ChargerSwitch(TeslaDevice, SwitchEntity):
"""Representation of a Tesla charger switch."""
async def async_turn_on(self, **kwargs):
"""Send the on command."""
_LOGGER.debug("Enable charging: %s", self.name)
await self.tesla_device.start_charge()
self.async_write_ha_state()
async def async_turn_off(self, **kwargs):
"""Send the off command."""
_LOGGER.debug("Disable charging for: %s", self.name)
await self.tesla_device.stop_charge()
self.async_write_ha_state()
@property
def is_on(self):
"""Get whether the switch is in on state."""
if self.tesla_device.is_charging() is None:
return None
return self.tesla_device.is_charging()
class RangeSwitch(TeslaDevice, SwitchEntity):
"""Representation of a Tesla max range charging switch."""
async def async_turn_on(self, **kwargs):
"""Send the on command."""
_LOGGER.debug("Enable max range charging: %s", self.name)
await self.tesla_device.set_max()
self.async_write_ha_state()
async def async_turn_off(self, **kwargs):
"""Send the off command."""
_LOGGER.debug("Disable max range charging: %s", self.name)
await self.tesla_device.set_standard()
self.async_write_ha_state()
@property
def is_on(self):
"""Get whether the switch is in on state."""
if self.tesla_device.is_maxrange() is None:
return None
return bool(self.tesla_device.is_maxrange())
class UpdateSwitch(TeslaDevice, SwitchEntity):
"""Representation of a Tesla update switch."""
def __init__(self, tesla_device, coordinator):
"""Initialise the switch."""
super().__init__(tesla_device, coordinator)
self.controller = coordinator.controller
@property
def name(self):
"""Return the name of the device."""
return super().name.replace("charger", "update")
@property
def unique_id(self) -> str:
"""Return a unique ID."""
return super().unique_id.replace("charger", "update")
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)
self.async_write_ha_state()
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)
self.async_write_ha_state()
@property
def is_on(self):
"""Get whether the switch is in on state."""
if self.controller.get_updates(self.tesla_device.id()) is None:
return None
return bool(self.controller.get_updates(self.tesla_device.id()))
class SentryModeSwitch(TeslaDevice, SwitchEntity):
"""Representation of a Tesla sentry mode switch."""
async def async_turn_on(self, **kwargs):
"""Send the on command."""
_LOGGER.debug("Enable sentry mode: %s", self.name)
await self.tesla_device.enable_sentry_mode()
self.async_write_ha_state()
async def async_turn_off(self, **kwargs):
"""Send the off command."""
_LOGGER.debug("Disable sentry mode: %s", self.name)
await self.tesla_device.disable_sentry_mode()
self.async_write_ha_state()
@property
def is_on(self):
"""Get whether the switch is in on state."""
if self.tesla_device.is_on() is None:
return None
return self.tesla_device.is_on()

View File

@ -1,34 +0,0 @@
{
"config": {
"abort": {
"already_configured": "El compte ja ha estat configurat",
"reauth_successful": "Re-autenticaci\u00f3 realitzada correctament"
},
"error": {
"already_configured": "El compte ja ha estat configurat",
"cannot_connect": "Ha fallat la connexi\u00f3",
"invalid_auth": "Autenticaci\u00f3 inv\u00e0lida"
},
"step": {
"user": {
"data": {
"mfa": "Codi MFA (opcional)",
"password": "Contrasenya",
"username": "Correu electr\u00f2nic"
},
"description": "Introdueix la teva informaci\u00f3.",
"title": "Configuraci\u00f3 de Tesla"
}
}
},
"options": {
"step": {
"init": {
"data": {
"enable_wake_on_start": "For\u00e7a el despertar del cotxe en la posada en marxa",
"scan_interval": "Segons entre escanejos"
}
}
}
}
}

View File

@ -1,32 +0,0 @@
{
"config": {
"abort": {
"already_configured": "\u00da\u010det je ji\u017e nastaven",
"reauth_successful": "Op\u011btovn\u00e9 ov\u011b\u0159en\u00ed bylo \u00fasp\u011b\u0161n\u00e9"
},
"error": {
"already_configured": "\u00da\u010det je ji\u017e nastaven",
"cannot_connect": "Nepoda\u0159ilo se p\u0159ipojit",
"invalid_auth": "Neplatn\u00e9 ov\u011b\u0159en\u00ed"
},
"step": {
"user": {
"data": {
"password": "Heslo",
"username": "E-mail"
},
"description": "Zadejte sv\u00e9 \u00fadaje.",
"title": "Tesla - Nastaven\u00ed"
}
}
},
"options": {
"step": {
"init": {
"data": {
"scan_interval": "Po\u010det sekund mezi sledov\u00e1n\u00edm"
}
}
}
}
}

View File

@ -1,23 +0,0 @@
{
"config": {
"step": {
"user": {
"data": {
"password": "Adgangskode",
"username": "Email-adresse"
},
"description": "Indtast dine oplysninger.",
"title": "Tesla - Konfiguration"
}
}
},
"options": {
"step": {
"init": {
"data": {
"scan_interval": "Sekunder mellem scanninger"
}
}
}
}
}

View File

@ -1,34 +0,0 @@
{
"config": {
"abort": {
"already_configured": "Konto wurde bereits konfiguriert",
"reauth_successful": "Die erneute Authentifizierung war erfolgreich"
},
"error": {
"already_configured": "Konto wurde bereits konfiguriert",
"cannot_connect": "Verbindung fehlgeschlagen",
"invalid_auth": "Ung\u00fcltige Authentifizierung"
},
"step": {
"user": {
"data": {
"mfa": "MFA-Code (optional)",
"password": "Passwort",
"username": "E-Mail"
},
"description": "Bitte gib deine Daten ein.",
"title": "Tesla - Konfiguration"
}
}
},
"options": {
"step": {
"init": {
"data": {
"enable_wake_on_start": "Aufwachen des Autos beim Start erzwingen",
"scan_interval": "Sekunden zwischen den Scans"
}
}
}
}
}

View File

@ -1,34 +0,0 @@
{
"config": {
"abort": {
"already_configured": "Account is already configured",
"reauth_successful": "Re-authentication was successful"
},
"error": {
"already_configured": "Account is already configured",
"cannot_connect": "Failed to connect",
"invalid_auth": "Invalid authentication"
},
"step": {
"user": {
"data": {
"mfa": "MFA Code (optional)",
"password": "Password",
"username": "Email"
},
"description": "Please enter your information.",
"title": "Tesla - Configuration"
}
}
},
"options": {
"step": {
"init": {
"data": {
"enable_wake_on_start": "Force cars awake on startup",
"scan_interval": "Seconds between scans"
}
}
}
}
}

View File

@ -1,24 +0,0 @@
{
"config": {
"step": {
"user": {
"data": {
"password": "Contrase\u00f1a",
"username": "Direcci\u00f3n de correo electr\u00f3nico"
},
"description": "Por favor ingrese su informaci\u00f3n.",
"title": "Tesla - Configuraci\u00f3n"
}
}
},
"options": {
"step": {
"init": {
"data": {
"enable_wake_on_start": "Forzar a autom\u00f3viles despertar al inicio",
"scan_interval": "Segundos entre escaneos"
}
}
}
}
}

View File

@ -1,34 +0,0 @@
{
"config": {
"abort": {
"already_configured": "La cuenta ya ha sido configurada",
"reauth_successful": "La reautenticaci\u00f3n se realiz\u00f3 correctamente"
},
"error": {
"already_configured": "La cuenta ya ha sido configurada",
"cannot_connect": "No se pudo conectar",
"invalid_auth": "Autenticaci\u00f3n no v\u00e1lida"
},
"step": {
"user": {
"data": {
"mfa": "C\u00f3digo MFA (opcional)",
"password": "Contrase\u00f1a",
"username": "Correo electr\u00f3nico"
},
"description": "Por favor, introduzca su informaci\u00f3n.",
"title": "Tesla - Configuraci\u00f3n"
}
}
},
"options": {
"step": {
"init": {
"data": {
"enable_wake_on_start": "Forzar autom\u00f3viles despiertos al inicio",
"scan_interval": "Segundos entre escaneos"
}
}
}
}
}

View File

@ -1,34 +0,0 @@
{
"config": {
"abort": {
"already_configured": "Kasutaja on juba seadistatud",
"reauth_successful": "Taastuvastamine \u00f5nnestus"
},
"error": {
"already_configured": "Konto on juba h\u00e4\u00e4lestatud",
"cannot_connect": "\u00dchendamine nurjus",
"invalid_auth": "Tuvastamise viga"
},
"step": {
"user": {
"data": {
"mfa": "MFA kood (valikuline)",
"password": "Salas\u00f5na",
"username": "E-post"
},
"description": "Palun sisesta oma andmed.",
"title": "Tesla - seadistamine"
}
}
},
"options": {
"step": {
"init": {
"data": {
"enable_wake_on_start": "Sunni autod k\u00e4ivitamisel \u00e4rkama (?)",
"scan_interval": "P\u00e4ringute vahe sekundites"
}
}
}
}
}

View File

@ -1,11 +0,0 @@
{
"config": {
"step": {
"user": {
"data": {
"mfa": "MFA-koodi (valinnainen)"
}
}
}
}
}

View File

@ -1,34 +0,0 @@
{
"config": {
"abort": {
"already_configured": "Le compte est d\u00e9j\u00e0 configur\u00e9",
"reauth_successful": "La r\u00e9-authentification a r\u00e9ussi"
},
"error": {
"already_configured": "Le compte est d\u00e9j\u00e0 configur\u00e9",
"cannot_connect": "\u00c9chec de connexion",
"invalid_auth": "Authentification invalide"
},
"step": {
"user": {
"data": {
"mfa": "Code MFA (facultatif)",
"password": "Mot de passe",
"username": "Email"
},
"description": "Veuillez saisir vos informations.",
"title": "Tesla - Configuration"
}
}
},
"options": {
"step": {
"init": {
"data": {
"enable_wake_on_start": "Forcer les voitures \u00e0 se r\u00e9veiller au d\u00e9marrage",
"scan_interval": "Secondes entre les scans"
}
}
}
}
}

View File

@ -1,21 +0,0 @@
{
"config": {
"abort": {
"already_configured": "\u05ea\u05e6\u05d5\u05e8\u05ea \u05d4\u05d7\u05e9\u05d1\u05d5\u05df \u05db\u05d1\u05e8 \u05e0\u05e7\u05d1\u05e2\u05d4",
"reauth_successful": "\u05d4\u05d0\u05d9\u05de\u05d5\u05ea \u05de\u05d7\u05d3\u05e9 \u05d4\u05e6\u05dc\u05d9\u05d7"
},
"error": {
"already_configured": "\u05ea\u05e6\u05d5\u05e8\u05ea \u05d4\u05d7\u05e9\u05d1\u05d5\u05df \u05db\u05d1\u05e8 \u05e0\u05e7\u05d1\u05e2\u05d4",
"cannot_connect": "\u05d4\u05d4\u05ea\u05d7\u05d1\u05e8\u05d5\u05ea \u05e0\u05db\u05e9\u05dc\u05d4",
"invalid_auth": "\u05d0\u05d9\u05de\u05d5\u05ea \u05dc\u05d0 \u05d7\u05d5\u05e7\u05d9"
},
"step": {
"user": {
"data": {
"password": "\u05e1\u05d9\u05e1\u05de\u05d4",
"username": "\u05d3\u05d5\u05d0\"\u05dc"
}
}
}
}
}

View File

@ -1,34 +0,0 @@
{
"config": {
"abort": {
"already_configured": "A fi\u00f3k m\u00e1r konfigur\u00e1lva van",
"reauth_successful": "Az \u00fajrahiteles\u00edt\u00e9s sikeres volt"
},
"error": {
"already_configured": "A fi\u00f3k m\u00e1r konfigur\u00e1lva van",
"cannot_connect": "Sikertelen csatlakoz\u00e1s",
"invalid_auth": "\u00c9rv\u00e9nytelen hiteles\u00edt\u00e9s"
},
"step": {
"user": {
"data": {
"mfa": "MFA k\u00f3d (opcion\u00e1lis)",
"password": "Jelsz\u00f3",
"username": "E-mail"
},
"description": "K\u00e9rlek, add meg az adataidat.",
"title": "Tesla - Konfigur\u00e1ci\u00f3"
}
}
},
"options": {
"step": {
"init": {
"data": {
"enable_wake_on_start": "Az aut\u00f3k \u00e9bred\u00e9sre k\u00e9nyszer\u00edt\u00e9se ind\u00edt\u00e1skor",
"scan_interval": "Szkennel\u00e9sek k\u00f6z\u00f6tti m\u00e1sodpercek"
}
}
}
}
}

View File

@ -1,33 +0,0 @@
{
"config": {
"abort": {
"already_configured": "Akun sudah dikonfigurasi",
"reauth_successful": "Autentikasi ulang berhasil"
},
"error": {
"already_configured": "Akun sudah dikonfigurasi",
"cannot_connect": "Gagal terhubung",
"invalid_auth": "Autentikasi tidak valid"
},
"step": {
"user": {
"data": {
"password": "Kata Sandi",
"username": "Email"
},
"description": "Masukkan informasi Anda.",
"title": "Tesla - Konfigurasi"
}
}
},
"options": {
"step": {
"init": {
"data": {
"enable_wake_on_start": "Paksa mobil bangun saat dinyalakan",
"scan_interval": "Interval pemindaian dalam detik"
}
}
}
}
}

View File

@ -1,34 +0,0 @@
{
"config": {
"abort": {
"already_configured": "L'account \u00e8 gi\u00e0 configurato",
"reauth_successful": "La nuova autenticazione \u00e8 stata eseguita correttamente"
},
"error": {
"already_configured": "L'account \u00e8 gi\u00e0 configurato",
"cannot_connect": "Impossibile connettersi",
"invalid_auth": "Autenticazione non valida"
},
"step": {
"user": {
"data": {
"mfa": "Codice autenticazione a pi\u00f9 fattori MFA (facoltativo)",
"password": "Password",
"username": "E-mail"
},
"description": "Si prega di inserire le tue informazioni.",
"title": "Tesla - Configurazione"
}
}
},
"options": {
"step": {
"init": {
"data": {
"enable_wake_on_start": "Forza il risveglio delle auto all'avvio",
"scan_interval": "Secondi tra le scansioni"
}
}
}
}
}

View File

@ -1,7 +0,0 @@
{
"config": {
"error": {
"already_configured": "\u10d0\u10dc\u10d2\u10d0\u10e0\u10d8\u10e8\u10d8 \u10e3\u10d9\u10d5\u10d4 \u10d9\u10dd\u10dc\u10e4\u10d8\u10d2\u10e3\u10e0\u10d8\u10e0\u10d4\u10d1\u10e3\u10da\u10d8\u10d0"
}
}
}

View File

@ -1,33 +0,0 @@
{
"config": {
"abort": {
"already_configured": "\uacc4\uc815\uc774 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4",
"reauth_successful": "\uc7ac\uc778\uc99d\uc5d0 \uc131\uacf5\ud588\uc2b5\ub2c8\ub2e4"
},
"error": {
"already_configured": "\uacc4\uc815\uc774 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4",
"cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4",
"invalid_auth": "\uc778\uc99d\uc774 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4"
},
"step": {
"user": {
"data": {
"password": "\ube44\ubc00\ubc88\ud638",
"username": "\uc774\uba54\uc77c"
},
"description": "\uc0ac\uc6a9\uc790 \uc815\ubcf4\ub97c \uc785\ub825\ud574\uc8fc\uc138\uc694",
"title": "Tesla - \uad6c\uc131"
}
}
},
"options": {
"step": {
"init": {
"data": {
"enable_wake_on_start": "\uc2dc\ub3d9 \uc2dc \ucc28\ub7c9 \uae68\uc6b0\uae30",
"scan_interval": "\uc2a4\uce94 \uac04\uaca9 (\ucd08)"
}
}
}
}
}

View File

@ -1,29 +0,0 @@
{
"config": {
"error": {
"already_configured": "Kont ass scho konfigur\u00e9iert",
"cannot_connect": "Feeler beim verbannen",
"invalid_auth": "Ong\u00eblteg Authentifikatioun"
},
"step": {
"user": {
"data": {
"password": "Passwuert",
"username": "E-Mail"
},
"description": "F\u00ebllt \u00e4r Informatiounen aus.",
"title": "Tesla - Konfiguratioun"
}
}
},
"options": {
"step": {
"init": {
"data": {
"enable_wake_on_start": "Forc\u00e9ier d'Erw\u00e4chen vun den Autoen beim starten",
"scan_interval": "Sekonnen t\u00ebscht Scannen"
}
}
}
}
}

View File

@ -1,12 +0,0 @@
{
"config": {
"step": {
"user": {
"data": {
"password": "Parole",
"username": "E-pasta adrese"
}
}
}
}
}

View File

@ -1,34 +0,0 @@
{
"config": {
"abort": {
"already_configured": "Account is al geconfigureerd",
"reauth_successful": "Herauthenticatie was succesvol"
},
"error": {
"already_configured": "Account is al geconfigureerd",
"cannot_connect": "Kan geen verbinding maken",
"invalid_auth": "Ongeldige authenticatie"
},
"step": {
"user": {
"data": {
"mfa": "MFA Code (optioneel)",
"password": "Wachtwoord",
"username": "E-mail"
},
"description": "Vul alstublieft uw gegevens in.",
"title": "Tesla - Configuratie"
}
}
},
"options": {
"step": {
"init": {
"data": {
"enable_wake_on_start": "Forceer auto's wakker bij het opstarten",
"scan_interval": "Seconden tussen scans"
}
}
}
}
}

View File

@ -1,34 +0,0 @@
{
"config": {
"abort": {
"already_configured": "Kontoen er allerede konfigurert",
"reauth_successful": "Godkjenning p\u00e5 nytt var vellykket"
},
"error": {
"already_configured": "Kontoen er allerede konfigurert",
"cannot_connect": "Tilkobling mislyktes",
"invalid_auth": "Ugyldig godkjenning"
},
"step": {
"user": {
"data": {
"mfa": "MFA -kode (valgfritt)",
"password": "Passord",
"username": "E-post"
},
"description": "Vennligst fyll inn din informasjonen.",
"title": "Tesla - Konfigurasjon"
}
}
},
"options": {
"step": {
"init": {
"data": {
"enable_wake_on_start": "Tving biler til \u00e5 v\u00e5kne ved oppstart",
"scan_interval": "Sekunder mellom skanninger"
}
}
}
}
}

View File

@ -1,34 +0,0 @@
{
"config": {
"abort": {
"already_configured": "Konto jest ju\u017c skonfigurowane",
"reauth_successful": "Ponowne uwierzytelnienie powiod\u0142o si\u0119"
},
"error": {
"already_configured": "Konto jest ju\u017c skonfigurowane",
"cannot_connect": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia",
"invalid_auth": "Niepoprawne uwierzytelnienie"
},
"step": {
"user": {
"data": {
"mfa": "Kod uwierzytelniania wielosk\u0142adnikowego (opcjonalnie)",
"password": "Has\u0142o",
"username": "Adres e-mail"
},
"description": "Wprowad\u017a dane",
"title": "Tesla \u2014 konfiguracja"
}
}
},
"options": {
"step": {
"init": {
"data": {
"enable_wake_on_start": "Wymu\u015b wybudzenie samochod\u00f3w podczas uruchamiania",
"scan_interval": "Cz\u0119stotliwo\u015b\u0107 aktualizacji"
}
}
}
}
}

View File

@ -1,23 +0,0 @@
{
"config": {
"step": {
"user": {
"data": {
"password": "Senha",
"username": "Endere\u00e7o de e-mail"
},
"description": "Por favor, insira suas informa\u00e7\u00f5es.",
"title": "Tesla - Configura\u00e7\u00e3o"
}
}
},
"options": {
"step": {
"init": {
"data": {
"enable_wake_on_start": "For\u00e7ar carros a acordar na inicializa\u00e7\u00e3o"
}
}
}
}
}

View File

@ -1,17 +0,0 @@
{
"config": {
"error": {
"already_configured": "Conta j\u00e1 configurada",
"cannot_connect": "Falha na liga\u00e7\u00e3o",
"invalid_auth": "Autentica\u00e7\u00e3o inv\u00e1lida"
},
"step": {
"user": {
"data": {
"password": "Palavra-passe",
"username": "Endere\u00e7o de e-mail"
}
}
}
}
}

View File

@ -1,34 +0,0 @@
{
"config": {
"abort": {
"already_configured": "\u042d\u0442\u0430 \u0443\u0447\u0451\u0442\u043d\u0430\u044f \u0437\u0430\u043f\u0438\u0441\u044c \u0443\u0436\u0435 \u0434\u043e\u0431\u0430\u0432\u043b\u0435\u043d\u0430 \u0432 Home Assistant.",
"reauth_successful": "\u041f\u043e\u0432\u0442\u043e\u0440\u043d\u0430\u044f \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u044f \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0430 \u0443\u0441\u043f\u0435\u0448\u043d\u043e."
},
"error": {
"already_configured": "\u042d\u0442\u0430 \u0443\u0447\u0451\u0442\u043d\u0430\u044f \u0437\u0430\u043f\u0438\u0441\u044c \u0443\u0436\u0435 \u0434\u043e\u0431\u0430\u0432\u043b\u0435\u043d\u0430 \u0432 Home Assistant.",
"cannot_connect": "\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0438\u0442\u044c\u0441\u044f.",
"invalid_auth": "\u041e\u0448\u0438\u0431\u043a\u0430 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u0438."
},
"step": {
"user": {
"data": {
"mfa": "\u041a\u043e\u0434 MFA (\u043d\u0435\u043e\u0431\u044f\u0437\u0430\u0442\u0435\u043b\u044c\u043d\u043e)",
"password": "\u041f\u0430\u0440\u043e\u043b\u044c",
"username": "\u0410\u0434\u0440\u0435\u0441 \u044d\u043b\u0435\u043a\u0442\u0440\u043e\u043d\u043d\u043e\u0439 \u043f\u043e\u0447\u0442\u044b"
},
"description": "\u0412\u0432\u0435\u0434\u0438\u0442\u0435 \u0434\u0430\u043d\u043d\u044b\u0435 \u0443\u0447\u0451\u0442\u043d\u043e\u0439 \u0437\u0430\u043f\u0438\u0441\u0438.",
"title": "Tesla"
}
}
},
"options": {
"step": {
"init": {
"data": {
"enable_wake_on_start": "\u041f\u0440\u0438\u043d\u0443\u0434\u0438\u0442\u0435\u043b\u044c\u043d\u043e \u0440\u0430\u0437\u0431\u0443\u0434\u0438\u0442\u044c \u043c\u0430\u0448\u0438\u043d\u0443 \u043f\u0440\u0438 \u0437\u0430\u043f\u0443\u0441\u043a\u0435",
"scan_interval": "\u0418\u043d\u0442\u0435\u0440\u0432\u0430\u043b \u043c\u0435\u0436\u0434\u0443 \u0441\u043a\u0430\u043d\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f\u043c\u0438 (\u0441\u0435\u043a.)"
}
}
}
}
}

View File

@ -1,24 +0,0 @@
{
"config": {
"step": {
"user": {
"data": {
"password": "Geslo",
"username": "E-po\u0161tni naslov"
},
"description": "Prosimo, vnesite svoje podatke.",
"title": "Tesla - konfiguracija"
}
}
},
"options": {
"step": {
"init": {
"data": {
"enable_wake_on_start": "Vsili zbujanje avtomobila ob zagonu",
"scan_interval": "Sekund med skeniranjem"
}
}
}
}
}

View File

@ -1,23 +0,0 @@
{
"config": {
"step": {
"user": {
"data": {
"password": "L\u00f6senord",
"username": "E-postadress"
},
"description": "V\u00e4nligen ange din information.",
"title": "Tesla - Konfiguration"
}
}
},
"options": {
"step": {
"init": {
"data": {
"scan_interval": "Sekunder mellan skanningar"
}
}
}
}
}

View File

@ -1,18 +0,0 @@
{
"config": {
"error": {
"already_configured": "Hesap zaten yap\u0131land\u0131r\u0131lm\u0131\u015f",
"cannot_connect": "Ba\u011flanma hatas\u0131",
"invalid_auth": "Ge\u00e7ersiz kimlik do\u011frulama"
},
"step": {
"user": {
"data": {
"password": "Parola",
"username": "E-posta"
},
"description": "L\u00fctfen bilgilerinizi giriniz."
}
}
}
}

View File

@ -1,29 +0,0 @@
{
"config": {
"error": {
"already_configured": "\u0426\u0435\u0439 \u043e\u0431\u043b\u0456\u043a\u043e\u0432\u0438\u0439 \u0437\u0430\u043f\u0438\u0441 \u0432\u0436\u0435 \u0434\u043e\u0434\u0430\u043d\u043e \u0432 Home Assistant.",
"cannot_connect": "\u041d\u0435 \u0432\u0434\u0430\u043b\u043e\u0441\u044f \u043f\u0456\u0434'\u0454\u0434\u043d\u0430\u0442\u0438\u0441\u044f",
"invalid_auth": "\u041d\u0435\u0432\u0456\u0440\u043d\u0430 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0456\u043a\u0430\u0446\u0456\u044f."
},
"step": {
"user": {
"data": {
"password": "\u041f\u0430\u0440\u043e\u043b\u044c",
"username": "\u0410\u0434\u0440\u0435\u0441\u0430 \u0435\u043b\u0435\u043a\u0442\u0440\u043e\u043d\u043d\u043e\u0457 \u043f\u043e\u0448\u0442\u0438"
},
"description": "\u0412\u0432\u0435\u0434\u0456\u0442\u044c \u0434\u0430\u043d\u0456 \u043e\u0431\u043b\u0456\u043a\u043e\u0432\u043e\u0433\u043e \u0437\u0430\u043f\u0438\u0441\u0443.",
"title": "Tesla"
}
}
},
"options": {
"step": {
"init": {
"data": {
"enable_wake_on_start": "\u041f\u0440\u0438\u043c\u0443\u0441\u043e\u0432\u043e \u0440\u043e\u0437\u0431\u0443\u0434\u0438\u0442\u0438 \u043c\u0430\u0448\u0438\u043d\u0443 \u043f\u0440\u0438 \u0437\u0430\u043f\u0443\u0441\u043a\u0443",
"scan_interval": "\u0406\u043d\u0442\u0435\u0440\u0432\u0430\u043b \u043c\u0456\u0436 \u0441\u043a\u0430\u043d\u0443\u0432\u0430\u043d\u043d\u044f\u043c\u0438 (\u0441\u0435\u043a.)"
}
}
}
}
}

View File

@ -1,11 +0,0 @@
{
"config": {
"step": {
"user": {
"data": {
"mfa": "MFA \u4ee3\u7801\uff08\u53ef\u9009\uff09"
}
}
}
}
}

View File

@ -1,34 +0,0 @@
{
"config": {
"abort": {
"already_configured": "\u5e33\u865f\u5df2\u7d93\u8a2d\u5b9a\u5b8c\u6210",
"reauth_successful": "\u91cd\u65b0\u8a8d\u8b49\u6210\u529f"
},
"error": {
"already_configured": "\u5e33\u865f\u5df2\u7d93\u8a2d\u5b9a\u5b8c\u6210",
"cannot_connect": "\u9023\u7dda\u5931\u6557",
"invalid_auth": "\u9a57\u8b49\u78bc\u7121\u6548"
},
"step": {
"user": {
"data": {
"mfa": "MFA \u78bc\uff08\u9078\u9805\uff09",
"password": "\u5bc6\u78bc",
"username": "\u96fb\u5b50\u90f5\u4ef6"
},
"description": "\u8acb\u8f38\u5165\u8cc7\u8a0a\u3002",
"title": "Tesla - \u8a2d\u5b9a"
}
}
},
"options": {
"step": {
"init": {
"data": {
"enable_wake_on_start": "\u65bc\u555f\u52d5\u6642\u5f37\u5236\u559a\u9192\u6c7d\u8eca",
"scan_interval": "\u6383\u63cf\u9593\u9694\u79d2\u6578"
}
}
}
}
}

View File

@ -272,7 +272,6 @@ FLOWS = [
"tado",
"tasmota",
"tellduslive",
"tesla",
"tibber",
"tile",
"toon",

View File

@ -259,21 +259,6 @@ DHCP = [
"domain": "tado",
"hostname": "tado*"
},
{
"domain": "tesla",
"hostname": "tesla_*",
"macaddress": "4CFCAA*"
},
{
"domain": "tesla",
"hostname": "tesla_*",
"macaddress": "044EAF*"
},
{
"domain": "tesla",
"hostname": "tesla_*",
"macaddress": "98ED5C*"
},
{
"domain": "toon",
"hostname": "eneco-*",

View File

@ -1618,9 +1618,6 @@ ignore_errors = true
[mypy-homeassistant.components.template.*]
ignore_errors = true
[mypy-homeassistant.components.tesla.*]
ignore_errors = true
[mypy-homeassistant.components.toon.*]
ignore_errors = true

View File

@ -2283,9 +2283,6 @@ temperusb==1.5.3
# homeassistant.components.powerwall
tesla-powerwall==0.3.10
# homeassistant.components.tesla
teslajsonpy==0.18.3
# homeassistant.components.tensorflow
# tf-models-official==2.3.0

View File

@ -1275,9 +1275,6 @@ tellduslive==0.10.11
# homeassistant.components.powerwall
tesla-powerwall==0.3.10
# homeassistant.components.tesla
teslajsonpy==0.18.3
# homeassistant.components.toon
toonapi==0.2.0

View File

@ -128,7 +128,6 @@ IGNORED_MODULES: Final[list[str]] = [
"homeassistant.components.tado.*",
"homeassistant.components.telegram_bot.*",
"homeassistant.components.template.*",
"homeassistant.components.tesla.*",
"homeassistant.components.toon.*",
"homeassistant.components.tplink.*",
"homeassistant.components.unifi.*",

View File

@ -1 +0,0 @@
"""Tests for the Tesla integration."""

View File

@ -1,270 +0,0 @@
"""Test the Tesla config flow."""
import datetime
from unittest.mock import patch
from teslajsonpy.exceptions import IncompleteCredentials, TeslaException
from homeassistant import config_entries, data_entry_flow, setup
from homeassistant.components.tesla.const import (
CONF_EXPIRATION,
CONF_WAKE_ON_START,
DEFAULT_SCAN_INTERVAL,
DEFAULT_WAKE_ON_START,
DOMAIN,
MIN_SCAN_INTERVAL,
)
from homeassistant.const import (
CONF_ACCESS_TOKEN,
CONF_PASSWORD,
CONF_SCAN_INTERVAL,
CONF_TOKEN,
CONF_USERNAME,
HTTP_NOT_FOUND,
)
from tests.common import MockConfigEntry
TEST_USERNAME = "test-username"
TEST_TOKEN = "test-token"
TEST_PASSWORD = "test-password"
TEST_ACCESS_TOKEN = "test-access-token"
TEST_VALID_EXPIRATION = datetime.datetime.now().timestamp() * 2
async def test_form(hass):
"""Test we get the form."""
await setup.async_setup_component(hass, "persistent_notification", {})
result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": config_entries.SOURCE_USER}
)
assert result["type"] == "form"
assert result["errors"] == {}
with patch(
"homeassistant.components.tesla.config_flow.TeslaAPI.connect",
return_value={
"refresh_token": TEST_TOKEN,
CONF_ACCESS_TOKEN: TEST_ACCESS_TOKEN,
CONF_EXPIRATION: TEST_VALID_EXPIRATION,
},
), patch(
"homeassistant.components.tesla.async_setup", return_value=True
) as mock_setup, patch(
"homeassistant.components.tesla.async_setup_entry", return_value=True
) as mock_setup_entry:
result2 = await hass.config_entries.flow.async_configure(
result["flow_id"], {CONF_PASSWORD: "test", CONF_USERNAME: "test@email.com"}
)
await hass.async_block_till_done()
assert result2["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY
assert result2["title"] == "test@email.com"
assert result2["data"] == {
CONF_USERNAME: "test@email.com",
CONF_PASSWORD: "test",
CONF_TOKEN: TEST_TOKEN,
CONF_ACCESS_TOKEN: TEST_ACCESS_TOKEN,
CONF_EXPIRATION: TEST_VALID_EXPIRATION,
}
assert len(mock_setup.mock_calls) == 1
assert len(mock_setup_entry.mock_calls) == 1
async def test_form_invalid_auth(hass):
"""Test we handle invalid auth."""
result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": config_entries.SOURCE_USER}
)
with patch(
"homeassistant.components.tesla.config_flow.TeslaAPI.connect",
side_effect=TeslaException(401),
):
result2 = await hass.config_entries.flow.async_configure(
result["flow_id"],
{CONF_USERNAME: TEST_USERNAME, CONF_PASSWORD: TEST_PASSWORD},
)
assert result2["type"] == "form"
assert result2["errors"] == {"base": "invalid_auth"}
async def test_form_invalid_auth_incomplete_credentials(hass):
"""Test we handle invalid auth with incomplete credentials."""
result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": config_entries.SOURCE_USER}
)
with patch(
"homeassistant.components.tesla.config_flow.TeslaAPI.connect",
side_effect=IncompleteCredentials(401),
):
result2 = await hass.config_entries.flow.async_configure(
result["flow_id"],
{CONF_USERNAME: TEST_USERNAME, CONF_PASSWORD: TEST_PASSWORD},
)
assert result2["type"] == "form"
assert result2["errors"] == {"base": "invalid_auth"}
async def test_form_cannot_connect(hass):
"""Test we handle cannot connect error."""
result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": config_entries.SOURCE_USER}
)
with patch(
"homeassistant.components.tesla.config_flow.TeslaAPI.connect",
side_effect=TeslaException(code=HTTP_NOT_FOUND),
):
result2 = await hass.config_entries.flow.async_configure(
result["flow_id"],
{CONF_PASSWORD: TEST_PASSWORD, CONF_USERNAME: TEST_USERNAME},
)
assert result2["type"] == "form"
assert result2["errors"] == {"base": "cannot_connect"}
async def test_form_repeat_identifier(hass):
"""Test we handle repeat identifiers."""
entry = MockConfigEntry(
domain=DOMAIN,
title=TEST_USERNAME,
data={"username": TEST_USERNAME, "password": TEST_PASSWORD},
options=None,
)
entry.add_to_hass(hass)
result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": config_entries.SOURCE_USER}
)
with patch(
"homeassistant.components.tesla.config_flow.TeslaAPI.connect",
return_value={
"refresh_token": TEST_TOKEN,
CONF_ACCESS_TOKEN: TEST_ACCESS_TOKEN,
CONF_EXPIRATION: TEST_VALID_EXPIRATION,
},
):
result2 = await hass.config_entries.flow.async_configure(
result["flow_id"],
{CONF_USERNAME: TEST_USERNAME, CONF_PASSWORD: TEST_PASSWORD},
)
assert result2["type"] == "abort"
assert result2["reason"] == "already_configured"
async def test_form_reauth(hass):
"""Test we handle reauth."""
entry = MockConfigEntry(
domain=DOMAIN,
title=TEST_USERNAME,
data={"username": TEST_USERNAME, "password": "same"},
options=None,
)
entry.add_to_hass(hass)
result = await hass.config_entries.flow.async_init(
DOMAIN,
context={"source": config_entries.SOURCE_REAUTH},
data={"username": TEST_USERNAME},
)
with patch(
"homeassistant.components.tesla.config_flow.TeslaAPI.connect",
return_value={
"refresh_token": TEST_TOKEN,
CONF_ACCESS_TOKEN: TEST_ACCESS_TOKEN,
CONF_EXPIRATION: TEST_VALID_EXPIRATION,
},
):
result2 = await hass.config_entries.flow.async_configure(
result["flow_id"],
{CONF_USERNAME: TEST_USERNAME, CONF_PASSWORD: "new-password"},
)
assert result2["type"] == "abort"
assert result2["reason"] == "reauth_successful"
async def test_import(hass):
"""Test import step."""
with patch(
"homeassistant.components.tesla.config_flow.TeslaAPI.connect",
return_value={
"refresh_token": TEST_TOKEN,
CONF_ACCESS_TOKEN: TEST_ACCESS_TOKEN,
CONF_EXPIRATION: TEST_VALID_EXPIRATION,
},
):
result = await hass.config_entries.flow.async_init(
DOMAIN,
context={"source": config_entries.SOURCE_IMPORT},
data={CONF_PASSWORD: TEST_PASSWORD, CONF_USERNAME: TEST_USERNAME},
)
assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY
assert result["title"] == TEST_USERNAME
assert result["data"][CONF_ACCESS_TOKEN] == TEST_ACCESS_TOKEN
assert result["data"][CONF_TOKEN] == TEST_TOKEN
assert result["description_placeholders"] is None
async def test_option_flow(hass):
"""Test config flow options."""
entry = MockConfigEntry(domain=DOMAIN, data={}, options=None)
entry.add_to_hass(hass)
result = await hass.config_entries.options.async_init(entry.entry_id)
assert result["type"] == "form"
assert result["step_id"] == "init"
result = await hass.config_entries.options.async_configure(
result["flow_id"],
user_input={CONF_SCAN_INTERVAL: 350, CONF_WAKE_ON_START: True},
)
assert result["type"] == "create_entry"
assert result["data"] == {CONF_SCAN_INTERVAL: 350, CONF_WAKE_ON_START: True}
async def test_option_flow_defaults(hass):
"""Test config flow options."""
entry = MockConfigEntry(domain=DOMAIN, data={}, options=None)
entry.add_to_hass(hass)
result = await hass.config_entries.options.async_init(entry.entry_id)
assert result["type"] == "form"
assert result["step_id"] == "init"
result = await hass.config_entries.options.async_configure(
result["flow_id"], user_input={}
)
assert result["type"] == "create_entry"
assert result["data"] == {
CONF_SCAN_INTERVAL: DEFAULT_SCAN_INTERVAL,
CONF_WAKE_ON_START: DEFAULT_WAKE_ON_START,
}
async def test_option_flow_input_floor(hass):
"""Test config flow options."""
entry = MockConfigEntry(domain=DOMAIN, data={}, options=None)
entry.add_to_hass(hass)
result = await hass.config_entries.options.async_init(entry.entry_id)
assert result["type"] == "form"
assert result["step_id"] == "init"
result = await hass.config_entries.options.async_configure(
result["flow_id"], user_input={CONF_SCAN_INTERVAL: 1}
)
assert result["type"] == "create_entry"
assert result["data"] == {
CONF_SCAN_INTERVAL: MIN_SCAN_INTERVAL,
CONF_WAKE_ON_START: DEFAULT_WAKE_ON_START,
}