diff --git a/homeassistant/components/zha/fan.py b/homeassistant/components/zha/fan.py index 5473b7f0183..b8cf2cd0339 100644 --- a/homeassistant/components/zha/fan.py +++ b/homeassistant/components/zha/fan.py @@ -45,9 +45,6 @@ PRESET_MODE_SMART = "smart" SPEED_RANGE = (1, 3) # off is not included PRESET_MODES_TO_NAME = {4: PRESET_MODE_ON, 5: PRESET_MODE_AUTO, 6: PRESET_MODE_SMART} -NAME_TO_PRESET_MODE = {v: k for k, v in PRESET_MODES_TO_NAME.items()} -PRESET_MODES = list(NAME_TO_PRESET_MODE) - DEFAULT_ON_PERCENTAGE = 50 STRICT_MATCH = functools.partial(ZHA_ENTITIES.strict_match, Platform.FAN) @@ -85,12 +82,32 @@ class BaseFan(FanEntity): @property def preset_modes(self) -> list[str]: """Return the available preset modes.""" - return PRESET_MODES + return list(self.preset_modes_to_name.values()) + + @property + def preset_modes_to_name(self) -> dict[int, str]: + """Return a dict from preset mode to name.""" + return PRESET_MODES_TO_NAME + + @property + def preset_name_to_mode(self) -> dict[str, int]: + """Return a dict from preset name to mode.""" + return {v: k for k, v in self.preset_modes_to_name.items()} + + @property + def default_on_percentage(self) -> int: + """Return the default on percentage.""" + return DEFAULT_ON_PERCENTAGE + + @property + def speed_range(self) -> tuple[int, int]: + """Return the range of speeds the fan supports. Off is not included.""" + return SPEED_RANGE @property def speed_count(self) -> int: """Return the number of speeds the fan supports.""" - return int_states_in_range(SPEED_RANGE) + return int_states_in_range(self.speed_range) async def async_turn_on( self, @@ -100,7 +117,7 @@ class BaseFan(FanEntity): ) -> None: """Turn the entity on.""" if percentage is None: - percentage = DEFAULT_ON_PERCENTAGE + percentage = self.default_on_percentage await self.async_set_percentage(percentage) async def async_turn_off(self, **kwargs: Any) -> None: @@ -109,7 +126,7 @@ class BaseFan(FanEntity): async def async_set_percentage(self, percentage: int) -> None: """Set the speed percentage of the fan.""" - fan_mode = math.ceil(percentage_to_ranged_value(SPEED_RANGE, percentage)) + fan_mode = math.ceil(percentage_to_ranged_value(self.speed_range, percentage)) await self._async_set_fan_mode(fan_mode) async def async_set_preset_mode(self, preset_mode: str) -> None: @@ -119,7 +136,7 @@ class BaseFan(FanEntity): f"The preset_mode {preset_mode} is not a valid preset_mode:" f" {self.preset_modes}" ) - await self._async_set_fan_mode(NAME_TO_PRESET_MODE[preset_mode]) + await self._async_set_fan_mode(self.preset_name_to_mode[preset_mode]) @abstractmethod async def _async_set_fan_mode(self, fan_mode: int) -> None: @@ -151,19 +168,19 @@ class ZhaFan(BaseFan, ZhaEntity): """Return the current speed percentage.""" if ( self._fan_cluster_handler.fan_mode is None - or self._fan_cluster_handler.fan_mode > SPEED_RANGE[1] + or self._fan_cluster_handler.fan_mode > self.speed_range[1] ): return None if self._fan_cluster_handler.fan_mode == 0: return 0 return ranged_value_to_percentage( - SPEED_RANGE, self._fan_cluster_handler.fan_mode + self.speed_range, self._fan_cluster_handler.fan_mode ) @property def preset_mode(self) -> str | None: """Return the current preset mode.""" - return PRESET_MODES_TO_NAME.get(self._fan_cluster_handler.fan_mode) + return self.preset_modes_to_name.get(self._fan_cluster_handler.fan_mode) @callback def async_set_state(self, attr_id, attr_name, value): @@ -252,95 +269,33 @@ IKEA_PRESET_MODES_TO_NAME = { 9: "Speed 4.5", 10: "Speed 5", } -IKEA_NAME_TO_PRESET_MODE = {v: k for k, v in IKEA_PRESET_MODES_TO_NAME.items()} -IKEA_PRESET_MODES = list(IKEA_NAME_TO_PRESET_MODE) @MULTI_MATCH( cluster_handler_names="ikea_airpurifier", models={"STARKVIND Air purifier", "STARKVIND Air purifier table"}, ) -class IkeaFan(BaseFan, ZhaEntity): - """Representation of a ZHA fan.""" +class IkeaFan(ZhaFan): + """Representation of an Ikea fan.""" - def __init__(self, unique_id, zha_device, cluster_handlers, **kwargs): + def __init__(self, unique_id, zha_device, cluster_handlers, **kwargs) -> None: """Init this sensor.""" super().__init__(unique_id, zha_device, cluster_handlers, **kwargs) self._fan_cluster_handler = self.cluster_handlers.get("ikea_airpurifier") - async def async_added_to_hass(self) -> None: - """Run when about to be added to hass.""" - await super().async_added_to_hass() - self.async_accept_signal( - self._fan_cluster_handler, SIGNAL_ATTR_UPDATED, self.async_set_state + @property + def preset_modes_to_name(self) -> dict[int, str]: + """Return a dict from preset mode to name.""" + return IKEA_PRESET_MODES_TO_NAME + + @property + def speed_range(self) -> tuple[int, int]: + """Return the range of speeds the fan supports. Off is not included.""" + return IKEA_SPEED_RANGE + + @property + def default_on_percentage(self) -> int: + """Return the default on percentage.""" + return int( + (100 / self.speed_count) * self.preset_name_to_mode[PRESET_MODE_AUTO] ) - - @property - def preset_modes(self) -> list[str]: - """Return the available preset modes.""" - return IKEA_PRESET_MODES - - @property - def speed_count(self) -> int: - """Return the number of speeds the fan supports.""" - return int_states_in_range(IKEA_SPEED_RANGE) - - async def async_set_percentage(self, percentage: int) -> None: - """Set the speed percentage of the fan.""" - fan_mode = math.ceil(percentage_to_ranged_value(IKEA_SPEED_RANGE, percentage)) - await self._async_set_fan_mode(fan_mode) - - async def async_set_preset_mode(self, preset_mode: str) -> None: - """Set the preset mode for the fan.""" - if preset_mode not in self.preset_modes: - raise NotValidPresetModeError( - f"The preset_mode {preset_mode} is not a valid preset_mode:" - f" {self.preset_modes}" - ) - await self._async_set_fan_mode(IKEA_NAME_TO_PRESET_MODE[preset_mode]) - - @property - def percentage(self) -> int | None: - """Return the current speed percentage.""" - if ( - self._fan_cluster_handler.fan_mode is None - or self._fan_cluster_handler.fan_mode > IKEA_SPEED_RANGE[1] - ): - return None - if self._fan_cluster_handler.fan_mode == 0: - return 0 - return ranged_value_to_percentage( - IKEA_SPEED_RANGE, self._fan_cluster_handler.fan_mode - ) - - @property - def preset_mode(self) -> str | None: - """Return the current preset mode.""" - return IKEA_PRESET_MODES_TO_NAME.get(self._fan_cluster_handler.fan_mode) - - async def async_turn_on( - self, - percentage: int | None = None, - preset_mode: str | None = None, - **kwargs: Any, - ) -> None: - """Turn the entity on.""" - if percentage is None: - percentage = int( - (100 / self.speed_count) * IKEA_NAME_TO_PRESET_MODE[PRESET_MODE_AUTO] - ) - await self.async_set_percentage(percentage) - - async def async_turn_off(self, **kwargs: Any) -> None: - """Turn the entity off.""" - await self.async_set_percentage(0) - - @callback - def async_set_state(self, attr_id, attr_name, value): - """Handle state update from cluster handler.""" - self.async_write_ha_state() - - async def _async_set_fan_mode(self, fan_mode: int) -> None: - """Set the fan mode for the fan.""" - await self._fan_cluster_handler.async_set_speed(fan_mode) - self.async_set_state(0, "fan_mode", fan_mode)