Rename KNX Climate preset modes according to specification (#123964)
* Rename KNX Climate preset modes according to specification * change icon for "standby"
This commit is contained in:
parent
9911aa4ede
commit
b042ebe4ff
5 changed files with 54 additions and 59 deletions
|
@ -10,11 +10,10 @@ from xknx.devices import (
|
||||||
ClimateMode as XknxClimateMode,
|
ClimateMode as XknxClimateMode,
|
||||||
Device as XknxDevice,
|
Device as XknxDevice,
|
||||||
)
|
)
|
||||||
from xknx.dpt.dpt_20 import HVACControllerMode
|
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 (
|
||||||
PRESET_AWAY,
|
|
||||||
ClimateEntity,
|
ClimateEntity,
|
||||||
ClimateEntityFeature,
|
ClimateEntityFeature,
|
||||||
HVACAction,
|
HVACAction,
|
||||||
|
@ -32,19 +31,12 @@ from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||||
from homeassistant.helpers.typing import ConfigType
|
from homeassistant.helpers.typing import ConfigType
|
||||||
|
|
||||||
from . import KNXModule
|
from . import KNXModule
|
||||||
from .const import (
|
from .const import CONTROLLER_MODES, CURRENT_HVAC_ACTIONS, DATA_KNX_CONFIG, DOMAIN
|
||||||
CONTROLLER_MODES,
|
|
||||||
CURRENT_HVAC_ACTIONS,
|
|
||||||
DATA_KNX_CONFIG,
|
|
||||||
DOMAIN,
|
|
||||||
PRESET_MODES,
|
|
||||||
)
|
|
||||||
from .knx_entity import KnxYamlEntity
|
from .knx_entity import KnxYamlEntity
|
||||||
from .schema import ClimateSchema
|
from .schema import ClimateSchema
|
||||||
|
|
||||||
ATTR_COMMAND_VALUE = "command_value"
|
ATTR_COMMAND_VALUE = "command_value"
|
||||||
CONTROLLER_MODES_INV = {value: key for key, value in CONTROLLER_MODES.items()}
|
CONTROLLER_MODES_INV = {value: key for key, value in CONTROLLER_MODES.items()}
|
||||||
PRESET_MODES_INV = {value: key for key, value in PRESET_MODES.items()}
|
|
||||||
|
|
||||||
|
|
||||||
async def async_setup_entry(
|
async def async_setup_entry(
|
||||||
|
@ -142,6 +134,7 @@ class KNXClimate(KnxYamlEntity, ClimateEntity):
|
||||||
|
|
||||||
_device: XknxClimate
|
_device: XknxClimate
|
||||||
_attr_temperature_unit = UnitOfTemperature.CELSIUS
|
_attr_temperature_unit = UnitOfTemperature.CELSIUS
|
||||||
|
_attr_translation_key = "knx_climate"
|
||||||
_enable_turn_on_off_backwards_compatibility = False
|
_enable_turn_on_off_backwards_compatibility = False
|
||||||
|
|
||||||
def __init__(self, knx_module: KNXModule, config: ConfigType) -> None:
|
def __init__(self, knx_module: KNXModule, config: ConfigType) -> None:
|
||||||
|
@ -165,8 +158,14 @@ class KNXClimate(KnxYamlEntity, ClimateEntity):
|
||||||
ClimateEntityFeature.TURN_OFF | ClimateEntityFeature.TURN_ON
|
ClimateEntityFeature.TURN_OFF | ClimateEntityFeature.TURN_ON
|
||||||
)
|
)
|
||||||
|
|
||||||
if self.preset_modes:
|
if (
|
||||||
|
self._device.mode is not None
|
||||||
|
and self._device.mode.operation_modes # empty list when not writable
|
||||||
|
):
|
||||||
self._attr_supported_features |= ClimateEntityFeature.PRESET_MODE
|
self._attr_supported_features |= ClimateEntityFeature.PRESET_MODE
|
||||||
|
self._attr_preset_modes = [
|
||||||
|
mode.name.lower() for mode in self._device.mode.operation_modes
|
||||||
|
]
|
||||||
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}_"
|
||||||
|
@ -309,32 +308,18 @@ class KNXClimate(KnxYamlEntity, ClimateEntity):
|
||||||
Requires ClimateEntityFeature.PRESET_MODE.
|
Requires ClimateEntityFeature.PRESET_MODE.
|
||||||
"""
|
"""
|
||||||
if self._device.mode is not None and self._device.mode.supports_operation_mode:
|
if self._device.mode is not None and self._device.mode.supports_operation_mode:
|
||||||
return PRESET_MODES.get(self._device.mode.operation_mode, PRESET_AWAY)
|
return self._device.mode.operation_mode.name.lower()
|
||||||
return None
|
return None
|
||||||
|
|
||||||
@property
|
|
||||||
def preset_modes(self) -> list[str] | None:
|
|
||||||
"""Return a list of available preset modes.
|
|
||||||
|
|
||||||
Requires ClimateEntityFeature.PRESET_MODE.
|
|
||||||
"""
|
|
||||||
if self._device.mode is None:
|
|
||||||
return None
|
|
||||||
|
|
||||||
presets = [
|
|
||||||
PRESET_MODES.get(operation_mode)
|
|
||||||
for operation_mode in self._device.mode.operation_modes
|
|
||||||
]
|
|
||||||
return list(filter(None, presets))
|
|
||||||
|
|
||||||
async def async_set_preset_mode(self, preset_mode: str) -> None:
|
async def async_set_preset_mode(self, preset_mode: str) -> None:
|
||||||
"""Set new preset mode."""
|
"""Set new preset mode."""
|
||||||
if (
|
if (
|
||||||
self._device.mode is not None
|
self._device.mode is not None
|
||||||
and self._device.mode.supports_operation_mode
|
and self._device.mode.operation_modes # empty list when not writable
|
||||||
and (knx_operation_mode := PRESET_MODES_INV.get(preset_mode)) is not None
|
|
||||||
):
|
):
|
||||||
await self._device.mode.set_operation_mode(knx_operation_mode)
|
await self._device.mode.set_operation_mode(
|
||||||
|
HVACOperationMode[preset_mode.upper()]
|
||||||
|
)
|
||||||
self.async_write_ha_state()
|
self.async_write_ha_state()
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
|
|
@ -6,18 +6,10 @@ from collections.abc import Awaitable, Callable
|
||||||
from enum import Enum
|
from enum import Enum
|
||||||
from typing import Final, TypedDict
|
from typing import Final, TypedDict
|
||||||
|
|
||||||
from xknx.dpt.dpt_20 import HVACControllerMode, HVACOperationMode
|
from xknx.dpt.dpt_20 import HVACControllerMode
|
||||||
from xknx.telegram import Telegram
|
from xknx.telegram import Telegram
|
||||||
|
|
||||||
from homeassistant.components.climate import (
|
from homeassistant.components.climate import HVACAction, HVACMode
|
||||||
PRESET_AWAY,
|
|
||||||
PRESET_COMFORT,
|
|
||||||
PRESET_ECO,
|
|
||||||
PRESET_NONE,
|
|
||||||
PRESET_SLEEP,
|
|
||||||
HVACAction,
|
|
||||||
HVACMode,
|
|
||||||
)
|
|
||||||
from homeassistant.const import Platform
|
from homeassistant.const import Platform
|
||||||
|
|
||||||
DOMAIN: Final = "knx"
|
DOMAIN: Final = "knx"
|
||||||
|
@ -174,12 +166,3 @@ CURRENT_HVAC_ACTIONS: Final = {
|
||||||
HVACMode.FAN_ONLY: HVACAction.FAN,
|
HVACMode.FAN_ONLY: HVACAction.FAN,
|
||||||
HVACMode.DRY: HVACAction.DRYING,
|
HVACMode.DRY: HVACAction.DRYING,
|
||||||
}
|
}
|
||||||
|
|
||||||
PRESET_MODES: Final = {
|
|
||||||
# Map DPT 20.102 HVAC operating modes to HA presets
|
|
||||||
HVACOperationMode.AUTO: PRESET_NONE,
|
|
||||||
HVACOperationMode.BUILDING_PROTECTION: PRESET_ECO,
|
|
||||||
HVACOperationMode.ECONOMY: PRESET_SLEEP,
|
|
||||||
HVACOperationMode.STANDBY: PRESET_AWAY,
|
|
||||||
HVACOperationMode.COMFORT: PRESET_COMFORT,
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,5 +1,19 @@
|
||||||
{
|
{
|
||||||
"entity": {
|
"entity": {
|
||||||
|
"climate": {
|
||||||
|
"knx_climate": {
|
||||||
|
"state_attributes": {
|
||||||
|
"preset_mode": {
|
||||||
|
"state": {
|
||||||
|
"comfort": "mdi:sofa",
|
||||||
|
"standby": "mdi:home-export-outline",
|
||||||
|
"economy": "mdi:leaf",
|
||||||
|
"building_protection": "mdi:sun-snowflake-variant"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"sensor": {
|
"sensor": {
|
||||||
"individual_address": {
|
"individual_address": {
|
||||||
"default": "mdi:router-network"
|
"default": "mdi:router-network"
|
||||||
|
|
|
@ -267,6 +267,22 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"entity": {
|
"entity": {
|
||||||
|
"climate": {
|
||||||
|
"knx_climate": {
|
||||||
|
"state_attributes": {
|
||||||
|
"preset_mode": {
|
||||||
|
"name": "[%key:component::climate::entity_component::_::state_attributes::preset_mode::name%]",
|
||||||
|
"state": {
|
||||||
|
"auto": "Auto",
|
||||||
|
"comfort": "[%key:component::climate::entity_component::_::state_attributes::preset_mode::state::comfort%]",
|
||||||
|
"standby": "Standby",
|
||||||
|
"economy": "[%key:component::climate::entity_component::_::state_attributes::preset_mode::state::eco%]",
|
||||||
|
"building_protection": "Building protection"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"sensor": {
|
"sensor": {
|
||||||
"individual_address": {
|
"individual_address": {
|
||||||
"name": "[%key:component::knx::config::step::routing::data::individual_address%]"
|
"name": "[%key:component::knx::config::step::routing::data::individual_address%]"
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from homeassistant.components.climate import PRESET_ECO, PRESET_SLEEP, HVACMode
|
from homeassistant.components.climate import 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
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
|
@ -331,7 +331,6 @@ async def test_climate_preset_mode(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
events = async_capture_events(hass, "state_changed")
|
|
||||||
|
|
||||||
# StateUpdater initialize state
|
# StateUpdater initialize state
|
||||||
# StateUpdater semaphore allows 2 concurrent requests
|
# StateUpdater semaphore allows 2 concurrent requests
|
||||||
|
@ -340,30 +339,28 @@ async def test_climate_preset_mode(
|
||||||
await knx.receive_response("1/2/3", RAW_FLOAT_21_0)
|
await knx.receive_response("1/2/3", RAW_FLOAT_21_0)
|
||||||
await knx.receive_response("1/2/5", RAW_FLOAT_22_0)
|
await knx.receive_response("1/2/5", RAW_FLOAT_22_0)
|
||||||
await knx.assert_read("1/2/7")
|
await knx.assert_read("1/2/7")
|
||||||
await knx.receive_response("1/2/7", (0x01,))
|
await knx.receive_response("1/2/7", (0x01,)) # comfort
|
||||||
events.clear()
|
|
||||||
|
|
||||||
|
knx.assert_state("climate.test", HVACMode.HEAT, preset_mode="comfort")
|
||||||
# set preset mode
|
# set preset mode
|
||||||
await hass.services.async_call(
|
await hass.services.async_call(
|
||||||
"climate",
|
"climate",
|
||||||
"set_preset_mode",
|
"set_preset_mode",
|
||||||
{"entity_id": "climate.test", "preset_mode": PRESET_ECO},
|
{"entity_id": "climate.test", "preset_mode": "building_protection"},
|
||||||
blocking=True,
|
blocking=True,
|
||||||
)
|
)
|
||||||
await knx.assert_write("1/2/6", (0x04,))
|
await knx.assert_write("1/2/6", (0x04,))
|
||||||
assert len(events) == 1
|
knx.assert_state("climate.test", HVACMode.HEAT, preset_mode="building_protection")
|
||||||
events.pop()
|
|
||||||
|
|
||||||
# set preset mode
|
# set preset mode
|
||||||
await hass.services.async_call(
|
await hass.services.async_call(
|
||||||
"climate",
|
"climate",
|
||||||
"set_preset_mode",
|
"set_preset_mode",
|
||||||
{"entity_id": "climate.test", "preset_mode": PRESET_SLEEP},
|
{"entity_id": "climate.test", "preset_mode": "economy"},
|
||||||
blocking=True,
|
blocking=True,
|
||||||
)
|
)
|
||||||
await knx.assert_write("1/2/6", (0x03,))
|
await knx.assert_write("1/2/6", (0x03,))
|
||||||
assert len(events) == 1
|
knx.assert_state("climate.test", HVACMode.HEAT, preset_mode="economy")
|
||||||
events.pop()
|
|
||||||
|
|
||||||
assert len(knx.xknx.devices) == 2
|
assert len(knx.xknx.devices) == 2
|
||||||
assert len(knx.xknx.devices[0].device_updated_cbs) == 2
|
assert len(knx.xknx.devices[0].device_updated_cbs) == 2
|
||||||
|
|
Loading…
Add table
Reference in a new issue