Require the location scope for Teslemetry location sensors (#183467)

This commit is contained in:
Brett Adams
2026-09-30 11:28:31 +02:00
committed by GitHub
parent 62ba1ca817
commit 120e94cf0e
4 changed files with 80 additions and 1 deletions
@@ -5,6 +5,7 @@ from dataclasses import dataclass
from typing import cast, override
from tesla_fleet_api import firmware_at_least
from tesla_fleet_api.const import Scope
from teslemetry_stream.vehicle import TeslemetryStreamVehicle
from homeassistant.components.binary_sensor import (
@@ -52,6 +53,7 @@ class TeslemetryBinarySensorEntityDescription(BinarySensorEntityDescription):
| None
) = None
streaming_firmware: str = "2024.26"
requires_location_scope: bool = False
VEHICLE_DESCRIPTIONS: tuple[TeslemetryBinarySensorEntityDescription, ...] = (
@@ -351,6 +353,7 @@ VEHICLE_DESCRIPTIONS: tuple[TeslemetryBinarySensorEntityDescription, ...] = (
),
TeslemetryBinarySensorEntityDescription(
key="gps_state",
requires_location_scope=True,
streaming_listener=lambda vehicle, callback: vehicle.listen_GpsState(callback),
entity_registry_enabled_default=False,
entity_category=EntityCategory.DIAGNOSTIC,
@@ -556,9 +559,12 @@ async def async_setup_entry(
) -> None:
"""Set up the Teslemetry binary sensor platform from a config entry."""
location_scope = Scope.VEHICLE_LOCATION in entry.runtime_data.scopes
entities: list[BinarySensorEntity] = []
for vehicle in entry.runtime_data.vehicles:
for description in VEHICLE_DESCRIPTIONS:
if description.requires_location_scope and not location_scope:
continue
if (
not vehicle.poll
and description.streaming_listener
@@ -6,6 +6,7 @@ from datetime import datetime, timedelta
from typing import override
from tesla_fleet_api import firmware_at_least
from tesla_fleet_api.const import Scope
from teslemetry_stream import TeslemetryStream, TeslemetryStreamVehicle
from teslemetry_stream.const import CreditsEvent
@@ -215,6 +216,7 @@ class TeslemetryVehicleSensorEntityDescription(SensorEntityDescription):
) = None
streaming_firmware: str = "2024.26"
requires_hw4: bool = False
requires_location_scope: bool = False
VEHICLE_DESCRIPTIONS: tuple[TeslemetryVehicleSensorEntityDescription, ...] = (
@@ -548,6 +550,7 @@ VEHICLE_DESCRIPTIONS: tuple[TeslemetryVehicleSensorEntityDescription, ...] = (
),
TeslemetryVehicleSensorEntityDescription(
key="drive_state_active_route_destination",
requires_location_scope=True,
polling=True,
streaming_listener=lambda vehicle, callback: vehicle.listen_DestinationName(
callback
@@ -1097,6 +1100,7 @@ VEHICLE_DESCRIPTIONS: tuple[TeslemetryVehicleSensorEntityDescription, ...] = (
),
TeslemetryVehicleSensorEntityDescription(
key="gps_heading",
requires_location_scope=True,
streaming_listener=lambda vehicle, callback: vehicle.listen_GpsHeading(
callback
),
@@ -1674,9 +1678,12 @@ async def async_setup_entry(
) -> None:
"""Set up the Teslemetry sensor platform from a config entry."""
location_scope = Scope.VEHICLE_LOCATION in entry.runtime_data.scopes
entities: list[SensorEntity] = []
for vehicle in entry.runtime_data.vehicles:
for description in VEHICLE_DESCRIPTIONS:
if description.requires_location_scope and not location_scope:
continue
if (
not vehicle.poll
and description.streaming_listener
@@ -7,13 +7,14 @@ import pytest
from syrupy.assertion import SnapshotAssertion
from teslemetry_stream import Signal
from homeassistant.components.teslemetry.const import DOMAIN
from homeassistant.components.teslemetry.coordinator import VEHICLE_INTERVAL
from homeassistant.const import STATE_UNAVAILABLE, STATE_UNKNOWN, Platform
from homeassistant.core import HomeAssistant, State
from homeassistant.helpers import entity_registry as er
from . import assert_entities, assert_entities_alt, setup_platform
from .const import VEHICLE_DATA_ALT
from .const import METADATA, VEHICLE_DATA_ALT
from tests.common import async_fire_time_changed, mock_restore_cache
@@ -162,3 +163,35 @@ async def test_binary_sensors_connectivity(
# Assert the entities have correct state with concrete assertions
assert hass.states.get("binary_sensor.test_cellular").state == "on"
assert hass.states.get("binary_sensor.test_wi_fi").state == "off"
@pytest.mark.parametrize(
("scopes", "expected"),
[
pytest.param(METADATA["scopes"], True, id="location_scope"),
pytest.param(
[scope for scope in METADATA["scopes"] if scope != "vehicle_location"],
False,
id="no_location_scope",
),
],
)
async def test_gps_state_requires_location_scope(
hass: HomeAssistant,
entity_registry: er.EntityRegistry,
mock_metadata: AsyncMock,
scopes: list[str],
expected: bool,
) -> None:
"""Test the GPS state binary sensor is only created with the location scope."""
mock_metadata.return_value = {**METADATA, "scopes": scopes}
await setup_platform(hass, [Platform.BINARY_SENSOR])
assert (
entity_registry.async_get_entity_id(
Platform.BINARY_SENSOR, DOMAIN, "LRW3F7EK4NC700000-gps_state"
)
is not None
) is expected
@@ -745,3 +745,36 @@ async def test_energy_history_unavailable_while_stream_disconnected(
mock_energy_totals_stream.send(is_cache=True)
await hass.async_block_till_done()
assert hass.states.get(ENERGY_HISTORY_ENTITY).state == "0.036"
@pytest.mark.parametrize(
("scopes", "expected"),
[
pytest.param(METADATA["scopes"], True, id="location_scope"),
pytest.param(
[scope for scope in METADATA["scopes"] if scope != "vehicle_location"],
False,
id="no_location_scope",
),
],
)
async def test_location_sensors_require_location_scope(
hass: HomeAssistant,
entity_registry: er.EntityRegistry,
mock_metadata: AsyncMock,
scopes: list[str],
expected: bool,
) -> None:
"""Test location sensors are only created with the vehicle location scope."""
mock_metadata.return_value = {**METADATA, "scopes": scopes}
await setup_platform(hass, [Platform.SENSOR])
for key in ("gps_heading", "drive_state_active_route_destination"):
assert (
entity_registry.async_get_entity_id(
Platform.SENSOR, DOMAIN, f"{VEHICLE_VIN}-{key}"
)
is not None
) is expected