1 """Support for KNX/IP climate devices."""
3 from __future__
import annotations
8 from xknx.devices
import (
9 Climate
as XknxClimate,
10 ClimateMode
as XknxClimateMode,
13 from xknx.devices.fan
import FanSpeedMode
14 from xknx.dpt.dpt_20
import HVACControllerMode, HVACOperationMode
16 from homeassistant
import config_entries
38 from .
import KNXModule
39 from .const
import CONTROLLER_MODES, CURRENT_HVAC_ACTIONS, KNX_MODULE_KEY
40 from .entity
import KnxYamlEntity
41 from .schema
import ClimateSchema
43 ATTR_COMMAND_VALUE =
"command_value"
44 CONTROLLER_MODES_INV = {value: key
for key, value
in CONTROLLER_MODES.items()}
50 async_add_entities: AddEntitiesCallback,
52 """Set up climate(s) for KNX platform."""
53 knx_module = hass.data[KNX_MODULE_KEY]
54 config: list[ConfigType] = knx_module.config_yaml[Platform.CLIMATE]
57 KNXClimate(knx_module, entity_config)
for entity_config
in config
62 """Return a KNX Climate device to be used within XKNX."""
63 climate_mode = XknxClimateMode(
65 name=f
"{config[CONF_NAME]} Mode",
66 group_address_operation_mode=config.get(
67 ClimateSchema.CONF_OPERATION_MODE_ADDRESS
69 group_address_operation_mode_state=config.get(
70 ClimateSchema.CONF_OPERATION_MODE_STATE_ADDRESS
72 group_address_controller_status=config.get(
73 ClimateSchema.CONF_CONTROLLER_STATUS_ADDRESS
75 group_address_controller_status_state=config.get(
76 ClimateSchema.CONF_CONTROLLER_STATUS_STATE_ADDRESS
78 group_address_controller_mode=config.get(
79 ClimateSchema.CONF_CONTROLLER_MODE_ADDRESS
81 group_address_controller_mode_state=config.get(
82 ClimateSchema.CONF_CONTROLLER_MODE_STATE_ADDRESS
84 group_address_operation_mode_protection=config.get(
85 ClimateSchema.CONF_OPERATION_MODE_FROST_PROTECTION_ADDRESS
87 group_address_operation_mode_economy=config.get(
88 ClimateSchema.CONF_OPERATION_MODE_NIGHT_ADDRESS
90 group_address_operation_mode_comfort=config.get(
91 ClimateSchema.CONF_OPERATION_MODE_COMFORT_ADDRESS
93 group_address_operation_mode_standby=config.get(
94 ClimateSchema.CONF_OPERATION_MODE_STANDBY_ADDRESS
96 group_address_heat_cool=config.get(ClimateSchema.CONF_HEAT_COOL_ADDRESS),
97 group_address_heat_cool_state=config.get(
98 ClimateSchema.CONF_HEAT_COOL_STATE_ADDRESS
100 operation_modes=config.get(ClimateSchema.CONF_OPERATION_MODES),
101 controller_modes=config.get(ClimateSchema.CONF_CONTROLLER_MODES),
106 name=config[CONF_NAME],
107 group_address_temperature=config[ClimateSchema.CONF_TEMPERATURE_ADDRESS],
108 group_address_target_temperature=config.get(
109 ClimateSchema.CONF_TARGET_TEMPERATURE_ADDRESS
111 group_address_target_temperature_state=config[
112 ClimateSchema.CONF_TARGET_TEMPERATURE_STATE_ADDRESS
114 group_address_setpoint_shift=config.get(
115 ClimateSchema.CONF_SETPOINT_SHIFT_ADDRESS
117 group_address_setpoint_shift_state=config.get(
118 ClimateSchema.CONF_SETPOINT_SHIFT_STATE_ADDRESS
120 setpoint_shift_mode=config.get(ClimateSchema.CONF_SETPOINT_SHIFT_MODE),
121 setpoint_shift_max=config[ClimateSchema.CONF_SETPOINT_SHIFT_MAX],
122 setpoint_shift_min=config[ClimateSchema.CONF_SETPOINT_SHIFT_MIN],
123 temperature_step=config[ClimateSchema.CONF_TEMPERATURE_STEP],
124 group_address_on_off=config.get(ClimateSchema.CONF_ON_OFF_ADDRESS),
125 group_address_on_off_state=config.get(ClimateSchema.CONF_ON_OFF_STATE_ADDRESS),
126 on_off_invert=config[ClimateSchema.CONF_ON_OFF_INVERT],
127 group_address_active_state=config.get(ClimateSchema.CONF_ACTIVE_STATE_ADDRESS),
128 group_address_command_value_state=config.get(
129 ClimateSchema.CONF_COMMAND_VALUE_STATE_ADDRESS
131 min_temp=config.get(ClimateSchema.CONF_MIN_TEMP),
132 max_temp=config.get(ClimateSchema.CONF_MAX_TEMP),
134 group_address_fan_speed=config.get(ClimateSchema.CONF_FAN_SPEED_ADDRESS),
135 group_address_fan_speed_state=config.get(
136 ClimateSchema.CONF_FAN_SPEED_STATE_ADDRESS
138 fan_speed_mode=config[ClimateSchema.CONF_FAN_SPEED_MODE],
139 group_address_humidity_state=config.get(
140 ClimateSchema.CONF_HUMIDITY_STATE_ADDRESS
146 """Representation of a KNX climate device."""
149 _attr_temperature_unit = UnitOfTemperature.CELSIUS
150 _attr_translation_key =
"knx_climate"
151 _enable_turn_on_off_backwards_compatibility =
False
153 def __init__(self, knx_module: KNXModule, config: ConfigType) ->
None:
154 """Initialize of a KNX climate device."""
156 knx_module=knx_module,
161 if self.
_device_device.supports_on_off:
163 ClimateEntityFeature.TURN_OFF | ClimateEntityFeature.TURN_ON
166 self.
_device_device.mode
is not None
167 and len(self.
_device_device.mode.controller_modes) >= 2
168 and HVACControllerMode.OFF
in self.
_device_device.mode.controller_modes
171 ClimateEntityFeature.TURN_OFF | ClimateEntityFeature.TURN_ON
175 self.
_device_device.mode
is not None
176 and self.
_device_device.mode.operation_modes
180 mode.name.lower()
for mode
in self.
_device_device.mode.operation_modes
183 fan_max_step = config[ClimateSchema.CONF_FAN_MAX_STEP]
185 int(100 * i / fan_max_step)
for i
in range(fan_max_step + 1)
187 self.fan_zero_mode: str = config[ClimateSchema.CONF_FAN_ZERO_MODE]
189 if self.
_device_device.fan_speed
is not None and self.
_device_device.fan_speed.initialized:
192 if fan_max_step == 3:
199 elif fan_max_step == 2:
200 self.
_attr_fan_modes_attr_fan_modes = [self.fan_zero_mode, FAN_LOW, FAN_HIGH]
201 elif fan_max_step == 1:
203 elif self.
_device_device.fan_speed_mode == FanSpeedMode.STEP:
205 str(i)
for i
in range(1, fan_max_step + 1)
214 f
"{self._device.temperature.group_address_state}_"
215 f
"{self._device.target_temperature.group_address_state}_"
216 f
"{self._device.target_temperature.group_address}_"
217 f
"{self._device._setpoint_shift.group_address}"
219 self.default_hvac_mode: HVACMode = config[
220 ClimateSchema.CONF_DEFAULT_CONTROLLER_MODE
227 """Return the current temperature."""
228 return self.
_device_device.temperature.value
232 """Return the temperature we try to reach."""
233 return self.
_device_device.target_temperature.value
237 """Return the minimum temperature."""
238 temp = self.
_device_device.target_temperature_min
239 return temp
if temp
is not None else super().min_temp
243 """Return the maximum temperature."""
244 temp = self.
_device_device.target_temperature_max
245 return temp
if temp
is not None else super().max_temp
248 """Turn the entity on."""
249 if self.
_device_device.supports_on_off:
255 self.
_device_device.mode
is not None
256 and self.
_device_device.mode.supports_controller_mode
257 and (knx_controller_mode := CONTROLLER_MODES_INV.get(self.
_last_hvac_mode_last_hvac_mode))
260 await self.
_device_device.mode.set_controller_mode(knx_controller_mode)
264 """Turn the entity off."""
265 if self.
_device_device.supports_on_off:
271 self.
_device_device.mode
is not None
272 and HVACControllerMode.OFF
in self.
_device_device.mode.controller_modes
274 await self.
_device_device.mode.set_controller_mode(HVACControllerMode.OFF)
278 """Set new target temperature."""
279 temperature = kwargs.get(ATTR_TEMPERATURE)
280 if temperature
is not None:
281 await self.
_device_device.set_target_temperature(temperature)
286 """Return current operation ie. heat, cool, idle."""
287 if self.
_device_device.supports_on_off
and not self.
_device_device.is_on:
289 if self.
_device_device.mode
is not None and self.
_device_device.mode.supports_controller_mode:
290 return CONTROLLER_MODES.get(
291 self.
_device_device.mode.controller_mode, self.default_hvac_mode
293 return self.default_hvac_mode
297 """Return the list of available operation/controller modes."""
298 ha_controller_modes: list[HVACMode |
None] = []
299 if self.
_device_device.mode
is not None:
300 ha_controller_modes.extend(
301 CONTROLLER_MODES.get(knx_controller_mode)
302 for knx_controller_mode
in self.
_device_device.mode.controller_modes
305 if self.
_device_device.supports_on_off:
306 if not ha_controller_modes:
308 ha_controller_modes.append(HVACMode.OFF)
310 hvac_modes =
list(set(filter(
None, ha_controller_modes)))
319 """Return the current running hvac operation if supported.
321 Need to be one of CURRENT_HVAC_*.
323 if self.
_device_device.supports_on_off
and not self.
_device_device.is_on:
324 return HVACAction.OFF
325 if self.
_device_device.is_active
is False:
326 return HVACAction.IDLE
328 self.
_device_device.mode
is not None and self.
_device_device.mode.supports_controller_mode
329 )
or self.
_device_device.is_active:
334 """Set controller mode."""
335 if self.
_device_device.mode
is not None and self.
_device_device.mode.supports_controller_mode:
336 knx_controller_mode = CONTROLLER_MODES_INV.get(hvac_mode)
337 if knx_controller_mode
in self.
_device_device.mode.controller_modes:
338 await self.
_device_device.mode.set_controller_mode(knx_controller_mode)
340 if self.
_device_device.supports_on_off:
341 if hvac_mode == HVACMode.OFF:
343 elif not self.
_device_device.is_on:
349 """Return the current preset mode, e.g., home, away, temp.
351 Requires ClimateEntityFeature.PRESET_MODE.
353 if self.
_device_device.mode
is not None and self.
_device_device.mode.supports_operation_mode:
354 return self.
_device_device.mode.operation_mode.name.lower()
358 """Set new preset mode."""
360 self.
_device_device.mode
is not None
361 and self.
_device_device.mode.operation_modes
363 await self.
_device_device.mode.set_operation_mode(
364 HVACOperationMode[preset_mode.upper()]
370 """Return the fan setting."""
372 fan_speed = self.
_device_device.current_fan_speed
375 return self.fan_zero_mode
377 if self.
_device_device.fan_speed_mode == FanSpeedMode.STEP:
381 closest_percentage =
min(
383 key=
lambda x: abs(x - fan_speed),
397 if self.
_device_device.fan_speed_mode == FanSpeedMode.STEP:
398 await self.
_device_device.set_fan_speed(fan_mode_index)
405 """Return the current humidity."""
406 return self.
_device_device.humidity.value
410 """Return device specific state attributes."""
411 attr: dict[str, Any] = {}
413 if self.
_device_device.command_value.initialized:
414 attr[ATTR_COMMAND_VALUE] = self.
_device_device.command_value.value
418 """Store register state change callback and start device object."""
420 if self.
_device_device.mode
is not None:
422 self.
_device_device.mode.xknx.devices.async_add(self.
_device_device.mode)
425 """Disconnect device object when removed."""
426 if self.
_device_device.mode
is not None:
428 self.
_device_device.mode.xknx.devices.async_remove(self.
_device_device.mode)
432 """Call after device was updated."""
433 if self.
_device_device.mode
is not None and self.
_device_device.mode.supports_controller_mode:
434 hvac_mode = CONTROLLER_MODES.get(
435 self.
_device_device.mode.controller_mode, self.default_hvac_mode
437 if hvac_mode
is not HVACMode.OFF:
HVACMode|None hvac_mode(self)
float|None current_humidity(self)
None __init__(self, KNXModule knx_module, ConfigType config)
list[HVACMode] hvac_modes(self)
None async_set_preset_mode(self, str preset_mode)
None async_set_fan_mode(self, str fan_mode)
None async_will_remove_from_hass(self)
None async_set_hvac_mode(self, HVACMode hvac_mode)
None async_added_to_hass(self)
float|None current_temperature(self)
str|None preset_mode(self)
dict[str, Any]|None extra_state_attributes(self)
None async_turn_off(self)
None after_update_callback(self, XknxDevice _device)
HVACAction|None hvac_action(self)
_attr_target_temperature_step
None async_set_temperature(self, **Any kwargs)
float|None target_temperature(self)
None after_update_callback(self, XknxDevice _device)
None async_write_ha_state(self)
XknxClimate _create_climate(XKNX xknx, ConfigType config)
None async_setup_entry(HomeAssistant hass, config_entries.ConfigEntry config_entry, AddEntitiesCallback async_add_entities)