async def _async_wait_template(self, action, variables, context): """Handle a wait template.""" # Call ourselves in the future to continue work wait_template = action[CONF_WAIT_TEMPLATE] wait_template.hass = self.hass self.last_action = action.get(CONF_ALIAS, 'wait template') self._log("Executing step %s" % self.last_action) # check if condition already okay if condition.async_template(self.hass, wait_template, variables): return @callback def async_script_wait(entity_id, from_s, to_s): """Handle script after template condition is true.""" self._async_remove_listener() self.hass.async_create_task(self.async_run(variables, context)) self._async_listener.append( async_track_template(self.hass, wait_template, async_script_wait, variables)) if CONF_TIMEOUT in action: self._async_set_timeout(action, variables, context, action.get(CONF_CONTINUE, True)) raise _SuspendScript
async def _async_wait_template_step(self): """Handle a wait template.""" if CONF_TIMEOUT in self._action: delay = self._get_pos_time_period_template( CONF_TIMEOUT).total_seconds() else: delay = None self._script.last_action = self._action.get(CONF_ALIAS, "wait template") self._log( "Executing step %s%s", self._script.last_action, "" if delay is None else f" (timeout: {timedelta(seconds=delay)})", ) self._variables["wait"] = {"remaining": delay, "completed": False} wait_template = self._action[CONF_WAIT_TEMPLATE] wait_template.hass = self._hass # check if condition already okay if condition.async_template(self._hass, wait_template, self._variables): self._variables["wait"]["completed"] = True return @callback def async_script_wait(entity_id, from_s, to_s): """Handle script after template condition is true.""" self._variables["wait"] = { "remaining": to_context.remaining if to_context else delay, "completed": True, } done.set() to_context = None unsub = async_track_template(self._hass, wait_template, async_script_wait, self._variables) self._changed() done = asyncio.Event() tasks = [ self._hass.async_create_task(flag.wait()) for flag in (self._stop, done) ] try: async with timeout(delay) as to_context: await asyncio.wait(tasks, return_when=asyncio.FIRST_COMPLETED) except asyncio.TimeoutError as ex: if not self._action.get(CONF_CONTINUE_ON_TIMEOUT, True): self._log(_TIMEOUT_MSG) raise _StopScript from ex self._variables["wait"]["remaining"] = 0.0 finally: for task in tasks: task.cancel() unsub()
async def _async_wait_template_step(self): """Handle a wait template.""" if CONF_TIMEOUT in self._action: timeout = self._get_pos_time_period_template( CONF_TIMEOUT).total_seconds() else: timeout = None self._step_log("wait template", timeout) self._variables["wait"] = {"remaining": timeout, "completed": False} trace_set_result(wait=self._variables["wait"]) wait_template = self._action[CONF_WAIT_TEMPLATE] wait_template.hass = self._hass # check if condition already okay if condition.async_template(self._hass, wait_template, self._variables, False): self._variables["wait"]["completed"] = True return @callback def async_script_wait(entity_id, from_s, to_s): """Handle script after template condition is true.""" wait_var = self._variables["wait"] wait_var[ "remaining"] = to_context.remaining if to_context else timeout wait_var["completed"] = True done.set() to_context = None unsub = async_track_template(self._hass, wait_template, async_script_wait, self._variables) self._changed() done = asyncio.Event() tasks = [ self._hass.async_create_task(flag.wait()) for flag in (self._stop, done) ] try: async with async_timeout.timeout(timeout) as to_context: await asyncio.wait(tasks, return_when=asyncio.FIRST_COMPLETED) except asyncio.TimeoutError as ex: self._variables["wait"]["remaining"] = 0.0 if not self._action.get(CONF_CONTINUE_ON_TIMEOUT, True): self._log(_TIMEOUT_MSG) trace_set_result(wait=self._variables["wait"], timeout=True) raise _StopScript from ex finally: for task in tasks: task.cancel() unsub()
def _prep_wait_template_step(self, async_script_wait): wait_template = self._action[CONF_WAIT_TEMPLATE] wait_template.hass = self._hass self._script.last_action = self._action.get(CONF_ALIAS, "wait template") self._log("Executing step %s", self._script.last_action) # check if condition already okay if condition.async_template(self._hass, wait_template, self._variables): return None return async_track_template(self._hass, wait_template, async_script_wait, self._variables)
async def async_trigger(hass, config, action, automation_info): """Listen for state changes based on configuration.""" value_template = config.get(CONF_VALUE_TEMPLATE) value_template.hass = hass time_delta = config.get(CONF_FOR) unsub_track_same = None @callback def template_listener(entity_id, from_s, to_s): """Listen for state changes and calls action.""" nonlocal unsub_track_same @callback def call_action(): """Call action with right context.""" hass.async_run_job( action( { 'trigger': { 'platform': 'template', 'entity_id': entity_id, 'from_state': from_s, 'to_state': to_s, }, }, context=(to_s.context if to_s else None))) if not time_delta: call_action() return unsub_track_same = async_track_same_state( hass, time_delta, call_action, lambda _, _2, _3: condition.async_template(hass, value_template), value_template.extract_entities()) unsub = async_track_template(hass, value_template, template_listener) @callback def async_remove(): """Remove state listeners async.""" unsub() if unsub_track_same: # pylint: disable=not-callable unsub_track_same() return async_remove
def async_trigger(hass, config, action): """Listen for state changes based on configuration.""" value_template = config.get(CONF_VALUE_TEMPLATE) value_template.hass = hass @callback def template_listener(entity_id, from_s, to_s): """Listen for state changes and calls action.""" hass.async_run_job(action, { 'trigger': { 'platform': 'template', 'entity_id': entity_id, 'from_state': from_s, 'to_state': to_s, }, }) return async_track_template(hass, value_template, template_listener)
async def _async_wait_template_step(self): """Handle a wait template.""" self._script.last_action = self._action.get(CONF_ALIAS, "wait template") self._log("Executing step %s", self._script.last_action) wait_template = self._action[CONF_WAIT_TEMPLATE] wait_template.hass = self._hass # check if condition already okay if condition.async_template(self._hass, wait_template, self._variables): return @callback def async_script_wait(entity_id, from_s, to_s): """Handle script after template condition is true.""" done.set() unsub = async_track_template(self._hass, wait_template, async_script_wait, self._variables) self._changed() try: delay = self._action[CONF_TIMEOUT].total_seconds() except KeyError: delay = None done = asyncio.Event() tasks = [ self._hass.async_create_task(flag.wait()) for flag in (self._stop, done) ] try: async with timeout(delay): await asyncio.wait(tasks, return_when=asyncio.FIRST_COMPLETED) except asyncio.TimeoutError: if not self._action.get(CONF_CONTINUE_ON_TIMEOUT, True): self._log(_TIMEOUT_MSG) raise _StopScript finally: for task in tasks: task.cancel() unsub()
async def async_run(self, variables: Optional[Sequence] = None) -> None: """Run script. This method is a coroutine. """ self.last_triggered = date_util.utcnow() if self._cur == -1: self._log('Running script') self._cur = 0 # Unregister callback if we were in a delay or wait but turn on is # called again. In that case we just continue execution. self._async_remove_listener() for cur, action in islice(enumerate(self.sequence), self._cur, None): if CONF_DELAY in action: # Call ourselves in the future to continue work unsub = None @callback def async_script_delay(now): """Handle delay.""" # pylint: disable=cell-var-from-loop self._async_listener.remove(unsub) self.hass.async_add_job(self.async_run(variables)) delay = action[CONF_DELAY] try: if isinstance(delay, template.Template): delay = vol.All( cv.time_period, cv.positive_timedelta)( delay.async_render(variables)) except (TemplateError, vol.Invalid) as ex: _LOGGER.error("Error rendering '%s' delay template: %s", self.name, ex) break unsub = async_track_point_in_utc_time( self.hass, async_script_delay, date_util.utcnow() + delay ) self._async_listener.append(unsub) self._cur = cur + 1 if self._change_listener: self.hass.async_add_job(self._change_listener) return if CONF_WAIT_TEMPLATE in action: # Call ourselves in the future to continue work wait_template = action[CONF_WAIT_TEMPLATE] wait_template.hass = self.hass # check if condition already okay if condition.async_template( self.hass, wait_template, variables): continue @callback def async_script_wait(entity_id, from_s, to_s): """Handle script after template condition is true.""" self._async_remove_listener() self.hass.async_add_job(self.async_run(variables)) self._async_listener.append(async_track_template( self.hass, wait_template, async_script_wait, variables)) self._cur = cur + 1 if self._change_listener: self.hass.async_add_job(self._change_listener) if CONF_TIMEOUT in action: self._async_set_timeout( action, variables, action.get(CONF_CONTINUE, True)) return if CONF_CONDITION in action: if not self._async_check_condition(action, variables): break elif CONF_EVENT in action: self._async_fire_event(action, variables) else: await self._async_call_service(action, variables) self._cur = -1 self.last_action = None if self._change_listener: self.hass.async_add_job(self._change_listener)
def async_run(self, variables: Optional[Sequence]=None) -> None: """Run script. This method is a coroutine. """ self.last_triggered = date_util.utcnow() if self._cur == -1: self._log('Running script') self._cur = 0 # Unregister callback if we were in a delay or wait but turn on is # called again. In that case we just continue execution. self._async_remove_listener() for cur, action in islice(enumerate(self.sequence), self._cur, None): if CONF_DELAY in action: # Call ourselves in the future to continue work unsub = None @callback def async_script_delay(now): """Called after delay is done.""" # pylint: disable=cell-var-from-loop self._async_listener.remove(unsub) self.hass.async_add_job(self.async_run(variables)) delay = action[CONF_DELAY] if isinstance(delay, template.Template): delay = vol.All( cv.time_period, cv.positive_timedelta)( delay.async_render(variables)) unsub = async_track_point_in_utc_time( self.hass, async_script_delay, date_util.utcnow() + delay ) self._async_listener.append(unsub) self._cur = cur + 1 if self._change_listener: self.hass.async_add_job(self._change_listener) return elif CONF_WAIT_TEMPLATE in action: # Call ourselves in the future to continue work wait_template = action[CONF_WAIT_TEMPLATE] wait_template.hass = self.hass # check if condition allready okay if condition.async_template( self.hass, wait_template, variables): continue @callback def async_script_wait(entity_id, from_s, to_s): """Called after template condition is true.""" self._async_remove_listener() self.hass.async_add_job(self.async_run(variables)) self._async_listener.append(async_track_template( self.hass, wait_template, async_script_wait)) self._cur = cur + 1 if self._change_listener: self.hass.async_add_job(self._change_listener) if CONF_TIMEOUT in action: self._async_set_timeout(action, variables) return elif CONF_CONDITION in action: if not self._async_check_condition(action, variables): break elif CONF_EVENT in action: self._async_fire_event(action) else: yield from self._async_call_service(action, variables) self._cur = -1 self.last_action = None if self._change_listener: self.hass.async_add_job(self._change_listener)
async def test_track_template(hass): """Test tracking template.""" specific_runs = [] wildcard_runs = [] wildercard_runs = [] template_condition = Template("{{states.switch.test.state == 'on'}}", hass) template_condition_var = Template( "{{states.switch.test.state == 'on' and test == 5}}", hass) hass.states.async_set("switch.test", "off") def specific_run_callback(entity_id, old_state, new_state): specific_runs.append(1) async_track_template(hass, template_condition, specific_run_callback) @ha.callback def wildcard_run_callback(entity_id, old_state, new_state): wildcard_runs.append((old_state, new_state)) async_track_template(hass, template_condition, wildcard_run_callback) @asyncio.coroutine def wildercard_run_callback(entity_id, old_state, new_state): wildercard_runs.append((old_state, new_state)) async_track_template(hass, template_condition_var, wildercard_run_callback, {"test": 5}) hass.states.async_set("switch.test", "on") await hass.async_block_till_done() assert len(specific_runs) == 1 assert len(wildcard_runs) == 1 assert len(wildercard_runs) == 1 hass.states.async_set("switch.test", "on") await hass.async_block_till_done() assert len(specific_runs) == 1 assert len(wildcard_runs) == 1 assert len(wildercard_runs) == 1 hass.states.async_set("switch.test", "off") await hass.async_block_till_done() assert len(specific_runs) == 1 assert len(wildcard_runs) == 1 assert len(wildercard_runs) == 1 hass.states.async_set("switch.test", "off") await hass.async_block_till_done() assert len(specific_runs) == 1 assert len(wildcard_runs) == 1 assert len(wildercard_runs) == 1 hass.states.async_set("switch.test", "on") await hass.async_block_till_done() assert len(specific_runs) == 2 assert len(wildcard_runs) == 2 assert len(wildercard_runs) == 2
async def async_attach_trigger(hass, config, action, automation_info, *, platform_type="numeric_state"): """Listen for state changes based on configuration.""" value_template = config.get(CONF_VALUE_TEMPLATE) value_template.hass = hass time_delta = config.get(CONF_FOR) template.attach(hass, time_delta) unsub_track_same = None @callback def template_listener(entity_id, from_s, to_s): """Listen for state changes and calls action.""" nonlocal unsub_track_same @callback def call_action(): """Call action with right context.""" hass.async_run_job( action( { "trigger": { "platform": "template", "entity_id": entity_id, "from_state": from_s, "to_state": to_s, "for": time_delta if not time_delta else period, } }, context=(to_s.context if to_s else None), )) if not time_delta: call_action() return variables = { "trigger": { "platform": platform_type, "entity_id": entity_id, "from_state": from_s, "to_state": to_s, } } try: if isinstance(time_delta, template.Template): period = vol.All(cv.time_period, cv.positive_timedelta)( time_delta.async_render(variables)) elif isinstance(time_delta, dict): time_delta_data = {} time_delta_data.update( template.render_complex(time_delta, variables)) period = vol.All(cv.time_period, cv.positive_timedelta)(time_delta_data) else: period = time_delta except (exceptions.TemplateError, vol.Invalid) as ex: _LOGGER.error("Error rendering '%s' for template: %s", automation_info["name"], ex) return unsub_track_same = async_track_same_state( hass, period, call_action, lambda _, _2, _3: condition.async_template(hass, value_template), value_template.extract_entities(), ) unsub = async_track_template(hass, value_template, template_listener) @callback def async_remove(): """Remove state listeners async.""" unsub() if unsub_track_same: # pylint: disable=not-callable unsub_track_same() return async_remove
async def async_run(self, variables: Optional[Sequence] = None, context: Optional[Context] = None) -> None: """Run script. This method is a coroutine. """ self.last_triggered = date_util.utcnow() if self._cur == -1: self._log('Running script') self._cur = 0 # Unregister callback if we were in a delay or wait but turn on is # called again. In that case we just continue execution. self._async_remove_listener() for cur, action in islice(enumerate(self.sequence), self._cur, None): if CONF_DELAY in action: # Call ourselves in the future to continue work unsub = None @callback def async_script_delay(now): """Handle delay.""" # pylint: disable=cell-var-from-loop with suppress(ValueError): self._async_listener.remove(unsub) self.hass.async_create_task( self.async_run(variables, context)) delay = action[CONF_DELAY] try: if isinstance(delay, template.Template): delay = vol.All( cv.time_period, cv.positive_timedelta)( delay.async_render(variables)) elif isinstance(delay, dict): delay_data = {} delay_data.update( template.render_complex(delay, variables)) delay = cv.time_period(delay_data) except (TemplateError, vol.Invalid) as ex: _LOGGER.error("Error rendering '%s' delay template: %s", self.name, ex) break unsub = async_track_point_in_utc_time( self.hass, async_script_delay, date_util.utcnow() + delay ) self._async_listener.append(unsub) self._cur = cur + 1 if self._change_listener: self.hass.async_add_job(self._change_listener) return if CONF_WAIT_TEMPLATE in action: # Call ourselves in the future to continue work wait_template = action[CONF_WAIT_TEMPLATE] wait_template.hass = self.hass # check if condition already okay if condition.async_template( self.hass, wait_template, variables): continue @callback def async_script_wait(entity_id, from_s, to_s): """Handle script after template condition is true.""" self._async_remove_listener() self.hass.async_create_task( self.async_run(variables, context)) self._async_listener.append(async_track_template( self.hass, wait_template, async_script_wait, variables)) self._cur = cur + 1 if self._change_listener: self.hass.async_add_job(self._change_listener) if CONF_TIMEOUT in action: self._async_set_timeout( action, variables, context, action.get(CONF_CONTINUE, True)) return if CONF_CONDITION in action: if not self._async_check_condition(action, variables): break elif CONF_EVENT in action: self._async_fire_event(action, variables, context) else: await self._async_call_service(action, variables, context) self._cur = -1 self.last_action = None if self._change_listener: self.hass.async_add_job(self._change_listener)