Remove sleep and forbidden handling from Teslemetry (#132784)

This commit is contained in:
Brett Adams 2024-12-10 17:35:53 +10:00 committed by GitHub
parent 1ee3b68824
commit 17521f25b6
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
25 changed files with 107 additions and 555 deletions

View File

@ -253,7 +253,6 @@ def create_handle_vehicle_stream(vin: str, coordinator) -> Callable[[dict], None
"""Handle vehicle data from the stream.""" """Handle vehicle data from the stream."""
if "vehicle_data" in data: if "vehicle_data" in data:
LOGGER.debug("Streaming received vehicle data from %s", vin) LOGGER.debug("Streaming received vehicle data from %s", vin)
coordinator.updated_once = True
coordinator.async_set_updated_data(flatten(data["vehicle_data"])) coordinator.async_set_updated_data(flatten(data["vehicle_data"]))
elif "state" in data: elif "state" in data:
LOGGER.debug("Streaming received state from %s", vin) LOGGER.debug("Streaming received state from %s", vin)

View File

@ -223,15 +223,12 @@ class TeslemetryVehicleBinarySensorEntity(TeslemetryVehicleEntity, BinarySensorE
def _async_update_attrs(self) -> None: def _async_update_attrs(self) -> None:
"""Update the attributes of the binary sensor.""" """Update the attributes of the binary sensor."""
if self.coordinator.updated_once: if self._value is None:
if self._value is None: self._attr_available = False
self._attr_available = False
self._attr_is_on = None
else:
self._attr_available = True
self._attr_is_on = self.entity_description.is_on(self._value)
else:
self._attr_is_on = None self._attr_is_on = None
else:
self._attr_available = True
self._attr_is_on = self.entity_description.is_on(self._value)
class TeslemetryEnergyLiveBinarySensorEntity( class TeslemetryEnergyLiveBinarySensorEntity(

View File

@ -96,9 +96,7 @@ class TeslemetryClimateEntity(TeslemetryVehicleEntity, ClimateEntity):
def _async_update_attrs(self) -> None: def _async_update_attrs(self) -> None:
"""Update the attributes of the entity.""" """Update the attributes of the entity."""
value = self.get("climate_state_is_climate_on") value = self.get("climate_state_is_climate_on")
if value is None: if value:
self._attr_hvac_mode = None
elif value:
self._attr_hvac_mode = HVACMode.HEAT_COOL self._attr_hvac_mode = HVACMode.HEAT_COOL
else: else:
self._attr_hvac_mode = HVACMode.OFF self._attr_hvac_mode = HVACMode.OFF

View File

@ -6,18 +6,16 @@ from typing import Any
from tesla_fleet_api import EnergySpecific, VehicleSpecific from tesla_fleet_api import EnergySpecific, VehicleSpecific
from tesla_fleet_api.const import TeslaEnergyPeriod, VehicleDataEndpoint from tesla_fleet_api.const import TeslaEnergyPeriod, VehicleDataEndpoint
from tesla_fleet_api.exceptions import ( from tesla_fleet_api.exceptions import (
Forbidden,
InvalidToken, InvalidToken,
SubscriptionRequired, SubscriptionRequired,
TeslaFleetError, TeslaFleetError,
VehicleOffline,
) )
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from homeassistant.exceptions import ConfigEntryAuthFailed from homeassistant.exceptions import ConfigEntryAuthFailed
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
from .const import ENERGY_HISTORY_FIELDS, LOGGER, TeslemetryState from .const import ENERGY_HISTORY_FIELDS, LOGGER
from .helpers import flatten from .helpers import flatten
VEHICLE_INTERVAL = timedelta(seconds=30) VEHICLE_INTERVAL = timedelta(seconds=30)
@ -39,7 +37,6 @@ ENDPOINTS = [
class TeslemetryVehicleDataCoordinator(DataUpdateCoordinator[dict[str, Any]]): class TeslemetryVehicleDataCoordinator(DataUpdateCoordinator[dict[str, Any]]):
"""Class to manage fetching data from the Teslemetry API.""" """Class to manage fetching data from the Teslemetry API."""
updated_once: bool
last_active: datetime last_active: datetime
def __init__( def __init__(
@ -54,43 +51,24 @@ class TeslemetryVehicleDataCoordinator(DataUpdateCoordinator[dict[str, Any]]):
) )
self.api = api self.api = api
self.data = flatten(product) self.data = flatten(product)
self.updated_once = False
self.last_active = datetime.now() self.last_active = datetime.now()
async def _async_update_data(self) -> dict[str, Any]: async def _async_update_data(self) -> dict[str, Any]:
"""Update vehicle data using Teslemetry API.""" """Update vehicle data using Teslemetry API."""
try: try:
if self.data["state"] != TeslemetryState.ONLINE: data = (await self.api.vehicle_data(endpoints=ENDPOINTS))["response"]
response = await self.api.vehicle() except (InvalidToken, SubscriptionRequired) as e:
self.data["state"] = response["response"]["state"]
if self.data["state"] != TeslemetryState.ONLINE:
return self.data
response = await self.api.vehicle_data(endpoints=ENDPOINTS)
data = response["response"]
except VehicleOffline:
self.data["state"] = TeslemetryState.OFFLINE
return self.data
except InvalidToken as e:
raise ConfigEntryAuthFailed from e
except SubscriptionRequired as e:
raise ConfigEntryAuthFailed from e raise ConfigEntryAuthFailed from e
except TeslaFleetError as e: except TeslaFleetError as e:
raise UpdateFailed(e.message) from e raise UpdateFailed(e.message) from e
self.updated_once = True
return flatten(data) return flatten(data)
class TeslemetryEnergySiteLiveCoordinator(DataUpdateCoordinator[dict[str, Any]]): class TeslemetryEnergySiteLiveCoordinator(DataUpdateCoordinator[dict[str, Any]]):
"""Class to manage fetching energy site live status from the Teslemetry API.""" """Class to manage fetching energy site live status from the Teslemetry API."""
updated_once: bool
def __init__(self, hass: HomeAssistant, api: EnergySpecific) -> None: def __init__(self, hass: HomeAssistant, api: EnergySpecific) -> None:
"""Initialize Teslemetry Energy Site Live coordinator.""" """Initialize Teslemetry Energy Site Live coordinator."""
super().__init__( super().__init__(
@ -106,7 +84,7 @@ class TeslemetryEnergySiteLiveCoordinator(DataUpdateCoordinator[dict[str, Any]])
try: try:
data = (await self.api.live_status())["response"] data = (await self.api.live_status())["response"]
except (InvalidToken, Forbidden, SubscriptionRequired) as e: except (InvalidToken, SubscriptionRequired) as e:
raise ConfigEntryAuthFailed from e raise ConfigEntryAuthFailed from e
except TeslaFleetError as e: except TeslaFleetError as e:
raise UpdateFailed(e.message) from e raise UpdateFailed(e.message) from e
@ -122,8 +100,6 @@ class TeslemetryEnergySiteLiveCoordinator(DataUpdateCoordinator[dict[str, Any]])
class TeslemetryEnergySiteInfoCoordinator(DataUpdateCoordinator[dict[str, Any]]): class TeslemetryEnergySiteInfoCoordinator(DataUpdateCoordinator[dict[str, Any]]):
"""Class to manage fetching energy site info from the Teslemetry API.""" """Class to manage fetching energy site info from the Teslemetry API."""
updated_once: bool
def __init__(self, hass: HomeAssistant, api: EnergySpecific, product: dict) -> None: def __init__(self, hass: HomeAssistant, api: EnergySpecific, product: dict) -> None:
"""Initialize Teslemetry Energy Info coordinator.""" """Initialize Teslemetry Energy Info coordinator."""
super().__init__( super().__init__(
@ -140,7 +116,7 @@ class TeslemetryEnergySiteInfoCoordinator(DataUpdateCoordinator[dict[str, Any]])
try: try:
data = (await self.api.site_info())["response"] data = (await self.api.site_info())["response"]
except (InvalidToken, Forbidden, SubscriptionRequired) as e: except (InvalidToken, SubscriptionRequired) as e:
raise ConfigEntryAuthFailed from e raise ConfigEntryAuthFailed from e
except TeslaFleetError as e: except TeslaFleetError as e:
raise UpdateFailed(e.message) from e raise UpdateFailed(e.message) from e
@ -151,8 +127,6 @@ class TeslemetryEnergySiteInfoCoordinator(DataUpdateCoordinator[dict[str, Any]])
class TeslemetryEnergyHistoryCoordinator(DataUpdateCoordinator[dict[str, Any]]): class TeslemetryEnergyHistoryCoordinator(DataUpdateCoordinator[dict[str, Any]]):
"""Class to manage fetching energy site info from the Teslemetry API.""" """Class to manage fetching energy site info from the Teslemetry API."""
updated_once: bool
def __init__(self, hass: HomeAssistant, api: EnergySpecific) -> None: def __init__(self, hass: HomeAssistant, api: EnergySpecific) -> None:
"""Initialize Teslemetry Energy Info coordinator.""" """Initialize Teslemetry Energy Info coordinator."""
super().__init__( super().__init__(
@ -168,13 +142,11 @@ class TeslemetryEnergyHistoryCoordinator(DataUpdateCoordinator[dict[str, Any]]):
try: try:
data = (await self.api.energy_history(TeslaEnergyPeriod.DAY))["response"] data = (await self.api.energy_history(TeslaEnergyPeriod.DAY))["response"]
except (InvalidToken, Forbidden, SubscriptionRequired) as e: except (InvalidToken, SubscriptionRequired) as e:
raise ConfigEntryAuthFailed from e raise ConfigEntryAuthFailed from e
except TeslaFleetError as e: except TeslaFleetError as e:
raise UpdateFailed(e.message) from e raise UpdateFailed(e.message) from e
self.updated_once = True
# Add all time periods together # Add all time periods together
output = {key: 0 for key in ENERGY_HISTORY_FIELDS} output = {key: 0 for key in ENERGY_HISTORY_FIELDS}
for period in data.get("time_series", []): for period in data.get("time_series", []):

View File

@ -73,9 +73,6 @@ class TeslemetryWindowEntity(TeslemetryVehicleEntity, CoverEntity):
# All closed set to closed # All closed set to closed
elif CLOSED == fd == fp == rd == rp: elif CLOSED == fd == fp == rd == rp:
self._attr_is_closed = True self._attr_is_closed = True
# Otherwise, set to unknown
else:
self._attr_is_closed = None
async def async_open_cover(self, **kwargs: Any) -> None: async def async_open_cover(self, **kwargs: Any) -> None:
"""Vent windows.""" """Vent windows."""

View File

@ -82,8 +82,6 @@ class TeslemetryCableLockEntity(TeslemetryVehicleEntity, LockEntity):
def _async_update_attrs(self) -> None: def _async_update_attrs(self) -> None:
"""Update entity attributes.""" """Update entity attributes."""
if self._value is None:
self._attr_is_locked = None
self._attr_is_locked = self._value == ENGAGED self._attr_is_locked = self._value == ENGAGED
async def async_lock(self, **kwargs: Any) -> None: async def async_lock(self, **kwargs: Any) -> None:

View File

@ -90,10 +90,12 @@ async def async_setup_entry(
) )
for description in SEAT_HEATER_DESCRIPTIONS for description in SEAT_HEATER_DESCRIPTIONS
for vehicle in entry.runtime_data.vehicles for vehicle in entry.runtime_data.vehicles
if description.key in vehicle.coordinator.data
), ),
( (
TeslemetryWheelHeaterSelectEntity(vehicle, entry.runtime_data.scopes) TeslemetryWheelHeaterSelectEntity(vehicle, entry.runtime_data.scopes)
for vehicle in entry.runtime_data.vehicles for vehicle in entry.runtime_data.vehicles
if vehicle.coordinator.data.get("climate_state_steering_wheel_heater")
), ),
( (
TeslemetryOperationSelectEntity(energysite, entry.runtime_data.scopes) TeslemetryOperationSelectEntity(energysite, entry.runtime_data.scopes)
@ -137,7 +139,7 @@ class TeslemetrySeatHeaterSelectEntity(TeslemetryVehicleEntity, SelectEntity):
"""Handle updated data from the coordinator.""" """Handle updated data from the coordinator."""
self._attr_available = self.entity_description.available_fn(self) self._attr_available = self.entity_description.available_fn(self)
value = self._value value = self._value
if value is None: if not isinstance(value, int):
self._attr_current_option = None self._attr_current_option = None
else: else:
self._attr_current_option = self._attr_options[value] self._attr_current_option = self._attr_options[value]
@ -182,7 +184,7 @@ class TeslemetryWheelHeaterSelectEntity(TeslemetryVehicleEntity, SelectEntity):
"""Handle updated data from the coordinator.""" """Handle updated data from the coordinator."""
value = self._value value = self._value
if value is None: if not isinstance(value, int):
self._attr_current_option = None self._attr_current_option = None
else: else:
self._attr_current_option = self._attr_options[value] self._attr_current_option = self._attr_options[value]

View File

@ -102,6 +102,7 @@ async def async_setup_entry(
) )
for vehicle in entry.runtime_data.vehicles for vehicle in entry.runtime_data.vehicles
for description in VEHICLE_DESCRIPTIONS for description in VEHICLE_DESCRIPTIONS
if description.key in vehicle.coordinator.data
), ),
( (
TeslemetryChargeSwitchEntity( TeslemetryChargeSwitchEntity(
@ -150,10 +151,7 @@ class TeslemetryVehicleSwitchEntity(TeslemetryVehicleEntity, TeslemetrySwitchEnt
def _async_update_attrs(self) -> None: def _async_update_attrs(self) -> None:
"""Update the attributes of the sensor.""" """Update the attributes of the sensor."""
if self._value is None: self._attr_is_on = bool(self._value)
self._attr_is_on = None
else:
self._attr_is_on = bool(self._value)
async def async_turn_on(self, **kwargs: Any) -> None: async def async_turn_on(self, **kwargs: Any) -> None:
"""Turn on the Switch.""" """Turn on the Switch."""

View File

@ -12,6 +12,8 @@ WAKE_UP_ASLEEP = {"response": {"state": TeslemetryState.ASLEEP}, "error": None}
PRODUCTS = load_json_object_fixture("products.json", DOMAIN) PRODUCTS = load_json_object_fixture("products.json", DOMAIN)
VEHICLE_DATA = load_json_object_fixture("vehicle_data.json", DOMAIN) VEHICLE_DATA = load_json_object_fixture("vehicle_data.json", DOMAIN)
VEHICLE_DATA_ASLEEP = load_json_object_fixture("vehicle_data.json", DOMAIN)
VEHICLE_DATA_ASLEEP["response"]["state"] = TeslemetryState.OFFLINE
VEHICLE_DATA_ALT = load_json_object_fixture("vehicle_data_alt.json", DOMAIN) VEHICLE_DATA_ALT = load_json_object_fixture("vehicle_data_alt.json", DOMAIN)
LIVE_STATUS = load_json_object_fixture("live_status.json", DOMAIN) LIVE_STATUS = load_json_object_fixture("live_status.json", DOMAIN)
SITE_INFO = load_json_object_fixture("site_info.json", DOMAIN) SITE_INFO = load_json_object_fixture("site_info.json", DOMAIN)

View File

@ -24,7 +24,6 @@
"battery_range": 266.87, "battery_range": 266.87,
"charge_amps": 16, "charge_amps": 16,
"charge_current_request": 16, "charge_current_request": 16,
"charge_current_request_max": 16,
"charge_enable_request": true, "charge_enable_request": true,
"charge_energy_added": 0, "charge_energy_added": 0,
"charge_limit_soc": 80, "charge_limit_soc": 80,
@ -72,16 +71,16 @@
"user_charge_enable_request": true "user_charge_enable_request": true
}, },
"climate_state": { "climate_state": {
"allow_cabin_overheat_protection": true, "allow_cabin_overheat_protection": null,
"auto_seat_climate_left": false, "auto_seat_climate_left": false,
"auto_seat_climate_right": false, "auto_seat_climate_right": false,
"auto_steering_wheel_heat": false, "auto_steering_wheel_heat": false,
"battery_heater": true, "battery_heater": true,
"battery_heater_no_power": null, "battery_heater_no_power": null,
"cabin_overheat_protection": "Off", "cabin_overheat_protection": null,
"cabin_overheat_protection_actively_cooling": false, "cabin_overheat_protection_actively_cooling": false,
"climate_keeper_mode": "off", "climate_keeper_mode": "off",
"cop_activation_temperature": "Low", "cop_activation_temperature": null,
"defrost_mode": 0, "defrost_mode": 0,
"driver_temp_setting": 22, "driver_temp_setting": 22,
"fan_status": 0, "fan_status": 0,
@ -106,7 +105,7 @@
"seat_heater_right": 0, "seat_heater_right": 0,
"side_mirror_heaters": false, "side_mirror_heaters": false,
"steering_wheel_heat_level": 0, "steering_wheel_heat_level": 0,
"steering_wheel_heater": false, "steering_wheel_heater": true,
"supports_fan_only_cabin_overheat_protection": true, "supports_fan_only_cabin_overheat_protection": true,
"timestamp": 1705707520649, "timestamp": 1705707520649,
"wiper_blade_heater": false "wiper_blade_heater": false
@ -204,9 +203,9 @@
"is_user_present": true, "is_user_present": true,
"locked": false, "locked": false,
"media_info": { "media_info": {
"audio_volume": 2.6667, "audio_volume": null,
"audio_volume_increment": 0.333333, "audio_volume_increment": null,
"audio_volume_max": 10.333333, "audio_volume_max": null,
"media_playback_status": "Stopped", "media_playback_status": "Stopped",
"now_playing_album": "", "now_playing_album": "",
"now_playing_artist": "", "now_playing_artist": "",

View File

@ -208,7 +208,7 @@
'last_changed': <ANY>, 'last_changed': <ANY>,
'last_reported': <ANY>, 'last_reported': <ANY>,
'last_updated': <ANY>, 'last_updated': <ANY>,
'state': 'off', 'state': 'unknown',
}) })
# --- # ---
# name: test_climate_alt[climate.test_climate-entry] # name: test_climate_alt[climate.test_climate-entry]
@ -365,146 +365,6 @@
'unit_of_measurement': None, 'unit_of_measurement': None,
}) })
# --- # ---
# name: test_climate_offline[climate.test_cabin_overheat_protection-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': dict({
'hvac_modes': list([
<HVACMode.OFF: 'off'>,
<HVACMode.COOL: 'cool'>,
<HVACMode.FAN_ONLY: 'fan_only'>,
]),
'max_temp': 40,
'min_temp': 30,
'target_temp_step': 5,
}),
'config_entry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'climate',
'entity_category': None,
'entity_id': 'climate.test_cabin_overheat_protection',
'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': 'Cabin overheat protection',
'platform': 'teslemetry',
'previous_unique_id': None,
'supported_features': <ClimateEntityFeature: 384>,
'translation_key': 'climate_state_cabin_overheat_protection',
'unique_id': 'LRW3F7EK4NC700000-climate_state_cabin_overheat_protection',
'unit_of_measurement': None,
})
# ---
# name: test_climate_offline[climate.test_cabin_overheat_protection-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'current_temperature': None,
'friendly_name': 'Test Cabin overheat protection',
'hvac_modes': list([
<HVACMode.OFF: 'off'>,
<HVACMode.COOL: 'cool'>,
<HVACMode.FAN_ONLY: 'fan_only'>,
]),
'max_temp': 40,
'min_temp': 30,
'supported_features': <ClimateEntityFeature: 384>,
'target_temp_step': 5,
}),
'context': <ANY>,
'entity_id': 'climate.test_cabin_overheat_protection',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'unknown',
})
# ---
# name: test_climate_offline[climate.test_climate-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': dict({
'hvac_modes': list([
<HVACMode.HEAT_COOL: 'heat_cool'>,
<HVACMode.OFF: 'off'>,
]),
'max_temp': 28.0,
'min_temp': 15.0,
'preset_modes': list([
'off',
'keep',
'dog',
'camp',
]),
}),
'config_entry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'climate',
'entity_category': None,
'entity_id': 'climate.test_climate',
'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': 'Climate',
'platform': 'teslemetry',
'previous_unique_id': None,
'supported_features': <ClimateEntityFeature: 401>,
'translation_key': <TeslemetryClimateSide.DRIVER: 'driver_temp'>,
'unique_id': 'LRW3F7EK4NC700000-driver_temp',
'unit_of_measurement': None,
})
# ---
# name: test_climate_offline[climate.test_climate-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'current_temperature': None,
'friendly_name': 'Test Climate',
'hvac_modes': list([
<HVACMode.HEAT_COOL: 'heat_cool'>,
<HVACMode.OFF: 'off'>,
]),
'max_temp': 28.0,
'min_temp': 15.0,
'preset_mode': None,
'preset_modes': list([
'off',
'keep',
'dog',
'camp',
]),
'supported_features': <ClimateEntityFeature: 401>,
'temperature': None,
}),
'context': <ANY>,
'entity_id': 'climate.test_climate',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'unknown',
})
# ---
# name: test_invalid_error[error] # name: test_invalid_error[error]
'Command returned exception: The data request or command is unknown.' 'Command returned exception: The data request or command is unknown.'
# --- # ---

View File

@ -99,3 +99,37 @@
'state': 'home', 'state': 'home',
}) })
# --- # ---
# name: test_device_tracker_alt[device_tracker.test_location-statealt]
StateSnapshot({
'attributes': ReadOnlyDict({
'friendly_name': 'Test Location',
'gps_accuracy': 0,
'latitude': -30.222626,
'longitude': -97.6236871,
'source_type': <SourceType.GPS: 'gps'>,
}),
'context': <ANY>,
'entity_id': 'device_tracker.test_location',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'not_home',
})
# ---
# name: test_device_tracker_alt[device_tracker.test_route-statealt]
StateSnapshot({
'attributes': ReadOnlyDict({
'friendly_name': 'Test Route',
'gps_accuracy': 0,
'latitude': 30.2226265,
'longitude': -97.6236871,
'source_type': <SourceType.GPS: 'gps'>,
}),
'context': <ANY>,
'entity_id': 'device_tracker.test_route',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'not_home',
})
# ---

View File

@ -67,7 +67,6 @@
'media_title': '', 'media_title': '',
'source': 'Spotify', 'source': 'Spotify',
'supported_features': <MediaPlayerEntityFeature: 16437>, 'supported_features': <MediaPlayerEntityFeature: 16437>,
'volume_level': 0.25806775026025003,
}), }),
'context': <ANY>, 'context': <ANY>,
'entity_id': 'media_player.test_media_player', 'entity_id': 'media_player.test_media_player',

View File

@ -408,178 +408,3 @@
'state': 'off', 'state': 'off',
}) })
# --- # ---
# name: test_select[select.test_seat_heater_third_row_left-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': dict({
'options': list([
'off',
'low',
'medium',
'high',
]),
}),
'config_entry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'select',
'entity_category': None,
'entity_id': 'select.test_seat_heater_third_row_left',
'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': 'Seat heater third row left',
'platform': 'teslemetry',
'previous_unique_id': None,
'supported_features': 0,
'translation_key': 'climate_state_seat_heater_third_row_left',
'unique_id': 'LRW3F7EK4NC700000-climate_state_seat_heater_third_row_left',
'unit_of_measurement': None,
})
# ---
# name: test_select[select.test_seat_heater_third_row_left-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'friendly_name': 'Test Seat heater third row left',
'options': list([
'off',
'low',
'medium',
'high',
]),
}),
'context': <ANY>,
'entity_id': 'select.test_seat_heater_third_row_left',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'unavailable',
})
# ---
# name: test_select[select.test_seat_heater_third_row_right-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': dict({
'options': list([
'off',
'low',
'medium',
'high',
]),
}),
'config_entry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'select',
'entity_category': None,
'entity_id': 'select.test_seat_heater_third_row_right',
'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': 'Seat heater third row right',
'platform': 'teslemetry',
'previous_unique_id': None,
'supported_features': 0,
'translation_key': 'climate_state_seat_heater_third_row_right',
'unique_id': 'LRW3F7EK4NC700000-climate_state_seat_heater_third_row_right',
'unit_of_measurement': None,
})
# ---
# name: test_select[select.test_seat_heater_third_row_right-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'friendly_name': 'Test Seat heater third row right',
'options': list([
'off',
'low',
'medium',
'high',
]),
}),
'context': <ANY>,
'entity_id': 'select.test_seat_heater_third_row_right',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'unavailable',
})
# ---
# name: test_select[select.test_steering_wheel_heater-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': dict({
'options': list([
'off',
'low',
'high',
]),
}),
'config_entry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'select',
'entity_category': None,
'entity_id': 'select.test_steering_wheel_heater',
'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': 'Steering wheel heater',
'platform': 'teslemetry',
'previous_unique_id': None,
'supported_features': 0,
'translation_key': 'climate_state_steering_wheel_heat_level',
'unique_id': 'LRW3F7EK4NC700000-climate_state_steering_wheel_heat_level',
'unit_of_measurement': None,
})
# ---
# name: test_select[select.test_steering_wheel_heater-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'friendly_name': 'Test Steering wheel heater',
'options': list([
'off',
'low',
'high',
]),
}),
'context': <ANY>,
'entity_id': 'select.test_steering_wheel_heater',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'off',
})
# ---

View File

@ -5,10 +5,9 @@ from unittest.mock import AsyncMock
from freezegun.api import FrozenDateTimeFactory from freezegun.api import FrozenDateTimeFactory
import pytest import pytest
from syrupy.assertion import SnapshotAssertion from syrupy.assertion import SnapshotAssertion
from tesla_fleet_api.exceptions import VehicleOffline
from homeassistant.components.teslemetry.coordinator import VEHICLE_INTERVAL from homeassistant.components.teslemetry.coordinator import VEHICLE_INTERVAL
from homeassistant.const import STATE_UNKNOWN, Platform from homeassistant.const import Platform
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from homeassistant.helpers import entity_registry as er from homeassistant.helpers import entity_registry as er
@ -49,15 +48,3 @@ async def test_binary_sensor_refresh(
await hass.async_block_till_done() await hass.async_block_till_done()
assert_entities_alt(hass, entry.entry_id, entity_registry, snapshot) assert_entities_alt(hass, entry.entry_id, entity_registry, snapshot)
async def test_binary_sensor_offline(
hass: HomeAssistant,
mock_vehicle_data: AsyncMock,
) -> None:
"""Tests that the binary sensor entities are correct when offline."""
mock_vehicle_data.side_effect = VehicleOffline
await setup_platform(hass, [Platform.BINARY_SENSOR])
state = hass.states.get("binary_sensor.test_status")
assert state.state == STATE_UNKNOWN

View File

@ -5,7 +5,7 @@ from unittest.mock import AsyncMock, patch
from freezegun.api import FrozenDateTimeFactory from freezegun.api import FrozenDateTimeFactory
import pytest import pytest
from syrupy.assertion import SnapshotAssertion from syrupy.assertion import SnapshotAssertion
from tesla_fleet_api.exceptions import InvalidCommand, VehicleOffline from tesla_fleet_api.exceptions import InvalidCommand
from homeassistant.components.climate import ( from homeassistant.components.climate import (
ATTR_HVAC_MODE, ATTR_HVAC_MODE,
@ -19,7 +19,6 @@ from homeassistant.components.climate import (
SERVICE_TURN_ON, SERVICE_TURN_ON,
HVACMode, HVACMode,
) )
from homeassistant.components.teslemetry.coordinator import VEHICLE_INTERVAL
from homeassistant.const import ATTR_ENTITY_ID, Platform from homeassistant.const import ATTR_ENTITY_ID, Platform
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from homeassistant.exceptions import HomeAssistantError, ServiceValidationError from homeassistant.exceptions import HomeAssistantError, ServiceValidationError
@ -31,12 +30,11 @@ from .const import (
COMMAND_IGNORED_REASON, COMMAND_IGNORED_REASON,
METADATA_NOSCOPE, METADATA_NOSCOPE,
VEHICLE_DATA_ALT, VEHICLE_DATA_ALT,
VEHICLE_DATA_ASLEEP,
WAKE_UP_ASLEEP, WAKE_UP_ASLEEP,
WAKE_UP_ONLINE, WAKE_UP_ONLINE,
) )
from tests.common import async_fire_time_changed
@pytest.mark.usefixtures("entity_registry_enabled_by_default") @pytest.mark.usefixtures("entity_registry_enabled_by_default")
async def test_climate( async def test_climate(
@ -205,20 +203,6 @@ async def test_climate_alt(
assert_entities(hass, entry.entry_id, entity_registry, snapshot) assert_entities(hass, entry.entry_id, entity_registry, snapshot)
@pytest.mark.usefixtures("entity_registry_enabled_by_default")
async def test_climate_offline(
hass: HomeAssistant,
snapshot: SnapshotAssertion,
entity_registry: er.EntityRegistry,
mock_vehicle_data: AsyncMock,
) -> None:
"""Tests that the climate entity is correct."""
mock_vehicle_data.side_effect = VehicleOffline
entry = await setup_platform(hass, [Platform.CLIMATE])
assert_entities(hass, entry.entry_id, entity_registry, snapshot)
async def test_invalid_error(hass: HomeAssistant, snapshot: SnapshotAssertion) -> None: async def test_invalid_error(hass: HomeAssistant, snapshot: SnapshotAssertion) -> None:
"""Tests service error is handled.""" """Tests service error is handled."""
@ -296,18 +280,9 @@ async def test_asleep_or_offline(
) -> None: ) -> None:
"""Tests asleep is handled.""" """Tests asleep is handled."""
mock_vehicle_data.return_value = VEHICLE_DATA_ASLEEP
await setup_platform(hass, [Platform.CLIMATE]) await setup_platform(hass, [Platform.CLIMATE])
entity_id = "climate.test_climate" entity_id = "climate.test_climate"
mock_vehicle_data.assert_called_once()
# Put the vehicle alseep
mock_vehicle_data.reset_mock()
mock_vehicle_data.side_effect = VehicleOffline
freezer.tick(VEHICLE_INTERVAL)
async_fire_time_changed(hass)
await hass.async_block_till_done()
mock_vehicle_data.assert_called_once()
mock_wake_up.reset_mock()
# Run a command but fail trying to wake up the vehicle # Run a command but fail trying to wake up the vehicle
mock_wake_up.side_effect = InvalidCommand mock_wake_up.side_effect = InvalidCommand

View File

@ -4,7 +4,6 @@ from unittest.mock import AsyncMock, patch
import pytest import pytest
from syrupy.assertion import SnapshotAssertion from syrupy.assertion import SnapshotAssertion
from tesla_fleet_api.exceptions import VehicleOffline
from homeassistant.components.cover import ( from homeassistant.components.cover import (
DOMAIN as COVER_DOMAIN, DOMAIN as COVER_DOMAIN,
@ -13,7 +12,7 @@ from homeassistant.components.cover import (
SERVICE_STOP_COVER, SERVICE_STOP_COVER,
CoverState, CoverState,
) )
from homeassistant.const import ATTR_ENTITY_ID, STATE_UNKNOWN, Platform from homeassistant.const import ATTR_ENTITY_ID, Platform
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from homeassistant.helpers import entity_registry as er from homeassistant.helpers import entity_registry as er
@ -61,18 +60,6 @@ async def test_cover_noscope(
assert_entities(hass, entry.entry_id, entity_registry, snapshot) assert_entities(hass, entry.entry_id, entity_registry, snapshot)
async def test_cover_offline(
hass: HomeAssistant,
mock_vehicle_data: AsyncMock,
) -> None:
"""Tests that the cover entities are correct when offline."""
mock_vehicle_data.side_effect = VehicleOffline
await setup_platform(hass, [Platform.COVER])
state = hass.states.get("cover.test_windows")
assert state.state == STATE_UNKNOWN
@pytest.mark.usefixtures("entity_registry_enabled_by_default") @pytest.mark.usefixtures("entity_registry_enabled_by_default")
async def test_cover_services( async def test_cover_services(
hass: HomeAssistant, hass: HomeAssistant,

View File

@ -1,13 +1,15 @@
"""Test the Teslemetry device tracker platform.""" """Test the Teslemetry device tracker platform."""
from syrupy.assertion import SnapshotAssertion from unittest.mock import AsyncMock
from tesla_fleet_api.exceptions import VehicleOffline
from homeassistant.const import STATE_UNKNOWN, Platform from syrupy.assertion import SnapshotAssertion
from homeassistant.const import Platform
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from homeassistant.helpers import entity_registry as er from homeassistant.helpers import entity_registry as er
from . import assert_entities, setup_platform from . import assert_entities, assert_entities_alt, setup_platform
from .const import VEHICLE_DATA_ALT
async def test_device_tracker( async def test_device_tracker(
@ -21,13 +23,14 @@ async def test_device_tracker(
assert_entities(hass, entry.entry_id, entity_registry, snapshot) assert_entities(hass, entry.entry_id, entity_registry, snapshot)
async def test_device_tracker_offline( async def test_device_tracker_alt(
hass: HomeAssistant, hass: HomeAssistant,
mock_vehicle_data, snapshot: SnapshotAssertion,
entity_registry: er.EntityRegistry,
mock_vehicle_data: AsyncMock,
) -> None: ) -> None:
"""Tests that the device tracker entities are correct when offline.""" """Tests that the device tracker entities are correct."""
mock_vehicle_data.side_effect = VehicleOffline mock_vehicle_data.return_value = VEHICLE_DATA_ALT
await setup_platform(hass, [Platform.DEVICE_TRACKER]) entry = await setup_platform(hass, [Platform.DEVICE_TRACKER])
state = hass.states.get("device_tracker.test_location") assert_entities_alt(hass, entry.entry_id, entity_registry, snapshot)
assert state.state == STATE_UNKNOWN

View File

@ -20,7 +20,7 @@ from homeassistant.core import HomeAssistant
from homeassistant.helpers import device_registry as dr from homeassistant.helpers import device_registry as dr
from . import setup_platform from . import setup_platform
from .const import VEHICLE_DATA_ALT, WAKE_UP_ASLEEP from .const import VEHICLE_DATA_ALT
from tests.common import async_fire_time_changed from tests.common import async_fire_time_changed
@ -69,22 +69,6 @@ async def test_devices(
assert device == snapshot(name=f"{device.identifiers}") assert device == snapshot(name=f"{device.identifiers}")
# Vehicle Coordinator
async def test_vehicle_refresh_asleep(
hass: HomeAssistant,
mock_vehicle: AsyncMock,
mock_vehicle_data: AsyncMock,
freezer: FrozenDateTimeFactory,
) -> None:
"""Test coordinator refresh with an error."""
mock_vehicle.return_value = WAKE_UP_ASLEEP
entry = await setup_platform(hass, [Platform.CLIMATE])
assert entry.state is ConfigEntryState.LOADED
mock_vehicle.assert_called_once()
mock_vehicle_data.assert_not_called()
async def test_vehicle_refresh_offline( async def test_vehicle_refresh_offline(
hass: HomeAssistant, mock_vehicle_data: AsyncMock, freezer: FrozenDateTimeFactory hass: HomeAssistant, mock_vehicle_data: AsyncMock, freezer: FrozenDateTimeFactory
) -> None: ) -> None:

View File

@ -1,10 +1,9 @@
"""Test the Teslemetry lock platform.""" """Test the Teslemetry lock platform."""
from unittest.mock import AsyncMock, patch from unittest.mock import patch
import pytest import pytest
from syrupy.assertion import SnapshotAssertion from syrupy.assertion import SnapshotAssertion
from tesla_fleet_api.exceptions import VehicleOffline
from homeassistant.components.lock import ( from homeassistant.components.lock import (
DOMAIN as LOCK_DOMAIN, DOMAIN as LOCK_DOMAIN,
@ -12,7 +11,7 @@ from homeassistant.components.lock import (
SERVICE_UNLOCK, SERVICE_UNLOCK,
LockState, LockState,
) )
from homeassistant.const import ATTR_ENTITY_ID, STATE_UNKNOWN, Platform from homeassistant.const import ATTR_ENTITY_ID, Platform
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from homeassistant.exceptions import ServiceValidationError from homeassistant.exceptions import ServiceValidationError
from homeassistant.helpers import entity_registry as er from homeassistant.helpers import entity_registry as er
@ -32,18 +31,6 @@ async def test_lock(
assert_entities(hass, entry.entry_id, entity_registry, snapshot) assert_entities(hass, entry.entry_id, entity_registry, snapshot)
async def test_lock_offline(
hass: HomeAssistant,
mock_vehicle_data: AsyncMock,
) -> None:
"""Tests that the lock entities are correct when offline."""
mock_vehicle_data.side_effect = VehicleOffline
await setup_platform(hass, [Platform.LOCK])
state = hass.states.get("lock.test_lock")
assert state.state == STATE_UNKNOWN
async def test_lock_services( async def test_lock_services(
hass: HomeAssistant, hass: HomeAssistant,
) -> None: ) -> None:

View File

@ -3,7 +3,6 @@
from unittest.mock import AsyncMock, patch from unittest.mock import AsyncMock, patch
from syrupy.assertion import SnapshotAssertion from syrupy.assertion import SnapshotAssertion
from tesla_fleet_api.exceptions import VehicleOffline
from homeassistant.components.media_player import ( from homeassistant.components.media_player import (
ATTR_MEDIA_VOLUME_LEVEL, ATTR_MEDIA_VOLUME_LEVEL,
@ -47,18 +46,6 @@ async def test_media_player_alt(
assert_entities_alt(hass, entry.entry_id, entity_registry, snapshot) assert_entities_alt(hass, entry.entry_id, entity_registry, snapshot)
async def test_media_player_offline(
hass: HomeAssistant,
mock_vehicle_data: AsyncMock,
) -> None:
"""Tests that the media player entities are correct when offline."""
mock_vehicle_data.side_effect = VehicleOffline
await setup_platform(hass, [Platform.MEDIA_PLAYER])
state = hass.states.get("media_player.test_media_player")
assert state.state == MediaPlayerState.OFF
async def test_media_player_noscope( async def test_media_player_noscope(
hass: HomeAssistant, hass: HomeAssistant,
snapshot: SnapshotAssertion, snapshot: SnapshotAssertion,

View File

@ -4,14 +4,13 @@ from unittest.mock import AsyncMock, patch
import pytest import pytest
from syrupy.assertion import SnapshotAssertion from syrupy.assertion import SnapshotAssertion
from tesla_fleet_api.exceptions import VehicleOffline
from homeassistant.components.number import ( from homeassistant.components.number import (
ATTR_VALUE, ATTR_VALUE,
DOMAIN as NUMBER_DOMAIN, DOMAIN as NUMBER_DOMAIN,
SERVICE_SET_VALUE, SERVICE_SET_VALUE,
) )
from homeassistant.const import ATTR_ENTITY_ID, STATE_UNKNOWN, Platform from homeassistant.const import ATTR_ENTITY_ID, Platform
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from homeassistant.helpers import entity_registry as er from homeassistant.helpers import entity_registry as er
@ -31,18 +30,6 @@ async def test_number(
assert_entities(hass, entry.entry_id, entity_registry, snapshot) assert_entities(hass, entry.entry_id, entity_registry, snapshot)
async def test_number_offline(
hass: HomeAssistant,
mock_vehicle_data: AsyncMock,
) -> None:
"""Tests that the number entities are correct when offline."""
mock_vehicle_data.side_effect = VehicleOffline
await setup_platform(hass, [Platform.NUMBER])
state = hass.states.get("number.test_charge_current")
assert state.state == STATE_UNKNOWN
@pytest.mark.usefixtures("entity_registry_enabled_by_default") @pytest.mark.usefixtures("entity_registry_enabled_by_default")
async def test_number_services( async def test_number_services(
hass: HomeAssistant, mock_vehicle_data: AsyncMock hass: HomeAssistant, mock_vehicle_data: AsyncMock

View File

@ -5,7 +5,6 @@ from unittest.mock import AsyncMock, patch
import pytest import pytest
from syrupy.assertion import SnapshotAssertion from syrupy.assertion import SnapshotAssertion
from tesla_fleet_api.const import EnergyExportMode, EnergyOperationMode from tesla_fleet_api.const import EnergyExportMode, EnergyOperationMode
from tesla_fleet_api.exceptions import VehicleOffline
from homeassistant.components.select import ( from homeassistant.components.select import (
ATTR_OPTION, ATTR_OPTION,
@ -33,18 +32,6 @@ async def test_select(
assert_entities(hass, entry.entry_id, entity_registry, snapshot) assert_entities(hass, entry.entry_id, entity_registry, snapshot)
async def test_select_offline(
hass: HomeAssistant,
mock_vehicle_data: AsyncMock,
) -> None:
"""Tests that the select entities are correct when offline."""
mock_vehicle_data.side_effect = VehicleOffline
await setup_platform(hass, [Platform.SELECT])
state = hass.states.get("select.test_seat_heater_front_left")
assert state.state == STATE_UNKNOWN
async def test_select_services(hass: HomeAssistant, mock_vehicle_data) -> None: async def test_select_services(hass: HomeAssistant, mock_vehicle_data) -> None:
"""Tests that the select services work.""" """Tests that the select services work."""
mock_vehicle_data.return_value = VEHICLE_DATA_ALT mock_vehicle_data.return_value = VEHICLE_DATA_ALT
@ -112,3 +99,23 @@ async def test_select_services(hass: HomeAssistant, mock_vehicle_data) -> None:
state = hass.states.get(entity_id) state = hass.states.get(entity_id)
assert state.state == EnergyExportMode.BATTERY_OK.value assert state.state == EnergyExportMode.BATTERY_OK.value
call.assert_called_once() call.assert_called_once()
async def test_select_invalid_data(
hass: HomeAssistant,
snapshot: SnapshotAssertion,
entity_registry: er.EntityRegistry,
mock_vehicle_data: AsyncMock,
) -> None:
"""Tests that the select entities handle invalid data."""
broken_data = VEHICLE_DATA_ALT.copy()
broken_data["response"]["climate_state"]["seat_heater_left"] = "green"
broken_data["response"]["climate_state"]["steering_wheel_heat_level"] = "yellow"
mock_vehicle_data.return_value = broken_data
await setup_platform(hass, [Platform.SELECT])
state = hass.states.get("select.test_seat_heater_front_left")
assert state.state == STATE_UNKNOWN
state = hass.states.get("select.test_steering_wheel_heater")
assert state.state == STATE_UNKNOWN

View File

@ -4,20 +4,13 @@ from unittest.mock import AsyncMock, patch
import pytest import pytest
from syrupy.assertion import SnapshotAssertion from syrupy.assertion import SnapshotAssertion
from tesla_fleet_api.exceptions import VehicleOffline
from homeassistant.components.switch import ( from homeassistant.components.switch import (
DOMAIN as SWITCH_DOMAIN, DOMAIN as SWITCH_DOMAIN,
SERVICE_TURN_OFF, SERVICE_TURN_OFF,
SERVICE_TURN_ON, SERVICE_TURN_ON,
) )
from homeassistant.const import ( from homeassistant.const import ATTR_ENTITY_ID, STATE_OFF, STATE_ON, Platform
ATTR_ENTITY_ID,
STATE_OFF,
STATE_ON,
STATE_UNKNOWN,
Platform,
)
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from homeassistant.helpers import entity_registry as er from homeassistant.helpers import entity_registry as er
@ -49,18 +42,6 @@ async def test_switch_alt(
assert_entities_alt(hass, entry.entry_id, entity_registry, snapshot) assert_entities_alt(hass, entry.entry_id, entity_registry, snapshot)
async def test_switch_offline(
hass: HomeAssistant,
mock_vehicle_data: AsyncMock,
) -> None:
"""Tests that the switch entities are correct when offline."""
mock_vehicle_data.side_effect = VehicleOffline
await setup_platform(hass, [Platform.SWITCH])
state = hass.states.get("switch.test_auto_seat_climate_left")
assert state.state == STATE_UNKNOWN
@pytest.mark.usefixtures("entity_registry_enabled_by_default") @pytest.mark.usefixtures("entity_registry_enabled_by_default")
@pytest.mark.parametrize( @pytest.mark.parametrize(
("name", "on", "off"), ("name", "on", "off"),

View File

@ -5,12 +5,11 @@ from unittest.mock import AsyncMock, patch
from freezegun.api import FrozenDateTimeFactory from freezegun.api import FrozenDateTimeFactory
from syrupy.assertion import SnapshotAssertion from syrupy.assertion import SnapshotAssertion
from tesla_fleet_api.exceptions import VehicleOffline
from homeassistant.components.teslemetry.coordinator import VEHICLE_INTERVAL from homeassistant.components.teslemetry.coordinator import VEHICLE_INTERVAL
from homeassistant.components.teslemetry.update import INSTALLING from homeassistant.components.teslemetry.update import INSTALLING
from homeassistant.components.update import DOMAIN as UPDATE_DOMAIN, SERVICE_INSTALL from homeassistant.components.update import DOMAIN as UPDATE_DOMAIN, SERVICE_INSTALL
from homeassistant.const import ATTR_ENTITY_ID, STATE_UNKNOWN, Platform from homeassistant.const import ATTR_ENTITY_ID, Platform
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from homeassistant.helpers import entity_registry as er from homeassistant.helpers import entity_registry as er
@ -44,18 +43,6 @@ async def test_update_alt(
assert_entities(hass, entry.entry_id, entity_registry, snapshot) assert_entities(hass, entry.entry_id, entity_registry, snapshot)
async def test_update_offline(
hass: HomeAssistant,
mock_vehicle_data: AsyncMock,
) -> None:
"""Tests that the update entities are correct when offline."""
mock_vehicle_data.side_effect = VehicleOffline
await setup_platform(hass, [Platform.UPDATE])
state = hass.states.get("update.test_update")
assert state.state == STATE_UNKNOWN
async def test_update_services( async def test_update_services(
hass: HomeAssistant, hass: HomeAssistant,
mock_vehicle_data: AsyncMock, mock_vehicle_data: AsyncMock,