Пример #1
0
    async def _async_template_startup(self, *_) -> None:
        template_var_tups = []
        for template, attributes in self._template_attrs.items():
            template_var_tups.append(TrackTemplate(template, None))
            for attribute in attributes:
                attribute.async_setup()

        result_info = async_track_template_result(self.opp, template_var_tups,
                                                  self._handle_results)
        self.async_on_remove(result_info.async_remove)
        self._async_update = result_info.async_refresh
        result_info.async_refresh()
Пример #2
0
    async def async_added_to_opp(self):
        """Subscribe to children and template state changes."""
        @callback
        def _async_on_dependency_update(event):
            """Update ha state when dependencies update."""
            self.async_set_context(event.context)
            self.async_schedule_update_op_state(True)

        @callback
        def _async_on_template_update(event, updates):
            """Update ha state when dependencies update."""
            result = updates.pop().result

            if isinstance(result, TemplateError):
                self._state_template_result = None
            else:
                self._state_template_result = result

            if event:
                self.async_set_context(event.context)

            self.async_schedule_update_op_state(True)

        if self._state_template is not None:
            result = async_track_template_result(
                self.opp,
                [TrackTemplate(self._state_template, None)],
                _async_on_template_update,
            )
            self.opp.bus.async_listen_once(
                EVENT_OPENPEERPOWER_START,
                callback(lambda _: result.async_refresh()))

            self.async_on_remove(result.async_remove)

        depend = copy(self._children)
        for entity in self._attrs.values():
            depend.append(entity[0])

        self.async_on_remove(
            self.opp.helpers.event.async_track_state_change_event(
                list(set(depend)), _async_on_dependency_update))
Пример #3
0
async def handle_render_template(opp: OpenPeerPower,
                                 connection: ActiveConnection,
                                 msg: dict[str, Any]) -> None:
    """Handle render_template command."""
    template_str = msg["template"]
    template_obj = template.Template(template_str,
                                     opp)  # type: ignore[no-untyped-call]
    variables = msg.get("variables")
    timeout = msg.get("timeout")
    info = None

    if timeout:
        try:
            timed_out = await template_obj.async_render_will_timeout(
                timeout, strict=msg["strict"])
        except TemplateError as ex:
            connection.send_error(msg["id"], const.ERR_TEMPLATE_ERROR, str(ex))
            return

        if timed_out:
            connection.send_error(
                msg["id"],
                const.ERR_TEMPLATE_ERROR,
                f"Exceeded maximum execution time of {timeout}s",
            )
            return

    @callback
    def _template_listener(event: Event,
                           updates: list[TrackTemplateResult]) -> None:
        nonlocal info
        track_template_result = updates.pop()
        result = track_template_result.result
        if isinstance(result, TemplateError):
            connection.send_error(msg["id"], const.ERR_TEMPLATE_ERROR,
                                  str(result))
            return

        connection.send_message(
            messages.event_message(
                msg["id"],
                {
                    "result": result,
                    "listeners": info.listeners
                }  # type: ignore[attr-defined]
            ))

    try:
        info = async_track_template_result(
            opp,
            [TrackTemplate(template_obj, variables)],
            _template_listener,
            raise_on_template_error=True,
            strict=msg["strict"],
        )
    except TemplateError as ex:
        connection.send_error(msg["id"], const.ERR_TEMPLATE_ERROR, str(ex))
        return

    connection.subscriptions[msg["id"]] = info.async_remove

    connection.send_result(msg["id"])

    opp.loop.call_soon_threadsafe(info.async_refresh)
Пример #4
0
    async def async_added_to_opp(self):
        """
        Call when entity about to be added.

        All relevant update logic for instance attributes occurs within this closure.
        Other methods in this class are designed to avoid directly modifying instance
        attributes, by instead focusing on returning relevant data back to this method.

        The goal of this method is to ensure that `self.current_observations` and `self.probability`
        are set on a best-effort basis when this entity is register with opp.

        In addition, this method must register the state listener defined within, which
        will be called any time a relevant entity changes its state.
        """
        @callback
        def async_threshold_sensor_state_listener(event):
            """
            Handle sensor state changes.

            When a state changes, we must update our list of current observations,
            then calculate the new probability.
            """
            new_state = event.data.get("new_state")

            if new_state is None or new_state.state == STATE_UNKNOWN:
                return

            entity = event.data.get("entity_id")

            self.current_observations.update(
                self._record_entity_observations(entity))
            self.async_set_context(event.context)
            self._recalculate_and_write_state()

        self.async_on_remove(
            async_track_state_change_event(
                self.opp,
                list(self.observations_by_entity),
                async_threshold_sensor_state_listener,
            ))

        @callback
        def _async_template_result_changed(event, updates):
            track_template_result = updates.pop()
            template = track_template_result.template
            result = track_template_result.result
            entity = event and event.data.get("entity_id")

            if isinstance(result, TemplateError):
                _LOGGER.error(
                    "TemplateError('%s') "
                    "while processing template '%s' "
                    "in entity '%s'",
                    result,
                    template,
                    self.entity_id,
                )

                should_trigger = False
            else:
                should_trigger = result_as_boolean(result)

            for obs in self.observations_by_template[template]:
                if should_trigger:
                    obs_entry = {"entity_id": entity, **obs}
                else:
                    obs_entry = None
                self.current_observations[obs["id"]] = obs_entry

            if event:
                self.async_set_context(event.context)
            self._recalculate_and_write_state()

        for template in self.observations_by_template:
            info = async_track_template_result(
                self.opp,
                [TrackTemplate(template, None)],
                _async_template_result_changed,
            )

            self._callbacks.append(info)
            self.async_on_remove(info.async_remove)
            info.async_refresh()

        self.current_observations.update(
            self._initialize_current_observations())
        self.probability = self._calculate_new_probability()
        self._deviation = bool(self.probability >= self._probability_threshold)
Пример #5
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