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."""
if "vehicle_data" in data:
LOGGER.debug("Streaming received vehicle data from %s", vin)
coordinator.updated_once = True
coordinator.async_set_updated_data(flatten(data["vehicle_data"]))
elif "state" in data:
LOGGER.debug("Streaming received state from %s", vin)

View File

@ -223,15 +223,12 @@ class TeslemetryVehicleBinarySensorEntity(TeslemetryVehicleEntity, BinarySensorE
def _async_update_attrs(self) -> None:
"""Update the attributes of the binary sensor."""
if self.coordinator.updated_once:
if self._value is None:
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:
if self._value is None:
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)
class TeslemetryEnergyLiveBinarySensorEntity(

View File

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

View File

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

View File

@ -82,8 +82,6 @@ class TeslemetryCableLockEntity(TeslemetryVehicleEntity, LockEntity):
def _async_update_attrs(self) -> None:
"""Update entity attributes."""
if self._value is None:
self._attr_is_locked = None
self._attr_is_locked = self._value == ENGAGED
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 vehicle in entry.runtime_data.vehicles
if description.key in vehicle.coordinator.data
),
(
TeslemetryWheelHeaterSelectEntity(vehicle, entry.runtime_data.scopes)
for vehicle in entry.runtime_data.vehicles
if vehicle.coordinator.data.get("climate_state_steering_wheel_heater")
),
(
TeslemetryOperationSelectEntity(energysite, entry.runtime_data.scopes)
@ -137,7 +139,7 @@ class TeslemetrySeatHeaterSelectEntity(TeslemetryVehicleEntity, SelectEntity):
"""Handle updated data from the coordinator."""
self._attr_available = self.entity_description.available_fn(self)
value = self._value
if value is None:
if not isinstance(value, int):
self._attr_current_option = None
else:
self._attr_current_option = self._attr_options[value]
@ -182,7 +184,7 @@ class TeslemetryWheelHeaterSelectEntity(TeslemetryVehicleEntity, SelectEntity):
"""Handle updated data from the coordinator."""
value = self._value
if value is None:
if not isinstance(value, int):
self._attr_current_option = None
else:
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 description in VEHICLE_DESCRIPTIONS
if description.key in vehicle.coordinator.data
),
(
TeslemetryChargeSwitchEntity(
@ -150,10 +151,7 @@ class TeslemetryVehicleSwitchEntity(TeslemetryVehicleEntity, TeslemetrySwitchEnt
def _async_update_attrs(self) -> None:
"""Update the attributes of the sensor."""
if self._value is None:
self._attr_is_on = None
else:
self._attr_is_on = bool(self._value)
self._attr_is_on = bool(self._value)
async def async_turn_on(self, **kwargs: Any) -> None:
"""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)
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)
LIVE_STATUS = load_json_object_fixture("live_status.json", DOMAIN)
SITE_INFO = load_json_object_fixture("site_info.json", DOMAIN)

View File

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

View File

@ -208,7 +208,7 @@
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'off',
'state': 'unknown',
})
# ---
# name: test_climate_alt[climate.test_climate-entry]
@ -365,146 +365,6 @@
'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]
'Command returned exception: The data request or command is unknown.'
# ---

View File

@ -99,3 +99,37 @@
'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': '',
'source': 'Spotify',
'supported_features': <MediaPlayerEntityFeature: 16437>,
'volume_level': 0.25806775026025003,
}),
'context': <ANY>,
'entity_id': 'media_player.test_media_player',

View File

@ -408,178 +408,3 @@
'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
import pytest
from syrupy.assertion import SnapshotAssertion
from tesla_fleet_api.exceptions import VehicleOffline
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.helpers import entity_registry as er
@ -49,15 +48,3 @@ async def test_binary_sensor_refresh(
await hass.async_block_till_done()
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
import pytest
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 (
ATTR_HVAC_MODE,
@ -19,7 +19,6 @@ from homeassistant.components.climate import (
SERVICE_TURN_ON,
HVACMode,
)
from homeassistant.components.teslemetry.coordinator import VEHICLE_INTERVAL
from homeassistant.const import ATTR_ENTITY_ID, Platform
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import HomeAssistantError, ServiceValidationError
@ -31,12 +30,11 @@ from .const import (
COMMAND_IGNORED_REASON,
METADATA_NOSCOPE,
VEHICLE_DATA_ALT,
VEHICLE_DATA_ASLEEP,
WAKE_UP_ASLEEP,
WAKE_UP_ONLINE,
)
from tests.common import async_fire_time_changed
@pytest.mark.usefixtures("entity_registry_enabled_by_default")
async def test_climate(
@ -205,20 +203,6 @@ async def test_climate_alt(
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:
"""Tests service error is handled."""
@ -296,18 +280,9 @@ async def test_asleep_or_offline(
) -> None:
"""Tests asleep is handled."""
mock_vehicle_data.return_value = VEHICLE_DATA_ASLEEP
await setup_platform(hass, [Platform.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
mock_wake_up.side_effect = InvalidCommand

View File

@ -4,7 +4,6 @@ from unittest.mock import AsyncMock, patch
import pytest
from syrupy.assertion import SnapshotAssertion
from tesla_fleet_api.exceptions import VehicleOffline
from homeassistant.components.cover import (
DOMAIN as COVER_DOMAIN,
@ -13,7 +12,7 @@ from homeassistant.components.cover import (
SERVICE_STOP_COVER,
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.helpers import entity_registry as er
@ -61,18 +60,6 @@ async def test_cover_noscope(
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")
async def test_cover_services(
hass: HomeAssistant,

View File

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

View File

@ -20,7 +20,7 @@ from homeassistant.core import HomeAssistant
from homeassistant.helpers import device_registry as dr
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
@ -69,22 +69,6 @@ async def test_devices(
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(
hass: HomeAssistant, mock_vehicle_data: AsyncMock, freezer: FrozenDateTimeFactory
) -> None:

View File

@ -1,10 +1,9 @@
"""Test the Teslemetry lock platform."""
from unittest.mock import AsyncMock, patch
from unittest.mock import patch
import pytest
from syrupy.assertion import SnapshotAssertion
from tesla_fleet_api.exceptions import VehicleOffline
from homeassistant.components.lock import (
DOMAIN as LOCK_DOMAIN,
@ -12,7 +11,7 @@ from homeassistant.components.lock import (
SERVICE_UNLOCK,
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.exceptions import ServiceValidationError
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)
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(
hass: HomeAssistant,
) -> None:

View File

@ -3,7 +3,6 @@
from unittest.mock import AsyncMock, patch
from syrupy.assertion import SnapshotAssertion
from tesla_fleet_api.exceptions import VehicleOffline
from homeassistant.components.media_player import (
ATTR_MEDIA_VOLUME_LEVEL,
@ -47,18 +46,6 @@ async def test_media_player_alt(
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(
hass: HomeAssistant,
snapshot: SnapshotAssertion,

View File

@ -4,14 +4,13 @@ from unittest.mock import AsyncMock, patch
import pytest
from syrupy.assertion import SnapshotAssertion
from tesla_fleet_api.exceptions import VehicleOffline
from homeassistant.components.number import (
ATTR_VALUE,
DOMAIN as NUMBER_DOMAIN,
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.helpers import entity_registry as er
@ -31,18 +30,6 @@ async def test_number(
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")
async def test_number_services(
hass: HomeAssistant, mock_vehicle_data: AsyncMock

View File

@ -5,7 +5,6 @@ from unittest.mock import AsyncMock, patch
import pytest
from syrupy.assertion import SnapshotAssertion
from tesla_fleet_api.const import EnergyExportMode, EnergyOperationMode
from tesla_fleet_api.exceptions import VehicleOffline
from homeassistant.components.select import (
ATTR_OPTION,
@ -33,18 +32,6 @@ async def test_select(
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:
"""Tests that the select services work."""
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)
assert state.state == EnergyExportMode.BATTERY_OK.value
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
from syrupy.assertion import SnapshotAssertion
from tesla_fleet_api.exceptions import VehicleOffline
from homeassistant.components.switch import (
DOMAIN as SWITCH_DOMAIN,
SERVICE_TURN_OFF,
SERVICE_TURN_ON,
)
from homeassistant.const import (
ATTR_ENTITY_ID,
STATE_OFF,
STATE_ON,
STATE_UNKNOWN,
Platform,
)
from homeassistant.const import ATTR_ENTITY_ID, STATE_OFF, STATE_ON, Platform
from homeassistant.core import HomeAssistant
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)
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.parametrize(
("name", "on", "off"),

View File

@ -5,12 +5,11 @@ from unittest.mock import AsyncMock, patch
from freezegun.api import FrozenDateTimeFactory
from syrupy.assertion import SnapshotAssertion
from tesla_fleet_api.exceptions import VehicleOffline
from homeassistant.components.teslemetry.coordinator import VEHICLE_INTERVAL
from homeassistant.components.teslemetry.update import INSTALLING
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.helpers import entity_registry as er
@ -44,18 +43,6 @@ async def test_update_alt(
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(
hass: HomeAssistant,
mock_vehicle_data: AsyncMock,