diff --git a/homeassistant/components/deconz/__init__.py b/homeassistant/components/deconz/__init__.py index fec7b82e365..1b8c47d36a2 100644 --- a/homeassistant/components/deconz/__init__.py +++ b/homeassistant/components/deconz/__init__.py @@ -1,11 +1,17 @@ """Support for deCONZ devices.""" import voluptuous as vol -from homeassistant.const import EVENT_HOMEASSISTANT_STOP -from homeassistant.helpers.typing import UNDEFINED +from homeassistant.const import ( + CONF_API_KEY, + CONF_HOST, + CONF_PORT, + EVENT_HOMEASSISTANT_STOP, +) +from homeassistant.core import callback +from homeassistant.helpers.entity_registry import async_migrate_entries from .config_flow import get_master_gateway -from .const import CONF_BRIDGE_ID, CONF_GROUP_ID_BASE, CONF_MASTER_GATEWAY, DOMAIN +from .const import CONF_GROUP_ID_BASE, CONF_MASTER_GATEWAY, DOMAIN from .gateway import DeconzGateway from .services import async_setup_services, async_unload_services @@ -28,6 +34,8 @@ async def async_setup_entry(hass, config_entry): if DOMAIN not in hass.data: hass.data[DOMAIN] = {} + await async_update_group_unique_id(hass, config_entry) + if not config_entry.options: await async_update_master_gateway(hass, config_entry) @@ -36,18 +44,6 @@ async def async_setup_entry(hass, config_entry): if not await gateway.async_setup(): return False - # 0.104 introduced config entry unique id, this makes upgrading possible - if config_entry.unique_id is None: - - new_data = UNDEFINED - if CONF_BRIDGE_ID in config_entry.data: - new_data = dict(config_entry.data) - new_data[CONF_GROUP_ID_BASE] = config_entry.data[CONF_BRIDGE_ID] - - hass.config_entries.async_update_entry( - config_entry, unique_id=gateway.api.config.bridgeid, data=new_data - ) - hass.data[DOMAIN][config_entry.unique_id] = gateway await gateway.async_update_device_registry() @@ -84,3 +80,30 @@ async def async_update_master_gateway(hass, config_entry): options = {**config_entry.options, CONF_MASTER_GATEWAY: master} hass.config_entries.async_update_entry(config_entry, options=options) + + +async def async_update_group_unique_id(hass, config_entry) -> None: + """Update unique ID entities based on deCONZ groups.""" + if not (old_unique_id := config_entry.data.get(CONF_GROUP_ID_BASE)): + return + + new_unique_id: str = config_entry.unique_id + + @callback + def update_unique_id(entity_entry): + """Update unique ID of entity entry.""" + if f"{old_unique_id}-" not in entity_entry.unique_id: + return None + return { + "new_unique_id": entity_entry.unique_id.replace( + old_unique_id, new_unique_id + ) + } + + await async_migrate_entries(hass, config_entry.entry_id, update_unique_id) + data = { + CONF_API_KEY: config_entry.data[CONF_API_KEY], + CONF_HOST: config_entry.data[CONF_HOST], + CONF_PORT: config_entry.data[CONF_PORT], + } + hass.config_entries.async_update_entry(config_entry, data=data) diff --git a/homeassistant/components/deconz/light.py b/homeassistant/components/deconz/light.py index 9080160c76f..2da435c5530 100644 --- a/homeassistant/components/deconz/light.py +++ b/homeassistant/components/deconz/light.py @@ -26,7 +26,6 @@ from homeassistant.helpers.dispatcher import async_dispatcher_connect import homeassistant.util.color as color_util from .const import ( - CONF_GROUP_ID_BASE, COVER_TYPES, DOMAIN as DECONZ_DOMAIN, LOCK_TYPES, @@ -248,10 +247,7 @@ class DeconzGroup(DeconzBaseLight): def __init__(self, device, gateway): """Set up group and create an unique id.""" - group_id_base = gateway.config_entry.unique_id - if CONF_GROUP_ID_BASE in gateway.config_entry.data: - group_id_base = gateway.config_entry.data[CONF_GROUP_ID_BASE] - self._unique_id = f"{group_id_base}-{device.deconz_id}" + self._unique_id = f"{gateway.bridgeid}-{device.deconz_id}" super().__init__(device, gateway) diff --git a/tests/components/deconz/test_gateway.py b/tests/components/deconz/test_gateway.py index 1790b6ed6e1..f670f2a1d10 100644 --- a/tests/components/deconz/test_gateway.py +++ b/tests/components/deconz/test_gateway.py @@ -66,6 +66,7 @@ async def setup_deconz_integration( options=ENTRY_OPTIONS, get_state_response=DECONZ_WEB_REQUEST, entry_id="1", + unique_id=BRIDGEID, source="user", ): """Create the deCONZ gateway.""" @@ -76,6 +77,7 @@ async def setup_deconz_integration( connection_class=CONN_CLASS_LOCAL_PUSH, options=deepcopy(options), entry_id=entry_id, + unique_id=unique_id, ) config_entry.add_to_hass(hass) diff --git a/tests/components/deconz/test_init.py b/tests/components/deconz/test_init.py index d408d764d0e..43c0c48440c 100644 --- a/tests/components/deconz/test_init.py +++ b/tests/components/deconz/test_init.py @@ -8,12 +8,21 @@ from homeassistant.components.deconz import ( DeconzGateway, async_setup_entry, async_unload_entry, + async_update_group_unique_id, +) +from homeassistant.components.deconz.const import ( + CONF_GROUP_ID_BASE, + DOMAIN as DECONZ_DOMAIN, ) -from homeassistant.components.deconz.const import DOMAIN as DECONZ_DOMAIN from homeassistant.components.deconz.gateway import get_gateway_from_config_entry +from homeassistant.components.light import DOMAIN as LIGHT_DOMAIN +from homeassistant.const import CONF_API_KEY, CONF_HOST, CONF_PORT +from homeassistant.helpers import entity_registry from .test_gateway import DECONZ_WEB_REQUEST, setup_deconz_integration +from tests.common import MockConfigEntry + ENTRY1_HOST = "1.2.3.4" ENTRY1_PORT = 80 ENTRY1_API_KEY = "1234567890ABCDEF" @@ -67,7 +76,7 @@ async def test_setup_entry_multiple_gateways(hass): data = deepcopy(DECONZ_WEB_REQUEST) data["config"]["bridgeid"] = "01234E56789B" config_entry2 = await setup_deconz_integration( - hass, get_state_response=data, entry_id="2" + hass, get_state_response=data, entry_id="2", unique_id="01234E56789B" ) gateway2 = get_gateway_from_config_entry(hass, config_entry2) @@ -92,7 +101,7 @@ async def test_unload_entry_multiple_gateways(hass): data = deepcopy(DECONZ_WEB_REQUEST) data["config"]["bridgeid"] = "01234E56789B" config_entry2 = await setup_deconz_integration( - hass, get_state_response=data, entry_id="2" + hass, get_state_response=data, entry_id="2", unique_id="01234E56789B" ) gateway2 = get_gateway_from_config_entry(hass, config_entry2) @@ -102,3 +111,73 @@ async def test_unload_entry_multiple_gateways(hass): assert len(hass.data[DECONZ_DOMAIN]) == 1 assert hass.data[DECONZ_DOMAIN][gateway2.bridgeid].master + + +async def test_update_group_unique_id(hass): + """Test successful migration of entry data.""" + old_unique_id = "123" + new_unique_id = "1234" + entry = MockConfigEntry( + domain=DECONZ_DOMAIN, + unique_id=new_unique_id, + data={ + CONF_API_KEY: "1", + CONF_HOST: "2", + CONF_GROUP_ID_BASE: old_unique_id, + CONF_PORT: "3", + }, + ) + + registry = await entity_registry.async_get_registry(hass) + # Create entity entry to migrate to new unique ID + registry.async_get_or_create( + LIGHT_DOMAIN, + DECONZ_DOMAIN, + f"{old_unique_id}-OLD", + suggested_object_id="old", + config_entry=entry, + ) + # Create entity entry with new unique ID + registry.async_get_or_create( + LIGHT_DOMAIN, + DECONZ_DOMAIN, + f"{new_unique_id}-NEW", + suggested_object_id="new", + config_entry=entry, + ) + + await async_update_group_unique_id(hass, entry) + + assert entry.data == {CONF_API_KEY: "1", CONF_HOST: "2", CONF_PORT: "3"} + + old_entity = registry.async_get(f"{LIGHT_DOMAIN}.old") + assert old_entity.unique_id == f"{new_unique_id}-OLD" + + new_entity = registry.async_get(f"{LIGHT_DOMAIN}.new") + assert new_entity.unique_id == f"{new_unique_id}-NEW" + + +async def test_update_group_unique_id_no_legacy_group_id(hass): + """Test migration doesn't trigger without old legacy group id in entry data.""" + old_unique_id = "123" + new_unique_id = "1234" + entry = MockConfigEntry( + domain=DECONZ_DOMAIN, + unique_id=new_unique_id, + data={}, + ) + + registry = await entity_registry.async_get_registry(hass) + # Create entity entry to migrate to new unique ID + registry.async_get_or_create( + LIGHT_DOMAIN, + DECONZ_DOMAIN, + f"{old_unique_id}-OLD", + suggested_object_id="old", + config_entry=entry, + ) + + await async_update_group_unique_id(hass, entry) + + old_entity = registry.async_get(f"{LIGHT_DOMAIN}.old") + assert old_entity.unique_id == f"{old_unique_id}-OLD"