Rename KNX Climate preset modes according to specification (#123964)

* Rename KNX Climate preset modes according to specification

* change icon for "standby"
pull/124202/head
Matthias Alphart 2024-08-15 08:37:10 +02:00 committed by GitHub
parent 9911aa4ede
commit b042ebe4ff
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 54 additions and 59 deletions

View File

@ -10,11 +10,10 @@ from xknx.devices import (
ClimateMode as XknxClimateMode,
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.components.climate import (
PRESET_AWAY,
ClimateEntity,
ClimateEntityFeature,
HVACAction,
@ -32,19 +31,12 @@ from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.typing import ConfigType
from . import KNXModule
from .const import (
CONTROLLER_MODES,
CURRENT_HVAC_ACTIONS,
DATA_KNX_CONFIG,
DOMAIN,
PRESET_MODES,
)
from .const import CONTROLLER_MODES, CURRENT_HVAC_ACTIONS, DATA_KNX_CONFIG, DOMAIN
from .knx_entity import KnxYamlEntity
from .schema import ClimateSchema
ATTR_COMMAND_VALUE = "command_value"
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(
@ -142,6 +134,7 @@ class KNXClimate(KnxYamlEntity, ClimateEntity):
_device: XknxClimate
_attr_temperature_unit = UnitOfTemperature.CELSIUS
_attr_translation_key = "knx_climate"
_enable_turn_on_off_backwards_compatibility = False
def __init__(self, knx_module: KNXModule, config: ConfigType) -> None:
@ -165,8 +158,14 @@ class KNXClimate(KnxYamlEntity, ClimateEntity):
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_preset_modes = [
mode.name.lower() for mode in self._device.mode.operation_modes
]
self._attr_target_temperature_step = self._device.temperature_step
self._attr_unique_id = (
f"{self._device.temperature.group_address_state}_"
@ -309,32 +308,18 @@ class KNXClimate(KnxYamlEntity, ClimateEntity):
Requires ClimateEntityFeature.PRESET_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
@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:
"""Set new preset mode."""
if (
self._device.mode is not None
and self._device.mode.supports_operation_mode
and (knx_operation_mode := PRESET_MODES_INV.get(preset_mode)) is not None
and self._device.mode.operation_modes # empty list when not writable
):
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()
@property

View File

@ -6,18 +6,10 @@ from collections.abc import Awaitable, Callable
from enum import Enum
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 homeassistant.components.climate import (
PRESET_AWAY,
PRESET_COMFORT,
PRESET_ECO,
PRESET_NONE,
PRESET_SLEEP,
HVACAction,
HVACMode,
)
from homeassistant.components.climate import HVACAction, HVACMode
from homeassistant.const import Platform
DOMAIN: Final = "knx"
@ -174,12 +166,3 @@ CURRENT_HVAC_ACTIONS: Final = {
HVACMode.FAN_ONLY: HVACAction.FAN,
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,
}

View File

@ -1,5 +1,19 @@
{
"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": {
"individual_address": {
"default": "mdi:router-network"

View File

@ -267,6 +267,22 @@
}
},
"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": {
"individual_address": {
"name": "[%key:component::knx::config::step::routing::data::individual_address%]"

View File

@ -2,7 +2,7 @@
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.const import CONF_NAME, STATE_IDLE
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 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/5", RAW_FLOAT_22_0)
await knx.assert_read("1/2/7")
await knx.receive_response("1/2/7", (0x01,))
events.clear()
await knx.receive_response("1/2/7", (0x01,)) # comfort
knx.assert_state("climate.test", HVACMode.HEAT, preset_mode="comfort")
# set preset mode
await hass.services.async_call(
"climate",
"set_preset_mode",
{"entity_id": "climate.test", "preset_mode": PRESET_ECO},
{"entity_id": "climate.test", "preset_mode": "building_protection"},
blocking=True,
)
await knx.assert_write("1/2/6", (0x04,))
assert len(events) == 1
events.pop()
knx.assert_state("climate.test", HVACMode.HEAT, preset_mode="building_protection")
# set preset mode
await hass.services.async_call(
"climate",
"set_preset_mode",
{"entity_id": "climate.test", "preset_mode": PRESET_SLEEP},
{"entity_id": "climate.test", "preset_mode": "economy"},
blocking=True,
)
await knx.assert_write("1/2/6", (0x03,))
assert len(events) == 1
events.pop()
knx.assert_state("climate.test", HVACMode.HEAT, preset_mode="economy")
assert len(knx.xknx.devices) == 2
assert len(knx.xknx.devices[0].device_updated_cbs) == 2