"""Support for Homekit fans.""" import logging from homekit.model.characteristics import CharacteristicsTypes from homeassistant.components.fan import ( DIRECTION_FORWARD, DIRECTION_REVERSE, SPEED_HIGH, SPEED_LOW, SPEED_MEDIUM, SPEED_OFF, SUPPORT_DIRECTION, SUPPORT_OSCILLATE, SUPPORT_SET_SPEED, FanEntity, ) from . import KNOWN_DEVICES, HomeKitEntity _LOGGER = logging.getLogger(__name__) # 0 is clockwise, 1 is counter-clockwise. The match to forward and reverse is so that # its consistent with homeassistant.components.homekit. DIRECTION_TO_HK = { DIRECTION_REVERSE: 1, DIRECTION_FORWARD: 0, } HK_DIRECTION_TO_HA = {v: k for (k, v) in DIRECTION_TO_HK.items()} SPEED_TO_PCNT = { SPEED_HIGH: 100, SPEED_MEDIUM: 50, SPEED_LOW: 25, SPEED_OFF: 0, } class BaseHomeKitFan(HomeKitEntity, FanEntity): """Representation of a Homekit fan.""" # This must be set in subclasses to the name of a boolean characteristic # that controls whether the fan is on or off. on_characteristic = None def __init__(self, *args): """Initialise the fan.""" self._on = None self._features = 0 self._rotation_direction = 0 self._rotation_speed = 0 self._swing_mode = 0 super().__init__(*args) def get_characteristic_types(self): """Define the homekit characteristics the entity cares about.""" return [ CharacteristicsTypes.SWING_MODE, CharacteristicsTypes.ROTATION_DIRECTION, CharacteristicsTypes.ROTATION_SPEED, ] def _setup_rotation_direction(self, char): self._features |= SUPPORT_DIRECTION def _setup_rotation_speed(self, char): self._features |= SUPPORT_SET_SPEED def _setup_swing_mode(self, char): self._features |= SUPPORT_OSCILLATE def _update_rotation_direction(self, value): self._rotation_direction = value def _update_rotation_speed(self, value): self._rotation_speed = value def _update_swing_mode(self, value): self._swing_mode = value @property def is_on(self): """Return true if device is on.""" return self._on @property def speed(self): """Return the current speed.""" if not self.is_on: return SPEED_OFF if self._rotation_speed > SPEED_TO_PCNT[SPEED_MEDIUM]: return SPEED_HIGH if self._rotation_speed > SPEED_TO_PCNT[SPEED_LOW]: return SPEED_MEDIUM if self._rotation_speed > SPEED_TO_PCNT[SPEED_OFF]: return SPEED_LOW return SPEED_OFF @property def speed_list(self): """Get the list of available speeds.""" if self.supported_features & SUPPORT_SET_SPEED: return [SPEED_OFF, SPEED_LOW, SPEED_MEDIUM, SPEED_HIGH] return [] @property def current_direction(self): """Return the current direction of the fan.""" return HK_DIRECTION_TO_HA[self._rotation_direction] @property def oscillating(self): """Return whether or not the fan is currently oscillating.""" return self._swing_mode == 1 @property def supported_features(self): """Flag supported features.""" return self._features async def async_set_direction(self, direction): """Set the direction of the fan.""" if self.supported_features & SUPPORT_DIRECTION: await self._accessory.put_characteristics( [ { "aid": self._aid, "iid": self._chars["rotation.direction"], "value": DIRECTION_TO_HK[direction], } ] ) async def async_set_speed(self, speed): """Set the speed of the fan.""" if speed == SPEED_OFF: return await self.async_turn_off() if self.supported_features & SUPPORT_SET_SPEED: await self._accessory.put_characteristics( [ { "aid": self._aid, "iid": self._chars["rotation.speed"], "value": SPEED_TO_PCNT[speed], } ] ) async def async_oscillate(self, oscillating: bool): """Oscillate the fan.""" if self.supported_features & SUPPORT_OSCILLATE: await self._accessory.put_characteristics( [ { "aid": self._aid, "iid": self._chars["swing-mode"], "value": 1 if oscillating else 0, } ] ) async def async_turn_on(self, speed=None, **kwargs): """Turn the specified fan on.""" characteristics = [] if not self.is_on: characteristics.append( { "aid": self._aid, "iid": self._chars[self.on_characteristic], "value": True, } ) if self.supported_features & SUPPORT_SET_SPEED and speed: characteristics.append( { "aid": self._aid, "iid": self._chars["rotation.speed"], "value": SPEED_TO_PCNT[speed], }, ) if not characteristics: return await self._accessory.put_characteristics(characteristics) async def async_turn_off(self, **kwargs): """Turn the specified fan off.""" characteristics = [ { "aid": self._aid, "iid": self._chars[self.on_characteristic], "value": False, } ] await self._accessory.put_characteristics(characteristics) class HomeKitFanV1(BaseHomeKitFan): """Implement fan support for public.hap.service.fan.""" on_characteristic = "on" def get_characteristic_types(self): """Define the homekit characteristics the entity cares about.""" return [CharacteristicsTypes.ON] + super().get_characteristic_types() def _update_on(self, value): self._on = value == 1 class HomeKitFanV2(BaseHomeKitFan): """Implement fan support for public.hap.service.fanv2.""" on_characteristic = "active" def get_characteristic_types(self): """Define the homekit characteristics the entity cares about.""" return [CharacteristicsTypes.ACTIVE] + super().get_characteristic_types() def _update_active(self, value): self._on = value == 1 ENTITY_TYPES = { "fan": HomeKitFanV1, "fanv2": HomeKitFanV2, } async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): """Legacy set up platform.""" pass async def async_setup_entry(hass, config_entry, async_add_entities): """Set up Homekit fans.""" hkid = config_entry.data["AccessoryPairingID"] conn = hass.data[KNOWN_DEVICES][hkid] def async_add_service(aid, service): entity_class = ENTITY_TYPES.get(service["stype"]) if not entity_class: return False info = {"aid": aid, "iid": service["iid"]} async_add_entities([entity_class(conn, info)], True) return True conn.add_listener(async_add_service)