async def async_run(self) -> None: """Run script.""" try: self._log("Running %s", self._script.running_description) for self._step, self._action in enumerate(self._script.sequence): if self._stop.is_set(): script_execution_set("cancelled") break await self._async_step(log_exceptions=False) else: script_execution_set("finished") except _StopScript: script_execution_set("aborted") except Exception: script_execution_set("error") raise finally: self._finish()
async def async_trigger(self, run_variables, context=None, skip_condition=False): """Trigger automation. This method is a coroutine. """ reason = "" if "trigger" in run_variables and "description" in run_variables["trigger"]: reason = f' by {run_variables["trigger"]["description"]}' self._logger.debug("Automation triggered%s", reason) # Create a new context referring to the old context. parent_id = None if context is None else context.id trigger_context = Context(parent_id=parent_id) with trace_automation( self.hass, self.unique_id, self._raw_config, self._blueprint_inputs, trigger_context, self._trace_config, ) as automation_trace: if self._variables: try: variables = self._variables.async_render(self.hass, run_variables) except template.TemplateError as err: self._logger.error("Error rendering variables: %s", err) automation_trace.set_error(err) return else: variables = run_variables # Prepare tracing the automation automation_trace.set_trace(trace_get()) # Set trigger reason trigger_description = variables.get("trigger", {}).get("description") automation_trace.set_trigger_description(trigger_description) # Add initial variables as the trigger step if "trigger" in variables and "id" in variables["trigger"]: trigger_path = f"trigger/{variables['trigger']['id']}" else: trigger_path = "trigger" trace_element = TraceElement(variables, trigger_path) trace_append_element(trace_element) if ( not skip_condition and self._cond_func is not None and not self._cond_func(variables) ): self._logger.debug( "Conditions not met, aborting automation. Condition summary: %s", trace_get(clear=False), ) script_execution_set("failed_conditions") return self.async_set_context(trigger_context) event_data = { ATTR_NAME: self._name, ATTR_ENTITY_ID: self.entity_id, } if "trigger" in variables and "description" in variables["trigger"]: event_data[ATTR_SOURCE] = variables["trigger"]["description"] @callback def started_action(): self.hass.bus.async_fire( EVENT_AUTOMATION_TRIGGERED, event_data, context=trigger_context ) try: with trace_path("action"): await self.action_script.async_run( variables, trigger_context, started_action ) except (vol.Invalid, HomeAssistantError) as err: self._logger.error( "Error while executing automation %s: %s", self.entity_id, err, ) automation_trace.set_error(err) except Exception as err: # pylint: disable=broad-except self._logger.exception("While executing automation %s", self.entity_id) automation_trace.set_error(err)
async def async_run( self, run_variables: _VarsType | None = None, context: Context | None = None, started_action: Callable[..., Any] | None = None, ) -> None: """Run script.""" if context is None: self._log("Running script requires passing in a context", level=logging.WARNING) context = Context() if self.is_running: if self.script_mode == SCRIPT_MODE_SINGLE: if self._max_exceeded != "SILENT": self._log("Already running", level=LOGSEVERITY[self._max_exceeded]) script_execution_set("failed_single") return if self.script_mode != SCRIPT_MODE_RESTART and self.runs == self.max_runs: if self._max_exceeded != "SILENT": self._log( "Maximum number of runs exceeded", level=LOGSEVERITY[self._max_exceeded], ) script_execution_set("failed_max_runs") return # If this is a top level Script then make a copy of the variables in case they # are read-only, but more importantly, so as not to leak any variables created # during the run back to the caller. if self._top_level: if self.variables: try: variables = self.variables.async_render( self._hass, run_variables, ) except template.TemplateError as err: self._log("Error rendering variables: %s", err, level=logging.ERROR) raise elif run_variables: variables = dict(run_variables) else: variables = {} variables["context"] = context else: variables = cast(dict, run_variables) if self.script_mode != SCRIPT_MODE_QUEUED: cls = _ScriptRun else: cls = _QueuedScriptRun run = cls(self._hass, self, cast(dict, variables), context, self._log_exceptions) self._runs.append(run) if self.script_mode == SCRIPT_MODE_RESTART: # When script mode is SCRIPT_MODE_RESTART, first add the new run and then # stop any other runs. If we stop other runs first, self.is_running will # return false after the other script runs were stopped until our task # resumes running. self._log("Restarting") await self.async_stop(update_state=False, spare=run) if started_action: self._hass.async_run_job(started_action) self.last_triggered = utcnow() self._changed() try: await asyncio.shield(run.async_run()) except asyncio.CancelledError: await run.async_stop() self._changed() raise
async def async_trigger( self, run_variables: dict[str, Any], context: Context | None = None, skip_condition: bool = False, ) -> None: """Trigger automation. This method is a coroutine. """ reason = "" alias = "" if "trigger" in run_variables: if "description" in run_variables["trigger"]: reason = f' by {run_variables["trigger"]["description"]}' if "alias" in run_variables["trigger"]: alias = f' trigger \'{run_variables["trigger"]["alias"]}\'' self._logger.debug("Automation%s triggered%s", alias, reason) # Create a new context referring to the old context. parent_id = None if context is None else context.id trigger_context = Context(parent_id=parent_id) with trace_automation( self.hass, self.unique_id, self._raw_config, self._blueprint_inputs, trigger_context, self._trace_config, ) as automation_trace: this = None if state := self.hass.states.get(self.entity_id): this = state.as_dict() variables: dict[str, Any] = {"this": this, **(run_variables or {})} if self._variables: try: variables = self._variables.async_render( self.hass, variables) except TemplateError as err: self._logger.error("Error rendering variables: %s", err) automation_trace.set_error(err) return # Prepare tracing the automation automation_trace.set_trace(trace_get()) # Set trigger reason trigger_description = variables.get("trigger", {}).get("description") automation_trace.set_trigger_description(trigger_description) # Add initial variables as the trigger step if "trigger" in variables and "idx" in variables["trigger"]: trigger_path = f"trigger/{variables['trigger']['idx']}" else: trigger_path = "trigger" trace_element = TraceElement(variables, trigger_path) trace_append_element(trace_element) if (not skip_condition and self._cond_func is not None and not self._cond_func(variables)): self._logger.debug( "Conditions not met, aborting automation. Condition summary: %s", trace_get(clear=False), ) script_execution_set("failed_conditions") return self.async_set_context(trigger_context) event_data = { ATTR_NAME: self.name, ATTR_ENTITY_ID: self.entity_id, } if "trigger" in variables and "description" in variables["trigger"]: event_data[ATTR_SOURCE] = variables["trigger"]["description"] @callback def started_action(): self.hass.bus.async_fire(EVENT_AUTOMATION_TRIGGERED, event_data, context=trigger_context) # Make a new empty script stack; automations are allowed # to recursively trigger themselves script_stack_cv.set([]) try: with trace_path("action"): await self.action_script.async_run(variables, trigger_context, started_action) except ServiceNotFound as err: async_create_issue( self.hass, DOMAIN, f"{self.entity_id}_service_not_found_{err.domain}.{err.service}", is_fixable=True, is_persistent=True, severity=IssueSeverity.ERROR, translation_key="service_not_found", translation_placeholders={ "service": f"{err.domain}.{err.service}", "entity_id": self.entity_id, "name": self.name or self.entity_id, "edit": f"/config/automation/edit/{self.unique_id}", }, ) automation_trace.set_error(err) except (vol.Invalid, HomeAssistantError) as err: self._logger.error( "Error while executing automation %s: %s", self.entity_id, err, ) automation_trace.set_error(err) except Exception as err: # pylint: disable=broad-except self._logger.exception("While executing automation %s", self.entity_id) automation_trace.set_error(err)