Skip to content

Commit

Permalink
Add swing support for KNX climate entities (#136752)
Browse files Browse the repository at this point in the history
* added swing to knx climate

* added tests for climate swing

* removed type ignores

* removed unreachable code
  • Loading branch information
richardpolzer authored Jan 28, 2025
1 parent 814e98f commit 77d9309
Show file tree
Hide file tree
Showing 3 changed files with 135 additions and 0 deletions.
39 changes: 39 additions & 0 deletions homeassistant/components/knx/climate.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@
FAN_LOW,
FAN_MEDIUM,
FAN_ON,
SWING_OFF,
SWING_ON,
ClimateEntity,
ClimateEntityFeature,
HVACAction,
Expand Down Expand Up @@ -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
),
Expand Down Expand Up @@ -207,6 +217,13 @@ def __init__(self, knx_module: KNXModule, config: ConfigType) -> None:
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 = (
Expand Down Expand Up @@ -399,6 +416,28 @@ async def async_set_fan_mode(self, fan_mode: str) -> None:

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."""
Expand Down
8 changes: 8 additions & 0 deletions homeassistant/components/knx/schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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,
}
),
Expand Down
88 changes: 88 additions & 0 deletions tests/components/knx/test_climate.py
Original file line number Diff line number Diff line change
Expand Up @@ -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")

0 comments on commit 77d9309

Please sign in to comment.