Home Assistant Unofficial Reference 2024.12.1
climate.py
Go to the documentation of this file.
1 """Climate platform for Teslemetry integration."""
2 
3 from __future__ import annotations
4 
5 from itertools import chain
6 from typing import Any, cast
7 
8 from tesla_fleet_api.const import CabinOverheatProtectionTemp, Scope
9 
11  ATTR_HVAC_MODE,
12  ClimateEntity,
13  ClimateEntityFeature,
14  HVACMode,
15 )
16 from homeassistant.const import (
17  ATTR_TEMPERATURE,
18  PRECISION_HALVES,
19  PRECISION_WHOLE,
20  UnitOfTemperature,
21 )
22 from homeassistant.core import HomeAssistant
23 from homeassistant.exceptions import ServiceValidationError
24 from homeassistant.helpers.entity_platform import AddEntitiesCallback
25 
26 from . import TeslemetryConfigEntry
27 from .const import DOMAIN, TeslemetryClimateSide
28 from .entity import TeslemetryVehicleEntity
29 from .helpers import handle_vehicle_command
30 from .models import TeslemetryVehicleData
31 
32 DEFAULT_MIN_TEMP = 15
33 DEFAULT_MAX_TEMP = 28
34 
35 PARALLEL_UPDATES = 0
36 
37 
39  hass: HomeAssistant,
40  entry: TeslemetryConfigEntry,
41  async_add_entities: AddEntitiesCallback,
42 ) -> None:
43  """Set up the Teslemetry Climate platform from a config entry."""
44 
46  chain(
47  (
49  vehicle, TeslemetryClimateSide.DRIVER, entry.runtime_data.scopes
50  )
51  for vehicle in entry.runtime_data.vehicles
52  ),
53  (
55  vehicle, entry.runtime_data.scopes
56  )
57  for vehicle in entry.runtime_data.vehicles
58  ),
59  )
60  )
61 
62 
64  """Telemetry vehicle climate entity."""
65 
66  _attr_precision = PRECISION_HALVES
67 
68  _attr_temperature_unit = UnitOfTemperature.CELSIUS
69  _attr_hvac_modes = [HVACMode.HEAT_COOL, HVACMode.OFF]
70  _attr_supported_features = (
71  ClimateEntityFeature.TURN_ON
72  | ClimateEntityFeature.TURN_OFF
73  | ClimateEntityFeature.TARGET_TEMPERATURE
74  | ClimateEntityFeature.PRESET_MODE
75  )
76  _attr_preset_modes = ["off", "keep", "dog", "camp"]
77  _enable_turn_on_off_backwards_compatibility = False
78 
79  def __init__(
80  self,
81  data: TeslemetryVehicleData,
82  side: TeslemetryClimateSide,
83  scopes: Scope,
84  ) -> None:
85  """Initialize the climate."""
86  self.scopedscoped = Scope.VEHICLE_CMDS in scopes
87 
88  if not self.scopedscoped:
90  self._attr_hvac_modes_attr_hvac_modes_attr_hvac_modes = []
91 
92  super().__init__(
93  data,
94  side,
95  )
96 
97  def _async_update_attrs(self) -> None:
98  """Update the attributes of the entity."""
99  value = self.getget("climate_state_is_climate_on")
100  if value is None:
101  self._attr_hvac_mode_attr_hvac_mode = None
102  elif value:
103  self._attr_hvac_mode_attr_hvac_mode = HVACMode.HEAT_COOL
104  else:
105  self._attr_hvac_mode_attr_hvac_mode = HVACMode.OFF
106 
107  # If not scoped, prevent the user from changing the HVAC mode by making it the only option
108  if self._attr_hvac_mode_attr_hvac_mode and not self.scopedscoped:
109  self._attr_hvac_modes_attr_hvac_modes_attr_hvac_modes = [self._attr_hvac_mode_attr_hvac_mode]
110 
111  self._attr_current_temperature_attr_current_temperature = self.getget("climate_state_inside_temp")
112  self._attr_target_temperature_attr_target_temperature = self.getget(f"climate_state_{self.key}_setting")
113  self._attr_preset_mode_attr_preset_mode = self.getget("climate_state_climate_keeper_mode")
114  self._attr_min_temp_attr_min_temp = cast(
115  float, self.getget("climate_state_min_avail_temp", DEFAULT_MIN_TEMP)
116  )
117  self._attr_max_temp_attr_max_temp = cast(
118  float, self.getget("climate_state_max_avail_temp", DEFAULT_MAX_TEMP)
119  )
120 
121  async def async_turn_on(self) -> None:
122  """Set the climate state to on."""
123 
124  self.raise_for_scoperaise_for_scope(Scope.VEHICLE_CMDS)
125  await self.wake_up_if_asleepwake_up_if_asleep()
126  await handle_vehicle_command(self.apiapiapiapiapiapi.auto_conditioning_start())
127 
128  self._attr_hvac_mode_attr_hvac_mode = HVACMode.HEAT_COOL
129  self.async_write_ha_stateasync_write_ha_state()
130 
131  async def async_turn_off(self) -> None:
132  """Set the climate state to off."""
133 
134  self.raise_for_scoperaise_for_scope(Scope.VEHICLE_CMDS)
135  await self.wake_up_if_asleepwake_up_if_asleep()
136  await handle_vehicle_command(self.apiapiapiapiapiapi.auto_conditioning_stop())
137 
138  self._attr_hvac_mode_attr_hvac_mode = HVACMode.OFF
139  self._attr_preset_mode_attr_preset_mode = self._attr_preset_modes_attr_preset_modes[0]
140  self.async_write_ha_stateasync_write_ha_state()
141 
142  async def async_set_temperature(self, **kwargs: Any) -> None:
143  """Set the climate temperature."""
144  if temp := kwargs.get(ATTR_TEMPERATURE):
145  await self.wake_up_if_asleepwake_up_if_asleep()
147  self.apiapiapiapiapiapi.set_temps(
148  driver_temp=temp,
149  passenger_temp=temp,
150  )
151  )
152  self._attr_target_temperature_attr_target_temperature = temp
153 
154  if mode := kwargs.get(ATTR_HVAC_MODE):
155  # Set HVAC mode will call write_ha_state
156  await self.async_set_hvac_modeasync_set_hvac_modeasync_set_hvac_mode(mode)
157  else:
158  self.async_write_ha_stateasync_write_ha_state()
159 
160  async def async_set_hvac_mode(self, hvac_mode: HVACMode) -> None:
161  """Set the climate mode and state."""
162  if hvac_mode == HVACMode.OFF:
163  await self.async_turn_offasync_turn_offasync_turn_off()
164  else:
165  await self.async_turn_onasync_turn_onasync_turn_on()
166 
167  async def async_set_preset_mode(self, preset_mode: str) -> None:
168  """Set the climate preset mode."""
169  await self.wake_up_if_asleepwake_up_if_asleep()
171  self.apiapiapiapiapiapi.set_climate_keeper_mode(
172  climate_keeper_mode=self._attr_preset_modes_attr_preset_modes.index(preset_mode)
173  )
174  )
175  self._attr_preset_mode_attr_preset_mode = preset_mode
176  if preset_mode != self._attr_preset_modes_attr_preset_modes[0]:
177  # Changing preset mode will also turn on climate
178  self._attr_hvac_mode_attr_hvac_mode = HVACMode.HEAT_COOL
179  self.async_write_ha_stateasync_write_ha_state()
180 
181 
182 COP_MODES = {
183  "Off": HVACMode.OFF,
184  "On": HVACMode.COOL,
185  "FanOnly": HVACMode.FAN_ONLY,
186 }
187 
188 # String to celsius
189 COP_LEVELS = {
190  "Low": 30,
191  "Medium": 35,
192  "High": 40,
193 }
194 
195 # Celsius to IntEnum
196 TEMP_LEVELS = {
197  30: CabinOverheatProtectionTemp.LOW,
198  35: CabinOverheatProtectionTemp.MEDIUM,
199  40: CabinOverheatProtectionTemp.HIGH,
200 }
201 
202 
204  """Telemetry vehicle cabin overheat protection entity."""
205 
206  _attr_precision = PRECISION_WHOLE
207  _attr_target_temperature_step = 5
208  _attr_min_temp = COP_LEVELS["Low"]
209  _attr_max_temp = COP_LEVELS["High"]
210  _attr_temperature_unit = UnitOfTemperature.CELSIUS
211  _attr_hvac_modes = list(COP_MODES.values())
212  _enable_turn_on_off_backwards_compatibility = False
213  _attr_entity_registry_enabled_default = False
214 
215  def __init__(
216  self,
217  data: TeslemetryVehicleData,
218  scopes: Scope,
219  ) -> None:
220  """Initialize the climate."""
221 
222  self.scopedscoped = Scope.VEHICLE_CMDS in scopes
223  if self.scopedscoped:
224  self._attr_supported_features_attr_supported_features = (
225  ClimateEntityFeature.TURN_ON | ClimateEntityFeature.TURN_OFF
226  )
227  else:
228  self._attr_supported_features_attr_supported_features = ClimateEntityFeature(0)
229  self._attr_hvac_modes_attr_hvac_modes = []
230 
231  super().__init__(data, "climate_state_cabin_overheat_protection")
232 
233  # Supported Features from data
234  if self.scopedscoped and self.getget("vehicle_config_cop_user_set_temp_supported"):
235  self._attr_supported_features_attr_supported_features |= ClimateEntityFeature.TARGET_TEMPERATURE
236 
237  def _async_update_attrs(self) -> None:
238  """Update the attributes of the entity."""
239 
240  if (state := self.getget("climate_state_cabin_overheat_protection")) is None:
241  self._attr_hvac_mode_attr_hvac_mode = None
242  else:
243  self._attr_hvac_mode_attr_hvac_mode = COP_MODES.get(state)
244 
245  # If not scoped, prevent the user from changing the HVAC mode by making it the only option
246  if self._attr_hvac_mode_attr_hvac_mode and not self.scopedscoped:
247  self._attr_hvac_modes_attr_hvac_modes = [self._attr_hvac_mode_attr_hvac_mode]
248 
249  if (level := self.getget("climate_state_cop_activation_temperature")) is None:
250  self._attr_target_temperature_attr_target_temperature = None
251  else:
252  self._attr_target_temperature_attr_target_temperature = COP_LEVELS.get(level)
253 
254  self._attr_current_temperature_attr_current_temperature = self.getget("climate_state_inside_temp")
255 
256  async def async_turn_on(self) -> None:
257  """Set the climate state to on."""
258  await self.async_set_hvac_modeasync_set_hvac_modeasync_set_hvac_mode(HVACMode.COOL)
259 
260  async def async_turn_off(self) -> None:
261  """Set the climate state to off."""
262  await self.async_set_hvac_modeasync_set_hvac_modeasync_set_hvac_mode(HVACMode.OFF)
263 
264  async def async_set_temperature(self, **kwargs: Any) -> None:
265  """Set the climate temperature."""
266  self.raise_for_scoperaise_for_scope(Scope.VEHICLE_CMDS)
267 
268  if (temp := kwargs.get(ATTR_TEMPERATURE)) is None or (
269  cop_mode := TEMP_LEVELS.get(temp)
270  ) is None:
272  translation_domain=DOMAIN,
273  translation_key="invalid_cop_temp",
274  )
275 
276  await self.wake_up_if_asleepwake_up_if_asleep()
277  await handle_vehicle_command(self.apiapiapiapiapiapi.set_cop_temp(cop_mode))
278  self._attr_target_temperature_attr_target_temperature = temp
279 
280  if mode := kwargs.get(ATTR_HVAC_MODE):
281  await self._async_set_cop_async_set_cop(mode)
282 
283  self.async_write_ha_stateasync_write_ha_state()
284 
285  async def _async_set_cop(self, hvac_mode: HVACMode) -> None:
286  if hvac_mode == HVACMode.OFF:
288  self.apiapiapiapiapiapi.set_cabin_overheat_protection(on=False, fan_only=False)
289  )
290  elif hvac_mode == HVACMode.COOL:
292  self.apiapiapiapiapiapi.set_cabin_overheat_protection(on=True, fan_only=False)
293  )
294  elif hvac_mode == HVACMode.FAN_ONLY:
296  self.apiapiapiapiapiapi.set_cabin_overheat_protection(on=True, fan_only=True)
297  )
298 
299  self._attr_hvac_mode_attr_hvac_mode = hvac_mode
300 
301  async def async_set_hvac_mode(self, hvac_mode: HVACMode) -> None:
302  """Set the climate mode and state."""
303  self.raise_for_scoperaise_for_scope(Scope.VEHICLE_CMDS)
304  await self.wake_up_if_asleepwake_up_if_asleep()
305  await self._async_set_cop_async_set_cop(hvac_mode)
306  self.async_write_ha_stateasync_write_ha_state()
None async_set_hvac_mode(self, HVACMode hvac_mode)
Definition: __init__.py:813
None __init__(self, TeslemetryVehicleData data, Scope scopes)
Definition: climate.py:219
None __init__(self, TeslemetryVehicleData data, TeslemetryClimateSide side, Scope scopes)
Definition: climate.py:84
Any|None get(self, str key, Any|None default=None)
Definition: entity.py:61
bool handle_vehicle_command(Awaitable command)
Definition: helpers.py:50
None async_setup_entry(HomeAssistant hass, TeslemetryConfigEntry entry, AddEntitiesCallback async_add_entities)
Definition: climate.py:42