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."""
|
"""Initialize of a KNX climate device."""
|
||||||
super().__init__(_create_climate(xknx, config))
|
super().__init__(_create_climate(xknx, config))
|
||||||
self._attr_entity_category = config.get(CONF_ENTITY_CATEGORY)
|
self._attr_entity_category = config.get(CONF_ENTITY_CATEGORY)
|
||||||
self._attr_supported_features = (
|
self._attr_supported_features = ClimateEntityFeature.TARGET_TEMPERATURE
|
||||||
ClimateEntityFeature.TARGET_TEMPERATURE | ClimateEntityFeature.TURN_ON
|
|
||||||
)
|
|
||||||
if self._device.supports_on_off:
|
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:
|
if self.preset_modes:
|
||||||
self._attr_supported_features |= ClimateEntityFeature.PRESET_MODE
|
self._attr_supported_features |= ClimateEntityFeature.PRESET_MODE
|
||||||
self._attr_target_temperature_step = self._device.temperature_step
|
self._attr_target_temperature_step = self._device.temperature_step
|
||||||
|
@ -158,6 +167,8 @@ class KNXClimate(KnxEntity, ClimateEntity):
|
||||||
self.default_hvac_mode: HVACMode = config[
|
self.default_hvac_mode: HVACMode = config[
|
||||||
ClimateSchema.CONF_DEFAULT_CONTROLLER_MODE
|
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
|
@property
|
||||||
def current_temperature(self) -> float | None:
|
def current_temperature(self) -> float | None:
|
||||||
|
@ -181,6 +192,34 @@ class KNXClimate(KnxEntity, ClimateEntity):
|
||||||
temp = self._device.target_temperature_max
|
temp = self._device.target_temperature_max
|
||||||
return temp if temp is not None else super().max_temp
|
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:
|
async def async_set_temperature(self, **kwargs: Any) -> None:
|
||||||
"""Set new target temperature."""
|
"""Set new target temperature."""
|
||||||
temperature = kwargs.get(ATTR_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:
|
if self._device.supports_on_off and not self._device.is_on:
|
||||||
return HVACMode.OFF
|
return HVACMode.OFF
|
||||||
if self._device.mode is not None and self._device.mode.supports_controller_mode:
|
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
|
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
|
return self.default_hvac_mode
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
@ -234,21 +276,23 @@ class KNXClimate(KnxEntity, ClimateEntity):
|
||||||
return None
|
return None
|
||||||
|
|
||||||
async def async_set_hvac_mode(self, hvac_mode: HVACMode) -> None:
|
async def async_set_hvac_mode(self, hvac_mode: HVACMode) -> None:
|
||||||
"""Set operation mode."""
|
"""Set controller mode."""
|
||||||
if self._device.supports_on_off and hvac_mode == HVACMode.OFF:
|
if self._device.mode is not None and self._device.mode.supports_controller_mode:
|
||||||
await self._device.turn_off()
|
knx_controller_mode = HVACControllerMode(
|
||||||
else:
|
CONTROLLER_MODES_INV.get(hvac_mode)
|
||||||
if self._device.supports_on_off and not self._device.is_on:
|
)
|
||||||
await self._device.turn_on()
|
if knx_controller_mode in self._device.mode.controller_modes:
|
||||||
if (
|
|
||||||
self._device.mode is not None
|
|
||||||
and self._device.mode.supports_controller_mode
|
|
||||||
):
|
|
||||||
knx_controller_mode = HVACControllerMode(
|
|
||||||
CONTROLLER_MODES_INV.get(hvac_mode)
|
|
||||||
)
|
|
||||||
await self._device.mode.set_controller_mode(knx_controller_mode)
|
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
|
@property
|
||||||
def preset_mode(self) -> str | None:
|
def preset_mode(self) -> str | None:
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
"""Test KNX climate."""
|
"""Test KNX climate."""
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
from homeassistant.components.climate import PRESET_ECO, PRESET_SLEEP, HVACMode
|
from homeassistant.components.climate import PRESET_ECO, PRESET_SLEEP, HVACMode
|
||||||
from homeassistant.components.knx.schema import ClimateSchema
|
from homeassistant.components.knx.schema import ClimateSchema
|
||||||
from homeassistant.const import CONF_NAME, STATE_IDLE
|
from homeassistant.const import CONF_NAME, STATE_IDLE
|
||||||
|
@ -52,6 +54,94 @@ async def test_climate_basic_temperature_set(
|
||||||
assert len(events) == 1
|
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:
|
async def test_climate_hvac_mode(hass: HomeAssistant, knx: KNXTestKit) -> None:
|
||||||
"""Test KNX climate hvac mode."""
|
"""Test KNX climate hvac mode."""
|
||||||
await knx.setup_integration(
|
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()
|
await hass.async_block_till_done()
|
||||||
# read states state updater
|
# 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.assert_read("1/2/5")
|
||||||
await knx.receive_response("1/2/5", RAW_FLOAT_22_0)
|
await knx.receive_response("1/2/5", RAW_FLOAT_22_0)
|
||||||
|
|
||||||
# turn hvac off
|
# turn hvac mode to off
|
||||||
await hass.services.async_call(
|
await hass.services.async_call(
|
||||||
"climate",
|
"climate",
|
||||||
"set_hvac_mode",
|
"set_hvac_mode",
|
||||||
{"entity_id": "climate.test", "hvac_mode": HVACMode.OFF},
|
{"entity_id": "climate.test", "hvac_mode": HVACMode.OFF},
|
||||||
blocking=True,
|
blocking=True,
|
||||||
)
|
)
|
||||||
await knx.assert_write("1/2/8", False)
|
await knx.assert_write("1/2/6", (0x06,))
|
||||||
|
|
||||||
# turn hvac on
|
# turn hvac on
|
||||||
await hass.services.async_call(
|
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},
|
{"entity_id": "climate.test", "hvac_mode": HVACMode.HEAT},
|
||||||
blocking=True,
|
blocking=True,
|
||||||
)
|
)
|
||||||
await knx.assert_write("1/2/8", True)
|
|
||||||
await knx.assert_write("1/2/6", (0x01,))
|
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", {})
|
assert await async_setup_component(hass, "homeassistant", {})
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
async_capture_events(hass, "state_changed")
|
|
||||||
|
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
# read states state updater
|
# read states state updater
|
||||||
|
|
Loading…
Reference in New Issue