From 40fd7cf85220a33f1fcc6b8f4085e65ff2ed4c79 Mon Sep 17 00:00:00 2001 From: starkillerOG Date: Mon, 14 Apr 2025 20:12:34 +0200 Subject: [PATCH] Select correct Reolink device uid (#142864) * Select correct device_uid * Fix styling * restructure * Add test * Update test_util.py * Add explanation string --- homeassistant/components/reolink/__init__.py | 16 ++++++++ homeassistant/components/reolink/util.py | 12 ++++-- tests/components/reolink/conftest.py | 1 + tests/components/reolink/test_util.py | 41 +++++++++++++++++++- 4 files changed, 65 insertions(+), 5 deletions(-) diff --git a/homeassistant/components/reolink/__init__.py b/homeassistant/components/reolink/__init__.py index c326f1120c9..f7d13c1d90f 100644 --- a/homeassistant/components/reolink/__init__.py +++ b/homeassistant/components/reolink/__init__.py @@ -371,6 +371,9 @@ def migrate_entity_ids( new_device_id = f"{host.unique_id}" else: new_device_id = f"{host.unique_id}_{device_uid[1]}" + _LOGGER.debug( + "Updating Reolink device UID from %s to %s", device_uid, new_device_id + ) new_identifiers = {(DOMAIN, new_device_id)} device_reg.async_update_device(device.id, new_identifiers=new_identifiers) @@ -383,6 +386,9 @@ def migrate_entity_ids( new_device_id = f"{host.unique_id}_{host.api.camera_uid(ch)}" else: new_device_id = f"{device_uid[0]}_{host.api.camera_uid(ch)}" + _LOGGER.debug( + "Updating Reolink device UID from %s to %s", device_uid, new_device_id + ) new_identifiers = {(DOMAIN, new_device_id)} existing_device = device_reg.async_get_device(identifiers=new_identifiers) if existing_device is None: @@ -415,6 +421,11 @@ def migrate_entity_ids( host.unique_id ): new_id = f"{host.unique_id}_{entity.unique_id.split('_', 1)[1]}" + _LOGGER.debug( + "Updating Reolink entity unique_id from %s to %s", + entity.unique_id, + new_id, + ) entity_reg.async_update_entity(entity.entity_id, new_unique_id=new_id) if entity.device_id in ch_device_ids: @@ -430,6 +441,11 @@ def migrate_entity_ids( continue if host.api.supported(ch, "UID") and id_parts[1] != host.api.camera_uid(ch): new_id = f"{host.unique_id}_{host.api.camera_uid(ch)}_{id_parts[2]}" + _LOGGER.debug( + "Updating Reolink entity unique_id from %s to %s", + entity.unique_id, + new_id, + ) existing_entity = entity_reg.async_get_entity_id( entity.domain, entity.platform, new_id ) diff --git a/homeassistant/components/reolink/util.py b/homeassistant/components/reolink/util.py index 12b4825caeb..17e666ac52c 100644 --- a/homeassistant/components/reolink/util.py +++ b/homeassistant/components/reolink/util.py @@ -79,11 +79,15 @@ def get_device_uid_and_ch( device: dr.DeviceEntry, host: ReolinkHost ) -> tuple[list[str], int | None, bool]: """Get the channel and the split device_uid from a reolink DeviceEntry.""" - device_uid = [ - dev_id[1].split("_") for dev_id in device.identifiers if dev_id[0] == DOMAIN - ][0] - + device_uid = [] is_chime = False + + for dev_id in device.identifiers: + if dev_id[0] == DOMAIN: + device_uid = dev_id[1].split("_") + if device_uid[0] == host.unique_id: + break + if len(device_uid) < 2: # NVR itself ch = None diff --git a/tests/components/reolink/conftest.py b/tests/components/reolink/conftest.py index 21acced3d1d..3bd1539fc36 100644 --- a/tests/components/reolink/conftest.py +++ b/tests/components/reolink/conftest.py @@ -77,6 +77,7 @@ def reolink_connect_class() -> Generator[MagicMock]: host_mock.check_new_firmware.return_value = False host_mock.unsubscribe.return_value = True host_mock.logout.return_value = True + host_mock.is_nvr = True host_mock.is_hub = False host_mock.mac_address = TEST_MAC host_mock.uid = TEST_UID diff --git a/tests/components/reolink/test_util.py b/tests/components/reolink/test_util.py index ef66d471801..181249b8bff 100644 --- a/tests/components/reolink/test_util.py +++ b/tests/components/reolink/test_util.py @@ -23,15 +23,21 @@ from homeassistant.components.number import ( DOMAIN as NUMBER_DOMAIN, SERVICE_SET_VALUE, ) +from homeassistant.components.reolink.const import DOMAIN +from homeassistant.components.reolink.util import get_device_uid_and_ch from homeassistant.config_entries import ConfigEntryState from homeassistant.const import ATTR_ENTITY_ID, Platform from homeassistant.core import HomeAssistant from homeassistant.exceptions import HomeAssistantError, ServiceValidationError +from homeassistant.helpers import device_registry as dr -from .conftest import TEST_NVR_NAME +from .conftest import TEST_NVR_NAME, TEST_UID, TEST_UID_CAM from tests.common import MockConfigEntry +DEV_ID_NVR = f"{TEST_UID}_{TEST_UID_CAM}" +DEV_ID_STANDALONE_CAM = f"{TEST_UID_CAM}" + @pytest.mark.parametrize( ("side_effect", "expected"), @@ -123,3 +129,36 @@ async def test_try_function( assert err.value.translation_key == expected.translation_key reolink_connect.set_volume.reset_mock(side_effect=True) + + +@pytest.mark.parametrize( + ("identifiers"), + [ + ({(DOMAIN, DEV_ID_NVR), (DOMAIN, DEV_ID_STANDALONE_CAM)}), + ({(DOMAIN, DEV_ID_STANDALONE_CAM), (DOMAIN, DEV_ID_NVR)}), + ], +) +async def test_get_device_uid_and_ch( + hass: HomeAssistant, + config_entry: MockConfigEntry, + reolink_connect: MagicMock, + device_registry: dr.DeviceRegistry, + identifiers: set[tuple[str, str]], +) -> None: + """Test get_device_uid_and_ch with multiple identifiers.""" + reolink_connect.channels = [0] + + dev_entry = device_registry.async_get_or_create( + identifiers=identifiers, + config_entry_id=config_entry.entry_id, + disabled_by=None, + ) + + # setup CH 0 and host entities/device + with patch("homeassistant.components.reolink.PLATFORMS", [Platform.SWITCH]): + assert await hass.config_entries.async_setup(config_entry.entry_id) + await hass.async_block_till_done() + + result = get_device_uid_and_ch(dev_entry, config_entry.runtime_data.host) + # always get the uid and channel form the DEV_ID_NVR since is_nvr = True + assert result == ([TEST_UID, TEST_UID_CAM], 0, False)