mirror of
https://github.com/home-assistant/core.git
synced 2026-04-02 00:20:30 +01:00
Migrate Tessie setup and coordinator to tesla_fleet_api (#167018)
This commit is contained in:
@@ -1,19 +1,21 @@
|
||||
"""Tessie integration."""
|
||||
|
||||
import asyncio
|
||||
from http import HTTPStatus
|
||||
import logging
|
||||
|
||||
from aiohttp import ClientError, ClientResponseError
|
||||
from tesla_fleet_api.const import Scope
|
||||
from tesla_fleet_api.exceptions import (
|
||||
Forbidden,
|
||||
GatewayTimeout,
|
||||
InvalidResponse,
|
||||
InvalidToken,
|
||||
MissingToken,
|
||||
RateLimited,
|
||||
ServiceUnavailable,
|
||||
SubscriptionRequired,
|
||||
TeslaFleetError,
|
||||
)
|
||||
from tesla_fleet_api.tessie import Tessie
|
||||
from tessie_api import get_state_of_all_vehicles
|
||||
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import CONF_ACCESS_TOKEN, Platform
|
||||
@@ -54,57 +56,69 @@ _LOGGER = logging.getLogger(__name__)
|
||||
|
||||
type TessieConfigEntry = ConfigEntry[TessieData]
|
||||
|
||||
RETRY_EXCEPTIONS = (
|
||||
InvalidResponse,
|
||||
RateLimited,
|
||||
ServiceUnavailable,
|
||||
GatewayTimeout,
|
||||
)
|
||||
|
||||
|
||||
async def async_setup_entry(hass: HomeAssistant, entry: TessieConfigEntry) -> bool:
|
||||
"""Set up Tessie config."""
|
||||
api_key = entry.data[CONF_ACCESS_TOKEN]
|
||||
session = async_get_clientsession(hass)
|
||||
tessie = Tessie(session, api_key)
|
||||
|
||||
try:
|
||||
state_of_all_vehicles = await get_state_of_all_vehicles(
|
||||
session=session,
|
||||
api_key=api_key,
|
||||
only_active=True,
|
||||
)
|
||||
except ClientResponseError as e:
|
||||
if e.status == HTTPStatus.UNAUTHORIZED:
|
||||
raise ConfigEntryAuthFailed from e
|
||||
raise ConfigEntryError("Setup failed, unable to connect to Tessie") from e
|
||||
except ClientError as e:
|
||||
state_of_all_vehicles = await tessie.list_vehicles(only_active=True)
|
||||
except (InvalidToken, MissingToken) as e:
|
||||
raise ConfigEntryAuthFailed from e
|
||||
except RETRY_EXCEPTIONS as e:
|
||||
raise ConfigEntryNotReady from e
|
||||
except TeslaFleetError as e:
|
||||
raise ConfigEntryError(
|
||||
translation_domain=DOMAIN,
|
||||
translation_key="cannot_connect",
|
||||
) from e
|
||||
|
||||
vehicles = [
|
||||
TessieVehicleData(
|
||||
vin=vehicle["vin"],
|
||||
data_coordinator=TessieStateUpdateCoordinator(
|
||||
hass,
|
||||
entry,
|
||||
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"],
|
||||
vehicles: list[TessieVehicleData] = []
|
||||
for vehicle in state_of_all_vehicles["results"]:
|
||||
if vehicle["last_state"] is None:
|
||||
continue
|
||||
|
||||
vin = vehicle["vin"]
|
||||
vehicle_api = tessie.vehicles.create(vin)
|
||||
vehicles.append(
|
||||
TessieVehicleData(
|
||||
vin=vin,
|
||||
data_coordinator=TessieStateUpdateCoordinator(
|
||||
hass,
|
||||
entry,
|
||||
api=vehicle_api,
|
||||
api_key=api_key,
|
||||
vin=vin,
|
||||
data=vehicle["last_state"],
|
||||
),
|
||||
sw_version=vehicle["last_state"]["vehicle_state"]["car_version"].split(
|
||||
" "
|
||||
)[0],
|
||||
hw_version=vehicle["last_state"]["vehicle_config"]["driver_assist"],
|
||||
serial_number=vehicle["vin"],
|
||||
),
|
||||
device=DeviceInfo(
|
||||
identifiers={(DOMAIN, 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=vin,
|
||||
),
|
||||
)
|
||||
)
|
||||
for vehicle in state_of_all_vehicles["results"]
|
||||
if vehicle["last_state"] is not None
|
||||
]
|
||||
|
||||
# Energy Sites
|
||||
tessie = Tessie(session, api_key)
|
||||
energysites: list[TessieEnergyData] = []
|
||||
|
||||
try:
|
||||
|
||||
@@ -10,8 +10,7 @@ from typing import TYPE_CHECKING, Any
|
||||
from aiohttp import ClientError, ClientResponseError
|
||||
from tesla_fleet_api.const import TeslaEnergyPeriod
|
||||
from tesla_fleet_api.exceptions import InvalidToken, MissingToken, TeslaFleetError
|
||||
from tesla_fleet_api.tessie import EnergySite
|
||||
from tessie_api import get_state
|
||||
from tesla_fleet_api.tessie import EnergySite, Vehicle
|
||||
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.exceptions import ConfigEntryAuthFailed
|
||||
@@ -54,6 +53,7 @@ class TessieStateUpdateCoordinator(DataUpdateCoordinator[dict[str, Any]]):
|
||||
self,
|
||||
hass: HomeAssistant,
|
||||
config_entry: TessieConfigEntry,
|
||||
api: Vehicle,
|
||||
api_key: str,
|
||||
vin: str,
|
||||
data: dict[str, Any],
|
||||
@@ -66,6 +66,7 @@ class TessieStateUpdateCoordinator(DataUpdateCoordinator[dict[str, Any]]):
|
||||
name="Tessie",
|
||||
update_interval=timedelta(seconds=TESSIE_SYNC_INTERVAL),
|
||||
)
|
||||
self.api = api
|
||||
self.api_key = api_key
|
||||
self.vin = vin
|
||||
self.session = async_get_clientsession(hass)
|
||||
@@ -74,12 +75,14 @@ class TessieStateUpdateCoordinator(DataUpdateCoordinator[dict[str, Any]]):
|
||||
async def _async_update_data(self) -> dict[str, Any]:
|
||||
"""Update vehicle data using Tessie API."""
|
||||
try:
|
||||
vehicle = await get_state(
|
||||
session=self.session,
|
||||
api_key=self.api_key,
|
||||
vin=self.vin,
|
||||
use_cache=True,
|
||||
)
|
||||
vehicle = await self.api.state(use_cache=True)
|
||||
except (InvalidToken, MissingToken) as e:
|
||||
raise ConfigEntryAuthFailed from e
|
||||
except TeslaFleetError as e:
|
||||
raise UpdateFailed(
|
||||
translation_domain=DOMAIN,
|
||||
translation_key="cannot_connect",
|
||||
) from e
|
||||
except ClientResponseError as e:
|
||||
if e.status == HTTPStatus.UNAUTHORIZED:
|
||||
raise ConfigEntryAuthFailed from e
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from copy import deepcopy
|
||||
from unittest.mock import patch
|
||||
from unittest.mock import AsyncMock, patch
|
||||
|
||||
import pytest
|
||||
|
||||
@@ -25,9 +25,10 @@ from .common import (
|
||||
def mock_get_state():
|
||||
"""Mock get_state function."""
|
||||
with patch(
|
||||
"homeassistant.components.tessie.coordinator.get_state",
|
||||
return_value=TEST_VEHICLE_STATE_ONLINE,
|
||||
"tesla_fleet_api.tessie.Vehicle.state",
|
||||
new_callable=AsyncMock,
|
||||
) as mock_get_state:
|
||||
mock_get_state.return_value = TEST_VEHICLE_STATE_ONLINE
|
||||
yield mock_get_state
|
||||
|
||||
|
||||
@@ -35,9 +36,10 @@ def mock_get_state():
|
||||
def mock_get_state_of_all_vehicles():
|
||||
"""Mock get_state_of_all_vehicles function."""
|
||||
with patch(
|
||||
"homeassistant.components.tessie.get_state_of_all_vehicles",
|
||||
return_value=TEST_STATE_OF_ALL_VEHICLES,
|
||||
"tesla_fleet_api.tessie.Tessie.list_vehicles",
|
||||
new_callable=AsyncMock,
|
||||
) as mock_get_state_of_all_vehicles:
|
||||
mock_get_state_of_all_vehicles.return_value = TEST_STATE_OF_ALL_VEHICLES
|
||||
yield mock_get_state_of_all_vehicles
|
||||
|
||||
|
||||
|
||||
@@ -1,13 +1,18 @@
|
||||
"""Test the Tessie init."""
|
||||
|
||||
from unittest.mock import patch
|
||||
from unittest.mock import AsyncMock, patch
|
||||
|
||||
from tesla_fleet_api.exceptions import TeslaFleetError
|
||||
from tesla_fleet_api.exceptions import (
|
||||
InvalidRequest,
|
||||
InvalidToken,
|
||||
ServiceUnavailable,
|
||||
TeslaFleetError,
|
||||
)
|
||||
|
||||
from homeassistant.config_entries import ConfigEntryState
|
||||
from homeassistant.core import HomeAssistant
|
||||
|
||||
from .common import ERROR_AUTH, ERROR_CONNECTION, ERROR_UNKNOWN, setup_platform
|
||||
from .common import setup_platform
|
||||
|
||||
|
||||
async def test_load_unload(hass: HomeAssistant) -> None:
|
||||
@@ -20,32 +25,40 @@ async def test_load_unload(hass: HomeAssistant) -> None:
|
||||
assert entry.state is ConfigEntryState.NOT_LOADED
|
||||
|
||||
|
||||
async def test_runtime_vehicle_api_handle_is_optional(hass: HomeAssistant) -> None:
|
||||
"""Test the runtime vehicle API handle remains optional during migration."""
|
||||
|
||||
entry = await setup_platform(hass)
|
||||
assert all(vehicle.api is None for vehicle in entry.runtime_data.vehicles)
|
||||
|
||||
|
||||
async def test_auth_failure(
|
||||
hass: HomeAssistant, mock_get_state_of_all_vehicles
|
||||
hass: HomeAssistant, mock_get_state_of_all_vehicles: AsyncMock
|
||||
) -> None:
|
||||
"""Test init with an authentication error."""
|
||||
|
||||
mock_get_state_of_all_vehicles.side_effect = ERROR_AUTH
|
||||
mock_get_state_of_all_vehicles.side_effect = InvalidToken()
|
||||
entry = await setup_platform(hass)
|
||||
assert entry.state is ConfigEntryState.SETUP_ERROR
|
||||
|
||||
|
||||
async def test_unknown_failure(
|
||||
hass: HomeAssistant, mock_get_state_of_all_vehicles
|
||||
hass: HomeAssistant, mock_get_state_of_all_vehicles: AsyncMock
|
||||
) -> None:
|
||||
"""Test init with an client response error."""
|
||||
"""Test init with a non-retryable fleet API error."""
|
||||
|
||||
mock_get_state_of_all_vehicles.side_effect = ERROR_UNKNOWN
|
||||
mock_get_state_of_all_vehicles.side_effect = InvalidRequest()
|
||||
entry = await setup_platform(hass)
|
||||
assert entry.state is ConfigEntryState.SETUP_ERROR
|
||||
assert entry.reason == "Failed to connect"
|
||||
|
||||
|
||||
async def test_connection_failure(
|
||||
hass: HomeAssistant, mock_get_state_of_all_vehicles
|
||||
async def test_retryable_api_failure(
|
||||
hass: HomeAssistant, mock_get_state_of_all_vehicles: AsyncMock
|
||||
) -> None:
|
||||
"""Test init with a network connection error."""
|
||||
"""Test init with a retryable fleet API error."""
|
||||
|
||||
mock_get_state_of_all_vehicles.side_effect = ERROR_CONNECTION
|
||||
mock_get_state_of_all_vehicles.side_effect = ServiceUnavailable()
|
||||
entry = await setup_platform(hass)
|
||||
assert entry.state is ConfigEntryState.SETUP_RETRY
|
||||
|
||||
|
||||
Reference in New Issue
Block a user