1 """Support for Tado thermostats."""
3 from __future__
import annotations
5 from collections.abc
import Mapping
10 import voluptuous
as vol
33 from .
import TadoConfigEntry, TadoConnector
35 CONST_EXCLUSIVE_OVERLAY_GROUP,
42 CONST_MODE_SMART_SCHEDULE,
44 CONST_OVERLAY_TADO_OPTIONS,
46 HA_TERMINATION_DURATION,
48 HA_TO_TADO_FAN_MODE_MAP,
49 HA_TO_TADO_FAN_MODE_MAP_LEGACY,
50 HA_TO_TADO_HVAC_MODE_MAP,
51 ORDERED_KNOWN_TADO_MODES,
53 SIGNAL_TADO_UPDATE_RECEIVED,
55 SUPPORT_PRESET_MANUAL,
56 TADO_DEFAULT_MAX_TEMP,
57 TADO_DEFAULT_MIN_TEMP,
58 TADO_FANLEVEL_SETTING,
59 TADO_FANSPEED_SETTING,
60 TADO_HORIZONTAL_SWING_SETTING,
61 TADO_HVAC_ACTION_TO_HA_HVAC_ACTION,
62 TADO_MODES_WITH_NO_TEMP_SETTING,
66 TADO_TO_HA_FAN_MODE_MAP,
67 TADO_TO_HA_FAN_MODE_MAP_LEGACY,
68 TADO_TO_HA_HVAC_MODE_MAP,
69 TADO_TO_HA_OFFSET_MAP,
70 TADO_TO_HA_SWING_MODE_MAP,
71 TADO_VERTICAL_SWING_SETTING,
73 TYPE_AIR_CONDITIONING,
76 from .entity
import TadoZoneEntity
77 from .helper
import decide_duration, decide_overlay_mode, generate_supported_fanmodes
79 _LOGGER = logging.getLogger(__name__)
81 SERVICE_CLIMATE_TIMER =
"set_climate_timer"
82 ATTR_TIME_PERIOD =
"time_period"
83 ATTR_REQUESTED_OVERLAY =
"requested_overlay"
85 CLIMATE_TIMER_SCHEMA: VolDictType = {
86 vol.Required(ATTR_TEMPERATURE): vol.Coerce(float),
87 vol.Exclusive(ATTR_TIME_PERIOD, CONST_EXCLUSIVE_OVERLAY_GROUP): vol.All(
88 cv.time_period, cv.positive_timedelta,
lambda td: td.total_seconds()
90 vol.Exclusive(ATTR_REQUESTED_OVERLAY, CONST_EXCLUSIVE_OVERLAY_GROUP): vol.In(
91 CONST_OVERLAY_TADO_OPTIONS
95 SERVICE_TEMP_OFFSET =
"set_climate_temperature_offset"
96 ATTR_OFFSET =
"offset"
98 CLIMATE_TEMP_OFFSET_SCHEMA: VolDictType = {
99 vol.Required(ATTR_OFFSET, default=0): vol.Coerce(float),
104 hass: HomeAssistant, entry: TadoConfigEntry, async_add_entities: AddEntitiesCallback
106 """Set up the Tado climate platform."""
108 tado = entry.runtime_data
109 entities = await hass.async_add_executor_job(_generate_entities, tado)
111 platform = entity_platform.async_get_current_platform()
113 platform.async_register_entity_service(
114 SERVICE_CLIMATE_TIMER,
115 CLIMATE_TIMER_SCHEMA,
119 platform.async_register_entity_service(
121 CLIMATE_TEMP_OFFSET_SCHEMA,
129 """Create all climate entities."""
131 for zone
in tado.zones:
132 if zone[
"type"]
in [TYPE_HEATING, TYPE_AIR_CONDITIONING]:
134 tado, zone[
"name"], zone[
"id"], zone[
"devices"][0]
137 entities.append(entity)
142 tado: TadoConnector, name: str, zone_id: int, device_info: dict
143 ) -> TadoClimate |
None:
144 """Create a Tado climate entity."""
145 capabilities = tado.get_capabilities(zone_id)
146 _LOGGER.debug(
"Capabilities for zone %s: %s", zone_id, capabilities)
148 zone_type = capabilities[
"type"]
150 ClimateEntityFeature.PRESET_MODE
151 | ClimateEntityFeature.TARGET_TEMPERATURE
152 | ClimateEntityFeature.TURN_OFF
153 | ClimateEntityFeature.TURN_ON
155 supported_hvac_modes = [
156 TADO_TO_HA_HVAC_MODE_MAP[CONST_MODE_OFF],
157 TADO_TO_HA_HVAC_MODE_MAP[CONST_MODE_SMART_SCHEDULE],
159 supported_fan_modes =
None
160 supported_swing_modes =
None
161 heat_temperatures =
None
162 cool_temperatures =
None
164 if zone_type == TYPE_AIR_CONDITIONING:
166 for mode
in ORDERED_KNOWN_TADO_MODES:
167 if mode
not in capabilities:
170 supported_hvac_modes.append(TADO_TO_HA_HVAC_MODE_MAP[mode])
172 TADO_SWING_SETTING
in capabilities[mode]
173 or TADO_VERTICAL_SWING_SETTING
in capabilities[mode]
174 or TADO_VERTICAL_SWING_SETTING
in capabilities[mode]
176 support_flags |= ClimateEntityFeature.SWING_MODE
177 supported_swing_modes = []
178 if TADO_SWING_SETTING
in capabilities[mode]:
179 supported_swing_modes.append(
180 TADO_TO_HA_SWING_MODE_MAP[TADO_SWING_ON]
182 if TADO_VERTICAL_SWING_SETTING
in capabilities[mode]:
183 supported_swing_modes.append(SWING_VERTICAL)
184 if TADO_HORIZONTAL_SWING_SETTING
in capabilities[mode]:
185 supported_swing_modes.append(SWING_HORIZONTAL)
187 SWING_HORIZONTAL
in supported_swing_modes
188 and SWING_VERTICAL
in supported_swing_modes
190 supported_swing_modes.append(SWING_BOTH)
191 supported_swing_modes.append(TADO_TO_HA_SWING_MODE_MAP[TADO_SWING_OFF])
194 TADO_FANSPEED_SETTING
not in capabilities[mode]
195 and TADO_FANLEVEL_SETTING
not in capabilities[mode]
199 support_flags |= ClimateEntityFeature.FAN_MODE
201 if supported_fan_modes:
204 if TADO_FANSPEED_SETTING
in capabilities[mode]:
206 TADO_TO_HA_FAN_MODE_MAP_LEGACY,
207 capabilities[mode][TADO_FANSPEED_SETTING],
212 TADO_TO_HA_FAN_MODE_MAP, capabilities[mode][TADO_FANLEVEL_SETTING]
215 cool_temperatures = capabilities[CONST_MODE_COOL][
"temperatures"]
217 supported_hvac_modes.append(HVACMode.HEAT)
219 if CONST_MODE_HEAT
in capabilities:
220 heat_temperatures = capabilities[CONST_MODE_HEAT][
"temperatures"]
222 if heat_temperatures
is None and "temperatures" in capabilities:
223 heat_temperatures = capabilities[
"temperatures"]
225 if cool_temperatures
is None and heat_temperatures
is None:
226 _LOGGER.debug(
"Not adding zone %s since it has no temperatures", name)
236 if heat_temperatures
is not None:
237 heat_min_temp =
float(heat_temperatures[
"celsius"][
"min"])
238 heat_max_temp =
float(heat_temperatures[
"celsius"][
"max"])
239 heat_step = heat_temperatures[
"celsius"].
get(
"step", PRECISION_TENTHS)
241 if cool_temperatures
is not None:
242 cool_min_temp =
float(cool_temperatures[
"celsius"][
"min"])
243 cool_max_temp =
float(cool_temperatures[
"celsius"][
"max"])
244 cool_step = cool_temperatures[
"celsius"].
get(
"step", PRECISION_TENTHS)
251 supported_hvac_modes,
261 supported_swing_modes,
266 """Representation of a Tado climate entity."""
268 _attr_temperature_unit = UnitOfTemperature.CELSIUS
270 _attr_translation_key = DOMAIN
272 _enable_turn_on_off_backwards_compatibility =
False
280 supported_hvac_modes: list[HVACMode],
281 support_flags: ClimateEntityFeature,
282 device_info: dict[str, str],
283 heat_min_temp: float |
None =
None,
284 heat_max_temp: float |
None =
None,
285 heat_step: float |
None =
None,
286 cool_min_temp: float |
None =
None,
287 cool_max_temp: float |
None =
None,
288 cool_step: float |
None =
None,
289 supported_fan_modes: list[str] |
None =
None,
290 supported_swing_modes: list[str] |
None =
None,
292 """Initialize of Tado climate entity."""
294 super().
__init__(zone_name, tado.home_id, zone_id)
304 self.
_ac_device_ac_device = zone_type == TYPE_AIR_CONDITIONING
331 capabilities = tado.get_capabilities(zone_id)
337 self._tado_zone_temp_offset: dict[str, Any] = {}
343 """Register for sensor updates."""
347 SIGNAL_TADO_UPDATE_RECEIVED.format(self.
_tado_tado.home_id,
"home",
"data"),
355 SIGNAL_TADO_UPDATE_RECEIVED.format(
364 """Return the current humidity."""
369 """Return the sensor temperature."""
374 """Return hvac operation ie. heat, cool mode.
376 Need to be one of HVAC_MODE_*.
382 """Return the current running hvac operation if supported.
384 Need to be one of CURRENT_HVAC_*.
386 return TADO_HVAC_ACTION_TO_HA_HVAC_ACTION.get(
387 self.
_tado_zone_data_tado_zone_data.current_hvac_action, HVACAction.OFF
392 """Return the fan setting."""
395 return TADO_TO_HA_FAN_MODE_MAP_LEGACY.get(
399 return TADO_TO_HA_FAN_MODE_MAP.get(
406 """Turn fan on/off."""
408 self.
_control_hvac_control_hvac(fan_mode=HA_TO_TADO_FAN_MODE_MAP_LEGACY[fan_mode])
410 self.
_control_hvac_control_hvac(fan_mode=HA_TO_TADO_FAN_MODE_MAP[fan_mode])
414 """Return the current preset mode (home, away or auto)."""
428 """Return a list of available preset modes."""
429 if self.
_tado_tado.get_auto_geofencing_supported():
430 return SUPPORT_PRESET_AUTO
431 return SUPPORT_PRESET_MANUAL
434 """Set new preset mode."""
435 self.
_tado_tado.set_presence(preset_mode)
439 """Return the supported step of target temperature."""
440 if self.
_tado_zone_data_tado_zone_data.current_hvac_mode == CONST_MODE_COOL:
446 """Return the temperature we try to reach."""
456 time_period: int |
None =
None,
457 requested_overlay: str |
None =
None,
459 """Set the timer on the entity, and temperature if supported."""
462 hvac_mode=CONST_MODE_HEAT,
463 target_temp=temperature,
464 duration=time_period,
465 overlay_mode=requested_overlay,
469 """Set offset on the entity."""
472 "Setting temperature offset for device %s setting to (%.1f)",
477 self.
_tado_tado.set_temperature_offset(self.
_device_id_device_id, offset)
480 """Set new target temperature."""
481 if (temperature := kwargs.get(ATTR_TEMPERATURE))
is None:
487 CONST_MODE_SMART_SCHEDULE,
492 new_hvac_mode = CONST_MODE_COOL
if self.
_ac_device_ac_device
else CONST_MODE_HEAT
493 self.
_control_hvac_control_hvac(target_temp=temperature, hvac_mode=new_hvac_mode)
496 """Set new target hvac mode."""
497 self.
_control_hvac_control_hvac(hvac_mode=HA_TO_TADO_HVAC_MODE_MAP[hvac_mode])
501 """Return if the device is available."""
506 """Return the minimum temperature."""
515 return TADO_DEFAULT_MIN_TEMP
519 """Return the maximum temperature."""
528 return TADO_DEFAULT_MAX_TEMP
532 """Active swing mode for the device."""
533 swing_modes_tuple = (
538 if swing_modes_tuple == (TADO_SWING_OFF, TADO_SWING_OFF, TADO_SWING_OFF):
539 return TADO_TO_HA_SWING_MODE_MAP[TADO_SWING_OFF]
540 if swing_modes_tuple == (TADO_SWING_ON, TADO_SWING_OFF, TADO_SWING_OFF):
541 return TADO_TO_HA_SWING_MODE_MAP[TADO_SWING_ON]
542 if swing_modes_tuple == (TADO_SWING_OFF, TADO_SWING_ON, TADO_SWING_OFF):
543 return SWING_VERTICAL
544 if swing_modes_tuple == (TADO_SWING_OFF, TADO_SWING_OFF, TADO_SWING_ON):
545 return SWING_HORIZONTAL
546 if swing_modes_tuple == (TADO_SWING_OFF, TADO_SWING_ON, TADO_SWING_ON):
549 return TADO_TO_HA_SWING_MODE_MAP[TADO_SWING_OFF]
553 """Return temperature offset."""
554 state_attr: dict[str, Any] = self._tado_zone_temp_offset
555 state_attr[HA_TERMINATION_TYPE] = (
558 state_attr[HA_TERMINATION_DURATION] = (
559 self.
_tado_zone_data_tado_zone_data.default_overlay_termination_duration
564 """Set swing modes for the device."""
565 vertical_swing =
None
566 horizontal_swing =
None
570 if swing_mode == SWING_OFF:
572 swing = TADO_SWING_OFF
574 horizontal_swing = TADO_SWING_OFF
576 vertical_swing = TADO_SWING_OFF
577 if swing_mode == SWING_ON:
578 swing = TADO_SWING_ON
579 if swing_mode == SWING_VERTICAL:
581 vertical_swing = TADO_SWING_ON
583 horizontal_swing = TADO_SWING_OFF
584 if swing_mode == SWING_HORIZONTAL:
586 vertical_swing = TADO_SWING_OFF
588 horizontal_swing = TADO_SWING_ON
589 if swing_mode == SWING_BOTH:
591 vertical_swing = TADO_SWING_ON
593 horizontal_swing = TADO_SWING_ON
597 vertical_swing=vertical_swing,
598 horizontal_swing=horizontal_swing,
603 """Load tado data into zone."""
607 for offset_key, attr
in TADO_TO_HA_OFFSET_MAP.items():
611 in self.
_tado_tado.data[
"device"][self.
_device_id_device_id][TEMP_OFFSET]
613 self._tado_zone_temp_offset[attr] = self.
_tado_tado.data[
"device"][
615 ][TEMP_OFFSET][offset_key]
637 """Load tado data and update state."""
643 """Load tado geofencing data into zone."""
648 """Load tado data and update state."""
653 def adjust_temp(min_temp, max_temp) -> float | None:
654 if max_temp
is not None and self.
_target_temp_target_temp > max_temp:
656 if min_temp
is not None and self.
_target_temp_target_temp < min_temp:
671 hvac_mode: str |
None =
None,
672 target_temp: float |
None =
None,
673 fan_mode: str |
None =
None,
674 swing_mode: str |
None =
None,
675 duration: int |
None =
None,
676 overlay_mode: str |
None =
None,
677 vertical_swing: str |
None =
None,
678 horizontal_swing: str |
None =
None,
680 """Send new target temperature to Tado."""
721 "Switching to SMART_SCHEDULE for zone %s (%d)",
729 tado=self.
_tado_tado,
731 overlay_mode=overlay_mode,
735 tado=self.
_tado_tado,
738 overlay_mode=overlay_mode,
742 "Switching to %s for zone %s (%d) with temperature %s °C and duration"
743 " %s using overlay %s"
756 temperature_to_send =
None
771 vertical_swing =
None
772 horizontal_swing =
None
789 self.
_tado_tado.set_zone_overlay(
791 overlay_mode=overlay_mode,
792 temperature=temperature_to_send,
799 vertical_swing=vertical_swing,
800 horizontal_swing=horizontal_swing,
812 self, setting: str, current_state: str |
None
ClimateEntityFeature supported_features(self)
HVACAction hvac_action(self)
None _async_update_home_callback(self)
float|None target_temperature(self)
int|None current_humidity(self)
def _control_hvac(self, str|None hvac_mode=None, float|None target_temp=None, str|None fan_mode=None, str|None swing_mode=None, int|None duration=None, str|None overlay_mode=None, str|None vertical_swing=None, str|None horizontal_swing=None)
bool _is_current_setting_supported_by_current_hvac_mode(self, str setting, str|None current_state)
None _async_update_zone_callback(self)
None __init__(self, TadoConnector tado, str zone_name, int zone_id, str zone_type, list[HVACMode] supported_hvac_modes, ClimateEntityFeature support_flags, dict[str, str] device_info, float|None heat_min_temp=None, float|None heat_max_temp=None, float|None heat_step=None, float|None cool_min_temp=None, float|None cool_max_temp=None, float|None cool_step=None, list[str]|None supported_fan_modes=None, list[str]|None supported_swing_modes=None)
None set_fan_mode(self, str fan_mode)
str|None swing_mode(self)
_current_tado_capabilities
_current_tado_hvac_action
list[str] preset_modes(self)
None set_hvac_mode(self, HVACMode hvac_mode)
None _async_update_zone_data(self)
None set_swing_mode(self, str swing_mode)
def set_timer(self, float temperature, int|None time_period=None, str|None requested_overlay=None)
None set_temp_offset(self, float offset)
bool _is_valid_setting_for_hvac_mode(self, str setting)
None async_added_to_hass(self)
_current_tado_horizontal_swing
None _normalize_target_temp_for_hvac_mode(self)
float|None current_temperature(self)
float|None target_temperature_step(self)
_current_tado_vertical_swing
Mapping[str, Any]|None extra_state_attributes(self)
None _async_update_home_data(self)
None set_preset_mode(self, str preset_mode)
None set_temperature(self, **Any kwargs)
None async_write_ha_state(self)
int|None supported_features(self)
None async_on_remove(self, CALLBACK_TYPE func)
web.Response get(self, web.Request request, str config_key)
list[TadoClimate] _generate_entities(TadoConnector tado)
TadoClimate|None create_climate_entity(TadoConnector tado, str name, int zone_id, dict device_info)
None async_setup_entry(HomeAssistant hass, TadoConfigEntry entry, AddEntitiesCallback async_add_entities)
None|int decide_duration(TadoConnector tado, int|None duration, int zone_id, str|None overlay_mode=None)
def generate_supported_fanmodes(dict[str, str] tado_to_ha_mapping, list[str] options)
str decide_overlay_mode(TadoConnector tado, int|None duration, int zone_id, str|None overlay_mode=None)
Callable[[], None] async_dispatcher_connect(HomeAssistant hass, str signal, Callable[..., Any] target)