コード例 #1
0
ファイル: state.py プロジェクト: jbouwh/core
async def async_attach_trigger(
    hass: HomeAssistant,
    config: ConfigType,
    action: TriggerActionType,
    trigger_info: TriggerInfo,
    *,
    platform_type: str = "state",
) -> CALLBACK_TYPE:
    """Listen for state changes based on configuration."""
    entity_ids = config[CONF_ENTITY_ID]

    if (from_state := config.get(CONF_FROM)) is not None:
        match_from_state = process_state_match(from_state)
コード例 #2
0
    platform_type: str = "state",
) -> CALLBACK_TYPE:
    """Listen for state changes based on configuration."""
    entity_ids = config[CONF_ENTITY_ID]
    if (from_state := config.get(CONF_FROM)) is None:
        from_state = MATCH_ALL
    if (to_state := config.get(CONF_TO)) is None:
        to_state = MATCH_ALL
    time_delta = config.get(CONF_FOR)
    template.attach(hass, time_delta)
    # If neither CONF_FROM or CONF_TO are specified,
    # fire on all changes to the state or an attribute
    match_all = CONF_FROM not in config and CONF_TO not in config
    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 = HassJob(action)

    trigger_data = automation_info["trigger_data"]
    _variables = automation_info["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:
コード例 #3
0
ファイル: state.py プロジェクト: xuefer/home-assistant-core
async def async_attach_trigger(
    hass: HomeAssistant,
    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(hass, 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 = HassJob(action)

    trigger_data = automation_info.get("trigger_data",
                                       {}) if automation_info else {}
    _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."""
            hass.async_run_hass_job(
                job,
                {
                    "trigger": {
                        **trigger_data,
                        "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}",
                    }
                },
                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(
            hass,
            period[entity],
            call_action,
            _check_same_state,
            entity_ids=entity,
        )

    unsub = async_track_state_change_event(hass, 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
コード例 #4
0
ファイル: state.py プロジェクト: jbouwh/core
async def async_attach_trigger(
    hass: HomeAssistant,
    config: ConfigType,
    action: TriggerActionType,
    trigger_info: TriggerInfo,
    *,
    platform_type: str = "state",
) -> CALLBACK_TYPE:
    """Listen for state changes based on configuration."""
    entity_ids = config[CONF_ENTITY_ID]

    if (from_state := config.get(CONF_FROM)) is not None:
        match_from_state = process_state_match(from_state)
    elif (not_from_state := config.get(CONF_NOT_FROM)) is not None:
        match_from_state = process_state_match(not_from_state, invert=True)
    else:
        match_from_state = process_state_match(MATCH_ALL)

    if (to_state := config.get(CONF_TO)) is not None:
        match_to_state = process_state_match(to_state)
    elif (not_to_state := config.get(CONF_NOT_TO)) is not None:
        match_to_state = process_state_match(not_to_state, invert=True)
    else:
        match_to_state = process_state_match(MATCH_ALL)

    time_delta = config.get(CONF_FOR)
    template.attach(hass, time_delta)
    # If neither CONF_FROM or CONF_TO are specified,
    # fire on all changes to the state or an attribute
    match_all = all(item not in config for item in (CONF_FROM, CONF_NOT_FROM,
コード例 #5
0
async def async_attach_trigger(
    hass: HomeAssistant,
    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(hass, 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)

    @callback
    def state_automation_listener(event: Event):
        """Listen for state changes and calls action."""
        entity: str = event.data["entity_id"]
        if entity not in entity_id:
            return

        from_s = event.data.get("old_state")
        to_s = event.data.get("new_state")
        old_state = getattr(from_s, "state", None)
        new_state = getattr(to_s, "state", None)

        if (not match_from_state(old_state) or not match_to_state(new_state)
                or (not match_all and old_state == new_state)):
            return

        @callback
        def call_action():
            """Call action with right context."""
            hass.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=event.context,
                ))

        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

        def _check_same_state(_, _2, new_st):
            if new_st is None:
                return False
            return new_st.state == to_s.state

        unsub_track_same[entity] = async_track_same_state(
            hass,
            period[entity],
            call_action,
            _check_same_state,
            entity_ids=entity,
        )

    unsub = async_track_state_change_event(hass, 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