Allow selecting Air Quality mode for Airzone Cloud (#106769)

* airzone_cloud: add Air Quality mode select

Signed-off-by: Álvaro Fernández Rojas <noltari@gmail.com>

* trigger CI

* trigger CI

* Update select.py

Co-authored-by: Erik Montnemery <erik@montnemery.com>

* airzone_cloud: select: remove AirzoneSelectDescriptionMixin usage

* airzone_cloud: select: rename AIR_QUALITY_DICT

---------

Signed-off-by: Álvaro Fernández Rojas <noltari@gmail.com>
Co-authored-by: Erik Montnemery <erik@montnemery.com>
This commit is contained in:
Álvaro Fernández Rojas 2024-04-17 17:11:29 +02:00 committed by GitHub
parent 9bae6d694d
commit 7e9b5b1128
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 196 additions and 0 deletions

View file

@ -16,6 +16,7 @@ from .coordinator import AirzoneUpdateCoordinator
PLATFORMS: list[Platform] = [ PLATFORMS: list[Platform] = [
Platform.BINARY_SENSOR, Platform.BINARY_SENSOR,
Platform.CLIMATE, Platform.CLIMATE,
Platform.SELECT,
Platform.SENSOR, Platform.SENSOR,
Platform.WATER_HEATER, Platform.WATER_HEATER,
] ]

View file

@ -0,0 +1,124 @@
"""Support for the Airzone Cloud select."""
from __future__ import annotations
from dataclasses import dataclass
from typing import Any, Final
from aioairzone_cloud.common import AirQualityMode
from aioairzone_cloud.const import (
API_AQ_MODE_CONF,
API_VALUE,
AZD_AQ_MODE_CONF,
AZD_ZONES,
)
from homeassistant.components.select import SelectEntity, SelectEntityDescription
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import EntityCategory
from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from .const import DOMAIN
from .coordinator import AirzoneUpdateCoordinator
from .entity import AirzoneEntity, AirzoneZoneEntity
@dataclass(frozen=True, kw_only=True)
class AirzoneSelectDescription(SelectEntityDescription):
"""Class to describe an Airzone select entity."""
api_param: str
options_dict: dict[str, str]
AIR_QUALITY_MAP: Final[dict[str, str]] = {
"off": AirQualityMode.OFF,
"on": AirQualityMode.ON,
"auto": AirQualityMode.AUTO,
}
ZONE_SELECT_TYPES: Final[tuple[AirzoneSelectDescription, ...]] = (
AirzoneSelectDescription(
api_param=API_AQ_MODE_CONF,
entity_category=EntityCategory.CONFIG,
key=AZD_AQ_MODE_CONF,
options=list(AIR_QUALITY_MAP),
options_dict=AIR_QUALITY_MAP,
translation_key="air_quality",
),
)
async def async_setup_entry(
hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback
) -> None:
"""Add Airzone Cloud select from a config_entry."""
coordinator: AirzoneUpdateCoordinator = hass.data[DOMAIN][entry.entry_id]
# Zones
async_add_entities(
AirzoneZoneSelect(
coordinator,
description,
zone_id,
zone_data,
)
for description in ZONE_SELECT_TYPES
for zone_id, zone_data in coordinator.data.get(AZD_ZONES, {}).items()
if description.key in zone_data
)
class AirzoneBaseSelect(AirzoneEntity, SelectEntity):
"""Define an Airzone Cloud select."""
entity_description: AirzoneSelectDescription
values_dict: dict[str, str]
@callback
def _handle_coordinator_update(self) -> None:
"""Update attributes when the coordinator updates."""
self._async_update_attrs()
super()._handle_coordinator_update()
def _get_current_option(self) -> str | None:
"""Get current selected option."""
value = self.get_airzone_value(self.entity_description.key)
return self.values_dict.get(value)
@callback
def _async_update_attrs(self) -> None:
"""Update select attributes."""
self._attr_current_option = self._get_current_option()
class AirzoneZoneSelect(AirzoneZoneEntity, AirzoneBaseSelect):
"""Define an Airzone Cloud Zone select."""
def __init__(
self,
coordinator: AirzoneUpdateCoordinator,
description: AirzoneSelectDescription,
zone_id: str,
zone_data: dict[str, Any],
) -> None:
"""Initialize."""
super().__init__(coordinator, zone_id, zone_data)
self._attr_unique_id = f"{zone_id}_{description.key}"
self.entity_description = description
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]
params: dict[str, Any] = {}
params[param] = {
API_VALUE: value,
}
await self._async_update_params(params)

View file

@ -21,6 +21,16 @@
"air_quality_active": { "air_quality_active": {
"name": "Air Quality active" "name": "Air Quality active"
} }
},
"select": {
"air_quality": {
"name": "Air Quality mode",
"state": {
"off": "Off",
"on": "On",
"auto": "Auto"
}
}
} }
} }
} }

View file

@ -0,0 +1,61 @@
"""The select tests for the Airzone Cloud platform."""
from unittest.mock import patch
import pytest
from homeassistant.components.select import 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 .util import async_init_integration
async def test_airzone_create_selects(
hass: HomeAssistant, entity_registry_enabled_by_default: None
) -> None:
"""Test creation of selects."""
await async_init_integration(hass)
# Zones
state = hass.states.get("select.dormitorio_air_quality_mode")
assert state.state == "auto"
state = hass.states.get("select.salon_air_quality_mode")
assert state.state == "auto"
async def test_airzone_select_air_quality_mode(hass: HomeAssistant) -> None:
"""Test select Air Quality mode."""
await async_init_integration(hass)
with pytest.raises(ServiceValidationError):
await hass.services.async_call(
SELECT_DOMAIN,
SERVICE_SELECT_OPTION,
{
ATTR_ENTITY_ID: "select.dormitorio_air_quality_mode",
ATTR_OPTION: "Invalid",
},
blocking=True,
)
with patch(
"homeassistant.components.airzone_cloud.AirzoneCloudApi.api_patch_device",
return_value=None,
):
await hass.services.async_call(
SELECT_DOMAIN,
SERVICE_SELECT_OPTION,
{
ATTR_ENTITY_ID: "select.dormitorio_air_quality_mode",
ATTR_OPTION: "off",
},
blocking=True,
)
state = hass.states.get("select.dormitorio_air_quality_mode")
assert state.state == "off"