diff --git a/homeassistant/components/airzone_cloud/__init__.py b/homeassistant/components/airzone_cloud/__init__.py index c6908b191d7..e53c01e0f81 100644 --- a/homeassistant/components/airzone_cloud/__init__.py +++ b/homeassistant/components/airzone_cloud/__init__.py @@ -16,6 +16,7 @@ from .coordinator import AirzoneUpdateCoordinator PLATFORMS: list[Platform] = [ Platform.BINARY_SENSOR, Platform.CLIMATE, + Platform.SELECT, Platform.SENSOR, Platform.WATER_HEATER, ] diff --git a/homeassistant/components/airzone_cloud/select.py b/homeassistant/components/airzone_cloud/select.py new file mode 100644 index 00000000000..c5c9f664503 --- /dev/null +++ b/homeassistant/components/airzone_cloud/select.py @@ -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) diff --git a/homeassistant/components/airzone_cloud/strings.json b/homeassistant/components/airzone_cloud/strings.json index fe7c38c8374..fe9455aa69e 100644 --- a/homeassistant/components/airzone_cloud/strings.json +++ b/homeassistant/components/airzone_cloud/strings.json @@ -21,6 +21,16 @@ "air_quality_active": { "name": "Air Quality active" } + }, + "select": { + "air_quality": { + "name": "Air Quality mode", + "state": { + "off": "Off", + "on": "On", + "auto": "Auto" + } + } } } } diff --git a/tests/components/airzone_cloud/test_select.py b/tests/components/airzone_cloud/test_select.py new file mode 100644 index 00000000000..1375b052050 --- /dev/null +++ b/tests/components/airzone_cloud/test_select.py @@ -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"