"""Midea Fan entries.""" import logging from typing import Any, cast from homeassistant.components.fan import FanEntity, FanEntityFeature from homeassistant.config_entries import ConfigEntry from homeassistant.const import ( CONF_DEVICE_ID, CONF_SWITCHES, Platform, ) from homeassistant.core import HomeAssistant from homeassistant.helpers.entity_platform import AddEntitiesCallback from midealocal.device import DeviceType from midealocal.devices.ac import DeviceAttributes as ACAttributes from midealocal.devices.ac import MideaACDevice from midealocal.devices.b6 import MideaB6Device from midealocal.devices.ce import DeviceAttributes as CEAttributes from midealocal.devices.ce import MideaCEDevice from midealocal.devices.fa import MideaFADevice from midealocal.devices.x40 import DeviceAttributes as X40Attributes from midealocal.devices.x40 import MideaX40Device from .const import DEVICES, DOMAIN from .midea_devices import ( FRESH_AIR_EXHAUST, FRESH_AIR_EXHAUST_MODE, FRESH_AIR_EXHAUST_POWER, FRESH_AIR_EXHAUST_SPEED, MIDEA_DEVICES, ) from .midea_entity import MideaEntity _LOGGER = logging.getLogger(__name__) async def async_setup_entry( hass: HomeAssistant, config_entry: ConfigEntry, async_add_entities: AddEntitiesCallback, ) -> None: """Set up fan entries.""" device_id = config_entry.data.get(CONF_DEVICE_ID) device = hass.data[DOMAIN][DEVICES].get(device_id) extra_switches = config_entry.options.get(CONF_SWITCHES, []) devs: list[ MideaFAFan | MideaB6Fan | MideaACFreshAirFan | MideaCEFan | MideaX40Fan ] = [] for entity_key, config in cast( "dict", MIDEA_DEVICES[device.device_type]["entities"], ).items(): required_attribute = config.get("required_attribute") if ( required_attribute is not None and required_attribute not in device.attributes ): continue if config["type"] == Platform.FAN and ( config.get("default") or entity_key in extra_switches ): if device.device_type == DeviceType.FA: devs.append(MideaFAFan(device, entity_key)) elif device.device_type == DeviceType.B6: devs.append(MideaB6Fan(device, entity_key)) elif device.device_type == DeviceType.AC: devs.append(MideaACFreshAirFan(device, entity_key)) elif device.device_type == DeviceType.CE: devs.append(MideaCEFan(device, entity_key)) elif device.device_type == DeviceType.X40: devs.append(MideaX40Fan(device, entity_key)) async_add_entities(devs) # HA version >= 2024,8 support TURN_ON | TURN_OFF,for future changes, ref PR #285 try: FAN_FEATURE_TURN_ON_OFF = FanEntityFeature["TURN_ON"] | FanEntityFeature["TURN_OFF"] except KeyError: FAN_FEATURE_TURN_ON_OFF = FanEntityFeature(0) class MideaFan(MideaEntity, FanEntity): """Midea Fan Entries Base Class.""" _enable_turn_on_off_backwards_compatibility = False # 2024.8~2025.1 @property def preset_modes(self) -> list[str] | None: """Midea Fan preset modes.""" return ( self._device.preset_modes if hasattr(self._device, "preset_modes") else None ) @property def is_on(self) -> bool: """Midea Fan is on.""" return cast("bool", self._device.get_attribute("power")) @property def oscillating(self) -> bool: """Midea Fan oscillating.""" return cast("bool", self._device.get_attribute("oscillate")) @property def preset_mode(self) -> str | None: """Midea Fan preset mode.""" return cast("str", self._device.get_attribute("mode")) @property def fan_speed(self) -> int | None: """Midea Fan fan speed.""" return cast("int", self._device.get_attribute("fan_speed")) def turn_off(self, **kwargs: Any) -> None: # ruff:ignore[any-type, unused-method-argument] """Midea Fan turn off.""" self._device.set_attribute(attr="power", value=False) def oscillate(self, oscillating: bool) -> None: """Midea Fan oscillate.""" self._device.set_attribute(attr="oscillate", value=oscillating) def set_preset_mode(self, preset_mode: str) -> None: """Midea Fan set preset mode.""" self._device.set_attribute(attr="mode", value=preset_mode.capitalize()) @property def percentage(self) -> int | None: """Midea Fan percentage.""" if not self.fan_speed: return None return int(round(self.fan_speed * self.percentage_step)) # ruff:ignore[unnecessary-cast-to-int] def set_percentage(self, percentage: int) -> None: """Midea Fan set percentage.""" fan_speed = round(percentage / self.percentage_step) self._device.set_attribute(attr="fan_speed", value=fan_speed) async def async_set_percentage(self, percentage: int) -> None: """Midea Fan async set percentage.""" if percentage == 0: await self.async_turn_off() else: await self.hass.async_add_executor_job(self.set_percentage, percentage) def update_state(self, status: Any) -> None: # ruff:ignore[any-type, unused-method-argument] """Midea Fan update state.""" if not self.hass: _LOGGER.warning( "Fan update_state skipped for %s [%s]: HASS is None", self.name, type(self), ) return self.schedule_update_if_running() class MideaFAFan(MideaFan): """Midea FA Fan Entries.""" _device: MideaFADevice def __init__(self, device: MideaFADevice, entity_key: str) -> None: """Midea FA Fan entity init.""" super().__init__(device, entity_key) self._attr_supported_features = ( FanEntityFeature.SET_SPEED | FanEntityFeature.OSCILLATE | FanEntityFeature.PRESET_MODE | FAN_FEATURE_TURN_ON_OFF ) self._attr_speed_count = self._device.speed_count def turn_on( self, percentage: int | None = None, preset_mode: str | None = None, **kwargs: Any, # ruff:ignore[any-type, unused-method-argument] ) -> None: """Midea FA Fan turn on.""" fan_speed = int(percentage / self.percentage_step + 0.5) if percentage else None self._device.turn_on(fan_speed=fan_speed, mode=preset_mode) class MideaB6Fan(MideaFan): """Midea B6 Fan Entries.""" _device: MideaB6Device def __init__(self, device: MideaB6Device, entity_key: str) -> None: """Midea B6 Fan entity init.""" super().__init__(device, entity_key) self._attr_supported_features = ( FanEntityFeature.SET_SPEED | FanEntityFeature.PRESET_MODE | FAN_FEATURE_TURN_ON_OFF ) self._attr_speed_count = self._device.speed_count def turn_on( self, percentage: int | None = None, preset_mode: str | None = None, **kwargs: Any, # ruff:ignore[any-type, unused-method-argument] ) -> None: """Midea B6 Fan turn on.""" fan_speed = int(percentage / self.percentage_step + 0.5) if percentage else None self._device.turn_on(fan_speed=fan_speed, mode=preset_mode) class MideaACFreshAirFan(MideaFan): """Midea AC Fresh Air Fan Entries.""" _device: MideaACDevice def __init__(self, device: MideaACDevice, entity_key: str) -> None: """Midea AC Fresh Air Fan entity init.""" super().__init__(device, entity_key) self._is_exhaust = entity_key == FRESH_AIR_EXHAUST self._attr_supported_features = ( FanEntityFeature.SET_SPEED | FanEntityFeature.PRESET_MODE | FAN_FEATURE_TURN_ON_OFF ) self._attr_speed_count = 100 @property def preset_modes(self) -> list[str] | None: """Midea AC Fan preset modes.""" if self._is_exhaust: return cast( "list[str]", getattr(self._device, "fresh_air_exhaust_fan_speeds", []), ) return cast("list", self._device.fresh_air_fan_speeds) @property def is_on(self) -> bool: """Midea AC Fan is on.""" attribute = ( FRESH_AIR_EXHAUST_POWER if self._is_exhaust else ACAttributes.fresh_air_power ) return cast("bool", self._device.get_attribute(attribute)) @property def fan_speed(self) -> int: """Midea AC Fan fan speed.""" attribute = ( FRESH_AIR_EXHAUST_SPEED if self._is_exhaust else ACAttributes.fresh_air_fan_speed ) return cast("int", self._device.get_attribute(attribute)) def turn_on( self, percentage: int | None = None, preset_mode: str | None = None, **kwargs: Any, # ruff:ignore[any-type, unused-method-argument] ) -> None: """Midea AC Fan tun on.""" if preset_mode is not None: self.set_preset_mode(preset_mode) return if percentage is not None: self.set_percentage(percentage) return attribute = ( FRESH_AIR_EXHAUST_POWER if self._is_exhaust else ACAttributes.fresh_air_power ) self._device.set_attribute(attr=attribute, value=True) def turn_off(self, **kwargs: Any) -> None: # ruff:ignore[any-type, unused-method-argument] """Midea AC Fan turn off.""" attribute = ( FRESH_AIR_EXHAUST_POWER if self._is_exhaust else ACAttributes.fresh_air_power ) self._device.set_attribute(attr=attribute, value=False) def set_percentage(self, percentage: int) -> None: """Midea AC Fan set percentage.""" fan_speed = int(percentage / self.percentage_step + 0.5) attribute = ( FRESH_AIR_EXHAUST_SPEED if self._is_exhaust else ACAttributes.fresh_air_fan_speed ) self._device.set_attribute( attr=attribute, value=fan_speed, ) def set_preset_mode(self, preset_mode: str) -> None: """Midea AC Fan set preset mode.""" attribute = ( FRESH_AIR_EXHAUST_MODE if self._is_exhaust else ACAttributes.fresh_air_mode ) self._device.set_attribute(attr=attribute, value=preset_mode) @property def preset_mode(self) -> str | None: """Midea AC Fan preset mode.""" attribute = ( FRESH_AIR_EXHAUST_MODE if self._is_exhaust else ACAttributes.fresh_air_mode ) return cast("str", self._device.get_attribute(attr=attribute)) class MideaCEFan(MideaFan): """Midea CE Fan Entries.""" _device: MideaCEDevice def __init__(self, device: MideaCEDevice, entity_key: str) -> None: """Midea CE Fan entity init.""" super().__init__(device, entity_key) self._attr_supported_features = ( FanEntityFeature.SET_SPEED | FanEntityFeature.PRESET_MODE | FAN_FEATURE_TURN_ON_OFF ) self._attr_speed_count = self._device.speed_count def turn_on( self, percentage: int | None = None, # ruff:ignore[unused-method-argument] preset_mode: str | None = None, # ruff:ignore[unused-method-argument] **kwargs: Any, # ruff:ignore[any-type, unused-method-argument] ) -> None: """Midea CE Fan turn on.""" self._device.set_attribute(attr=CEAttributes.power, value=True) async def async_set_percentage(self, percentage: int) -> None: """Midea CE Fan async set percentage.""" await self.hass.async_add_executor_job(self.set_percentage, percentage) class MideaX40Fan(MideaFan): """Midea X40 Fan Entries.""" _device: MideaX40Device def __init__(self, device: MideaX40Device, entity_key: str) -> None: """Midea X40 Fan entity init.""" super().__init__(device, entity_key) self._attr_supported_features = ( FanEntityFeature.SET_SPEED | FanEntityFeature.OSCILLATE | FAN_FEATURE_TURN_ON_OFF ) self._attr_speed_count = 2 @property def is_on(self) -> bool: """Midea X40 Fan is on.""" return cast("int", self._device.get_attribute(attr=X40Attributes.fan_speed)) > 0 def turn_on( self, percentage: int | None = None, # ruff:ignore[unused-method-argument] preset_mode: str | None = None, # ruff:ignore[unused-method-argument] **kwargs: Any, # ruff:ignore[any-type, unused-method-argument] ) -> None: """Midea X40 Fan turn on.""" self._device.set_attribute(attr=X40Attributes.fan_speed, value=1) def turn_off(self, **kwargs: Any) -> None: # ruff:ignore[any-type, unused-method-argument] """Midea X40 Fan turn off.""" self._device.set_attribute(attr=X40Attributes.fan_speed, value=0)