mirror of
https://github.com/home-assistant/core.git
synced 2025-07-14 16:57:10 +00:00
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:
parent
69ecdda5f5
commit
d2d3ab2d98
@ -10,10 +10,15 @@ from xknx.devices import (
|
||||
ClimateMode as XknxClimateMode,
|
||||
Device as XknxDevice,
|
||||
)
|
||||
from xknx.devices.fan import FanSpeedMode
|
||||
from xknx.dpt.dpt_20 import HVACControllerMode, HVACOperationMode
|
||||
|
||||
from homeassistant import config_entries
|
||||
from homeassistant.components.climate import (
|
||||
FAN_HIGH,
|
||||
FAN_LOW,
|
||||
FAN_MEDIUM,
|
||||
FAN_ON,
|
||||
ClimateEntity,
|
||||
ClimateEntityFeature,
|
||||
HVACAction,
|
||||
@ -126,6 +131,11 @@ def _create_climate(xknx: XKNX, config: ConfigType) -> XknxClimate:
|
||||
min_temp=config.get(ClimateSchema.CONF_MIN_TEMP),
|
||||
max_temp=config.get(ClimateSchema.CONF_MAX_TEMP),
|
||||
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 = [
|
||||
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_unique_id = (
|
||||
f"{self._device.temperature.group_address_state}_"
|
||||
@ -322,6 +362,41 @@ class KNXClimate(KnxYamlEntity, ClimateEntity):
|
||||
)
|
||||
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
|
||||
def extra_state_attributes(self) -> dict[str, Any] | None:
|
||||
"""Return device specific state attributes."""
|
||||
|
@ -3,13 +3,13 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from collections.abc import Awaitable, Callable
|
||||
from enum import Enum
|
||||
from enum import Enum, StrEnum
|
||||
from typing import TYPE_CHECKING, Final, TypedDict
|
||||
|
||||
from xknx.dpt.dpt_20 import HVACControllerMode
|
||||
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.util.hass_dict import HassKey
|
||||
|
||||
@ -129,6 +129,13 @@ class ColorTempModes(Enum):
|
||||
RELATIVE = "5.001"
|
||||
|
||||
|
||||
class FanZeroMode(StrEnum):
|
||||
"""Enum for setting the fan zero mode."""
|
||||
|
||||
OFF = FAN_OFF
|
||||
AUTO = FAN_AUTO
|
||||
|
||||
|
||||
SUPPORTED_PLATFORMS_YAML: Final = {
|
||||
Platform.BINARY_SENSOR,
|
||||
Platform.BUTTON,
|
||||
|
@ -7,7 +7,7 @@ from collections import OrderedDict
|
||||
from typing import ClassVar, Final
|
||||
|
||||
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.dpt_20 import HVACControllerMode, HVACOperationMode
|
||||
from xknx.exceptions import ConversionError, CouldNotParseTelegram
|
||||
@ -15,7 +15,7 @@ from xknx.exceptions import ConversionError, CouldNotParseTelegram
|
||||
from homeassistant.components.binary_sensor import (
|
||||
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 (
|
||||
DEVICE_CLASSES_SCHEMA as COVER_DEVICE_CLASSES_SCHEMA,
|
||||
)
|
||||
@ -54,6 +54,7 @@ from .const import (
|
||||
CONF_SYNC_STATE,
|
||||
KNX_ADDRESS,
|
||||
ColorTempModes,
|
||||
FanZeroMode,
|
||||
)
|
||||
from .validation import (
|
||||
backwards_compatible_xknx_climate_enum_member,
|
||||
@ -341,6 +342,11 @@ class ClimateSchema(KNXPlatformSchema):
|
||||
CONF_ON_OFF_INVERT = "on_off_invert"
|
||||
CONF_MIN_TEMP = "min_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_SETPOINT_SHIFT_MODE = "DPT6010"
|
||||
@ -348,6 +354,7 @@ class ClimateSchema(KNXPlatformSchema):
|
||||
DEFAULT_SETPOINT_SHIFT_MIN = -6
|
||||
DEFAULT_TEMPERATURE_STEP = 0.1
|
||||
DEFAULT_ON_OFF_INVERT = False
|
||||
DEFAULT_FAN_SPEED_MODE = "percent"
|
||||
|
||||
ENTITY_SCHEMA = vol.All(
|
||||
# deprecated since September 2020
|
||||
@ -423,6 +430,15 @@ class ClimateSchema(KNXPlatformSchema):
|
||||
vol.Optional(CONF_MIN_TEMP): vol.Coerce(float),
|
||||
vol.Optional(CONF_MAX_TEMP): vol.Coerce(float),
|
||||
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
|
||||
),
|
||||
}
|
||||
),
|
||||
)
|
||||
|
@ -439,3 +439,383 @@ async def test_command_value_idle_mode(hass: HomeAssistant, knx: KNXTestKit) ->
|
||||
knx.assert_state(
|
||||
"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")
|
||||
|
Loading…
x
Reference in New Issue
Block a user