diff --git a/homeassistant/components/airzone/climate.py b/homeassistant/components/airzone/climate.py index 1a167f49f78..764beddd433 100644 --- a/homeassistant/components/airzone/climate.py +++ b/homeassistant/components/airzone/climate.py @@ -8,6 +8,7 @@ from aioairzone.const import ( API_MODE, API_ON, API_SET_POINT, + API_SPEED, AZD_ACTION, AZD_HUMIDITY, AZD_MASTER, @@ -15,6 +16,8 @@ from aioairzone.const import ( AZD_MODES, AZD_NAME, AZD_ON, + AZD_SPEED, + AZD_SPEEDS, AZD_TEMP, AZD_TEMP_MAX, AZD_TEMP_MIN, @@ -24,6 +27,10 @@ from aioairzone.const import ( ) from homeassistant.components.climate import ( + FAN_AUTO, + FAN_HIGH, + FAN_LOW, + FAN_MEDIUM, ClimateEntity, ClimateEntityFeature, HVACAction, @@ -39,6 +46,22 @@ from .const import API_TEMPERATURE_STEP, DOMAIN, TEMP_UNIT_LIB_TO_HASS from .coordinator import AirzoneUpdateCoordinator from .entity import AirzoneZoneEntity +BASE_FAN_SPEEDS: Final[dict[int, str]] = { + 0: FAN_AUTO, + 1: FAN_LOW, +} +FAN_SPEED_MAPS: Final[dict[int, dict[int, str]]] = { + 2: BASE_FAN_SPEEDS + | { + 2: FAN_HIGH, + }, + 3: BASE_FAN_SPEEDS + | { + 2: FAN_MEDIUM, + 3: FAN_HIGH, + }, +} + HVAC_ACTION_LIB_TO_HASS: Final[dict[OperationAction, HVACAction]] = { OperationAction.COOLING: HVACAction.COOLING, OperationAction.DRYING: HVACAction.DRYING, @@ -84,6 +107,9 @@ async def async_setup_entry( class AirzoneClimate(AirzoneZoneEntity, ClimateEntity): """Define an Airzone sensor.""" + _speeds: dict[int, str] = {} + _speeds_reverse: dict[str, int] = {} + def __init__( self, coordinator: AirzoneUpdateCoordinator, @@ -106,8 +132,35 @@ class AirzoneClimate(AirzoneZoneEntity, ClimateEntity): self._attr_hvac_modes = [ HVAC_MODE_LIB_TO_HASS[mode] for mode in self.get_airzone_value(AZD_MODES) ] + if ( + self.get_airzone_value(AZD_SPEED) is not None + and self.get_airzone_value(AZD_SPEEDS) is not None + ): + self._set_fan_speeds() + self._async_update_attrs() + def _set_fan_speeds(self) -> None: + self._attr_supported_features |= ClimateEntityFeature.FAN_MODE + + speeds = self.get_airzone_value(AZD_SPEEDS) + max_speed = max(speeds) + if _speeds := FAN_SPEED_MAPS.get(max_speed): + self._speeds = _speeds + else: + for speed in speeds: + if speed == 0: + self._speeds[speed] = FAN_AUTO + else: + self._speeds[speed] = f"{int(round((speed * 100) / max_speed, 0))}%" + + self._speeds[1] = FAN_LOW + self._speeds[int(round((max_speed + 1) / 2, 0))] = FAN_MEDIUM + self._speeds[max_speed] = FAN_HIGH + + self._speeds_reverse = {v: k for k, v in self._speeds.items()} + self._attr_fan_modes = list(self._speeds_reverse) + async def async_turn_on(self) -> None: """Turn the entity on.""" params = { @@ -122,6 +175,13 @@ class AirzoneClimate(AirzoneZoneEntity, ClimateEntity): } await self._async_update_hvac_params(params) + async def async_set_fan_mode(self, fan_mode: str) -> None: + """Set fan mode.""" + params = { + API_SPEED: self._speeds_reverse.get(fan_mode), + } + await self._async_update_hvac_params(params) + async def async_set_hvac_mode(self, hvac_mode: HVACMode) -> None: """Set hvac mode.""" params = {} @@ -167,3 +227,5 @@ class AirzoneClimate(AirzoneZoneEntity, ClimateEntity): else: self._attr_hvac_mode = HVACMode.OFF self._attr_target_temperature = self.get_airzone_value(AZD_TEMP_SET) + if self.supported_features & ClimateEntityFeature.FAN_MODE: + self._attr_fan_mode = self._speeds.get(self.get_airzone_value(AZD_SPEED)) diff --git a/tests/components/airzone/test_climate.py b/tests/components/airzone/test_climate.py index caf8cfe13bd..cfbf6049e7c 100644 --- a/tests/components/airzone/test_climate.py +++ b/tests/components/airzone/test_climate.py @@ -7,6 +7,7 @@ from aioairzone.const import ( API_MODE, API_ON, API_SET_POINT, + API_SPEED, API_SYSTEM_ID, API_ZONE_ID, ) @@ -17,6 +18,8 @@ from homeassistant.components.airzone.const import API_TEMPERATURE_STEP 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, @@ -24,6 +27,11 @@ from homeassistant.components.climate import ( 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, @@ -50,6 +58,8 @@ async def test_airzone_create_climates(hass: HomeAssistant) -> None: assert state.state == HVACMode.OFF assert state.attributes.get(ATTR_CURRENT_HUMIDITY) == 36 assert state.attributes.get(ATTR_CURRENT_TEMPERATURE) == 21.2 + assert state.attributes.get(ATTR_FAN_MODE) is None + assert state.attributes.get(ATTR_FAN_MODES) is None assert state.attributes.get(ATTR_HVAC_ACTION) == HVACAction.OFF assert state.attributes.get(ATTR_HVAC_MODES) == [ HVACMode.OFF, @@ -67,6 +77,8 @@ async def test_airzone_create_climates(hass: HomeAssistant) -> None: assert state.state == HVACMode.HEAT assert state.attributes.get(ATTR_CURRENT_HUMIDITY) == 35 assert state.attributes.get(ATTR_CURRENT_TEMPERATURE) == 20.8 + assert state.attributes.get(ATTR_FAN_MODE) is None + assert state.attributes.get(ATTR_FAN_MODES) is None assert state.attributes.get(ATTR_HVAC_ACTION) == HVACAction.IDLE assert state.attributes.get(ATTR_HVAC_MODES) == [ HVACMode.OFF, @@ -84,6 +96,8 @@ async def test_airzone_create_climates(hass: HomeAssistant) -> None: assert state.state == HVACMode.OFF assert state.attributes.get(ATTR_CURRENT_HUMIDITY) == 40 assert state.attributes.get(ATTR_CURRENT_TEMPERATURE) == 20.5 + assert state.attributes.get(ATTR_FAN_MODE) is None + assert state.attributes.get(ATTR_FAN_MODES) is None assert state.attributes.get(ATTR_HVAC_ACTION) == HVACAction.OFF assert state.attributes.get(ATTR_HVAC_MODES) == [ HVACMode.OFF, @@ -101,6 +115,12 @@ async def test_airzone_create_climates(hass: HomeAssistant) -> None: assert state.state == HVACMode.HEAT assert state.attributes.get(ATTR_CURRENT_HUMIDITY) == 39 assert state.attributes.get(ATTR_CURRENT_TEMPERATURE) == 21.1 + assert state.attributes.get(ATTR_FAN_MODE) == FAN_AUTO + assert state.attributes.get(ATTR_FAN_MODES) == [ + FAN_AUTO, + FAN_LOW, + FAN_HIGH, + ] assert state.attributes.get(ATTR_HVAC_ACTION) == HVACAction.HEATING assert state.attributes.get(ATTR_HVAC_MODES) == [ HVACMode.OFF, @@ -118,6 +138,13 @@ async def test_airzone_create_climates(hass: HomeAssistant) -> None: assert state.state == HVACMode.OFF assert state.attributes.get(ATTR_CURRENT_HUMIDITY) == 34 assert state.attributes.get(ATTR_CURRENT_TEMPERATURE) == 19.6 + assert state.attributes.get(ATTR_FAN_MODE) == FAN_AUTO + assert state.attributes.get(ATTR_FAN_MODES) == [ + FAN_AUTO, + FAN_LOW, + FAN_MEDIUM, + FAN_HIGH, + ] assert state.attributes.get(ATTR_HVAC_ACTION) == HVACAction.OFF assert state.attributes.get(ATTR_HVAC_MODES) == [ HVACMode.OFF, @@ -135,6 +162,14 @@ async def test_airzone_create_climates(hass: HomeAssistant) -> None: assert state.state == HVACMode.OFF assert state.attributes.get(ATTR_CURRENT_HUMIDITY) == 62 assert state.attributes.get(ATTR_CURRENT_TEMPERATURE) == 22.3 + assert state.attributes.get(ATTR_FAN_MODE) == FAN_AUTO + assert state.attributes.get(ATTR_FAN_MODES) == [ + FAN_AUTO, + FAN_LOW, + FAN_MEDIUM, + "75%", + FAN_HIGH, + ] assert state.attributes.get(ATTR_HVAC_ACTION) == HVACAction.OFF assert state.attributes.get(ATTR_HVAC_MODES) == [ HVACMode.HEAT_COOL, @@ -149,6 +184,15 @@ async def test_airzone_create_climates(hass: HomeAssistant) -> None: assert state.state == HVACMode.HEAT_COOL assert state.attributes.get(ATTR_CURRENT_HUMIDITY) is None assert state.attributes.get(ATTR_CURRENT_TEMPERATURE) == 21.7 + assert state.attributes.get(ATTR_FAN_MODE) == "40%" + assert state.attributes.get(ATTR_FAN_MODES) == [ + FAN_AUTO, + FAN_LOW, + "40%", + FAN_MEDIUM, + "80%", + FAN_HIGH, + ] assert state.attributes.get(ATTR_HVAC_ACTION) == HVACAction.COOLING assert state.attributes.get(ATTR_HVAC_MODES) == [ HVACMode.FAN_ONLY, @@ -363,6 +407,39 @@ async def test_airzone_climate_set_hvac_slave_error(hass: HomeAssistant) -> None assert state.state == HVACMode.OFF +async def test_airzone_climate_set_fan_mode(hass: HomeAssistant) -> None: + """Test setting the target temperature.""" + + HVAC_MOCK = { + API_DATA: [ + { + API_SYSTEM_ID: 1, + API_ZONE_ID: 1, + API_SPEED: 2, + } + ] + } + + await async_init_integration(hass) + + with patch( + "homeassistant.components.airzone.AirzoneLocalApi.put_hvac", + return_value=HVAC_MOCK, + ): + await hass.services.async_call( + CLIMATE_DOMAIN, + SERVICE_SET_FAN_MODE, + { + ATTR_ENTITY_ID: "climate.salon", + ATTR_FAN_MODE: FAN_MEDIUM, + }, + blocking=True, + ) + + state = hass.states.get("climate.salon") + assert state.attributes.get(ATTR_FAN_MODE) == FAN_MEDIUM + + async def test_airzone_climate_set_temp(hass: HomeAssistant) -> None: """Test setting the target temperature.""" diff --git a/tests/components/airzone/util.py b/tests/components/airzone/util.py index bbbe00a431b..1f7bbd5bd88 100644 --- a/tests/components/airzone/util.py +++ b/tests/components/airzone/util.py @@ -93,6 +93,8 @@ HVAC_MOCK = { API_FLOOR_DEMAND: 0, API_HEAT_ANGLE: 0, API_COLD_ANGLE: 0, + API_SPEED: 0, + API_SPEEDS: 3, }, { API_SYSTEM_ID: 1, @@ -119,6 +121,8 @@ HVAC_MOCK = { API_FLOOR_DEMAND: 1, API_HEAT_ANGLE: 1, API_COLD_ANGLE: 2, + API_SPEED: 0, + API_SPEEDS: 2, }, { API_SYSTEM_ID: 1, @@ -221,6 +225,8 @@ HVAC_MOCK = { API_HUMIDITY: 62, API_UNITS: 0, API_ERRORS: [], + API_SPEED: 0, + API_SPEEDS: 4, }, ] },