Update vehicle type handling in Teslemetry (#148862)

This commit is contained in:
Brett Adams 2025-07-16 18:05:17 +10:00 committed by GitHub
parent bafd342d5d
commit 84e3dac406
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
24 changed files with 105 additions and 2390 deletions

View File

@ -133,7 +133,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: TeslemetryConfigEntry) -
)
firmware = vehicle_metadata[vin].get("firmware", "Unknown")
stream_vehicle = stream.get_vehicle(vin)
poll = product["command_signing"] == "off"
poll = vehicle_metadata[vin].get("polling", False)
vehicles.append(
TeslemetryVehicleData(

View File

@ -542,7 +542,7 @@ async def async_setup_entry(
for vehicle in entry.runtime_data.vehicles:
for description in VEHICLE_DESCRIPTIONS:
if (
not vehicle.api.pre2021
not vehicle.poll
and description.streaming_listener
and vehicle.firmware >= description.streaming_firmware
):

View File

@ -67,7 +67,7 @@ async def async_setup_entry(
TeslemetryVehiclePollingClimateEntity(
vehicle, TeslemetryClimateSide.DRIVER, entry.runtime_data.scopes
)
if vehicle.api.pre2021 or vehicle.firmware < "2024.44.25"
if vehicle.poll or vehicle.firmware < "2024.44.25"
else TeslemetryStreamingClimateEntity(
vehicle, TeslemetryClimateSide.DRIVER, entry.runtime_data.scopes
)
@ -77,7 +77,7 @@ async def async_setup_entry(
TeslemetryVehiclePollingCabinOverheatProtectionEntity(
vehicle, entry.runtime_data.scopes
)
if vehicle.api.pre2021 or vehicle.firmware < "2024.44.25"
if vehicle.poll or vehicle.firmware < "2024.44.25"
else TeslemetryStreamingCabinOverheatProtectionEntity(
vehicle, entry.runtime_data.scopes
)

View File

@ -45,7 +45,7 @@ async def async_setup_entry(
chain(
(
TeslemetryVehiclePollingWindowEntity(vehicle, entry.runtime_data.scopes)
if vehicle.api.pre2021 or vehicle.firmware < "2024.26"
if vehicle.poll or vehicle.firmware < "2024.26"
else TeslemetryStreamingWindowEntity(vehicle, entry.runtime_data.scopes)
for vehicle in entry.runtime_data.vehicles
),
@ -53,7 +53,7 @@ async def async_setup_entry(
TeslemetryVehiclePollingChargePortEntity(
vehicle, entry.runtime_data.scopes
)
if vehicle.api.pre2021 or vehicle.firmware < "2024.44.25"
if vehicle.poll or vehicle.firmware < "2024.44.25"
else TeslemetryStreamingChargePortEntity(
vehicle, entry.runtime_data.scopes
)
@ -63,7 +63,7 @@ async def async_setup_entry(
TeslemetryVehiclePollingFrontTrunkEntity(
vehicle, entry.runtime_data.scopes
)
if vehicle.api.pre2021 or vehicle.firmware < "2024.26"
if vehicle.poll or vehicle.firmware < "2024.26"
else TeslemetryStreamingFrontTrunkEntity(
vehicle, entry.runtime_data.scopes
)
@ -73,7 +73,7 @@ async def async_setup_entry(
TeslemetryVehiclePollingRearTrunkEntity(
vehicle, entry.runtime_data.scopes
)
if vehicle.api.pre2021 or vehicle.firmware < "2024.26"
if vehicle.poll or vehicle.firmware < "2024.26"
else TeslemetryStreamingRearTrunkEntity(
vehicle, entry.runtime_data.scopes
)
@ -82,7 +82,8 @@ async def async_setup_entry(
(
TeslemetrySunroofEntity(vehicle, entry.runtime_data.scopes)
for vehicle in entry.runtime_data.vehicles
if vehicle.coordinator.data.get("vehicle_config_sun_roof_installed")
if vehicle.poll
and vehicle.coordinator.data.get("vehicle_config_sun_roof_installed")
),
)
)

View File

@ -89,7 +89,7 @@ async def async_setup_entry(
for vehicle in entry.runtime_data.vehicles:
for description in DESCRIPTIONS:
if vehicle.api.pre2021 or vehicle.firmware < description.streaming_firmware:
if vehicle.poll or vehicle.firmware < description.streaming_firmware:
if description.polling_prefix:
entities.append(
TeslemetryVehiclePollingDeviceTrackerEntity(

View File

@ -42,7 +42,7 @@ async def async_setup_entry(
TeslemetryVehiclePollingVehicleLockEntity(
vehicle, Scope.VEHICLE_CMDS in entry.runtime_data.scopes
)
if vehicle.api.pre2021 or vehicle.firmware < "2024.26"
if vehicle.poll or vehicle.firmware < "2024.26"
else TeslemetryStreamingVehicleLockEntity(
vehicle, Scope.VEHICLE_CMDS in entry.runtime_data.scopes
)
@ -52,7 +52,7 @@ async def async_setup_entry(
TeslemetryVehiclePollingCableLockEntity(
vehicle, Scope.VEHICLE_CMDS in entry.runtime_data.scopes
)
if vehicle.api.pre2021 or vehicle.firmware < "2024.26"
if vehicle.poll or vehicle.firmware < "2024.26"
else TeslemetryStreamingCableLockEntity(
vehicle, Scope.VEHICLE_CMDS in entry.runtime_data.scopes
)

View File

@ -53,7 +53,7 @@ async def async_setup_entry(
async_add_entities(
TeslemetryVehiclePollingMediaEntity(vehicle, entry.runtime_data.scopes)
if vehicle.api.pre2021 or vehicle.firmware < "2025.2.6"
if vehicle.poll or vehicle.firmware < "2025.2.6"
else TeslemetryStreamingMediaEntity(vehicle, entry.runtime_data.scopes)
for vehicle in entry.runtime_data.vehicles
)

View File

@ -145,7 +145,7 @@ async def async_setup_entry(
description,
entry.runtime_data.scopes,
)
if vehicle.api.pre2021 or vehicle.firmware < "2024.26"
if vehicle.poll or vehicle.firmware < "2024.26"
else TeslemetryStreamingNumberEntity(
vehicle,
description,

View File

@ -180,7 +180,7 @@ async def async_setup_entry(
TeslemetryVehiclePollingSelectEntity(
vehicle, description, entry.runtime_data.scopes
)
if vehicle.api.pre2021
if vehicle.poll
or vehicle.firmware < "2024.26"
or description.streaming_listener is None
else TeslemetryStreamingSelectEntity(

View File

@ -1565,7 +1565,7 @@ async def async_setup_entry(
for vehicle in entry.runtime_data.vehicles:
for description in VEHICLE_DESCRIPTIONS:
if (
not vehicle.api.pre2021
not vehicle.poll
and description.streaming_listener
and vehicle.firmware >= description.streaming_firmware
):
@ -1575,7 +1575,7 @@ async def async_setup_entry(
for time_description in VEHICLE_TIME_DESCRIPTIONS:
if (
not vehicle.api.pre2021
not vehicle.poll
and vehicle.firmware >= time_description.streaming_firmware
):
entities.append(

View File

@ -147,8 +147,7 @@ async def async_setup_entry(
TeslemetryVehiclePollingVehicleSwitchEntity(
vehicle, description, entry.runtime_data.scopes
)
if vehicle.api.pre2021
or vehicle.firmware < description.streaming_firmware
if vehicle.poll or vehicle.firmware < description.streaming_firmware
else TeslemetryStreamingVehicleSwitchEntity(
vehicle, description, entry.runtime_data.scopes
)

View File

@ -39,7 +39,7 @@ async def async_setup_entry(
async_add_entities(
TeslemetryVehiclePollingUpdateEntity(vehicle, entry.runtime_data.scopes)
if vehicle.api.pre2021 or vehicle.firmware < "2024.44.25"
if vehicle.poll or vehicle.firmware < "2024.44.25"
else TeslemetryStreamingUpdateEntity(vehicle, entry.runtime_data.scopes)
for vehicle in entry.runtime_data.vehicles
)

View File

@ -14,6 +14,7 @@ from .const import (
ENERGY_HISTORY,
LIVE_STATUS,
METADATA,
METADATA_LEGACY,
PRODUCTS,
SITE_INFO,
VEHICLE_DATA,
@ -53,9 +54,9 @@ def mock_vehicle_data() -> Generator[AsyncMock]:
def mock_legacy():
"""Mock Tesla Fleet Api products method."""
with patch(
"tesla_fleet_api.teslemetry.Vehicle.pre2021", return_value=True
) as mock_pre2021:
yield mock_pre2021
"tesla_fleet_api.teslemetry.Teslemetry.metadata", return_value=METADATA_LEGACY
) as mock_products:
yield mock_products
@pytest.fixture(autouse=True)

View File

@ -37,6 +37,32 @@ COMMAND_ERRORS = (COMMAND_REASON, COMMAND_NOREASON, COMMAND_ERROR, COMMAND_NOERR
RESPONSE_OK = {"response": {}, "error": None}
METADATA = {
"uid": "abc-123",
"region": "NA",
"scopes": [
"openid",
"offline_access",
"user_data",
"vehicle_device_data",
"vehicle_cmds",
"vehicle_charging_cmds",
"vehicle_location",
"energy_device_data",
"energy_cmds",
],
"vehicles": {
"LRW3F7EK4NC700000": {
"proxy": True,
"access": True,
"polling": False,
"firmware": "2026.0.0",
"discounted": False,
"fleet_telemetry": "1.0.2",
"name": "Home Assistant",
}
},
}
METADATA_LEGACY = {
"uid": "abc-123",
"region": "NA",
"scopes": [
@ -56,6 +82,9 @@ METADATA = {
"access": True,
"polling": True,
"firmware": "2026.0.0",
"discounted": True,
"fleet_telemetry": "unknown",
"name": "Home Assistant",
}
},
}
@ -68,7 +97,10 @@ METADATA_NOSCOPE = {
"proxy": False,
"access": True,
"polling": True,
"firmware": "2024.44.25",
"firmware": "2026.0.0",
"discounted": True,
"fleet_telemetry": "unknown",
"name": "Home Assistant",
}
},
}

File diff suppressed because it is too large Load Diff

View File

@ -407,9 +407,8 @@
]),
'max_temp': 40,
'min_temp': 30,
'supported_features': <ClimateEntityFeature: 385>,
'supported_features': <ClimateEntityFeature: 384>,
'target_temp_step': 5,
'temperature': None,
}),
'context': <ANY>,
'entity_id': 'climate.test_cabin_overheat_protection',

View File

@ -23,6 +23,7 @@ async def test_binary_sensor(
hass: HomeAssistant,
snapshot: SnapshotAssertion,
entity_registry: er.EntityRegistry,
mock_legacy: AsyncMock,
) -> None:
"""Tests that the binary sensor entities are correct."""
@ -37,6 +38,7 @@ async def test_binary_sensor_refresh(
entity_registry: er.EntityRegistry,
mock_vehicle_data: AsyncMock,
freezer: FrozenDateTimeFactory,
mock_legacy: AsyncMock,
) -> None:
"""Tests that the binary sensor entities are correct."""

View File

@ -273,7 +273,6 @@ async def test_climate_noscope(
snapshot: SnapshotAssertion,
entity_registry: er.EntityRegistry,
mock_metadata: AsyncMock,
mock_legacy: AsyncMock,
) -> None:
"""Tests that the climate entity is correct."""
mock_metadata.return_value = METADATA_NOSCOPE

View File

@ -55,7 +55,6 @@ async def test_cover_noscope(
snapshot: SnapshotAssertion,
entity_registry: er.EntityRegistry,
mock_metadata: AsyncMock,
mock_legacy: AsyncMock,
) -> None:
"""Tests that the cover entities are correct without scopes."""
@ -67,6 +66,7 @@ async def test_cover_noscope(
@pytest.mark.usefixtures("entity_registry_enabled_by_default")
async def test_cover_services(
hass: HomeAssistant,
mock_legacy: AsyncMock,
) -> None:
"""Tests that the cover entities are correct."""

View File

@ -49,7 +49,6 @@ async def test_device_tracker_noscope(
entity_registry: er.EntityRegistry,
mock_metadata: AsyncMock,
mock_vehicle_data: AsyncMock,
mock_legacy: AsyncMock,
) -> None:
"""Tests that the device tracker entities are correct."""

View File

@ -1,5 +1,7 @@
"""Test the Telemetry Diagnostics."""
from unittest.mock import AsyncMock
from freezegun.api import FrozenDateTimeFactory
from syrupy.assertion import SnapshotAssertion
@ -18,6 +20,7 @@ async def test_diagnostics(
hass_client: ClientSessionGenerator,
snapshot: SnapshotAssertion,
freezer: FrozenDateTimeFactory,
mock_legacy: AsyncMock,
) -> None:
"""Test diagnostics."""

View File

@ -14,7 +14,13 @@ from tesla_fleet_api.exceptions import (
from homeassistant.components.teslemetry.coordinator import VEHICLE_INTERVAL
from homeassistant.components.teslemetry.models import TeslemetryData
from homeassistant.config_entries import ConfigEntryState
from homeassistant.const import STATE_OFF, STATE_ON, STATE_UNKNOWN, Platform
from homeassistant.const import (
STATE_OFF,
STATE_ON,
STATE_UNAVAILABLE,
STATE_UNKNOWN,
Platform,
)
from homeassistant.core import HomeAssistant
from homeassistant.helpers import device_registry as dr
@ -72,6 +78,7 @@ async def test_vehicle_refresh_error(
mock_vehicle_data: AsyncMock,
side_effect: TeslaFleetError,
state: ConfigEntryState,
mock_legacy: AsyncMock,
) -> None:
"""Test coordinator refresh with an error."""
mock_vehicle_data.side_effect = side_effect
@ -107,6 +114,7 @@ async def test_energy_site_refresh_error(
assert entry.state is state
@pytest.mark.usefixtures("entity_registry_enabled_by_default")
async def test_vehicle_stream(
hass: HomeAssistant,
mock_add_listener: AsyncMock,
@ -121,7 +129,7 @@ async def test_vehicle_stream(
assert state.state == STATE_UNKNOWN
state = hass.states.get("binary_sensor.test_user_present")
assert state.state == STATE_OFF
assert state.state == STATE_UNAVAILABLE
mock_add_listener.send(
{

View File

@ -55,7 +55,6 @@ async def test_media_player_noscope(
snapshot: SnapshotAssertion,
entity_registry: er.EntityRegistry,
mock_metadata: AsyncMock,
mock_legacy: AsyncMock,
) -> None:
"""Tests that the media player entities are correct without required scope."""

View File

@ -1,6 +1,6 @@
"""Test the Teslemetry sensor platform."""
from unittest.mock import AsyncMock, patch
from unittest.mock import AsyncMock
from freezegun.api import FrozenDateTimeFactory
import pytest
@ -26,6 +26,7 @@ async def test_sensors(
entity_registry: er.EntityRegistry,
freezer: FrozenDateTimeFactory,
mock_vehicle_data: AsyncMock,
mock_legacy: AsyncMock,
) -> None:
"""Tests that the sensor entities with the legacy polling are correct."""
@ -33,9 +34,7 @@ async def test_sensors(
async_fire_time_changed(hass)
await hass.async_block_till_done()
# Force the vehicle to use polling
with patch("tesla_fleet_api.teslemetry.Vehicle.pre2021", return_value=True):
entry = await setup_platform(hass, [Platform.SENSOR])
entry = await setup_platform(hass, [Platform.SENSOR])
assert_entities(hass, entry.entry_id, entity_registry, snapshot)