예제 #1
0
    def state_change_listener(event):
        """Handle specific state changes."""
        # Skip if the event's source does not match the trigger's source.
        from_state = event.data.get("old_state")
        to_state = event.data.get("new_state")
        if not source_match(from_state, source) and not source_match(
                to_state, source):
            return

        zone_state = hass.states.get(zone_entity_id)

        from_match = (condition.zone(hass, zone_state, from_state)
                      if from_state else False)
        to_match = condition.zone(hass, zone_state,
                                  to_state) if to_state else False

        if (trigger_event == EVENT_ENTER and not from_match and to_match or
                trigger_event == EVENT_LEAVE and from_match and not to_match):
            hass.async_run_hass_job(
                job,
                {
                    "trigger": {
                        "platform": "geo_location",
                        "source": source,
                        "entity_id": event.data.get("entity_id"),
                        "from_state": from_state,
                        "to_state": to_state,
                        "zone": zone_state,
                        "event": trigger_event,
                        "description": f"geo_location - {source}",
                    }
                },
                event.context,
            )
예제 #2
0
    def zone_automation_listener(zone_event):
        """Listen for state changes and calls action."""
        entity = zone_event.data.get("entity_id")
        from_s = zone_event.data.get("old_state")
        to_s = zone_event.data.get("new_state")

        if (from_s and not location.has_location(from_s)
                or not location.has_location(to_s)):
            return

        zone_state = hass.states.get(zone_entity_id)
        from_match = condition.zone(hass, zone_state,
                                    from_s) if from_s else False
        to_match = condition.zone(hass, zone_state, to_s) if to_s else False

        if (event == EVENT_ENTER and not from_match and to_match
                or event == EVENT_LEAVE and from_match and not to_match):
            description = f"{entity} {_EVENT_DESCRIPTION[event]} {zone_state.attributes[ATTR_FRIENDLY_NAME]}"
            hass.async_run_hass_job(
                job,
                {
                    "trigger": {
                        **trigger_data,
                        "platform": platform_type,
                        "entity_id": entity,
                        "from_state": from_s,
                        "to_state": to_s,
                        "zone": zone_state,
                        "event": event,
                        "description": description,
                    }
                },
                to_s.context,
            )
예제 #3
0
    def zone_automation_listener(entity, from_s, to_s):
        """Listen for state changes and calls action."""
        if from_s and not location.has_location(from_s) or \
           not location.has_location(to_s):
            return

        zone_state = hass.states.get(zone_entity_id)
        if from_s:
            from_match = condition.zone(hass, zone_state, from_s)
        else:
            from_match = False
        to_match = condition.zone(hass, zone_state, to_s)

        # pylint: disable=too-many-boolean-expressions
        if event == EVENT_ENTER and not from_match and to_match or \
           event == EVENT_LEAVE and from_match and not to_match:
            hass.async_add_job(action, {
                'trigger': {
                    'platform': 'zone',
                    'entity_id': entity,
                    'from_state': from_s,
                    'to_state': to_s,
                    'zone': zone_state,
                    'event': event,
                },
            })
예제 #4
0
    def zone_automation_listener(entity, from_s, to_s):
        """Listen for state changes and calls action."""
        if from_s and not location.has_location(from_s) or not location.has_location(to_s):
            return

        zone_state = hass.states.get(zone_entity_id)
        if from_s:
            from_match = condition.zone(hass, zone_state, from_s)
        else:
            from_match = False
        to_match = condition.zone(hass, zone_state, to_s)

        # pylint: disable=too-many-boolean-expressions
        if event == EVENT_ENTER and not from_match and to_match or event == EVENT_LEAVE and from_match and not to_match:
            action(
                {
                    "trigger": {
                        "platform": "zone",
                        "entity_id": entity,
                        "from_state": from_s,
                        "to_state": to_s,
                        "zone": zone_state,
                        "event": event,
                    }
                }
            )
예제 #5
0
    def zone_automation_listener(entity, from_s, to_s):
        """Listen for state changes and calls action."""
        if from_s and not location.has_location(from_s) or \
           not location.has_location(to_s):
            return

        zone_state = hass.states.get(zone_entity_id)
        if from_s:
            from_match = condition.zone(hass, zone_state, from_s)
        else:
            from_match = False
        to_match = condition.zone(hass, zone_state, to_s)

        # pylint: disable=too-many-boolean-expressions
        if event == EVENT_ENTER and not from_match and to_match or \
           event == EVENT_LEAVE and from_match and not to_match:
            hass.async_run_job(
                action, {
                    'trigger': {
                        'platform': 'zone',
                        'entity_id': entity,
                        'from_state': from_s,
                        'to_state': to_s,
                        'zone': zone_state,
                        'event': event,
                    },
                })
예제 #6
0
async def async_attach_trigger(
    hass, config, action, automation_info, *, platform_type: str = "zone"
) -> CALLBACK_TYPE:
    """Listen for state changes based on configuration."""
    trigger_data = automation_info["trigger_data"]
    entity_id = config.get(CONF_ENTITY_ID)
    zone_entity_id = config.get(CONF_ZONE)
    event = config.get(CONF_EVENT)
    job = HassJob(action)

    @callback
    def zone_automation_listener(zone_event):
        """Listen for state changes and calls action."""
        entity = zone_event.data.get("entity_id")
        from_s = zone_event.data.get("old_state")
        to_s = zone_event.data.get("new_state")

        if (
            from_s
            and not location.has_location(from_s)
            or not location.has_location(to_s)
        ):
            return

        if not (zone_state := hass.states.get(zone_entity_id)):
            _LOGGER.warning(
                "Automation '%s' is referencing non-existing zone '%s' in a zone trigger",
                automation_info["name"],
                zone_entity_id,
            )
            return

        from_match = condition.zone(hass, zone_state, from_s) if from_s else False
        to_match = condition.zone(hass, zone_state, to_s) if to_s else False

        if (
            event == EVENT_ENTER
            and not from_match
            and to_match
            or event == EVENT_LEAVE
            and from_match
            and not to_match
        ):
            description = f"{entity} {_EVENT_DESCRIPTION[event]} {zone_state.attributes[ATTR_FRIENDLY_NAME]}"
            hass.async_run_hass_job(
                job,
                {
                    "trigger": {
                        **trigger_data,
                        "platform": platform_type,
                        "entity_id": entity,
                        "from_state": from_s,
                        "to_state": to_s,
                        "zone": zone_state,
                        "event": event,
                        "description": description,
                    }
                },
                to_s.context,
            )
예제 #7
0
    def state_change_listener(event):
        """Handle specific state changes."""
        # Skip if the event is not a geo_location entity.
        if not event.data.get("entity_id").startswith(DOMAIN):
            return
        # Skip if the event's source does not match the trigger's source.
        from_state = event.data.get("old_state")
        to_state = event.data.get("new_state")
        if not source_match(from_state, source) and not source_match(
                to_state, source):
            return

        zone_state = hass.states.get(zone_entity_id)
        from_match = condition.zone(hass, zone_state, from_state)
        to_match = condition.zone(hass, zone_state, to_state)

        # pylint: disable=too-many-boolean-expressions
        if (trigger_event == EVENT_ENTER and not from_match and to_match or
                trigger_event == EVENT_LEAVE and from_match and not to_match):
            hass.async_run_job(
                action(
                    {
                        "trigger": {
                            "platform": "geo_location",
                            "source": source,
                            "entity_id": event.data.get("entity_id"),
                            "from_state": from_state,
                            "to_state": to_state,
                            "zone": zone_state,
                            "event": trigger_event,
                        }
                    },
                    context=event.context,
                ))
예제 #8
0
    def state_change_listener(event):
        """Handle specific state changes."""
        # Skip if the event is not a geo_location entity.
        if not event.data.get('entity_id').startswith(DOMAIN):
            return
        # Skip if the event's source does not match the trigger's source.
        from_state = event.data.get('old_state')
        to_state = event.data.get('new_state')
        if not source_match(from_state, source) \
           and not source_match(to_state, source):
            return

        zone_state = hass.states.get(zone_entity_id)
        from_match = condition.zone(hass, zone_state, from_state)
        to_match = condition.zone(hass, zone_state, to_state)

        # pylint: disable=too-many-boolean-expressions
        if trigger_event == EVENT_ENTER and not from_match and to_match or \
           trigger_event == EVENT_LEAVE and from_match and not to_match:
            hass.async_run_job(
                action(
                    {
                        'trigger': {
                            'platform': 'geo_location',
                            'source': source,
                            'entity_id': event.data.get('entity_id'),
                            'from_state': from_state,
                            'to_state': to_state,
                            'zone': zone_state,
                            'event': trigger_event,
                        },
                    },
                    context=event.context))
예제 #9
0
    def zone_automation_listener(entity, from_s, to_s):
        """Listen for state changes and calls action."""
        if (from_s and not location.has_location(from_s)
                or not location.has_location(to_s)):
            return

        zone_state = hass.states.get(zone_entity_id)
        if from_s:
            from_match = condition.zone(hass, zone_state, from_s)
        else:
            from_match = False
        to_match = condition.zone(hass, zone_state, to_s)

        # pylint: disable=too-many-boolean-expressions
        if (event == EVENT_ENTER and not from_match and to_match
                or event == EVENT_LEAVE and from_match and not to_match):
            hass.async_run_job(
                action(
                    {
                        "trigger": {
                            "platform": "zone",
                            "entity_id": entity,
                            "from_state": from_s,
                            "to_state": to_s,
                            "zone": zone_state,
                            "event": event,
                        }
                    },
                    context=to_s.context,
                ))
예제 #10
0
    def state_change_listener(event):
        """Handle specific state changes."""
        # Skip if the event is not a geo_location entity.
        if not event.data.get('entity_id').startswith(DOMAIN):
            return
        # Skip if the event's source does not match the trigger's source.
        from_state = event.data.get('old_state')
        to_state = event.data.get('new_state')
        if not source_match(from_state, source) \
           and not source_match(to_state, source):
            return

        zone_state = hass.states.get(zone_entity_id)
        from_match = condition.zone(hass, zone_state, from_state)
        to_match = condition.zone(hass, zone_state, to_state)

        # pylint: disable=too-many-boolean-expressions
        if trigger_event == EVENT_ENTER and not from_match and to_match or \
           trigger_event == EVENT_LEAVE and from_match and not to_match:
            hass.async_run_job(action({
                'trigger': {
                    'platform': 'geo_location',
                    'source': source,
                    'entity_id': event.data.get('entity_id'),
                    'from_state': from_state,
                    'to_state': to_state,
                    'zone': zone_state,
                    'event': trigger_event,
                },
            }, context=event.context))
예제 #11
0
파일: trigger.py 프로젝트: jcgoette/core
async def async_attach_trigger(
    hass: HomeAssistant,
    config: ConfigType,
    action: AutomationActionType,
    automation_info: AutomationTriggerInfo,
) -> CALLBACK_TYPE:
    """Listen for state changes based on configuration."""
    trigger_data = automation_info["trigger_data"]
    source: str = config[CONF_SOURCE].lower()
    zone_entity_id = config.get(CONF_ZONE)
    trigger_event = config.get(CONF_EVENT)
    job = HassJob(action)

    @callback
    def state_change_listener(event):
        """Handle specific state changes."""
        # Skip if the event's source does not match the trigger's source.
        from_state = event.data.get("old_state")
        to_state = event.data.get("new_state")
        if not source_match(from_state, source) and not source_match(
                to_state, source):
            return

        if (zone_state := hass.states.get(zone_entity_id)) is None:
            _LOGGER.warning(
                "Unable to execute automation %s: Zone %s not found",
                automation_info["name"],
                zone_entity_id,
            )
            return

        from_match = (condition.zone(hass, zone_state, from_state)
                      if from_state else False)
        to_match = condition.zone(hass, zone_state,
                                  to_state) if to_state else False

        if (trigger_event == EVENT_ENTER and not from_match and to_match or
                trigger_event == EVENT_LEAVE and from_match and not to_match):
            hass.async_run_hass_job(
                job,
                {
                    "trigger": {
                        **trigger_data,
                        "platform": "geo_location",
                        "source": source,
                        "entity_id": event.data.get("entity_id"),
                        "from_state": from_state,
                        "to_state": to_state,
                        "zone": zone_state,
                        "event": trigger_event,
                        "description": f"geo_location - {source}",
                    }
                },
                event.context,
            )
예제 #12
0
    def zone_automation_listener(zone_event):
        """Listen for state changes and calls action."""
        entity = zone_event.data.get("entity_id")
        from_s = zone_event.data.get("old_state")
        to_s = zone_event.data.get("new_state")

        if (
            from_s
            and not location.has_location(from_s)
            or not location.has_location(to_s)
        ):
            return

        zone_state = hass.states.get(zone_entity_id)
        from_match = condition.zone(hass, zone_state, from_s) if from_s else False
        to_match = condition.zone(hass, zone_state, to_s)

        if (
            event == EVENT_ENTER
            and not from_match
            and to_match
            or event == EVENT_LEAVE
            and from_match
            and not to_match
        ):
            hass.async_run_job(
                action(
                    {
                        "trigger": {
                            "platform": "zone",
                            "entity_id": entity,
                            "from_state": from_s,
                            "to_state": to_s,
                            "zone": zone_state,
                            "event": event,
                        }
                    },
                    context=to_s.context,
                )
            )
예제 #13
0
async def test_zone_raises(hass):
    """Test that zone raises ConditionError on errors."""
    test = await condition.async_from_config(
        hass,
        {
            "condition": "zone",
            "entity_id": "device_tracker.cat",
            "zone": "zone.home",
        },
    )

    with pytest.raises(ConditionError, match="no zone"):
        condition.zone(hass, zone_ent=None, entity="sensor.any")

    with pytest.raises(ConditionError, match="unknown zone"):
        test(hass)

    hass.states.async_set(
        "zone.home",
        "zoning",
        {"name": "home", "latitude": 2.1, "longitude": 1.1, "radius": 10},
    )

    with pytest.raises(ConditionError, match="no entity"):
        condition.zone(hass, zone_ent="zone.home", entity=None)

    with pytest.raises(ConditionError, match="unknown entity"):
        test(hass)

    hass.states.async_set(
        "device_tracker.cat",
        "home",
        {"friendly_name": "cat"},
    )

    with pytest.raises(ConditionError, match="latitude"):
        test(hass)

    hass.states.async_set(
        "device_tracker.cat",
        "home",
        {"friendly_name": "cat", "latitude": 2.1},
    )

    with pytest.raises(ConditionError, match="longitude"):
        test(hass)

    hass.states.async_set(
        "device_tracker.cat",
        "home",
        {"friendly_name": "cat", "latitude": 2.1, "longitude": 1.1},
    )

    # All okay, now test multiple failed conditions
    assert test(hass)

    test = await condition.async_from_config(
        hass,
        {
            "condition": "zone",
            "entity_id": ["device_tracker.cat", "device_tracker.dog"],
            "zone": ["zone.home", "zone.work"],
        },
    )

    with pytest.raises(ConditionError, match="dog"):
        test(hass)

    with pytest.raises(ConditionError, match="work"):
        test(hass)

    hass.states.async_set(
        "zone.work",
        "zoning",
        {"name": "work", "latitude": 20, "longitude": 10, "radius": 25000},
    )

    hass.states.async_set(
        "device_tracker.dog",
        "work",
        {"friendly_name": "dog", "latitude": 20.1, "longitude": 10.1},
    )

    assert test(hass)