Expand composite devices in tag trigger (#180247)

This commit is contained in:
Erik Montnemery
2026-08-26 14:39:58 +02:00
committed by GitHub
parent 602856054b
commit c9990fa048
2 changed files with 101 additions and 1 deletions
+13 -1
View File
@@ -4,7 +4,7 @@ import voluptuous as vol
from homeassistant.const import CONF_PLATFORM
from homeassistant.core import CALLBACK_TYPE, Event, HassJob, HomeAssistant
from homeassistant.helpers import config_validation as cv
from homeassistant.helpers import config_validation as cv, device_registry as dr
from homeassistant.helpers.trigger import TriggerActionType, TriggerInfo
from homeassistant.helpers.typing import ConfigType
@@ -31,6 +31,18 @@ async def async_attach_trigger(
device_ids: set[str] | None = (
set(config[DEVICE_ID]) if DEVICE_ID in config else None
)
if device_ids is not None:
device_registry = dr.async_get(hass)
# A pre-migration composite device id no longer refers to a registered device;
# a tag scanned event carries the id of one of the devices it was split into.
# Expand it to those split device ids so the trigger keeps matching.
for device_id in list(device_ids):
split_devices = device_registry.async_get_devices_for_composite_device_id(
device_id
)
if split_devices:
device_ids.discard(device_id)
device_ids.update(split_device.id for split_device in split_devices)
job = HassJob(action)
+88
View File
@@ -2,6 +2,7 @@
from typing import Any
import attr
import pytest
from homeassistant.components import automation
@@ -9,8 +10,11 @@ from homeassistant.components.tag import async_scan_tag
from homeassistant.components.tag.const import DEVICE_ID, DOMAIN, TAG_ID
from homeassistant.const import ATTR_ENTITY_ID, SERVICE_TURN_OFF
from homeassistant.core import HomeAssistant, ServiceCall
from homeassistant.helpers import device_registry as dr
from homeassistant.setup import async_setup_component
from tests.common import MockConfigEntry
@pytest.fixture
def tag_setup(hass: HomeAssistant, hass_storage: dict[str, Any]):
@@ -151,3 +155,87 @@ async def test_multiple_tags_and_devices_trigger(
assert service_calls[1].data["message"] == "service called"
assert service_calls[2].data["message"] == "service called"
assert service_calls[3].data["message"] == "service called"
COMPOSITE_ID = "composite00000000000000000000ab"
@pytest.fixture
def split_devices(
hass: HomeAssistant, device_registry: dr.DeviceRegistry
) -> tuple[dr.DeviceEntry, dr.DeviceEntry]:
"""Create two devices which are splits of a pre-migration composite device."""
entry_1 = MockConfigEntry(domain="itg1")
entry_1.add_to_hass(hass)
entry_2 = MockConfigEntry(domain="itg2")
entry_2.add_to_hass(hass)
device_1 = device_registry.async_get_or_create(
config_entry_id=entry_1.entry_id,
identifiers={("itg1", "1")},
name="Split device 1",
)
device_2 = device_registry.async_get_or_create(
config_entry_id=entry_2.entry_id,
identifiers={("itg2", "1")},
name="Split device 2",
)
device_registry._devices[device_1.id] = attr.evolve(
device_1, composite_device_id=COMPOSITE_ID
)
device_registry._devices[device_2.id] = attr.evolve(
device_2, composite_device_id=COMPOSITE_ID
)
return device_registry._devices[device_1.id], device_registry._devices[device_2.id]
async def test_composite_device_trigger(
hass: HomeAssistant,
tag_setup,
service_calls: list[ServiceCall],
split_devices: tuple[dr.DeviceEntry, dr.DeviceEntry],
) -> None:
"""Test a tag trigger configured with a pre-migration composite device id.
The composite device id no longer refers to a registered device; it is expanded to
the ids of the devices it was split into, so a tag scanned by either split device
fires the trigger.
"""
device_1, device_2 = split_devices
assert await tag_setup()
assert await async_setup_component(
hass,
automation.DOMAIN,
{
automation.DOMAIN: [
{
"trigger": {
"platform": DOMAIN,
TAG_ID: "abc123",
DEVICE_ID: COMPOSITE_ID,
},
"action": {
"service": "test.automation",
"data": {"message": "service called"},
},
}
]
},
)
await hass.async_block_till_done()
# A scan by the composite id or an unrelated device should not fire
await async_scan_tag(hass, tag_id="abc123", device_id=COMPOSITE_ID)
await async_scan_tag(hass, tag_id="abc123", device_id="other_device")
await hass.async_block_till_done()
assert len(service_calls) == 0
# A scan by either split device should fire
await async_scan_tag(hass, tag_id="abc123", device_id=device_1.id)
await hass.async_block_till_done()
await async_scan_tag(hass, tag_id="abc123", device_id=device_2.id)
await hass.async_block_till_done()
assert len(service_calls) == 2
assert service_calls[0].data["message"] == "service called"
assert service_calls[1].data["message"] == "service called"