""" Platform for Lutron shades. Provides shade functionality for Home Assistant. """ import logging from homeassistant.components.cover import ( CoverEntity, SUPPORT_OPEN, SUPPORT_CLOSE, SUPPORT_STOP, ATTR_POSITION, SUPPORT_SET_POSITION, 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__) class CasetaData: """Data holder for a shade.""" def __init__(self, caseta, hass): """Initialize the data holder.""" self._caseta = caseta self._hass = hass self._devices = [] self._added = {} self._later = None @property def devices(self): """Return list of devices.""" return self._devices @property def caseta(self): """Return Caseta reference.""" return self._caseta def set_devices(self, devices): """Set the list of devices.""" self._devices = devices async def read_output(self, mode, integration, action, value): """Receive output value from the bridge.""" # Expect: ~OUTPUT,Integration ID,Action Number,Parameters if mode == Caseta.OUTPUT: for device in self._devices: if device.integration == integration: _LOGGER.debug( "Got cover OUTPUT value: %s %d %d %f", mode, integration, action, value, ) if action == Caseta.Action.SET: # update zone level, e.g. 90.00 device.update_state(value) if device.hass is not None: await device.async_update_ha_state() break # pylint: disable=unused-argument async 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]) await bridge.open() data = CasetaData(bridge, hass) devices = [ CasetaCover(cover, data, discovery_info[CONF_MAC]) for cover in discovery_info[CONF_DEVICES] ] data.set_devices(devices) async_add_devices(devices) # register callbacks bridge.register(data.read_output) # start bridge main loop bridge.start(hass) class CasetaCover(CoverEntity): """Representation of a Lutron shade.""" def __init__(self, cover, data, mac): """Initialize a Lutron shade.""" self._data = data self._name = cover[CONF_NAME] self._area_name = None if CONF_AREA_NAME in cover: self._area_name = cover[CONF_AREA_NAME] # if available, prepend area name to cover self._name = cover[CONF_AREA_NAME] + " " + cover[CONF_NAME] self._integration = int(cover[CONF_ID]) self._position = 0 self._mac = mac async def async_added_to_hass(self): """Update initial state.""" await self.query() async def query(self): """Query the bridge for the current state of the device.""" await self._data.caseta.query( Caseta.OUTPUT, self._integration, Caseta.Action.SET ) def update_state(self, new_position): """Update position value.""" self._position = new_position @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 device.""" return self._name @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_closed(self): """Return if the cover is closed.""" return self._position < 1 @property def current_cover_position(self): """Return current position of the cover.""" return self._position async def async_open_cover(self, **kwargs): """Open the cover.""" # Rasing must be used for STOP to work await self._data.caseta.write( Caseta.OUTPUT, self._integration, Caseta.Action.RAISING, None ) # When a Caseta.Action.SET action is sent to 100, the bridge # will always send back the state right away to 100. # We need to update the state ourself as the bridge # will not do this on a Caseta.Action.RAISING self.update_state(100) async def async_close_cover(self, **kwargs): """Close the cover.""" # Lowering must be used for STOP to work await self._data.caseta.write( Caseta.OUTPUT, self._integration, Caseta.Action.LOWERING, None ) # When a Caseta.Action.SET action is sent to 0, the bridge # will always send back the state right away to 0. # We need to update the state ourself as the bridge # will not do this on a Caseta.Action.LOWERING self.update_state(0) async def async_set_cover_position(self, **kwargs): """Move the cover to a specific position.""" if ATTR_POSITION in kwargs: position = kwargs[ATTR_POSITION] # check values if position < 0: _LOGGER.warning("Tried to set cover position to less than 0.") position = 0 if position > 100: _LOGGER.warning( "Tried to set cover position to greater than maximum value 100." ) position = 100 # Parameters are Level, Fade, Delay # Fade is ignored and Delay set to 0 await self._data.caseta.write( Caseta.OUTPUT, self._integration, Caseta.Action.SET, position, 0, 0 ) @property def supported_features(self): """Flag supported features.""" return SUPPORT_OPEN | SUPPORT_CLOSE | SUPPORT_STOP | SUPPORT_SET_POSITION async def async_stop_cover(self, **kwargs): """Stop raising or lowering the shade.""" await self._data.caseta.write( Caseta.OUTPUT, self._integration, Caseta.Action.STOP, None )