mirror of
https://github.com/home-assistant/core
synced 2024-09-30 05:48:45 +00:00
Add new coordinators to Tessie (#118452)
* WIP * wip * Add energy classes * Add basis for Testing * Bump Library * fix case * bump library * bump library again * bump library for teslemetry * reorder * Fix super * Update strings.json * Tests * Small tweaks * Bump * Bump teslemetry * Remove version * Add WC states * Bump to match dev * Review feedback Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com> * Review feedback * Review feedback 1 * Review feedback 2 * TessieWallConnectorStates Enum * fixes * Fix translations and value * Update homeassistant/components/tessie/strings.json * Update homeassistant/components/tessie/strings.json --------- Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com>
This commit is contained in:
parent
fd67fe417e
commit
ec16fc235b
|
@ -1,9 +1,12 @@
|
||||||
"""Tessie integration."""
|
"""Tessie integration."""
|
||||||
|
|
||||||
|
import asyncio
|
||||||
from http import HTTPStatus
|
from http import HTTPStatus
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
from aiohttp import ClientError, ClientResponseError
|
from aiohttp import ClientError, ClientResponseError
|
||||||
|
from tesla_fleet_api import EnergySpecific, Tessie
|
||||||
|
from tesla_fleet_api.exceptions import TeslaFleetError
|
||||||
from tessie_api import get_state_of_all_vehicles
|
from tessie_api import get_state_of_all_vehicles
|
||||||
|
|
||||||
from homeassistant.config_entries import ConfigEntry
|
from homeassistant.config_entries import ConfigEntry
|
||||||
|
@ -14,8 +17,12 @@ from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
||||||
from homeassistant.helpers.device_registry import DeviceInfo
|
from homeassistant.helpers.device_registry import DeviceInfo
|
||||||
|
|
||||||
from .const import DOMAIN, MODELS
|
from .const import DOMAIN, MODELS
|
||||||
from .coordinator import TessieStateUpdateCoordinator
|
from .coordinator import (
|
||||||
from .models import TessieData, TessieVehicleData
|
TessieEnergySiteInfoCoordinator,
|
||||||
|
TessieEnergySiteLiveCoordinator,
|
||||||
|
TessieStateUpdateCoordinator,
|
||||||
|
)
|
||||||
|
from .models import TessieData, TessieEnergyData, TessieVehicleData
|
||||||
|
|
||||||
PLATFORMS = [
|
PLATFORMS = [
|
||||||
Platform.BINARY_SENSOR,
|
Platform.BINARY_SENSOR,
|
||||||
|
@ -40,10 +47,11 @@ type TessieConfigEntry = ConfigEntry[TessieData]
|
||||||
async def async_setup_entry(hass: HomeAssistant, entry: TessieConfigEntry) -> bool:
|
async def async_setup_entry(hass: HomeAssistant, entry: TessieConfigEntry) -> bool:
|
||||||
"""Set up Tessie config."""
|
"""Set up Tessie config."""
|
||||||
api_key = entry.data[CONF_ACCESS_TOKEN]
|
api_key = entry.data[CONF_ACCESS_TOKEN]
|
||||||
|
session = async_get_clientsession(hass)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
state_of_all_vehicles = await get_state_of_all_vehicles(
|
state_of_all_vehicles = await get_state_of_all_vehicles(
|
||||||
session=async_get_clientsession(hass),
|
session=session,
|
||||||
api_key=api_key,
|
api_key=api_key,
|
||||||
only_active=True,
|
only_active=True,
|
||||||
)
|
)
|
||||||
|
@ -84,7 +92,45 @@ async def async_setup_entry(hass: HomeAssistant, entry: TessieConfigEntry) -> bo
|
||||||
if vehicle["last_state"] is not None
|
if vehicle["last_state"] is not None
|
||||||
]
|
]
|
||||||
|
|
||||||
entry.runtime_data = TessieData(vehicles=vehicles)
|
# Energy Sites
|
||||||
|
tessie = Tessie(session, api_key)
|
||||||
|
try:
|
||||||
|
products = (await tessie.products())["response"]
|
||||||
|
except TeslaFleetError as e:
|
||||||
|
raise ConfigEntryNotReady from e
|
||||||
|
|
||||||
|
energysites: list[TessieEnergyData] = []
|
||||||
|
for product in products:
|
||||||
|
if "energy_site_id" in product:
|
||||||
|
site_id = product["energy_site_id"]
|
||||||
|
api = EnergySpecific(tessie.energy, site_id)
|
||||||
|
energysites.append(
|
||||||
|
TessieEnergyData(
|
||||||
|
api=api,
|
||||||
|
id=site_id,
|
||||||
|
live_coordinator=TessieEnergySiteLiveCoordinator(hass, api),
|
||||||
|
info_coordinator=TessieEnergySiteInfoCoordinator(hass, api),
|
||||||
|
device=DeviceInfo(
|
||||||
|
identifiers={(DOMAIN, str(site_id))},
|
||||||
|
manufacturer="Tesla",
|
||||||
|
name=product.get("site_name", "Energy Site"),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
# Populate coordinator data before forwarding to platforms
|
||||||
|
await asyncio.gather(
|
||||||
|
*(
|
||||||
|
energysite.live_coordinator.async_config_entry_first_refresh()
|
||||||
|
for energysite in energysites
|
||||||
|
),
|
||||||
|
*(
|
||||||
|
energysite.info_coordinator.async_config_entry_first_refresh()
|
||||||
|
for energysite in energysites
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
entry.runtime_data = TessieData(vehicles, energysites)
|
||||||
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
|
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
|
@ -79,3 +79,18 @@ TessieChargeStates = {
|
||||||
"Disconnected": "disconnected",
|
"Disconnected": "disconnected",
|
||||||
"NoPower": "no_power",
|
"NoPower": "no_power",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class TessieWallConnectorStates(IntEnum):
|
||||||
|
"""Tessie Wall Connector states."""
|
||||||
|
|
||||||
|
BOOTING = 0
|
||||||
|
CHARGING = 1
|
||||||
|
DISCONNECTED = 2
|
||||||
|
CONNECTED = 4
|
||||||
|
SCHEDULED = 5
|
||||||
|
NEGOTIATING = 6
|
||||||
|
ERROR = 7
|
||||||
|
CHARGING_FINISHED = 8
|
||||||
|
WAITING_CAR = 9
|
||||||
|
CHARGING_REDUCED = 10
|
||||||
|
|
|
@ -6,21 +6,37 @@ import logging
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
from aiohttp import ClientResponseError
|
from aiohttp import ClientResponseError
|
||||||
|
from tesla_fleet_api import EnergySpecific
|
||||||
|
from tesla_fleet_api.exceptions import InvalidToken, MissingToken, TeslaFleetError
|
||||||
from tessie_api import get_state, get_status
|
from tessie_api import get_state, get_status
|
||||||
|
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
from homeassistant.exceptions import ConfigEntryAuthFailed
|
from homeassistant.exceptions import ConfigEntryAuthFailed
|
||||||
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
||||||
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator
|
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
|
||||||
|
|
||||||
from .const import TessieStatus
|
from .const import TessieStatus
|
||||||
|
|
||||||
# This matches the update interval Tessie performs server side
|
# This matches the update interval Tessie performs server side
|
||||||
TESSIE_SYNC_INTERVAL = 10
|
TESSIE_SYNC_INTERVAL = 10
|
||||||
|
TESSIE_FLEET_API_SYNC_INTERVAL = timedelta(seconds=30)
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
def flatten(data: dict[str, Any], parent: str | None = None) -> dict[str, Any]:
|
||||||
|
"""Flatten the data structure."""
|
||||||
|
result = {}
|
||||||
|
for key, value in data.items():
|
||||||
|
if parent:
|
||||||
|
key = f"{parent}_{key}"
|
||||||
|
if isinstance(value, dict):
|
||||||
|
result.update(flatten(value, key))
|
||||||
|
else:
|
||||||
|
result[key] = value
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
class TessieStateUpdateCoordinator(DataUpdateCoordinator[dict[str, Any]]):
|
class TessieStateUpdateCoordinator(DataUpdateCoordinator[dict[str, Any]]):
|
||||||
"""Class to manage fetching data from the Tessie API."""
|
"""Class to manage fetching data from the Tessie API."""
|
||||||
|
|
||||||
|
@ -41,7 +57,7 @@ class TessieStateUpdateCoordinator(DataUpdateCoordinator[dict[str, Any]]):
|
||||||
self.api_key = api_key
|
self.api_key = api_key
|
||||||
self.vin = vin
|
self.vin = vin
|
||||||
self.session = async_get_clientsession(hass)
|
self.session = async_get_clientsession(hass)
|
||||||
self.data = self._flatten(data)
|
self.data = flatten(data)
|
||||||
|
|
||||||
async def _async_update_data(self) -> dict[str, Any]:
|
async def _async_update_data(self) -> dict[str, Any]:
|
||||||
"""Update vehicle data using Tessie API."""
|
"""Update vehicle data using Tessie API."""
|
||||||
|
@ -68,18 +84,61 @@ class TessieStateUpdateCoordinator(DataUpdateCoordinator[dict[str, Any]]):
|
||||||
raise ConfigEntryAuthFailed from e
|
raise ConfigEntryAuthFailed from e
|
||||||
raise
|
raise
|
||||||
|
|
||||||
return self._flatten(vehicle)
|
return flatten(vehicle)
|
||||||
|
|
||||||
def _flatten(
|
|
||||||
self, data: dict[str, Any], parent: str | None = None
|
class TessieEnergySiteLiveCoordinator(DataUpdateCoordinator[dict[str, Any]]):
|
||||||
) -> dict[str, Any]:
|
"""Class to manage fetching energy site live status from the Tessie API."""
|
||||||
"""Flatten the data structure."""
|
|
||||||
result = {}
|
def __init__(self, hass: HomeAssistant, api: EnergySpecific) -> None:
|
||||||
for key, value in data.items():
|
"""Initialize Tessie Energy Site Live coordinator."""
|
||||||
if parent:
|
super().__init__(
|
||||||
key = f"{parent}_{key}"
|
hass,
|
||||||
if isinstance(value, dict):
|
_LOGGER,
|
||||||
result.update(self._flatten(value, key))
|
name="Tessie Energy Site Live",
|
||||||
else:
|
update_interval=TESSIE_FLEET_API_SYNC_INTERVAL,
|
||||||
result[key] = value
|
)
|
||||||
return result
|
self.api = api
|
||||||
|
|
||||||
|
async def _async_update_data(self) -> dict[str, Any]:
|
||||||
|
"""Update energy site data using Tessie API."""
|
||||||
|
|
||||||
|
try:
|
||||||
|
data = (await self.api.live_status())["response"]
|
||||||
|
except (InvalidToken, MissingToken) as e:
|
||||||
|
raise ConfigEntryAuthFailed from e
|
||||||
|
except TeslaFleetError as e:
|
||||||
|
raise UpdateFailed(e.message) from e
|
||||||
|
|
||||||
|
# Convert Wall Connectors from array to dict
|
||||||
|
data["wall_connectors"] = {
|
||||||
|
wc["din"]: wc for wc in (data.get("wall_connectors") or [])
|
||||||
|
}
|
||||||
|
|
||||||
|
return data
|
||||||
|
|
||||||
|
|
||||||
|
class TessieEnergySiteInfoCoordinator(DataUpdateCoordinator[dict[str, Any]]):
|
||||||
|
"""Class to manage fetching energy site info from the Tessie API."""
|
||||||
|
|
||||||
|
def __init__(self, hass: HomeAssistant, api: EnergySpecific) -> None:
|
||||||
|
"""Initialize Tessie Energy Info coordinator."""
|
||||||
|
super().__init__(
|
||||||
|
hass,
|
||||||
|
_LOGGER,
|
||||||
|
name="Tessie Energy Site Info",
|
||||||
|
update_interval=TESSIE_FLEET_API_SYNC_INTERVAL,
|
||||||
|
)
|
||||||
|
self.api = api
|
||||||
|
|
||||||
|
async def _async_update_data(self) -> dict[str, Any]:
|
||||||
|
"""Update energy site data using Tessie API."""
|
||||||
|
|
||||||
|
try:
|
||||||
|
data = (await self.api.site_info())["response"]
|
||||||
|
except (InvalidToken, MissingToken) as e:
|
||||||
|
raise ConfigEntryAuthFailed from e
|
||||||
|
except TeslaFleetError as e:
|
||||||
|
raise UpdateFailed(e.message) from e
|
||||||
|
|
||||||
|
return flatten(data)
|
||||||
|
|
|
@ -1,36 +1,47 @@
|
||||||
"""Tessie parent entity class."""
|
"""Tessie parent entity class."""
|
||||||
|
|
||||||
|
from abc import abstractmethod
|
||||||
from collections.abc import Awaitable, Callable
|
from collections.abc import Awaitable, Callable
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
from aiohttp import ClientResponseError
|
from aiohttp import ClientResponseError
|
||||||
|
|
||||||
from homeassistant.exceptions import HomeAssistantError
|
from homeassistant.exceptions import HomeAssistantError
|
||||||
|
from homeassistant.helpers.device_registry import DeviceInfo
|
||||||
from homeassistant.helpers.update_coordinator import CoordinatorEntity
|
from homeassistant.helpers.update_coordinator import CoordinatorEntity
|
||||||
|
|
||||||
from .const import DOMAIN
|
from .const import DOMAIN
|
||||||
from .coordinator import TessieStateUpdateCoordinator
|
from .coordinator import (
|
||||||
from .models import TessieVehicleData
|
TessieEnergySiteInfoCoordinator,
|
||||||
|
TessieEnergySiteLiveCoordinator,
|
||||||
|
TessieStateUpdateCoordinator,
|
||||||
|
)
|
||||||
|
from .models import TessieEnergyData, TessieVehicleData
|
||||||
|
|
||||||
|
|
||||||
class TessieEntity(CoordinatorEntity[TessieStateUpdateCoordinator]):
|
class TessieBaseEntity(
|
||||||
"""Parent class for Tessie Entities."""
|
CoordinatorEntity[
|
||||||
|
TessieStateUpdateCoordinator
|
||||||
|
| TessieEnergySiteInfoCoordinator
|
||||||
|
| TessieEnergySiteLiveCoordinator
|
||||||
|
]
|
||||||
|
):
|
||||||
|
"""Parent class for Tessie entities."""
|
||||||
|
|
||||||
_attr_has_entity_name = True
|
_attr_has_entity_name = True
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
vehicle: TessieVehicleData,
|
coordinator: TessieStateUpdateCoordinator
|
||||||
|
| TessieEnergySiteInfoCoordinator
|
||||||
|
| TessieEnergySiteLiveCoordinator,
|
||||||
key: str,
|
key: str,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Initialize common aspects of a Tessie entity."""
|
"""Initialize common aspects of a Tessie entity."""
|
||||||
super().__init__(vehicle.data_coordinator)
|
|
||||||
self.vin = vehicle.vin
|
|
||||||
self.key = key
|
|
||||||
|
|
||||||
|
self.key = key
|
||||||
self._attr_translation_key = key
|
self._attr_translation_key = key
|
||||||
self._attr_unique_id = f"{vehicle.vin}-{key}"
|
super().__init__(coordinator)
|
||||||
self._attr_device_info = vehicle.device
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def _value(self) -> Any:
|
def _value(self) -> Any:
|
||||||
|
@ -41,15 +52,53 @@ class TessieEntity(CoordinatorEntity[TessieStateUpdateCoordinator]):
|
||||||
"""Return a specific value from coordinator data."""
|
"""Return a specific value from coordinator data."""
|
||||||
return self.coordinator.data.get(key or self.key, default)
|
return self.coordinator.data.get(key or self.key, default)
|
||||||
|
|
||||||
|
def _handle_coordinator_update(self) -> None:
|
||||||
|
"""Handle updated data from the coordinator."""
|
||||||
|
self._async_update_attrs()
|
||||||
|
super()._handle_coordinator_update()
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def _async_update_attrs(self) -> None:
|
||||||
|
"""Update the attributes of the entity."""
|
||||||
|
|
||||||
|
|
||||||
|
class TessieEntity(TessieBaseEntity):
|
||||||
|
"""Parent class for Tessie vehicle entities."""
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
vehicle: TessieVehicleData,
|
||||||
|
key: str,
|
||||||
|
) -> None:
|
||||||
|
"""Initialize common aspects of a Tessie vehicle entity."""
|
||||||
|
self.vin = vehicle.vin
|
||||||
|
self._session = vehicle.data_coordinator.session
|
||||||
|
self._api_key = vehicle.data_coordinator.api_key
|
||||||
|
self._attr_unique_id = f"{vehicle.vin}-{key}"
|
||||||
|
self._attr_device_info = vehicle.device
|
||||||
|
|
||||||
|
super().__init__(vehicle.data_coordinator, key)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def _value(self) -> Any:
|
||||||
|
"""Return value from coordinator data."""
|
||||||
|
return self.coordinator.data.get(self.key)
|
||||||
|
|
||||||
|
def set(self, *args: Any) -> None:
|
||||||
|
"""Set a value in coordinator data."""
|
||||||
|
for key, value in args:
|
||||||
|
self.coordinator.data[key] = value
|
||||||
|
self.async_write_ha_state()
|
||||||
|
|
||||||
async def run(
|
async def run(
|
||||||
self, func: Callable[..., Awaitable[dict[str, Any]]], **kargs: Any
|
self, func: Callable[..., Awaitable[dict[str, Any]]], **kargs: Any
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Run a tessie_api function and handle exceptions."""
|
"""Run a tessie_api function and handle exceptions."""
|
||||||
try:
|
try:
|
||||||
response = await func(
|
response = await func(
|
||||||
session=self.coordinator.session,
|
session=self._session,
|
||||||
vin=self.vin,
|
vin=self.vin,
|
||||||
api_key=self.coordinator.api_key,
|
api_key=self._api_key,
|
||||||
**kargs,
|
**kargs,
|
||||||
)
|
)
|
||||||
except ClientResponseError as e:
|
except ClientResponseError as e:
|
||||||
|
@ -63,8 +112,55 @@ class TessieEntity(CoordinatorEntity[TessieStateUpdateCoordinator]):
|
||||||
translation_placeholders={"name": name},
|
translation_placeholders={"name": name},
|
||||||
)
|
)
|
||||||
|
|
||||||
def set(self, *args: Any) -> None:
|
def _async_update_attrs(self) -> None:
|
||||||
"""Set a value in coordinator data."""
|
"""Update the attributes of the entity."""
|
||||||
for key, value in args:
|
# Not used in this class yet
|
||||||
self.coordinator.data[key] = value
|
|
||||||
self.async_write_ha_state()
|
|
||||||
|
class TessieEnergyEntity(TessieBaseEntity):
|
||||||
|
"""Parent class for Tessie energy site entities."""
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
data: TessieEnergyData,
|
||||||
|
coordinator: TessieEnergySiteInfoCoordinator | TessieEnergySiteLiveCoordinator,
|
||||||
|
key: str,
|
||||||
|
) -> None:
|
||||||
|
"""Initialize common aspects of a Tessie energy site entity."""
|
||||||
|
|
||||||
|
self._attr_unique_id = f"{data.id}-{key}"
|
||||||
|
self._attr_device_info = data.device
|
||||||
|
|
||||||
|
super().__init__(coordinator, key)
|
||||||
|
|
||||||
|
|
||||||
|
class TessieWallConnectorEntity(TessieBaseEntity):
|
||||||
|
"""Parent class for Tessie wall connector entities."""
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
data: TessieEnergyData,
|
||||||
|
din: str,
|
||||||
|
key: str,
|
||||||
|
) -> None:
|
||||||
|
"""Initialize common aspects of a Teslemetry entity."""
|
||||||
|
self.din = din
|
||||||
|
self._attr_unique_id = f"{data.id}-{din}-{key}"
|
||||||
|
self._attr_device_info = DeviceInfo(
|
||||||
|
identifiers={(DOMAIN, din)},
|
||||||
|
manufacturer="Tesla",
|
||||||
|
name="Wall Connector",
|
||||||
|
via_device=(DOMAIN, str(data.id)),
|
||||||
|
serial_number=din.split("-")[-1],
|
||||||
|
)
|
||||||
|
|
||||||
|
super().__init__(data.live_coordinator, key)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def _value(self) -> int:
|
||||||
|
"""Return a specific wall connector value from coordinator data."""
|
||||||
|
return (
|
||||||
|
self.coordinator.data.get("wall_connectors", {})
|
||||||
|
.get(self.din, {})
|
||||||
|
.get(self.key)
|
||||||
|
)
|
||||||
|
|
|
@ -189,6 +189,42 @@
|
||||||
},
|
},
|
||||||
"drive_state_active_route_destination": {
|
"drive_state_active_route_destination": {
|
||||||
"default": "mdi:map-marker"
|
"default": "mdi:map-marker"
|
||||||
|
},
|
||||||
|
"battery_power": {
|
||||||
|
"default": "mdi:home-battery"
|
||||||
|
},
|
||||||
|
"energy_left": {
|
||||||
|
"default": "mdi:battery"
|
||||||
|
},
|
||||||
|
"generator_power": {
|
||||||
|
"default": "mdi:generator-stationary"
|
||||||
|
},
|
||||||
|
"grid_power": {
|
||||||
|
"default": "mdi:transmission-tower"
|
||||||
|
},
|
||||||
|
"grid_services_power": {
|
||||||
|
"default": "mdi:transmission-tower"
|
||||||
|
},
|
||||||
|
"load_power": {
|
||||||
|
"default": "mdi:power-plug"
|
||||||
|
},
|
||||||
|
"solar_power": {
|
||||||
|
"default": "mdi:solar-power"
|
||||||
|
},
|
||||||
|
"total_pack_energy": {
|
||||||
|
"default": "mdi:battery-high"
|
||||||
|
},
|
||||||
|
"vin": {
|
||||||
|
"default": "mdi:car-electric"
|
||||||
|
},
|
||||||
|
"wall_connector_fault_state": {
|
||||||
|
"default": "mdi:ev-station"
|
||||||
|
},
|
||||||
|
"wall_connector_power": {
|
||||||
|
"default": "mdi:ev-station"
|
||||||
|
},
|
||||||
|
"wall_connector_state": {
|
||||||
|
"default": "mdi:ev-station"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"switch": {
|
"switch": {
|
||||||
|
|
|
@ -6,5 +6,5 @@
|
||||||
"documentation": "https://www.home-assistant.io/integrations/tessie",
|
"documentation": "https://www.home-assistant.io/integrations/tessie",
|
||||||
"iot_class": "cloud_polling",
|
"iot_class": "cloud_polling",
|
||||||
"loggers": ["tessie"],
|
"loggers": ["tessie"],
|
||||||
"requirements": ["tessie-api==0.0.9"]
|
"requirements": ["tessie-api==0.0.9", "tesla-fleet-api==0.6.1"]
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,9 +4,15 @@ from __future__ import annotations
|
||||||
|
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
|
|
||||||
|
from tesla_fleet_api import EnergySpecific
|
||||||
|
|
||||||
from homeassistant.helpers.device_registry import DeviceInfo
|
from homeassistant.helpers.device_registry import DeviceInfo
|
||||||
|
|
||||||
from .coordinator import TessieStateUpdateCoordinator
|
from .coordinator import (
|
||||||
|
TessieEnergySiteInfoCoordinator,
|
||||||
|
TessieEnergySiteLiveCoordinator,
|
||||||
|
TessieStateUpdateCoordinator,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
|
@ -14,6 +20,18 @@ class TessieData:
|
||||||
"""Data for the Tessie integration."""
|
"""Data for the Tessie integration."""
|
||||||
|
|
||||||
vehicles: list[TessieVehicleData]
|
vehicles: list[TessieVehicleData]
|
||||||
|
energysites: list[TessieEnergyData]
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class TessieEnergyData:
|
||||||
|
"""Data for a Energy Site in the Tessie integration."""
|
||||||
|
|
||||||
|
api: EnergySpecific
|
||||||
|
live_coordinator: TessieEnergySiteLiveCoordinator
|
||||||
|
info_coordinator: TessieEnergySiteInfoCoordinator
|
||||||
|
id: int
|
||||||
|
device: DeviceInfo
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
|
|
|
@ -5,6 +5,7 @@ from __future__ import annotations
|
||||||
from collections.abc import Callable
|
from collections.abc import Callable
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta
|
||||||
|
from itertools import chain
|
||||||
from typing import cast
|
from typing import cast
|
||||||
|
|
||||||
from homeassistant.components.sensor import (
|
from homeassistant.components.sensor import (
|
||||||
|
@ -33,9 +34,9 @@ from homeassistant.util import dt as dt_util
|
||||||
from homeassistant.util.variance import ignore_variance
|
from homeassistant.util.variance import ignore_variance
|
||||||
|
|
||||||
from . import TessieConfigEntry
|
from . import TessieConfigEntry
|
||||||
from .const import TessieChargeStates
|
from .const import TessieChargeStates, TessieWallConnectorStates
|
||||||
from .entity import TessieEntity
|
from .entity import TessieEnergyEntity, TessieEntity, TessieWallConnectorEntity
|
||||||
from .models import TessieVehicleData
|
from .models import TessieEnergyData, TessieVehicleData
|
||||||
|
|
||||||
|
|
||||||
@callback
|
@callback
|
||||||
|
@ -257,6 +258,115 @@ DESCRIPTIONS: tuple[TessieSensorEntityDescription, ...] = (
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
ENERGY_LIVE_DESCRIPTIONS: tuple[TessieSensorEntityDescription, ...] = (
|
||||||
|
TessieSensorEntityDescription(
|
||||||
|
key="solar_power",
|
||||||
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
|
native_unit_of_measurement=UnitOfPower.WATT,
|
||||||
|
suggested_unit_of_measurement=UnitOfPower.KILO_WATT,
|
||||||
|
suggested_display_precision=2,
|
||||||
|
device_class=SensorDeviceClass.POWER,
|
||||||
|
),
|
||||||
|
TessieSensorEntityDescription(
|
||||||
|
key="energy_left",
|
||||||
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
|
native_unit_of_measurement=UnitOfEnergy.WATT_HOUR,
|
||||||
|
suggested_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR,
|
||||||
|
suggested_display_precision=2,
|
||||||
|
device_class=SensorDeviceClass.ENERGY_STORAGE,
|
||||||
|
entity_category=EntityCategory.DIAGNOSTIC,
|
||||||
|
),
|
||||||
|
TessieSensorEntityDescription(
|
||||||
|
key="total_pack_energy",
|
||||||
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
|
native_unit_of_measurement=UnitOfEnergy.WATT_HOUR,
|
||||||
|
suggested_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR,
|
||||||
|
suggested_display_precision=2,
|
||||||
|
device_class=SensorDeviceClass.ENERGY_STORAGE,
|
||||||
|
entity_category=EntityCategory.DIAGNOSTIC,
|
||||||
|
entity_registry_enabled_default=False,
|
||||||
|
),
|
||||||
|
TessieSensorEntityDescription(
|
||||||
|
key="percentage_charged",
|
||||||
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
|
native_unit_of_measurement=PERCENTAGE,
|
||||||
|
device_class=SensorDeviceClass.BATTERY,
|
||||||
|
suggested_display_precision=2,
|
||||||
|
),
|
||||||
|
TessieSensorEntityDescription(
|
||||||
|
key="battery_power",
|
||||||
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
|
native_unit_of_measurement=UnitOfPower.WATT,
|
||||||
|
suggested_unit_of_measurement=UnitOfPower.KILO_WATT,
|
||||||
|
suggested_display_precision=2,
|
||||||
|
device_class=SensorDeviceClass.POWER,
|
||||||
|
),
|
||||||
|
TessieSensorEntityDescription(
|
||||||
|
key="load_power",
|
||||||
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
|
native_unit_of_measurement=UnitOfPower.WATT,
|
||||||
|
suggested_unit_of_measurement=UnitOfPower.KILO_WATT,
|
||||||
|
suggested_display_precision=2,
|
||||||
|
device_class=SensorDeviceClass.POWER,
|
||||||
|
),
|
||||||
|
TessieSensorEntityDescription(
|
||||||
|
key="grid_power",
|
||||||
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
|
native_unit_of_measurement=UnitOfPower.WATT,
|
||||||
|
suggested_unit_of_measurement=UnitOfPower.KILO_WATT,
|
||||||
|
suggested_display_precision=2,
|
||||||
|
device_class=SensorDeviceClass.POWER,
|
||||||
|
),
|
||||||
|
TessieSensorEntityDescription(
|
||||||
|
key="grid_services_power",
|
||||||
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
|
native_unit_of_measurement=UnitOfPower.WATT,
|
||||||
|
suggested_unit_of_measurement=UnitOfPower.KILO_WATT,
|
||||||
|
suggested_display_precision=2,
|
||||||
|
device_class=SensorDeviceClass.POWER,
|
||||||
|
),
|
||||||
|
TessieSensorEntityDescription(
|
||||||
|
key="generator_power",
|
||||||
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
|
native_unit_of_measurement=UnitOfPower.WATT,
|
||||||
|
suggested_unit_of_measurement=UnitOfPower.KILO_WATT,
|
||||||
|
suggested_display_precision=2,
|
||||||
|
device_class=SensorDeviceClass.POWER,
|
||||||
|
entity_registry_enabled_default=False,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
WALL_CONNECTOR_DESCRIPTIONS: tuple[TessieSensorEntityDescription, ...] = (
|
||||||
|
TessieSensorEntityDescription(
|
||||||
|
key="wall_connector_state",
|
||||||
|
entity_category=EntityCategory.DIAGNOSTIC,
|
||||||
|
entity_registry_enabled_default=False,
|
||||||
|
device_class=SensorDeviceClass.ENUM,
|
||||||
|
value_fn=lambda x: TessieWallConnectorStates(cast(int, x)).name.lower(),
|
||||||
|
options=[state.name.lower() for state in TessieWallConnectorStates],
|
||||||
|
),
|
||||||
|
TessieSensorEntityDescription(
|
||||||
|
key="wall_connector_power",
|
||||||
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
|
native_unit_of_measurement=UnitOfPower.WATT,
|
||||||
|
suggested_unit_of_measurement=UnitOfPower.KILO_WATT,
|
||||||
|
suggested_display_precision=2,
|
||||||
|
device_class=SensorDeviceClass.POWER,
|
||||||
|
),
|
||||||
|
TessieSensorEntityDescription(
|
||||||
|
key="vin",
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
ENERGY_INFO_DESCRIPTIONS: tuple[TessieSensorEntityDescription, ...] = (
|
||||||
|
TessieSensorEntityDescription(
|
||||||
|
key="vpp_backup_reserve_percent",
|
||||||
|
entity_category=EntityCategory.DIAGNOSTIC,
|
||||||
|
device_class=SensorDeviceClass.BATTERY,
|
||||||
|
native_unit_of_measurement=PERCENTAGE,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
async def async_setup_entry(
|
async def async_setup_entry(
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
|
@ -264,17 +374,38 @@ async def async_setup_entry(
|
||||||
async_add_entities: AddEntitiesCallback,
|
async_add_entities: AddEntitiesCallback,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Set up the Tessie sensor platform from a config entry."""
|
"""Set up the Tessie sensor platform from a config entry."""
|
||||||
data = entry.runtime_data
|
|
||||||
|
|
||||||
async_add_entities(
|
async_add_entities(
|
||||||
TessieSensorEntity(vehicle, description)
|
chain(
|
||||||
for vehicle in data.vehicles
|
( # Add vehicles
|
||||||
for description in DESCRIPTIONS
|
TessieVehicleSensorEntity(vehicle, description)
|
||||||
|
for vehicle in entry.runtime_data.vehicles
|
||||||
|
for description in DESCRIPTIONS
|
||||||
|
),
|
||||||
|
( # Add energy site info
|
||||||
|
TessieEnergyInfoSensorEntity(energysite, description)
|
||||||
|
for energysite in entry.runtime_data.energysites
|
||||||
|
for description in ENERGY_INFO_DESCRIPTIONS
|
||||||
|
if description.key in energysite.info_coordinator.data
|
||||||
|
),
|
||||||
|
( # Add energy site live
|
||||||
|
TessieEnergyLiveSensorEntity(energysite, description)
|
||||||
|
for energysite in entry.runtime_data.energysites
|
||||||
|
for description in ENERGY_LIVE_DESCRIPTIONS
|
||||||
|
if description.key in energysite.live_coordinator.data
|
||||||
|
),
|
||||||
|
( # Add wall connectors
|
||||||
|
TessieWallConnectorSensorEntity(energysite, din, description)
|
||||||
|
for energysite in entry.runtime_data.energysites
|
||||||
|
for din in energysite.live_coordinator.data.get("wall_connectors", {})
|
||||||
|
for description in WALL_CONNECTOR_DESCRIPTIONS
|
||||||
|
),
|
||||||
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class TessieSensorEntity(TessieEntity, SensorEntity):
|
class TessieVehicleSensorEntity(TessieEntity, SensorEntity):
|
||||||
"""Base class for Tessie metric sensors."""
|
"""Base class for Tessie sensor entities."""
|
||||||
|
|
||||||
entity_description: TessieSensorEntityDescription
|
entity_description: TessieSensorEntityDescription
|
||||||
|
|
||||||
|
@ -284,8 +415,8 @@ class TessieSensorEntity(TessieEntity, SensorEntity):
|
||||||
description: TessieSensorEntityDescription,
|
description: TessieSensorEntityDescription,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Initialize the sensor."""
|
"""Initialize the sensor."""
|
||||||
super().__init__(vehicle, description.key)
|
|
||||||
self.entity_description = description
|
self.entity_description = description
|
||||||
|
super().__init__(vehicle, description.key)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def native_value(self) -> StateType | datetime:
|
def native_value(self) -> StateType | datetime:
|
||||||
|
@ -296,3 +427,68 @@ class TessieSensorEntity(TessieEntity, SensorEntity):
|
||||||
def available(self) -> bool:
|
def available(self) -> bool:
|
||||||
"""Return if sensor is available."""
|
"""Return if sensor is available."""
|
||||||
return super().available and self.entity_description.available_fn(self.get())
|
return super().available and self.entity_description.available_fn(self.get())
|
||||||
|
|
||||||
|
|
||||||
|
class TessieEnergyLiveSensorEntity(TessieEnergyEntity, SensorEntity):
|
||||||
|
"""Base class for Tessie energy site sensor entity."""
|
||||||
|
|
||||||
|
entity_description: TessieSensorEntityDescription
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
data: TessieEnergyData,
|
||||||
|
description: TessieSensorEntityDescription,
|
||||||
|
) -> None:
|
||||||
|
"""Initialize the sensor."""
|
||||||
|
self.entity_description = description
|
||||||
|
super().__init__(data, data.live_coordinator, description.key)
|
||||||
|
|
||||||
|
def _async_update_attrs(self) -> None:
|
||||||
|
"""Update the attributes of the sensor."""
|
||||||
|
self._attr_available = self._value is not None
|
||||||
|
self._attr_native_value = self.entity_description.value_fn(self._value)
|
||||||
|
|
||||||
|
|
||||||
|
class TessieEnergyInfoSensorEntity(TessieEnergyEntity, SensorEntity):
|
||||||
|
"""Base class for Tessie energy site sensor entity."""
|
||||||
|
|
||||||
|
entity_description: TessieSensorEntityDescription
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
data: TessieEnergyData,
|
||||||
|
description: TessieSensorEntityDescription,
|
||||||
|
) -> None:
|
||||||
|
"""Initialize the sensor."""
|
||||||
|
self.entity_description = description
|
||||||
|
super().__init__(data, data.info_coordinator, description.key)
|
||||||
|
|
||||||
|
def _async_update_attrs(self) -> None:
|
||||||
|
"""Update the attributes of the sensor."""
|
||||||
|
self._attr_available = self._value is not None
|
||||||
|
self._attr_native_value = self._value
|
||||||
|
|
||||||
|
|
||||||
|
class TessieWallConnectorSensorEntity(TessieWallConnectorEntity, SensorEntity):
|
||||||
|
"""Base class for Tessie wall connector sensor entity."""
|
||||||
|
|
||||||
|
entity_description: TessieSensorEntityDescription
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
data: TessieEnergyData,
|
||||||
|
din: str,
|
||||||
|
description: TessieSensorEntityDescription,
|
||||||
|
) -> None:
|
||||||
|
"""Initialize the sensor."""
|
||||||
|
self.entity_description = description
|
||||||
|
super().__init__(
|
||||||
|
data,
|
||||||
|
din,
|
||||||
|
description.key,
|
||||||
|
)
|
||||||
|
|
||||||
|
def _async_update_attrs(self) -> None:
|
||||||
|
"""Update the attributes of the sensor."""
|
||||||
|
self._attr_available = self._value is not None
|
||||||
|
self._attr_native_value = self.entity_description.value_fn(self._value)
|
||||||
|
|
|
@ -167,6 +167,60 @@
|
||||||
},
|
},
|
||||||
"drive_state_active_route_destination": {
|
"drive_state_active_route_destination": {
|
||||||
"name": "Destination"
|
"name": "Destination"
|
||||||
|
},
|
||||||
|
"battery_power": {
|
||||||
|
"name": "Battery power"
|
||||||
|
},
|
||||||
|
"energy_left": {
|
||||||
|
"name": "Energy left"
|
||||||
|
},
|
||||||
|
"generator_power": {
|
||||||
|
"name": "Generator power"
|
||||||
|
},
|
||||||
|
"grid_power": {
|
||||||
|
"name": "Grid power"
|
||||||
|
},
|
||||||
|
"grid_services_power": {
|
||||||
|
"name": "Grid services power"
|
||||||
|
},
|
||||||
|
"load_power": {
|
||||||
|
"name": "Load power"
|
||||||
|
},
|
||||||
|
"percentage_charged": {
|
||||||
|
"name": "Percentage charged"
|
||||||
|
},
|
||||||
|
"solar_power": {
|
||||||
|
"name": "Solar power"
|
||||||
|
},
|
||||||
|
"total_pack_energy": {
|
||||||
|
"name": "Total pack energy"
|
||||||
|
},
|
||||||
|
"vin": {
|
||||||
|
"name": "Vehicle"
|
||||||
|
},
|
||||||
|
"vpp_backup_reserve_percent": {
|
||||||
|
"name": "VPP backup reserve"
|
||||||
|
},
|
||||||
|
"wall_connector_fault_state": {
|
||||||
|
"name": "Fault state code"
|
||||||
|
},
|
||||||
|
"wall_connector_power": {
|
||||||
|
"name": "Power"
|
||||||
|
},
|
||||||
|
"wall_connector_state": {
|
||||||
|
"name": "State",
|
||||||
|
"state": {
|
||||||
|
"booting": "Booting",
|
||||||
|
"charging": "[%key:component::tessie::entity::sensor::charge_state_charging_state::state::charging%]",
|
||||||
|
"disconnected": "[%key:common::state::disconnected%]",
|
||||||
|
"connected": "[%key:common::state::connected%]",
|
||||||
|
"scheduled": "Scheduled",
|
||||||
|
"negotiating": "Negotiating",
|
||||||
|
"error": "Error",
|
||||||
|
"charging_finished": "Charging finished",
|
||||||
|
"waiting_car": "Waiting car",
|
||||||
|
"charging_reduced": "Charging reduced"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"cover": {
|
"cover": {
|
||||||
|
|
|
@ -2713,6 +2713,7 @@ temperusb==1.6.1
|
||||||
# tensorflow==2.5.0
|
# tensorflow==2.5.0
|
||||||
|
|
||||||
# homeassistant.components.teslemetry
|
# homeassistant.components.teslemetry
|
||||||
|
# homeassistant.components.tessie
|
||||||
tesla-fleet-api==0.6.1
|
tesla-fleet-api==0.6.1
|
||||||
|
|
||||||
# homeassistant.components.powerwall
|
# homeassistant.components.powerwall
|
||||||
|
|
|
@ -2111,6 +2111,7 @@ temescal==0.5
|
||||||
temperusb==1.6.1
|
temperusb==1.6.1
|
||||||
|
|
||||||
# homeassistant.components.teslemetry
|
# homeassistant.components.teslemetry
|
||||||
|
# homeassistant.components.tessie
|
||||||
tesla-fleet-api==0.6.1
|
tesla-fleet-api==0.6.1
|
||||||
|
|
||||||
# homeassistant.components.powerwall
|
# homeassistant.components.powerwall
|
||||||
|
|
|
@ -16,6 +16,7 @@ from homeassistant.helpers.typing import UNDEFINED, UndefinedType
|
||||||
|
|
||||||
from tests.common import MockConfigEntry, load_json_object_fixture
|
from tests.common import MockConfigEntry, load_json_object_fixture
|
||||||
|
|
||||||
|
# Tessie library
|
||||||
TEST_STATE_OF_ALL_VEHICLES = load_json_object_fixture("vehicles.json", DOMAIN)
|
TEST_STATE_OF_ALL_VEHICLES = load_json_object_fixture("vehicles.json", DOMAIN)
|
||||||
TEST_VEHICLE_STATE_ONLINE = load_json_object_fixture("online.json", DOMAIN)
|
TEST_VEHICLE_STATE_ONLINE = load_json_object_fixture("online.json", DOMAIN)
|
||||||
TEST_VEHICLE_STATUS_AWAKE = {"status": TessieStatus.AWAKE}
|
TEST_VEHICLE_STATUS_AWAKE = {"status": TessieStatus.AWAKE}
|
||||||
|
@ -47,6 +48,13 @@ ERROR_VIRTUAL_KEY = ClientResponseError(
|
||||||
)
|
)
|
||||||
ERROR_CONNECTION = ClientConnectionError()
|
ERROR_CONNECTION = ClientConnectionError()
|
||||||
|
|
||||||
|
# Fleet API library
|
||||||
|
PRODUCTS = load_json_object_fixture("products.json", DOMAIN)
|
||||||
|
LIVE_STATUS = load_json_object_fixture("live_status.json", DOMAIN)
|
||||||
|
SITE_INFO = load_json_object_fixture("site_info.json", DOMAIN)
|
||||||
|
RESPONSE_OK = {"response": {}, "error": None}
|
||||||
|
COMMAND_OK = {"response": {"result": True, "reason": ""}}
|
||||||
|
|
||||||
|
|
||||||
async def setup_platform(
|
async def setup_platform(
|
||||||
hass: HomeAssistant, platforms: list[Platform] | UndefinedType = UNDEFINED
|
hass: HomeAssistant, platforms: list[Platform] | UndefinedType = UNDEFINED
|
||||||
|
|
|
@ -2,16 +2,23 @@
|
||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from copy import deepcopy
|
||||||
from unittest.mock import patch
|
from unittest.mock import patch
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from .common import (
|
from .common import (
|
||||||
|
COMMAND_OK,
|
||||||
|
LIVE_STATUS,
|
||||||
|
PRODUCTS,
|
||||||
|
SITE_INFO,
|
||||||
TEST_STATE_OF_ALL_VEHICLES,
|
TEST_STATE_OF_ALL_VEHICLES,
|
||||||
TEST_VEHICLE_STATE_ONLINE,
|
TEST_VEHICLE_STATE_ONLINE,
|
||||||
TEST_VEHICLE_STATUS_AWAKE,
|
TEST_VEHICLE_STATUS_AWAKE,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Tessie
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(autouse=True)
|
@pytest.fixture(autouse=True)
|
||||||
def mock_get_state():
|
def mock_get_state():
|
||||||
|
@ -41,3 +48,43 @@ def mock_get_state_of_all_vehicles():
|
||||||
return_value=TEST_STATE_OF_ALL_VEHICLES,
|
return_value=TEST_STATE_OF_ALL_VEHICLES,
|
||||||
) as mock_get_state_of_all_vehicles:
|
) as mock_get_state_of_all_vehicles:
|
||||||
yield mock_get_state_of_all_vehicles
|
yield mock_get_state_of_all_vehicles
|
||||||
|
|
||||||
|
|
||||||
|
# Fleet API
|
||||||
|
@pytest.fixture(autouse=True)
|
||||||
|
def mock_products():
|
||||||
|
"""Mock Tesla Fleet Api products method."""
|
||||||
|
with patch(
|
||||||
|
"homeassistant.components.tessie.Tessie.products", return_value=PRODUCTS
|
||||||
|
) as mock_products:
|
||||||
|
yield mock_products
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(autouse=True)
|
||||||
|
def mock_request():
|
||||||
|
"""Mock Tesla Fleet API request method."""
|
||||||
|
with patch(
|
||||||
|
"homeassistant.components.tessie.Tessie._request",
|
||||||
|
return_value=COMMAND_OK,
|
||||||
|
) as mock_request:
|
||||||
|
yield mock_request
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(autouse=True)
|
||||||
|
def mock_live_status():
|
||||||
|
"""Mock Tesla Fleet API EnergySpecific live_status method."""
|
||||||
|
with patch(
|
||||||
|
"homeassistant.components.tessie.EnergySpecific.live_status",
|
||||||
|
side_effect=lambda: deepcopy(LIVE_STATUS),
|
||||||
|
) as mock_live_status:
|
||||||
|
yield mock_live_status
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(autouse=True)
|
||||||
|
def mock_site_info():
|
||||||
|
"""Mock Tesla Fleet API EnergySpecific site_info method."""
|
||||||
|
with patch(
|
||||||
|
"homeassistant.components.tessie.EnergySpecific.site_info",
|
||||||
|
side_effect=lambda: deepcopy(SITE_INFO),
|
||||||
|
) as mock_live_status:
|
||||||
|
yield mock_live_status
|
||||||
|
|
33
tests/components/tessie/fixtures/live_status.json
Normal file
33
tests/components/tessie/fixtures/live_status.json
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
{
|
||||||
|
"response": {
|
||||||
|
"solar_power": 1185,
|
||||||
|
"energy_left": 38896.47368421053,
|
||||||
|
"total_pack_energy": 40727,
|
||||||
|
"percentage_charged": 95.50537403739663,
|
||||||
|
"backup_capable": true,
|
||||||
|
"battery_power": 5060,
|
||||||
|
"load_power": 6245,
|
||||||
|
"grid_status": "Active",
|
||||||
|
"grid_services_active": false,
|
||||||
|
"grid_power": 0,
|
||||||
|
"grid_services_power": 0,
|
||||||
|
"generator_power": 0,
|
||||||
|
"island_status": "on_grid",
|
||||||
|
"storm_mode_active": false,
|
||||||
|
"timestamp": "2024-01-01T00:00:00+00:00",
|
||||||
|
"wall_connectors": [
|
||||||
|
{
|
||||||
|
"din": "abd-123",
|
||||||
|
"wall_connector_state": 2,
|
||||||
|
"wall_connector_fault_state": 2,
|
||||||
|
"wall_connector_power": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"din": "bcd-234",
|
||||||
|
"wall_connector_state": 2,
|
||||||
|
"wall_connector_fault_state": 2,
|
||||||
|
"wall_connector_power": 0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
121
tests/components/tessie/fixtures/products.json
Normal file
121
tests/components/tessie/fixtures/products.json
Normal file
|
@ -0,0 +1,121 @@
|
||||||
|
{
|
||||||
|
"response": [
|
||||||
|
{
|
||||||
|
"id": 1234,
|
||||||
|
"user_id": 1234,
|
||||||
|
"vehicle_id": 1234,
|
||||||
|
"vin": "LRWXF7EK4KC700000",
|
||||||
|
"color": null,
|
||||||
|
"access_type": "OWNER",
|
||||||
|
"display_name": "Test",
|
||||||
|
"option_codes": null,
|
||||||
|
"cached_data": null,
|
||||||
|
"granular_access": { "hide_private": false },
|
||||||
|
"tokens": ["abc", "def"],
|
||||||
|
"state": "asleep",
|
||||||
|
"in_service": false,
|
||||||
|
"id_s": "1234",
|
||||||
|
"calendar_enabled": true,
|
||||||
|
"api_version": 71,
|
||||||
|
"backseat_token": null,
|
||||||
|
"backseat_token_updated_at": null,
|
||||||
|
"ble_autopair_enrolled": false,
|
||||||
|
"vehicle_config": {
|
||||||
|
"aux_park_lamps": "Eu",
|
||||||
|
"badge_version": 1,
|
||||||
|
"can_accept_navigation_requests": true,
|
||||||
|
"can_actuate_trunks": true,
|
||||||
|
"car_special_type": "base",
|
||||||
|
"car_type": "model3",
|
||||||
|
"charge_port_type": "CCS",
|
||||||
|
"cop_user_set_temp_supported": false,
|
||||||
|
"dashcam_clip_save_supported": true,
|
||||||
|
"default_charge_to_max": false,
|
||||||
|
"driver_assist": "TeslaAP3",
|
||||||
|
"ece_restrictions": false,
|
||||||
|
"efficiency_package": "M32021",
|
||||||
|
"eu_vehicle": true,
|
||||||
|
"exterior_color": "DeepBlue",
|
||||||
|
"exterior_trim": "Black",
|
||||||
|
"exterior_trim_override": "",
|
||||||
|
"has_air_suspension": false,
|
||||||
|
"has_ludicrous_mode": false,
|
||||||
|
"has_seat_cooling": false,
|
||||||
|
"headlamp_type": "Global",
|
||||||
|
"interior_trim_type": "White2",
|
||||||
|
"key_version": 2,
|
||||||
|
"motorized_charge_port": true,
|
||||||
|
"paint_color_override": "0,9,25,0.7,0.04",
|
||||||
|
"performance_package": "Base",
|
||||||
|
"plg": true,
|
||||||
|
"pws": true,
|
||||||
|
"rear_drive_unit": "PM216MOSFET",
|
||||||
|
"rear_seat_heaters": 1,
|
||||||
|
"rear_seat_type": 0,
|
||||||
|
"rhd": true,
|
||||||
|
"roof_color": "RoofColorGlass",
|
||||||
|
"seat_type": null,
|
||||||
|
"spoiler_type": "None",
|
||||||
|
"sun_roof_installed": null,
|
||||||
|
"supports_qr_pairing": false,
|
||||||
|
"third_row_seats": "None",
|
||||||
|
"timestamp": 1705701487912,
|
||||||
|
"trim_badging": "74d",
|
||||||
|
"use_range_badging": true,
|
||||||
|
"utc_offset": 36000,
|
||||||
|
"webcam_selfie_supported": true,
|
||||||
|
"webcam_supported": true,
|
||||||
|
"wheel_type": "Pinwheel18CapKit"
|
||||||
|
},
|
||||||
|
"command_signing": "allowed",
|
||||||
|
"release_notes_supported": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"energy_site_id": 123456,
|
||||||
|
"resource_type": "battery",
|
||||||
|
"site_name": "Energy Site",
|
||||||
|
"id": "ABC123",
|
||||||
|
"gateway_id": "ABC123",
|
||||||
|
"asset_site_id": "c0ffee",
|
||||||
|
"warp_site_number": "GA123456",
|
||||||
|
"energy_left": 23286.105263157893,
|
||||||
|
"total_pack_energy": 40804,
|
||||||
|
"percentage_charged": 57.068192488868476,
|
||||||
|
"battery_type": "ac_powerwall",
|
||||||
|
"backup_capable": true,
|
||||||
|
"battery_power": 14990,
|
||||||
|
"go_off_grid_test_banner_enabled": null,
|
||||||
|
"storm_mode_enabled": true,
|
||||||
|
"powerwall_onboarding_settings_set": true,
|
||||||
|
"powerwall_tesla_electric_interested_in": null,
|
||||||
|
"vpp_tour_enabled": null,
|
||||||
|
"sync_grid_alert_enabled": true,
|
||||||
|
"breaker_alert_enabled": true,
|
||||||
|
"components": {
|
||||||
|
"battery": true,
|
||||||
|
"battery_type": "ac_powerwall",
|
||||||
|
"solar": true,
|
||||||
|
"solar_type": "pv_panel",
|
||||||
|
"grid": true,
|
||||||
|
"load_meter": true,
|
||||||
|
"market_type": "residential",
|
||||||
|
"wall_connectors": [
|
||||||
|
{
|
||||||
|
"device_id": "abc-123",
|
||||||
|
"din": "123-abc",
|
||||||
|
"is_active": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"device_id": "bcd-234",
|
||||||
|
"din": "234-bcd",
|
||||||
|
"is_active": true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"features": {
|
||||||
|
"rate_plan_manager_no_pricing_constraint": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"count": 2
|
||||||
|
}
|
125
tests/components/tessie/fixtures/site_info.json
Normal file
125
tests/components/tessie/fixtures/site_info.json
Normal file
|
@ -0,0 +1,125 @@
|
||||||
|
{
|
||||||
|
"response": {
|
||||||
|
"id": "1233-abcd",
|
||||||
|
"site_name": "Site",
|
||||||
|
"backup_reserve_percent": 0,
|
||||||
|
"default_real_mode": "self_consumption",
|
||||||
|
"installation_date": "2022-01-01T00:00:00+00:00",
|
||||||
|
"user_settings": {
|
||||||
|
"go_off_grid_test_banner_enabled": false,
|
||||||
|
"storm_mode_enabled": true,
|
||||||
|
"powerwall_onboarding_settings_set": true,
|
||||||
|
"powerwall_tesla_electric_interested_in": false,
|
||||||
|
"vpp_tour_enabled": true,
|
||||||
|
"sync_grid_alert_enabled": true,
|
||||||
|
"breaker_alert_enabled": false
|
||||||
|
},
|
||||||
|
"components": {
|
||||||
|
"solar": true,
|
||||||
|
"solar_type": "pv_panel",
|
||||||
|
"battery": true,
|
||||||
|
"grid": true,
|
||||||
|
"backup": true,
|
||||||
|
"gateway": "teg",
|
||||||
|
"load_meter": true,
|
||||||
|
"tou_capable": true,
|
||||||
|
"storm_mode_capable": true,
|
||||||
|
"flex_energy_request_capable": false,
|
||||||
|
"car_charging_data_supported": false,
|
||||||
|
"off_grid_vehicle_charging_reserve_supported": true,
|
||||||
|
"vehicle_charging_performance_view_enabled": false,
|
||||||
|
"vehicle_charging_solar_offset_view_enabled": false,
|
||||||
|
"battery_solar_offset_view_enabled": true,
|
||||||
|
"solar_value_enabled": true,
|
||||||
|
"energy_value_header": "Energy Value",
|
||||||
|
"energy_value_subheader": "Estimated Value",
|
||||||
|
"energy_service_self_scheduling_enabled": true,
|
||||||
|
"show_grid_import_battery_source_cards": true,
|
||||||
|
"set_islanding_mode_enabled": true,
|
||||||
|
"wifi_commissioning_enabled": true,
|
||||||
|
"backup_time_remaining_enabled": true,
|
||||||
|
"battery_type": "ac_powerwall",
|
||||||
|
"configurable": true,
|
||||||
|
"grid_services_enabled": false,
|
||||||
|
"gateways": [
|
||||||
|
{
|
||||||
|
"device_id": "gateway-id",
|
||||||
|
"din": "gateway-din",
|
||||||
|
"serial_number": "CN00000000J50D",
|
||||||
|
"part_number": "1152100-14-J",
|
||||||
|
"part_type": 10,
|
||||||
|
"part_name": "Tesla Backup Gateway 2",
|
||||||
|
"is_active": true,
|
||||||
|
"site_id": "1234-abcd",
|
||||||
|
"firmware_version": "24.4.0 0fe780c9",
|
||||||
|
"updated_datetime": "2024-05-14T00:00:00.000Z"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"batteries": [
|
||||||
|
{
|
||||||
|
"device_id": "battery-1-id",
|
||||||
|
"din": "battery-1-din",
|
||||||
|
"serial_number": "TG000000001DA5",
|
||||||
|
"part_number": "3012170-10-B",
|
||||||
|
"part_type": 2,
|
||||||
|
"part_name": "Powerwall 2",
|
||||||
|
"nameplate_max_charge_power": 5000,
|
||||||
|
"nameplate_max_discharge_power": 5000,
|
||||||
|
"nameplate_energy": 13500
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"device_id": "battery-2-id",
|
||||||
|
"din": "battery-2-din",
|
||||||
|
"serial_number": "TG000000002DA5",
|
||||||
|
"part_number": "3012170-05-C",
|
||||||
|
"part_type": 2,
|
||||||
|
"part_name": "Powerwall 2",
|
||||||
|
"nameplate_max_charge_power": 5000,
|
||||||
|
"nameplate_max_discharge_power": 5000,
|
||||||
|
"nameplate_energy": 13500
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"wall_connectors": [
|
||||||
|
{
|
||||||
|
"device_id": "123abc",
|
||||||
|
"din": "abc123",
|
||||||
|
"is_active": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"device_id": "234bcd",
|
||||||
|
"din": "bcd234",
|
||||||
|
"is_active": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"disallow_charge_from_grid_with_solar_installed": true,
|
||||||
|
"customer_preferred_export_rule": "pv_only",
|
||||||
|
"net_meter_mode": "battery_ok",
|
||||||
|
"system_alerts_enabled": true
|
||||||
|
},
|
||||||
|
"version": "23.44.0 eb113390",
|
||||||
|
"battery_count": 2,
|
||||||
|
"tou_settings": {
|
||||||
|
"optimization_strategy": "economics",
|
||||||
|
"schedule": [
|
||||||
|
{
|
||||||
|
"target": "off_peak",
|
||||||
|
"week_days": [1, 0],
|
||||||
|
"start_seconds": 0,
|
||||||
|
"end_seconds": 3600
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"target": "peak",
|
||||||
|
"week_days": [1, 0],
|
||||||
|
"start_seconds": 3600,
|
||||||
|
"end_seconds": 0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"nameplate_power": 15000,
|
||||||
|
"nameplate_energy": 40500,
|
||||||
|
"installation_time_zone": "",
|
||||||
|
"max_site_meter_power_ac": 1000000000,
|
||||||
|
"min_site_meter_power_ac": -1000000000,
|
||||||
|
"vpp_backup_reserve_percent": 0
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,4 +1,562 @@
|
||||||
# serializer version: 1
|
# serializer version: 1
|
||||||
|
# name: test_sensors[sensor.energy_site_battery_power-entry]
|
||||||
|
EntityRegistryEntrySnapshot({
|
||||||
|
'aliases': set({
|
||||||
|
}),
|
||||||
|
'area_id': None,
|
||||||
|
'capabilities': dict({
|
||||||
|
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||||
|
}),
|
||||||
|
'config_entry_id': <ANY>,
|
||||||
|
'device_class': None,
|
||||||
|
'device_id': <ANY>,
|
||||||
|
'disabled_by': None,
|
||||||
|
'domain': 'sensor',
|
||||||
|
'entity_category': None,
|
||||||
|
'entity_id': 'sensor.energy_site_battery_power',
|
||||||
|
'has_entity_name': True,
|
||||||
|
'hidden_by': None,
|
||||||
|
'icon': None,
|
||||||
|
'id': <ANY>,
|
||||||
|
'labels': set({
|
||||||
|
}),
|
||||||
|
'name': None,
|
||||||
|
'options': dict({
|
||||||
|
'sensor': dict({
|
||||||
|
'suggested_display_precision': 2,
|
||||||
|
}),
|
||||||
|
'sensor.private': dict({
|
||||||
|
'suggested_unit_of_measurement': <UnitOfPower.KILO_WATT: 'kW'>,
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
'original_device_class': <SensorDeviceClass.POWER: 'power'>,
|
||||||
|
'original_icon': None,
|
||||||
|
'original_name': 'Battery power',
|
||||||
|
'platform': 'tessie',
|
||||||
|
'previous_unique_id': None,
|
||||||
|
'supported_features': 0,
|
||||||
|
'translation_key': 'battery_power',
|
||||||
|
'unique_id': '123456-battery_power',
|
||||||
|
'unit_of_measurement': <UnitOfPower.KILO_WATT: 'kW'>,
|
||||||
|
})
|
||||||
|
# ---
|
||||||
|
# name: test_sensors[sensor.energy_site_battery_power-state]
|
||||||
|
StateSnapshot({
|
||||||
|
'attributes': ReadOnlyDict({
|
||||||
|
'device_class': 'power',
|
||||||
|
'friendly_name': 'Energy Site Battery power',
|
||||||
|
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||||
|
'unit_of_measurement': <UnitOfPower.KILO_WATT: 'kW'>,
|
||||||
|
}),
|
||||||
|
'context': <ANY>,
|
||||||
|
'entity_id': 'sensor.energy_site_battery_power',
|
||||||
|
'last_changed': <ANY>,
|
||||||
|
'last_reported': <ANY>,
|
||||||
|
'last_updated': <ANY>,
|
||||||
|
'state': 'unknown',
|
||||||
|
})
|
||||||
|
# ---
|
||||||
|
# name: test_sensors[sensor.energy_site_energy_left-entry]
|
||||||
|
EntityRegistryEntrySnapshot({
|
||||||
|
'aliases': set({
|
||||||
|
}),
|
||||||
|
'area_id': None,
|
||||||
|
'capabilities': dict({
|
||||||
|
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||||
|
}),
|
||||||
|
'config_entry_id': <ANY>,
|
||||||
|
'device_class': None,
|
||||||
|
'device_id': <ANY>,
|
||||||
|
'disabled_by': None,
|
||||||
|
'domain': 'sensor',
|
||||||
|
'entity_category': <EntityCategory.DIAGNOSTIC: 'diagnostic'>,
|
||||||
|
'entity_id': 'sensor.energy_site_energy_left',
|
||||||
|
'has_entity_name': True,
|
||||||
|
'hidden_by': None,
|
||||||
|
'icon': None,
|
||||||
|
'id': <ANY>,
|
||||||
|
'labels': set({
|
||||||
|
}),
|
||||||
|
'name': None,
|
||||||
|
'options': dict({
|
||||||
|
'sensor': dict({
|
||||||
|
'suggested_display_precision': 2,
|
||||||
|
}),
|
||||||
|
'sensor.private': dict({
|
||||||
|
'suggested_unit_of_measurement': <UnitOfEnergy.KILO_WATT_HOUR: 'kWh'>,
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
'original_device_class': <SensorDeviceClass.ENERGY_STORAGE: 'energy_storage'>,
|
||||||
|
'original_icon': None,
|
||||||
|
'original_name': 'Energy left',
|
||||||
|
'platform': 'tessie',
|
||||||
|
'previous_unique_id': None,
|
||||||
|
'supported_features': 0,
|
||||||
|
'translation_key': 'energy_left',
|
||||||
|
'unique_id': '123456-energy_left',
|
||||||
|
'unit_of_measurement': <UnitOfEnergy.KILO_WATT_HOUR: 'kWh'>,
|
||||||
|
})
|
||||||
|
# ---
|
||||||
|
# name: test_sensors[sensor.energy_site_energy_left-state]
|
||||||
|
StateSnapshot({
|
||||||
|
'attributes': ReadOnlyDict({
|
||||||
|
'device_class': 'energy_storage',
|
||||||
|
'friendly_name': 'Energy Site Energy left',
|
||||||
|
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||||
|
'unit_of_measurement': <UnitOfEnergy.KILO_WATT_HOUR: 'kWh'>,
|
||||||
|
}),
|
||||||
|
'context': <ANY>,
|
||||||
|
'entity_id': 'sensor.energy_site_energy_left',
|
||||||
|
'last_changed': <ANY>,
|
||||||
|
'last_reported': <ANY>,
|
||||||
|
'last_updated': <ANY>,
|
||||||
|
'state': 'unknown',
|
||||||
|
})
|
||||||
|
# ---
|
||||||
|
# name: test_sensors[sensor.energy_site_generator_power-entry]
|
||||||
|
EntityRegistryEntrySnapshot({
|
||||||
|
'aliases': set({
|
||||||
|
}),
|
||||||
|
'area_id': None,
|
||||||
|
'capabilities': dict({
|
||||||
|
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||||
|
}),
|
||||||
|
'config_entry_id': <ANY>,
|
||||||
|
'device_class': None,
|
||||||
|
'device_id': <ANY>,
|
||||||
|
'disabled_by': None,
|
||||||
|
'domain': 'sensor',
|
||||||
|
'entity_category': None,
|
||||||
|
'entity_id': 'sensor.energy_site_generator_power',
|
||||||
|
'has_entity_name': True,
|
||||||
|
'hidden_by': None,
|
||||||
|
'icon': None,
|
||||||
|
'id': <ANY>,
|
||||||
|
'labels': set({
|
||||||
|
}),
|
||||||
|
'name': None,
|
||||||
|
'options': dict({
|
||||||
|
'sensor': dict({
|
||||||
|
'suggested_display_precision': 2,
|
||||||
|
}),
|
||||||
|
'sensor.private': dict({
|
||||||
|
'suggested_unit_of_measurement': <UnitOfPower.KILO_WATT: 'kW'>,
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
'original_device_class': <SensorDeviceClass.POWER: 'power'>,
|
||||||
|
'original_icon': None,
|
||||||
|
'original_name': 'Generator power',
|
||||||
|
'platform': 'tessie',
|
||||||
|
'previous_unique_id': None,
|
||||||
|
'supported_features': 0,
|
||||||
|
'translation_key': 'generator_power',
|
||||||
|
'unique_id': '123456-generator_power',
|
||||||
|
'unit_of_measurement': <UnitOfPower.KILO_WATT: 'kW'>,
|
||||||
|
})
|
||||||
|
# ---
|
||||||
|
# name: test_sensors[sensor.energy_site_generator_power-state]
|
||||||
|
StateSnapshot({
|
||||||
|
'attributes': ReadOnlyDict({
|
||||||
|
'device_class': 'power',
|
||||||
|
'friendly_name': 'Energy Site Generator power',
|
||||||
|
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||||
|
'unit_of_measurement': <UnitOfPower.KILO_WATT: 'kW'>,
|
||||||
|
}),
|
||||||
|
'context': <ANY>,
|
||||||
|
'entity_id': 'sensor.energy_site_generator_power',
|
||||||
|
'last_changed': <ANY>,
|
||||||
|
'last_reported': <ANY>,
|
||||||
|
'last_updated': <ANY>,
|
||||||
|
'state': 'unknown',
|
||||||
|
})
|
||||||
|
# ---
|
||||||
|
# name: test_sensors[sensor.energy_site_grid_power-entry]
|
||||||
|
EntityRegistryEntrySnapshot({
|
||||||
|
'aliases': set({
|
||||||
|
}),
|
||||||
|
'area_id': None,
|
||||||
|
'capabilities': dict({
|
||||||
|
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||||
|
}),
|
||||||
|
'config_entry_id': <ANY>,
|
||||||
|
'device_class': None,
|
||||||
|
'device_id': <ANY>,
|
||||||
|
'disabled_by': None,
|
||||||
|
'domain': 'sensor',
|
||||||
|
'entity_category': None,
|
||||||
|
'entity_id': 'sensor.energy_site_grid_power',
|
||||||
|
'has_entity_name': True,
|
||||||
|
'hidden_by': None,
|
||||||
|
'icon': None,
|
||||||
|
'id': <ANY>,
|
||||||
|
'labels': set({
|
||||||
|
}),
|
||||||
|
'name': None,
|
||||||
|
'options': dict({
|
||||||
|
'sensor': dict({
|
||||||
|
'suggested_display_precision': 2,
|
||||||
|
}),
|
||||||
|
'sensor.private': dict({
|
||||||
|
'suggested_unit_of_measurement': <UnitOfPower.KILO_WATT: 'kW'>,
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
'original_device_class': <SensorDeviceClass.POWER: 'power'>,
|
||||||
|
'original_icon': None,
|
||||||
|
'original_name': 'Grid power',
|
||||||
|
'platform': 'tessie',
|
||||||
|
'previous_unique_id': None,
|
||||||
|
'supported_features': 0,
|
||||||
|
'translation_key': 'grid_power',
|
||||||
|
'unique_id': '123456-grid_power',
|
||||||
|
'unit_of_measurement': <UnitOfPower.KILO_WATT: 'kW'>,
|
||||||
|
})
|
||||||
|
# ---
|
||||||
|
# name: test_sensors[sensor.energy_site_grid_power-state]
|
||||||
|
StateSnapshot({
|
||||||
|
'attributes': ReadOnlyDict({
|
||||||
|
'device_class': 'power',
|
||||||
|
'friendly_name': 'Energy Site Grid power',
|
||||||
|
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||||
|
'unit_of_measurement': <UnitOfPower.KILO_WATT: 'kW'>,
|
||||||
|
}),
|
||||||
|
'context': <ANY>,
|
||||||
|
'entity_id': 'sensor.energy_site_grid_power',
|
||||||
|
'last_changed': <ANY>,
|
||||||
|
'last_reported': <ANY>,
|
||||||
|
'last_updated': <ANY>,
|
||||||
|
'state': 'unknown',
|
||||||
|
})
|
||||||
|
# ---
|
||||||
|
# name: test_sensors[sensor.energy_site_grid_services_power-entry]
|
||||||
|
EntityRegistryEntrySnapshot({
|
||||||
|
'aliases': set({
|
||||||
|
}),
|
||||||
|
'area_id': None,
|
||||||
|
'capabilities': dict({
|
||||||
|
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||||
|
}),
|
||||||
|
'config_entry_id': <ANY>,
|
||||||
|
'device_class': None,
|
||||||
|
'device_id': <ANY>,
|
||||||
|
'disabled_by': None,
|
||||||
|
'domain': 'sensor',
|
||||||
|
'entity_category': None,
|
||||||
|
'entity_id': 'sensor.energy_site_grid_services_power',
|
||||||
|
'has_entity_name': True,
|
||||||
|
'hidden_by': None,
|
||||||
|
'icon': None,
|
||||||
|
'id': <ANY>,
|
||||||
|
'labels': set({
|
||||||
|
}),
|
||||||
|
'name': None,
|
||||||
|
'options': dict({
|
||||||
|
'sensor': dict({
|
||||||
|
'suggested_display_precision': 2,
|
||||||
|
}),
|
||||||
|
'sensor.private': dict({
|
||||||
|
'suggested_unit_of_measurement': <UnitOfPower.KILO_WATT: 'kW'>,
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
'original_device_class': <SensorDeviceClass.POWER: 'power'>,
|
||||||
|
'original_icon': None,
|
||||||
|
'original_name': 'Grid services power',
|
||||||
|
'platform': 'tessie',
|
||||||
|
'previous_unique_id': None,
|
||||||
|
'supported_features': 0,
|
||||||
|
'translation_key': 'grid_services_power',
|
||||||
|
'unique_id': '123456-grid_services_power',
|
||||||
|
'unit_of_measurement': <UnitOfPower.KILO_WATT: 'kW'>,
|
||||||
|
})
|
||||||
|
# ---
|
||||||
|
# name: test_sensors[sensor.energy_site_grid_services_power-state]
|
||||||
|
StateSnapshot({
|
||||||
|
'attributes': ReadOnlyDict({
|
||||||
|
'device_class': 'power',
|
||||||
|
'friendly_name': 'Energy Site Grid services power',
|
||||||
|
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||||
|
'unit_of_measurement': <UnitOfPower.KILO_WATT: 'kW'>,
|
||||||
|
}),
|
||||||
|
'context': <ANY>,
|
||||||
|
'entity_id': 'sensor.energy_site_grid_services_power',
|
||||||
|
'last_changed': <ANY>,
|
||||||
|
'last_reported': <ANY>,
|
||||||
|
'last_updated': <ANY>,
|
||||||
|
'state': 'unknown',
|
||||||
|
})
|
||||||
|
# ---
|
||||||
|
# name: test_sensors[sensor.energy_site_load_power-entry]
|
||||||
|
EntityRegistryEntrySnapshot({
|
||||||
|
'aliases': set({
|
||||||
|
}),
|
||||||
|
'area_id': None,
|
||||||
|
'capabilities': dict({
|
||||||
|
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||||
|
}),
|
||||||
|
'config_entry_id': <ANY>,
|
||||||
|
'device_class': None,
|
||||||
|
'device_id': <ANY>,
|
||||||
|
'disabled_by': None,
|
||||||
|
'domain': 'sensor',
|
||||||
|
'entity_category': None,
|
||||||
|
'entity_id': 'sensor.energy_site_load_power',
|
||||||
|
'has_entity_name': True,
|
||||||
|
'hidden_by': None,
|
||||||
|
'icon': None,
|
||||||
|
'id': <ANY>,
|
||||||
|
'labels': set({
|
||||||
|
}),
|
||||||
|
'name': None,
|
||||||
|
'options': dict({
|
||||||
|
'sensor': dict({
|
||||||
|
'suggested_display_precision': 2,
|
||||||
|
}),
|
||||||
|
'sensor.private': dict({
|
||||||
|
'suggested_unit_of_measurement': <UnitOfPower.KILO_WATT: 'kW'>,
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
'original_device_class': <SensorDeviceClass.POWER: 'power'>,
|
||||||
|
'original_icon': None,
|
||||||
|
'original_name': 'Load power',
|
||||||
|
'platform': 'tessie',
|
||||||
|
'previous_unique_id': None,
|
||||||
|
'supported_features': 0,
|
||||||
|
'translation_key': 'load_power',
|
||||||
|
'unique_id': '123456-load_power',
|
||||||
|
'unit_of_measurement': <UnitOfPower.KILO_WATT: 'kW'>,
|
||||||
|
})
|
||||||
|
# ---
|
||||||
|
# name: test_sensors[sensor.energy_site_load_power-state]
|
||||||
|
StateSnapshot({
|
||||||
|
'attributes': ReadOnlyDict({
|
||||||
|
'device_class': 'power',
|
||||||
|
'friendly_name': 'Energy Site Load power',
|
||||||
|
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||||
|
'unit_of_measurement': <UnitOfPower.KILO_WATT: 'kW'>,
|
||||||
|
}),
|
||||||
|
'context': <ANY>,
|
||||||
|
'entity_id': 'sensor.energy_site_load_power',
|
||||||
|
'last_changed': <ANY>,
|
||||||
|
'last_reported': <ANY>,
|
||||||
|
'last_updated': <ANY>,
|
||||||
|
'state': 'unknown',
|
||||||
|
})
|
||||||
|
# ---
|
||||||
|
# name: test_sensors[sensor.energy_site_percentage_charged-entry]
|
||||||
|
EntityRegistryEntrySnapshot({
|
||||||
|
'aliases': set({
|
||||||
|
}),
|
||||||
|
'area_id': None,
|
||||||
|
'capabilities': dict({
|
||||||
|
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||||
|
}),
|
||||||
|
'config_entry_id': <ANY>,
|
||||||
|
'device_class': None,
|
||||||
|
'device_id': <ANY>,
|
||||||
|
'disabled_by': None,
|
||||||
|
'domain': 'sensor',
|
||||||
|
'entity_category': None,
|
||||||
|
'entity_id': 'sensor.energy_site_percentage_charged',
|
||||||
|
'has_entity_name': True,
|
||||||
|
'hidden_by': None,
|
||||||
|
'icon': None,
|
||||||
|
'id': <ANY>,
|
||||||
|
'labels': set({
|
||||||
|
}),
|
||||||
|
'name': None,
|
||||||
|
'options': dict({
|
||||||
|
'sensor': dict({
|
||||||
|
'suggested_display_precision': 2,
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
'original_device_class': <SensorDeviceClass.BATTERY: 'battery'>,
|
||||||
|
'original_icon': None,
|
||||||
|
'original_name': 'Percentage charged',
|
||||||
|
'platform': 'tessie',
|
||||||
|
'previous_unique_id': None,
|
||||||
|
'supported_features': 0,
|
||||||
|
'translation_key': 'percentage_charged',
|
||||||
|
'unique_id': '123456-percentage_charged',
|
||||||
|
'unit_of_measurement': '%',
|
||||||
|
})
|
||||||
|
# ---
|
||||||
|
# name: test_sensors[sensor.energy_site_percentage_charged-state]
|
||||||
|
StateSnapshot({
|
||||||
|
'attributes': ReadOnlyDict({
|
||||||
|
'device_class': 'battery',
|
||||||
|
'friendly_name': 'Energy Site Percentage charged',
|
||||||
|
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||||
|
'unit_of_measurement': '%',
|
||||||
|
}),
|
||||||
|
'context': <ANY>,
|
||||||
|
'entity_id': 'sensor.energy_site_percentage_charged',
|
||||||
|
'last_changed': <ANY>,
|
||||||
|
'last_reported': <ANY>,
|
||||||
|
'last_updated': <ANY>,
|
||||||
|
'state': 'unknown',
|
||||||
|
})
|
||||||
|
# ---
|
||||||
|
# name: test_sensors[sensor.energy_site_solar_power-entry]
|
||||||
|
EntityRegistryEntrySnapshot({
|
||||||
|
'aliases': set({
|
||||||
|
}),
|
||||||
|
'area_id': None,
|
||||||
|
'capabilities': dict({
|
||||||
|
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||||
|
}),
|
||||||
|
'config_entry_id': <ANY>,
|
||||||
|
'device_class': None,
|
||||||
|
'device_id': <ANY>,
|
||||||
|
'disabled_by': None,
|
||||||
|
'domain': 'sensor',
|
||||||
|
'entity_category': None,
|
||||||
|
'entity_id': 'sensor.energy_site_solar_power',
|
||||||
|
'has_entity_name': True,
|
||||||
|
'hidden_by': None,
|
||||||
|
'icon': None,
|
||||||
|
'id': <ANY>,
|
||||||
|
'labels': set({
|
||||||
|
}),
|
||||||
|
'name': None,
|
||||||
|
'options': dict({
|
||||||
|
'sensor': dict({
|
||||||
|
'suggested_display_precision': 2,
|
||||||
|
}),
|
||||||
|
'sensor.private': dict({
|
||||||
|
'suggested_unit_of_measurement': <UnitOfPower.KILO_WATT: 'kW'>,
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
'original_device_class': <SensorDeviceClass.POWER: 'power'>,
|
||||||
|
'original_icon': None,
|
||||||
|
'original_name': 'Solar power',
|
||||||
|
'platform': 'tessie',
|
||||||
|
'previous_unique_id': None,
|
||||||
|
'supported_features': 0,
|
||||||
|
'translation_key': 'solar_power',
|
||||||
|
'unique_id': '123456-solar_power',
|
||||||
|
'unit_of_measurement': <UnitOfPower.KILO_WATT: 'kW'>,
|
||||||
|
})
|
||||||
|
# ---
|
||||||
|
# name: test_sensors[sensor.energy_site_solar_power-state]
|
||||||
|
StateSnapshot({
|
||||||
|
'attributes': ReadOnlyDict({
|
||||||
|
'device_class': 'power',
|
||||||
|
'friendly_name': 'Energy Site Solar power',
|
||||||
|
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||||
|
'unit_of_measurement': <UnitOfPower.KILO_WATT: 'kW'>,
|
||||||
|
}),
|
||||||
|
'context': <ANY>,
|
||||||
|
'entity_id': 'sensor.energy_site_solar_power',
|
||||||
|
'last_changed': <ANY>,
|
||||||
|
'last_reported': <ANY>,
|
||||||
|
'last_updated': <ANY>,
|
||||||
|
'state': 'unknown',
|
||||||
|
})
|
||||||
|
# ---
|
||||||
|
# name: test_sensors[sensor.energy_site_total_pack_energy-entry]
|
||||||
|
EntityRegistryEntrySnapshot({
|
||||||
|
'aliases': set({
|
||||||
|
}),
|
||||||
|
'area_id': None,
|
||||||
|
'capabilities': dict({
|
||||||
|
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||||
|
}),
|
||||||
|
'config_entry_id': <ANY>,
|
||||||
|
'device_class': None,
|
||||||
|
'device_id': <ANY>,
|
||||||
|
'disabled_by': None,
|
||||||
|
'domain': 'sensor',
|
||||||
|
'entity_category': <EntityCategory.DIAGNOSTIC: 'diagnostic'>,
|
||||||
|
'entity_id': 'sensor.energy_site_total_pack_energy',
|
||||||
|
'has_entity_name': True,
|
||||||
|
'hidden_by': None,
|
||||||
|
'icon': None,
|
||||||
|
'id': <ANY>,
|
||||||
|
'labels': set({
|
||||||
|
}),
|
||||||
|
'name': None,
|
||||||
|
'options': dict({
|
||||||
|
'sensor': dict({
|
||||||
|
'suggested_display_precision': 2,
|
||||||
|
}),
|
||||||
|
'sensor.private': dict({
|
||||||
|
'suggested_unit_of_measurement': <UnitOfEnergy.KILO_WATT_HOUR: 'kWh'>,
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
'original_device_class': <SensorDeviceClass.ENERGY_STORAGE: 'energy_storage'>,
|
||||||
|
'original_icon': None,
|
||||||
|
'original_name': 'Total pack energy',
|
||||||
|
'platform': 'tessie',
|
||||||
|
'previous_unique_id': None,
|
||||||
|
'supported_features': 0,
|
||||||
|
'translation_key': 'total_pack_energy',
|
||||||
|
'unique_id': '123456-total_pack_energy',
|
||||||
|
'unit_of_measurement': <UnitOfEnergy.KILO_WATT_HOUR: 'kWh'>,
|
||||||
|
})
|
||||||
|
# ---
|
||||||
|
# name: test_sensors[sensor.energy_site_total_pack_energy-state]
|
||||||
|
StateSnapshot({
|
||||||
|
'attributes': ReadOnlyDict({
|
||||||
|
'device_class': 'energy_storage',
|
||||||
|
'friendly_name': 'Energy Site Total pack energy',
|
||||||
|
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||||
|
'unit_of_measurement': <UnitOfEnergy.KILO_WATT_HOUR: 'kWh'>,
|
||||||
|
}),
|
||||||
|
'context': <ANY>,
|
||||||
|
'entity_id': 'sensor.energy_site_total_pack_energy',
|
||||||
|
'last_changed': <ANY>,
|
||||||
|
'last_reported': <ANY>,
|
||||||
|
'last_updated': <ANY>,
|
||||||
|
'state': 'unknown',
|
||||||
|
})
|
||||||
|
# ---
|
||||||
|
# name: test_sensors[sensor.energy_site_vpp_backup_reserve-entry]
|
||||||
|
EntityRegistryEntrySnapshot({
|
||||||
|
'aliases': set({
|
||||||
|
}),
|
||||||
|
'area_id': None,
|
||||||
|
'capabilities': None,
|
||||||
|
'config_entry_id': <ANY>,
|
||||||
|
'device_class': None,
|
||||||
|
'device_id': <ANY>,
|
||||||
|
'disabled_by': None,
|
||||||
|
'domain': 'sensor',
|
||||||
|
'entity_category': <EntityCategory.DIAGNOSTIC: 'diagnostic'>,
|
||||||
|
'entity_id': 'sensor.energy_site_vpp_backup_reserve',
|
||||||
|
'has_entity_name': True,
|
||||||
|
'hidden_by': None,
|
||||||
|
'icon': None,
|
||||||
|
'id': <ANY>,
|
||||||
|
'labels': set({
|
||||||
|
}),
|
||||||
|
'name': None,
|
||||||
|
'options': dict({
|
||||||
|
}),
|
||||||
|
'original_device_class': <SensorDeviceClass.BATTERY: 'battery'>,
|
||||||
|
'original_icon': None,
|
||||||
|
'original_name': 'VPP backup reserve',
|
||||||
|
'platform': 'tessie',
|
||||||
|
'previous_unique_id': None,
|
||||||
|
'supported_features': 0,
|
||||||
|
'translation_key': 'vpp_backup_reserve_percent',
|
||||||
|
'unique_id': '123456-vpp_backup_reserve_percent',
|
||||||
|
'unit_of_measurement': '%',
|
||||||
|
})
|
||||||
|
# ---
|
||||||
|
# name: test_sensors[sensor.energy_site_vpp_backup_reserve-state]
|
||||||
|
StateSnapshot({
|
||||||
|
'attributes': ReadOnlyDict({
|
||||||
|
'device_class': 'battery',
|
||||||
|
'friendly_name': 'Energy Site VPP backup reserve',
|
||||||
|
'unit_of_measurement': '%',
|
||||||
|
}),
|
||||||
|
'context': <ANY>,
|
||||||
|
'entity_id': 'sensor.energy_site_vpp_backup_reserve',
|
||||||
|
'last_changed': <ANY>,
|
||||||
|
'last_reported': <ANY>,
|
||||||
|
'last_updated': <ANY>,
|
||||||
|
'state': 'unknown',
|
||||||
|
})
|
||||||
|
# ---
|
||||||
# name: test_sensors[sensor.test_battery_level-entry]
|
# name: test_sensors[sensor.test_battery_level-entry]
|
||||||
EntityRegistryEntrySnapshot({
|
EntityRegistryEntrySnapshot({
|
||||||
'aliases': set({
|
'aliases': set({
|
||||||
|
@ -592,42 +1150,6 @@
|
||||||
'state': 'Giga Texas',
|
'state': 'Giga Texas',
|
||||||
})
|
})
|
||||||
# ---
|
# ---
|
||||||
# name: test_sensors[sensor.test_distance-entry]
|
|
||||||
EntityRegistryEntrySnapshot({
|
|
||||||
'aliases': set({
|
|
||||||
}),
|
|
||||||
'area_id': None,
|
|
||||||
'capabilities': dict({
|
|
||||||
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
|
||||||
}),
|
|
||||||
'config_entry_id': <ANY>,
|
|
||||||
'device_class': None,
|
|
||||||
'device_id': <ANY>,
|
|
||||||
'disabled_by': <RegistryEntryDisabler.INTEGRATION: 'integration'>,
|
|
||||||
'domain': 'sensor',
|
|
||||||
'entity_category': None,
|
|
||||||
'entity_id': 'sensor.test_distance',
|
|
||||||
'has_entity_name': True,
|
|
||||||
'hidden_by': None,
|
|
||||||
'icon': None,
|
|
||||||
'id': <ANY>,
|
|
||||||
'name': None,
|
|
||||||
'options': dict({
|
|
||||||
'sensor.private': dict({
|
|
||||||
'suggested_unit_of_measurement': <UnitOfLength.KILOMETERS: 'km'>,
|
|
||||||
}),
|
|
||||||
}),
|
|
||||||
'original_device_class': <SensorDeviceClass.DISTANCE: 'distance'>,
|
|
||||||
'original_icon': None,
|
|
||||||
'original_name': 'Distance',
|
|
||||||
'platform': 'tessie',
|
|
||||||
'previous_unique_id': None,
|
|
||||||
'supported_features': 0,
|
|
||||||
'translation_key': 'charge_state_est_battery_range',
|
|
||||||
'unique_id': 'VINVINVIN-charge_state_est_battery_range',
|
|
||||||
'unit_of_measurement': <UnitOfLength.KILOMETERS: 'km'>,
|
|
||||||
})
|
|
||||||
# ---
|
|
||||||
# name: test_sensors[sensor.test_distance_to_arrival-entry]
|
# name: test_sensors[sensor.test_distance_to_arrival-entry]
|
||||||
EntityRegistryEntrySnapshot({
|
EntityRegistryEntrySnapshot({
|
||||||
'aliases': set({
|
'aliases': set({
|
||||||
|
@ -1544,3 +2066,353 @@
|
||||||
'state': '0',
|
'state': '0',
|
||||||
})
|
})
|
||||||
# ---
|
# ---
|
||||||
|
# name: test_sensors[sensor.wall_connector_power-entry]
|
||||||
|
EntityRegistryEntrySnapshot({
|
||||||
|
'aliases': set({
|
||||||
|
}),
|
||||||
|
'area_id': None,
|
||||||
|
'capabilities': dict({
|
||||||
|
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||||
|
}),
|
||||||
|
'config_entry_id': <ANY>,
|
||||||
|
'device_class': None,
|
||||||
|
'device_id': <ANY>,
|
||||||
|
'disabled_by': None,
|
||||||
|
'domain': 'sensor',
|
||||||
|
'entity_category': None,
|
||||||
|
'entity_id': 'sensor.wall_connector_power',
|
||||||
|
'has_entity_name': True,
|
||||||
|
'hidden_by': None,
|
||||||
|
'icon': None,
|
||||||
|
'id': <ANY>,
|
||||||
|
'labels': set({
|
||||||
|
}),
|
||||||
|
'name': None,
|
||||||
|
'options': dict({
|
||||||
|
'sensor': dict({
|
||||||
|
'suggested_display_precision': 2,
|
||||||
|
}),
|
||||||
|
'sensor.private': dict({
|
||||||
|
'suggested_unit_of_measurement': <UnitOfPower.KILO_WATT: 'kW'>,
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
'original_device_class': <SensorDeviceClass.POWER: 'power'>,
|
||||||
|
'original_icon': None,
|
||||||
|
'original_name': 'Power',
|
||||||
|
'platform': 'tessie',
|
||||||
|
'previous_unique_id': None,
|
||||||
|
'supported_features': 0,
|
||||||
|
'translation_key': 'wall_connector_power',
|
||||||
|
'unique_id': '123456-abd-123-wall_connector_power',
|
||||||
|
'unit_of_measurement': <UnitOfPower.KILO_WATT: 'kW'>,
|
||||||
|
})
|
||||||
|
# ---
|
||||||
|
# name: test_sensors[sensor.wall_connector_power-state]
|
||||||
|
StateSnapshot({
|
||||||
|
'attributes': ReadOnlyDict({
|
||||||
|
'device_class': 'power',
|
||||||
|
'friendly_name': 'Wall Connector Power',
|
||||||
|
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||||
|
'unit_of_measurement': <UnitOfPower.KILO_WATT: 'kW'>,
|
||||||
|
}),
|
||||||
|
'context': <ANY>,
|
||||||
|
'entity_id': 'sensor.wall_connector_power',
|
||||||
|
'last_changed': <ANY>,
|
||||||
|
'last_reported': <ANY>,
|
||||||
|
'last_updated': <ANY>,
|
||||||
|
'state': 'unknown',
|
||||||
|
})
|
||||||
|
# ---
|
||||||
|
# name: test_sensors[sensor.wall_connector_power_2-entry]
|
||||||
|
EntityRegistryEntrySnapshot({
|
||||||
|
'aliases': set({
|
||||||
|
}),
|
||||||
|
'area_id': None,
|
||||||
|
'capabilities': dict({
|
||||||
|
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||||
|
}),
|
||||||
|
'config_entry_id': <ANY>,
|
||||||
|
'device_class': None,
|
||||||
|
'device_id': <ANY>,
|
||||||
|
'disabled_by': None,
|
||||||
|
'domain': 'sensor',
|
||||||
|
'entity_category': None,
|
||||||
|
'entity_id': 'sensor.wall_connector_power_2',
|
||||||
|
'has_entity_name': True,
|
||||||
|
'hidden_by': None,
|
||||||
|
'icon': None,
|
||||||
|
'id': <ANY>,
|
||||||
|
'labels': set({
|
||||||
|
}),
|
||||||
|
'name': None,
|
||||||
|
'options': dict({
|
||||||
|
'sensor': dict({
|
||||||
|
'suggested_display_precision': 2,
|
||||||
|
}),
|
||||||
|
'sensor.private': dict({
|
||||||
|
'suggested_unit_of_measurement': <UnitOfPower.KILO_WATT: 'kW'>,
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
'original_device_class': <SensorDeviceClass.POWER: 'power'>,
|
||||||
|
'original_icon': None,
|
||||||
|
'original_name': 'Power',
|
||||||
|
'platform': 'tessie',
|
||||||
|
'previous_unique_id': None,
|
||||||
|
'supported_features': 0,
|
||||||
|
'translation_key': 'wall_connector_power',
|
||||||
|
'unique_id': '123456-bcd-234-wall_connector_power',
|
||||||
|
'unit_of_measurement': <UnitOfPower.KILO_WATT: 'kW'>,
|
||||||
|
})
|
||||||
|
# ---
|
||||||
|
# name: test_sensors[sensor.wall_connector_power_2-state]
|
||||||
|
StateSnapshot({
|
||||||
|
'attributes': ReadOnlyDict({
|
||||||
|
'device_class': 'power',
|
||||||
|
'friendly_name': 'Wall Connector Power',
|
||||||
|
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||||
|
'unit_of_measurement': <UnitOfPower.KILO_WATT: 'kW'>,
|
||||||
|
}),
|
||||||
|
'context': <ANY>,
|
||||||
|
'entity_id': 'sensor.wall_connector_power_2',
|
||||||
|
'last_changed': <ANY>,
|
||||||
|
'last_reported': <ANY>,
|
||||||
|
'last_updated': <ANY>,
|
||||||
|
'state': 'unknown',
|
||||||
|
})
|
||||||
|
# ---
|
||||||
|
# name: test_sensors[sensor.wall_connector_state-entry]
|
||||||
|
EntityRegistryEntrySnapshot({
|
||||||
|
'aliases': set({
|
||||||
|
}),
|
||||||
|
'area_id': None,
|
||||||
|
'capabilities': dict({
|
||||||
|
'options': list([
|
||||||
|
'booting',
|
||||||
|
'charging',
|
||||||
|
'disconnected',
|
||||||
|
'connected',
|
||||||
|
'scheduled',
|
||||||
|
'negotiating',
|
||||||
|
'error',
|
||||||
|
'charging_finished',
|
||||||
|
'waiting_car',
|
||||||
|
'charging_reduced',
|
||||||
|
]),
|
||||||
|
}),
|
||||||
|
'config_entry_id': <ANY>,
|
||||||
|
'device_class': None,
|
||||||
|
'device_id': <ANY>,
|
||||||
|
'disabled_by': None,
|
||||||
|
'domain': 'sensor',
|
||||||
|
'entity_category': <EntityCategory.DIAGNOSTIC: 'diagnostic'>,
|
||||||
|
'entity_id': 'sensor.wall_connector_state',
|
||||||
|
'has_entity_name': True,
|
||||||
|
'hidden_by': None,
|
||||||
|
'icon': None,
|
||||||
|
'id': <ANY>,
|
||||||
|
'labels': set({
|
||||||
|
}),
|
||||||
|
'name': None,
|
||||||
|
'options': dict({
|
||||||
|
}),
|
||||||
|
'original_device_class': <SensorDeviceClass.ENUM: 'enum'>,
|
||||||
|
'original_icon': None,
|
||||||
|
'original_name': 'State',
|
||||||
|
'platform': 'tessie',
|
||||||
|
'previous_unique_id': None,
|
||||||
|
'supported_features': 0,
|
||||||
|
'translation_key': 'wall_connector_state',
|
||||||
|
'unique_id': '123456-abd-123-wall_connector_state',
|
||||||
|
'unit_of_measurement': None,
|
||||||
|
})
|
||||||
|
# ---
|
||||||
|
# name: test_sensors[sensor.wall_connector_state-state]
|
||||||
|
StateSnapshot({
|
||||||
|
'attributes': ReadOnlyDict({
|
||||||
|
'device_class': 'enum',
|
||||||
|
'friendly_name': 'Wall Connector State',
|
||||||
|
'options': list([
|
||||||
|
'booting',
|
||||||
|
'charging',
|
||||||
|
'disconnected',
|
||||||
|
'connected',
|
||||||
|
'scheduled',
|
||||||
|
'negotiating',
|
||||||
|
'error',
|
||||||
|
'charging_finished',
|
||||||
|
'waiting_car',
|
||||||
|
'charging_reduced',
|
||||||
|
]),
|
||||||
|
}),
|
||||||
|
'context': <ANY>,
|
||||||
|
'entity_id': 'sensor.wall_connector_state',
|
||||||
|
'last_changed': <ANY>,
|
||||||
|
'last_reported': <ANY>,
|
||||||
|
'last_updated': <ANY>,
|
||||||
|
'state': 'unknown',
|
||||||
|
})
|
||||||
|
# ---
|
||||||
|
# name: test_sensors[sensor.wall_connector_state_2-entry]
|
||||||
|
EntityRegistryEntrySnapshot({
|
||||||
|
'aliases': set({
|
||||||
|
}),
|
||||||
|
'area_id': None,
|
||||||
|
'capabilities': dict({
|
||||||
|
'options': list([
|
||||||
|
'booting',
|
||||||
|
'charging',
|
||||||
|
'disconnected',
|
||||||
|
'connected',
|
||||||
|
'scheduled',
|
||||||
|
'negotiating',
|
||||||
|
'error',
|
||||||
|
'charging_finished',
|
||||||
|
'waiting_car',
|
||||||
|
'charging_reduced',
|
||||||
|
]),
|
||||||
|
}),
|
||||||
|
'config_entry_id': <ANY>,
|
||||||
|
'device_class': None,
|
||||||
|
'device_id': <ANY>,
|
||||||
|
'disabled_by': None,
|
||||||
|
'domain': 'sensor',
|
||||||
|
'entity_category': <EntityCategory.DIAGNOSTIC: 'diagnostic'>,
|
||||||
|
'entity_id': 'sensor.wall_connector_state_2',
|
||||||
|
'has_entity_name': True,
|
||||||
|
'hidden_by': None,
|
||||||
|
'icon': None,
|
||||||
|
'id': <ANY>,
|
||||||
|
'labels': set({
|
||||||
|
}),
|
||||||
|
'name': None,
|
||||||
|
'options': dict({
|
||||||
|
}),
|
||||||
|
'original_device_class': <SensorDeviceClass.ENUM: 'enum'>,
|
||||||
|
'original_icon': None,
|
||||||
|
'original_name': 'State',
|
||||||
|
'platform': 'tessie',
|
||||||
|
'previous_unique_id': None,
|
||||||
|
'supported_features': 0,
|
||||||
|
'translation_key': 'wall_connector_state',
|
||||||
|
'unique_id': '123456-bcd-234-wall_connector_state',
|
||||||
|
'unit_of_measurement': None,
|
||||||
|
})
|
||||||
|
# ---
|
||||||
|
# name: test_sensors[sensor.wall_connector_state_2-state]
|
||||||
|
StateSnapshot({
|
||||||
|
'attributes': ReadOnlyDict({
|
||||||
|
'device_class': 'enum',
|
||||||
|
'friendly_name': 'Wall Connector State',
|
||||||
|
'options': list([
|
||||||
|
'booting',
|
||||||
|
'charging',
|
||||||
|
'disconnected',
|
||||||
|
'connected',
|
||||||
|
'scheduled',
|
||||||
|
'negotiating',
|
||||||
|
'error',
|
||||||
|
'charging_finished',
|
||||||
|
'waiting_car',
|
||||||
|
'charging_reduced',
|
||||||
|
]),
|
||||||
|
}),
|
||||||
|
'context': <ANY>,
|
||||||
|
'entity_id': 'sensor.wall_connector_state_2',
|
||||||
|
'last_changed': <ANY>,
|
||||||
|
'last_reported': <ANY>,
|
||||||
|
'last_updated': <ANY>,
|
||||||
|
'state': 'unknown',
|
||||||
|
})
|
||||||
|
# ---
|
||||||
|
# name: test_sensors[sensor.wall_connector_vehicle-entry]
|
||||||
|
EntityRegistryEntrySnapshot({
|
||||||
|
'aliases': set({
|
||||||
|
}),
|
||||||
|
'area_id': None,
|
||||||
|
'capabilities': None,
|
||||||
|
'config_entry_id': <ANY>,
|
||||||
|
'device_class': None,
|
||||||
|
'device_id': <ANY>,
|
||||||
|
'disabled_by': None,
|
||||||
|
'domain': 'sensor',
|
||||||
|
'entity_category': None,
|
||||||
|
'entity_id': 'sensor.wall_connector_vehicle',
|
||||||
|
'has_entity_name': True,
|
||||||
|
'hidden_by': None,
|
||||||
|
'icon': None,
|
||||||
|
'id': <ANY>,
|
||||||
|
'labels': set({
|
||||||
|
}),
|
||||||
|
'name': None,
|
||||||
|
'options': dict({
|
||||||
|
}),
|
||||||
|
'original_device_class': None,
|
||||||
|
'original_icon': None,
|
||||||
|
'original_name': 'Vehicle',
|
||||||
|
'platform': 'tessie',
|
||||||
|
'previous_unique_id': None,
|
||||||
|
'supported_features': 0,
|
||||||
|
'translation_key': 'vin',
|
||||||
|
'unique_id': '123456-abd-123-vin',
|
||||||
|
'unit_of_measurement': None,
|
||||||
|
})
|
||||||
|
# ---
|
||||||
|
# name: test_sensors[sensor.wall_connector_vehicle-state]
|
||||||
|
StateSnapshot({
|
||||||
|
'attributes': ReadOnlyDict({
|
||||||
|
'friendly_name': 'Wall Connector Vehicle',
|
||||||
|
}),
|
||||||
|
'context': <ANY>,
|
||||||
|
'entity_id': 'sensor.wall_connector_vehicle',
|
||||||
|
'last_changed': <ANY>,
|
||||||
|
'last_reported': <ANY>,
|
||||||
|
'last_updated': <ANY>,
|
||||||
|
'state': 'unknown',
|
||||||
|
})
|
||||||
|
# ---
|
||||||
|
# name: test_sensors[sensor.wall_connector_vehicle_2-entry]
|
||||||
|
EntityRegistryEntrySnapshot({
|
||||||
|
'aliases': set({
|
||||||
|
}),
|
||||||
|
'area_id': None,
|
||||||
|
'capabilities': None,
|
||||||
|
'config_entry_id': <ANY>,
|
||||||
|
'device_class': None,
|
||||||
|
'device_id': <ANY>,
|
||||||
|
'disabled_by': None,
|
||||||
|
'domain': 'sensor',
|
||||||
|
'entity_category': None,
|
||||||
|
'entity_id': 'sensor.wall_connector_vehicle_2',
|
||||||
|
'has_entity_name': True,
|
||||||
|
'hidden_by': None,
|
||||||
|
'icon': None,
|
||||||
|
'id': <ANY>,
|
||||||
|
'labels': set({
|
||||||
|
}),
|
||||||
|
'name': None,
|
||||||
|
'options': dict({
|
||||||
|
}),
|
||||||
|
'original_device_class': None,
|
||||||
|
'original_icon': None,
|
||||||
|
'original_name': 'Vehicle',
|
||||||
|
'platform': 'tessie',
|
||||||
|
'previous_unique_id': None,
|
||||||
|
'supported_features': 0,
|
||||||
|
'translation_key': 'vin',
|
||||||
|
'unique_id': '123456-bcd-234-vin',
|
||||||
|
'unit_of_measurement': None,
|
||||||
|
})
|
||||||
|
# ---
|
||||||
|
# name: test_sensors[sensor.wall_connector_vehicle_2-state]
|
||||||
|
StateSnapshot({
|
||||||
|
'attributes': ReadOnlyDict({
|
||||||
|
'friendly_name': 'Wall Connector Vehicle',
|
||||||
|
}),
|
||||||
|
'context': <ANY>,
|
||||||
|
'entity_id': 'sensor.wall_connector_vehicle_2',
|
||||||
|
'last_changed': <ANY>,
|
||||||
|
'last_reported': <ANY>,
|
||||||
|
'last_updated': <ANY>,
|
||||||
|
'state': 'unknown',
|
||||||
|
})
|
||||||
|
# ---
|
||||||
|
|
|
@ -2,11 +2,17 @@
|
||||||
|
|
||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
|
|
||||||
|
from freezegun.api import FrozenDateTimeFactory
|
||||||
|
from tesla_fleet_api.exceptions import Forbidden, InvalidToken
|
||||||
|
|
||||||
from homeassistant.components.tessie import PLATFORMS
|
from homeassistant.components.tessie import PLATFORMS
|
||||||
from homeassistant.components.tessie.coordinator import TESSIE_SYNC_INTERVAL
|
from homeassistant.components.tessie.coordinator import (
|
||||||
|
TESSIE_FLEET_API_SYNC_INTERVAL,
|
||||||
|
TESSIE_SYNC_INTERVAL,
|
||||||
|
)
|
||||||
|
from homeassistant.config_entries import ConfigEntryState
|
||||||
from homeassistant.const import STATE_OFF, STATE_ON, STATE_UNAVAILABLE, Platform
|
from homeassistant.const import STATE_OFF, STATE_ON, STATE_UNAVAILABLE, Platform
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
from homeassistant.util.dt import utcnow
|
|
||||||
|
|
||||||
from .common import (
|
from .common import (
|
||||||
ERROR_AUTH,
|
ERROR_AUTH,
|
||||||
|
@ -22,60 +28,124 @@ WAIT = timedelta(seconds=TESSIE_SYNC_INTERVAL)
|
||||||
|
|
||||||
|
|
||||||
async def test_coordinator_online(
|
async def test_coordinator_online(
|
||||||
hass: HomeAssistant, mock_get_state, mock_get_status
|
hass: HomeAssistant, mock_get_state, mock_get_status, freezer: FrozenDateTimeFactory
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Tests that the coordinator handles online vehicles."""
|
"""Tests that the coordinator handles online vehicles."""
|
||||||
|
|
||||||
await setup_platform(hass, PLATFORMS)
|
await setup_platform(hass, PLATFORMS)
|
||||||
|
|
||||||
async_fire_time_changed(hass, utcnow() + WAIT)
|
freezer.tick(WAIT)
|
||||||
|
async_fire_time_changed(hass)
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
mock_get_status.assert_called_once()
|
mock_get_status.assert_called_once()
|
||||||
mock_get_state.assert_called_once()
|
mock_get_state.assert_called_once()
|
||||||
assert hass.states.get("binary_sensor.test_status").state == STATE_ON
|
assert hass.states.get("binary_sensor.test_status").state == STATE_ON
|
||||||
|
|
||||||
|
|
||||||
async def test_coordinator_asleep(hass: HomeAssistant, mock_get_status) -> None:
|
async def test_coordinator_asleep(
|
||||||
|
hass: HomeAssistant, mock_get_status, freezer: FrozenDateTimeFactory
|
||||||
|
) -> None:
|
||||||
"""Tests that the coordinator handles asleep vehicles."""
|
"""Tests that the coordinator handles asleep vehicles."""
|
||||||
|
|
||||||
await setup_platform(hass, [Platform.BINARY_SENSOR])
|
await setup_platform(hass, [Platform.BINARY_SENSOR])
|
||||||
mock_get_status.return_value = TEST_VEHICLE_STATUS_ASLEEP
|
mock_get_status.return_value = TEST_VEHICLE_STATUS_ASLEEP
|
||||||
|
|
||||||
async_fire_time_changed(hass, utcnow() + WAIT)
|
freezer.tick(WAIT)
|
||||||
|
async_fire_time_changed(hass)
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
mock_get_status.assert_called_once()
|
mock_get_status.assert_called_once()
|
||||||
assert hass.states.get("binary_sensor.test_status").state == STATE_OFF
|
assert hass.states.get("binary_sensor.test_status").state == STATE_OFF
|
||||||
|
|
||||||
|
|
||||||
async def test_coordinator_clienterror(hass: HomeAssistant, mock_get_status) -> None:
|
async def test_coordinator_clienterror(
|
||||||
|
hass: HomeAssistant, mock_get_status, freezer: FrozenDateTimeFactory
|
||||||
|
) -> None:
|
||||||
"""Tests that the coordinator handles client errors."""
|
"""Tests that the coordinator handles client errors."""
|
||||||
|
|
||||||
mock_get_status.side_effect = ERROR_UNKNOWN
|
mock_get_status.side_effect = ERROR_UNKNOWN
|
||||||
await setup_platform(hass, [Platform.BINARY_SENSOR])
|
await setup_platform(hass, [Platform.BINARY_SENSOR])
|
||||||
|
|
||||||
async_fire_time_changed(hass, utcnow() + WAIT)
|
freezer.tick(WAIT)
|
||||||
|
async_fire_time_changed(hass)
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
mock_get_status.assert_called_once()
|
mock_get_status.assert_called_once()
|
||||||
assert hass.states.get("binary_sensor.test_status").state == STATE_UNAVAILABLE
|
assert hass.states.get("binary_sensor.test_status").state == STATE_UNAVAILABLE
|
||||||
|
|
||||||
|
|
||||||
async def test_coordinator_auth(hass: HomeAssistant, mock_get_status) -> None:
|
async def test_coordinator_auth(
|
||||||
"""Tests that the coordinator handles timeout errors."""
|
hass: HomeAssistant, mock_get_status, freezer: FrozenDateTimeFactory
|
||||||
|
) -> None:
|
||||||
|
"""Tests that the coordinator handles auth errors."""
|
||||||
|
|
||||||
mock_get_status.side_effect = ERROR_AUTH
|
mock_get_status.side_effect = ERROR_AUTH
|
||||||
await setup_platform(hass, [Platform.BINARY_SENSOR])
|
await setup_platform(hass, [Platform.BINARY_SENSOR])
|
||||||
|
|
||||||
async_fire_time_changed(hass, utcnow() + WAIT)
|
freezer.tick(WAIT)
|
||||||
|
async_fire_time_changed(hass)
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
mock_get_status.assert_called_once()
|
mock_get_status.assert_called_once()
|
||||||
|
|
||||||
|
|
||||||
async def test_coordinator_connection(hass: HomeAssistant, mock_get_status) -> None:
|
async def test_coordinator_connection(
|
||||||
|
hass: HomeAssistant, mock_get_status, freezer: FrozenDateTimeFactory
|
||||||
|
) -> None:
|
||||||
"""Tests that the coordinator handles connection errors."""
|
"""Tests that the coordinator handles connection errors."""
|
||||||
|
|
||||||
mock_get_status.side_effect = ERROR_CONNECTION
|
mock_get_status.side_effect = ERROR_CONNECTION
|
||||||
await setup_platform(hass, [Platform.BINARY_SENSOR])
|
await setup_platform(hass, [Platform.BINARY_SENSOR])
|
||||||
async_fire_time_changed(hass, utcnow() + WAIT)
|
freezer.tick(WAIT)
|
||||||
|
async_fire_time_changed(hass)
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
mock_get_status.assert_called_once()
|
mock_get_status.assert_called_once()
|
||||||
assert hass.states.get("binary_sensor.test_status").state == STATE_UNAVAILABLE
|
assert hass.states.get("binary_sensor.test_status").state == STATE_UNAVAILABLE
|
||||||
|
|
||||||
|
|
||||||
|
async def test_coordinator_live_error(
|
||||||
|
hass: HomeAssistant, mock_live_status, freezer: FrozenDateTimeFactory
|
||||||
|
) -> None:
|
||||||
|
"""Tests that the energy live coordinator handles fleet errors."""
|
||||||
|
|
||||||
|
await setup_platform(hass, [Platform.SENSOR])
|
||||||
|
|
||||||
|
mock_live_status.reset_mock()
|
||||||
|
mock_live_status.side_effect = Forbidden
|
||||||
|
freezer.tick(TESSIE_FLEET_API_SYNC_INTERVAL)
|
||||||
|
async_fire_time_changed(hass)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
mock_live_status.assert_called_once()
|
||||||
|
assert hass.states.get("sensor.energy_site_solar_power").state == STATE_UNAVAILABLE
|
||||||
|
|
||||||
|
|
||||||
|
async def test_coordinator_info_error(
|
||||||
|
hass: HomeAssistant, mock_site_info, freezer: FrozenDateTimeFactory
|
||||||
|
) -> None:
|
||||||
|
"""Tests that the energy info coordinator handles fleet errors."""
|
||||||
|
|
||||||
|
await setup_platform(hass, [Platform.SENSOR])
|
||||||
|
|
||||||
|
mock_site_info.reset_mock()
|
||||||
|
mock_site_info.side_effect = Forbidden
|
||||||
|
freezer.tick(TESSIE_FLEET_API_SYNC_INTERVAL)
|
||||||
|
async_fire_time_changed(hass)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
mock_site_info.assert_called_once()
|
||||||
|
assert (
|
||||||
|
hass.states.get("sensor.energy_site_vpp_backup_reserve").state
|
||||||
|
== STATE_UNAVAILABLE
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
async def test_coordinator_live_reauth(hass: HomeAssistant, mock_live_status) -> None:
|
||||||
|
"""Tests that the energy live coordinator handles auth errors."""
|
||||||
|
|
||||||
|
mock_live_status.side_effect = InvalidToken
|
||||||
|
entry = await setup_platform(hass, [Platform.SENSOR])
|
||||||
|
assert entry.state is ConfigEntryState.SETUP_ERROR
|
||||||
|
|
||||||
|
|
||||||
|
async def test_coordinator_info_reauth(hass: HomeAssistant, mock_site_info) -> None:
|
||||||
|
"""Tests that the energy info coordinator handles auth errors."""
|
||||||
|
|
||||||
|
mock_site_info.side_effect = InvalidToken
|
||||||
|
entry = await setup_platform(hass, [Platform.SENSOR])
|
||||||
|
assert entry.state is ConfigEntryState.SETUP_ERROR
|
||||||
|
|
|
@ -1,5 +1,9 @@
|
||||||
"""Test the Tessie init."""
|
"""Test the Tessie init."""
|
||||||
|
|
||||||
|
from unittest.mock import patch
|
||||||
|
|
||||||
|
from tesla_fleet_api.exceptions import TeslaFleetError
|
||||||
|
|
||||||
from homeassistant.config_entries import ConfigEntryState
|
from homeassistant.config_entries import ConfigEntryState
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
|
|
||||||
|
@ -44,3 +48,13 @@ async def test_connection_failure(
|
||||||
mock_get_state_of_all_vehicles.side_effect = ERROR_CONNECTION
|
mock_get_state_of_all_vehicles.side_effect = ERROR_CONNECTION
|
||||||
entry = await setup_platform(hass)
|
entry = await setup_platform(hass)
|
||||||
assert entry.state is ConfigEntryState.SETUP_RETRY
|
assert entry.state is ConfigEntryState.SETUP_RETRY
|
||||||
|
|
||||||
|
|
||||||
|
async def test_fleet_error(hass: HomeAssistant) -> None:
|
||||||
|
"""Test init with a fleet error."""
|
||||||
|
|
||||||
|
with patch(
|
||||||
|
"homeassistant.components.tessie.Tessie.products", side_effect=TeslaFleetError
|
||||||
|
):
|
||||||
|
entry = await setup_platform(hass)
|
||||||
|
assert entry.state is ConfigEntryState.SETUP_RETRY
|
||||||
|
|
Loading…
Reference in a new issue