Skip to content

Commit e70a8cf

Browse files
committed
Fix seat heater select
1 parent 72dd0dd commit e70a8cf

File tree

1 file changed

+56
-26
lines changed

1 file changed

+56
-26
lines changed

custom_components/teslemetry/select.py

Lines changed: 56 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
EnergyExportMode,
1010
EnergyOperationMode,
1111
)
12+
from tesla_fleet_api.teslemetry.vehicles import TeslemetryVehicleFleet
1213
from teslemetry_stream import Signal
1314

1415
from dataclasses import dataclass
@@ -18,12 +19,16 @@
1819
from homeassistant.core import HomeAssistant
1920
from homeassistant.helpers.entity_platform import AddEntitiesCallback
2021
from homeassistant.helpers.restore_state import RestoreEntity
22+
from teslemetry_stream.vehicle import TeslemetryStreamVehicle
23+
24+
from custom_components.teslemetry.helpers import handle_vehicle_command
2125

2226
from .const import TeslemetryHeaterOptions
2327
from .entity import (
28+
TeslemetryRootEntity,
2429
TeslemetryVehicleEntity,
2530
TeslemetryEnergyInfoEntity,
26-
TeslemetryVehicleStreamSingleEntity,
31+
TeslemetryVehicleStreamEntity,
2732
)
2833
from .models import TeslemetryEnergyData, TeslemetryVehicleData
2934

@@ -34,23 +39,28 @@ class SeatHeaterDescription(SelectEntityDescription):
3439

3540
position: Seat
3641
supported_fn: Callable = lambda _: True
37-
streaming_key: Signal | None = None
38-
42+
streaming_listener: (
43+
Callable[
44+
[TeslemetryStreamVehicle, Callable[[int | None], None]],
45+
Callable[[], None],
46+
]
47+
| None
48+
) = None
3949

4050
SEAT_HEATER_DESCRIPTIONS: tuple[SeatHeaterDescription, ...] = (
4151
SeatHeaterDescription(
4252
key="climate_state_seat_heater_left",
43-
streaming_key=Signal.SEAT_HEATER_LEFT,
53+
streaming_listener=lambda x, y: x.listen_SeatHeaterLeft(y),
4454
position=Seat.FRONT_LEFT,
4555
),
4656
SeatHeaterDescription(
4757
key="climate_state_seat_heater_right",
48-
streaming_key=Signal.SEAT_HEATER_RIGHT,
58+
streaming_listener=lambda x, y: x.listen_SeatHeaterRight(y),
4959
position=Seat.FRONT_RIGHT,
5060
),
5161
SeatHeaterDescription(
5262
key="climate_state_seat_heater_rear_left",
53-
streaming_key=Signal.SEAT_HEATER_REAR_LEFT,
63+
streaming_listener=lambda x, y: x.listen_SeatHeaterRearLeft(y),
5464
position=Seat.REAR_LEFT,
5565
supported_fn=lambda data: data.get(
5666
"vehicle_config_rear_seat_heaters"
@@ -59,7 +69,7 @@ class SeatHeaterDescription(SelectEntityDescription):
5969
),
6070
SeatHeaterDescription(
6171
key="climate_state_seat_heater_rear_center",
62-
streaming_key=Signal.SEAT_HEATER_REAR_CENTER,
72+
streaming_listener=lambda x, y: x.listen_SeatHeaterRearCenter(y),
6373
position=Seat.REAR_CENTER,
6474
supported_fn=lambda data: data.get(
6575
"vehicle_config_rear_seat_heaters"
@@ -68,7 +78,7 @@ class SeatHeaterDescription(SelectEntityDescription):
6878
),
6979
SeatHeaterDescription(
7080
key="climate_state_seat_heater_rear_right",
71-
streaming_key=Signal.SEAT_HEATER_REAR_RIGHT,
81+
streaming_listener=lambda x, y: x.listen_SeatHeaterRearRight(y),
7282
position=Seat.REAR_RIGHT,
7383
supported_fn=lambda data: data.get(
7484
"vehicle_config_rear_seat_heaters"
@@ -106,7 +116,7 @@ async def async_setup_entry(
106116
chain(
107117
(
108118
TeslemetryPollingSeatHeaterSelectEntity(vehicle, description, scoped)
109-
if vehicle.api.pre2021 or vehicle.firmware < "2024.26" or description.streaming_key is None
119+
if vehicle.api.pre2021 or vehicle.firmware < "2024.26" or description.streaming_listener is None
110120
else TeslemetryStreamingSeatHeaterSelectEntity(vehicle, description, scoped)
111121
for description in SEAT_HEATER_DESCRIPTIONS
112122
for vehicle in entry.runtime_data.vehicles
@@ -133,27 +143,28 @@ async def async_setup_entry(
133143
)
134144

135145

136-
class TeslemetrySeatHeaterSelectEntity(SelectEntity):
146+
class TeslemetrySeatHeaterSelectEntity(TeslemetryRootEntity, SelectEntity):
137147
"""Select entity for vehicle seat heater."""
138148

149+
api: TeslemetryVehicleFleet
139150
entity_description: SeatHeaterDescription
140-
141151
_attr_options = [
142152
TeslemetryHeaterOptions.OFF,
143153
TeslemetryHeaterOptions.LOW,
144154
TeslemetryHeaterOptions.MEDIUM,
145155
TeslemetryHeaterOptions.HIGH,
146156
]
157+
_climate_state: bool = False
147158

148159
async def async_select_option(self, option: str) -> None:
149160
"""Change the selected option."""
150161
self.raise_for_scope(Scope.VEHICLE_CMDS)
151162

152163
level = self._attr_options.index(option)
153164
# AC must be on to turn on seat heater
154-
if not self.get("climate_state_is_climate_on"):
155-
await self.handle_command(self.api.auto_conditioning_start())
156-
await self.handle_command(
165+
if not self._climate_state:
166+
await handle_vehicle_command(self.api.auto_conditioning_start())
167+
await handle_vehicle_command(
157168
self.api.remote_seat_heater_request(self.entity_description.position, level)
158169
)
159170
self._attr_current_option = option
@@ -177,13 +188,14 @@ def __init__(
177188

178189
def _async_update_attrs(self) -> None:
179190
"""Handle updated data from the coordinator."""
191+
self._climate_state = bool(self.get("climate_state_is_climate_on"))
180192
value = self._value
181193
if isinstance(value, int):
182194
self._attr_current_option = self._attr_options[value]
183195
else:
184196
self._attr_current_option = None
185197

186-
class TeslemetryStreamingSeatHeaterSelectEntity(TeslemetryVehicleStreamSingleEntity, TeslemetrySeatHeaterSelectEntity, RestoreEntity):
198+
class TeslemetryStreamingSeatHeaterSelectEntity(TeslemetryVehicleStreamEntity, TeslemetrySeatHeaterSelectEntity, RestoreEntity):
187199
"""Select entity for vehicle seat heater."""
188200

189201
def __init__(
@@ -193,27 +205,45 @@ def __init__(
193205
scoped: bool,
194206
) -> None:
195207
"""Initialize the vehicle seat select entity."""
196-
assert description.streaming_key
197208
super().__init__(
198-
data, description.key, description.streaming_key
209+
data, description.key
199210
)
200211
self.entity_description = description
201212
self.scoped = scoped
202213
self._attr_current_option = None
203214

204215
async def async_added_to_hass(self) -> None:
205-
"""Handle entity which will be added."""
206-
await super().async_added_to_hass()
207-
if (state := await self.async_get_last_state()) is not None:
208-
if (state.state in self._attr_options):
209-
self._attr_current_option = state.state
216+
"""Handle entity which will be added."""
217+
await super().async_added_to_hass()
218+
219+
# Restore state
220+
if (state := await self.async_get_last_state()) is not None:
221+
if state.state in self.entity_description.options:
222+
self._attr_current_option = state.state
223+
224+
# Listen for streaming data
225+
assert self.entity_description.streaming_listener is not None
226+
self.async_on_remove(
227+
self.entity_description.streaming_listener(
228+
self.vehicle.stream_vehicle, self._value_callback
229+
)
230+
)
210231

211-
def _async_value_from_stream(self, value) -> None:
232+
self.async_on_remove(
233+
self.vehicle.stream_vehicle.listen_HvacACEnabled(self._climate_callback)
234+
)
235+
236+
def _value_callback(self, value: int | None) -> None:
212237
"""Update the value of the entity."""
213-
if isinstance(value, int):
214-
self._attr_current_option = self._attr_options[value]
215-
else:
238+
if value is None:
216239
self._attr_current_option = None
240+
else:
241+
self._attr_current_option = self.entity_description.options[value]
242+
self.async_write_ha_state()
243+
244+
def _climate_callback(self, value: bool | None) -> None:
245+
"""Update the value of the entity."""
246+
self._climate_state = bool(value)
217247

218248

219249
class TeslemetryWheelHeaterSelectEntity(SelectEntity):

0 commit comments

Comments
 (0)