Beispiel #1
0
        async def upload_file(self, timeout=None):
            """Upload file to Jabber server and return new URL.

            upload a file with Jabber XEP_0363 from a remote URL or a local
            file path and return a URL of that file.
            """
            if data.get(ATTR_URL_TEMPLATE):
                _LOGGER.debug("Got url template: %s", data[ATTR_URL_TEMPLATE])
                templ = template_helper.Template(data[ATTR_URL_TEMPLATE], opp)
                get_url = template_helper.render_complex(templ, None)
                url = await self.upload_file_from_url(get_url, timeout=timeout)
            elif data.get(ATTR_URL):
                url = await self.upload_file_from_url(data[ATTR_URL],
                                                      timeout=timeout)
            elif data.get(ATTR_PATH_TEMPLATE):
                _LOGGER.debug("Got path template: %s",
                              data[ATTR_PATH_TEMPLATE])
                templ = template_helper.Template(data[ATTR_PATH_TEMPLATE], opp)
                get_path = template_helper.render_complex(templ, None)
                url = await self.upload_file_from_path(get_path,
                                                       timeout=timeout)
            elif data.get(ATTR_PATH):
                url = await self.upload_file_from_path(data[ATTR_PATH],
                                                       timeout=timeout)
            else:
                url = None

            if url is None:
                _LOGGER.error("No path or URL found for file")
                raise FileUploadError("Could not upload file")

            return url
Beispiel #2
0
    def _process_data(self) -> None:
        """Process new data."""
        try:
            rendered = dict(self._static_rendered)

            for key in self._to_render:
                rendered[key] = self._config[key].async_render(
                    self.coordinator.data["run_variables"],
                    parse_result=key in self._parse_result,
                )

            if CONF_ATTRIBUTES in self._config:
                rendered[CONF_ATTRIBUTES] = template.render_complex(
                    self._config[CONF_ATTRIBUTES],
                    self.coordinator.data["run_variables"],
                )

            self._rendered = rendered
        except template.TemplateError as err:
            logging.getLogger(f"{__package__}.{self.entity_id.split('.')[0]}").error(
                "Error rendering %s template for %s: %s", key, self.entity_id, err
            )
            self._rendered = self._static_rendered

        self.async_set_context(self.coordinator.data["context"])
Beispiel #3
0
    async def _async_delay(self, action, variables, context):
        """Handle delay."""
        # Call ourselves in the future to continue work
        unsub = None

        @callback
        def async_script_delay(now):
            """Handle delay."""
            with suppress(ValueError):
                self._async_listener.remove(unsub)

            self.opp.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 (exceptions.TemplateError, vol.Invalid) as ex:
            _LOGGER.error("Error rendering '%s' delay template: %s", self.name,
                          ex)
            raise _StopScript

        self.last_action = action.get(CONF_ALIAS, f"delay {delay}")
        self._log("Executing step %s" % self.last_action)

        unsub = async_track_point_in_utc_time(self.opp, async_script_delay,
                                              date_util.utcnow() + delay)
        self._async_listener.append(unsub)
        raise _SuspendScript
    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."""
            opp.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(
            opp,
            period,
            call_action,
            lambda _, _2, _3: condition.async_template(opp, value_template),
            value_template.extract_entities(),
        )
Beispiel #5
0
    def send_message(self, message="", **kwargs):
        """Send a notification to the device."""
        data = {**self.data, **kwargs.get(ATTR_DATA, {})}

        if data.get(ATTR_VALUE) is not None:
            templ = template_helper.Template(self.data[ATTR_VALUE], self.opp)
            data[ATTR_VALUE] = template_helper.render_complex(templ, None)

        self.opp.services.call(DOMAIN, SERVICE_SET_DEVICE_VALUE, data)
Beispiel #6
0
async def async_call_from_config(opp,
                                 config,
                                 blocking=False,
                                 variables=None,
                                 validate_config=True,
                                 context=None):
    """Call a service based on a config hash."""
    if validate_config:
        try:
            config = cv.SERVICE_SCHEMA(config)
        except vol.Invalid as ex:
            _LOGGER.error("Invalid config for calling service: %s", ex)
            return

    if CONF_SERVICE in config:
        domain_service = config[CONF_SERVICE]
    else:
        try:
            config[CONF_SERVICE_TEMPLATE].opp = opp
            domain_service = config[CONF_SERVICE_TEMPLATE].async_render(
                variables)
            domain_service = cv.service(domain_service)
        except TemplateError as ex:
            if blocking:
                raise
            _LOGGER.error("Error rendering service name template: %s", ex)
            return
        except vol.Invalid:
            if blocking:
                raise
            _LOGGER.error("Template rendered invalid service: %s",
                          domain_service)
            return

    domain, service_name = domain_service.split(".", 1)
    service_data = dict(config.get(CONF_SERVICE_DATA, {}))

    if CONF_SERVICE_DATA_TEMPLATE in config:
        try:
            template.attach(opp, config[CONF_SERVICE_DATA_TEMPLATE])
            service_data.update(
                template.render_complex(config[CONF_SERVICE_DATA_TEMPLATE],
                                        variables))
        except TemplateError as ex:
            _LOGGER.error("Error rendering data template: %s", ex)
            return

    if CONF_SERVICE_ENTITY_ID in config:
        service_data[ATTR_ENTITY_ID] = config[CONF_SERVICE_ENTITY_ID]

    await opp.services.async_call(domain,
                                  service_name,
                                  service_data,
                                  blocking=blocking,
                                  context=context)
Beispiel #7
0
 def _get_pos_time_period_template(self, key):
     try:
         return cv.positive_time_period(
             template.render_complex(self._action[key], self._variables))
     except (exceptions.TemplateError, vol.Invalid) as ex:
         self._log(
             "Error rendering %s %s template: %s",
             self._script.name,
             key,
             ex,
             level=logging.ERROR,
         )
         raise _StopScript from ex
Beispiel #8
0
    async def _async_fire_event(self, action, variables, context):
        """Fire an event."""
        self.last_action = action.get(CONF_ALIAS, action[CONF_EVENT])
        self._log("Executing step %s" % self.last_action)
        event_data = dict(action.get(CONF_EVENT_DATA, {}))
        if CONF_EVENT_DATA_TEMPLATE in action:
            try:
                event_data.update(
                    template.render_complex(action[CONF_EVENT_DATA_TEMPLATE],
                                            variables))
            except exceptions.TemplateError as ex:
                _LOGGER.error("Error rendering event data template: %s", ex)

        self.opp.bus.async_fire(action[CONF_EVENT],
                                event_data,
                                context=context)
Beispiel #9
0
    async def _async_event_step(self):
        """Fire an event."""
        self._step_log(self._action.get(CONF_ALIAS, self._action[CONF_EVENT]))
        event_data = {}
        for conf in [CONF_EVENT_DATA, CONF_EVENT_DATA_TEMPLATE]:
            if conf not in self._action:
                continue

            try:
                event_data.update(
                    template.render_complex(self._action[conf],
                                            self._variables))
            except exceptions.TemplateError as ex:
                self._log("Error rendering event data template: %s",
                          ex,
                          level=logging.ERROR)

        trace_set_result(event=self._action[CONF_EVENT], event_data=event_data)
        self._opp.bus.async_fire(self._action[CONF_EVENT],
                                 event_data,
                                 context=self._context)
Beispiel #10
0
async def async_call_action_from_config(opp: OpenPeerPower, config: dict,
                                        variables: dict,
                                        context: Context | None) -> None:
    """Execute a device action."""
    webhook_id = webhook_id_from_device_id(opp, config[CONF_DEVICE_ID])

    if webhook_id is None:
        raise InvalidDeviceAutomationConfig(
            "Unable to resolve webhook ID from the device ID")

    service_name = get_notify_service(opp, webhook_id)

    if service_name is None:
        raise InvalidDeviceAutomationConfig(
            "Unable to find notify service for webhook ID")

    service_data = {notify.ATTR_TARGET: webhook_id}

    # Render it here because we have access to variables here.
    for key in (notify.ATTR_MESSAGE, notify.ATTR_TITLE, notify.ATTR_DATA):
        if key not in config:
            continue

        value_template = config[key]
        template.attach(opp, value_template)

        try:
            service_data[key] = template.render_complex(
                value_template, variables)
        except template.TemplateError as err:
            raise InvalidDeviceAutomationConfig(
                f"Error rendering {key}: {err}") from err

    await opp.services.async_call(notify.DOMAIN,
                                  service_name,
                                  service_data,
                                  blocking=True,
                                  context=context)
Beispiel #11
0
    def state_automation_listener(entity, from_s, to_s):
        """Listen for state changes and calls action."""
        @callback
        def call_action():
            """Call action with right context."""
            opp.async_run_job(
                action(
                    {
                        "trigger": {
                            "platform":
                            platform_type,
                            "entity_id":
                            entity,
                            "from_state":
                            from_s,
                            "to_state":
                            to_s,
                            "for":
                            time_delta if not time_delta else period[entity],
                        }
                    },
                    context=to_s.context,
                ))

        # Ignore changes to state attributes if from/to is in use
        if (not match_all and from_s is not None and to_s is not None
                and from_s.state == to_s.state):
            return

        if not time_delta:
            call_action()
            return

        variables = {
            "trigger": {
                "platform": "state",
                "entity_id": entity,
                "from_state": from_s,
                "to_state": to_s,
            }
        }

        try:
            if isinstance(time_delta, template.Template):
                period[entity] = 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[entity] = vol.All(
                    cv.time_period, cv.positive_timedelta)(time_delta_data)
            else:
                period[entity] = 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[entity] = async_track_same_state(
            opp,
            period[entity],
            call_action,
            lambda _, _2, to_state: to_state.state == to_s.state,
            entity_ids=entity,
        )
Beispiel #12
0
async def async_attach_trigger(opp,
                               config,
                               action,
                               automation_info,
                               *,
                               platform_type="event"):
    """Listen for events based on configuration."""
    trigger_id = automation_info.get("trigger_id") if automation_info else None
    variables = None
    if automation_info:
        variables = automation_info.get("variables")

    template.attach(opp, config[CONF_EVENT_TYPE])
    event_types = template.render_complex(config[CONF_EVENT_TYPE],
                                          variables,
                                          limited=True)
    removes = []

    event_data_schema = None
    if CONF_EVENT_DATA in config:
        # Render the schema input
        template.attach(opp, config[CONF_EVENT_DATA])
        event_data = {}
        event_data.update(
            template.render_complex(config[CONF_EVENT_DATA],
                                    variables,
                                    limited=True))
        # Build the schema
        event_data_schema = vol.Schema(
            {vol.Required(key): value
             for key, value in event_data.items()},
            extra=vol.ALLOW_EXTRA,
        )

    event_context_schema = None
    if CONF_EVENT_CONTEXT in config:
        # Render the schema input
        template.attach(opp, config[CONF_EVENT_CONTEXT])
        event_context = {}
        event_context.update(
            template.render_complex(config[CONF_EVENT_CONTEXT],
                                    variables,
                                    limited=True))
        # Build the schema
        event_context_schema = vol.Schema(
            {
                vol.Required(key): _schema_value(value)
                for key, value in event_context.items()
            },
            extra=vol.ALLOW_EXTRA,
        )

    job = OppJob(action)

    @callback
    def handle_event(event):
        """Listen for events and calls the action when data matches."""
        try:
            # Check that the event data and context match the configured
            # schema if one was provided
            if event_data_schema:
                event_data_schema(event.data)
            if event_context_schema:
                event_context_schema(event.context.as_dict())
        except vol.Invalid:
            # If event doesn't match, skip event
            return

        opp.async_run_opp_job(
            job,
            {
                "trigger": {
                    "platform": platform_type,
                    "event": event,
                    "description": f"event '{event.event_type}'",
                    "id": trigger_id,
                }
            },
            event.context,
        )

    removes = [
        opp.bus.async_listen(event_type, handle_event)
        for event_type in event_types
    ]

    @callback
    def remove_listen_events():
        """Remove event listeners."""
        for remove in removes:
            remove()

    return remove_listen_events
Beispiel #13
0
    def state_automation_listener(event: Event):
        """Listen for state changes and calls action."""
        entity: str = event.data["entity_id"]
        from_s: State | None = event.data.get("old_state")
        to_s: State | None = event.data.get("new_state")

        if from_s is None:
            old_value = None
        elif attribute is None:
            old_value = from_s.state
        else:
            old_value = from_s.attributes.get(attribute)

        if to_s is None:
            new_value = None
        elif attribute is None:
            new_value = to_s.state
        else:
            new_value = to_s.attributes.get(attribute)

        # When we listen for state changes with `match_all`, we
        # will trigger even if just an attribute changes. When
        # we listen to just an attribute, we should ignore all
        # other attribute changes.
        if attribute is not None and old_value == new_value:
            return

        if (
            not match_from_state(old_value)
            or not match_to_state(new_value)
            or (not match_all and old_value == new_value)
        ):
            return

        @callback
        def call_action():
            """Call action with right context."""
            opp.async_run_opp_job(
                job,
                {
                    "trigger": {
                        "platform": platform_type,
                        "entity_id": entity,
                        "from_state": from_s,
                        "to_state": to_s,
                        "for": time_delta if not time_delta else period[entity],
                        "attribute": attribute,
                        "description": f"state of {entity}",
                        "id": trigger_id,
                    }
                },
                event.context,
            )

        if not time_delta:
            call_action()
            return

        trigger_info = {
            "trigger": {
                "platform": "state",
                "entity_id": entity,
                "from_state": from_s,
                "to_state": to_s,
            }
        }
        variables = {**_variables, **trigger_info}

        try:
            period[entity] = cv.positive_time_period(
                template.render_complex(time_delta, variables)
            )
        except (exceptions.TemplateError, vol.Invalid) as ex:
            _LOGGER.error(
                "Error rendering '%s' for template: %s", automation_info["name"], ex
            )
            return

        def _check_same_state(_, _2, new_st: State):
            if new_st is None:
                return False

            if attribute is None:
                cur_value = new_st.state
            else:
                cur_value = new_st.attributes.get(attribute)

            if CONF_FROM in config and CONF_TO not in config:
                return cur_value != old_value

            return cur_value == new_value

        unsub_track_same[entity] = async_track_same_state(
            opp,
            period[entity],
            call_action,
            _check_same_state,
            entity_ids=entity,
        )
Beispiel #14
0
    def state_automation_listener(event):
        """Listen for state changes and calls action."""
        entity_id = event.data.get("entity_id")
        from_s = event.data.get("old_state")
        to_s = event.data.get("new_state")

        @callback
        def call_action():
            """Call action with right context."""
            opp.async_run_opp_job(
                job,
                {
                    "trigger": {
                        "platform": platform_type,
                        "entity_id": entity_id,
                        "below": below,
                        "above": above,
                        "from_state": from_s,
                        "to_state": to_s,
                        "for": time_delta if not time_delta else period[entity_id],
                        "description": f"numeric state of {entity_id}",
                        "id": trigger_id,
                    }
                },
                to_s.context,
            )

        @callback
        def check_numeric_state_no_raise(entity_id, from_s, to_s):
            """Return True if the criteria are now met, False otherwise."""
            try:
                return check_numeric_state(entity_id, from_s, to_s)
            except exceptions.ConditionError:
                # This is an internal same-state listener so we just drop the
                # error. The same error will be reached and logged by the
                # primary async_track_state_change_event() listener.
                return False

        try:
            matching = check_numeric_state(entity_id, from_s, to_s)
        except exceptions.ConditionError as ex:
            _LOGGER.warning("Error in '%s' trigger: %s", automation_info["name"], ex)
            return

        if not matching:
            armed_entities.add(entity_id)
        elif entity_id in armed_entities:
            armed_entities.discard(entity_id)

            if time_delta:
                try:
                    period[entity_id] = cv.positive_time_period(
                        template.render_complex(time_delta, variables(entity_id))
                    )
                except (exceptions.TemplateError, vol.Invalid) as ex:
                    _LOGGER.error(
                        "Error rendering '%s' for template: %s",
                        automation_info["name"],
                        ex,
                    )
                    return

                unsub_track_same[entity_id] = async_track_same_state(
                    opp,
                    period[entity_id],
                    call_action,
                    entity_ids=entity_id,
                    async_check_same_func=check_numeric_state_no_raise,
                )
            else:
                call_action()
Beispiel #15
0
    def state_automation_listener(entity, from_s, to_s):
        """Listen for state changes and calls action."""
        @callback
        def call_action():
            """Call action with right context."""
            opp.async_run_job(
                action(
                    {
                        "trigger": {
                            "platform":
                            platform_type,
                            "entity_id":
                            entity,
                            "below":
                            below,
                            "above":
                            above,
                            "from_state":
                            from_s,
                            "to_state":
                            to_s,
                            "for":
                            time_delta if not time_delta else period[entity],
                        }
                    },
                    context=to_s.context,
                ))

        matching = check_numeric_state(entity, from_s, to_s)

        if not matching:
            entities_triggered.discard(entity)
        elif entity not in entities_triggered:
            entities_triggered.add(entity)

            if time_delta:
                variables = {
                    "trigger": {
                        "platform": "numeric_state",
                        "entity_id": entity,
                        "below": below,
                        "above": above,
                    }
                }

                try:
                    if isinstance(time_delta, template.Template):
                        period[entity] = 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[entity] = vol.All(
                            cv.time_period,
                            cv.positive_timedelta)(time_delta_data)
                    else:
                        period[entity] = time_delta
                except (exceptions.TemplateError, vol.Invalid) as ex:
                    _LOGGER.error(
                        "Error rendering '%s' for template: %s",
                        automation_info["name"],
                        ex,
                    )
                    entities_triggered.discard(entity)
                    return

                unsub_track_same[entity] = async_track_same_state(
                    opp,
                    period[entity],
                    call_action,
                    entity_ids=entity,
                    async_check_same_func=check_numeric_state,
                )
            else:
                call_action()
Beispiel #16
0
def async_prepare_call_from_config(
    opp: OpenPeerPower,
    config: ConfigType,
    variables: TemplateVarsType = None,
    validate_config: bool = False,
) -> ServiceParams:
    """Prepare to call a service based on a config hash."""
    if validate_config:
        try:
            config = cv.SERVICE_SCHEMA(config)
        except vol.Invalid as ex:
            raise OpenPeerPowerError(
                f"Invalid config for calling service: {ex}") from ex

    if CONF_SERVICE in config:
        domain_service = config[CONF_SERVICE]
    else:
        domain_service = config[CONF_SERVICE_TEMPLATE]

    if isinstance(domain_service, template.Template):
        try:
            domain_service.opp = opp
            domain_service = domain_service.async_render(variables)
            domain_service = cv.service(domain_service)
        except TemplateError as ex:
            raise OpenPeerPowerError(
                f"Error rendering service name template: {ex}") from ex
        except vol.Invalid as ex:
            raise OpenPeerPowerError(
                f"Template rendered invalid service: {domain_service}") from ex

    domain, service = domain_service.split(".", 1)

    target = {}
    if CONF_TARGET in config:
        conf = config[CONF_TARGET]
        try:
            if isinstance(conf, template.Template):
                conf.opp = opp
                target.update(conf.async_render(variables))
            else:
                template.attach(opp, conf)
                target.update(template.render_complex(conf, variables))

            if CONF_ENTITY_ID in target:
                target[CONF_ENTITY_ID] = cv.comp_entity_ids(
                    target[CONF_ENTITY_ID])
        except TemplateError as ex:
            raise OpenPeerPowerError(
                f"Error rendering service target template: {ex}") from ex
        except vol.Invalid as ex:
            raise OpenPeerPowerError(
                f"Template rendered invalid entity IDs: {target[CONF_ENTITY_ID]}"
            ) from ex

    service_data = {}

    for conf in [CONF_SERVICE_DATA, CONF_SERVICE_DATA_TEMPLATE]:
        if conf not in config:
            continue
        try:
            template.attach(opp, config[conf])
            service_data.update(
                template.render_complex(config[conf], variables))
        except TemplateError as ex:
            raise OpenPeerPowerError(
                f"Error rendering data template: {ex}") from ex

    if CONF_SERVICE_ENTITY_ID in config:
        if target:
            target[ATTR_ENTITY_ID] = config[CONF_SERVICE_ENTITY_ID]
        else:
            target = {ATTR_ENTITY_ID: config[CONF_SERVICE_ENTITY_ID]}

    return {
        "domain": domain,
        "service": service,
        "service_data": service_data,
        "target": target,
    }
Beispiel #17
0
async def test_confirmable_notification(opp: OpenPeerPower) -> None:
    """Test confirmable notification blueprint."""
    with patch_blueprint(
            "confirmable_notification.yaml",
            BUILTIN_BLUEPRINT_FOLDER / "confirmable_notification.yaml",
    ):
        assert await async_setup_component(
            opp,
            script.DOMAIN,
            {
                "script": {
                    "confirm": {
                        "use_blueprint": {
                            "path": "confirmable_notification.yaml",
                            "input": {
                                "notify_device":
                                "frodo",
                                "title":
                                "Lord of the things",
                                "message":
                                "Throw ring in mountain?",
                                "confirm_action": [{
                                    "service": "openpeerpower.turn_on",
                                    "target": {
                                        "entity_id": "mount.doom"
                                    },
                                }],
                            },
                        }
                    }
                }
            },
        )

    turn_on_calls = async_mock_service(opp, "openpeerpower", "turn_on")
    context = Context()

    with patch(
            "openpeerpower.components.mobile_app.device_action.async_call_action_from_config"
    ) as mock_call_action:

        # Trigger script
        await opp.services.async_call(script.DOMAIN,
                                      "confirm",
                                      context=context)

        # Give script the time to attach the trigger.
        await asyncio.sleep(0.1)

    opp.bus.async_fire("mobile_app_notification_action",
                       {"action": "ANYTHING_ELSE"})
    opp.bus.async_fire("mobile_app_notification_action",
                       {"action": "CONFIRM_" + Context().id})
    opp.bus.async_fire("mobile_app_notification_action",
                       {"action": "CONFIRM_" + context.id})
    await opp.async_block_till_done()

    assert len(mock_call_action.mock_calls) == 1
    _opp, config, variables, _context = mock_call_action.mock_calls[0][1]

    template.attach(opp, config)
    rendered_config = template.render_complex(config, variables)

    assert rendered_config == {
        "title": "Lord of the things",
        "message": "Throw ring in mountain?",
        "alias": "Send notification",
        "domain": "mobile_app",
        "type": "notify",
        "device_id": "frodo",
        "data": {
            "actions": [
                {
                    "action": "CONFIRM_" + _context.id,
                    "title": "Confirm"
                },
                {
                    "action": "DISMISS_" + _context.id,
                    "title": "Dismiss"
                },
            ]
        },
    }

    assert len(turn_on_calls) == 1
    assert turn_on_calls[0].data == {
        "entity_id": ["mount.doom"],
    }
Beispiel #18
0
    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)