Add streaming binary sensors to Teslemetry (#135248)

Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com>
This commit is contained in:
Brett Adams 2025-01-14 22:46:10 +10:00 committed by GitHub
parent edc7c0ff2f
commit 6a032baa48
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 1876 additions and 69 deletions

View File

@ -4,17 +4,20 @@ from __future__ import annotations
from collections.abc import Callable from collections.abc import Callable
from dataclasses import dataclass from dataclasses import dataclass
from itertools import chain
from typing import cast from typing import cast
from teslemetry_stream import Signal
from teslemetry_stream.const import WindowState
from homeassistant.components.binary_sensor import ( from homeassistant.components.binary_sensor import (
BinarySensorDeviceClass, BinarySensorDeviceClass,
BinarySensorEntity, BinarySensorEntity,
BinarySensorEntityDescription, BinarySensorEntityDescription,
) )
from homeassistant.const import EntityCategory from homeassistant.const import STATE_ON, EntityCategory
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.restore_state import RestoreEntity
from homeassistant.helpers.typing import StateType from homeassistant.helpers.typing import StateType
from . import TeslemetryConfigEntry from . import TeslemetryConfigEntry
@ -23,6 +26,7 @@ from .entity import (
TeslemetryEnergyInfoEntity, TeslemetryEnergyInfoEntity,
TeslemetryEnergyLiveEntity, TeslemetryEnergyLiveEntity,
TeslemetryVehicleEntity, TeslemetryVehicleEntity,
TeslemetryVehicleStreamEntity,
) )
from .models import TeslemetryEnergyData, TeslemetryVehicleData from .models import TeslemetryEnergyData, TeslemetryVehicleData
@ -33,133 +37,327 @@ PARALLEL_UPDATES = 0
class TeslemetryBinarySensorEntityDescription(BinarySensorEntityDescription): class TeslemetryBinarySensorEntityDescription(BinarySensorEntityDescription):
"""Describes Teslemetry binary sensor entity.""" """Describes Teslemetry binary sensor entity."""
is_on: Callable[[StateType], bool] = bool polling_value_fn: Callable[[StateType], bool | None] = bool
polling: bool = False
streaming_key: Signal | None = None
streaming_firmware: str = "2024.26"
streaming_value_fn: Callable[[StateType], bool | None] = (
lambda x: x is True or x == "true"
)
VEHICLE_DESCRIPTIONS: tuple[TeslemetryBinarySensorEntityDescription, ...] = ( VEHICLE_DESCRIPTIONS: tuple[TeslemetryBinarySensorEntityDescription, ...] = (
TeslemetryBinarySensorEntityDescription( TeslemetryBinarySensorEntityDescription(
key="state", key="state",
polling=True,
polling_value_fn=lambda x: x == TeslemetryState.ONLINE,
device_class=BinarySensorDeviceClass.CONNECTIVITY, device_class=BinarySensorDeviceClass.CONNECTIVITY,
is_on=lambda x: x == TeslemetryState.ONLINE,
), ),
TeslemetryBinarySensorEntityDescription( TeslemetryBinarySensorEntityDescription(
key="charge_state_battery_heater_on", key="charge_state_battery_heater_on",
polling=True,
streaming_key=Signal.BATTERY_HEATER_ON,
device_class=BinarySensorDeviceClass.HEAT, device_class=BinarySensorDeviceClass.HEAT,
entity_category=EntityCategory.DIAGNOSTIC, entity_category=EntityCategory.DIAGNOSTIC,
entity_registry_enabled_default=False, entity_registry_enabled_default=False,
), ),
TeslemetryBinarySensorEntityDescription( TeslemetryBinarySensorEntityDescription(
key="charge_state_charger_phases", key="charge_state_charger_phases",
is_on=lambda x: cast(int, x) > 1, polling=True,
streaming_key=Signal.CHARGER_PHASES,
polling_value_fn=lambda x: cast(int, x) > 1,
streaming_value_fn=lambda x: cast(int, x) > 1,
entity_registry_enabled_default=False, entity_registry_enabled_default=False,
), ),
TeslemetryBinarySensorEntityDescription( TeslemetryBinarySensorEntityDescription(
key="charge_state_preconditioning_enabled", key="charge_state_preconditioning_enabled",
polling=True,
streaming_key=Signal.PRECONDITIONING_ENABLED,
entity_category=EntityCategory.DIAGNOSTIC, entity_category=EntityCategory.DIAGNOSTIC,
entity_registry_enabled_default=False, entity_registry_enabled_default=False,
), ),
TeslemetryBinarySensorEntityDescription( TeslemetryBinarySensorEntityDescription(
key="climate_state_is_preconditioning", key="climate_state_is_preconditioning",
polling=True,
entity_category=EntityCategory.DIAGNOSTIC, entity_category=EntityCategory.DIAGNOSTIC,
entity_registry_enabled_default=False, entity_registry_enabled_default=False,
), ),
TeslemetryBinarySensorEntityDescription( TeslemetryBinarySensorEntityDescription(
key="charge_state_scheduled_charging_pending", key="charge_state_scheduled_charging_pending",
polling=True,
streaming_key=Signal.SCHEDULED_CHARGING_PENDING,
entity_category=EntityCategory.DIAGNOSTIC, entity_category=EntityCategory.DIAGNOSTIC,
entity_registry_enabled_default=False, entity_registry_enabled_default=False,
), ),
TeslemetryBinarySensorEntityDescription( TeslemetryBinarySensorEntityDescription(
key="charge_state_trip_charging", key="charge_state_trip_charging",
polling=True,
entity_category=EntityCategory.DIAGNOSTIC, entity_category=EntityCategory.DIAGNOSTIC,
entity_registry_enabled_default=False, entity_registry_enabled_default=False,
), ),
TeslemetryBinarySensorEntityDescription( TeslemetryBinarySensorEntityDescription(
key="charge_state_conn_charge_cable", key="charge_state_conn_charge_cable",
is_on=lambda x: x != "<invalid>", polling=True,
polling_value_fn=lambda x: x != "<invalid>",
entity_category=EntityCategory.DIAGNOSTIC, entity_category=EntityCategory.DIAGNOSTIC,
device_class=BinarySensorDeviceClass.CONNECTIVITY, device_class=BinarySensorDeviceClass.CONNECTIVITY,
), ),
TeslemetryBinarySensorEntityDescription( TeslemetryBinarySensorEntityDescription(
key="climate_state_cabin_overheat_protection_actively_cooling", key="climate_state_cabin_overheat_protection_actively_cooling",
polling=True,
device_class=BinarySensorDeviceClass.HEAT, device_class=BinarySensorDeviceClass.HEAT,
entity_category=EntityCategory.DIAGNOSTIC, entity_category=EntityCategory.DIAGNOSTIC,
entity_registry_enabled_default=False, entity_registry_enabled_default=False,
), ),
TeslemetryBinarySensorEntityDescription( TeslemetryBinarySensorEntityDescription(
key="vehicle_state_dashcam_state", key="vehicle_state_dashcam_state",
polling=True,
device_class=BinarySensorDeviceClass.RUNNING, device_class=BinarySensorDeviceClass.RUNNING,
is_on=lambda x: x == "Recording", polling_value_fn=lambda x: x == "Recording",
entity_category=EntityCategory.DIAGNOSTIC, entity_category=EntityCategory.DIAGNOSTIC,
entity_registry_enabled_default=False, entity_registry_enabled_default=False,
), ),
TeslemetryBinarySensorEntityDescription( TeslemetryBinarySensorEntityDescription(
key="vehicle_state_is_user_present", key="vehicle_state_is_user_present",
polling=True,
device_class=BinarySensorDeviceClass.PRESENCE, device_class=BinarySensorDeviceClass.PRESENCE,
), ),
TeslemetryBinarySensorEntityDescription( TeslemetryBinarySensorEntityDescription(
key="vehicle_state_tpms_soft_warning_fl", key="vehicle_state_tpms_soft_warning_fl",
polling=True,
device_class=BinarySensorDeviceClass.PROBLEM, device_class=BinarySensorDeviceClass.PROBLEM,
entity_category=EntityCategory.DIAGNOSTIC, entity_category=EntityCategory.DIAGNOSTIC,
entity_registry_enabled_default=False, entity_registry_enabled_default=False,
), ),
TeslemetryBinarySensorEntityDescription( TeslemetryBinarySensorEntityDescription(
key="vehicle_state_tpms_soft_warning_fr", key="vehicle_state_tpms_soft_warning_fr",
polling=True,
device_class=BinarySensorDeviceClass.PROBLEM, device_class=BinarySensorDeviceClass.PROBLEM,
entity_category=EntityCategory.DIAGNOSTIC, entity_category=EntityCategory.DIAGNOSTIC,
entity_registry_enabled_default=False, entity_registry_enabled_default=False,
), ),
TeslemetryBinarySensorEntityDescription( TeslemetryBinarySensorEntityDescription(
key="vehicle_state_tpms_soft_warning_rl", key="vehicle_state_tpms_soft_warning_rl",
polling=True,
device_class=BinarySensorDeviceClass.PROBLEM, device_class=BinarySensorDeviceClass.PROBLEM,
entity_category=EntityCategory.DIAGNOSTIC, entity_category=EntityCategory.DIAGNOSTIC,
entity_registry_enabled_default=False, entity_registry_enabled_default=False,
), ),
TeslemetryBinarySensorEntityDescription( TeslemetryBinarySensorEntityDescription(
key="vehicle_state_tpms_soft_warning_rr", key="vehicle_state_tpms_soft_warning_rr",
polling=True,
device_class=BinarySensorDeviceClass.PROBLEM, device_class=BinarySensorDeviceClass.PROBLEM,
entity_category=EntityCategory.DIAGNOSTIC, entity_category=EntityCategory.DIAGNOSTIC,
entity_registry_enabled_default=False, entity_registry_enabled_default=False,
), ),
TeslemetryBinarySensorEntityDescription( TeslemetryBinarySensorEntityDescription(
key="vehicle_state_fd_window", key="vehicle_state_fd_window",
polling=True,
streaming_key=Signal.FD_WINDOW,
streaming_value_fn=lambda x: WindowState.get(x) != "Closed",
device_class=BinarySensorDeviceClass.WINDOW, device_class=BinarySensorDeviceClass.WINDOW,
entity_category=EntityCategory.DIAGNOSTIC, entity_category=EntityCategory.DIAGNOSTIC,
), ),
TeslemetryBinarySensorEntityDescription( TeslemetryBinarySensorEntityDescription(
key="vehicle_state_fp_window", key="vehicle_state_fp_window",
polling=True,
streaming_key=Signal.FP_WINDOW,
streaming_value_fn=lambda x: WindowState.get(x) != "Closed",
device_class=BinarySensorDeviceClass.WINDOW, device_class=BinarySensorDeviceClass.WINDOW,
entity_category=EntityCategory.DIAGNOSTIC, entity_category=EntityCategory.DIAGNOSTIC,
), ),
TeslemetryBinarySensorEntityDescription( TeslemetryBinarySensorEntityDescription(
key="vehicle_state_rd_window", key="vehicle_state_rd_window",
polling=True,
streaming_key=Signal.RD_WINDOW,
streaming_value_fn=lambda x: WindowState.get(x) != "Closed",
device_class=BinarySensorDeviceClass.WINDOW, device_class=BinarySensorDeviceClass.WINDOW,
entity_category=EntityCategory.DIAGNOSTIC, entity_category=EntityCategory.DIAGNOSTIC,
), ),
TeslemetryBinarySensorEntityDescription( TeslemetryBinarySensorEntityDescription(
key="vehicle_state_rp_window", key="vehicle_state_rp_window",
polling=True,
streaming_key=Signal.RP_WINDOW,
streaming_value_fn=lambda x: WindowState.get(x) != "Closed",
device_class=BinarySensorDeviceClass.WINDOW, device_class=BinarySensorDeviceClass.WINDOW,
entity_category=EntityCategory.DIAGNOSTIC, entity_category=EntityCategory.DIAGNOSTIC,
), ),
TeslemetryBinarySensorEntityDescription( TeslemetryBinarySensorEntityDescription(
key="vehicle_state_df", key="vehicle_state_df",
polling=True,
device_class=BinarySensorDeviceClass.DOOR, device_class=BinarySensorDeviceClass.DOOR,
streaming_key=Signal.DOOR_STATE,
streaming_value_fn=lambda x: cast(dict, x).get("DriverFront"),
entity_category=EntityCategory.DIAGNOSTIC, entity_category=EntityCategory.DIAGNOSTIC,
), ),
TeslemetryBinarySensorEntityDescription( TeslemetryBinarySensorEntityDescription(
key="vehicle_state_dr", key="vehicle_state_dr",
polling=True,
device_class=BinarySensorDeviceClass.DOOR, device_class=BinarySensorDeviceClass.DOOR,
streaming_key=Signal.DOOR_STATE,
streaming_value_fn=lambda x: cast(dict, x).get("DriverRear"),
entity_category=EntityCategory.DIAGNOSTIC, entity_category=EntityCategory.DIAGNOSTIC,
), ),
TeslemetryBinarySensorEntityDescription( TeslemetryBinarySensorEntityDescription(
key="vehicle_state_pf", key="vehicle_state_pf",
polling=True,
device_class=BinarySensorDeviceClass.DOOR, device_class=BinarySensorDeviceClass.DOOR,
streaming_key=Signal.DOOR_STATE,
streaming_value_fn=lambda x: cast(dict, x).get("PassengerFront"),
entity_category=EntityCategory.DIAGNOSTIC, entity_category=EntityCategory.DIAGNOSTIC,
), ),
TeslemetryBinarySensorEntityDescription( TeslemetryBinarySensorEntityDescription(
key="vehicle_state_pr", key="vehicle_state_pr",
polling=True,
device_class=BinarySensorDeviceClass.DOOR, device_class=BinarySensorDeviceClass.DOOR,
streaming_key=Signal.DOOR_STATE,
streaming_value_fn=lambda x: cast(dict, x).get("PassengerRear"),
entity_category=EntityCategory.DIAGNOSTIC, entity_category=EntityCategory.DIAGNOSTIC,
), ),
TeslemetryBinarySensorEntityDescription(
key="automatic_blind_spot_camera",
streaming_key=Signal.AUTOMATIC_BLIND_SPOT_CAMERA,
entity_registry_enabled_default=False,
),
TeslemetryBinarySensorEntityDescription(
key="automatic_emergency_braking_off",
streaming_key=Signal.AUTOMATIC_EMERGENCY_BRAKING_OFF,
entity_registry_enabled_default=False,
),
TeslemetryBinarySensorEntityDescription(
key="blind_spot_collision_warning_chime",
streaming_key=Signal.BLIND_SPOT_COLLISION_WARNING_CHIME,
entity_registry_enabled_default=False,
),
TeslemetryBinarySensorEntityDescription(
key="bms_full_charge_complete",
streaming_key=Signal.BMS_FULL_CHARGE_COMPLETE,
entity_registry_enabled_default=False,
),
TeslemetryBinarySensorEntityDescription(
key="brake_pedal",
streaming_key=Signal.BRAKE_PEDAL,
entity_registry_enabled_default=False,
),
TeslemetryBinarySensorEntityDescription(
key="charge_port_cold_weather_mode",
streaming_key=Signal.CHARGE_PORT_COLD_WEATHER_MODE,
entity_registry_enabled_default=False,
),
TeslemetryBinarySensorEntityDescription(
key="service_mode",
streaming_key=Signal.SERVICE_MODE,
entity_registry_enabled_default=False,
),
TeslemetryBinarySensorEntityDescription(
key="pin_to_drive_enabled",
streaming_key=Signal.PIN_TO_DRIVE_ENABLED,
entity_registry_enabled_default=False,
),
TeslemetryBinarySensorEntityDescription(
key="drive_rail",
streaming_key=Signal.DRIVE_RAIL,
entity_registry_enabled_default=False,
),
TeslemetryBinarySensorEntityDescription(
key="driver_seat_belt",
streaming_key=Signal.DRIVER_SEAT_BELT,
entity_registry_enabled_default=False,
),
TeslemetryBinarySensorEntityDescription(
key="driver_seat_occupied",
streaming_key=Signal.DRIVER_SEAT_OCCUPIED,
entity_registry_enabled_default=False,
),
TeslemetryBinarySensorEntityDescription(
key="passenger_seat_belt",
streaming_key=Signal.PASSENGER_SEAT_BELT,
entity_registry_enabled_default=False,
),
TeslemetryBinarySensorEntityDescription(
key="fast_charger_present",
streaming_key=Signal.FAST_CHARGER_PRESENT,
entity_registry_enabled_default=False,
),
TeslemetryBinarySensorEntityDescription(
key="gps_state",
streaming_key=Signal.GPS_STATE,
entity_registry_enabled_default=False,
entity_category=EntityCategory.DIAGNOSTIC,
device_class=BinarySensorDeviceClass.CONNECTIVITY,
),
TeslemetryBinarySensorEntityDescription(
key="guest_mode_enabled",
streaming_key=Signal.GUEST_MODE_ENABLED,
entity_registry_enabled_default=False,
),
TeslemetryBinarySensorEntityDescription(
key="dc_dc_enable",
streaming_key=Signal.DC_DC_ENABLE,
entity_registry_enabled_default=False,
),
TeslemetryBinarySensorEntityDescription(
key="emergency_lane_departure_avoidance",
streaming_key=Signal.EMERGENCY_LANE_DEPARTURE_AVOIDANCE,
entity_registry_enabled_default=False,
),
TeslemetryBinarySensorEntityDescription(
key="supercharger_session_trip_planner",
streaming_key=Signal.SUPERCHARGER_SESSION_TRIP_PLANNER,
entity_registry_enabled_default=False,
),
TeslemetryBinarySensorEntityDescription(
key="wiper_heat_enabled",
streaming_key=Signal.WIPER_HEAT_ENABLED,
streaming_firmware="2024.44.25",
entity_registry_enabled_default=False,
),
TeslemetryBinarySensorEntityDescription(
key="rear_display_hvac_enabled",
streaming_key=Signal.REAR_DISPLAY_HVAC_ENABLED,
streaming_firmware="2024.44.25",
entity_registry_enabled_default=False,
),
TeslemetryBinarySensorEntityDescription(
key="offroad_lightbar_present",
streaming_key=Signal.OFFROAD_LIGHTBAR_PRESENT,
streaming_firmware="2024.44.25",
entity_registry_enabled_default=False,
),
TeslemetryBinarySensorEntityDescription(
key="homelink_nearby",
streaming_key=Signal.HOMELINK_NEARBY,
streaming_firmware="2024.44.25",
entity_registry_enabled_default=False,
),
TeslemetryBinarySensorEntityDescription(
key="europe_vehicle",
streaming_key=Signal.EUROPE_VEHICLE,
streaming_firmware="2024.44.25",
entity_registry_enabled_default=False,
),
TeslemetryBinarySensorEntityDescription(
key="right_hand_drive",
streaming_key=Signal.RIGHT_HAND_DRIVE,
streaming_firmware="2024.44.25",
entity_registry_enabled_default=False,
),
TeslemetryBinarySensorEntityDescription(
key="located_at_home",
streaming_key=Signal.LOCATED_AT_HOME,
streaming_firmware="2024.44.32",
),
TeslemetryBinarySensorEntityDescription(
key="located_at_work",
streaming_key=Signal.LOCATED_AT_WORK,
streaming_firmware="2024.44.32",
),
TeslemetryBinarySensorEntityDescription(
key="located_at_favorite",
streaming_key=Signal.LOCATED_AT_FAVORITE,
streaming_firmware="2024.44.32",
entity_registry_enabled_default=False,
),
) )
ENERGY_LIVE_DESCRIPTIONS: tuple[BinarySensorEntityDescription, ...] = ( ENERGY_LIVE_DESCRIPTIONS: tuple[BinarySensorEntityDescription, ...] = (
@ -183,31 +381,42 @@ async def async_setup_entry(
) -> None: ) -> None:
"""Set up the Teslemetry binary sensor platform from a config entry.""" """Set up the Teslemetry binary sensor platform from a config entry."""
async_add_entities( entities: list[BinarySensorEntity] = []
chain( for vehicle in entry.runtime_data.vehicles:
( # Vehicles for description in VEHICLE_DESCRIPTIONS:
TeslemetryVehicleBinarySensorEntity(vehicle, description) if (
for vehicle in entry.runtime_data.vehicles not vehicle.api.pre2021
for description in VEHICLE_DESCRIPTIONS and description.streaming_key
), and vehicle.firmware >= description.streaming_firmware
( # Energy Site Live ):
TeslemetryEnergyLiveBinarySensorEntity(energysite, description) entities.append(
for energysite in entry.runtime_data.energysites TeslemetryVehicleStreamingBinarySensorEntity(vehicle, description)
if energysite.live_coordinator )
for description in ENERGY_LIVE_DESCRIPTIONS elif description.polling:
if energysite.info_coordinator.data.get("components_battery") entities.append(
), TeslemetryVehiclePollingBinarySensorEntity(vehicle, description)
( # Energy Site Info )
TeslemetryEnergyInfoBinarySensorEntity(energysite, description)
for energysite in entry.runtime_data.energysites entities.extend(
for description in ENERGY_INFO_DESCRIPTIONS TeslemetryEnergyLiveBinarySensorEntity(energysite, description)
if energysite.info_coordinator.data.get("components_battery") for energysite in entry.runtime_data.energysites
), if energysite.live_coordinator
) for description in ENERGY_LIVE_DESCRIPTIONS
if description.key in energysite.live_coordinator.data
)
entities.extend(
TeslemetryEnergyInfoBinarySensorEntity(energysite, description)
for energysite in entry.runtime_data.energysites
for description in ENERGY_INFO_DESCRIPTIONS
if description.key in energysite.info_coordinator.data
) )
async_add_entities(entities)
class TeslemetryVehicleBinarySensorEntity(TeslemetryVehicleEntity, BinarySensorEntity):
class TeslemetryVehiclePollingBinarySensorEntity(
TeslemetryVehicleEntity, BinarySensorEntity
):
"""Base class for Teslemetry vehicle binary sensors.""" """Base class for Teslemetry vehicle binary sensors."""
entity_description: TeslemetryBinarySensorEntityDescription entity_description: TeslemetryBinarySensorEntityDescription
@ -224,12 +433,40 @@ 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._value is None: self._attr_available = self._value is not None
self._attr_available = False if self._attr_available:
self._attr_is_on = None assert self._value is not None
else: self._attr_is_on = self.entity_description.polling_value_fn(self._value)
self._attr_available = True
self._attr_is_on = self.entity_description.is_on(self._value)
class TeslemetryVehicleStreamingBinarySensorEntity(
TeslemetryVehicleStreamEntity, BinarySensorEntity, RestoreEntity
):
"""Base class for Teslemetry vehicle streaming sensors."""
entity_description: TeslemetryBinarySensorEntityDescription
def __init__(
self,
data: TeslemetryVehicleData,
description: TeslemetryBinarySensorEntityDescription,
) -> None:
"""Initialize the sensor."""
self.entity_description = description
assert description.streaming_key
super().__init__(data, description.key, description.streaming_key)
async def async_added_to_hass(self) -> None:
"""Handle entity which will be added."""
await super().async_added_to_hass()
if (state := await self.async_get_last_state()) is not None:
self._attr_is_on = state.state == STATE_ON
def _async_value_from_stream(self, value) -> None:
"""Update the value of the entity."""
self._attr_available = value is not None
if self._attr_available:
self._attr_is_on = self.entity_description.streaming_value_fn(value)
class TeslemetryEnergyLiveBinarySensorEntity( class TeslemetryEnergyLiveBinarySensorEntity(

View File

@ -51,7 +51,7 @@
"name": "Trip charging" "name": "Trip charging"
}, },
"climate_state_cabin_overheat_protection_actively_cooling": { "climate_state_cabin_overheat_protection_actively_cooling": {
"name": "Cabin overheat protection actively cooling" "name": "Cabin overheat protection active"
}, },
"climate_state_is_preconditioning": { "climate_state_is_preconditioning": {
"name": "Preconditioning" "name": "Preconditioning"
@ -68,6 +68,27 @@
"storm_mode_active": { "storm_mode_active": {
"name": "Storm watch active" "name": "Storm watch active"
}, },
"automatic_blind_spot_camera": {
"name": "Automatic blind spot camera"
},
"automatic_emergency_braking_off": {
"name": "Automatic emergency braking off"
},
"blind_spot_collision_warning_chime": {
"name": "Blind spot collision warning chime"
},
"bms_full_charge_complete": {
"name": "BMS full charge"
},
"brake_pedal": {
"name": "Brake pedal"
},
"charge_port_cold_weather_mode": {
"name": "Charge port cold weather mode"
},
"service_mode": {
"name": "Service mode"
},
"vehicle_state_dashcam_state": { "vehicle_state_dashcam_state": {
"name": "Dashcam" "name": "Dashcam"
}, },
@ -109,6 +130,66 @@
}, },
"vehicle_state_tpms_soft_warning_rr": { "vehicle_state_tpms_soft_warning_rr": {
"name": "Tire pressure warning rear right" "name": "Tire pressure warning rear right"
},
"pin_to_drive_enabled": {
"name": "Pin to drive enabled"
},
"drive_rail": {
"name": "Drive rail"
},
"driver_seat_belt": {
"name": "Driver seat belt"
},
"driver_seat_occupied": {
"name": "Driver seat occupied"
},
"passenger_seat_belt": {
"name": "Passenger seat belt"
},
"fast_charger_present": {
"name": "Fast charger present"
},
"gps_state": {
"name": "GPS state"
},
"guest_mode_enabled": {
"name": "Guest mode enabled"
},
"dc_dc_enable": {
"name": "DC to DC converter"
},
"emergency_lane_departure_avoidance": {
"name": "Emergency lane departure avoidance"
},
"supercharger_session_trip_planner": {
"name": "Supercharger session trip planner"
},
"wiper_heat_enabled": {
"name": "Wiper heat"
},
"rear_display_hvac_enabled": {
"name": "Rear display HVAC"
},
"offroad_lightbar_present": {
"name": "Offroad lightbar"
},
"homelink_nearby": {
"name": "Homelink nearby"
},
"europe_vehicle": {
"name": "European vehicle"
},
"right_hand_drive": {
"name": "Right hand drive"
},
"located_at_home": {
"name": "Located at home"
},
"located_at_work": {
"name": "Located at work"
},
"located_at_favorite": {
"name": "Located at favorite"
} }
}, },
"button": { "button": {

File diff suppressed because it is too large Load Diff

View File

@ -5,6 +5,7 @@ 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 teslemetry_stream import Signal
from homeassistant.components.teslemetry.coordinator import VEHICLE_INTERVAL from homeassistant.components.teslemetry.coordinator import VEHICLE_INTERVAL
from homeassistant.const import Platform from homeassistant.const import Platform
@ -48,3 +49,58 @@ 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)
@pytest.mark.usefixtures("entity_registry_enabled_by_default")
async def test_binary_sensors_streaming(
hass: HomeAssistant,
snapshot: SnapshotAssertion,
entity_registry: er.EntityRegistry,
freezer: FrozenDateTimeFactory,
mock_vehicle_data: AsyncMock,
mock_add_listener: AsyncMock,
) -> None:
"""Tests that the binary sensor entities with streaming are correct."""
freezer.move_to("2024-01-01 00:00:00+00:00")
entry = await setup_platform(hass, [Platform.BINARY_SENSOR])
# Stream update
mock_add_listener.send(
{
"vin": VEHICLE_DATA_ALT["response"]["vin"],
"data": {
Signal.FD_WINDOW: "WindowStateOpened",
Signal.FP_WINDOW: "INVALID_VALUE",
Signal.DOOR_STATE: {
"DoorState": {
"DriverFront": True,
"DriverRear": False,
"PassengerFront": False,
"PassengerRear": False,
"TrunkFront": False,
"TrunkRear": False,
}
},
Signal.DRIVER_SEAT_BELT: None,
},
"createdAt": "2024-10-04T10:45:17.537Z",
}
)
await hass.async_block_till_done()
# Reload the entry
await hass.config_entries.async_reload(entry.entry_id)
await hass.async_block_till_done()
# Assert the entities restored their values
for entity_id in (
"binary_sensor.test_front_driver_window",
"binary_sensor.test_front_passenger_window",
"binary_sensor.test_front_driver_door",
"binary_sensor.test_front_passenger_door",
"binary_sensor.test_driver_seat_belt",
):
state = hass.states.get(entity_id)
assert state.state == snapshot(name=f"{entity_id}-state")