From 8003a49571f39be25d345e350f4a07a128ad75b7 Mon Sep 17 00:00:00 2001 From: Brett Adams Date: Fri, 12 Sep 2025 04:44:38 -0400 Subject: [PATCH] Add guest mode switch to Teslemetry (#151550) --- .../components/teslemetry/icons.json | 3 + .../components/teslemetry/strings.json | 3 + homeassistant/components/teslemetry/switch.py | 77 ++++++++++++------- 3 files changed, 56 insertions(+), 27 deletions(-) diff --git a/homeassistant/components/teslemetry/icons.json b/homeassistant/components/teslemetry/icons.json index f50f5a75f70c..46b63fc2c733 100644 --- a/homeassistant/components/teslemetry/icons.json +++ b/homeassistant/components/teslemetry/icons.json @@ -752,6 +752,9 @@ }, "vehicle_state_valet_mode": { "default": "mdi:speedometer-slow" + }, + "guest_mode_enabled": { + "default": "mdi:account-group" } } }, diff --git a/homeassistant/components/teslemetry/strings.json b/homeassistant/components/teslemetry/strings.json index 510e2b45a02c..b78f2d00f60f 100644 --- a/homeassistant/components/teslemetry/strings.json +++ b/homeassistant/components/teslemetry/strings.json @@ -1084,6 +1084,9 @@ }, "vehicle_state_valet_mode": { "name": "Valet mode" + }, + "guest_mode_enabled": { + "name": "Guest mode" } }, "update": { diff --git a/homeassistant/components/teslemetry/switch.py b/homeassistant/components/teslemetry/switch.py index aae973cf315b..c0ad058ee2c4 100644 --- a/homeassistant/components/teslemetry/switch.py +++ b/homeassistant/components/teslemetry/switch.py @@ -4,7 +4,6 @@ from __future__ import annotations from collections.abc import Awaitable, Callable from dataclasses import dataclass -from itertools import chain from typing import Any from tesla_fleet_api.const import AutoSeat, Scope @@ -38,6 +37,7 @@ PARALLEL_UPDATES = 0 class TeslemetrySwitchEntityDescription(SwitchEntityDescription): """Describes Teslemetry Switch entity.""" + polling: bool = False on_func: Callable[[Vehicle], Awaitable[dict[str, Any]]] off_func: Callable[[Vehicle], Awaitable[dict[str, Any]]] scopes: list[Scope] @@ -53,6 +53,7 @@ class TeslemetrySwitchEntityDescription(SwitchEntityDescription): VEHICLE_DESCRIPTIONS: tuple[TeslemetrySwitchEntityDescription, ...] = ( TeslemetrySwitchEntityDescription( key="vehicle_state_sentry_mode", + polling=True, streaming_listener=lambda vehicle, callback: vehicle.listen_SentryMode( lambda value: callback(None if value is None else value != "Off") ), @@ -62,6 +63,7 @@ VEHICLE_DESCRIPTIONS: tuple[TeslemetrySwitchEntityDescription, ...] = ( ), TeslemetrySwitchEntityDescription( key="vehicle_state_valet_mode", + polling=True, streaming_listener=lambda vehicle, value: vehicle.listen_ValetModeEnabled( value ), @@ -72,6 +74,7 @@ VEHICLE_DESCRIPTIONS: tuple[TeslemetrySwitchEntityDescription, ...] = ( ), TeslemetrySwitchEntityDescription( key="climate_state_auto_seat_climate_left", + polling=True, streaming_listener=lambda vehicle, callback: vehicle.listen_AutoSeatClimateLeft( callback ), @@ -85,6 +88,7 @@ VEHICLE_DESCRIPTIONS: tuple[TeslemetrySwitchEntityDescription, ...] = ( ), TeslemetrySwitchEntityDescription( key="climate_state_auto_seat_climate_right", + polling=True, streaming_listener=lambda vehicle, callback: vehicle.listen_AutoSeatClimateRight(callback), on_func=lambda api: api.remote_auto_seat_climate_request( @@ -97,6 +101,7 @@ VEHICLE_DESCRIPTIONS: tuple[TeslemetrySwitchEntityDescription, ...] = ( ), TeslemetrySwitchEntityDescription( key="climate_state_auto_steering_wheel_heat", + polling=True, streaming_listener=lambda vehicle, callback: vehicle.listen_HvacSteeringWheelHeatAuto(callback), on_func=lambda api: api.remote_auto_steering_wheel_heat_climate_request( @@ -109,6 +114,7 @@ VEHICLE_DESCRIPTIONS: tuple[TeslemetrySwitchEntityDescription, ...] = ( ), TeslemetrySwitchEntityDescription( key="climate_state_defrost_mode", + polling=True, streaming_listener=lambda vehicle, callback: vehicle.listen_DefrostMode( lambda value: callback(None if value is None else value != "Off") ), @@ -120,6 +126,7 @@ VEHICLE_DESCRIPTIONS: tuple[TeslemetrySwitchEntityDescription, ...] = ( ), TeslemetrySwitchEntityDescription( key="charge_state_charging_state", + polling=True, unique_id="charge_state_user_charge_enable_request", value_func=lambda state: state in {"Starting", "Charging"}, streaming_listener=lambda vehicle, callback: vehicle.listen_DetailedChargeState( @@ -131,6 +138,17 @@ VEHICLE_DESCRIPTIONS: tuple[TeslemetrySwitchEntityDescription, ...] = ( off_func=lambda api: api.charge_stop(), scopes=[Scope.VEHICLE_CMDS, Scope.VEHICLE_CHARGING_CMDS], ), + TeslemetrySwitchEntityDescription( + key="guest_mode_enabled", + polling=False, + unique_id="guest_mode_enabled", + streaming_listener=lambda vehicle, callback: vehicle.listen_GuestModeEnabled( + callback + ), + on_func=lambda api: api.guest_mode(True), + off_func=lambda api: api.guest_mode(False), + scopes=[Scope.VEHICLE_CMDS], + ), ) @@ -141,35 +159,40 @@ async def async_setup_entry( ) -> None: """Set up the Teslemetry Switch platform from a config entry.""" - async_add_entities( - chain( - ( - TeslemetryVehiclePollingVehicleSwitchEntity( - vehicle, description, entry.runtime_data.scopes + entities: list[SwitchEntity] = [] + + for vehicle in entry.runtime_data.vehicles: + for description in VEHICLE_DESCRIPTIONS: + if vehicle.poll or vehicle.firmware < description.streaming_firmware: + if description.polling: + entities.append( + TeslemetryVehiclePollingVehicleSwitchEntity( + vehicle, description, entry.runtime_data.scopes + ) + ) + else: + entities.append( + TeslemetryStreamingVehicleSwitchEntity( + vehicle, description, entry.runtime_data.scopes + ) ) - if vehicle.poll or vehicle.firmware < description.streaming_firmware - else TeslemetryStreamingVehicleSwitchEntity( - vehicle, description, entry.runtime_data.scopes - ) - for vehicle in entry.runtime_data.vehicles - for description in VEHICLE_DESCRIPTIONS - ), - ( - TeslemetryChargeFromGridSwitchEntity( - energysite, - entry.runtime_data.scopes, - ) - for energysite in entry.runtime_data.energysites - if energysite.info_coordinator.data.get("components_battery") - and energysite.info_coordinator.data.get("components_solar") - ), - ( - TeslemetryStormModeSwitchEntity(energysite, entry.runtime_data.scopes) - for energysite in entry.runtime_data.energysites - if energysite.info_coordinator.data.get("components_storm_mode_capable") - ), + + entities.extend( + TeslemetryChargeFromGridSwitchEntity( + energysite, + entry.runtime_data.scopes, ) + for energysite in entry.runtime_data.energysites + if energysite.info_coordinator.data.get("components_battery") + and energysite.info_coordinator.data.get("components_solar") ) + entities.extend( + TeslemetryStormModeSwitchEntity(energysite, entry.runtime_data.scopes) + for energysite in entry.runtime_data.energysites + if energysite.info_coordinator.data.get("components_storm_mode_capable") + ) + + async_add_entities(entities) class TeslemetryVehicleSwitchEntity(TeslemetryRootEntity, SwitchEntity):