Cleanup wrongly combined Reolink devices (#144771)

This commit is contained in:
starkillerOG 2025-05-16 12:58:28 +02:00 committed by GitHub
parent ff4aed1f6e
commit 07db244f91
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 147 additions and 45 deletions

View File

@ -364,53 +364,90 @@ def migrate_entity_ids(
devices = dr.async_entries_for_config_entry(device_reg, config_entry_id)
ch_device_ids = {}
for device in devices:
(device_uid, ch, is_chime) = get_device_uid_and_ch(device, host)
for dev_id in device.identifiers:
(device_uid, ch, is_chime) = get_device_uid_and_ch(dev_id, host)
if not device_uid:
continue
if host.api.supported(None, "UID") and device_uid[0] != host.unique_id:
if ch is None:
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)
if ch is None or is_chime:
continue # Do not consider the NVR itself or chimes
# Check for wrongfully added MAC of the NVR/Hub to the camera
# Can be removed in HA 2025.12
host_connnection = (CONNECTION_NETWORK_MAC, host.api.mac_address)
if host_connnection in device.connections:
new_connections = device.connections.copy()
new_connections.remove(host_connnection)
device_reg.async_update_device(device.id, new_connections=new_connections)
ch_device_ids[device.id] = ch
if host.api.supported(ch, "UID") and device_uid[1] != host.api.camera_uid(ch):
if host.api.supported(None, "UID"):
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:
if host.api.supported(None, "UID") and device_uid[0] != host.unique_id:
if ch is None:
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
)
else:
_LOGGER.warning(
"Reolink device with uid %s already exists, "
"removing device with uid %s",
new_device_id,
device_uid,
if ch is None or is_chime:
continue # Do not consider the NVR itself or chimes
# Check for wrongfully combined host with NVR entities in one device
# Can be removed in HA 2025.12
if (DOMAIN, host.unique_id) in device.identifiers:
new_identifiers = device.identifiers.copy()
for old_id in device.identifiers:
if old_id[0] == DOMAIN and old_id[1] != host.unique_id:
new_identifiers.remove(old_id)
_LOGGER.debug(
"Updating Reolink device identifiers from %s to %s",
device.identifiers,
new_identifiers,
)
device_reg.async_remove_device(device.id)
device_reg.async_update_device(
device.id, new_identifiers=new_identifiers
)
break
# Check for wrongfully added MAC of the NVR/Hub to the camera
# Can be removed in HA 2025.12
host_connnection = (CONNECTION_NETWORK_MAC, host.api.mac_address)
if host_connnection in device.connections:
new_connections = device.connections.copy()
new_connections.remove(host_connnection)
_LOGGER.debug(
"Updating Reolink device connections from %s to %s",
device.connections,
new_connections,
)
device_reg.async_update_device(
device.id, new_connections=new_connections
)
ch_device_ids[device.id] = ch
if host.api.supported(ch, "UID") and device_uid[1] != host.api.camera_uid(
ch
):
if host.api.supported(None, "UID"):
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:
device_reg.async_update_device(
device.id, new_identifiers=new_identifiers
)
else:
_LOGGER.warning(
"Reolink device with uid %s already exists, "
"removing device with uid %s",
new_device_id,
device_uid,
)
device_reg.async_remove_device(device.id)
entity_reg = er.async_get(hass)
entities = er.async_entries_for_config_entry(entity_reg, config_entry_id)

View File

@ -76,13 +76,18 @@ def get_store(hass: HomeAssistant, config_entry_id: str) -> Store[str]:
def get_device_uid_and_ch(
device: dr.DeviceEntry, host: ReolinkHost
device: dr.DeviceEntry | tuple[str, str], host: ReolinkHost
) -> tuple[list[str], int | None, bool]:
"""Get the channel and the split device_uid from a reolink DeviceEntry."""
device_uid = []
is_chime = False
for dev_id in device.identifiers:
if isinstance(device, dr.DeviceEntry):
dev_ids = device.identifiers
else:
dev_ids = {device}
for dev_id in dev_ids:
if dev_id[0] == DOMAIN:
device_uid = dev_id[1].split("_")
if device_uid[0] == host.unique_id:

View File

@ -630,7 +630,7 @@ async def test_cleanup_mac_connection(
domain = Platform.SWITCH
dev_entry = device_registry.async_get_or_create(
identifiers={(DOMAIN, dev_id)},
identifiers={(DOMAIN, dev_id), ("OTHER_INTEGRATION", "SOME_ID")},
connections={(CONNECTION_NETWORK_MAC, TEST_MAC)},
config_entry_id=config_entry.entry_id,
disabled_by=None,
@ -664,6 +664,66 @@ async def test_cleanup_mac_connection(
reolink_connect.baichuan.mac_address.return_value = TEST_MAC_CAM
async def test_cleanup_combined_with_NVR(
hass: HomeAssistant,
config_entry: MockConfigEntry,
reolink_connect: MagicMock,
entity_registry: er.EntityRegistry,
device_registry: dr.DeviceRegistry,
) -> None:
"""Test cleanup of the device registry if IPC camera device was combined with the NVR device."""
reolink_connect.channels = [0]
reolink_connect.baichuan.mac_address.return_value = None
entity_id = f"{TEST_UID}_{TEST_UID_CAM}_record_audio"
dev_id = f"{TEST_UID}_{TEST_UID_CAM}"
domain = Platform.SWITCH
start_identifiers = {
(DOMAIN, dev_id),
(DOMAIN, TEST_UID),
("OTHER_INTEGRATION", "SOME_ID"),
}
dev_entry = device_registry.async_get_or_create(
identifiers=start_identifiers,
connections={(CONNECTION_NETWORK_MAC, TEST_MAC)},
config_entry_id=config_entry.entry_id,
disabled_by=None,
)
entity_registry.async_get_or_create(
domain=domain,
platform=DOMAIN,
unique_id=entity_id,
config_entry=config_entry,
suggested_object_id=entity_id,
disabled_by=None,
device_id=dev_entry.id,
)
assert entity_registry.async_get_entity_id(domain, DOMAIN, entity_id)
device = device_registry.async_get_device(identifiers={(DOMAIN, dev_id)})
assert device
assert device.identifiers == start_identifiers
# setup CH 0 and host entities/device
with patch("homeassistant.components.reolink.PLATFORMS", [domain]):
assert await hass.config_entries.async_setup(config_entry.entry_id)
await hass.async_block_till_done()
assert entity_registry.async_get_entity_id(domain, DOMAIN, entity_id)
device = device_registry.async_get_device(identifiers={(DOMAIN, dev_id)})
assert device
assert device.identifiers == {(DOMAIN, dev_id)}
host_device = device_registry.async_get_device(identifiers={(DOMAIN, TEST_UID)})
assert host_device
assert host_device.identifiers == {
(DOMAIN, TEST_UID),
("OTHER_INTEGRATION", "SOME_ID"),
}
reolink_connect.baichuan.mac_address.return_value = TEST_MAC_CAM
async def test_no_repair_issue(
hass: HomeAssistant, config_entry: MockConfigEntry, issue_registry: ir.IssueRegistry
) -> None: