From 6a52283ce038a0a85f29cd7775b70784b50785bb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=81lvaro=20Fern=C3=A1ndez=20Rojas?= Date: Wed, 27 Sep 2023 20:30:32 +0200 Subject: [PATCH] Implement Airzone Cloud Aidoo climate support (#101011) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Implement Airzone Cloud Aidoo climate support Signed-off-by: Álvaro Fernández Rojas * airzone_cloud: climate: add entity naming Signed-off-by: Álvaro Fernández Rojas --------- Signed-off-by: Álvaro Fernández Rojas --- .../components/airzone_cloud/climate.py | 56 +++++++++++- .../components/airzone_cloud/entity.py | 14 +++ .../components/airzone_cloud/test_climate.py | 89 +++++++++++++++++++ 3 files changed, 156 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/airzone_cloud/climate.py b/homeassistant/components/airzone_cloud/climate.py index 18393031ae3..af6c38b80b4 100644 --- a/homeassistant/components/airzone_cloud/climate.py +++ b/homeassistant/components/airzone_cloud/climate.py @@ -12,6 +12,7 @@ from aioairzone_cloud.const import ( API_UNITS, API_VALUE, AZD_ACTION, + AZD_AIDOOS, AZD_HUMIDITY, AZD_MASTER, AZD_MODE, @@ -39,7 +40,7 @@ from homeassistant.helpers.entity_platform import AddEntitiesCallback from .const import DOMAIN from .coordinator import AirzoneUpdateCoordinator -from .entity import AirzoneEntity, AirzoneZoneEntity +from .entity import AirzoneAidooEntity, AirzoneEntity, AirzoneZoneEntity HVAC_ACTION_LIB_TO_HASS: Final[dict[OperationAction, HVACAction]] = { OperationAction.COOLING: HVACAction.COOLING, @@ -82,6 +83,16 @@ async def async_setup_entry( entities: list[AirzoneClimate] = [] + # Aidoos + for aidoo_id, aidoo_data in coordinator.data.get(AZD_AIDOOS, {}).items(): + entities.append( + AirzoneAidooClimate( + coordinator, + aidoo_id, + aidoo_data, + ) + ) + # Zones for zone_id, zone_data in coordinator.data.get(AZD_ZONES, {}).items(): entities.append( @@ -98,6 +109,7 @@ async def async_setup_entry( class AirzoneClimate(AirzoneEntity, ClimateEntity): """Define an Airzone Cloud climate.""" + _attr_has_entity_name = True _attr_supported_features = ClimateEntityFeature.TARGET_TEMPERATURE _attr_temperature_unit = UnitOfTemperature.CELSIUS @@ -156,11 +168,49 @@ class AirzoneClimate(AirzoneEntity, ClimateEntity): self._attr_target_temperature = self.get_airzone_value(AZD_TEMP_SET) +class AirzoneAidooClimate(AirzoneAidooEntity, AirzoneClimate): + """Define an Airzone Cloud Aidoo climate.""" + + def __init__( + self, + coordinator: AirzoneUpdateCoordinator, + aidoo_id: str, + aidoo_data: dict, + ) -> None: + """Initialize Airzone Cloud Aidoo climate.""" + super().__init__(coordinator, aidoo_id, aidoo_data) + + self._attr_unique_id = aidoo_id + self._attr_target_temperature_step = self.get_airzone_value(AZD_TEMP_STEP) + self._attr_hvac_modes = [ + HVAC_MODE_LIB_TO_HASS[mode] for mode in self.get_airzone_value(AZD_MODES) + ] + if HVACMode.OFF not in self._attr_hvac_modes: + self._attr_hvac_modes += [HVACMode.OFF] + + self._async_update_attrs() + + async def async_set_hvac_mode(self, hvac_mode: HVACMode) -> None: + """Set hvac mode.""" + params: dict[str, Any] = {} + if hvac_mode == HVACMode.OFF: + params[API_POWER] = { + API_VALUE: False, + } + else: + mode = HVAC_MODE_HASS_TO_LIB[hvac_mode] + params[API_MODE] = { + API_VALUE: mode.value, + } + params[API_POWER] = { + API_VALUE: True, + } + await self._async_update_params(params) + + class AirzoneZoneClimate(AirzoneZoneEntity, AirzoneClimate): """Define an Airzone Cloud Zone climate.""" - _attr_has_entity_name = True - def __init__( self, coordinator: AirzoneUpdateCoordinator, diff --git a/homeassistant/components/airzone_cloud/entity.py b/homeassistant/components/airzone_cloud/entity.py index 3214869aaab..b304f06d42b 100644 --- a/homeassistant/components/airzone_cloud/entity.py +++ b/homeassistant/components/airzone_cloud/entity.py @@ -74,6 +74,20 @@ class AirzoneAidooEntity(AirzoneEntity): value = aidoo.get(key) return value + async def _async_update_params(self, params: dict[str, Any]) -> None: + """Send Aidoo parameters to Cloud API.""" + _LOGGER.debug("aidoo=%s: update_params=%s", self.name, params) + try: + await self.coordinator.airzone.api_set_aidoo_id_params( + self.aidoo_id, params + ) + except AirzoneCloudError as error: + raise HomeAssistantError( + f"Failed to set {self.name} params: {error}" + ) from error + + self.coordinator.async_set_updated_data(self.coordinator.airzone.data()) + class AirzoneSystemEntity(AirzoneEntity): """Define an Airzone Cloud System entity.""" diff --git a/tests/components/airzone_cloud/test_climate.py b/tests/components/airzone_cloud/test_climate.py index acf1d082c29..fcd60f605ba 100644 --- a/tests/components/airzone_cloud/test_climate.py +++ b/tests/components/airzone_cloud/test_climate.py @@ -37,6 +37,25 @@ async def test_airzone_create_climates(hass: HomeAssistant) -> None: await async_init_integration(hass) + # Aidoos + state = hass.states.get("climate.bron") + assert state.state == HVACMode.OFF + assert state.attributes.get(ATTR_CURRENT_HUMIDITY) is None + assert state.attributes.get(ATTR_CURRENT_TEMPERATURE) == 21.0 + assert state.attributes.get(ATTR_HVAC_ACTION) == HVACAction.OFF + assert state.attributes.get(ATTR_HVAC_MODES) == [ + HVACMode.HEAT_COOL, + HVACMode.COOL, + HVACMode.HEAT, + HVACMode.FAN_ONLY, + HVACMode.DRY, + HVACMode.OFF, + ] + assert state.attributes.get(ATTR_MAX_TEMP) == 30 + assert state.attributes.get(ATTR_MIN_TEMP) == 15 + assert state.attributes.get(ATTR_TARGET_TEMP_STEP) == API_TEMPERATURE_STEP + assert state.attributes.get(ATTR_TEMPERATURE) == 22.0 + # Zones state = hass.states.get("climate.dormitorio") assert state.state == HVACMode.OFF @@ -78,6 +97,23 @@ async def test_airzone_climate_turn_on_off(hass: HomeAssistant) -> None: await async_init_integration(hass) + # Aidoos + with patch( + "homeassistant.components.airzone_cloud.AirzoneCloudApi.api_patch_device", + return_value=None, + ): + await hass.services.async_call( + CLIMATE_DOMAIN, + SERVICE_TURN_ON, + { + ATTR_ENTITY_ID: "climate.bron", + }, + blocking=True, + ) + + state = hass.states.get("climate.bron") + assert state.state == HVACMode.HEAT + # Zones with patch( "homeassistant.components.airzone_cloud.AirzoneCloudApi.api_patch_device", @@ -117,6 +153,41 @@ async def test_airzone_climate_set_hvac_mode(hass: HomeAssistant) -> None: await async_init_integration(hass) + # Aidoos + with patch( + "homeassistant.components.airzone_cloud.AirzoneCloudApi.api_patch_device", + return_value=None, + ): + await hass.services.async_call( + CLIMATE_DOMAIN, + SERVICE_SET_HVAC_MODE, + { + ATTR_ENTITY_ID: "climate.bron", + ATTR_HVAC_MODE: HVACMode.HEAT_COOL, + }, + blocking=True, + ) + + state = hass.states.get("climate.bron") + assert state.state == HVACMode.HEAT_COOL + + with patch( + "homeassistant.components.airzone_cloud.AirzoneCloudApi.api_patch_device", + return_value=None, + ): + await hass.services.async_call( + CLIMATE_DOMAIN, + SERVICE_SET_HVAC_MODE, + { + ATTR_ENTITY_ID: "climate.bron", + ATTR_HVAC_MODE: HVACMode.OFF, + }, + blocking=True, + ) + + state = hass.states.get("climate.bron") + assert state.state == HVACMode.OFF + # Zones with patch( "homeassistant.components.airzone_cloud.AirzoneCloudApi.api_patch_device", @@ -205,6 +276,24 @@ async def test_airzone_climate_set_temp_error(hass: HomeAssistant) -> None: await async_init_integration(hass) + # Aidoos + with patch( + "homeassistant.components.airzone_cloud.AirzoneCloudApi.api_patch_device", + side_effect=AirzoneCloudError, + ), pytest.raises(HomeAssistantError): + await hass.services.async_call( + CLIMATE_DOMAIN, + SERVICE_SET_TEMPERATURE, + { + ATTR_ENTITY_ID: "climate.bron", + ATTR_TEMPERATURE: 20.5, + }, + blocking=True, + ) + + state = hass.states.get("climate.bron") + assert state.attributes.get(ATTR_TEMPERATURE) == 22.0 + # Zones with patch( "homeassistant.components.airzone_cloud.AirzoneCloudApi.api_patch_device",