Home Assistant Unofficial Reference 2024.12.1
climate.py
Go to the documentation of this file.
1 """Climate platform for Tesla Fleet 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 TeslaFleetConfigEntry
27 from .const import DOMAIN, TeslaFleetClimateSide
28 from .entity import TeslaFleetVehicleEntity
29 from .helpers import handle_vehicle_command
30 from .models import TeslaFleetVehicleData
31 
32 DEFAULT_MIN_TEMP = 15
33 DEFAULT_MAX_TEMP = 28
34 
35 PARALLEL_UPDATES = 0
36 
37 
39  hass: HomeAssistant,
40  entry: TeslaFleetConfigEntry,
41  async_add_entities: AddEntitiesCallback,
42 ) -> None:
43  """Set up the Tesla Fleet Climate platform from a config entry."""
44 
46  chain(
47  (
49  vehicle, TeslaFleetClimateSide.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  """Tesla Fleet 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: TeslaFleetVehicleData,
82  side: TeslaFleetClimateSide,
83  scopes: Scope,
84  ) -> None:
85  """Initialize the climate."""
86 
87  self.read_onlyread_only = Scope.VEHICLE_CMDS not in scopes
88 
89  if self.read_onlyread_only:
91  self._attr_hvac_modes_attr_hvac_modes_attr_hvac_modes = []
92 
93  super().__init__(
94  data,
95  side,
96  )
97 
98  def _async_update_attrs(self) -> None:
99  """Update the attributes of the entity."""
100  value = self.getget("climate_state_is_climate_on")
101  if value is None:
102  self._attr_hvac_mode_attr_hvac_mode = None
103  elif value:
104  self._attr_hvac_mode_attr_hvac_mode = HVACMode.HEAT_COOL
105  else:
106  self._attr_hvac_mode_attr_hvac_mode = HVACMode.OFF
107 
108  # If not scoped, prevent the user from changing the HVAC mode by making it the only option
109  if self._attr_hvac_mode_attr_hvac_mode and self.read_onlyread_only:
110  self._attr_hvac_modes_attr_hvac_modes_attr_hvac_modes = [self._attr_hvac_mode_attr_hvac_mode]
111 
112  self._attr_current_temperature_attr_current_temperature = self.getget("climate_state_inside_temp")
113  self._attr_target_temperature_attr_target_temperature = self.getget(f"climate_state_{self.key}_setting")
114  self._attr_preset_mode_attr_preset_mode = self.getget("climate_state_climate_keeper_mode")
115  self._attr_min_temp_attr_min_temp = cast(
116  float, self.getget("climate_state_min_avail_temp", DEFAULT_MIN_TEMP)
117  )
118  self._attr_max_temp_attr_max_temp = cast(
119  float, self.getget("climate_state_max_avail_temp", DEFAULT_MAX_TEMP)
120  )
121 
122  async def async_turn_on(self) -> None:
123  """Set the climate state to on."""
124 
125  await self.wake_up_if_asleepwake_up_if_asleep()
126  await handle_vehicle_command(self.apiapiapiapiapi.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  await self.wake_up_if_asleepwake_up_if_asleep()
135  await handle_vehicle_command(self.apiapiapiapiapi.auto_conditioning_stop())
136 
137  self._attr_hvac_mode_attr_hvac_mode = HVACMode.OFF
138  self._attr_preset_mode_attr_preset_mode = self._attr_preset_modes_attr_preset_modes[0]
139  self.async_write_ha_stateasync_write_ha_state()
140 
141  async def async_set_temperature(self, **kwargs: Any) -> None:
142  """Set the climate temperature."""
143 
144  if ATTR_TEMPERATURE not in kwargs:
146  translation_domain=DOMAIN,
147  translation_key="missing_temperature",
148  )
149 
150  temp = kwargs[ATTR_TEMPERATURE]
151  await self.wake_up_if_asleepwake_up_if_asleep()
153  self.apiapiapiapiapi.set_temps(
154  driver_temp=temp,
155  passenger_temp=temp,
156  )
157  )
158  self._attr_target_temperature_attr_target_temperature = temp
159 
160  if mode := kwargs.get(ATTR_HVAC_MODE):
161  # Set HVAC mode will call write_ha_state
162  await self.async_set_hvac_modeasync_set_hvac_modeasync_set_hvac_mode(mode)
163  else:
164  self.async_write_ha_stateasync_write_ha_state()
165 
166  async def async_set_hvac_mode(self, hvac_mode: HVACMode) -> None:
167  """Set the climate mode and state."""
168  if hvac_mode not in self.hvac_modeshvac_modes:
170  translation_domain=DOMAIN,
171  translation_key="invalid_hvac_mode",
172  translation_placeholders={"hvac_mode": hvac_mode},
173  )
174  if hvac_mode == HVACMode.OFF:
175  await self.async_turn_offasync_turn_offasync_turn_off()
176  else:
177  await self.async_turn_onasync_turn_onasync_turn_on()
178 
179  async def async_set_preset_mode(self, preset_mode: str) -> None:
180  """Set the climate preset mode."""
181  await self.wake_up_if_asleepwake_up_if_asleep()
183  self.apiapiapiapiapi.set_climate_keeper_mode(
184  climate_keeper_mode=self._attr_preset_modes_attr_preset_modes.index(preset_mode)
185  )
186  )
187  self._attr_preset_mode_attr_preset_mode = preset_mode
188  if preset_mode != self._attr_preset_modes_attr_preset_modes[0]:
189  self._attr_hvac_mode_attr_hvac_mode = HVACMode.HEAT_COOL
190  self.async_write_ha_stateasync_write_ha_state()
191 
192 
193 COP_MODES = {
194  "Off": HVACMode.OFF,
195  "On": HVACMode.COOL,
196  "FanOnly": HVACMode.FAN_ONLY,
197 }
198 
199 # String to celsius
200 COP_LEVELS = {
201  "Low": 30,
202  "Medium": 35,
203  "High": 40,
204 }
205 
206 # Celsius to IntEnum
207 TEMP_LEVELS = {
208  30: CabinOverheatProtectionTemp.LOW,
209  35: CabinOverheatProtectionTemp.MEDIUM,
210  40: CabinOverheatProtectionTemp.HIGH,
211 }
212 
213 
215  """Tesla Fleet vehicle cabin overheat protection entity."""
216 
217  _attr_precision = PRECISION_WHOLE
218  _attr_target_temperature_step = 5
219  _attr_min_temp = COP_LEVELS["Low"]
220  _attr_max_temp = COP_LEVELS["High"]
221  _attr_temperature_unit = UnitOfTemperature.CELSIUS
222  _attr_hvac_modes = list(COP_MODES.values())
223  _enable_turn_on_off_backwards_compatibility = False
224  _attr_entity_registry_enabled_default = False
225 
226  def __init__(
227  self,
228  data: TeslaFleetVehicleData,
229  scopes: Scope,
230  ) -> None:
231  """Initialize the cabin overheat climate entity."""
232 
233  # Scopes
234  self.read_onlyread_only = Scope.VEHICLE_CMDS not in scopes
235 
236  # Supported Features
237  if self.read_onlyread_only:
238  self._attr_supported_features_attr_supported_features = ClimateEntityFeature(0)
239  self._attr_hvac_modes_attr_hvac_modes = []
240  else:
241  self._attr_supported_features_attr_supported_features = (
242  ClimateEntityFeature.TURN_ON | ClimateEntityFeature.TURN_OFF
243  )
244 
245  super().__init__(data, "climate_state_cabin_overheat_protection")
246 
247  def _async_update_attrs(self) -> None:
248  """Update the attributes of the entity."""
249 
250  if (state := self.getget("climate_state_cabin_overheat_protection")) is None:
251  self._attr_hvac_mode_attr_hvac_mode = None
252  else:
253  self._attr_hvac_mode_attr_hvac_mode = COP_MODES.get(state)
254 
255  # If not scoped, prevent the user from changing the HVAC mode by making it the only option
256  if self._attr_hvac_mode_attr_hvac_mode and self.read_onlyread_only:
257  self._attr_hvac_modes_attr_hvac_modes = [self._attr_hvac_mode_attr_hvac_mode]
258 
259  if (level := self.getget("climate_state_cop_activation_temperature")) is None:
260  self._attr_target_temperature_attr_target_temperature = None
261  else:
262  self._attr_target_temperature_attr_target_temperature = COP_LEVELS.get(level)
263 
264  self._attr_current_temperature_attr_current_temperature = self.getget("climate_state_inside_temp")
265 
266  @property
267  def supported_features(self) -> ClimateEntityFeature:
268  """Return the list of supported features."""
269  if not self.read_onlyread_only and self.getget(
270  "vehicle_config_cop_user_set_temp_supported"
271  ):
272  return (
273  self._attr_supported_features_attr_supported_features | ClimateEntityFeature.TARGET_TEMPERATURE
274  )
275  return self._attr_supported_features_attr_supported_features
276 
277  async def async_turn_on(self) -> None:
278  """Set the climate state to on."""
279  await self.async_set_hvac_modeasync_set_hvac_modeasync_set_hvac_mode(HVACMode.COOL)
280 
281  async def async_turn_off(self) -> None:
282  """Set the climate state to off."""
283  await self.async_set_hvac_modeasync_set_hvac_modeasync_set_hvac_mode(HVACMode.OFF)
284 
285  async def async_set_temperature(self, **kwargs: Any) -> None:
286  """Set the climate temperature."""
287 
288  if ATTR_TEMPERATURE not in kwargs:
290  translation_domain=DOMAIN,
291  translation_key="missing_temperature",
292  )
293 
294  temp = kwargs[ATTR_TEMPERATURE]
295  if (cop_mode := TEMP_LEVELS.get(temp)) is None:
297  translation_domain=DOMAIN,
298  translation_key="invalid_cop_temp",
299  )
300 
301  await self.wake_up_if_asleepwake_up_if_asleep()
302  await handle_vehicle_command(self.apiapiapiapiapi.set_cop_temp(cop_mode))
303  self._attr_target_temperature_attr_target_temperature = temp
304 
305  if mode := kwargs.get(ATTR_HVAC_MODE):
306  await self._async_set_cop_async_set_cop(mode)
307 
308  self.async_write_ha_stateasync_write_ha_state()
309 
310  async def async_set_hvac_mode(self, hvac_mode: HVACMode) -> None:
311  """Set the climate mode and state."""
312  await self.wake_up_if_asleepwake_up_if_asleep()
313  await self._async_set_cop_async_set_cop(hvac_mode)
314  self.async_write_ha_stateasync_write_ha_state()
315 
316  async def _async_set_cop(self, hvac_mode: HVACMode) -> None:
317  if hvac_mode == HVACMode.OFF:
319  self.apiapiapiapiapi.set_cabin_overheat_protection(on=False, fan_only=False)
320  )
321  elif hvac_mode == HVACMode.COOL:
323  self.apiapiapiapiapi.set_cabin_overheat_protection(on=True, fan_only=False)
324  )
325  elif hvac_mode == HVACMode.FAN_ONLY:
327  self.apiapiapiapiapi.set_cabin_overheat_protection(on=True, fan_only=True)
328  )
329 
330  self._attr_hvac_mode_attr_hvac_mode = hvac_mode
None async_set_hvac_mode(self, HVACMode hvac_mode)
Definition: __init__.py:813
None __init__(self, TeslaFleetVehicleData data, Scope scopes)
Definition: climate.py:230
None __init__(self, TeslaFleetVehicleData data, TeslaFleetClimateSide side, Scope scopes)
Definition: climate.py:84
Any|None get(self, str key, Any|None default=None)
Definition: entity.py:61
None async_setup_entry(HomeAssistant hass, TeslaFleetConfigEntry entry, AddEntitiesCallback async_add_entities)
Definition: climate.py:42
bool handle_vehicle_command(Awaitable command)
Definition: helpers.py:50