"""Support for SwitchBot Fans.""" from collections.abc import Mapping import logging from typing import Any, override import switchbot from switchbot import AirPurifierMode, FanMode, StandingFanMode from homeassistant.components.fan import FanEntity, FanEntityFeature from homeassistant.core import HomeAssistant from homeassistant.exceptions import HomeAssistantError from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback from homeassistant.helpers.restore_state import RestoreEntity from .const import DOMAIN from .coordinator import SwitchbotConfigEntry, SwitchbotDataUpdateCoordinator from .entity import SwitchbotEntity, exception_handler _LOGGER = logging.getLogger(__name__) PARALLEL_UPDATES = 0 async def async_setup_entry( hass: HomeAssistant, entry: SwitchbotConfigEntry, async_add_entities: AddConfigEntryEntitiesCallback, ) -> None: """Set up Switchbot fan based on a config entry.""" coordinator = entry.runtime_data if isinstance(coordinator.device, switchbot.SwitchbotAirPurifier): async_add_entities([SwitchBotAirPurifierEntity(coordinator)]) elif isinstance(coordinator.device, switchbot.SwitchbotStandingFan): async_add_entities([SwitchBotStandingFanEntity(coordinator)]) else: async_add_entities([SwitchBotFanEntity(coordinator)]) class SwitchBotFanEntity(SwitchbotEntity, FanEntity, RestoreEntity): """Representation of a Switchbot.""" _device: switchbot.SwitchbotFan _attr_supported_features = ( FanEntityFeature.SET_SPEED | FanEntityFeature.OSCILLATE | FanEntityFeature.PRESET_MODE | FanEntityFeature.TURN_OFF | FanEntityFeature.TURN_ON ) _attr_preset_modes = FanMode.get_modes() _attr_translation_key = "fan" _attr_name = None def __init__(self, coordinator: SwitchbotDataUpdateCoordinator) -> None: """Initialize the switchbot.""" super().__init__(coordinator) self._attr_is_on = False @property @override def is_on(self) -> bool | None: """Return true if device is on.""" return self._device.is_on() @property @override def percentage(self) -> int | None: """Return the current speed as a percentage.""" return self._device.get_current_percentage() @property @override def oscillating(self) -> bool | None: """Return whether or not the fan is currently oscillating.""" return self._device.get_oscillating_state() @property @override def preset_mode(self) -> str | None: """Return the current preset mode.""" return self._device.get_current_mode() @override async def async_set_preset_mode(self, preset_mode: str) -> None: """Set the preset mode of the fan.""" _LOGGER.debug( "Switchbot fan to set preset mode %s %s", preset_mode, self._address ) self._last_run_success = bool(await self._device.set_preset_mode(preset_mode)) self.async_write_ha_state() @override async def async_set_percentage(self, percentage: int) -> None: """Set the speed percentage of the fan.""" _LOGGER.debug( "Switchbot fan to set percentage %d %s", percentage, self._address ) self._last_run_success = bool(await self._device.set_percentage(percentage)) self.async_write_ha_state() @override async def async_oscillate(self, oscillating: bool) -> None: """Oscillate the fan.""" _LOGGER.debug( "Switchbot fan to set oscillating %s %s", oscillating, self._address ) self._last_run_success = bool(await self._device.set_oscillation(oscillating)) self.async_write_ha_state() @override async def async_turn_on( self, percentage: int | None = None, preset_mode: str | None = None, **kwargs: Any, ) -> None: """Turn on the fan.""" _LOGGER.debug( "Switchbot fan to set turn on %s %s %s", percentage, preset_mode, self._address, ) self._last_run_success = bool(await self._device.turn_on()) self.async_write_ha_state() @override async def async_turn_off(self, **kwargs: Any) -> None: """Turn off the fan.""" _LOGGER.debug("Switchbot fan to set turn off %s", self._address) self._last_run_success = bool(await self._device.turn_off()) self.async_write_ha_state() class SwitchBotStandingFanEntity(SwitchBotFanEntity): """Representation of a Switchbot Standing Fan.""" _device: switchbot.SwitchbotStandingFan _attr_preset_modes = StandingFanMode.get_modes() _attr_translation_key = "standing_fan" @property @override def extra_state_attributes(self) -> Mapping[str, Any]: """Return the state attributes. Failures surface as HomeAssistantError, so last_run_success is not used. """ return {} @exception_handler @override async def async_set_preset_mode(self, preset_mode: str) -> None: """Set the preset mode of the fan.""" _LOGGER.debug( "Switchbot standing fan to set preset mode %s %s", preset_mode, self._address, ) if not await self._device.set_preset_mode(preset_mode): raise HomeAssistantError( translation_domain=DOMAIN, translation_key="set_fan_state_failed" ) self.async_write_ha_state() @exception_handler @override async def async_set_percentage(self, percentage: int) -> None: """Set the speed percentage of the fan.""" _LOGGER.debug( "Switchbot standing fan to set percentage %d %s", percentage, self._address ) if not await self._device.set_percentage(percentage): raise HomeAssistantError( translation_domain=DOMAIN, translation_key="set_fan_state_failed" ) self.async_write_ha_state() @exception_handler @override async def async_oscillate(self, oscillating: bool) -> None: """Oscillate the fan.""" _LOGGER.debug( "Switchbot standing fan to set oscillating %s %s", oscillating, self._address, ) if not await self._device.set_oscillation(oscillating): raise HomeAssistantError( translation_domain=DOMAIN, translation_key="set_fan_state_failed" ) self.async_write_ha_state() @exception_handler @override async def async_turn_on( self, percentage: int | None = None, preset_mode: str | None = None, **kwargs: Any, ) -> None: """Turn on the fan.""" _LOGGER.debug( "Switchbot standing fan to set turn on %s %s %s", percentage, preset_mode, self._address, ) if not await self._device.turn_on(): raise HomeAssistantError( translation_domain=DOMAIN, translation_key="set_fan_state_failed" ) self.async_write_ha_state() @exception_handler @override async def async_turn_off(self, **kwargs: Any) -> None: """Turn off the fan.""" _LOGGER.debug("Switchbot standing fan to set turn off %s", self._address) if not await self._device.turn_off(): raise HomeAssistantError( translation_domain=DOMAIN, translation_key="set_fan_state_failed" ) self.async_write_ha_state() class SwitchBotAirPurifierEntity(SwitchbotEntity, FanEntity): """Representation of a Switchbot air purifier.""" _device: switchbot.SwitchbotAirPurifier _attr_supported_features = ( FanEntityFeature.PRESET_MODE | FanEntityFeature.SET_SPEED | FanEntityFeature.TURN_OFF | FanEntityFeature.TURN_ON ) _attr_preset_modes = AirPurifierMode.get_modes() _attr_translation_key = "air_purifier" _attr_name = None @property @override def is_on(self) -> bool | None: """Return true if device is on.""" return self._device.is_on() @property @override def preset_mode(self) -> str | None: """Return the current preset mode.""" return self._device.get_current_mode() @property @override def percentage(self) -> int | None: """Return the speed percentage of the air purifier.""" return self._device.get_current_percentage() @exception_handler @override async def async_set_preset_mode(self, preset_mode: str) -> None: """Set the preset mode of the air purifier.""" _LOGGER.debug( "Switchbot air purifier to set preset mode %s %s", preset_mode, self._address, ) self._last_run_success = bool(await self._device.set_preset_mode(preset_mode)) self.async_write_ha_state() @exception_handler @override async def async_set_percentage(self, percentage: int) -> None: """Set the speed percentage of the air purifier.""" _LOGGER.debug( "Switchbot air purifier to set percentage %d %s", percentage, self._address ) await self._device.set_percentage(percentage) self.async_write_ha_state() @exception_handler @override async def async_turn_on( self, percentage: int | None = None, preset_mode: str | None = None, **kwargs: Any, ) -> None: """Turn on the air purifier.""" _LOGGER.debug( "Switchbot air purifier to set turn on %s %s %s", percentage, preset_mode, self._address, ) self._last_run_success = bool(await self._device.turn_on()) self.async_write_ha_state() @exception_handler @override async def async_turn_off(self, **kwargs: Any) -> None: """Turn off the air purifier.""" _LOGGER.debug("Switchbot air purifier to set turn off %s", self._address) self._last_run_success = bool(await self._device.turn_off()) self.async_write_ha_state()