""" Platform for Lutron fans. Provides fan functionality for Home Assistant. """ import asyncio import logging from homeassistant.components.fan import ( SPEED_LOW, SPEED_MEDIUM, SPEED_HIGH, SPEED_OFF, SUPPORT_SET_SPEED, FanEntity, DOMAIN, ) from homeassistant.const import CONF_DEVICES, CONF_HOST, CONF_MAC, CONF_NAME, CONF_ID from . import ( Caseta, ATTR_AREA_NAME, CONF_AREA_NAME, ATTR_INTEGRATION_ID, DOMAIN as COMPONENT_DOMAIN, ) _LOGGER = logging.getLogger(__name__) SPEED_MEDIUM_HIGH = "medium_high" SPEED_MAPPING = { SPEED_OFF: 0.00, SPEED_LOW: 25.10, SPEED_MEDIUM: 50.20, SPEED_MEDIUM_HIGH: 75.30, SPEED_HIGH: 100.00, } class CasetaData: """Data holder for a fan.""" def __init__(self, caseta): """Initialize the data holder.""" self._caseta = caseta self._devices = [] @property def devices(self): """Return the device list.""" return self._devices @property def caseta(self): """Return a reference to Casetify instance.""" return self._caseta def set_devices(self, devices): """Set the device list.""" self._devices = devices @asyncio.coroutine def read_output(self, mode, integration, action, value): """Receive output value from the bridge.""" # find integration ID in devices if mode == Caseta.OUTPUT: for device in self._devices: if device.integration == integration: _LOGGER.debug( "Got fan OUTPUT value: %s %d %d %.2f", mode, integration, action, value, ) if action == Caseta.Action.SET: device.update_state(value) if device.hass is not None: yield from device.async_update_ha_state() break # pylint: disable=unused-argument @asyncio.coroutine def async_setup_platform(hass, config, async_add_devices, discovery_info=None): """Configure the platform.""" if discovery_info is None: return bridge = Caseta(discovery_info[CONF_HOST]) yield from bridge.open() data = CasetaData(bridge) devices = [ CasetaFan(fan, data, discovery_info[CONF_MAC]) for fan in discovery_info[CONF_DEVICES] ] data.set_devices(devices) async_add_devices(devices, True) # register callbacks bridge.register(data.read_output) # start bridge main loop bridge.start(hass) class CasetaFan(FanEntity): """Representation of a Lutron fan.""" def __init__(self, fan, data, mac): """Initialize a Lutron fan.""" self._data = data self._name = fan[CONF_NAME] self._area_name = None if CONF_AREA_NAME in fan: self._area_name = fan[CONF_AREA_NAME] # if available, prepend area name to fan self._name = fan[CONF_AREA_NAME] + " " + fan[CONF_NAME] self._integration = int(fan[CONF_ID]) self._is_on = False self._mac = mac self._speed = SPEED_OFF @asyncio.coroutine def async_added_to_hass(self): """Update initial state.""" yield from self.query() @asyncio.coroutine def query(self): """Query the bridge for the current level.""" yield from self._data.caseta.query( Caseta.OUTPUT, self._integration, Caseta.Action.SET ) @property def integration(self): """Return the Integration ID.""" return self._integration @property def unique_id(self) -> str: """Return a unique ID.""" if self._mac is not None: return "{}_{}_{}_{}".format( COMPONENT_DOMAIN, DOMAIN, self._mac, self._integration ) return None @property def name(self): """Return the display name of this fan.""" return self._name @property def should_poll(self): """No polling needed for fan.""" return False @property def device_state_attributes(self): """Return device specific state attributes.""" attr = {ATTR_INTEGRATION_ID: self._integration} if self._area_name: attr[ATTR_AREA_NAME] = self._area_name return attr @property def is_on(self): """Return true if fan is on.""" return self._is_on @property def speed(self) -> str: """Return the current speed.""" return self._speed @property def speed_list(self) -> list: """Get the list of available speeds.""" return [SPEED_OFF, SPEED_LOW, SPEED_MEDIUM, SPEED_MEDIUM_HIGH, SPEED_HIGH] @property def supported_features(self) -> int: """Flag supported features.""" return SUPPORT_SET_SPEED async def async_turn_on(self, speed: str = None, **kwargs) -> None: """Instruct the fan to turn on.""" if speed is None: speed = SPEED_HIGH await self.async_set_speed(speed) async def async_set_speed(self, speed: str) -> None: """Set the speed of the fan.""" self._speed = speed if speed not in SPEED_MAPPING: _LOGGER.debug("Unknown speed %s, setting to %s", speed, SPEED_HIGH) self._speed = SPEED_HIGH _LOGGER.debug( "Writing fan OUTPUT value: %d %d %.2f", self._integration, Caseta.Action.SET, SPEED_MAPPING[self._speed], ) await self._data.caseta.write( Caseta.OUTPUT, self._integration, Caseta.Action.SET, SPEED_MAPPING[self._speed], ) async def async_turn_off(self, **kwargs) -> None: """Instruct the fan to turn off.""" await self.async_set_speed(SPEED_OFF) def update_state(self, value): """Update internal state and fan speed.""" self._is_on = value > SPEED_MAPPING[SPEED_OFF] if SPEED_MAPPING[SPEED_MEDIUM_HIGH] < value <= SPEED_MAPPING[SPEED_HIGH]: self._speed = SPEED_HIGH elif SPEED_MAPPING[SPEED_MEDIUM] < value <= SPEED_MAPPING[SPEED_MEDIUM_HIGH]: self._speed = SPEED_MEDIUM_HIGH elif SPEED_MAPPING[SPEED_LOW] < value <= SPEED_MAPPING[SPEED_MEDIUM]: self._speed = SPEED_MEDIUM elif SPEED_MAPPING[SPEED_OFF] < value <= SPEED_MAPPING[SPEED_LOW]: self._speed = SPEED_LOW elif value == SPEED_MAPPING[SPEED_OFF]: self._speed = SPEED_OFF _LOGGER.debug("Fan speed is %s", self._speed)