Add `async_turn_on/off` methods for KNX climate entities (#117882)

Add async_turn_on/off methods for KNX climate entities
pull/116734/head^2
Matthias Alphart 2024-05-21 23:54:43 +02:00 committed by GitHub
parent c2b3bf3fb9
commit b94735a445
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 155 additions and 24 deletions

View File

@ -141,11 +141,20 @@ class KNXClimate(KnxEntity, ClimateEntity):
"""Initialize of a KNX climate device."""
super().__init__(_create_climate(xknx, config))
self._attr_entity_category = config.get(CONF_ENTITY_CATEGORY)
self._attr_supported_features = (
ClimateEntityFeature.TARGET_TEMPERATURE | ClimateEntityFeature.TURN_ON
)
self._attr_supported_features = ClimateEntityFeature.TARGET_TEMPERATURE
if self._device.supports_on_off:
self._attr_supported_features |= ClimateEntityFeature.TURN_OFF
self._attr_supported_features |= (
ClimateEntityFeature.TURN_OFF | ClimateEntityFeature.TURN_ON
)
if (
self._device.mode is not None
and len(self._device.mode.controller_modes) >= 2
and HVACControllerMode.OFF in self._device.mode.controller_modes
):
self._attr_supported_features |= (
ClimateEntityFeature.TURN_OFF | ClimateEntityFeature.TURN_ON
)
if self.preset_modes:
self._attr_supported_features |= ClimateEntityFeature.PRESET_MODE
self._attr_target_temperature_step = self._device.temperature_step
@ -158,6 +167,8 @@ class KNXClimate(KnxEntity, ClimateEntity):
self.default_hvac_mode: HVACMode = config[
ClimateSchema.CONF_DEFAULT_CONTROLLER_MODE
]
# non-OFF HVAC mode to be used when turning on the device without on_off address
self._last_hvac_mode: HVACMode = self.default_hvac_mode
@property
def current_temperature(self) -> float | None:
@ -181,6 +192,34 @@ class KNXClimate(KnxEntity, ClimateEntity):
temp = self._device.target_temperature_max
return temp if temp is not None else super().max_temp
async def async_turn_on(self) -> None:
"""Turn the entity on."""
if self._device.supports_on_off:
await self._device.turn_on()
self.async_write_ha_state()
return
if self._device.mode is not None and self._device.mode.supports_controller_mode:
knx_controller_mode = HVACControllerMode(
CONTROLLER_MODES_INV.get(self._last_hvac_mode)
)
await self._device.mode.set_controller_mode(knx_controller_mode)
self.async_write_ha_state()
async def async_turn_off(self) -> None:
"""Turn the entity off."""
if self._device.supports_on_off:
await self._device.turn_off()
self.async_write_ha_state()
return
if (
self._device.mode is not None
and HVACControllerMode.OFF in self._device.mode.controller_modes
):
await self._device.mode.set_controller_mode(HVACControllerMode.OFF)
self.async_write_ha_state()
async def async_set_temperature(self, **kwargs: Any) -> None:
"""Set new target temperature."""
temperature = kwargs.get(ATTR_TEMPERATURE)
@ -194,9 +233,12 @@ class KNXClimate(KnxEntity, ClimateEntity):
if self._device.supports_on_off and not self._device.is_on:
return HVACMode.OFF
if self._device.mode is not None and self._device.mode.supports_controller_mode:
return CONTROLLER_MODES.get(
hvac_mode = CONTROLLER_MODES.get(
self._device.mode.controller_mode.value, self.default_hvac_mode
)
if hvac_mode is not HVACMode.OFF:
self._last_hvac_mode = hvac_mode
return hvac_mode
return self.default_hvac_mode
@property
@ -234,21 +276,23 @@ class KNXClimate(KnxEntity, ClimateEntity):
return None
async def async_set_hvac_mode(self, hvac_mode: HVACMode) -> None:
"""Set operation mode."""
if self._device.supports_on_off and hvac_mode == HVACMode.OFF:
await self._device.turn_off()
else:
if self._device.supports_on_off and not self._device.is_on:
await self._device.turn_on()
if (
self._device.mode is not None
and self._device.mode.supports_controller_mode
):
knx_controller_mode = HVACControllerMode(
CONTROLLER_MODES_INV.get(hvac_mode)
)
"""Set controller mode."""
if self._device.mode is not None and self._device.mode.supports_controller_mode:
knx_controller_mode = HVACControllerMode(
CONTROLLER_MODES_INV.get(hvac_mode)
)
if knx_controller_mode in self._device.mode.controller_modes:
await self._device.mode.set_controller_mode(knx_controller_mode)
self.async_write_ha_state()
self.async_write_ha_state()
return
if self._device.supports_on_off:
if hvac_mode == HVACMode.OFF:
await self._device.turn_off()
elif not self._device.is_on:
# for default hvac mode, otherwise above would have triggered
await self._device.turn_on()
self.async_write_ha_state()
@property
def preset_mode(self) -> str | None:

View File

@ -1,5 +1,7 @@
"""Test KNX climate."""
import pytest
from homeassistant.components.climate import PRESET_ECO, PRESET_SLEEP, HVACMode
from homeassistant.components.knx.schema import ClimateSchema
from homeassistant.const import CONF_NAME, STATE_IDLE
@ -52,6 +54,94 @@ async def test_climate_basic_temperature_set(
assert len(events) == 1
@pytest.mark.parametrize("heat_cool", [False, True])
async def test_climate_on_off(
hass: HomeAssistant, knx: KNXTestKit, heat_cool: bool
) -> None:
"""Test KNX climate on/off."""
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_ON_OFF_ADDRESS: "1/2/8",
ClimateSchema.CONF_ON_OFF_STATE_ADDRESS: "1/2/9",
}
| (
{
ClimateSchema.CONF_HEAT_COOL_ADDRESS: "1/2/10",
ClimateSchema.CONF_HEAT_COOL_STATE_ADDRESS: "1/2/11",
}
if heat_cool
else {}
)
}
)
await hass.async_block_till_done()
# read heat/cool state
if heat_cool:
await knx.assert_read("1/2/11")
await knx.receive_response("1/2/11", 0) # cool
# read temperature state
await knx.assert_read("1/2/3")
await knx.receive_response("1/2/3", RAW_FLOAT_20_0)
# read target temperature state
await knx.assert_read("1/2/5")
await knx.receive_response("1/2/5", RAW_FLOAT_22_0)
# read on/off state
await knx.assert_read("1/2/9")
await knx.receive_response("1/2/9", 1)
# turn off
await hass.services.async_call(
"climate",
"turn_off",
{"entity_id": "climate.test"},
blocking=True,
)
await knx.assert_write("1/2/8", 0)
assert hass.states.get("climate.test").state == "off"
# turn on
await hass.services.async_call(
"climate",
"turn_on",
{"entity_id": "climate.test"},
blocking=True,
)
await knx.assert_write("1/2/8", 1)
if heat_cool:
# does not fall back to default hvac mode after turn_on
assert hass.states.get("climate.test").state == "cool"
else:
assert hass.states.get("climate.test").state == "heat"
# set hvac mode to off triggers turn_off if no controller_mode is available
await hass.services.async_call(
"climate",
"set_hvac_mode",
{"entity_id": "climate.test", "hvac_mode": HVACMode.OFF},
blocking=True,
)
await knx.assert_write("1/2/8", 0)
# set hvac mode to heat
await hass.services.async_call(
"climate",
"set_hvac_mode",
{"entity_id": "climate.test", "hvac_mode": HVACMode.HEAT},
blocking=True,
)
if heat_cool:
# only set new hvac_mode without changing on/off - actuator shall handle that
await knx.assert_write("1/2/10", 1)
else:
await knx.assert_write("1/2/8", 1)
async def test_climate_hvac_mode(hass: HomeAssistant, knx: KNXTestKit) -> None:
"""Test KNX climate hvac mode."""
await knx.setup_integration(
@ -68,7 +158,6 @@ async def test_climate_hvac_mode(hass: HomeAssistant, knx: KNXTestKit) -> None:
}
}
)
async_capture_events(hass, "state_changed")
await hass.async_block_till_done()
# read states state updater
@ -82,14 +171,14 @@ async def test_climate_hvac_mode(hass: HomeAssistant, knx: KNXTestKit) -> None:
await knx.assert_read("1/2/5")
await knx.receive_response("1/2/5", RAW_FLOAT_22_0)
# turn hvac off
# turn hvac mode to off
await hass.services.async_call(
"climate",
"set_hvac_mode",
{"entity_id": "climate.test", "hvac_mode": HVACMode.OFF},
blocking=True,
)
await knx.assert_write("1/2/8", False)
await knx.assert_write("1/2/6", (0x06,))
# turn hvac on
await hass.services.async_call(
@ -98,7 +187,6 @@ async def test_climate_hvac_mode(hass: HomeAssistant, knx: KNXTestKit) -> None:
{"entity_id": "climate.test", "hvac_mode": HVACMode.HEAT},
blocking=True,
)
await knx.assert_write("1/2/8", True)
await knx.assert_write("1/2/6", (0x01,))
@ -182,7 +270,6 @@ async def test_update_entity(hass: HomeAssistant, knx: KNXTestKit) -> None:
)
assert await async_setup_component(hass, "homeassistant", {})
await hass.async_block_till_done()
async_capture_events(hass, "state_changed")
await hass.async_block_till_done()
# read states state updater