From 49c7d2ff899dd7da832d20bf572e1f0f470bdb46 Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Wed, 21 Jun 2023 14:50:40 +0200 Subject: [PATCH] Teach cover device trigger about entity registry ids (#94971) --- .../components/cover/device_trigger.py | 6 +- tests/components/cover/test_device_trigger.py | 194 ++++++++++++++---- 2 files changed, 154 insertions(+), 46 deletions(-) diff --git a/homeassistant/components/cover/device_trigger.py b/homeassistant/components/cover/device_trigger.py index aad225c8039..2fb456d726d 100644 --- a/homeassistant/components/cover/device_trigger.py +++ b/homeassistant/components/cover/device_trigger.py @@ -43,7 +43,7 @@ STATE_TRIGGER_TYPES = {"opened", "closed", "opening", "closing"} POSITION_TRIGGER_SCHEMA = vol.All( DEVICE_TRIGGER_BASE_SCHEMA.extend( { - vol.Required(CONF_ENTITY_ID): cv.entity_id, + vol.Required(CONF_ENTITY_ID): cv.entity_id_or_uuid, vol.Required(CONF_TYPE): vol.In(POSITION_TRIGGER_TYPES), vol.Optional(CONF_ABOVE): vol.All( vol.Coerce(int), vol.Range(min=0, max=100) @@ -58,7 +58,7 @@ POSITION_TRIGGER_SCHEMA = vol.All( STATE_TRIGGER_SCHEMA = DEVICE_TRIGGER_BASE_SCHEMA.extend( { - vol.Required(CONF_ENTITY_ID): cv.entity_id, + vol.Required(CONF_ENTITY_ID): cv.entity_id_or_uuid, vol.Required(CONF_TYPE): vol.In(STATE_TRIGGER_TYPES), vol.Optional(CONF_FOR): cv.positive_time_period_dict, } @@ -87,7 +87,7 @@ async def async_get_triggers( CONF_PLATFORM: "device", CONF_DEVICE_ID: device_id, CONF_DOMAIN: DOMAIN, - CONF_ENTITY_ID: entry.entity_id, + CONF_ENTITY_ID: entry.id, } if supports_open_close: diff --git a/tests/components/cover/test_device_trigger.py b/tests/components/cover/test_device_trigger.py index aede85fa63c..fc82bbd1499 100644 --- a/tests/components/cover/test_device_trigger.py +++ b/tests/components/cover/test_device_trigger.py @@ -88,7 +88,7 @@ async def test_get_triggers( config_entry_id=config_entry.entry_id, connections={(dr.CONNECTION_NETWORK_MAC, "12:34:56:AB:CD:EF")}, ) - entity_registry.async_get_or_create( + entity_entry = entity_registry.async_get_or_create( DOMAIN, "test", "5678", @@ -97,7 +97,7 @@ async def test_get_triggers( ) if set_state: hass.states.async_set( - f"{DOMAIN}.test_5678", + entity_entry.entity_id, "attributes", {"supported_features": features_state}, ) @@ -110,7 +110,7 @@ async def test_get_triggers( "domain": DOMAIN, "type": trigger, "device_id": device_entry.id, - "entity_id": f"{DOMAIN}.test_5678", + "entity_id": entity_entry.id, "metadata": {"secondary": False}, } for trigger in expected_trigger_types @@ -144,7 +144,7 @@ async def test_get_triggers_hidden_auxiliary( config_entry_id=config_entry.entry_id, connections={(dr.CONNECTION_NETWORK_MAC, "12:34:56:AB:CD:EF")}, ) - entity_registry.async_get_or_create( + entity_entry = entity_registry.async_get_or_create( DOMAIN, "test", "5678", @@ -159,7 +159,7 @@ async def test_get_triggers_hidden_auxiliary( "domain": DOMAIN, "type": trigger, "device_id": device_entry.id, - "entity_id": f"{DOMAIN}.test_5678", + "entity_id": entity_entry.id, "metadata": {"secondary": True}, } for trigger in ["opened", "closed", "opening", "closing"] @@ -208,6 +208,45 @@ async def test_get_trigger_capabilities( } +async def test_get_trigger_capabilities_legacy( + hass: HomeAssistant, + device_registry: dr.DeviceRegistry, + entity_registry: er.EntityRegistry, + enable_custom_integrations: None, +) -> None: + """Test we get the expected capabilities from a cover trigger.""" + platform = getattr(hass.components, f"test.{DOMAIN}") + platform.init() + ent = platform.ENTITIES[0] + assert await async_setup_component(hass, DOMAIN, {DOMAIN: {CONF_PLATFORM: "test"}}) + await hass.async_block_till_done() + + config_entry = MockConfigEntry(domain="test", data={}) + config_entry.add_to_hass(hass) + device_entry = device_registry.async_get_or_create( + config_entry_id=config_entry.entry_id, + connections={(dr.CONNECTION_NETWORK_MAC, "12:34:56:AB:CD:EF")}, + ) + entity_registry.async_get_or_create( + DOMAIN, "test", ent.unique_id, device_id=device_entry.id + ) + + triggers = await async_get_device_automations( + hass, DeviceAutomationType.TRIGGER, device_entry.id + ) + assert len(triggers) == 4 + for trigger in triggers: + trigger["entity_id"] = entity_registry.async_get(trigger["entity_id"]).entity_id + capabilities = await async_get_device_automation_capabilities( + hass, DeviceAutomationType.TRIGGER, trigger + ) + assert capabilities == { + "extra_fields": [ + {"name": "for", "optional": True, "type": "positive_time_period_dict"} + ] + } + + async def test_get_trigger_capabilities_set_pos( hass: HomeAssistant, device_registry: dr.DeviceRegistry, @@ -338,9 +377,13 @@ async def test_get_trigger_capabilities_set_tilt_pos( } -async def test_if_fires_on_state_change(hass: HomeAssistant, calls) -> None: +async def test_if_fires_on_state_change( + hass: HomeAssistant, entity_registry: er.EntityRegistry, calls +) -> None: """Test for state triggers firing.""" - hass.states.async_set("cover.entity", STATE_CLOSED) + entry = entity_registry.async_get_or_create(DOMAIN, "test", "5678") + + hass.states.async_set(entry.entity_id, STATE_CLOSED) assert await async_setup_component( hass, @@ -352,7 +395,7 @@ async def test_if_fires_on_state_change(hass: HomeAssistant, calls) -> None: "platform": "device", "domain": DOMAIN, "device_id": "", - "entity_id": "cover.entity", + "entity_id": entry.id, "type": "opened", }, "action": { @@ -374,7 +417,7 @@ async def test_if_fires_on_state_change(hass: HomeAssistant, calls) -> None: "platform": "device", "domain": DOMAIN, "device_id": "", - "entity_id": "cover.entity", + "entity_id": entry.id, "type": "closed", }, "action": { @@ -396,7 +439,7 @@ async def test_if_fires_on_state_change(hass: HomeAssistant, calls) -> None: "platform": "device", "domain": DOMAIN, "device_id": "", - "entity_id": "cover.entity", + "entity_id": entry.id, "type": "opening", }, "action": { @@ -418,7 +461,7 @@ async def test_if_fires_on_state_change(hass: HomeAssistant, calls) -> None: "platform": "device", "domain": DOMAIN, "device_id": "", - "entity_id": "cover.entity", + "entity_id": entry.id, "type": "closing", }, "action": { @@ -440,42 +483,49 @@ async def test_if_fires_on_state_change(hass: HomeAssistant, calls) -> None: ) # Fake that the entity is opened. - hass.states.async_set("cover.entity", STATE_OPEN) + hass.states.async_set(entry.entity_id, STATE_OPEN) await hass.async_block_till_done() assert len(calls) == 1 - assert calls[0].data[ - "some" - ] == "opened - device - {} - closed - open - None".format("cover.entity") + assert ( + calls[0].data["some"] + == f"opened - device - {entry.entity_id} - closed - open - None" + ) # Fake that the entity is closed. - hass.states.async_set("cover.entity", STATE_CLOSED) + hass.states.async_set(entry.entity_id, STATE_CLOSED) await hass.async_block_till_done() assert len(calls) == 2 - assert calls[1].data[ - "some" - ] == "closed - device - {} - open - closed - None".format("cover.entity") + assert ( + calls[1].data["some"] + == f"closed - device - {entry.entity_id} - open - closed - None" + ) # Fake that the entity is opening. - hass.states.async_set("cover.entity", STATE_OPENING) + hass.states.async_set(entry.entity_id, STATE_OPENING) await hass.async_block_till_done() assert len(calls) == 3 - assert calls[2].data[ - "some" - ] == "opening - device - {} - closed - opening - None".format("cover.entity") + assert ( + calls[2].data["some"] + == f"opening - device - {entry.entity_id} - closed - opening - None" + ) # Fake that the entity is closing. - hass.states.async_set("cover.entity", STATE_CLOSING) + hass.states.async_set(entry.entity_id, STATE_CLOSING) await hass.async_block_till_done() assert len(calls) == 4 - assert calls[3].data[ - "some" - ] == "closing - device - {} - opening - closing - None".format("cover.entity") + assert ( + calls[3].data["some"] + == f"closing - device - {entry.entity_id} - opening - closing - None" + ) -async def test_if_fires_on_state_change_with_for(hass: HomeAssistant, calls) -> None: - """Test for triggers firing with delay.""" - entity_id = "cover.entity" - hass.states.async_set(entity_id, STATE_CLOSED) +async def test_if_fires_on_state_change_legacy( + hass: HomeAssistant, entity_registry: er.EntityRegistry, calls +) -> None: + """Test for state triggers firing.""" + entry = entity_registry.async_get_or_create(DOMAIN, "test", "5678") + + hass.states.async_set(entry.entity_id, STATE_CLOSED) assert await async_setup_component( hass, @@ -487,7 +537,56 @@ async def test_if_fires_on_state_change_with_for(hass: HomeAssistant, calls) -> "platform": "device", "domain": DOMAIN, "device_id": "", - "entity_id": entity_id, + "entity_id": entry.entity_id, + "type": "opened", + }, + "action": { + "service": "test.automation", + "data_template": { + "some": ( + "opened " + "- {{ trigger.platform }} " + "- {{ trigger.entity_id }} " + "- {{ trigger.from_state.state }} " + "- {{ trigger.to_state.state }} " + "- {{ trigger.for }}" + ) + }, + }, + }, + ] + }, + ) + + # Fake that the entity is opened. + hass.states.async_set(entry.entity_id, STATE_OPEN) + await hass.async_block_till_done() + assert len(calls) == 1 + assert ( + calls[0].data["some"] + == f"opened - device - {entry.entity_id} - closed - open - None" + ) + + +async def test_if_fires_on_state_change_with_for( + hass: HomeAssistant, entity_registry: er.EntityRegistry, calls +) -> None: + """Test for triggers firing with delay.""" + entry = entity_registry.async_get_or_create(DOMAIN, "test", "5678") + + hass.states.async_set(entry.entity_id, STATE_CLOSED) + + assert await async_setup_component( + hass, + automation.DOMAIN, + { + automation.DOMAIN: [ + { + "trigger": { + "platform": "device", + "domain": DOMAIN, + "device_id": "", + "entity_id": entry.id, "type": "opened", "for": {"seconds": 5}, }, @@ -511,10 +610,9 @@ async def test_if_fires_on_state_change_with_for(hass: HomeAssistant, calls) -> }, ) await hass.async_block_till_done() - assert hass.states.get(entity_id).state == STATE_CLOSED assert len(calls) == 0 - hass.states.async_set(entity_id, STATE_OPEN) + hass.states.async_set(entry.entity_id, STATE_OPEN) await hass.async_block_till_done() assert len(calls) == 0 async_fire_time_changed(hass, dt_util.utcnow() + timedelta(seconds=10)) @@ -523,12 +621,15 @@ async def test_if_fires_on_state_change_with_for(hass: HomeAssistant, calls) -> await hass.async_block_till_done() assert ( calls[0].data["some"] - == f"turn_off device - {entity_id} - closed - open - 0:00:05" + == f"turn_off device - {entry.entity_id} - closed - open - 0:00:05" ) async def test_if_fires_on_position( - hass: HomeAssistant, calls, enable_custom_integrations: None + hass: HomeAssistant, + entity_registry: er.EntityRegistry, + calls, + enable_custom_integrations: None, ) -> None: """Test for position triggers.""" platform = getattr(hass.components, f"test.{DOMAIN}") @@ -537,6 +638,8 @@ async def test_if_fires_on_position( assert await async_setup_component(hass, DOMAIN, {DOMAIN: {CONF_PLATFORM: "test"}}) await hass.async_block_till_done() + entry = entity_registry.async_get(ent.entity_id) + assert await async_setup_component( hass, automation.DOMAIN, @@ -548,7 +651,7 @@ async def test_if_fires_on_position( "platform": "device", "domain": DOMAIN, "device_id": "", - "entity_id": ent.entity_id, + "entity_id": entry.id, "type": "position", "above": 45, } @@ -573,7 +676,7 @@ async def test_if_fires_on_position( "platform": "device", "domain": DOMAIN, "device_id": "", - "entity_id": ent.entity_id, + "entity_id": entry.id, "type": "position", "below": 90, } @@ -598,7 +701,7 @@ async def test_if_fires_on_position( "platform": "device", "domain": DOMAIN, "device_id": "", - "entity_id": ent.entity_id, + "entity_id": entry.id, "type": "position", "above": 45, "below": 90, @@ -669,7 +772,10 @@ async def test_if_fires_on_position( async def test_if_fires_on_tilt_position( - hass: HomeAssistant, calls, enable_custom_integrations: None + hass: HomeAssistant, + entity_registry: er.EntityRegistry, + calls, + enable_custom_integrations: None, ) -> None: """Test for tilt position triggers.""" platform = getattr(hass.components, f"test.{DOMAIN}") @@ -678,6 +784,8 @@ async def test_if_fires_on_tilt_position( assert await async_setup_component(hass, DOMAIN, {DOMAIN: {CONF_PLATFORM: "test"}}) await hass.async_block_till_done() + entry = entity_registry.async_get(ent.entity_id) + assert await async_setup_component( hass, automation.DOMAIN, @@ -689,7 +797,7 @@ async def test_if_fires_on_tilt_position( "platform": "device", "domain": DOMAIN, "device_id": "", - "entity_id": ent.entity_id, + "entity_id": entry.id, "type": "tilt_position", "above": 45, } @@ -714,7 +822,7 @@ async def test_if_fires_on_tilt_position( "platform": "device", "domain": DOMAIN, "device_id": "", - "entity_id": ent.entity_id, + "entity_id": entry.id, "type": "tilt_position", "below": 90, } @@ -739,7 +847,7 @@ async def test_if_fires_on_tilt_position( "platform": "device", "domain": DOMAIN, "device_id": "", - "entity_id": ent.entity_id, + "entity_id": entry.id, "type": "tilt_position", "above": 45, "below": 90,