Correct the behavior of the Charge switch in Tessie/Teslemetry/Tesla Fleet (#136562)

This commit is contained in:
Brett Adams 2025-01-29 20:41:33 +10:00 committed by GitHub
parent aa6ffb3da5
commit ea62da553e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
12 changed files with 58 additions and 90 deletions

View File

@ -298,7 +298,7 @@
}
},
"switch": {
"charge_state_user_charge_enable_request": {
"charge_state_charging_state": {
"default": "mdi:ev-station"
},
"climate_state_auto_seat_climate_left": {

View File

@ -522,7 +522,7 @@
}
},
"switch": {
"charge_state_user_charge_enable_request": {
"charge_state_charging_state": {
"name": "Charge"
},
"climate_state_auto_seat_climate_left": {

View File

@ -16,6 +16,7 @@ from homeassistant.components.switch import (
)
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.typing import StateType
from . import TeslaFleetConfigEntry
from .entity import TeslaFleetEnergyInfoEntity, TeslaFleetVehicleEntity
@ -32,6 +33,8 @@ class TeslaFleetSwitchEntityDescription(SwitchEntityDescription):
on_func: Callable
off_func: Callable
scopes: list[Scope]
value_func: Callable[[StateType], bool] = bool
unique_id: str | None = None
VEHICLE_DESCRIPTIONS: tuple[TeslaFleetSwitchEntityDescription, ...] = (
@ -77,13 +80,14 @@ VEHICLE_DESCRIPTIONS: tuple[TeslaFleetSwitchEntityDescription, ...] = (
),
scopes=[Scope.VEHICLE_CMDS],
),
)
VEHICLE_CHARGE_DESCRIPTION = TeslaFleetSwitchEntityDescription(
key="charge_state_user_charge_enable_request",
on_func=lambda api: api.charge_start(),
off_func=lambda api: api.charge_stop(),
scopes=[Scope.VEHICLE_CHARGING_CMDS, Scope.VEHICLE_CMDS],
TeslaFleetSwitchEntityDescription(
key="charge_state_charging_state",
unique_id="charge_state_user_charge_enable_request",
on_func=lambda api: api.charge_start(),
off_func=lambda api: api.charge_stop(),
value_func=lambda state: state in {"Starting", "Charging"},
scopes=[Scope.VEHICLE_CHARGING_CMDS, Scope.VEHICLE_CMDS],
),
)
@ -103,12 +107,6 @@ async def async_setup_entry(
for vehicle in entry.runtime_data.vehicles
for description in VEHICLE_DESCRIPTIONS
),
(
TeslaFleetChargeSwitchEntity(
vehicle, VEHICLE_CHARGE_DESCRIPTION, entry.runtime_data.scopes
)
for vehicle in entry.runtime_data.vehicles
),
(
TeslaFleetChargeFromGridSwitchEntity(
energysite,
@ -144,16 +142,18 @@ class TeslaFleetVehicleSwitchEntity(TeslaFleetVehicleEntity, TeslaFleetSwitchEnt
scopes: list[Scope],
) -> None:
"""Initialize the Switch."""
super().__init__(data, description.key)
self.entity_description = description
self.scoped = any(scope in scopes for scope in description.scopes)
super().__init__(data, description.key)
if description.unique_id:
self._attr_unique_id = f"{data.vin}-{description.unique_id}"
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 = self.entity_description.value_func(self._value)
async def async_turn_on(self, **kwargs: Any) -> None:
"""Turn on the Switch."""
@ -172,17 +172,6 @@ class TeslaFleetVehicleSwitchEntity(TeslaFleetVehicleEntity, TeslaFleetSwitchEnt
self.async_write_ha_state()
class TeslaFleetChargeSwitchEntity(TeslaFleetVehicleSwitchEntity):
"""Entity class for TeslaFleet charge switch."""
def _async_update_attrs(self) -> None:
"""Update the attributes of the entity."""
if self._value is None:
self._attr_is_on = self.get("charge_state_charge_enable_request")
else:
self._attr_is_on = self._value
class TeslaFleetChargeFromGridSwitchEntity(
TeslaFleetEnergyInfoEntity, TeslaFleetSwitchEntity
):

View File

@ -291,7 +291,7 @@
}
},
"switch": {
"charge_state_user_charge_enable_request": {
"charge_state_charging_state": {
"default": "mdi:ev-station"
},
"climate_state_auto_seat_climate_left": {

View File

@ -608,7 +608,7 @@
}
},
"switch": {
"charge_state_user_charge_enable_request": {
"charge_state_charging_state": {
"name": "Charge"
},
"climate_state_auto_seat_climate_left": {

View File

@ -16,6 +16,7 @@ from homeassistant.components.switch import (
)
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.typing import StateType
from . import TeslemetryConfigEntry
from .entity import TeslemetryEnergyInfoEntity, TeslemetryVehicleEntity
@ -32,6 +33,8 @@ class TeslemetrySwitchEntityDescription(SwitchEntityDescription):
on_func: Callable
off_func: Callable
scopes: list[Scope]
value_func: Callable[[StateType], bool] = bool
unique_id: str | None = None
VEHICLE_DESCRIPTIONS: tuple[TeslemetrySwitchEntityDescription, ...] = (
@ -77,13 +80,14 @@ VEHICLE_DESCRIPTIONS: tuple[TeslemetrySwitchEntityDescription, ...] = (
),
scopes=[Scope.VEHICLE_CMDS],
),
)
VEHICLE_CHARGE_DESCRIPTION = TeslemetrySwitchEntityDescription(
key="charge_state_user_charge_enable_request",
on_func=lambda api: api.charge_start(),
off_func=lambda api: api.charge_stop(),
scopes=[Scope.VEHICLE_CMDS, Scope.VEHICLE_CHARGING_CMDS],
TeslemetrySwitchEntityDescription(
key="charge_state_charging_state",
unique_id="charge_state_user_charge_enable_request",
on_func=lambda api: api.charge_start(),
off_func=lambda api: api.charge_stop(),
value_func=lambda state: state in {"Starting", "Charging"},
scopes=[Scope.VEHICLE_CMDS, Scope.VEHICLE_CHARGING_CMDS],
),
)
@ -104,12 +108,6 @@ async def async_setup_entry(
for description in VEHICLE_DESCRIPTIONS
if description.key in vehicle.coordinator.data
),
(
TeslemetryChargeSwitchEntity(
vehicle, VEHICLE_CHARGE_DESCRIPTION, entry.runtime_data.scopes
)
for vehicle in entry.runtime_data.vehicles
),
(
TeslemetryChargeFromGridSwitchEntity(
energysite,
@ -145,13 +143,15 @@ class TeslemetryVehicleSwitchEntity(TeslemetryVehicleEntity, TeslemetrySwitchEnt
scopes: list[Scope],
) -> None:
"""Initialize the Switch."""
super().__init__(data, description.key)
self.entity_description = description
self.scoped = any(scope in scopes for scope in description.scopes)
super().__init__(data, description.key)
if description.unique_id:
self._attr_unique_id = f"{data.vin}-{description.unique_id}"
def _async_update_attrs(self) -> None:
"""Update the attributes of the sensor."""
self._attr_is_on = bool(self._value)
self._attr_is_on = self.entity_description.value_func(self._value)
async def async_turn_on(self, **kwargs: Any) -> None:
"""Turn on the Switch."""
@ -170,17 +170,6 @@ class TeslemetryVehicleSwitchEntity(TeslemetryVehicleEntity, TeslemetrySwitchEnt
self.async_write_ha_state()
class TeslemetryChargeSwitchEntity(TeslemetryVehicleSwitchEntity):
"""Entity class for Teslemetry charge switch."""
def _async_update_attrs(self) -> None:
"""Update the attributes of the entity."""
if self._value is None:
self._attr_is_on = self.get("charge_state_charge_enable_request")
else:
self._attr_is_on = self._value
class TeslemetryChargeFromGridSwitchEntity(
TeslemetryEnergyInfoEntity, TeslemetrySwitchEntity
):

View File

@ -459,7 +459,7 @@
}
},
"switch": {
"charge_state_charge_enable_request": {
"charge_state_charging_state": {
"name": "Charge"
},
"climate_state_defrost_mode": {

View File

@ -27,6 +27,7 @@ from homeassistant.components.switch import (
)
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.typing import StateType
from . import TessieConfigEntry
from .entity import TessieEnergyEntity, TessieEntity
@ -40,6 +41,8 @@ class TessieSwitchEntityDescription(SwitchEntityDescription):
on_func: Callable
off_func: Callable
value_func: Callable[[StateType], bool] = bool
unique_id: str | None = None
DESCRIPTIONS: tuple[TessieSwitchEntityDescription, ...] = (
@ -63,12 +66,13 @@ DESCRIPTIONS: tuple[TessieSwitchEntityDescription, ...] = (
on_func=lambda: start_steering_wheel_heater,
off_func=lambda: stop_steering_wheel_heater,
),
)
CHARGE_DESCRIPTION: TessieSwitchEntityDescription = TessieSwitchEntityDescription(
key="charge_state_charge_enable_request",
on_func=lambda: start_charging,
off_func=lambda: stop_charging,
TessieSwitchEntityDescription(
key="charge_state_charging_state",
unique_id="charge_state_charge_enable_request",
on_func=lambda: start_charging,
off_func=lambda: stop_charging,
value_func=lambda state: state in {"Starting", "Charging"},
),
)
PARALLEL_UPDATES = 0
@ -89,10 +93,6 @@ async def async_setup_entry(
for description in DESCRIPTIONS
if description.key in vehicle.data_coordinator.data
),
(
TessieChargeSwitchEntity(vehicle, CHARGE_DESCRIPTION)
for vehicle in entry.runtime_data.vehicles
),
(
TessieChargeFromGridSwitchEntity(energysite)
for energysite in entry.runtime_data.energysites
@ -120,13 +120,15 @@ class TessieSwitchEntity(TessieEntity, SwitchEntity):
description: TessieSwitchEntityDescription,
) -> None:
"""Initialize the Switch."""
super().__init__(vehicle, description.key)
self.entity_description = description
super().__init__(vehicle, description.key)
if description.unique_id:
self._attr_unique_id = f"{vehicle.vin}-{description.unique_id}"
@property
def is_on(self) -> bool:
"""Return the state of the Switch."""
return self._value
return self.entity_description.value_func(self._value)
async def async_turn_on(self, **kwargs: Any) -> None:
"""Turn on the Switch."""
@ -139,18 +141,6 @@ class TessieSwitchEntity(TessieEntity, SwitchEntity):
self.set((self.entity_description.key, False))
class TessieChargeSwitchEntity(TessieSwitchEntity):
"""Entity class for Tessie charge switch."""
@property
def is_on(self) -> bool:
"""Return the state of the Switch."""
if (charge := self.get("charge_state_user_charge_enable_request")) is not None:
return charge
return self._value
class TessieChargeFromGridSwitchEntity(TessieEnergyEntity, SwitchEntity):
"""Entity class for Charge From Grid switch."""

View File

@ -262,7 +262,7 @@
'platform': 'tesla_fleet',
'previous_unique_id': None,
'supported_features': 0,
'translation_key': 'charge_state_user_charge_enable_request',
'translation_key': 'charge_state_charging_state',
'unique_id': 'LRWXF7EK4KC700000-charge_state_user_charge_enable_request',
'unit_of_measurement': None,
})
@ -278,7 +278,7 @@
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'on',
'state': 'off',
})
# ---
# name: test_switch[switch.test_defrost-entry]
@ -456,7 +456,7 @@
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'on',
'state': 'off',
})
# ---
# name: test_switch_alt[switch.test_defrost-statealt]

View File

@ -262,7 +262,7 @@
'platform': 'teslemetry',
'previous_unique_id': None,
'supported_features': 0,
'translation_key': 'charge_state_user_charge_enable_request',
'translation_key': 'charge_state_charging_state',
'unique_id': 'LRW3F7EK4NC700000-charge_state_user_charge_enable_request',
'unit_of_measurement': None,
})
@ -278,7 +278,7 @@
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'on',
'state': 'off',
})
# ---
# name: test_switch[switch.test_defrost-entry]
@ -456,7 +456,7 @@
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'on',
'state': 'off',
})
# ---
# name: test_switch_alt[switch.test_defrost-statealt]

View File

@ -119,7 +119,7 @@
'platform': 'tessie',
'previous_unique_id': None,
'supported_features': 0,
'translation_key': 'charge_state_charge_enable_request',
'translation_key': 'charge_state_charging_state',
'unique_id': 'VINVINVIN-charge_state_charge_enable_request',
'unit_of_measurement': None,
})
@ -351,6 +351,6 @@
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'on',
'state': 'off',
})
# ---

View File

@ -20,7 +20,7 @@ from .common import RESPONSE_OK, assert_entities, setup_platform
async def test_switches(
hass: HomeAssistant, snapshot: SnapshotAssertion, entity_registry: er.EntityRegistry
) -> None:
"""Tests that the switche entities are correct."""
"""Tests that the switch entities are correct."""
entry = await setup_platform(hass, [Platform.SWITCH])