"""Support for Fjäråskupan fans.""" from __future__ import annotations from fjaraskupan import ( COMMAND_AFTERCOOKINGTIMERAUTO, COMMAND_AFTERCOOKINGTIMERMANUAL, COMMAND_AFTERCOOKINGTIMEROFF, COMMAND_STOP_FAN, Device, State, ) from homeassistant.components.fan import ( SUPPORT_PRESET_MODE, SUPPORT_SET_SPEED, FanEntity, ) from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant, callback from homeassistant.exceptions import HomeAssistantError from homeassistant.helpers.entity import DeviceInfo from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.update_coordinator import ( CoordinatorEntity, DataUpdateCoordinator, ) from homeassistant.util.percentage import ( ordered_list_item_to_percentage, percentage_to_ordered_list_item, ) from . import DeviceState, async_setup_entry_platform ORDERED_NAMED_FAN_SPEEDS = ["1", "2", "3", "4", "5", "6", "7", "8"] PRESET_MODE_NORMAL = "normal" PRESET_MODE_AFTER_COOKING_MANUAL = "after_cooking_manual" PRESET_MODE_AFTER_COOKING_AUTO = "after_cooking_auto" PRESET_MODE_PERIODIC_VENTILATION = "periodic_ventilation" PRESET_MODES = [ PRESET_MODE_NORMAL, PRESET_MODE_AFTER_COOKING_AUTO, PRESET_MODE_AFTER_COOKING_MANUAL, PRESET_MODE_PERIODIC_VENTILATION, ] PRESET_TO_COMMAND = { PRESET_MODE_AFTER_COOKING_MANUAL: COMMAND_AFTERCOOKINGTIMERMANUAL, PRESET_MODE_AFTER_COOKING_AUTO: COMMAND_AFTERCOOKINGTIMERAUTO, PRESET_MODE_NORMAL: COMMAND_AFTERCOOKINGTIMEROFF, } class UnsupportedPreset(HomeAssistantError): """The preset is unsupported.""" async def async_setup_entry( hass: HomeAssistant, config_entry: ConfigEntry, async_add_entities: AddEntitiesCallback, ) -> None: """Set up sensors dynamically through discovery.""" def _constructor(device_state: DeviceState): return [ Fan(device_state.coordinator, device_state.device, device_state.device_info) ] async_setup_entry_platform(hass, config_entry, async_add_entities, _constructor) class Fan(CoordinatorEntity[State], FanEntity): """Fan entity.""" def __init__( self, coordinator: DataUpdateCoordinator[State], device: Device, device_info: DeviceInfo, ) -> None: """Init fan entity.""" super().__init__(coordinator) self._device = device self._default_on_speed = 25 self._attr_name = device_info["name"] self._attr_unique_id = device.address self._attr_device_info = device_info self._percentage = 0 self._preset_mode = PRESET_MODE_NORMAL self._update_from_device_data(coordinator.data) async def async_set_percentage(self, percentage: int) -> None: """Set speed.""" new_speed = percentage_to_ordered_list_item( ORDERED_NAMED_FAN_SPEEDS, percentage ) await self._device.send_fan_speed(int(new_speed)) self.coordinator.async_set_updated_data(self._device.state) async def async_turn_on( self, speed: str = None, percentage: int = None, preset_mode: str = None, **kwargs, ) -> None: """Turn on the fan.""" if preset_mode is None: preset_mode = self._preset_mode if percentage is None: percentage = self._default_on_speed new_speed = percentage_to_ordered_list_item( ORDERED_NAMED_FAN_SPEEDS, percentage ) async with self._device: if preset_mode != self._preset_mode: if command := PRESET_TO_COMMAND.get(preset_mode): await self._device.send_command(command) else: raise UnsupportedPreset(f"The preset {preset_mode} is unsupported") if preset_mode == PRESET_MODE_NORMAL: await self._device.send_fan_speed(int(new_speed)) elif preset_mode == PRESET_MODE_AFTER_COOKING_MANUAL: await self._device.send_after_cooking(int(new_speed)) elif preset_mode == PRESET_MODE_AFTER_COOKING_AUTO: await self._device.send_after_cooking(0) self.coordinator.async_set_updated_data(self._device.state) async def async_set_preset_mode(self, preset_mode: str) -> None: """Set new preset mode.""" if command := PRESET_TO_COMMAND.get(preset_mode): await self._device.send_command(command) self.coordinator.async_set_updated_data(self._device.state) else: raise UnsupportedPreset(f"The preset {preset_mode} is unsupported") async def async_turn_off(self, **kwargs) -> None: """Turn the entity off.""" await self._device.send_command(COMMAND_STOP_FAN) self.coordinator.async_set_updated_data(self._device.state) @property def speed_count(self) -> int: """Return the number of speeds the fan supports.""" return len(ORDERED_NAMED_FAN_SPEEDS) @property def percentage(self) -> int | None: """Return the current speed.""" return self._percentage @property def supported_features(self) -> int: """Flag supported features.""" return SUPPORT_SET_SPEED | SUPPORT_PRESET_MODE @property def is_on(self) -> bool: """Return true if fan is on.""" return self._percentage != 0 @property def preset_mode(self) -> str | None: """Return the current preset mode.""" return self._preset_mode @property def preset_modes(self) -> list[str] | None: """Return a list of available preset modes.""" return PRESET_MODES def _update_from_device_data(self, data: State | None) -> None: """Handle data update.""" if not data: self._percentage = 0 return if data.fan_speed: self._percentage = ordered_list_item_to_percentage( ORDERED_NAMED_FAN_SPEEDS, str(data.fan_speed) ) else: self._percentage = 0 if data.after_cooking_on: if data.after_cooking_fan_speed: self._preset_mode = PRESET_MODE_AFTER_COOKING_MANUAL else: self._preset_mode = PRESET_MODE_AFTER_COOKING_AUTO elif data.periodic_venting_on: self._preset_mode = PRESET_MODE_PERIODIC_VENTILATION else: self._preset_mode = PRESET_MODE_NORMAL @callback def _handle_coordinator_update(self) -> None: """Handle data update.""" self._update_from_device_data(self.coordinator.data) self.async_write_ha_state()