1
0
mirror of https://github.com/home-assistant/core.git synced 2026-02-15 07:36:16 +00:00

Fix Airzone Q-Adapt select entities (#160695)

Signed-off-by: Álvaro Fernández Rojas <noltari@gmail.com>
This commit is contained in:
Álvaro Fernández Rojas
2026-01-12 15:48:07 +01:00
committed by GitHub
parent 0163a4d289
commit 46a3e5e5b5
3 changed files with 162 additions and 16 deletions

View File

@@ -85,6 +85,22 @@ class AirzoneSystemEntity(AirzoneEntity):
value = system[key]
return value
async def _async_update_sys_params(self, params: dict[str, Any]) -> None:
"""Send system parameters to API."""
_params = {
API_SYSTEM_ID: self.system_id,
**params,
}
_LOGGER.debug("update_sys_params=%s", _params)
try:
await self.coordinator.airzone.set_sys_parameters(_params)
except AirzoneError as error:
raise HomeAssistantError(
f"Failed to set system {self.entity_id}: {error}"
) from error
self.coordinator.async_set_updated_data(self.coordinator.airzone.data())
class AirzoneHotWaterEntity(AirzoneEntity):
"""Define an Airzone Hot Water entity."""

View File

@@ -20,6 +20,7 @@ from aioairzone.const import (
AZD_MODES,
AZD_Q_ADAPT,
AZD_SLEEP,
AZD_SYSTEMS,
AZD_ZONES,
)
@@ -30,7 +31,7 @@ from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from .coordinator import AirzoneConfigEntry, AirzoneUpdateCoordinator
from .entity import AirzoneEntity, AirzoneZoneEntity
from .entity import AirzoneEntity, AirzoneSystemEntity, AirzoneZoneEntity
@dataclass(frozen=True, kw_only=True)
@@ -85,14 +86,7 @@ def main_zone_options(
return [k for k, v in options.items() if v in modes]
MAIN_ZONE_SELECT_TYPES: Final[tuple[AirzoneSelectDescription, ...]] = (
AirzoneSelectDescription(
api_param=API_MODE,
key=AZD_MODE,
options_dict=MODE_DICT,
options_fn=main_zone_options,
translation_key="modes",
),
SYSTEM_SELECT_TYPES: Final[tuple[AirzoneSelectDescription, ...]] = (
AirzoneSelectDescription(
api_param=API_Q_ADAPT,
entity_category=EntityCategory.CONFIG,
@@ -104,6 +98,17 @@ MAIN_ZONE_SELECT_TYPES: Final[tuple[AirzoneSelectDescription, ...]] = (
)
MAIN_ZONE_SELECT_TYPES: Final[tuple[AirzoneSelectDescription, ...]] = (
AirzoneSelectDescription(
api_param=API_MODE,
key=AZD_MODE,
options_dict=MODE_DICT,
options_fn=main_zone_options,
translation_key="modes",
),
)
ZONE_SELECT_TYPES: Final[tuple[AirzoneSelectDescription, ...]] = (
AirzoneSelectDescription(
api_param=API_COLD_ANGLE,
@@ -140,16 +145,37 @@ async def async_setup_entry(
"""Add Airzone select from a config_entry."""
coordinator = entry.runtime_data
added_systems: set[str] = set()
added_zones: set[str] = set()
def _async_entity_listener() -> None:
"""Handle additions of select."""
entities: list[AirzoneBaseSelect] = []
systems_data = coordinator.data.get(AZD_SYSTEMS, {})
received_systems = set(systems_data)
new_systems = received_systems - added_systems
if new_systems:
entities.extend(
AirzoneSystemSelect(
coordinator,
description,
entry,
system_id,
systems_data.get(system_id),
)
for system_id in new_systems
for description in SYSTEM_SELECT_TYPES
if description.key in systems_data.get(system_id)
)
added_systems.update(new_systems)
zones_data = coordinator.data.get(AZD_ZONES, {})
received_zones = set(zones_data)
new_zones = received_zones - added_zones
if new_zones:
entities: list[AirzoneZoneSelect] = [
entities.extend(
AirzoneZoneSelect(
coordinator,
description,
@@ -161,8 +187,8 @@ async def async_setup_entry(
for description in MAIN_ZONE_SELECT_TYPES
if description.key in zones_data.get(system_zone_id)
and zones_data.get(system_zone_id).get(AZD_MASTER) is True
]
entities += [
)
entities.extend(
AirzoneZoneSelect(
coordinator,
description,
@@ -173,10 +199,11 @@ async def async_setup_entry(
for system_zone_id in new_zones
for description in ZONE_SELECT_TYPES
if description.key in zones_data.get(system_zone_id)
]
async_add_entities(entities)
)
added_zones.update(new_zones)
async_add_entities(entities)
entry.async_on_unload(coordinator.async_add_listener(_async_entity_listener))
_async_entity_listener()
@@ -203,6 +230,38 @@ class AirzoneBaseSelect(AirzoneEntity, SelectEntity):
self._attr_current_option = self._get_current_option()
class AirzoneSystemSelect(AirzoneSystemEntity, AirzoneBaseSelect):
"""Define an Airzone System select."""
def __init__(
self,
coordinator: AirzoneUpdateCoordinator,
description: AirzoneSelectDescription,
entry: ConfigEntry,
system_id: str,
system_data: dict[str, Any],
) -> None:
"""Initialize."""
super().__init__(coordinator, entry, system_data)
self._attr_unique_id = f"{self._attr_unique_id}_{system_id}_{description.key}"
self.entity_description = description
self._attr_options = self.entity_description.options_fn(
system_data, description.options_dict
)
self.values_dict = {v: k for k, v in description.options_dict.items()}
self._async_update_attrs()
async def async_select_option(self, option: str) -> None:
"""Change the selected option."""
param = self.entity_description.api_param
value = self.entity_description.options_dict[option]
await self._async_update_sys_params({param: value})
class AirzoneZoneSelect(AirzoneZoneEntity, AirzoneBaseSelect):
"""Define an Airzone Zone select."""

View File

@@ -2,12 +2,13 @@
from unittest.mock import patch
from aioairzone.common import OperationMode
from aioairzone.common import OperationMode, QAdapt
from aioairzone.const import (
API_COLD_ANGLE,
API_DATA,
API_HEAT_ANGLE,
API_MODE,
API_Q_ADAPT,
API_SLEEP,
API_SYSTEM_ID,
API_ZONE_ID,
@@ -17,7 +18,7 @@ import pytest
from homeassistant.components.select import ATTR_OPTIONS, DOMAIN as SELECT_DOMAIN
from homeassistant.const import ATTR_ENTITY_ID, ATTR_OPTION, SERVICE_SELECT_OPTION
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import ServiceValidationError
from homeassistant.exceptions import HomeAssistantError, ServiceValidationError
from .util import async_init_integration
@@ -27,6 +28,11 @@ async def test_airzone_create_selects(hass: HomeAssistant) -> None:
await async_init_integration(hass)
# Systems
state = hass.states.get("select.system_1_q_adapt")
assert state.state == "standard"
# Zones
state = hass.states.get("select.despacho_cold_angle")
assert state.state == "90deg"
@@ -95,6 +101,71 @@ async def test_airzone_create_selects(hass: HomeAssistant) -> None:
assert state.state == "off"
async def test_airzone_select_sys_qadapt(hass: HomeAssistant) -> None:
"""Test select system Q-Adapt."""
await async_init_integration(hass)
put_q_adapt = {
API_DATA: {
API_SYSTEM_ID: 1,
API_Q_ADAPT: QAdapt.SILENCE,
}
}
with pytest.raises(ServiceValidationError):
await hass.services.async_call(
SELECT_DOMAIN,
SERVICE_SELECT_OPTION,
{
ATTR_ENTITY_ID: "select.system_1_q_adapt",
ATTR_OPTION: "Invalid",
},
blocking=True,
)
with patch(
"homeassistant.components.airzone.AirzoneLocalApi.put_hvac",
return_value=put_q_adapt,
):
await hass.services.async_call(
SELECT_DOMAIN,
SERVICE_SELECT_OPTION,
{
ATTR_ENTITY_ID: "select.system_1_q_adapt",
ATTR_OPTION: "silence",
},
blocking=True,
)
state = hass.states.get("select.system_1_q_adapt")
assert state.state == "silence"
put_q_adapt = {
API_DATA: {
API_SYSTEM_ID: 2,
API_Q_ADAPT: QAdapt.SILENCE,
}
}
with (
patch(
"homeassistant.components.airzone.AirzoneLocalApi.put_hvac",
return_value=put_q_adapt,
),
pytest.raises(HomeAssistantError),
):
await hass.services.async_call(
SELECT_DOMAIN,
SERVICE_SELECT_OPTION,
{
ATTR_ENTITY_ID: "select.system_1_q_adapt",
ATTR_OPTION: "silence",
},
blocking=True,
)
async def test_airzone_select_sleep(hass: HomeAssistant) -> None:
"""Test select sleep."""