Пример #1
0
 def __init__(
     self,
     opp: OpenPeerPower,
     sequence: Sequence[Dict[str, Any]],
     name: Optional[str] = None,
     change_listener: Optional[Callable[..., Any]] = None,
 ) -> None:
     """Initialize the script."""
     self.opp = opp
     self.sequence = sequence
     template.attach(opp, self.sequence)
     self.name = name
     self._change_listener = change_listener
     self._cur = -1
     self._exception_step: Optional[int] = None
     self.last_action = None
     self.last_triggered: Optional[datetime] = None
     self.can_cancel = any(
         CONF_DELAY in action or CONF_WAIT_TEMPLATE in action
         for action in self.sequence)
     self._async_listener: List[CALLBACK_TYPE] = []
     self._config_cache: Dict[Set[Tuple], Callable[..., bool]] = {}
     self._actions = {
         ACTION_DELAY: self._async_delay,
         ACTION_WAIT_TEMPLATE: self._async_wait_template,
         ACTION_CHECK_CONDITION: self._async_check_condition,
         ACTION_FIRE_EVENT: self._async_fire_event,
         ACTION_CALL_SERVICE: self._async_call_service,
         ACTION_DEVICE_AUTOMATION: self._async_device_automation,
         ACTION_ACTIVATE_SCENE: self._async_activate_scene,
     }
     self._referenced_entities: Optional[Set[str]] = None
     self._referenced_devices: Optional[Set[str]] = None
Пример #2
0
    def __init__(
        self,
        opp,
        camera_entity,
        name,
        category_index,
        config,
    ):
        """Initialize the TensorFlow entity."""
        model_config = config.get(CONF_MODEL)
        self.opp = opp
        self._camera_entity = camera_entity
        if name:
            self._name = name
        else:
            self._name = f"TensorFlow {split_entity_id(camera_entity)[1]}"
        self._category_index = category_index
        self._min_confidence = config.get(CONF_CONFIDENCE)
        self._file_out = config.get(CONF_FILE_OUT)

        # handle categories and specific detection areas
        self._label_id_offset = model_config.get(CONF_LABEL_OFFSET)
        categories = model_config.get(CONF_CATEGORIES)
        self._include_categories = []
        self._category_areas = {}
        for category in categories:
            if isinstance(category, dict):
                category_name = category.get(CONF_CATEGORY)
                category_area = category.get(CONF_AREA)
                self._include_categories.append(category_name)
                self._category_areas[category_name] = [0, 0, 1, 1]
                if category_area:
                    self._category_areas[category_name] = [
                        category_area.get(CONF_TOP),
                        category_area.get(CONF_LEFT),
                        category_area.get(CONF_BOTTOM),
                        category_area.get(CONF_RIGHT),
                    ]
            else:
                self._include_categories.append(category)
                self._category_areas[category] = [0, 0, 1, 1]

        # Handle global detection area
        self._area = [0, 0, 1, 1]
        area_config = model_config.get(CONF_AREA)
        if area_config:
            self._area = [
                area_config.get(CONF_TOP),
                area_config.get(CONF_LEFT),
                area_config.get(CONF_BOTTOM),
                area_config.get(CONF_RIGHT),
            ]

        template.attach(opp, self._file_out)

        self._matches = {}
        self._total_matches = 0
        self._last_image = None
        self._process_time = 0
Пример #3
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)
Пример #4
0
async def webhook_render_template(opp, config_entry, data):
    """Handle a render template webhook."""
    resp = {}
    for key, item in data.items():
        try:
            tpl = item[ATTR_TEMPLATE]
            attach(opp, tpl)
            resp[key] = tpl.async_render(item.get(ATTR_TEMPLATE_VARIABLES))
        except TemplateError as ex:
            resp[key] = {"error": str(ex)}

    return webhook_response(resp, registration=config_entry.data)
Пример #5
0
async def async_setup(opp, config):
    """Activate Alexa component."""
    intents = copy.deepcopy(config[DOMAIN])
    template.attach(opp, intents)

    for intent_type, conf in intents.items():
        if CONF_ACTION in conf:
            conf[CONF_ACTION] = script.Script(opp, conf[CONF_ACTION],
                                              f"Intent Script {intent_type}",
                                              DOMAIN)
        intent.async_register(opp, ScriptIntentHandler(intent_type, conf))

    return True
Пример #6
0
    def test_not_mutate_input(self):
        """Test for immutable input."""
        config = cv.SERVICE_SCHEMA({
            "service": "test_domain.test_service",
            "entity_id": "hello.world, sensor.beer",
            "data": {
                "hello": 1
            },
            "data_template": {
                "nested": {
                    "value": "{{ 1 + 1 }}"
                }
            },
        })
        orig = deepcopy(config)

        # Only change after call is each template getting opp.attached
        template.attach(self.opp, orig)

        service.call_from_config(self.opp, config, validate_config=False)
        assert orig == config
Пример #7
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)
Пример #8
0
async def async_attach_trigger(
    opp: OpenPeerPower,
    config,
    action,
    automation_info,
    *,
    platform_type: str = "state",
) -> CALLBACK_TYPE:
    """Listen for state changes based on configuration."""
    entity_id = config.get(CONF_ENTITY_ID)
    from_state = config.get(CONF_FROM, MATCH_ALL)
    to_state = config.get(CONF_TO, MATCH_ALL)
    time_delta = config.get(CONF_FOR)
    template.attach(opp, time_delta)
    match_all = from_state == MATCH_ALL and to_state == MATCH_ALL
    unsub_track_same = {}
    period: Dict[str, timedelta] = {}

    @callback
    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,
        )

    unsub = async_track_state_change(opp, entity_id, state_automation_listener,
                                     from_state, to_state)

    @callback
    def async_remove():
        """Remove state listeners async."""
        unsub()
        for async_remove in unsub_track_same.values():
            async_remove()
        unsub_track_same.clear()

    return async_remove
Пример #9
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
Пример #10
0
 async def async_added_to_opp(self) -> None:
     """Handle being added to Open Peer Power."""
     template.attach(self.opp, self._config)
     await super().async_added_to_opp()
     if self.coordinator.data is not None:
         self._process_data()
Пример #11
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,
    }
Пример #12
0
async def async_attach_trigger(
    opp, config, action, automation_info, *, platform_type="numeric_state"
):
    """Listen for state changes based on configuration."""
    value_template = config.get(CONF_VALUE_TEMPLATE)
    value_template.opp = opp
    time_delta = config.get(CONF_FOR)
    template.attach(opp, 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."""
            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(),
        )

    unsub = async_track_template(opp, 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
Пример #13
0
    def __init__(
        self,
        opp: OpenPeerPower,
        sequence: Sequence[dict[str, Any]],
        name: str,
        domain: str,
        *,
        # Used in "Running <running_description>" log message
        running_description: str | None = None,
        change_listener: Callable[..., Any] | None = None,
        script_mode: str = DEFAULT_SCRIPT_MODE,
        max_runs: int = DEFAULT_MAX,
        max_exceeded: str = DEFAULT_MAX_EXCEEDED,
        logger: logging.Logger | None = None,
        log_exceptions: bool = True,
        top_level: bool = True,
        variables: ScriptVariables | None = None,
    ) -> None:
        """Initialize the script."""
        all_scripts = opp.data.get(DATA_SCRIPTS)
        if not all_scripts:
            all_scripts = opp.data[DATA_SCRIPTS] = []
            opp.bus.async_listen_once(
                EVENT_OPENPEERPOWER_STOP,
                partial(_async_stop_scripts_at_shutdown, opp))
        self._top_level = top_level
        if top_level:
            all_scripts.append({
                "instance": self,
                "started_before_shutdown": not opp.is_stopping
            })
        if DATA_SCRIPT_BREAKPOINTS not in opp.data:
            opp.data[DATA_SCRIPT_BREAKPOINTS] = {}

        self._opp = opp
        self.sequence = sequence
        template.attach(opp, self.sequence)
        self.name = name
        self.domain = domain
        self.running_description = running_description or f"{domain} script"
        self._change_listener = change_listener
        self._change_listener_job = (None if change_listener is None else
                                     OppJob(change_listener))

        self.script_mode = script_mode
        self._set_logger(logger)
        self._log_exceptions = log_exceptions

        self.last_action = None
        self.last_triggered: datetime | None = None

        self._runs: list[_ScriptRun] = []
        self.max_runs = max_runs
        self._max_exceeded = max_exceeded
        if script_mode == SCRIPT_MODE_QUEUED:
            self._queue_lck = asyncio.Lock()
        self._config_cache: dict[set[tuple], Callable[..., bool]] = {}
        self._repeat_script: dict[int, Script] = {}
        self._choose_data: dict[int, _ChooseData] = {}
        self._referenced_entities: set[str] | None = None
        self._referenced_devices: set[str] | None = None
        self._referenced_areas: set[str] | None = None
        self.variables = variables
        self._variables_dynamic = template.is_complex(variables)
        if self._variables_dynamic:
            template.attach(opp, variables)
Пример #14
0
    def __init__(self, opp, camera_entity, name, doods, detector, config):
        """Initialize the DOODS entity."""
        self.opp = opp
        self._camera_entity = camera_entity
        if name:
            self._name = name
        else:
            name = split_entity_id(camera_entity)[1]
            self._name = f"Doods {name}"
        self._doods = doods
        self._file_out = config[CONF_FILE_OUT]
        self._detector_name = detector["name"]

        # detector config and aspect ratio
        self._width = None
        self._height = None
        self._aspect = None
        if detector["width"] and detector["height"]:
            self._width = detector["width"]
            self._height = detector["height"]
            self._aspect = self._width / self._height

        # the base confidence
        dconfig = {}
        confidence = config[CONF_CONFIDENCE]

        # handle labels and specific detection areas
        labels = config[CONF_LABELS]
        self._label_areas = {}
        self._label_covers = {}
        for label in labels:
            if isinstance(label, dict):
                label_name = label[CONF_NAME]
                if label_name not in detector["labels"] and label_name != "*":
                    _LOGGER.warning("Detector does not support label %s", label_name)
                    continue

                # If label confidence is not specified, use global confidence
                label_confidence = label.get(CONF_CONFIDENCE)
                if not label_confidence:
                    label_confidence = confidence
                if label_name not in dconfig or dconfig[label_name] > label_confidence:
                    dconfig[label_name] = label_confidence

                # Label area
                label_area = label.get(CONF_AREA)
                self._label_areas[label_name] = [0, 0, 1, 1]
                self._label_covers[label_name] = True
                if label_area:
                    self._label_areas[label_name] = [
                        label_area[CONF_TOP],
                        label_area[CONF_LEFT],
                        label_area[CONF_BOTTOM],
                        label_area[CONF_RIGHT],
                    ]
                    self._label_covers[label_name] = label_area[CONF_COVERS]
            else:
                if label not in detector["labels"] and label != "*":
                    _LOGGER.warning("Detector does not support label %s", label)
                    continue
                self._label_areas[label] = [0, 0, 1, 1]
                self._label_covers[label] = True
                if label not in dconfig or dconfig[label] > confidence:
                    dconfig[label] = confidence

        if not dconfig:
            dconfig["*"] = confidence

        # Handle global detection area
        self._area = [0, 0, 1, 1]
        self._covers = True
        area_config = config.get(CONF_AREA)
        if area_config:
            self._area = [
                area_config[CONF_TOP],
                area_config[CONF_LEFT],
                area_config[CONF_BOTTOM],
                area_config[CONF_RIGHT],
            ]
            self._covers = area_config[CONF_COVERS]

        template.attach(opp, self._file_out)

        self._dconfig = dconfig
        self._matches = {}
        self._total_matches = 0
        self._last_image = None
        self._process_time = 0
Пример #15
0
async def async_attach_trigger(
    opp, config, action, automation_info, *, platform_type="numeric_state"
) -> CALLBACK_TYPE:
    """Listen for state changes based on configuration."""
    entity_ids = config.get(CONF_ENTITY_ID)
    below = config.get(CONF_BELOW)
    above = config.get(CONF_ABOVE)
    time_delta = config.get(CONF_FOR)
    template.attach(opp, time_delta)
    value_template = config.get(CONF_VALUE_TEMPLATE)
    unsub_track_same = {}
    armed_entities = set()
    period: dict = {}
    attribute = config.get(CONF_ATTRIBUTE)
    job = OppJob(action)

    trigger_id = automation_info.get("trigger_id") if automation_info else None
    _variables = {}
    if automation_info:
        _variables = automation_info.get("variables") or {}

    if value_template is not None:
        value_template.opp = opp

    def variables(entity_id):
        """Return a dict with trigger variables."""
        trigger_info = {
            "trigger": {
                "platform": "numeric_state",
                "entity_id": entity_id,
                "below": below,
                "above": above,
                "attribute": attribute,
            }
        }
        return {**_variables, **trigger_info}

    @callback
    def check_numeric_state(entity_id, from_s, to_s):
        """Return whether the criteria are met, raise ConditionError if unknown."""
        return condition.async_numeric_state(
            opp, to_s, below, above, value_template, variables(entity_id), attribute
        )

    # Each entity that starts outside the range is already armed (ready to fire).
    for entity_id in entity_ids:
        try:
            if not check_numeric_state(entity_id, None, entity_id):
                armed_entities.add(entity_id)
        except exceptions.ConditionError as ex:
            _LOGGER.warning(
                "Error initializing '%s' trigger: %s",
                automation_info["name"],
                ex,
            )

    @callback
    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()

    unsub = async_track_state_change_event(opp, entity_ids, state_automation_listener)

    @callback
    def async_remove():
        """Remove state listeners async."""
        unsub()
        for async_remove in unsub_track_same.values():
            async_remove()
        unsub_track_same.clear()

    return async_remove
Пример #16
0
async def async_attach_trigger(opp, config, action, automation_info):
    """Listen for state changes based on configuration."""
    trigger_id = automation_info.get("trigger_id") if automation_info else None
    topic = config[CONF_TOPIC]
    wanted_payload = config.get(CONF_PAYLOAD)
    value_template = config.get(CONF_VALUE_TEMPLATE)
    encoding = config[CONF_ENCODING] or None
    qos = config[CONF_QOS]
    job = OppJob(action)
    variables = None
    if automation_info:
        variables = automation_info.get("variables")

    template.attach(opp, wanted_payload)
    if wanted_payload:
        wanted_payload = wanted_payload.async_render(variables,
                                                     limited=True,
                                                     parse_result=False)

    template.attach(opp, topic)
    if isinstance(topic, template.Template):
        topic = topic.async_render(variables, limited=True, parse_result=False)
        topic = mqtt.util.valid_subscribe_topic(topic)

    template.attach(opp, value_template)

    @callback
    def mqtt_automation_listener(mqttmsg):
        """Listen for MQTT messages."""
        payload = mqttmsg.payload

        if value_template is not None:
            payload = value_template.async_render_with_possible_json_value(
                payload,
                error_value=None,
            )

        if wanted_payload is None or wanted_payload == payload:
            data = {
                "platform": "mqtt",
                "topic": mqttmsg.topic,
                "payload": mqttmsg.payload,
                "qos": mqttmsg.qos,
                "description": f"mqtt topic {mqttmsg.topic}",
                "id": trigger_id,
            }

            with suppress(ValueError):
                data["payload_json"] = json.loads(mqttmsg.payload)

            opp.async_run_opp_job(job, {"trigger": data})

    _LOGGER.debug("Attaching MQTT trigger for topic: '%s', payload: '%s'",
                  topic, wanted_payload)

    remove = await mqtt.async_subscribe(opp,
                                        topic,
                                        mqtt_automation_listener,
                                        encoding=encoding,
                                        qos=qos)
    return remove
Пример #17
0
async def async_attach_trigger(opp,
                               config,
                               action,
                               automation_info,
                               *,
                               platform_type="numeric_state") -> CALLBACK_TYPE:
    """Listen for state changes based on configuration."""
    entity_id = config.get(CONF_ENTITY_ID)
    below = config.get(CONF_BELOW)
    above = config.get(CONF_ABOVE)
    time_delta = config.get(CONF_FOR)
    template.attach(opp, time_delta)
    value_template = config.get(CONF_VALUE_TEMPLATE)
    unsub_track_same = {}
    entities_triggered = set()
    period: dict = {}

    if value_template is not None:
        value_template.opp = opp

    @callback
    def check_numeric_state(entity, from_s, to_s):
        """Return True if criteria are now met."""
        if to_s is None:
            return False

        variables = {
            "trigger": {
                "platform": "numeric_state",
                "entity_id": entity,
                "below": below,
                "above": above,
            }
        }
        return condition.async_numeric_state(opp, to_s, below, above,
                                             value_template, variables)

    @callback
    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()

    unsub = async_track_state_change(opp, entity_id, state_automation_listener)

    @callback
    def async_remove():
        """Remove state listeners async."""
        unsub()
        for async_remove in unsub_track_same.values():
            async_remove()
        unsub_track_same.clear()

    return async_remove
Пример #18
0
 def __init__(self, opp, flash_briefings):
     """Initialize Alexa view."""
     super().__init__()
     self.flash_briefings = copy.deepcopy(flash_briefings)
     template.attach(opp, self.flash_briefings)
Пример #19
0
async def async_attach_trigger(
    opp: OpenPeerPower,
    config,
    action,
    automation_info,
    *,
    platform_type: str = "state",
) -> CALLBACK_TYPE:
    """Listen for state changes based on configuration."""
    entity_id = config.get(CONF_ENTITY_ID)
    from_state = config.get(CONF_FROM, MATCH_ALL)
    to_state = config.get(CONF_TO, MATCH_ALL)
    time_delta = config.get(CONF_FOR)
    template.attach(opp, time_delta)
    match_all = from_state == MATCH_ALL and to_state == MATCH_ALL
    unsub_track_same = {}
    period: dict[str, timedelta] = {}
    match_from_state = process_state_match(from_state)
    match_to_state = process_state_match(to_state)
    attribute = config.get(CONF_ATTRIBUTE)
    job = OppJob(action)

    trigger_id = automation_info.get("trigger_id") if automation_info else None
    _variables = {}
    if automation_info:
        _variables = automation_info.get("variables") or {}

    @callback
    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,
        )

    unsub = async_track_state_change_event(opp, entity_id, state_automation_listener)

    @callback
    def async_remove():
        """Remove state listeners async."""
        unsub()
        for async_remove in unsub_track_same.values():
            async_remove()
        unsub_track_same.clear()

    return async_remove
Пример #20
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"],
    }
Пример #21
0
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