diff --git a/homeassistant/components/airzone_cloud/climate.py b/homeassistant/components/airzone_cloud/climate.py index 5aa03a14ada55..8fcdee1153510 100644 --- a/homeassistant/components/airzone_cloud/climate.py +++ b/homeassistant/components/airzone_cloud/climate.py @@ -11,6 +11,7 @@ API_PARAMS, API_POWER, API_SETPOINT, + API_SPEED_CONF, API_UNITS, API_VALUE, AZD_ACTION, @@ -24,6 +25,8 @@ AZD_NUM_DEVICES, AZD_NUM_GROUPS, AZD_POWER, + AZD_SPEED, + AZD_SPEEDS, AZD_TEMP, AZD_TEMP_SET, AZD_TEMP_SET_MAX, @@ -34,6 +37,10 @@ from homeassistant.components.climate import ( ATTR_HVAC_MODE, + FAN_AUTO, + FAN_HIGH, + FAN_LOW, + FAN_MEDIUM, ClimateEntity, ClimateEntityFeature, HVACAction, @@ -55,6 +62,22 @@ AirzoneZoneEntity, ) +FAN_SPEED_AUTO: dict[int, str] = { + 0: FAN_AUTO, +} + +FAN_SPEED_MAPS: Final[dict[int, dict[int, str]]] = { + 2: { + 1: FAN_LOW, + 2: FAN_HIGH, + }, + 3: { + 1: FAN_LOW, + 2: FAN_MEDIUM, + 3: FAN_HIGH, + }, +} + HVAC_ACTION_LIB_TO_HASS: Final[dict[OperationAction, HVACAction]] = { OperationAction.COOLING: HVACAction.COOLING, OperationAction.DRYING: HVACAction.DRYING, @@ -275,6 +298,9 @@ async def async_set_hvac_mode(self, hvac_mode: HVACMode) -> None: class AirzoneAidooClimate(AirzoneAidooEntity, AirzoneDeviceClimate): """Define an Airzone Cloud Aidoo climate.""" + _speeds: dict[int, str] + _speeds_reverse: dict[str, int] + def __init__( self, coordinator: AirzoneUpdateCoordinator, @@ -291,9 +317,52 @@ def __init__( ] if HVACMode.OFF not in self._attr_hvac_modes: self._attr_hvac_modes += [HVACMode.OFF] + if ( + self.get_airzone_value(AZD_SPEED) is not None + and self.get_airzone_value(AZD_SPEEDS) is not None + ): + self._initialize_fan_speeds() self._async_update_attrs() + def _initialize_fan_speeds(self) -> None: + """Initialize Aidoo fan speeds.""" + azd_speeds: dict[int, int] = self.get_airzone_value(AZD_SPEEDS) + max_speed = max(azd_speeds) + + fan_speeds: dict[int, str] + if speeds_map := FAN_SPEED_MAPS.get(max_speed): + fan_speeds = speeds_map + else: + fan_speeds = {} + + for speed in azd_speeds: + if speed != 0: + fan_speeds[speed] = f"{int(round((speed * 100) / max_speed, 0))}%" + + if 0 in azd_speeds: + fan_speeds = FAN_SPEED_AUTO | fan_speeds + + self._speeds = {} + for key, value in fan_speeds.items(): + _key = azd_speeds.get(key) + if _key is not None: + self._speeds[_key] = value + + self._speeds_reverse = {v: k for k, v in self._speeds.items()} + self._attr_fan_modes = list(self._speeds_reverse) + + self._attr_supported_features |= ClimateEntityFeature.FAN_MODE + + async def async_set_fan_mode(self, fan_mode: str) -> None: + """Set Aidoo fan mode.""" + params: dict[str, Any] = { + API_SPEED_CONF: { + API_VALUE: self._speeds_reverse.get(fan_mode), + } + } + await self._async_update_params(params) + async def async_set_hvac_mode(self, hvac_mode: HVACMode) -> None: """Set hvac mode.""" params: dict[str, Any] = {} @@ -311,6 +380,14 @@ async def async_set_hvac_mode(self, hvac_mode: HVACMode) -> None: } await self._async_update_params(params) + @callback + def _async_update_attrs(self) -> None: + """Update Aidoo climate attributes.""" + super()._async_update_attrs() + + if self.supported_features & ClimateEntityFeature.FAN_MODE: + self._attr_fan_mode = self._speeds.get(self.get_airzone_value(AZD_SPEED)) + class AirzoneGroupClimate(AirzoneGroupEntity, AirzoneDeviceGroupClimate): """Define an Airzone Cloud Group climate.""" diff --git a/tests/components/airzone_cloud/test_climate.py b/tests/components/airzone_cloud/test_climate.py index 848c9b8fb2d48..46fc16b07dfae 100644 --- a/tests/components/airzone_cloud/test_climate.py +++ b/tests/components/airzone_cloud/test_climate.py @@ -9,6 +9,8 @@ from homeassistant.components.climate import ( ATTR_CURRENT_HUMIDITY, ATTR_CURRENT_TEMPERATURE, + ATTR_FAN_MODE, + ATTR_FAN_MODES, ATTR_HVAC_ACTION, ATTR_HVAC_MODE, ATTR_HVAC_MODES, @@ -16,6 +18,11 @@ ATTR_MIN_TEMP, ATTR_TARGET_TEMP_STEP, DOMAIN as CLIMATE_DOMAIN, + FAN_AUTO, + FAN_HIGH, + FAN_LOW, + FAN_MEDIUM, + SERVICE_SET_FAN_MODE, SERVICE_SET_HVAC_MODE, SERVICE_SET_TEMPERATURE, HVACAction, @@ -43,6 +50,12 @@ async def test_airzone_create_climates(hass: HomeAssistant) -> None: assert state.state == HVACMode.OFF assert ATTR_CURRENT_HUMIDITY not in state.attributes assert state.attributes[ATTR_CURRENT_TEMPERATURE] == 21.0 + assert state.attributes[ATTR_FAN_MODE] == FAN_HIGH + assert state.attributes[ATTR_FAN_MODES] == [ + FAN_LOW, + FAN_MEDIUM, + FAN_HIGH, + ] assert state.attributes[ATTR_HVAC_ACTION] == HVACAction.OFF assert state.attributes[ATTR_HVAC_MODES] == [ HVACMode.HEAT_COOL, @@ -61,6 +74,15 @@ async def test_airzone_create_climates(hass: HomeAssistant) -> None: assert state.state == HVACMode.COOL assert ATTR_CURRENT_HUMIDITY not in state.attributes assert state.attributes[ATTR_CURRENT_TEMPERATURE] == 20.0 + assert state.attributes[ATTR_FAN_MODE] == "60%" + assert state.attributes[ATTR_FAN_MODES] == [ + FAN_AUTO, + "20%", + "40%", + "60%", + "80%", + "100%", + ] assert state.attributes[ATTR_HVAC_ACTION] == HVACAction.COOLING assert state.attributes[ATTR_HVAC_MODES] == [ HVACMode.HEAT_COOL, @@ -80,6 +102,8 @@ async def test_airzone_create_climates(hass: HomeAssistant) -> None: assert state.state == HVACMode.COOL assert state.attributes[ATTR_CURRENT_HUMIDITY] == 27 assert state.attributes[ATTR_CURRENT_TEMPERATURE] == 22.5 + assert ATTR_FAN_MODE not in state.attributes + assert ATTR_FAN_MODES not in state.attributes assert state.attributes[ATTR_HVAC_ACTION] == HVACAction.COOLING assert state.attributes[ATTR_HVAC_MODES] == [ HVACMode.COOL, @@ -98,6 +122,8 @@ async def test_airzone_create_climates(hass: HomeAssistant) -> None: assert state.state == HVACMode.COOL assert state.attributes[ATTR_CURRENT_HUMIDITY] == 27 assert state.attributes[ATTR_CURRENT_TEMPERATURE] == 21.5 + assert ATTR_FAN_MODE not in state.attributes + assert ATTR_FAN_MODES not in state.attributes assert state.attributes[ATTR_HVAC_ACTION] == HVACAction.COOLING assert state.attributes[ATTR_HVAC_MODES] == [ HVACMode.HEAT_COOL, @@ -117,6 +143,8 @@ async def test_airzone_create_climates(hass: HomeAssistant) -> None: assert state.state == HVACMode.OFF assert state.attributes[ATTR_CURRENT_HUMIDITY] == 24 assert state.attributes[ATTR_CURRENT_TEMPERATURE] == 25.0 + assert ATTR_FAN_MODE not in state.attributes + assert ATTR_FAN_MODES not in state.attributes assert state.attributes[ATTR_HVAC_ACTION] == HVACAction.OFF assert state.attributes[ATTR_HVAC_MODES] == [ HVACMode.COOL, @@ -134,6 +162,8 @@ async def test_airzone_create_climates(hass: HomeAssistant) -> None: assert state.state == HVACMode.COOL assert state.attributes[ATTR_CURRENT_HUMIDITY] == 30 assert state.attributes[ATTR_CURRENT_TEMPERATURE] == 20.0 + assert ATTR_FAN_MODE not in state.attributes + assert ATTR_FAN_MODES not in state.attributes assert state.attributes[ATTR_HVAC_ACTION] == HVACAction.COOLING assert state.attributes[ATTR_HVAC_MODES] == [ HVACMode.COOL, @@ -270,6 +300,47 @@ async def test_airzone_climate_turn_on_off(hass: HomeAssistant) -> None: assert state.state == HVACMode.OFF +async def test_airzone_climate_set_fan_mode(hass: HomeAssistant) -> None: + """Test setting the fan mode.""" + + 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_FAN_MODE, + { + ATTR_ENTITY_ID: "climate.bron", + ATTR_FAN_MODE: FAN_LOW, + }, + blocking=True, + ) + + state = hass.states.get("climate.bron") + assert state.attributes[ATTR_FAN_MODE] == FAN_LOW + + with patch( + "homeassistant.components.airzone_cloud.AirzoneCloudApi.api_patch_device", + return_value=None, + ): + await hass.services.async_call( + CLIMATE_DOMAIN, + SERVICE_SET_FAN_MODE, + { + ATTR_ENTITY_ID: "climate.bron_pro", + ATTR_FAN_MODE: FAN_AUTO, + }, + blocking=True, + ) + + state = hass.states.get("climate.bron_pro") + assert state.attributes[ATTR_FAN_MODE] == FAN_AUTO + + async def test_airzone_climate_set_hvac_mode(hass: HomeAssistant) -> None: """Test setting the HVAC mode."""