diff --git a/homeassistant/components/airzone/__init__.py b/homeassistant/components/airzone/__init__.py index 754dfe90dceb18c3a67f9252702b247242aff645..5d1f9f051a36a8c6f484804fd9abffc43735636e 100644 --- a/homeassistant/components/airzone/__init__.py +++ b/homeassistant/components/airzone/__init__.py @@ -24,6 +24,7 @@ PLATFORMS: list[Platform] = [ Platform.CLIMATE, Platform.SELECT, Platform.SENSOR, + Platform.SWITCH, Platform.WATER_HEATER, ] diff --git a/homeassistant/components/airzone/switch.py b/homeassistant/components/airzone/switch.py new file mode 100644 index 0000000000000000000000000000000000000000..93136810604977c80bd82666d7669e2bb6a6aa58 --- /dev/null +++ b/homeassistant/components/airzone/switch.py @@ -0,0 +1,122 @@ +"""Support for the Airzone switch.""" + +from __future__ import annotations + +from dataclasses import dataclass +from typing import Any, Final + +from aioairzone.const import API_ON, AZD_ON, AZD_ZONES + +from homeassistant.components.switch import ( + SwitchDeviceClass, + SwitchEntity, + SwitchEntityDescription, +) +from homeassistant.config_entries import ConfigEntry +from homeassistant.core import HomeAssistant, callback +from homeassistant.helpers.entity_platform import AddEntitiesCallback + +from . import AirzoneConfigEntry +from .coordinator import AirzoneUpdateCoordinator +from .entity import AirzoneEntity, AirzoneZoneEntity + + +@dataclass(frozen=True, kw_only=True) +class AirzoneSwitchDescription(SwitchEntityDescription): + """Class to describe an Airzone switch entity.""" + + api_param: str + + +ZONE_SWITCH_TYPES: Final[tuple[AirzoneSwitchDescription, ...]] = ( + AirzoneSwitchDescription( + api_param=API_ON, + device_class=SwitchDeviceClass.SWITCH, + key=AZD_ON, + ), +) + + +async def async_setup_entry( + hass: HomeAssistant, + entry: AirzoneConfigEntry, + async_add_entities: AddEntitiesCallback, +) -> None: + """Add Airzone switch from a config_entry.""" + coordinator = entry.runtime_data + + added_zones: set[str] = set() + + def _async_entity_listener() -> None: + """Handle additions of switch.""" + + zones_data = coordinator.data.get(AZD_ZONES, {}) + received_zones = set(zones_data) + new_zones = received_zones - added_zones + if new_zones: + async_add_entities( + AirzoneZoneSwitch( + coordinator, + description, + entry, + system_zone_id, + zones_data.get(system_zone_id), + ) + for system_zone_id in new_zones + for description in ZONE_SWITCH_TYPES + if description.key in zones_data.get(system_zone_id) + ) + added_zones.update(new_zones) + + entry.async_on_unload(coordinator.async_add_listener(_async_entity_listener)) + _async_entity_listener() + + +class AirzoneBaseSwitch(AirzoneEntity, SwitchEntity): + """Define an Airzone switch.""" + + entity_description: AirzoneSwitchDescription + + @callback + def _handle_coordinator_update(self) -> None: + """Update attributes when the coordinator updates.""" + self._async_update_attrs() + super()._handle_coordinator_update() + + @callback + def _async_update_attrs(self) -> None: + """Update switch attributes.""" + self._attr_is_on = self.get_airzone_value(self.entity_description.key) + + +class AirzoneZoneSwitch(AirzoneZoneEntity, AirzoneBaseSwitch): + """Define an Airzone Zone switch.""" + + def __init__( + self, + coordinator: AirzoneUpdateCoordinator, + description: AirzoneSwitchDescription, + entry: ConfigEntry, + system_zone_id: str, + zone_data: dict[str, Any], + ) -> None: + """Initialize.""" + super().__init__(coordinator, entry, system_zone_id, zone_data) + + self._attr_name = None + self._attr_unique_id = ( + f"{self._attr_unique_id}_{system_zone_id}_{description.key}" + ) + self.entity_description = description + + self._async_update_attrs() + + async def async_turn_on(self, **kwargs: Any) -> None: + """Turn the entity on.""" + param = self.entity_description.api_param + await self._async_update_hvac_params({param: True}) + + async def async_turn_off(self, **kwargs: Any) -> None: + """Turn the entity off.""" + param = self.entity_description.api_param + await self._async_update_hvac_params({param: False}) diff --git a/tests/components/airzone/test_switch.py b/tests/components/airzone/test_switch.py new file mode 100644 index 0000000000000000000000000000000000000000..f761b53ed4c657ebc878e9eddc2ab4dbbaec368f --- /dev/null +++ b/tests/components/airzone/test_switch.py @@ -0,0 +1,102 @@ +"""The switch tests for the Airzone platform.""" + +from unittest.mock import patch + +from aioairzone.const import API_DATA, API_ON, API_SYSTEM_ID, API_ZONE_ID + +from homeassistant.components.switch import DOMAIN as SWITCH_DOMAIN +from homeassistant.const import ( + ATTR_ENTITY_ID, + SERVICE_TURN_OFF, + SERVICE_TURN_ON, + STATE_OFF, + STATE_ON, +) +from homeassistant.core import HomeAssistant + +from .util import async_init_integration + + +async def test_airzone_create_switches(hass: HomeAssistant) -> None: + """Test creation of switches.""" + + await async_init_integration(hass) + + state = hass.states.get("switch.despacho") + assert state.state == STATE_OFF + + state = hass.states.get("switch.dorm_1") + assert state.state == STATE_ON + + state = hass.states.get("switch.dorm_2") + assert state.state == STATE_OFF + + state = hass.states.get("switch.dorm_ppal") + assert state.state == STATE_ON + + state = hass.states.get("switch.salon") + assert state.state == STATE_OFF + + +async def test_airzone_switch_off(hass: HomeAssistant) -> None: + """Test switch off.""" + + await async_init_integration(hass) + + put_hvac_off = { + API_DATA: [ + { + API_SYSTEM_ID: 1, + API_ZONE_ID: 3, + API_ON: False, + } + ] + } + + with patch( + "homeassistant.components.airzone.AirzoneLocalApi.put_hvac", + return_value=put_hvac_off, + ): + await hass.services.async_call( + SWITCH_DOMAIN, + SERVICE_TURN_OFF, + { + ATTR_ENTITY_ID: "switch.dorm_1", + }, + blocking=True, + ) + + state = hass.states.get("switch.dorm_1") + assert state.state == STATE_OFF + + +async def test_airzone_switch_on(hass: HomeAssistant) -> None: + """Test switch on.""" + + await async_init_integration(hass) + + put_hvac_on = { + API_DATA: [ + { + API_SYSTEM_ID: 1, + API_ZONE_ID: 5, + API_ON: True, + } + ] + } + + with patch( + "homeassistant.components.airzone.AirzoneLocalApi.put_hvac", + return_value=put_hvac_on, + ): + await hass.services.async_call( + SWITCH_DOMAIN, + SERVICE_TURN_ON, + { + ATTR_ENTITY_ID: "switch.dorm_2", + }, + blocking=True, + ) + + state = hass.states.get("switch.dorm_2") + assert state.state == STATE_ON