Add `async_turn_on/off` methods for KNX climate entities (#117882)
Add async_turn_on/off methods for KNX climate entitiespull/116734/head^2
parent
c2b3bf3fb9
commit
b94735a445
|
@ -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:
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue