mirror of
https://github.com/home-assistant/core.git
synced 2025-04-24 09:17:53 +00:00
Reolink cleanup when CAM disconnected from NVR (#103888)
Co-authored-by: Franck Nijhof <frenck@frenck.nl>
This commit is contained in:
parent
ec647677e9
commit
9fa163c107
@ -16,6 +16,7 @@ from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import EVENT_HOMEASSISTANT_STOP, Platform
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.exceptions import ConfigEntryAuthFailed, ConfigEntryNotReady
|
||||
from homeassistant.helpers import device_registry as dr, entity_registry as er
|
||||
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
|
||||
|
||||
from .const import DOMAIN
|
||||
@ -148,6 +149,8 @@ async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> b
|
||||
firmware_coordinator=firmware_coordinator,
|
||||
)
|
||||
|
||||
cleanup_disconnected_cams(hass, config_entry.entry_id, host)
|
||||
|
||||
await hass.config_entries.async_forward_entry_setups(config_entry, PLATFORMS)
|
||||
|
||||
config_entry.async_on_unload(
|
||||
@ -175,3 +178,56 @@ async def async_unload_entry(hass: HomeAssistant, config_entry: ConfigEntry) ->
|
||||
hass.data[DOMAIN].pop(config_entry.entry_id)
|
||||
|
||||
return unload_ok
|
||||
|
||||
|
||||
def cleanup_disconnected_cams(
|
||||
hass: HomeAssistant, config_entry_id: str, host: ReolinkHost
|
||||
) -> None:
|
||||
"""Clean-up disconnected camera channels or channels where a different model camera is connected."""
|
||||
if not host.api.is_nvr:
|
||||
return
|
||||
|
||||
device_reg = dr.async_get(hass)
|
||||
devices = dr.async_entries_for_config_entry(device_reg, config_entry_id)
|
||||
for device in devices:
|
||||
device_id = [
|
||||
dev_id[1].split("_ch")
|
||||
for dev_id in device.identifiers
|
||||
if dev_id[0] == DOMAIN
|
||||
][0]
|
||||
|
||||
if len(device_id) < 2:
|
||||
# Do not consider the NVR itself
|
||||
continue
|
||||
|
||||
ch = int(device_id[1])
|
||||
ch_model = host.api.camera_model(ch)
|
||||
remove = False
|
||||
if ch not in host.api.channels:
|
||||
remove = True
|
||||
_LOGGER.debug(
|
||||
"Removing Reolink device %s, since no camera is connected to NVR channel %s anymore",
|
||||
device.name,
|
||||
ch,
|
||||
)
|
||||
if ch_model not in [device.model, "Unknown"]:
|
||||
remove = True
|
||||
_LOGGER.debug(
|
||||
"Removing Reolink device %s, since the camera model connected to channel %s changed from %s to %s",
|
||||
device.name,
|
||||
ch,
|
||||
device.model,
|
||||
ch_model,
|
||||
)
|
||||
if not remove:
|
||||
continue
|
||||
|
||||
# clean entity and device registry
|
||||
entity_reg = er.async_get(hass)
|
||||
entities = er.async_entries_for_device(
|
||||
entity_reg, device.id, include_disabled_entities=True
|
||||
)
|
||||
for entity in entities:
|
||||
entity_reg.async_remove(entity.entity_id)
|
||||
|
||||
device_reg.async_remove_device(device.id)
|
||||
|
@ -25,6 +25,8 @@ TEST_PORT = 1234
|
||||
TEST_NVR_NAME = "test_reolink_name"
|
||||
TEST_NVR_NAME2 = "test2_reolink_name"
|
||||
TEST_USE_HTTPS = True
|
||||
TEST_HOST_MODEL = "RLN8-410"
|
||||
TEST_CAM_MODEL = "RLC-123"
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
@ -70,8 +72,8 @@ def reolink_connect_class(
|
||||
host_mock.hardware_version = "IPC_00000"
|
||||
host_mock.sw_version = "v1.0.0.0.0.0000"
|
||||
host_mock.manufacturer = "Reolink"
|
||||
host_mock.model = "RLC-123"
|
||||
host_mock.camera_model.return_value = "RLC-123"
|
||||
host_mock.model = TEST_HOST_MODEL
|
||||
host_mock.camera_model.return_value = TEST_CAM_MODEL
|
||||
host_mock.camera_name.return_value = TEST_NVR_NAME
|
||||
host_mock.camera_sw_version.return_value = "v1.1.0.0.0.0000"
|
||||
host_mock.session_active = True
|
||||
|
@ -41,7 +41,7 @@
|
||||
'event connection': 'Fast polling',
|
||||
'firmware version': 'v1.0.0.0.0.0000',
|
||||
'hardware version': 'IPC_00000',
|
||||
'model': 'RLC-123',
|
||||
'model': 'RLN8-410',
|
||||
'stream channels': list([
|
||||
0,
|
||||
]),
|
||||
|
@ -11,11 +11,15 @@ from homeassistant.config import async_process_ha_core_config
|
||||
from homeassistant.config_entries import ConfigEntryState
|
||||
from homeassistant.const import STATE_OFF, STATE_UNAVAILABLE, Platform
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers import issue_registry as ir
|
||||
from homeassistant.helpers import (
|
||||
device_registry as dr,
|
||||
entity_registry as er,
|
||||
issue_registry as ir,
|
||||
)
|
||||
from homeassistant.setup import async_setup_component
|
||||
from homeassistant.util.dt import utcnow
|
||||
|
||||
from .conftest import TEST_NVR_NAME
|
||||
from .conftest import TEST_CAM_MODEL, TEST_HOST_MODEL, TEST_NVR_NAME
|
||||
|
||||
from tests.common import MockConfigEntry, async_fire_time_changed
|
||||
|
||||
@ -102,6 +106,7 @@ async def test_entry_reloading(
|
||||
reolink_connect: MagicMock,
|
||||
) -> None:
|
||||
"""Test the entry is reloaded correctly when settings change."""
|
||||
reolink_connect.is_nvr = False
|
||||
assert await hass.config_entries.async_setup(config_entry.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
@ -115,6 +120,58 @@ async def test_entry_reloading(
|
||||
assert config_entry.title == "New Name"
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("attr", "value", "expected_models"),
|
||||
[
|
||||
(
|
||||
None,
|
||||
None,
|
||||
[TEST_HOST_MODEL, TEST_CAM_MODEL],
|
||||
),
|
||||
("channels", [], [TEST_HOST_MODEL]),
|
||||
(
|
||||
"camera_model",
|
||||
Mock(return_value="RLC-567"),
|
||||
[TEST_HOST_MODEL, "RLC-567"],
|
||||
),
|
||||
],
|
||||
)
|
||||
async def test_cleanup_disconnected_cams(
|
||||
hass: HomeAssistant,
|
||||
config_entry: MockConfigEntry,
|
||||
reolink_connect: MagicMock,
|
||||
device_registry: dr.DeviceRegistry,
|
||||
entity_registry: er.EntityRegistry,
|
||||
attr: str | None,
|
||||
value: Any,
|
||||
expected_models: list[str],
|
||||
) -> None:
|
||||
"""Test device and entity registry are cleaned up when camera is disconnected from NVR."""
|
||||
reolink_connect.channels = [0]
|
||||
# setup CH 0 and NVR switch 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()
|
||||
|
||||
device_entries = dr.async_entries_for_config_entry(
|
||||
device_registry, config_entry.entry_id
|
||||
)
|
||||
device_models = [device.model for device in device_entries]
|
||||
assert sorted(device_models) == sorted([TEST_HOST_MODEL, TEST_CAM_MODEL])
|
||||
|
||||
# reload integration after 'disconnecting' a camera.
|
||||
if attr is not None:
|
||||
setattr(reolink_connect, attr, value)
|
||||
with patch("homeassistant.components.reolink.PLATFORMS", [Platform.SWITCH]):
|
||||
assert await hass.config_entries.async_reload(config_entry.entry_id)
|
||||
|
||||
device_entries = dr.async_entries_for_config_entry(
|
||||
device_registry, config_entry.entry_id
|
||||
)
|
||||
device_models = [device.model for device in device_entries]
|
||||
assert sorted(device_models) == sorted(expected_models)
|
||||
|
||||
|
||||
async def test_no_repair_issue(
|
||||
hass: HomeAssistant, config_entry: MockConfigEntry
|
||||
) -> None:
|
||||
|
Loading…
x
Reference in New Issue
Block a user