Add fan support for KNX climate entities (#126368)

* Add fan mode support to knx climate

* fix linting errors

* remove unneeded None protection from CONF_FAN_PERCENTAGES_MODES

* Update homeassistant/components/knx/climate.py

Co-authored-by: Matthias Alphart <farmio@alphart.net>

* Update homeassistant/components/knx/climate.py

Co-authored-by: Matthias Alphart <farmio@alphart.net>

* Update homeassistant/components/knx/climate.py

Co-authored-by: Matthias Alphart <farmio@alphart.net>

* Update homeassistant/components/knx/schema.py

Co-authored-by: Matthias Alphart <farmio@alphart.net>

* find closest percentage when not in fan modes

* new field for fan speed mode, max steps apply to both step and percentage

* not picking FAN_OFF when the percentage is closest to zero

* add fan zero mode to support auto mode

* use StrEnum for FanZeroMode

* change default to 'percent'

* fix mypy errors

---------

Co-authored-by: Matthias Alphart <farmio@alphart.net>
This commit is contained in:
Doron Somech 2024-09-24 22:38:09 +03:00 committed by GitHub
parent 69ecdda5f5
commit d2d3ab2d98
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 482 additions and 4 deletions

View File

@ -10,10 +10,15 @@ from xknx.devices import (
ClimateMode as XknxClimateMode, ClimateMode as XknxClimateMode,
Device as XknxDevice, Device as XknxDevice,
) )
from xknx.devices.fan import FanSpeedMode
from xknx.dpt.dpt_20 import HVACControllerMode, HVACOperationMode from xknx.dpt.dpt_20 import HVACControllerMode, HVACOperationMode
from homeassistant import config_entries from homeassistant import config_entries
from homeassistant.components.climate import ( from homeassistant.components.climate import (
FAN_HIGH,
FAN_LOW,
FAN_MEDIUM,
FAN_ON,
ClimateEntity, ClimateEntity,
ClimateEntityFeature, ClimateEntityFeature,
HVACAction, HVACAction,
@ -126,6 +131,11 @@ def _create_climate(xknx: XKNX, config: ConfigType) -> XknxClimate:
min_temp=config.get(ClimateSchema.CONF_MIN_TEMP), min_temp=config.get(ClimateSchema.CONF_MIN_TEMP),
max_temp=config.get(ClimateSchema.CONF_MAX_TEMP), max_temp=config.get(ClimateSchema.CONF_MAX_TEMP),
mode=climate_mode, mode=climate_mode,
group_address_fan_speed=config.get(ClimateSchema.CONF_FAN_SPEED_ADDRESS),
group_address_fan_speed_state=config.get(
ClimateSchema.CONF_FAN_SPEED_STATE_ADDRESS
),
fan_speed_mode=config[ClimateSchema.CONF_FAN_SPEED_MODE],
) )
@ -166,6 +176,36 @@ class KNXClimate(KnxYamlEntity, ClimateEntity):
self._attr_preset_modes = [ self._attr_preset_modes = [
mode.name.lower() for mode in self._device.mode.operation_modes mode.name.lower() for mode in self._device.mode.operation_modes
] ]
fan_max_step = config[ClimateSchema.CONF_FAN_MAX_STEP]
self._fan_modes_percentages = [
int(100 * i / fan_max_step) for i in range(fan_max_step + 1)
]
self.fan_zero_mode: str = config[ClimateSchema.CONF_FAN_ZERO_MODE]
if self._device.fan_speed is not None and self._device.fan_speed.initialized:
self._attr_supported_features |= ClimateEntityFeature.FAN_MODE
if fan_max_step == 3:
self._attr_fan_modes = [
self.fan_zero_mode,
FAN_LOW,
FAN_MEDIUM,
FAN_HIGH,
]
elif fan_max_step == 2:
self._attr_fan_modes = [self.fan_zero_mode, FAN_LOW, FAN_HIGH]
elif fan_max_step == 1:
self._attr_fan_modes = [self.fan_zero_mode, FAN_ON]
elif self._device.fan_speed_mode == FanSpeedMode.STEP:
self._attr_fan_modes = [self.fan_zero_mode] + [
str(i) for i in range(1, fan_max_step + 1)
]
else:
self._attr_fan_modes = [self.fan_zero_mode] + [
f"{percentage}%" for percentage in self._fan_modes_percentages[1:]
]
self._attr_target_temperature_step = self._device.temperature_step self._attr_target_temperature_step = self._device.temperature_step
self._attr_unique_id = ( self._attr_unique_id = (
f"{self._device.temperature.group_address_state}_" f"{self._device.temperature.group_address_state}_"
@ -322,6 +362,41 @@ class KNXClimate(KnxYamlEntity, ClimateEntity):
) )
self.async_write_ha_state() self.async_write_ha_state()
@property
def fan_mode(self) -> str:
"""Return the fan setting."""
fan_speed = self._device.current_fan_speed
if not fan_speed or self._attr_fan_modes is None:
return self.fan_zero_mode
if self._device.fan_speed_mode == FanSpeedMode.STEP:
return self._attr_fan_modes[fan_speed]
# Find the closest fan mode percentage
closest_percentage = min(
self._fan_modes_percentages[1:], # fan_speed == 0 is handled above
key=lambda x: abs(x - fan_speed),
)
return self._attr_fan_modes[
self._fan_modes_percentages.index(closest_percentage)
]
async def async_set_fan_mode(self, fan_mode: str) -> None:
"""Set fan mode."""
if self._attr_fan_modes is None:
return
fan_mode_index = self._attr_fan_modes.index(fan_mode)
if self._device.fan_speed_mode == FanSpeedMode.STEP:
await self._device.set_fan_speed(fan_mode_index)
return
await self._device.set_fan_speed(self._fan_modes_percentages[fan_mode_index])
@property @property
def extra_state_attributes(self) -> dict[str, Any] | None: def extra_state_attributes(self) -> dict[str, Any] | None:
"""Return device specific state attributes.""" """Return device specific state attributes."""

View File

@ -3,13 +3,13 @@
from __future__ import annotations from __future__ import annotations
from collections.abc import Awaitable, Callable from collections.abc import Awaitable, Callable
from enum import Enum from enum import Enum, StrEnum
from typing import TYPE_CHECKING, Final, TypedDict from typing import TYPE_CHECKING, Final, TypedDict
from xknx.dpt.dpt_20 import HVACControllerMode from xknx.dpt.dpt_20 import HVACControllerMode
from xknx.telegram import Telegram from xknx.telegram import Telegram
from homeassistant.components.climate import HVACAction, HVACMode from homeassistant.components.climate import FAN_AUTO, FAN_OFF, HVACAction, HVACMode
from homeassistant.const import Platform from homeassistant.const import Platform
from homeassistant.util.hass_dict import HassKey from homeassistant.util.hass_dict import HassKey
@ -129,6 +129,13 @@ class ColorTempModes(Enum):
RELATIVE = "5.001" RELATIVE = "5.001"
class FanZeroMode(StrEnum):
"""Enum for setting the fan zero mode."""
OFF = FAN_OFF
AUTO = FAN_AUTO
SUPPORTED_PLATFORMS_YAML: Final = { SUPPORTED_PLATFORMS_YAML: Final = {
Platform.BINARY_SENSOR, Platform.BINARY_SENSOR,
Platform.BUTTON, Platform.BUTTON,

View File

@ -7,7 +7,7 @@ from collections import OrderedDict
from typing import ClassVar, Final from typing import ClassVar, Final
import voluptuous as vol import voluptuous as vol
from xknx.devices.climate import SetpointShiftMode from xknx.devices.climate import FanSpeedMode, SetpointShiftMode
from xknx.dpt import DPTBase, DPTNumeric from xknx.dpt import DPTBase, DPTNumeric
from xknx.dpt.dpt_20 import HVACControllerMode, HVACOperationMode from xknx.dpt.dpt_20 import HVACControllerMode, HVACOperationMode
from xknx.exceptions import ConversionError, CouldNotParseTelegram from xknx.exceptions import ConversionError, CouldNotParseTelegram
@ -15,7 +15,7 @@ from xknx.exceptions import ConversionError, CouldNotParseTelegram
from homeassistant.components.binary_sensor import ( from homeassistant.components.binary_sensor import (
DEVICE_CLASSES_SCHEMA as BINARY_SENSOR_DEVICE_CLASSES_SCHEMA, DEVICE_CLASSES_SCHEMA as BINARY_SENSOR_DEVICE_CLASSES_SCHEMA,
) )
from homeassistant.components.climate import HVACMode from homeassistant.components.climate import FAN_OFF, HVACMode
from homeassistant.components.cover import ( from homeassistant.components.cover import (
DEVICE_CLASSES_SCHEMA as COVER_DEVICE_CLASSES_SCHEMA, DEVICE_CLASSES_SCHEMA as COVER_DEVICE_CLASSES_SCHEMA,
) )
@ -54,6 +54,7 @@ from .const import (
CONF_SYNC_STATE, CONF_SYNC_STATE,
KNX_ADDRESS, KNX_ADDRESS,
ColorTempModes, ColorTempModes,
FanZeroMode,
) )
from .validation import ( from .validation import (
backwards_compatible_xknx_climate_enum_member, backwards_compatible_xknx_climate_enum_member,
@ -341,6 +342,11 @@ class ClimateSchema(KNXPlatformSchema):
CONF_ON_OFF_INVERT = "on_off_invert" CONF_ON_OFF_INVERT = "on_off_invert"
CONF_MIN_TEMP = "min_temp" CONF_MIN_TEMP = "min_temp"
CONF_MAX_TEMP = "max_temp" CONF_MAX_TEMP = "max_temp"
CONF_FAN_SPEED_ADDRESS = "fan_speed_address"
CONF_FAN_SPEED_STATE_ADDRESS = "fan_speed_state_address"
CONF_FAN_MAX_STEP = "fan_max_step"
CONF_FAN_SPEED_MODE = "fan_speed_mode"
CONF_FAN_ZERO_MODE = "fan_zero_mode"
DEFAULT_NAME = "KNX Climate" DEFAULT_NAME = "KNX Climate"
DEFAULT_SETPOINT_SHIFT_MODE = "DPT6010" DEFAULT_SETPOINT_SHIFT_MODE = "DPT6010"
@ -348,6 +354,7 @@ class ClimateSchema(KNXPlatformSchema):
DEFAULT_SETPOINT_SHIFT_MIN = -6 DEFAULT_SETPOINT_SHIFT_MIN = -6
DEFAULT_TEMPERATURE_STEP = 0.1 DEFAULT_TEMPERATURE_STEP = 0.1
DEFAULT_ON_OFF_INVERT = False DEFAULT_ON_OFF_INVERT = False
DEFAULT_FAN_SPEED_MODE = "percent"
ENTITY_SCHEMA = vol.All( ENTITY_SCHEMA = vol.All(
# deprecated since September 2020 # deprecated since September 2020
@ -423,6 +430,15 @@ class ClimateSchema(KNXPlatformSchema):
vol.Optional(CONF_MIN_TEMP): vol.Coerce(float), vol.Optional(CONF_MIN_TEMP): vol.Coerce(float),
vol.Optional(CONF_MAX_TEMP): vol.Coerce(float), vol.Optional(CONF_MAX_TEMP): vol.Coerce(float),
vol.Optional(CONF_ENTITY_CATEGORY): ENTITY_CATEGORIES_SCHEMA, vol.Optional(CONF_ENTITY_CATEGORY): ENTITY_CATEGORIES_SCHEMA,
vol.Optional(CONF_FAN_SPEED_ADDRESS): ga_list_validator,
vol.Optional(CONF_FAN_SPEED_STATE_ADDRESS): ga_list_validator,
vol.Optional(CONF_FAN_MAX_STEP, default=3): cv.byte,
vol.Optional(
CONF_FAN_SPEED_MODE, default=DEFAULT_FAN_SPEED_MODE
): vol.All(vol.Upper, cv.enum(FanSpeedMode)),
vol.Optional(CONF_FAN_ZERO_MODE, default=FAN_OFF): vol.Coerce(
FanZeroMode
),
} }
), ),
) )

View File

@ -439,3 +439,383 @@ async def test_command_value_idle_mode(hass: HomeAssistant, knx: KNXTestKit) ->
knx.assert_state( knx.assert_state(
"climate.test", HVACMode.HEAT, command_value=0, hvac_action=STATE_IDLE "climate.test", HVACMode.HEAT, command_value=0, hvac_action=STATE_IDLE
) )
async def test_fan_speed_3_steps(hass: HomeAssistant, knx: KNXTestKit) -> None:
"""Test KNX climate fan speed 3 steps."""
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_FAN_SPEED_ADDRESS: "1/2/6",
ClimateSchema.CONF_FAN_SPEED_STATE_ADDRESS: "1/2/7",
ClimateSchema.CONF_FAN_SPEED_MODE: "step",
ClimateSchema.CONF_FAN_MAX_STEP: 3,
}
}
)
# 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", (0x01,))
knx.assert_state(
"climate.test",
HVACMode.HEAT,
fan_mode="low",
fan_modes=["off", "low", "medium", "high"],
)
# set fan mode
await hass.services.async_call(
"climate",
"set_fan_mode",
{"entity_id": "climate.test", "fan_mode": "medium"},
blocking=True,
)
await knx.assert_write("1/2/6", (0x02,))
knx.assert_state("climate.test", HVACMode.HEAT, fan_mode="medium")
# turn off
await hass.services.async_call(
"climate",
"set_fan_mode",
{"entity_id": "climate.test", "fan_mode": "off"},
blocking=True,
)
await knx.assert_write("1/2/6", (0x0,))
knx.assert_state("climate.test", HVACMode.HEAT, fan_mode="off")
async def test_fan_speed_2_steps(hass: HomeAssistant, knx: KNXTestKit) -> None:
"""Test KNX climate fan speed 2 steps."""
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_FAN_SPEED_ADDRESS: "1/2/6",
ClimateSchema.CONF_FAN_SPEED_STATE_ADDRESS: "1/2/7",
ClimateSchema.CONF_FAN_SPEED_MODE: "step",
ClimateSchema.CONF_FAN_MAX_STEP: 2,
}
}
)
# 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", (0x01,))
knx.assert_state(
"climate.test", HVACMode.HEAT, fan_mode="low", fan_modes=["off", "low", "high"]
)
# set fan mode
await hass.services.async_call(
"climate",
"set_fan_mode",
{"entity_id": "climate.test", "fan_mode": "high"},
blocking=True,
)
await knx.assert_write("1/2/6", (0x02,))
knx.assert_state("climate.test", HVACMode.HEAT, fan_mode="high")
# turn off
await hass.services.async_call(
"climate",
"set_fan_mode",
{"entity_id": "climate.test", "fan_mode": "off"},
blocking=True,
)
await knx.assert_write("1/2/6", (0x0,))
knx.assert_state("climate.test", HVACMode.HEAT, fan_mode="off")
async def test_fan_speed_1_step(hass: HomeAssistant, knx: KNXTestKit) -> None:
"""Test KNX climate fan speed 1 step."""
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_FAN_SPEED_ADDRESS: "1/2/6",
ClimateSchema.CONF_FAN_SPEED_STATE_ADDRESS: "1/2/7",
ClimateSchema.CONF_FAN_SPEED_MODE: "step",
ClimateSchema.CONF_FAN_MAX_STEP: 1,
}
}
)
# 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", (0x01,))
knx.assert_state(
"climate.test", HVACMode.HEAT, fan_mode="on", fan_modes=["off", "on"]
)
# turn off
await hass.services.async_call(
"climate",
"set_fan_mode",
{"entity_id": "climate.test", "fan_mode": "off"},
blocking=True,
)
await knx.assert_write("1/2/6", (0x0,))
knx.assert_state("climate.test", HVACMode.HEAT, fan_mode="off")
async def test_fan_speed_5_steps(hass: HomeAssistant, knx: KNXTestKit) -> None:
"""Test KNX climate fan speed 5 steps."""
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_FAN_SPEED_ADDRESS: "1/2/6",
ClimateSchema.CONF_FAN_SPEED_STATE_ADDRESS: "1/2/7",
ClimateSchema.CONF_FAN_SPEED_MODE: "step",
ClimateSchema.CONF_FAN_MAX_STEP: 5,
}
}
)
# 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", (0x01,))
knx.assert_state(
"climate.test",
HVACMode.HEAT,
fan_mode="1",
fan_modes=["off", "1", "2", "3", "4", "5"],
)
# set fan mode
await hass.services.async_call(
"climate",
"set_fan_mode",
{"entity_id": "climate.test", "fan_mode": "4"},
blocking=True,
)
await knx.assert_write("1/2/6", (0x04,))
knx.assert_state("climate.test", HVACMode.HEAT, fan_mode="4")
# turn off
await hass.services.async_call(
"climate",
"set_fan_mode",
{"entity_id": "climate.test", "fan_mode": "off"},
blocking=True,
)
await knx.assert_write("1/2/6", (0x0,))
knx.assert_state("climate.test", HVACMode.HEAT, fan_mode="off")
async def test_fan_speed_percentage(hass: HomeAssistant, knx: KNXTestKit) -> None:
"""Test KNX climate fan speed percentage."""
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_FAN_SPEED_ADDRESS: "1/2/6",
ClimateSchema.CONF_FAN_SPEED_STATE_ADDRESS: "1/2/7",
ClimateSchema.CONF_FAN_SPEED_MODE: "percent",
}
}
)
# 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", (84,)) # 84 / 255 = 33%
knx.assert_state(
"climate.test",
HVACMode.HEAT,
fan_mode="low",
fan_modes=["off", "low", "medium", "high"],
)
# set fan mode
await hass.services.async_call(
"climate",
"set_fan_mode",
{"entity_id": "climate.test", "fan_mode": "medium"},
blocking=True,
)
await knx.assert_write("1/2/6", (168,)) # 168 / 255 = 66%
knx.assert_state("climate.test", HVACMode.HEAT, fan_mode="medium")
# turn off
await hass.services.async_call(
"climate",
"set_fan_mode",
{"entity_id": "climate.test", "fan_mode": "off"},
blocking=True,
)
await knx.assert_write("1/2/6", (0x0,))
knx.assert_state("climate.test", HVACMode.HEAT, fan_mode="off")
# check fan mode that is not in the fan modes list
await knx.receive_write("1/2/6", (127,)) # 127 / 255 = 50%
knx.assert_state("climate.test", HVACMode.HEAT, fan_mode="medium")
# check FAN_OFF is not picked when fan_speed is closest to zero
await knx.receive_write("1/2/6", (3,))
knx.assert_state("climate.test", HVACMode.HEAT, fan_mode="low")
async def test_fan_speed_percentage_4_steps(
hass: HomeAssistant, knx: KNXTestKit
) -> None:
"""Test KNX climate fan speed percentage with 4 steps."""
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_FAN_SPEED_ADDRESS: "1/2/6",
ClimateSchema.CONF_FAN_SPEED_STATE_ADDRESS: "1/2/7",
ClimateSchema.CONF_FAN_SPEED_MODE: "percent",
ClimateSchema.CONF_FAN_MAX_STEP: 4,
}
}
)
# 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", (64,)) # 64 / 255 = 25%
knx.assert_state(
"climate.test",
HVACMode.HEAT,
fan_mode="25%",
fan_modes=["off", "25%", "50%", "75%", "100%"],
)
# set fan mode
await hass.services.async_call(
"climate",
"set_fan_mode",
{"entity_id": "climate.test", "fan_mode": "50%"},
blocking=True,
)
await knx.assert_write("1/2/6", (128,)) # 128 / 255 = 50%
knx.assert_state("climate.test", HVACMode.HEAT, fan_mode="50%")
# turn off
await hass.services.async_call(
"climate",
"set_fan_mode",
{"entity_id": "climate.test", "fan_mode": "off"},
blocking=True,
)
await knx.assert_write("1/2/6", (0x0,))
knx.assert_state("climate.test", HVACMode.HEAT, fan_mode="off")
# check fan mode that is not in the fan modes list
await knx.receive_write("1/2/6", (168,)) # 168 / 255 = 66%
knx.assert_state("climate.test", HVACMode.HEAT, fan_mode="75%")
async def test_fan_speed_zero_mode_auto(hass: HomeAssistant, knx: KNXTestKit) -> None:
"""Test KNX climate fan speed 3 steps."""
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_FAN_SPEED_ADDRESS: "1/2/6",
ClimateSchema.CONF_FAN_SPEED_STATE_ADDRESS: "1/2/7",
ClimateSchema.CONF_FAN_MAX_STEP: 3,
ClimateSchema.CONF_FAN_SPEED_MODE: "step",
ClimateSchema.CONF_FAN_ZERO_MODE: "auto",
}
}
)
# 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", (0x01,))
knx.assert_state(
"climate.test",
HVACMode.HEAT,
fan_mode="low",
fan_modes=["auto", "low", "medium", "high"],
)
# set auto
await hass.services.async_call(
"climate",
"set_fan_mode",
{"entity_id": "climate.test", "fan_mode": "auto"},
blocking=True,
)
await knx.assert_write("1/2/6", (0x0,))
knx.assert_state("climate.test", HVACMode.HEAT, fan_mode="auto")