diff --git a/homeassistant/components/knx/climate.py b/homeassistant/components/knx/climate.py index 2c0153c5d2b..e3bb63581e7 100644 --- a/homeassistant/components/knx/climate.py +++ b/homeassistant/components/knx/climate.py @@ -19,6 +19,8 @@ from homeassistant.components.climate import ( FAN_LOW, FAN_MEDIUM, FAN_ON, + SWING_OFF, + SWING_ON, ClimateEntity, ClimateEntityFeature, HVACAction, @@ -136,6 +138,14 @@ def _create_climate(xknx: XKNX, config: ConfigType) -> XknxClimate: ClimateSchema.CONF_FAN_SPEED_STATE_ADDRESS ), fan_speed_mode=config[ClimateSchema.CONF_FAN_SPEED_MODE], + group_address_swing=config.get(ClimateSchema.CONF_SWING_ADDRESS), + group_address_swing_state=config.get(ClimateSchema.CONF_SWING_STATE_ADDRESS), + group_address_horizontal_swing=config.get( + ClimateSchema.CONF_SWING_HORIZONTAL_ADDRESS + ), + group_address_horizontal_swing_state=config.get( + ClimateSchema.CONF_SWING_HORIZONTAL_STATE_ADDRESS + ), group_address_humidity_state=config.get( ClimateSchema.CONF_HUMIDITY_STATE_ADDRESS ), @@ -207,6 +217,13 @@ class KNXClimate(KnxYamlEntity, ClimateEntity): self._attr_fan_modes = [self.fan_zero_mode] + [ f"{percentage}%" for percentage in self._fan_modes_percentages[1:] ] + if self._device.swing.initialized: + self._attr_supported_features |= ClimateEntityFeature.SWING_MODE + self._attr_swing_modes = [SWING_ON, SWING_OFF] + + if self._device.horizontal_swing.initialized: + self._attr_supported_features |= ClimateEntityFeature.SWING_HORIZONTAL_MODE + self._attr_swing_horizontal_modes = [SWING_ON, SWING_OFF] self._attr_target_temperature_step = self._device.temperature_step self._attr_unique_id = ( @@ -399,6 +416,28 @@ class KNXClimate(KnxYamlEntity, ClimateEntity): await self._device.set_fan_speed(self._fan_modes_percentages[fan_mode_index]) + async def async_set_swing_mode(self, swing_mode: str) -> None: + """Set the swing setting.""" + await self._device.set_swing(swing_mode == SWING_ON) + + async def async_set_swing_horizontal_mode(self, swing_horizontal_mode: str) -> None: + """Set the horizontal swing setting.""" + await self._device.set_horizontal_swing(swing_horizontal_mode == SWING_ON) + + @property + def swing_mode(self) -> str | None: + """Return the swing setting.""" + if self._device.swing.value is not None: + return SWING_ON if self._device.swing.value else SWING_OFF + return None + + @property + def swing_horizontal_mode(self) -> str | None: + """Return the horizontal swing setting.""" + if self._device.horizontal_swing.value is not None: + return SWING_ON if self._device.horizontal_swing.value else SWING_OFF + return None + @property def current_humidity(self) -> float | None: """Return the current humidity.""" diff --git a/homeassistant/components/knx/schema.py b/homeassistant/components/knx/schema.py index 5c83da58c3a..1ac2b82247c 100644 --- a/homeassistant/components/knx/schema.py +++ b/homeassistant/components/knx/schema.py @@ -339,6 +339,10 @@ class ClimateSchema(KNXPlatformSchema): CONF_FAN_SPEED_MODE = "fan_speed_mode" CONF_FAN_ZERO_MODE = "fan_zero_mode" CONF_HUMIDITY_STATE_ADDRESS = "humidity_state_address" + CONF_SWING_ADDRESS = "swing_address" + CONF_SWING_STATE_ADDRESS = "swing_state_address" + CONF_SWING_HORIZONTAL_ADDRESS = "swing_horizontal_address" + CONF_SWING_HORIZONTAL_STATE_ADDRESS = "swing_horizontal_state_address" DEFAULT_NAME = "KNX Climate" DEFAULT_SETPOINT_SHIFT_MODE = "DPT6010" @@ -427,6 +431,10 @@ class ClimateSchema(KNXPlatformSchema): vol.Optional(CONF_FAN_ZERO_MODE, default=FAN_OFF): vol.Coerce( FanZeroMode ), + vol.Optional(CONF_SWING_ADDRESS): ga_list_validator, + vol.Optional(CONF_SWING_STATE_ADDRESS): ga_list_validator, + vol.Optional(CONF_SWING_HORIZONTAL_ADDRESS): ga_list_validator, + vol.Optional(CONF_SWING_HORIZONTAL_STATE_ADDRESS): ga_list_validator, vol.Optional(CONF_HUMIDITY_STATE_ADDRESS): ga_list_validator, } ), diff --git a/tests/components/knx/test_climate.py b/tests/components/knx/test_climate.py index 8fb348f1724..b5a90428ef2 100644 --- a/tests/components/knx/test_climate.py +++ b/tests/components/knx/test_climate.py @@ -850,3 +850,91 @@ async def test_climate_humidity(hass: HomeAssistant, knx: KNXTestKit) -> None: HVACMode.HEAT, current_humidity=45.6, ) + + +async def test_swing(hass: HomeAssistant, knx: KNXTestKit) -> None: + """Test KNX climate swing.""" + await knx.setup_integration( + { + ClimateSchema.PLATFORM: { + CONF_NAME: "test", + ClimateSchema.CONF_TEMPERATURE_ADDRESS: "1/2/3", + ClimateSchema.CONF_TARGET_TEMPERATURE_ADDRESS: "1/2/4", + ClimateSchema.CONF_TARGET_TEMPERATURE_STATE_ADDRESS: "1/2/5", + ClimateSchema.CONF_SWING_ADDRESS: "1/2/6", + ClimateSchema.CONF_SWING_STATE_ADDRESS: "1/2/7", + } + } + ) + + # read states state updater + await knx.assert_read("1/2/3") + await knx.assert_read("1/2/5") + + # StateUpdater initialize state + await knx.receive_response("1/2/5", RAW_FLOAT_22_0) + await knx.receive_response("1/2/3", RAW_FLOAT_21_0) + + # Query status + await knx.assert_read("1/2/7") + await knx.receive_response("1/2/7", True) + knx.assert_state( + "climate.test", + HVACMode.HEAT, + swing_mode="on", + swing_modes=["on", "off"], + ) + + # turn off + await hass.services.async_call( + "climate", + "set_swing_mode", + {"entity_id": "climate.test", "swing_mode": "off"}, + blocking=True, + ) + await knx.assert_write("1/2/6", False) + knx.assert_state("climate.test", HVACMode.HEAT, swing_mode="off") + + +async def test_horizontal_swing(hass: HomeAssistant, knx: KNXTestKit) -> None: + """Test KNX climate horizontal swing.""" + await knx.setup_integration( + { + ClimateSchema.PLATFORM: { + CONF_NAME: "test", + ClimateSchema.CONF_TEMPERATURE_ADDRESS: "1/2/3", + ClimateSchema.CONF_TARGET_TEMPERATURE_ADDRESS: "1/2/4", + ClimateSchema.CONF_TARGET_TEMPERATURE_STATE_ADDRESS: "1/2/5", + ClimateSchema.CONF_SWING_HORIZONTAL_ADDRESS: "1/2/6", + ClimateSchema.CONF_SWING_HORIZONTAL_STATE_ADDRESS: "1/2/7", + } + } + ) + + # read states state updater + await knx.assert_read("1/2/3") + await knx.assert_read("1/2/5") + + # StateUpdater initialize state + await knx.receive_response("1/2/5", RAW_FLOAT_22_0) + await knx.receive_response("1/2/3", RAW_FLOAT_21_0) + + # Query status + await knx.assert_read("1/2/7") + await knx.receive_response("1/2/7", True) + knx.assert_state( + "climate.test", + HVACMode.HEAT, + swing_horizontal_mode="on", + swing_horizontal_modes=["on", "off"], + ) + + # turn off + await hass.services.async_call( + "climate", + "set_swing_horizontal_mode", + {"entity_id": "climate.test", "swing_horizontal_mode": "off"}, + blocking=True, + ) + await knx.assert_write("1/2/6", False) + knx.assert_state("climate.test", HVACMode.HEAT, swing_horizontal_mode="off")