Extract entities from device triggers and device actions (#175454)

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
This commit is contained in:
Franck Nijhof
2026-07-09 19:19:00 +02:00
committed by GitHub
co-authored by copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
parent 750850f0ba
commit 2395296739
7 changed files with 83 additions and 1 deletions
+9
View File
@@ -1829,6 +1829,15 @@ class Script:
for trigger in step[CONF_WAIT_FOR_TRIGGER]:
referenced |= set(trigger_helper.async_extract_entities(trigger))
elif action == cv.SCRIPT_ACTION_DEVICE_AUTOMATION:
# Only extract the entity if it has been resolved to an entity
# id during validation; unvalidated configs hold an entity
# registry id.
if isinstance(
entity_id := step.get(ATTR_ENTITY_ID), str
) and valid_entity_id(entity_id):
referenced.add(entity_id)
elif action == cv.SCRIPT_ACTION_ACTIVATE_SCENE:
referenced.add(step[CONF_SCENE])
+9
View File
@@ -2067,6 +2067,15 @@ def async_extract_entities(trigger_conf: dict) -> list[str]:
entity_ids.append(at_time[CONF_ENTITY_ID])
return entity_ids
if trigger_conf[CONF_PLATFORM] == "device":
# Only extract the entity if it has been resolved to an entity id
# during validation; unvalidated configs hold an entity registry id.
if isinstance(
entity_id := trigger_conf.get(CONF_ENTITY_ID), str
) and valid_entity_id(entity_id):
return [entity_id]
return []
if trigger_conf[CONF_PLATFORM] == "calendar":
return [trigger_conf[CONF_OPTIONS][CONF_ENTITY_ID]]
+1
View File
@@ -2481,6 +2481,7 @@ async def test_extraction_functions(
"sensor.trigger_state",
"sensor.trigger_numeric_state",
"sensor.trigger_event",
"light.bla",
"light.condition_state",
"light.in_both",
"light.in_first",
+1
View File
@@ -919,6 +919,7 @@ async def test_extraction_functions(
"script.test3",
}
assert set(script.entities_in_script(hass, "script.test1")) == {
"light.device_in_both",
"light.in_both",
"light.in_first",
}
+2 -1
View File
@@ -568,6 +568,7 @@ async def test_search(
ItemType.AREA: {living_room_area.id},
ItemType.CONFIG_ENTRY: {wled_config_entry.entry_id},
ItemType.DEVICE: {wled_device.id},
ItemType.ENTITY: {wled_segment_1_entity.entity_id},
ItemType.FLOOR: {first_floor.floor_id},
ItemType.INTEGRATION: {"wled"},
}
@@ -711,7 +712,7 @@ async def test_search(
assert not search(ItemType.ENTITY, "sensor.unknown")
assert search(ItemType.ENTITY, wled_segment_1_entity.entity_id) == {
ItemType.AREA: {living_room_area.id},
ItemType.AUTOMATION: {"automation.wled_entity"},
ItemType.AUTOMATION: {"automation.wled_entity", "automation.wled_device"},
ItemType.CONFIG_ENTRY: {wled_config_entry.entry_id},
ItemType.DEVICE: {wled_device.id},
ItemType.FLOOR: {first_floor.floor_id},
+13
View File
@@ -4833,6 +4833,18 @@ async def test_referenced_entities(hass: HomeAssistant) -> None:
},
{"action": "test.script", "data": {"without": "entity_id"}},
{"scene": "scene.hello"},
{
"domain": "light",
"device_id": "abcdefgh",
"entity_id": "light.device_action",
"type": "turn_on",
},
{
"domain": "light",
"device_id": "abcdefgh",
"entity_id": "1234567890abcdef1234567890abcdef",
"type": "turn_on",
},
{
"choose": [
{
@@ -4989,6 +5001,7 @@ async def test_referenced_entities(hass: HomeAssistant) -> None:
"light.condition_list_2",
"light.condition_target",
"light.default_seq",
"light.device_action",
"light.direct_entity_referenced",
"light.entity_in_data_template",
"light.entity_in_target",
+48
View File
@@ -6020,6 +6020,54 @@ async def test_async_extract_entities(
assert trigger.async_extract_entities(trigger_conf) == expected
@pytest.mark.parametrize(
("trigger_conf", "expected"),
[
pytest.param(
{
"platform": "device",
"device_id": "abcdefgh",
"domain": "light",
"entity_id": "light.kitchen",
"type": "turned_on",
},
["light.kitchen"],
id="resolved-entity-id",
),
pytest.param(
{
"platform": "device",
"device_id": "abcdefgh",
"domain": "light",
"entity_id": "1234567890abcdef1234567890abcdef",
"type": "turned_on",
},
[],
id="unresolved-registry-id",
),
pytest.param(
{
"platform": "device",
"device_id": "abcdefgh",
"domain": "sensor",
"type": "battery_level",
},
[],
id="no-entity-id",
),
],
)
def test_async_extract_entities_device_trigger(
trigger_conf: dict[str, Any], expected: list[str]
) -> None:
"""Test extracting entities from device trigger configs.
Validation resolves the entity registry id to an entity id; extraction
ignores unresolved registry ids.
"""
assert trigger.async_extract_entities(trigger_conf) == expected
_MOCK_DEVICE_ID = "_mock_device_id_"