Refactor Tessie for future PR (#120406)

* Bump tessie-api

* Refactor

* revert bump

* Fix cover

* Apply suggestions from code review

Co-authored-by: G Johansson <goran.johansson@shiftit.se>

---------

Co-authored-by: G Johansson <goran.johansson@shiftit.se>
This commit is contained in:
Brett Adams 2024-06-26 19:46:30 +10:00 committed by GitHub
parent a8bf671663
commit d4dc7d76d9
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
15 changed files with 92 additions and 71 deletions

View file

@ -11,9 +11,11 @@ from homeassistant.const import CONF_ACCESS_TOKEN, Platform
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from homeassistant.exceptions import ConfigEntryAuthFailed, ConfigEntryNotReady from homeassistant.exceptions import ConfigEntryAuthFailed, ConfigEntryNotReady
from homeassistant.helpers.aiohttp_client import async_get_clientsession from homeassistant.helpers.aiohttp_client import async_get_clientsession
from homeassistant.helpers.device_registry import DeviceInfo
from .const import DOMAIN, MODELS
from .coordinator import TessieStateUpdateCoordinator from .coordinator import TessieStateUpdateCoordinator
from .models import TessieData from .models import TessieData, TessieVehicleData
PLATFORMS = [ PLATFORMS = [
Platform.BINARY_SENSOR, Platform.BINARY_SENSOR,
@ -40,7 +42,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: TessieConfigEntry) -> bo
api_key = entry.data[CONF_ACCESS_TOKEN] api_key = entry.data[CONF_ACCESS_TOKEN]
try: try:
vehicles = await get_state_of_all_vehicles( state_of_all_vehicles = await get_state_of_all_vehicles(
session=async_get_clientsession(hass), session=async_get_clientsession(hass),
api_key=api_key, api_key=api_key,
only_active=True, only_active=True,
@ -54,13 +56,31 @@ async def async_setup_entry(hass: HomeAssistant, entry: TessieConfigEntry) -> bo
raise ConfigEntryNotReady from e raise ConfigEntryNotReady from e
vehicles = [ vehicles = [
TessieStateUpdateCoordinator( TessieVehicleData(
hass,
api_key=api_key,
vin=vehicle["vin"], vin=vehicle["vin"],
data=vehicle["last_state"], data_coordinator=TessieStateUpdateCoordinator(
hass,
api_key=api_key,
vin=vehicle["vin"],
data=vehicle["last_state"],
),
device=DeviceInfo(
identifiers={(DOMAIN, vehicle["vin"])},
manufacturer="Tesla",
configuration_url="https://my.tessie.com/",
name=vehicle["last_state"]["display_name"],
model=MODELS.get(
vehicle["last_state"]["vehicle_config"]["car_type"],
vehicle["last_state"]["vehicle_config"]["car_type"],
),
sw_version=vehicle["last_state"]["vehicle_state"]["car_version"].split(
" "
)[0],
hw_version=vehicle["last_state"]["vehicle_config"]["driver_assist"],
serial_number=vehicle["vin"],
),
) )
for vehicle in vehicles["results"] for vehicle in state_of_all_vehicles["results"]
if vehicle["last_state"] is not None if vehicle["last_state"] is not None
] ]

View file

@ -16,8 +16,8 @@ from homeassistant.helpers.entity_platform import AddEntitiesCallback
from . import TessieConfigEntry from . import TessieConfigEntry
from .const import TessieState from .const import TessieState
from .coordinator import TessieStateUpdateCoordinator
from .entity import TessieEntity from .entity import TessieEntity
from .models import TessieVehicleData
@dataclass(frozen=True, kw_only=True) @dataclass(frozen=True, kw_only=True)
@ -180,11 +180,11 @@ class TessieBinarySensorEntity(TessieEntity, BinarySensorEntity):
def __init__( def __init__(
self, self,
coordinator: TessieStateUpdateCoordinator, vehicle: TessieVehicleData,
description: TessieBinarySensorEntityDescription, description: TessieBinarySensorEntityDescription,
) -> None: ) -> None:
"""Initialize the sensor.""" """Initialize the sensor."""
super().__init__(coordinator, description.key) super().__init__(vehicle, description.key)
self.entity_description = description self.entity_description = description
@property @property

View file

@ -19,8 +19,8 @@ from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.entity_platform import AddEntitiesCallback
from . import TessieConfigEntry from . import TessieConfigEntry
from .coordinator import TessieStateUpdateCoordinator
from .entity import TessieEntity from .entity import TessieEntity
from .models import TessieVehicleData
@dataclass(frozen=True, kw_only=True) @dataclass(frozen=True, kw_only=True)
@ -67,11 +67,11 @@ class TessieButtonEntity(TessieEntity, ButtonEntity):
def __init__( def __init__(
self, self,
coordinator: TessieStateUpdateCoordinator, vehicle: TessieVehicleData,
description: TessieButtonEntityDescription, description: TessieButtonEntityDescription,
) -> None: ) -> None:
"""Initialize the Button.""" """Initialize the Button."""
super().__init__(coordinator, description.key) super().__init__(vehicle, description.key)
self.entity_description = description self.entity_description = description
async def async_press(self) -> None: async def async_press(self) -> None:

View file

@ -23,8 +23,8 @@ from homeassistant.helpers.entity_platform import AddEntitiesCallback
from . import TessieConfigEntry from . import TessieConfigEntry
from .const import TessieClimateKeeper from .const import TessieClimateKeeper
from .coordinator import TessieStateUpdateCoordinator
from .entity import TessieEntity from .entity import TessieEntity
from .models import TessieVehicleData
async def async_setup_entry( async def async_setup_entry(
@ -62,10 +62,10 @@ class TessieClimateEntity(TessieEntity, ClimateEntity):
def __init__( def __init__(
self, self,
coordinator: TessieStateUpdateCoordinator, vehicle: TessieVehicleData,
) -> None: ) -> None:
"""Initialize the Climate entity.""" """Initialize the Climate entity."""
super().__init__(coordinator, "primary") super().__init__(vehicle, "primary")
@property @property
def hvac_mode(self) -> HVACMode | None: def hvac_mode(self) -> HVACMode | None:

View file

@ -23,8 +23,8 @@ from homeassistant.helpers.entity_platform import AddEntitiesCallback
from . import TessieConfigEntry from . import TessieConfigEntry
from .const import TessieCoverStates from .const import TessieCoverStates
from .coordinator import TessieStateUpdateCoordinator
from .entity import TessieEntity from .entity import TessieEntity
from .models import TessieVehicleData
async def async_setup_entry( async def async_setup_entry(
@ -53,9 +53,9 @@ class TessieWindowEntity(TessieEntity, CoverEntity):
_attr_device_class = CoverDeviceClass.WINDOW _attr_device_class = CoverDeviceClass.WINDOW
_attr_supported_features = CoverEntityFeature.OPEN | CoverEntityFeature.CLOSE _attr_supported_features = CoverEntityFeature.OPEN | CoverEntityFeature.CLOSE
def __init__(self, coordinator: TessieStateUpdateCoordinator) -> None: def __init__(self, vehicle: TessieVehicleData) -> None:
"""Initialize the sensor.""" """Initialize the sensor."""
super().__init__(coordinator, "windows") super().__init__(vehicle, "windows")
@property @property
def is_closed(self) -> bool | None: def is_closed(self) -> bool | None:
@ -94,9 +94,9 @@ class TessieChargePortEntity(TessieEntity, CoverEntity):
_attr_device_class = CoverDeviceClass.DOOR _attr_device_class = CoverDeviceClass.DOOR
_attr_supported_features = CoverEntityFeature.OPEN | CoverEntityFeature.CLOSE _attr_supported_features = CoverEntityFeature.OPEN | CoverEntityFeature.CLOSE
def __init__(self, coordinator: TessieStateUpdateCoordinator) -> None: def __init__(self, vehicle: TessieVehicleData) -> None:
"""Initialize the sensor.""" """Initialize the sensor."""
super().__init__(coordinator, "charge_state_charge_port_door_open") super().__init__(vehicle, "charge_state_charge_port_door_open")
@property @property
def is_closed(self) -> bool | None: def is_closed(self) -> bool | None:
@ -120,9 +120,9 @@ class TessieFrontTrunkEntity(TessieEntity, CoverEntity):
_attr_device_class = CoverDeviceClass.DOOR _attr_device_class = CoverDeviceClass.DOOR
_attr_supported_features = CoverEntityFeature.OPEN _attr_supported_features = CoverEntityFeature.OPEN
def __init__(self, coordinator: TessieStateUpdateCoordinator) -> None: def __init__(self, vehicle: TessieVehicleData) -> None:
"""Initialize the sensor.""" """Initialize the sensor."""
super().__init__(coordinator, "vehicle_state_ft") super().__init__(vehicle, "vehicle_state_ft")
@property @property
def is_closed(self) -> bool | None: def is_closed(self) -> bool | None:
@ -141,9 +141,9 @@ class TessieRearTrunkEntity(TessieEntity, CoverEntity):
_attr_device_class = CoverDeviceClass.DOOR _attr_device_class = CoverDeviceClass.DOOR
_attr_supported_features = CoverEntityFeature.OPEN | CoverEntityFeature.CLOSE _attr_supported_features = CoverEntityFeature.OPEN | CoverEntityFeature.CLOSE
def __init__(self, coordinator: TessieStateUpdateCoordinator) -> None: def __init__(self, vehicle: TessieVehicleData) -> None:
"""Initialize the sensor.""" """Initialize the sensor."""
super().__init__(coordinator, "vehicle_state_rt") super().__init__(vehicle, "vehicle_state_rt")
@property @property
def is_closed(self) -> bool | None: def is_closed(self) -> bool | None:

View file

@ -9,8 +9,8 @@ from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.typing import StateType from homeassistant.helpers.typing import StateType
from . import TessieConfigEntry from . import TessieConfigEntry
from .coordinator import TessieStateUpdateCoordinator
from .entity import TessieEntity from .entity import TessieEntity
from .models import TessieVehicleData
async def async_setup_entry( async def async_setup_entry(
@ -36,10 +36,10 @@ class TessieDeviceTrackerEntity(TessieEntity, TrackerEntity):
def __init__( def __init__(
self, self,
coordinator: TessieStateUpdateCoordinator, vehicle: TessieVehicleData,
) -> None: ) -> None:
"""Initialize the device tracker.""" """Initialize the device tracker."""
super().__init__(coordinator, self.key) super().__init__(vehicle, self.key)
@property @property
def source_type(self) -> SourceType | str: def source_type(self) -> SourceType | str:

View file

@ -6,11 +6,11 @@ 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, MODELS from .const import DOMAIN
from .coordinator import TessieStateUpdateCoordinator from .coordinator import TessieStateUpdateCoordinator
from .models import TessieVehicleData
class TessieEntity(CoordinatorEntity[TessieStateUpdateCoordinator]): class TessieEntity(CoordinatorEntity[TessieStateUpdateCoordinator]):
@ -20,28 +20,17 @@ class TessieEntity(CoordinatorEntity[TessieStateUpdateCoordinator]):
def __init__( def __init__(
self, self,
coordinator: TessieStateUpdateCoordinator, vehicle: TessieVehicleData,
key: str, key: str,
) -> None: ) -> None:
"""Initialize common aspects of a Tessie entity.""" """Initialize common aspects of a Tessie entity."""
super().__init__(coordinator) super().__init__(vehicle.data_coordinator)
self.vin = coordinator.vin self.vin = vehicle.vin
self.key = key self.key = key
car_type = coordinator.data["vehicle_config_car_type"]
self._attr_translation_key = key self._attr_translation_key = key
self._attr_unique_id = f"{self.vin}-{key}" self._attr_unique_id = f"{vehicle.vin}-{key}"
self._attr_device_info = DeviceInfo( self._attr_device_info = vehicle.device
identifiers={(DOMAIN, self.vin)},
manufacturer="Tesla",
configuration_url="https://my.tessie.com/",
name=coordinator.data["display_name"],
model=MODELS.get(car_type, car_type),
sw_version=coordinator.data["vehicle_state_car_version"].split(" ")[0],
hw_version=coordinator.data["vehicle_config_driver_assist"],
serial_number=self.vin,
)
@property @property
def _value(self) -> Any: def _value(self) -> Any:

View file

@ -23,8 +23,8 @@ from homeassistant.helpers.entity_platform import AddEntitiesCallback
from . import TessieConfigEntry from . import TessieConfigEntry
from .const import DOMAIN, TessieChargeCableLockStates from .const import DOMAIN, TessieChargeCableLockStates
from .coordinator import TessieStateUpdateCoordinator
from .entity import TessieEntity from .entity import TessieEntity
from .models import TessieVehicleData
async def async_setup_entry( async def async_setup_entry(
@ -82,10 +82,10 @@ class TessieLockEntity(TessieEntity, LockEntity):
def __init__( def __init__(
self, self,
coordinator: TessieStateUpdateCoordinator, vehicle: TessieVehicleData,
) -> None: ) -> None:
"""Initialize the sensor.""" """Initialize the sensor."""
super().__init__(coordinator, "vehicle_state_locked") super().__init__(vehicle, "vehicle_state_locked")
@property @property
def is_locked(self) -> bool | None: def is_locked(self) -> bool | None:
@ -110,10 +110,10 @@ class TessieSpeedLimitEntity(TessieEntity, LockEntity):
def __init__( def __init__(
self, self,
coordinator: TessieStateUpdateCoordinator, vehicle: TessieVehicleData,
) -> None: ) -> None:
"""Initialize the sensor.""" """Initialize the sensor."""
super().__init__(coordinator, "vehicle_state_speed_limit_mode_active") super().__init__(vehicle, "vehicle_state_speed_limit_mode_active")
@property @property
def is_locked(self) -> bool | None: def is_locked(self) -> bool | None:
@ -160,10 +160,10 @@ class TessieCableLockEntity(TessieEntity, LockEntity):
def __init__( def __init__(
self, self,
coordinator: TessieStateUpdateCoordinator, vehicle: TessieVehicleData,
) -> None: ) -> None:
"""Initialize the sensor.""" """Initialize the sensor."""
super().__init__(coordinator, "charge_state_charge_port_latch") super().__init__(vehicle, "charge_state_charge_port_latch")
@property @property
def is_locked(self) -> bool | None: def is_locked(self) -> bool | None:

View file

@ -11,8 +11,8 @@ from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.entity_platform import AddEntitiesCallback
from . import TessieConfigEntry from . import TessieConfigEntry
from .coordinator import TessieStateUpdateCoordinator
from .entity import TessieEntity from .entity import TessieEntity
from .models import TessieVehicleData
STATES = { STATES = {
"Playing": MediaPlayerState.PLAYING, "Playing": MediaPlayerState.PLAYING,
@ -39,10 +39,10 @@ class TessieMediaEntity(TessieEntity, MediaPlayerEntity):
def __init__( def __init__(
self, self,
coordinator: TessieStateUpdateCoordinator, vehicle: TessieVehicleData,
) -> None: ) -> None:
"""Initialize the media player entity.""" """Initialize the media player entity."""
super().__init__(coordinator, "media") super().__init__(vehicle, "media")
@property @property
def state(self) -> MediaPlayerState: def state(self) -> MediaPlayerState:

View file

@ -4,6 +4,8 @@ from __future__ import annotations
from dataclasses import dataclass from dataclasses import dataclass
from homeassistant.helpers.device_registry import DeviceInfo
from .coordinator import TessieStateUpdateCoordinator from .coordinator import TessieStateUpdateCoordinator
@ -11,4 +13,13 @@ from .coordinator import TessieStateUpdateCoordinator
class TessieData: class TessieData:
"""Data for the Tessie integration.""" """Data for the Tessie integration."""
vehicles: list[TessieStateUpdateCoordinator] vehicles: list[TessieVehicleData]
@dataclass
class TessieVehicleData:
"""Data for a Tessie vehicle."""
data_coordinator: TessieStateUpdateCoordinator
device: DeviceInfo
vin: str

View file

@ -23,8 +23,8 @@ from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.entity_platform import AddEntitiesCallback
from . import TessieConfigEntry from . import TessieConfigEntry
from .coordinator import TessieStateUpdateCoordinator
from .entity import TessieEntity from .entity import TessieEntity
from .models import TessieVehicleData
@dataclass(frozen=True, kw_only=True) @dataclass(frozen=True, kw_only=True)
@ -101,11 +101,11 @@ class TessieNumberEntity(TessieEntity, NumberEntity):
def __init__( def __init__(
self, self,
coordinator: TessieStateUpdateCoordinator, vehicle: TessieVehicleData,
description: TessieNumberEntityDescription, description: TessieNumberEntityDescription,
) -> None: ) -> None:
"""Initialize the Number entity.""" """Initialize the Number entity."""
super().__init__(coordinator, description.key) super().__init__(vehicle, description.key)
self.entity_description = description self.entity_description = description
@property @property

View file

@ -35,7 +35,8 @@ async def async_setup_entry(
TessieSeatHeaterSelectEntity(vehicle, key) TessieSeatHeaterSelectEntity(vehicle, key)
for vehicle in data.vehicles for vehicle in data.vehicles
for key in SEAT_HEATERS for key in SEAT_HEATERS
if key in vehicle.data # not all vehicles have rear center or third row if key
in vehicle.data_coordinator.data # not all vehicles have rear center or third row
) )

View file

@ -34,8 +34,8 @@ from homeassistant.util.variance import ignore_variance
from . import TessieConfigEntry from . import TessieConfigEntry
from .const import TessieChargeStates from .const import TessieChargeStates
from .coordinator import TessieStateUpdateCoordinator
from .entity import TessieEntity from .entity import TessieEntity
from .models import TessieVehicleData
@callback @callback
@ -280,11 +280,11 @@ class TessieSensorEntity(TessieEntity, SensorEntity):
def __init__( def __init__(
self, self,
coordinator: TessieStateUpdateCoordinator, vehicle: TessieVehicleData,
description: TessieSensorEntityDescription, description: TessieSensorEntityDescription,
) -> None: ) -> None:
"""Initialize the sensor.""" """Initialize the sensor."""
super().__init__(coordinator, description.key) super().__init__(vehicle, description.key)
self.entity_description = description self.entity_description = description
@property @property

View file

@ -29,8 +29,8 @@ from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.entity_platform import AddEntitiesCallback
from . import TessieConfigEntry from . import TessieConfigEntry
from .coordinator import TessieStateUpdateCoordinator
from .entity import TessieEntity from .entity import TessieEntity
from .models import TessieVehicleData
@dataclass(frozen=True, kw_only=True) @dataclass(frozen=True, kw_only=True)
@ -84,7 +84,7 @@ async def async_setup_entry(
TessieSwitchEntity(vehicle, description) TessieSwitchEntity(vehicle, description)
for vehicle in entry.runtime_data.vehicles for vehicle in entry.runtime_data.vehicles
for description in DESCRIPTIONS for description in DESCRIPTIONS
if description.key in vehicle.data if description.key in vehicle.data_coordinator.data
), ),
( (
TessieChargeSwitchEntity(vehicle, CHARGE_DESCRIPTION) TessieChargeSwitchEntity(vehicle, CHARGE_DESCRIPTION)
@ -102,11 +102,11 @@ class TessieSwitchEntity(TessieEntity, SwitchEntity):
def __init__( def __init__(
self, self,
coordinator: TessieStateUpdateCoordinator, vehicle: TessieVehicleData,
description: TessieSwitchEntityDescription, description: TessieSwitchEntityDescription,
) -> None: ) -> None:
"""Initialize the Switch.""" """Initialize the Switch."""
super().__init__(coordinator, description.key) super().__init__(vehicle, description.key)
self.entity_description = description self.entity_description = description
@property @property

View file

@ -12,8 +12,8 @@ from homeassistant.helpers.entity_platform import AddEntitiesCallback
from . import TessieConfigEntry from . import TessieConfigEntry
from .const import TessieUpdateStatus from .const import TessieUpdateStatus
from .coordinator import TessieStateUpdateCoordinator
from .entity import TessieEntity from .entity import TessieEntity
from .models import TessieVehicleData
async def async_setup_entry( async def async_setup_entry(
@ -34,10 +34,10 @@ class TessieUpdateEntity(TessieEntity, UpdateEntity):
def __init__( def __init__(
self, self,
coordinator: TessieStateUpdateCoordinator, vehicle: TessieVehicleData,
) -> None: ) -> None:
"""Initialize the Update.""" """Initialize the Update."""
super().__init__(coordinator, "update") super().__init__(vehicle, "update")
@property @property
def supported_features(self) -> UpdateEntityFeature: def supported_features(self) -> UpdateEntityFeature: