diff --git a/homeassistant/components/device_tracker/device_trigger.py b/homeassistant/components/device_tracker/device_trigger.py index 49a52fa887e..926519c2243 100644 --- a/homeassistant/components/device_tracker/device_trigger.py +++ b/homeassistant/components/device_tracker/device_trigger.py @@ -89,7 +89,7 @@ async def async_attach_trigger( CONF_ZONE: config[CONF_ZONE], CONF_EVENT: event, } - zone_config = zone.TRIGGER_SCHEMA(zone_config) + zone_config = await zone.async_validate_trigger_config(hass, zone_config) return await zone.async_attach_trigger( hass, zone_config, action, automation_info, platform_type="device" ) diff --git a/homeassistant/components/zone/trigger.py b/homeassistant/components/zone/trigger.py index ef054b39714..373727e3f4d 100644 --- a/homeassistant/components/zone/trigger.py +++ b/homeassistant/components/zone/trigger.py @@ -8,9 +8,15 @@ from homeassistant.const import ( CONF_PLATFORM, CONF_ZONE, ) -from homeassistant.core import CALLBACK_TYPE, HassJob, callback -from homeassistant.helpers import condition, config_validation as cv, location +from homeassistant.core import CALLBACK_TYPE, HassJob, HomeAssistant, callback +from homeassistant.helpers import ( + condition, + config_validation as cv, + entity_registry as er, + location, +) from homeassistant.helpers.event import async_track_state_change_event +from homeassistant.helpers.typing import ConfigType # mypy: allow-incomplete-defs, allow-untyped-defs # mypy: no-check-untyped-defs @@ -21,10 +27,10 @@ DEFAULT_EVENT = EVENT_ENTER _EVENT_DESCRIPTION = {EVENT_ENTER: "entering", EVENT_LEAVE: "leaving"} -TRIGGER_SCHEMA = cv.TRIGGER_BASE_SCHEMA.extend( +_TRIGGER_SCHEMA = cv.TRIGGER_BASE_SCHEMA.extend( { vol.Required(CONF_PLATFORM): "zone", - vol.Required(CONF_ENTITY_ID): cv.entity_ids, + vol.Required(CONF_ENTITY_ID): cv.entity_ids_or_uuids, vol.Required(CONF_ZONE): cv.entity_id, vol.Required(CONF_EVENT, default=DEFAULT_EVENT): vol.Any( EVENT_ENTER, EVENT_LEAVE @@ -33,6 +39,18 @@ TRIGGER_SCHEMA = cv.TRIGGER_BASE_SCHEMA.extend( ) +async def async_validate_trigger_config( + hass: HomeAssistant, config: ConfigType +) -> ConfigType: + """Validate trigger config.""" + config = _TRIGGER_SCHEMA(config) + registry = er.async_get(hass) + config[CONF_ENTITY_ID] = er.async_resolve_entity_ids( + registry, config[CONF_ENTITY_ID] + ) + return config + + async def async_attach_trigger( hass, config, action, automation_info, *, platform_type: str = "zone" ) -> CALLBACK_TYPE: diff --git a/tests/components/zone/test_trigger.py b/tests/components/zone/test_trigger.py index 978603ae242..ee62dd5df3a 100644 --- a/tests/components/zone/test_trigger.py +++ b/tests/components/zone/test_trigger.py @@ -4,6 +4,7 @@ import pytest from homeassistant.components import automation, zone from homeassistant.const import ATTR_ENTITY_ID, ENTITY_MATCH_ALL, SERVICE_TURN_OFF from homeassistant.core import Context +from homeassistant.helpers import entity_registry as er from homeassistant.setup import async_setup_component from tests.common import async_mock_service, mock_component @@ -108,6 +109,85 @@ async def test_if_fires_on_zone_enter(hass, calls): assert len(calls) == 1 +async def test_if_fires_on_zone_enter_uuid(hass, calls): + """Test for firing on zone enter when device is specified by entity registry id.""" + context = Context() + + registry = er.async_get(hass) + entry = registry.async_get_or_create( + "test", "hue", "1234", suggested_object_id="entity" + ) + assert entry.entity_id == "test.entity" + + hass.states.async_set( + "test.entity", "hello", {"latitude": 32.881011, "longitude": -117.234758} + ) + await hass.async_block_till_done() + + assert await async_setup_component( + hass, + automation.DOMAIN, + { + automation.DOMAIN: { + "trigger": { + "platform": "zone", + "entity_id": entry.id, + "zone": "zone.test", + "event": "enter", + }, + "action": { + "service": "test.automation", + "data_template": { + "some": "{{ trigger.%s }}" + % "}} - {{ trigger.".join( + ( + "platform", + "entity_id", + "from_state.state", + "to_state.state", + "zone.name", + "id", + ) + ) + }, + }, + } + }, + ) + + hass.states.async_set( + "test.entity", + "hello", + {"latitude": 32.880586, "longitude": -117.237564}, + context=context, + ) + await hass.async_block_till_done() + + assert len(calls) == 1 + assert calls[0].context.parent_id == context.id + assert calls[0].data["some"] == "zone - test.entity - hello - hello - test - 0" + + # Set out of zone again so we can trigger call + hass.states.async_set( + "test.entity", "hello", {"latitude": 32.881011, "longitude": -117.234758} + ) + await hass.async_block_till_done() + + await hass.services.async_call( + automation.DOMAIN, + SERVICE_TURN_OFF, + {ATTR_ENTITY_ID: ENTITY_MATCH_ALL}, + blocking=True, + ) + + hass.states.async_set( + "test.entity", "hello", {"latitude": 32.880586, "longitude": -117.237564} + ) + await hass.async_block_till_done() + + assert len(calls) == 1 + + async def test_if_not_fires_for_enter_on_zone_leave(hass, calls): """Test for not firing on zone leave.""" hass.states.async_set(