1 """Support for ESPHome climate devices."""
3 from __future__
import annotations
5 from functools
import partial
6 from typing
import Any, cast
8 from aioesphomeapi
import (
21 ATTR_TARGET_TEMP_HIGH,
60 convert_api_error_ha_error,
61 esphome_float_state_property,
62 esphome_state_property,
63 platform_async_setup_entry,
65 from .enum_mapper
import EsphomeEnumMapper
72 ClimateMode.OFF: HVACMode.OFF,
73 ClimateMode.HEAT_COOL: HVACMode.HEAT_COOL,
74 ClimateMode.COOL: HVACMode.COOL,
75 ClimateMode.HEAT: HVACMode.HEAT,
76 ClimateMode.FAN_ONLY: HVACMode.FAN_ONLY,
77 ClimateMode.DRY: HVACMode.DRY,
78 ClimateMode.AUTO: HVACMode.AUTO,
81 _CLIMATE_ACTIONS: EsphomeEnumMapper[ClimateAction, HVACAction] =
EsphomeEnumMapper(
83 ClimateAction.OFF: HVACAction.OFF,
84 ClimateAction.COOLING: HVACAction.COOLING,
85 ClimateAction.HEATING: HVACAction.HEATING,
86 ClimateAction.IDLE: HVACAction.IDLE,
87 ClimateAction.DRYING: HVACAction.DRYING,
88 ClimateAction.FAN: HVACAction.FAN,
93 ClimateFanMode.ON: FAN_ON,
94 ClimateFanMode.OFF: FAN_OFF,
95 ClimateFanMode.AUTO: FAN_AUTO,
96 ClimateFanMode.LOW: FAN_LOW,
97 ClimateFanMode.MEDIUM: FAN_MEDIUM,
98 ClimateFanMode.HIGH: FAN_HIGH,
99 ClimateFanMode.MIDDLE: FAN_MIDDLE,
100 ClimateFanMode.FOCUS: FAN_FOCUS,
101 ClimateFanMode.DIFFUSE: FAN_DIFFUSE,
102 ClimateFanMode.QUIET: FAN_QUIET,
107 ClimateSwingMode.OFF: SWING_OFF,
108 ClimateSwingMode.BOTH: SWING_BOTH,
109 ClimateSwingMode.VERTICAL: SWING_VERTICAL,
110 ClimateSwingMode.HORIZONTAL: SWING_HORIZONTAL,
115 ClimatePreset.NONE: PRESET_NONE,
116 ClimatePreset.HOME: PRESET_HOME,
117 ClimatePreset.AWAY: PRESET_AWAY,
118 ClimatePreset.BOOST: PRESET_BOOST,
119 ClimatePreset.COMFORT: PRESET_COMFORT,
120 ClimatePreset.ECO: PRESET_ECO,
121 ClimatePreset.SLEEP: PRESET_SLEEP,
122 ClimatePreset.ACTIVITY: PRESET_ACTIVITY,
128 """A climate implementation for ESPHome."""
130 _attr_temperature_unit = UnitOfTemperature.CELSIUS
131 _attr_translation_key =
"climate"
132 _enable_turn_on_off_backwards_compatibility =
False
136 """Set attrs from static info."""
141 _CLIMATE_MODES.from_esphome(mode)
for mode
in static_info.supported_modes
144 _FAN_MODES.from_esphome(mode)
for mode
in static_info.supported_fan_modes
145 ] + static_info.supported_custom_fan_modes
147 _PRESETS.from_esphome(preset)
148 for preset
in static_info.supported_presets_compat(self.
_api_version_api_version)
149 ] + static_info.supported_custom_presets
151 _SWING_MODES.from_esphome(mode)
152 for mode
in static_info.supported_swing_modes
156 static_info.visual_target_temperature_step, 1
163 if static_info.supports_two_point_target_temperature:
164 features |= ClimateEntityFeature.TARGET_TEMPERATURE_RANGE
166 features |= ClimateEntityFeature.TARGET_TEMPERATURE
167 if static_info.supports_target_humidity:
168 features |= ClimateEntityFeature.TARGET_HUMIDITY
170 features |= ClimateEntityFeature.PRESET_MODE
172 features |= ClimateEntityFeature.FAN_MODE
174 features |= ClimateEntityFeature.SWING_MODE
176 features |= ClimateEntityFeature.TURN_OFF | ClimateEntityFeature.TURN_ON
180 """Return the precision of the climate device."""
181 precicions = [PRECISION_WHOLE, PRECISION_HALVES, PRECISION_TENTHS]
183 if static_info.visual_current_temperature_step != 0:
184 step = static_info.visual_current_temperature_step
186 step = static_info.visual_target_temperature_step
187 for prec
in precicions:
191 return PRECISION_TENTHS
194 @esphome_state_property
196 """Return current operation ie. heat, cool, idle."""
197 return _CLIMATE_MODES.from_esphome(self.
_state_state.mode)
200 @esphome_state_property
202 """Return current action."""
206 return _CLIMATE_ACTIONS.from_esphome(self.
_state_state.action)
209 @esphome_state_property
211 """Return current fan setting."""
213 return state.custom_fan_mode
or _FAN_MODES.from_esphome(state.fan_mode)
216 @esphome_state_property
218 """Return current preset mode."""
220 return state.custom_preset
or _PRESETS.from_esphome(
225 @esphome_state_property
227 """Return current swing mode."""
228 return _SWING_MODES.from_esphome(self.
_state_state.swing_mode)
231 @esphome_float_state_property
233 """Return the current temperature."""
234 return self.
_state_state.current_temperature
237 @esphome_state_property
239 """Return the current humidity."""
240 if not self.
_static_info_static_info.supports_current_humidity:
242 return round(self.
_state_state.current_humidity)
245 @esphome_float_state_property
247 """Return the temperature we try to reach."""
248 return self.
_state_state.target_temperature
251 @esphome_float_state_property
253 """Return the lowbound target temperature we try to reach."""
254 return self.
_state_state.target_temperature_low
257 @esphome_float_state_property
259 """Return the highbound target temperature we try to reach."""
260 return self.
_state_state.target_temperature_high
263 @esphome_state_property
265 """Return the humidity we try to reach."""
266 return round(self.
_state_state.target_humidity)
268 @convert_api_error_ha_error
270 """Set new target temperature (and operation mode if set)."""
271 data: dict[str, Any] = {
"key": self.
_key_key}
272 if ATTR_HVAC_MODE
in kwargs:
273 data[
"mode"] = _CLIMATE_MODES.from_hass(
274 cast(HVACMode, kwargs[ATTR_HVAC_MODE])
276 if ATTR_TEMPERATURE
in kwargs:
277 data[
"target_temperature"] = kwargs[ATTR_TEMPERATURE]
278 if ATTR_TARGET_TEMP_LOW
in kwargs:
279 data[
"target_temperature_low"] = kwargs[ATTR_TARGET_TEMP_LOW]
280 if ATTR_TARGET_TEMP_HIGH
in kwargs:
281 data[
"target_temperature_high"] = kwargs[ATTR_TARGET_TEMP_HIGH]
282 self.
_client_client.climate_command(**data)
284 @convert_api_error_ha_error
286 """Set new target humidity."""
287 self.
_client_client.climate_command(key=self.
_key_key, target_humidity=humidity)
289 @convert_api_error_ha_error
291 """Set new target operation mode."""
292 self.
_client_client.climate_command(
293 key=self.
_key_key, mode=_CLIMATE_MODES.from_hass(hvac_mode)
296 @convert_api_error_ha_error
298 """Set preset mode."""
299 kwargs: dict[str, Any] = {
"key": self.
_key_key}
300 if preset_mode
in self.
_static_info_static_info.supported_custom_presets:
301 kwargs[
"custom_preset"] = preset_mode
303 kwargs[
"preset"] = _PRESETS.from_hass(preset_mode)
304 self.
_client_client.climate_command(**kwargs)
306 @convert_api_error_ha_error
308 """Set new fan mode."""
309 kwargs: dict[str, Any] = {
"key": self.
_key_key}
310 if fan_mode
in self.
_static_info_static_info.supported_custom_fan_modes:
311 kwargs[
"custom_fan_mode"] = fan_mode
313 kwargs[
"fan_mode"] = _FAN_MODES.from_hass(fan_mode)
314 self.
_client_client.climate_command(**kwargs)
316 @convert_api_error_ha_error
318 """Set new swing mode."""
319 self.
_client_client.climate_command(
320 key=self.
_key_key, swing_mode=_SWING_MODES.from_hass(swing_mode)
324 async_setup_entry = partial(
325 platform_async_setup_entry,
326 info_type=ClimateInfo,
327 entity_type=EsphomeClimateEntity,
328 state_type=ClimateState,
list[str]|None fan_modes(self)
list[str]|None preset_modes(self)
list[str]|None swing_modes(self)
list[HVACMode] hvac_modes(self)
None async_set_humidity(self, int humidity)
int target_humidity(self)
None async_set_temperature(self, **Any kwargs)
str|None preset_mode(self)
_attr_target_temperature_step
None async_set_hvac_mode(self, HVACMode hvac_mode)
None _on_static_info_update(self, EntityInfo static_info)
float|None target_temperature_high(self)
None async_set_swing_mode(self, str swing_mode)
str|None swing_mode(self)
float|None current_temperature(self)
float|None target_temperature_low(self)
None async_set_preset_mode(self, str preset_mode)
HVACAction|None hvac_action(self)
float _get_precision(self)
int|None current_humidity(self)
None async_set_fan_mode(self, str fan_mode)
float|None target_temperature(self)