class ScriptEntity(ToggleEntity): """Representation of a script entity.""" def __init__(self, hass, object_id, name, sequence): """Initialize the script.""" self.object_id = object_id self.entity_id = ENTITY_ID_FORMAT.format(object_id) self.script = Script(hass, sequence, name, self.async_update_ha_state) @property def should_poll(self): """No polling needed.""" return False @property def name(self): """Return the name of the entity.""" return self.script.name @property def state_attributes(self): """Return the state attributes.""" attrs = {} attrs[ATTR_LAST_TRIGGERED] = self.script.last_triggered if self.script.can_cancel: attrs[ATTR_CAN_CANCEL] = self.script.can_cancel if self.script.last_action: attrs[ATTR_LAST_ACTION] = self.script.last_action return attrs @property def is_on(self): """Return true if script is on.""" return self.script.is_running @asyncio.coroutine def async_turn_on(self, **kwargs): """Turn the script on.""" yield from self.script.async_run(kwargs.get(ATTR_VARIABLES)) @asyncio.coroutine def async_turn_off(self, **kwargs): """Turn script off.""" self.script.async_stop() def async_remove(self): """Remove script from HASS. This method must be run in the event loop and returns a coroutine. """ if self.script.is_running: self.script.async_stop() # remove service self.hass.services.async_remove(DOMAIN, self.object_id) return super().async_remove()
class ScriptEntity(ToggleEntity): """Representation of a script entity.""" def __init__(self, hass, object_id, name, sequence): """Initialize the script.""" self.object_id = object_id self.entity_id = ENTITY_ID_FORMAT.format(object_id) self.script = Script(hass, sequence, name, self.async_update_ha_state) @property def should_poll(self): """No polling needed.""" return False @property def name(self): """Return the name of the entity.""" return self.script.name @property def state_attributes(self): """Return the state attributes.""" attrs = {} if self.script.can_cancel: attrs[ATTR_CAN_CANCEL] = self.script.can_cancel if self.script.last_action: attrs[ATTR_LAST_ACTION] = self.script.last_action return attrs @property def is_on(self): """Return true if script is on.""" return self.script.is_running @asyncio.coroutine def async_turn_on(self, **kwargs): """Turn the script on.""" yield from self.script.async_run(kwargs.get(ATTR_VARIABLES)) @asyncio.coroutine def async_turn_off(self, **kwargs): """Turn script off.""" self.script.async_stop()
class CoverTemplate(CoverDevice): """Representation of a Template cover.""" def __init__(self, hass, device_id, friendly_name, state_template, position_template, tilt_template, icon_template, open_action, close_action, stop_action, position_action, tilt_action, optimistic, tilt_optimistic, entity_ids): """Initialize the Template cover.""" self.hass = hass self.entity_id = async_generate_entity_id( ENTITY_ID_FORMAT, device_id, hass=hass) self._name = friendly_name self._template = state_template self._position_template = position_template self._tilt_template = tilt_template self._icon_template = icon_template self._open_script = None if open_action is not None: self._open_script = Script(hass, open_action) self._close_script = None if close_action is not None: self._close_script = Script(hass, close_action) self._stop_script = None if stop_action is not None: self._stop_script = Script(hass, stop_action) self._position_script = None if position_action is not None: self._position_script = Script(hass, position_action) self._tilt_script = None if tilt_action is not None: self._tilt_script = Script(hass, tilt_action) self._optimistic = (optimistic or (not state_template and not position_template)) self._tilt_optimistic = tilt_optimistic or not tilt_template self._icon = None self._position = None self._tilt_value = None self._entities = entity_ids if self._template is not None: self._template.hass = self.hass if self._position_template is not None: self._position_template.hass = self.hass if self._tilt_template is not None: self._tilt_template.hass = self.hass if self._icon_template is not None: self._icon_template.hass = self.hass @asyncio.coroutine def async_added_to_hass(self): """Register callbacks.""" state = yield from async_get_last_state(self.hass, self.entity_id) if state: self._position = 100 if state.state == STATE_OPEN else 0 @callback def template_cover_state_listener(entity, old_state, new_state): """Handle target device state changes.""" self.hass.async_add_job(self.async_update_ha_state(True)) @callback def template_cover_startup(event): """Update template on startup.""" async_track_state_change( self.hass, self._entities, template_cover_state_listener) self.hass.async_add_job(self.async_update_ha_state(True)) self.hass.bus.async_listen_once( EVENT_HOMEASSISTANT_START, template_cover_startup) @property def name(self): """Return the name of the cover.""" return self._name @property def is_closed(self): """Return if the cover is closed.""" return self._position == 0 @property def current_cover_position(self): """Return current position of cover. None is unknown, 0 is closed, 100 is fully open. """ return self._position @property def current_cover_tilt_position(self): """Return current position of cover tilt. None is unknown, 0 is closed, 100 is fully open. """ return self._tilt_value @property def icon(self): """Return the icon to use in the frontend, if any.""" return self._icon @property def supported_features(self): """Flag supported features.""" supported_features = SUPPORT_OPEN | SUPPORT_CLOSE if self._stop_script is not None: supported_features |= SUPPORT_STOP if self._position_script is not None: supported_features |= SUPPORT_SET_POSITION if self.current_cover_tilt_position is not None: supported_features |= TILT_FEATURES return supported_features @property def should_poll(self): """Return the polling state.""" return False @asyncio.coroutine def async_open_cover(self, **kwargs): """Move the cover up.""" if self._open_script: yield from self._open_script.async_run() elif self._position_script: yield from self._position_script.async_run({"position": 100}) if self._optimistic: self._position = 100 self.hass.async_add_job(self.async_update_ha_state()) @asyncio.coroutine def async_close_cover(self, **kwargs): """Move the cover down.""" if self._close_script: yield from self._close_script.async_run() elif self._position_script: yield from self._position_script.async_run({"position": 0}) if self._optimistic: self._position = 0 self.hass.async_add_job(self.async_update_ha_state()) @asyncio.coroutine def async_stop_cover(self, **kwargs): """Fire the stop action.""" if self._stop_script: self.hass.async_add_job(self._stop_script.async_run()) @asyncio.coroutine def async_set_cover_position(self, **kwargs): """Set cover position.""" self._position = kwargs[ATTR_POSITION] yield from self._position_script.async_run( {"position": self._position}) if self._optimistic: self.hass.async_add_job(self.async_update_ha_state()) @asyncio.coroutine def async_open_cover_tilt(self, **kwargs): """Tilt the cover open.""" self._tilt_value = 100 yield from self._tilt_script.async_run({"tilt": self._tilt_value}) if self._tilt_optimistic: self.hass.async_add_job(self.async_update_ha_state()) @asyncio.coroutine def async_close_cover_tilt(self, **kwargs): """Tilt the cover closed.""" self._tilt_value = 0 yield from self._tilt_script.async_run( {"tilt": self._tilt_value}) if self._tilt_optimistic: self.hass.async_add_job(self.async_update_ha_state()) @asyncio.coroutine def async_set_cover_tilt_position(self, **kwargs): """Move the cover tilt to a specific position.""" self._tilt_value = kwargs[ATTR_TILT_POSITION] yield from self._tilt_script.async_run({"tilt": self._tilt_value}) if self._tilt_optimistic: self.hass.async_add_job(self.async_update_ha_state()) @asyncio.coroutine def async_update(self): """Update the state from the template.""" if self._template is not None: try: state = self._template.async_render().lower() if state in _VALID_STATES: if state in ('true', STATE_OPEN): self._position = 100 else: self._position = 0 else: _LOGGER.error( 'Received invalid cover is_on state: %s. Expected: %s', state, ', '.join(_VALID_STATES)) self._position = None except TemplateError as ex: _LOGGER.error(ex) self._position = None if self._position_template is not None: try: state = float(self._position_template.async_render()) if state < 0 or state > 100: self._position = None _LOGGER.error("Cover position value must be" " between 0 and 100." " Value was: %.2f", state) else: self._position = state except TemplateError as ex: _LOGGER.error(ex) self._position = None except ValueError as ex: _LOGGER.error(ex) self._position = None if self._tilt_template is not None: try: state = float(self._tilt_template.async_render()) if state < 0 or state > 100: self._tilt_value = None _LOGGER.error("Tilt value must be between 0 and 100." " Value was: %.2f", state) else: self._tilt_value = state except TemplateError as ex: _LOGGER.error(ex) self._tilt_value = None except ValueError as ex: _LOGGER.error(ex) self._tilt_value = None if self._icon_template is not None: try: self._icon = self._icon_template.async_render() except TemplateError as ex: if ex.args and ex.args[0].startswith( "UndefinedError: 'None' has no attribute"): # Common during HA startup - so just a warning _LOGGER.warning('Could not render icon template %s,' ' the state is unknown.', self._name) return self._icon = super().icon _LOGGER.error('Could not render icon template %s: %s', self._name, ex)
class LightTemplate(Light): """Representation of a templated Light, including dimmable.""" def __init__(self, hass, device_id, friendly_name, state_template, on_action, off_action, level_action, level_template, entity_ids): """Initialize the light.""" self.hass = hass self.entity_id = async_generate_entity_id(ENTITY_ID_FORMAT, device_id, hass=hass) self._name = friendly_name self._template = state_template self._on_script = Script(hass, on_action) self._off_script = Script(hass, off_action) self._level_script = None if level_action is not None: self._level_script = Script(hass, level_action) self._level_template = level_template self._state = False self._brightness = None self._entities = entity_ids if self._template is not None: self._template.hass = self.hass if self._level_template is not None: self._level_template.hass = self.hass @property def brightness(self): """Return the brightness of the light.""" return self._brightness @property def name(self): """Return the display name of this light.""" return self._name @property def supported_features(self): """Flag supported features.""" if self._level_script is not None: return SUPPORT_BRIGHTNESS return 0 @property def is_on(self): """Return true if device is on.""" return self._state @property def should_poll(self): """Return the polling state.""" return False @asyncio.coroutine def async_added_to_hass(self): """Register callbacks.""" @callback def template_light_state_listener(entity, old_state, new_state): """Handle target device state changes.""" self.async_schedule_update_ha_state(True) @callback def template_light_startup(event): """Update template on startup.""" if (self._template is not None or self._level_template is not None): async_track_state_change(self.hass, self._entities, template_light_state_listener) self.async_schedule_update_ha_state(True) self.hass.bus.async_listen_once(EVENT_HOMEASSISTANT_START, template_light_startup) @asyncio.coroutine def async_turn_on(self, **kwargs): """Turn the light on.""" optimistic_set = False # set optimistic states if self._template is None: self._state = True optimistic_set = True if self._level_template is None and ATTR_BRIGHTNESS in kwargs: _LOGGER.info("Optimistically setting brightness to %s", kwargs[ATTR_BRIGHTNESS]) self._brightness = kwargs[ATTR_BRIGHTNESS] optimistic_set = True if ATTR_BRIGHTNESS in kwargs and self._level_script: self.hass.async_add_job( self._level_script.async_run( {"brightness": kwargs[ATTR_BRIGHTNESS]})) else: self.hass.async_add_job(self._on_script.async_run()) if optimistic_set: self.async_schedule_update_ha_state() @asyncio.coroutine def async_turn_off(self, **kwargs): """Turn the light off.""" self.hass.async_add_job(self._off_script.async_run()) if self._template is None: self._state = False self.async_schedule_update_ha_state() @asyncio.coroutine def async_update(self): """Update the state from the template.""" print("ASYNC UPDATE") if self._template is not None: try: state = self._template.async_render().lower() except TemplateError as ex: _LOGGER.error(ex) self._state = None if state in _VALID_STATES: self._state = state in ('true', STATE_ON) else: _LOGGER.error( 'Received invalid light is_on state: %s. ' + 'Expected: %s', state, ', '.join(_VALID_STATES)) self._state = None if self._level_template is not None: try: brightness = self._level_template.async_render() except TemplateError as ex: _LOGGER.error(ex) self._state = None if 0 <= int(brightness) <= 255: self._brightness = brightness else: _LOGGER.error( 'Received invalid brightness : %s' + 'Expected: 0-255', brightness) self._brightness = None
class ScriptEntity(ToggleEntity): """Representation of a script entity.""" icon = None def __init__(self, hass, object_id, cfg): """Initialize the script.""" self.object_id = object_id self.icon = cfg.get(CONF_ICON) self.entity_id = ENTITY_ID_FORMAT.format(object_id) self.script = Script( hass, cfg[CONF_SEQUENCE], cfg.get(CONF_ALIAS, object_id), DOMAIN, running_description="script sequence", change_listener=self.async_change_listener, script_mode=cfg[CONF_MODE], max_runs=cfg[CONF_MAX], max_exceeded=cfg[CONF_MAX_EXCEEDED], logger=logging.getLogger(f"{__name__}.{object_id}"), variables=cfg.get(CONF_VARIABLES), ) self._changed = asyncio.Event() @property def should_poll(self): """No polling needed.""" return False @property def name(self): """Return the name of the entity.""" return self.script.name @property def state_attributes(self): """Return the state attributes.""" attrs = { ATTR_LAST_TRIGGERED: self.script.last_triggered, ATTR_MODE: self.script.script_mode, ATTR_CUR: self.script.runs, } if self.script.supports_max: attrs[ATTR_MAX] = self.script.max_runs if self.script.last_action: attrs[ATTR_LAST_ACTION] = self.script.last_action return attrs @property def is_on(self): """Return true if script is on.""" return self.script.is_running @callback def async_change_listener(self): """Update state.""" self.async_write_ha_state() self._changed.set() async def async_turn_on(self, **kwargs): """Turn the script on.""" variables = kwargs.get("variables") context = kwargs.get("context") wait = kwargs.get("wait", True) self.async_set_context(context) self.hass.bus.async_fire( EVENT_SCRIPT_STARTED, {ATTR_NAME: self.script.name, ATTR_ENTITY_ID: self.entity_id}, context=context, ) coro = self.script.async_run(variables, context) if wait: await coro return # Caller does not want to wait for called script to finish so let script run in # separate Task. However, wait for first state change so we can guarantee that # it is written to the State Machine before we return. self._changed.clear() self.hass.async_create_task(coro) await self._changed.wait() async def async_turn_off(self, **kwargs): """Turn script off.""" await self.script.async_stop() async def async_will_remove_from_hass(self): """Stop script and remove service when it will be removed from Home Assistant.""" await self.script.async_stop() # remove service self.hass.services.async_remove(DOMAIN, self.object_id)
class CoverTemplate(CoverDevice): """Representation of a Template cover.""" def __init__(self, hass, device_id, friendly_name, state_template, position_template, tilt_template, icon_template, open_action, close_action, stop_action, position_action, tilt_action, entity_ids): """Initialize the Template cover.""" self.hass = hass self.entity_id = async_generate_entity_id( ENTITY_ID_FORMAT, device_id, hass=hass) self._name = friendly_name self._template = state_template self._position_template = position_template self._tilt_template = tilt_template self._icon_template = icon_template self._open_script = Script(hass, open_action) self._close_script = Script(hass, close_action) self._stop_script = Script(hass, stop_action) self._position_script = None if position_action is not None: self._position_script = Script(hass, position_action) self._tilt_script = None if tilt_action is not None: self._tilt_script = Script(hass, tilt_action) self._icon = None self._position = None self._tilt_value = None self._entities = entity_ids if self._template is not None: self._template.hass = self.hass if self._position_template is not None: self._position_template.hass = self.hass if self._tilt_template is not None: self._tilt_template.hass = self.hass if self._icon_template is not None: self._icon_template.hass = self.hass @asyncio.coroutine def async_added_to_hass(self): """Register callbacks.""" state = yield from async_get_last_state(self.hass, self.entity_id) if state: self._position = 100 if state.state == STATE_OPEN else 0 @callback def template_cover_state_listener(entity, old_state, new_state): """Handle target device state changes.""" self.hass.async_add_job(self.async_update_ha_state(True)) @callback def template_cover_startup(event): """Update template on startup.""" async_track_state_change( self.hass, self._entities, template_cover_state_listener) self.hass.async_add_job(self.async_update_ha_state(True)) self.hass.bus.async_listen_once( EVENT_HOMEASSISTANT_START, template_cover_startup) @property def name(self): """Return the name of the cover.""" return self._name @property def is_closed(self): """Return if the cover is closed.""" return self._position == 0 @property def current_cover_position(self): """Return current position of cover. None is unknown, 0 is closed, 100 is fully open. """ return self._position @property def current_cover_tilt_position(self): """Return current position of cover tilt. None is unknown, 0 is closed, 100 is fully open. """ return self._tilt_value @property def icon(self): """Return the icon to use in the frontend, if any.""" return self._icon @property def supported_features(self): """Flag supported features.""" supported_features = SUPPORT_OPEN | SUPPORT_CLOSE | SUPPORT_STOP if self.current_cover_position is not None: supported_features |= SUPPORT_SET_POSITION if self.current_cover_tilt_position is not None: supported_features |= TILT_FEATURES return supported_features @property def should_poll(self): """Return the polling state.""" return False @asyncio.coroutine def async_open_cover(self, **kwargs): """Move the cover up.""" self.hass.async_add_job(self._open_script.async_run()) @asyncio.coroutine def async_close_cover(self, **kwargs): """Move the cover down.""" self.hass.async_add_job(self._close_script.async_run()) @asyncio.coroutine def async_stop_cover(self, **kwargs): """Fire the stop action.""" self.hass.async_add_job(self._stop_script.async_run()) @asyncio.coroutine def async_set_cover_position(self, **kwargs): """Set cover position.""" if ATTR_POSITION not in kwargs: return self._position = kwargs[ATTR_POSITION] self.hass.async_add_job(self._position_script.async_run( {"position": self._position})) @asyncio.coroutine def async_open_cover_tilt(self, **kwargs): """Tilt the cover open.""" self._tilt_value = 100 self.hass.async_add_job(self._tilt_script.async_run( {"tilt": self._tilt_value})) @asyncio.coroutine def async_close_cover_tilt(self, **kwargs): """Tilt the cover closed.""" self._tilt_value = 0 self.hass.async_add_job(self._tilt_script.async_run( {"tilt": self._tilt_value})) @asyncio.coroutine def async_set_cover_tilt_position(self, **kwargs): """Move the cover tilt to a specific position.""" if ATTR_TILT_POSITION not in kwargs: return self._tilt_value = kwargs[ATTR_TILT_POSITION] self.hass.async_add_job(self._tilt_script.async_run( {"tilt": self._tilt_value})) @asyncio.coroutine def async_update(self): """Update the state from the template.""" if self._template is not None: try: state = self._template.async_render().lower() if state in _VALID_STATES: if state in ('true', STATE_OPEN): self._position = 100 else: self._position = 0 else: _LOGGER.error( 'Received invalid cover is_on state: %s. Expected: %s', state, ', '.join(_VALID_STATES)) self._position = None except TemplateError as ex: _LOGGER.error(ex) self._position = None if self._position_template is not None: try: state = float(self._position_template.async_render()) if state < 0 or state > 100: self._position = None _LOGGER.error("Cover position value must be" " between 0 and 100." " Value was: %.2f", state) else: self._position = state except TemplateError as ex: _LOGGER.error(ex) self._position = None except ValueError as ex: _LOGGER.error(ex) self._position = None if self._tilt_template is not None: try: state = float(self._tilt_template.async_render()) if state < 0 or state > 100: self._tilt_value = None _LOGGER.error("Tilt value must be between 0 and 100." " Value was: %.2f", state) else: self._tilt_value = state except TemplateError as ex: _LOGGER.error(ex) self._tilt_value = None except ValueError as ex: _LOGGER.error(ex) self._tilt_value = None if self._icon_template is not None: try: self._icon = self._icon_template.async_render() except TemplateError as ex: if ex.args and ex.args[0].startswith( "UndefinedError: 'None' has no attribute"): # Common during HA startup - so just a warning _LOGGER.warning('Could not render icon template %s,' ' the state is unknown.', self._name) return self._icon = super().icon _LOGGER.error('Could not render icon template %s: %s', self._name, ex)
class LightTemplate(Light): """Representation of a templated Light, including dimmable.""" def __init__(self, hass, device_id, friendly_name, state_template, icon_template, entity_picture_template, on_action, off_action, level_action, level_template, entity_ids): """Initialize the light.""" self.hass = hass self.entity_id = async_generate_entity_id( ENTITY_ID_FORMAT, device_id, hass=hass) self._name = friendly_name self._template = state_template self._icon_template = icon_template self._entity_picture_template = entity_picture_template self._on_script = Script(hass, on_action) self._off_script = Script(hass, off_action) self._level_script = None if level_action is not None: self._level_script = Script(hass, level_action) self._level_template = level_template self._state = False self._icon = None self._entity_picture = None self._brightness = None self._entities = entity_ids if self._template is not None: self._template.hass = self.hass if self._level_template is not None: self._level_template.hass = self.hass if self._icon_template is not None: self._icon_template.hass = self.hass if self._entity_picture_template is not None: self._entity_picture_template.hass = self.hass @property def brightness(self): """Return the brightness of the light.""" return self._brightness @property def name(self): """Return the display name of this light.""" return self._name @property def supported_features(self): """Flag supported features.""" if self._level_script is not None: return SUPPORT_BRIGHTNESS return 0 @property def is_on(self): """Return true if device is on.""" return self._state @property def should_poll(self): """Return the polling state.""" return False @property def icon(self): """Return the icon to use in the frontend, if any.""" return self._icon @property def entity_picture(self): """Return the entity picture to use in the frontend, if any.""" return self._entity_picture @asyncio.coroutine def async_added_to_hass(self): """Register callbacks.""" @callback def template_light_state_listener(entity, old_state, new_state): """Handle target device state changes.""" self.async_schedule_update_ha_state(True) @callback def template_light_startup(event): """Update template on startup.""" if (self._template is not None or self._level_template is not None): async_track_state_change( self.hass, self._entities, template_light_state_listener) self.async_schedule_update_ha_state(True) self.hass.bus.async_listen_once( EVENT_HOMEASSISTANT_START, template_light_startup) @asyncio.coroutine def async_turn_on(self, **kwargs): """Turn the light on.""" optimistic_set = False # set optimistic states if self._template is None: self._state = True optimistic_set = True if self._level_template is None and ATTR_BRIGHTNESS in kwargs: _LOGGER.info("Optimistically setting brightness to %s", kwargs[ATTR_BRIGHTNESS]) self._brightness = kwargs[ATTR_BRIGHTNESS] optimistic_set = True if ATTR_BRIGHTNESS in kwargs and self._level_script: self.hass.async_add_job(self._level_script.async_run( {"brightness": kwargs[ATTR_BRIGHTNESS]})) else: yield from self._on_script.async_run() if optimistic_set: self.async_schedule_update_ha_state() @asyncio.coroutine def async_turn_off(self, **kwargs): """Turn the light off.""" yield from self._off_script.async_run() if self._template is None: self._state = False self.async_schedule_update_ha_state() @asyncio.coroutine def async_update(self): """Update the state from the template.""" if self._template is not None: try: state = self._template.async_render().lower() except TemplateError as ex: _LOGGER.error(ex) self._state = None if state in _VALID_STATES: self._state = state in ('true', STATE_ON) else: _LOGGER.error( 'Received invalid light is_on state: %s. Expected: %s', state, ', '.join(_VALID_STATES)) self._state = None if self._level_template is not None: try: brightness = self._level_template.async_render() except TemplateError as ex: _LOGGER.error(ex) self._state = None if 0 <= int(brightness) <= 255: self._brightness = int(brightness) else: _LOGGER.error( 'Received invalid brightness : %s. Expected: 0-255', brightness) self._brightness = None for property_name, template in ( ('_icon', self._icon_template), ('_entity_picture', self._entity_picture_template)): if template is None: continue try: setattr(self, property_name, template.async_render()) except TemplateError as ex: friendly_property_name = property_name[1:].replace('_', ' ') if ex.args and ex.args[0].startswith( "UndefinedError: 'None' has no attribute"): # Common during HA startup - so just a warning _LOGGER.warning('Could not render %s template %s,' ' the state is unknown.', friendly_property_name, self._name) return try: setattr(self, property_name, getattr(super(), property_name)) except AttributeError: _LOGGER.error('Could not render %s template %s: %s', friendly_property_name, self._name, ex)
class SwitchTemplate(SwitchDevice): """Representation of a Template switch.""" def __init__(self, hass, device_id, friendly_name, state_template, icon_template, entity_picture_template, on_action, off_action, entity_ids): """Initialize the Template switch.""" self.hass = hass self.entity_id = async_generate_entity_id( ENTITY_ID_FORMAT, device_id, hass=hass) self._name = friendly_name self._template = state_template self._on_script = Script(hass, on_action) self._off_script = Script(hass, off_action) self._state = False self._icon_template = icon_template self._entity_picture_template = entity_picture_template self._icon = None self._entity_picture = None self._entities = entity_ids @asyncio.coroutine def async_added_to_hass(self): """Register callbacks.""" @callback def template_switch_state_listener(entity, old_state, new_state): """Handle target device state changes.""" self.async_schedule_update_ha_state(True) @callback def template_switch_startup(event): """Update template on startup.""" async_track_state_change( self.hass, self._entities, template_switch_state_listener) self.async_schedule_update_ha_state(True) self.hass.bus.async_listen_once( EVENT_HOMEASSISTANT_START, template_switch_startup) @property def name(self): """Return the name of the switch.""" return self._name @property def is_on(self): """Return true if device is on.""" return self._state @property def should_poll(self): """Return the polling state.""" return False @property def available(self): """If switch is available.""" return self._state is not None @property def icon(self): """Return the icon to use in the frontend, if any.""" return self._icon @property def entity_picture(self): """Return the entity_picture to use in the frontend, if any.""" return self._entity_picture @asyncio.coroutine def async_turn_on(self, **kwargs): """Fire the on action.""" yield from self._on_script.async_run() @asyncio.coroutine def async_turn_off(self, **kwargs): """Fire the off action.""" yield from self._off_script.async_run() @asyncio.coroutine def async_update(self): """Update the state from the template.""" try: state = self._template.async_render().lower() if state in _VALID_STATES: self._state = state in ('true', STATE_ON) else: _LOGGER.error( 'Received invalid switch is_on state: %s. Expected: %s', state, ', '.join(_VALID_STATES)) self._state = None except TemplateError as ex: _LOGGER.error(ex) self._state = None for property_name, template in ( ('_icon', self._icon_template), ('_entity_picture', self._entity_picture_template)): if template is None: continue try: setattr(self, property_name, template.async_render()) except TemplateError as ex: friendly_property_name = property_name[1:].replace('_', ' ') if ex.args and ex.args[0].startswith( "UndefinedError: 'None' has no attribute"): # Common during HA startup - so just a warning _LOGGER.warning('Could not render %s template %s,' ' the state is unknown.', friendly_property_name, self._name) return try: setattr(self, property_name, getattr(super(), property_name)) except AttributeError: _LOGGER.error('Could not render %s template %s: %s', friendly_property_name, self._name, ex)
class LightTemplate(Light): """Representation of a templated Light, including dimmable.""" def __init__(self, hass, device_id, friendly_name, state_template, on_action, off_action, level_action, level_template, entity_ids): """Initialize the light.""" self.hass = hass self.entity_id = async_generate_entity_id( ENTITY_ID_FORMAT, device_id, hass=hass) self._name = friendly_name self._template = state_template self._on_script = Script(hass, on_action) self._off_script = Script(hass, off_action) self._level_script = None if level_action is not None: self._level_script = Script(hass, level_action) self._level_template = level_template self._state = False self._brightness = None self._entities = entity_ids if self._template is not None: self._template.hass = self.hass if self._level_template is not None: self._level_template.hass = self.hass @property def brightness(self): """Return the brightness of the light.""" return self._brightness @property def name(self): """Return the display name of this light.""" return self._name @property def supported_features(self): """Flag supported features.""" if self._level_script is not None: return SUPPORT_BRIGHTNESS return 0 @property def is_on(self): """Return true if device is on.""" return self._state @property def should_poll(self): """Return the polling state.""" return False @asyncio.coroutine def async_added_to_hass(self): """Register callbacks.""" @callback def template_light_state_listener(entity, old_state, new_state): """Handle target device state changes.""" self.async_schedule_update_ha_state(True) @callback def template_light_startup(event): """Update template on startup.""" if (self._template is not None or self._level_template is not None): async_track_state_change( self.hass, self._entities, template_light_state_listener) self.async_schedule_update_ha_state(True) self.hass.bus.async_listen_once( EVENT_HOMEASSISTANT_START, template_light_startup) @asyncio.coroutine def async_turn_on(self, **kwargs): """Turn the light on.""" optimistic_set = False # set optimistic states if self._template is None: self._state = True optimistic_set = True if self._level_template is None and ATTR_BRIGHTNESS in kwargs: _LOGGER.info("Optimistically setting brightness to %s", kwargs[ATTR_BRIGHTNESS]) self._brightness = kwargs[ATTR_BRIGHTNESS] optimistic_set = True if ATTR_BRIGHTNESS in kwargs and self._level_script: self.hass.async_add_job(self._level_script.async_run( {"brightness": kwargs[ATTR_BRIGHTNESS]})) else: self.hass.async_add_job(self._on_script.async_run()) if optimistic_set: self.async_schedule_update_ha_state() @asyncio.coroutine def async_turn_off(self, **kwargs): """Turn the light off.""" self.hass.async_add_job(self._off_script.async_run()) if self._template is None: self._state = False self.async_schedule_update_ha_state() @asyncio.coroutine def async_update(self): """Update the state from the template.""" print("ASYNC UPDATE") if self._template is not None: try: state = self._template.async_render().lower() except TemplateError as ex: _LOGGER.error(ex) self._state = None if state in _VALID_STATES: self._state = state in ('true', STATE_ON) else: _LOGGER.error( 'Received invalid light is_on state: %s. ' + 'Expected: %s', state, ', '.join(_VALID_STATES)) self._state = None if self._level_template is not None: try: brightness = self._level_template.async_render() except TemplateError as ex: _LOGGER.error(ex) self._state = None if 0 <= int(brightness) <= 255: self._brightness = brightness else: _LOGGER.error( 'Received invalid brightness : %s' + 'Expected: 0-255', brightness) self._brightness = None
class TemplateLock(LockDevice): def __init__(self, hass, name, value_template, command_lock, command_unlock, optimistic): self._state = None self._hass = hass self._name = name self._state_template = value_template self._state_entities = value_template.extract_entities() self._command_lock = Script(hass, command_lock) self._command_unlock = Script(hass, command_unlock) self._optimistic = optimistic @asyncio.coroutine def async_added_to_hass(self): @callback def template_lock_state_listener(entity, old_state, new_state): self.update_state() @callback def template_lock_startup(event): async_track_state_change(self._hass, self._state_entities, template_lock_state_listener) self.update_state() self._hass.bus.async_listen_once(EVENT_HOMEASSISTANT_START, template_lock_startup) @property def assumed_state(self): return self._optimistic @property def should_poll(self): return False @property def name(self): return self._name @property def is_locked(self): return self._state def update_state(self): try: self._state = self._state_template.async_render().lower() in ( 'true', 'on', '1', 'locked') except TemplateError as ex: self._state = None _LOGGER.error('Could not render template %s: %s', self._name, ex) self.async_schedule_update_ha_state(True) @asyncio.coroutine def async_lock(self, **kwargs): if self._optimistic: self._state = True self.async_schedule_update_ha_state() yield from self._command_lock.async_run() @asyncio.coroutine def async_unlock(self, **kwargs): if self._optimistic: self._state = False self.async_schedule_update_ha_state() yield from self._command_unlock.async_run()
class ScriptEntity(ToggleEntity): """Representation of a script entity.""" icon = None def __init__(self, hass, object_id, name, icon, sequence, mode, queue_max): """Initialize the script.""" self.object_id = object_id self.icon = icon self.entity_id = ENTITY_ID_FORMAT.format(object_id) self.script = Script( hass, sequence, name, self.async_change_listener, mode, queue_max, logging.getLogger(f"{__name__}.{object_id}"), ) self._changed = asyncio.Event() @property def should_poll(self): """No polling needed.""" return False @property def name(self): """Return the name of the entity.""" return self.script.name @property def state_attributes(self): """Return the state attributes.""" attrs = {ATTR_LAST_TRIGGERED: self.script.last_triggered} if self.script.can_cancel: attrs[ATTR_CAN_CANCEL] = self.script.can_cancel if self.script.last_action: attrs[ATTR_LAST_ACTION] = self.script.last_action return attrs @property def is_on(self): """Return true if script is on.""" return self.script.is_running @callback def async_change_listener(self): """Update state.""" self.async_write_ha_state() self._changed.set() async def async_turn_on(self, **kwargs): """Turn the script on.""" variables = kwargs.get("variables") context = kwargs.get("context") wait = kwargs.get("wait", True) self.async_set_context(context) self.hass.bus.async_fire( EVENT_SCRIPT_STARTED, { ATTR_NAME: self.script.name, ATTR_ENTITY_ID: self.entity_id }, context=context, ) coro = self.script.async_run(variables, context) if wait: await coro return # Caller does not want to wait for called script to finish so let script run in # separate Task. However, wait for first state change so we can guarantee that # it is written to the State Machine before we return. Only do this for # non-legacy scripts, since legacy scripts don't necessarily change state # immediately. self._changed.clear() self.hass.async_create_task(coro) if not self.script.is_legacy: await self._changed.wait() async def async_turn_off(self, **kwargs): """Turn script off.""" await self.script.async_stop() async def async_will_remove_from_hass(self): """Stop script and remove service when it will be removed from Home Assistant.""" await self.script.async_stop() # remove service self.hass.services.async_remove(DOMAIN, self.object_id)