def _template_changed_listener( event: Event, updates: list[TrackTemplateResult] ) -> None: """Check if condition is correct and run action.""" track_result = updates.pop() template = track_result.template last_result = track_result.last_result result = track_result.result if isinstance(result, TemplateError): _LOGGER.error( "Error while processing template: %s", template.template, exc_info=result, ) return if ( not isinstance(last_result, TemplateError) and result_as_boolean(last_result) or not result_as_boolean(result) ): return opp.async_run_opp_job( job, event and event.data.get("entity_id"), event and event.data.get("old_state"), event and event.data.get("new_state"), )
def _update_state(self, result): super()._update_state(result) if self._delay_cancel: self._delay_cancel() self._delay_cancel = None state = (None if isinstance(result, TemplateError) else template.result_as_boolean(result)) if state == self._state: return # state without delay if (state is None or (state and not self._delay_on) or (not state and not self._delay_off)): self._state = state return @callback def _set_state(_): """Set state of template binary sensor.""" self._state = state self.async_write_op_state() delay = (self._delay_on if state else self._delay_off).total_seconds() # state with delay. Cancelled if template result changes. self._delay_cancel = async_call_later(self.opp, delay, _set_state)
def _async_template_result_changed(event, updates): track_template_result = updates.pop() template = track_template_result.template result = track_template_result.result entity = event and event.data.get("entity_id") if isinstance(result, TemplateError): _LOGGER.error( "TemplateError('%s') " "while processing template '%s' " "in entity '%s'", result, template, self.entity_id, ) should_trigger = False else: should_trigger = result_as_boolean(result) for obs in self.observations_by_template[template]: if should_trigger: obs_entry = {"entity_id": entity, **obs} else: obs_entry = None self.current_observations[obs["id"]] = obs_entry if event: self.async_set_context(event.context) self._recalculate_and_write_state()
def _handle_coordinator_update(self) -> None: """Handle update of the data.""" self._process_data() if self._delay_cancel: self._delay_cancel() self._delay_cancel = None if self._auto_off_cancel: self._auto_off_cancel() self._auto_off_cancel = None if not self.available: self.async_write_op_state() return raw = self._rendered.get(CONF_STATE) state = template.result_as_boolean(raw) key = CONF_DELAY_ON if state else CONF_DELAY_OFF delay = self._rendered.get(key) or self._config.get(key) # state without delay. None means rendering failed. if self._state == state or state is None or delay is None: self._set_state(state) return if not isinstance(delay, timedelta): try: delay = cv.positive_time_period(delay) except vol.Invalid as err: logging.getLogger(__name__).warning( "Error rendering %s template: %s", key, err) return # state with delay. Cancelled if new trigger received self._delay_cancel = async_call_later(self.opp, delay.total_seconds(), partial(self._set_state, state))
def _update_available(self, result): if isinstance(result, TemplateError): self._available = True return self._available = result_as_boolean(result)
def template_listener(event, updates): """Listen for state changes and calls action.""" nonlocal delay_cancel, armed result = updates.pop().result if isinstance(result, exceptions.TemplateError): _LOGGER.warning( "Error evaluating 'template' trigger for '%s': %s", automation_info["name"], result, ) return if delay_cancel: # pylint: disable=not-callable delay_cancel() delay_cancel = None if not result_as_boolean(result): armed = True return # Only fire when previously armed. if not armed: return # Fire! armed = False entity_id = event and event.data.get("entity_id") from_s = event and event.data.get("old_state") to_s = event and event.data.get("new_state") if entity_id is not None: description = f"{entity_id} via template" else: description = "time change or manual update via template" template_variables = { "platform": platform_type, "entity_id": entity_id, "from_state": from_s, "to_state": to_s, } trigger_variables = { "for": time_delta, "description": description, "id": trigger_id, } @callback def call_action(*_): """Call action with right context.""" nonlocal trigger_variables opp.async_run_opp_job( job, {"trigger": { **template_variables, **trigger_variables }}, (to_s.context if to_s else None), ) if not time_delta: call_action() return try: period = cv.positive_time_period( template.render_complex(time_delta, {"trigger": template_variables})) except (exceptions.TemplateError, vol.Invalid) as ex: _LOGGER.error("Error rendering '%s' for template: %s", automation_info["name"], ex) return trigger_variables["for"] = period delay_cancel = async_call_later(opp, period.total_seconds(), call_action)
async def async_attach_trigger(opp, config, action, automation_info, *, platform_type="template"): """Listen for state changes based on configuration.""" trigger_id = automation_info.get("trigger_id") if automation_info else None value_template = config.get(CONF_VALUE_TEMPLATE) value_template.opp = opp time_delta = config.get(CONF_FOR) template.attach(opp, time_delta) delay_cancel = None job = OppJob(action) armed = False # Arm at setup if the template is already false. try: if not result_as_boolean( value_template.async_render(automation_info["variables"])): armed = True except exceptions.TemplateError as ex: _LOGGER.warning( "Error initializing 'template' trigger for '%s': %s", automation_info["name"], ex, ) @callback def template_listener(event, updates): """Listen for state changes and calls action.""" nonlocal delay_cancel, armed result = updates.pop().result if isinstance(result, exceptions.TemplateError): _LOGGER.warning( "Error evaluating 'template' trigger for '%s': %s", automation_info["name"], result, ) return if delay_cancel: # pylint: disable=not-callable delay_cancel() delay_cancel = None if not result_as_boolean(result): armed = True return # Only fire when previously armed. if not armed: return # Fire! armed = False entity_id = event and event.data.get("entity_id") from_s = event and event.data.get("old_state") to_s = event and event.data.get("new_state") if entity_id is not None: description = f"{entity_id} via template" else: description = "time change or manual update via template" template_variables = { "platform": platform_type, "entity_id": entity_id, "from_state": from_s, "to_state": to_s, } trigger_variables = { "for": time_delta, "description": description, "id": trigger_id, } @callback def call_action(*_): """Call action with right context.""" nonlocal trigger_variables opp.async_run_opp_job( job, {"trigger": { **template_variables, **trigger_variables }}, (to_s.context if to_s else None), ) if not time_delta: call_action() return try: period = cv.positive_time_period( template.render_complex(time_delta, {"trigger": template_variables})) except (exceptions.TemplateError, vol.Invalid) as ex: _LOGGER.error("Error rendering '%s' for template: %s", automation_info["name"], ex) return trigger_variables["for"] = period delay_cancel = async_call_later(opp, period.total_seconds(), call_action) info = async_track_template_result( opp, [TrackTemplate(value_template, automation_info["variables"])], template_listener, ) unsub = info.async_remove @callback def async_remove(): """Remove state listeners async.""" unsub() if delay_cancel: # pylint: disable=not-callable delay_cancel() return async_remove