mirror of
https://github.com/home-assistant/core.git
synced 2026-08-24 10:13:52 -05:00
Fix handling of tracked devices on cleanup in FRITZ!Box Tools (#170574)
Co-authored-by: Martin Hjelmare <marhje52@gmail.com>
This commit is contained in:
co-authored by
Martin Hjelmare
parent
1076d65c9c
commit
34254c138f
@@ -54,6 +54,8 @@ async def async_setup_entry(hass: HomeAssistant, entry: FritzConfigEntry) -> boo
|
||||
),
|
||||
)
|
||||
|
||||
hass.data.setdefault(FRITZ_DATA_KEY, FritzData())
|
||||
|
||||
try:
|
||||
await avm_wrapper.async_setup(entry.options)
|
||||
except FRITZ_AUTH_EXCEPTIONS as ex:
|
||||
@@ -68,13 +70,9 @@ async def async_setup_entry(hass: HomeAssistant, entry: FritzConfigEntry) -> boo
|
||||
raise ConfigEntryAuthFailed("Missing UPnP configuration")
|
||||
|
||||
await avm_wrapper.async_config_entry_first_refresh()
|
||||
await avm_wrapper.async_trigger_cleanup()
|
||||
|
||||
entry.runtime_data = avm_wrapper
|
||||
|
||||
if FRITZ_DATA_KEY not in hass.data:
|
||||
hass.data[FRITZ_DATA_KEY] = FritzData()
|
||||
|
||||
# Load the other platforms like switch
|
||||
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
|
||||
|
||||
@@ -89,6 +87,8 @@ async def async_unload_entry(hass: HomeAssistant, entry: FritzConfigEntry) -> bo
|
||||
|
||||
if avm_wrapper.unique_id in fritz_data.tracked:
|
||||
fritz_data.tracked.pop(avm_wrapper.unique_id)
|
||||
fritz_data.profile_switches.pop(avm_wrapper.unique_id)
|
||||
fritz_data.wol_buttons.pop(avm_wrapper.unique_id)
|
||||
|
||||
if not bool(fritz_data.tracked):
|
||||
hass.data.pop(FRITZ_DATA_KEY)
|
||||
|
||||
@@ -217,9 +217,6 @@ def _async_wol_buttons_list(
|
||||
|
||||
new_wols: list[FritzBoxWOLButton] = []
|
||||
|
||||
if avm_wrapper.unique_id not in data_fritz.wol_buttons:
|
||||
data_fritz.wol_buttons[avm_wrapper.unique_id] = set()
|
||||
|
||||
for mac, device in avm_wrapper.devices.items():
|
||||
if _is_tracked(mac, data_fritz.wol_buttons.values()):
|
||||
_LOGGER.debug("Skipping wol button creation for device %s", device.hostname)
|
||||
|
||||
@@ -187,6 +187,10 @@ class FritzBoxTools(DataUpdateCoordinator[UpdateCoordinatorDataType]):
|
||||
self._options = options
|
||||
await self.hass.async_add_executor_job(self.setup)
|
||||
|
||||
self.hass.data[FRITZ_DATA_KEY].tracked[self.unique_id] = set()
|
||||
self.hass.data[FRITZ_DATA_KEY].profile_switches[self.unique_id] = set()
|
||||
self.hass.data[FRITZ_DATA_KEY].wol_buttons[self.unique_id] = set()
|
||||
|
||||
device_registry = dr.async_get(self.hass)
|
||||
device_registry.async_get_or_create(
|
||||
config_entry_id=self.config_entry.entry_id,
|
||||
@@ -715,6 +719,7 @@ class FritzBoxTools(DataUpdateCoordinator[UpdateCoordinatorDataType]):
|
||||
) and entry_mac not in device_hosts:
|
||||
_LOGGER.debug("Removing orphan entity entry %s", entity.entity_id)
|
||||
entity_reg.async_remove(entity.entity_id)
|
||||
self._devices.pop(entry_mac, None)
|
||||
|
||||
device_reg = dr.async_get(self.hass)
|
||||
valid_connections = {
|
||||
@@ -729,6 +734,29 @@ class FritzBoxTools(DataUpdateCoordinator[UpdateCoordinatorDataType]):
|
||||
device.id, remove_config_entry_id=config_entry.entry_id
|
||||
)
|
||||
|
||||
fritz_data = self.hass.data[FRITZ_DATA_KEY]
|
||||
|
||||
tracked = fritz_data.tracked.get(self.unique_id, set())
|
||||
for mac in tracked.copy():
|
||||
if mac in device_hosts:
|
||||
continue
|
||||
_LOGGER.debug("Removing orphan mac address %s from device trackers", mac)
|
||||
tracked.remove(mac)
|
||||
|
||||
profile_switches = fritz_data.profile_switches.get(self.unique_id, set())
|
||||
for mac in profile_switches.copy():
|
||||
if mac in device_hosts:
|
||||
continue
|
||||
_LOGGER.debug("Removing orphan mac address %s from profile switches", mac)
|
||||
profile_switches.remove(mac)
|
||||
|
||||
wol_buttons = fritz_data.wol_buttons.get(self.unique_id, set())
|
||||
for mac in wol_buttons.copy():
|
||||
if mac in device_hosts:
|
||||
continue
|
||||
_LOGGER.debug("Removing orphan mac address %s from WOL buttons", mac)
|
||||
wol_buttons.remove(mac)
|
||||
|
||||
|
||||
class AvmWrapper(FritzBoxTools):
|
||||
"""Setup AVM wrapper for API calls."""
|
||||
|
||||
@@ -51,9 +51,6 @@ def _async_add_entities(
|
||||
"""Add new tracker entities from the AVM device."""
|
||||
|
||||
new_tracked = []
|
||||
if avm_wrapper.unique_id not in data_fritz.tracked:
|
||||
data_fritz.tracked[avm_wrapper.unique_id] = set()
|
||||
|
||||
for mac, device in avm_wrapper.devices.items():
|
||||
if device_filter_out_from_trackers(mac, device, data_fritz.tracked.values()):
|
||||
continue
|
||||
|
||||
@@ -242,9 +242,6 @@ async def _async_profile_entities_list(
|
||||
if "X_AVM-DE_HostFilter1" not in avm_wrapper.connection.services:
|
||||
return new_profiles
|
||||
|
||||
if avm_wrapper.unique_id not in data_fritz.profile_switches:
|
||||
data_fritz.profile_switches[avm_wrapper.unique_id] = set()
|
||||
|
||||
for mac, device in avm_wrapper.devices.items():
|
||||
if device_filter_out_from_trackers(
|
||||
mac, device, data_fritz.profile_switches.values()
|
||||
|
||||
@@ -907,84 +907,88 @@ MOCK_NEW_DEVICE_NODE = {
|
||||
],
|
||||
}
|
||||
|
||||
MOCK_HOST_PRINTER = {
|
||||
"Index": 1,
|
||||
"IPAddress": MOCK_IPS["printer"],
|
||||
"MACAddress": "AA:BB:CC:00:11:22",
|
||||
"Active": True,
|
||||
"HostName": "printer",
|
||||
"InterfaceType": "Ethernet",
|
||||
"X_AVM-DE_Port": 1,
|
||||
"X_AVM-DE_Speed": 1000,
|
||||
"X_AVM-DE_UpdateAvailable": False,
|
||||
"X_AVM-DE_UpdateSuccessful": "unknown",
|
||||
"X_AVM-DE_InfoURL": None,
|
||||
"X_AVM-DE_MACAddressList": None,
|
||||
"X_AVM-DE_Model": None,
|
||||
"X_AVM-DE_URL": f"http://{MOCK_IPS['printer']}",
|
||||
"X_AVM-DE_Guest": False,
|
||||
"X_AVM-DE_RequestClient": "0",
|
||||
"X_AVM-DE_VPN": False,
|
||||
"X_AVM-DE_WANAccess": "granted",
|
||||
"X_AVM-DE_Disallow": False,
|
||||
"X_AVM-DE_IsMeshable": "0",
|
||||
"X_AVM-DE_Priority": "0",
|
||||
"X_AVM-DE_FriendlyName": "printer",
|
||||
"X_AVM-DE_FriendlyNameIsWriteable": "1",
|
||||
}
|
||||
|
||||
MOCK_HOST_FRITZBOX = {
|
||||
"Index": 2,
|
||||
"IPAddress": MOCK_IPS["fritz.box"],
|
||||
"MACAddress": MOCK_MESH_MASTER_MAC,
|
||||
"Active": True,
|
||||
"HostName": "fritz.box",
|
||||
"InterfaceType": None,
|
||||
"X_AVM-DE_Port": 0,
|
||||
"X_AVM-DE_Speed": 0,
|
||||
"X_AVM-DE_UpdateAvailable": False,
|
||||
"X_AVM-DE_UpdateSuccessful": "unknown",
|
||||
"X_AVM-DE_InfoURL": None,
|
||||
"X_AVM-DE_MACAddressList": f"{MOCK_MESH_MASTER_MAC},{MOCK_MESH_MASTER_WIFI1_MAC}",
|
||||
"X_AVM-DE_Model": None,
|
||||
"X_AVM-DE_URL": f"http://{MOCK_IPS['fritz.box']}",
|
||||
"X_AVM-DE_Guest": False,
|
||||
"X_AVM-DE_RequestClient": "0",
|
||||
"X_AVM-DE_VPN": False,
|
||||
"X_AVM-DE_WANAccess": "granted",
|
||||
"X_AVM-DE_Disallow": False,
|
||||
"X_AVM-DE_IsMeshable": "1",
|
||||
"X_AVM-DE_Priority": "0",
|
||||
"X_AVM-DE_FriendlyName": "fritz.box",
|
||||
"X_AVM-DE_FriendlyNameIsWriteable": "0",
|
||||
}
|
||||
|
||||
MOCK_HOST_SERVER = {
|
||||
"Index": 3,
|
||||
"IPAddress": MOCK_IPS["server"],
|
||||
"MACAddress": "AA:BB:CC:33:44:55",
|
||||
"Active": True,
|
||||
"HostName": "server",
|
||||
"InterfaceType": "Ethernet",
|
||||
"X_AVM-DE_Port": 1,
|
||||
"X_AVM-DE_Speed": 1000,
|
||||
"X_AVM-DE_UpdateAvailable": False,
|
||||
"X_AVM-DE_UpdateSuccessful": "unknown",
|
||||
"X_AVM-DE_InfoURL": None,
|
||||
"X_AVM-DE_MACAddressList": None,
|
||||
"X_AVM-DE_Model": None,
|
||||
"X_AVM-DE_URL": f"http://{MOCK_IPS['server']}",
|
||||
"X_AVM-DE_Guest": False,
|
||||
"X_AVM-DE_RequestClient": "0",
|
||||
"X_AVM-DE_VPN": False,
|
||||
"X_AVM-DE_WANAccess": "granted",
|
||||
"X_AVM-DE_Disallow": False,
|
||||
"X_AVM-DE_IsMeshable": "0",
|
||||
"X_AVM-DE_Priority": "0",
|
||||
"X_AVM-DE_FriendlyName": "server",
|
||||
"X_AVM-DE_FriendlyNameIsWriteable": "1",
|
||||
}
|
||||
|
||||
MOCK_HOST_ATTRIBUTES_DATA = [
|
||||
{
|
||||
"Index": 1,
|
||||
"IPAddress": MOCK_IPS["printer"],
|
||||
"MACAddress": "AA:BB:CC:00:11:22",
|
||||
"Active": True,
|
||||
"HostName": "printer",
|
||||
"InterfaceType": "Ethernet",
|
||||
"X_AVM-DE_Port": 1,
|
||||
"X_AVM-DE_Speed": 1000,
|
||||
"X_AVM-DE_UpdateAvailable": False,
|
||||
"X_AVM-DE_UpdateSuccessful": "unknown",
|
||||
"X_AVM-DE_InfoURL": None,
|
||||
"X_AVM-DE_MACAddressList": None,
|
||||
"X_AVM-DE_Model": None,
|
||||
"X_AVM-DE_URL": f"http://{MOCK_IPS['printer']}",
|
||||
"X_AVM-DE_Guest": False,
|
||||
"X_AVM-DE_RequestClient": "0",
|
||||
"X_AVM-DE_VPN": False,
|
||||
"X_AVM-DE_WANAccess": "granted",
|
||||
"X_AVM-DE_Disallow": False,
|
||||
"X_AVM-DE_IsMeshable": "0",
|
||||
"X_AVM-DE_Priority": "0",
|
||||
"X_AVM-DE_FriendlyName": "printer",
|
||||
"X_AVM-DE_FriendlyNameIsWriteable": "1",
|
||||
},
|
||||
{
|
||||
"Index": 2,
|
||||
"IPAddress": MOCK_IPS["fritz.box"],
|
||||
"MACAddress": MOCK_MESH_MASTER_MAC,
|
||||
"Active": True,
|
||||
"HostName": "fritz.box",
|
||||
"InterfaceType": None,
|
||||
"X_AVM-DE_Port": 0,
|
||||
"X_AVM-DE_Speed": 0,
|
||||
"X_AVM-DE_UpdateAvailable": False,
|
||||
"X_AVM-DE_UpdateSuccessful": "unknown",
|
||||
"X_AVM-DE_InfoURL": None,
|
||||
"X_AVM-DE_MACAddressList": (
|
||||
f"{MOCK_MESH_MASTER_MAC},{MOCK_MESH_MASTER_WIFI1_MAC}"
|
||||
),
|
||||
"X_AVM-DE_Model": None,
|
||||
"X_AVM-DE_URL": f"http://{MOCK_IPS['fritz.box']}",
|
||||
"X_AVM-DE_Guest": False,
|
||||
"X_AVM-DE_RequestClient": "0",
|
||||
"X_AVM-DE_VPN": False,
|
||||
"X_AVM-DE_WANAccess": "granted",
|
||||
"X_AVM-DE_Disallow": False,
|
||||
"X_AVM-DE_IsMeshable": "1",
|
||||
"X_AVM-DE_Priority": "0",
|
||||
"X_AVM-DE_FriendlyName": "fritz.box",
|
||||
"X_AVM-DE_FriendlyNameIsWriteable": "0",
|
||||
},
|
||||
{
|
||||
"Index": 3,
|
||||
"IPAddress": MOCK_IPS["server"],
|
||||
"MACAddress": "AA:BB:CC:33:44:55",
|
||||
"Active": True,
|
||||
"HostName": "server",
|
||||
"InterfaceType": "Ethernet",
|
||||
"X_AVM-DE_Port": 1,
|
||||
"X_AVM-DE_Speed": 1000,
|
||||
"X_AVM-DE_UpdateAvailable": False,
|
||||
"X_AVM-DE_UpdateSuccessful": "unknown",
|
||||
"X_AVM-DE_InfoURL": None,
|
||||
"X_AVM-DE_MACAddressList": None,
|
||||
"X_AVM-DE_Model": None,
|
||||
"X_AVM-DE_URL": f"http://{MOCK_IPS['server']}",
|
||||
"X_AVM-DE_Guest": False,
|
||||
"X_AVM-DE_RequestClient": "0",
|
||||
"X_AVM-DE_VPN": False,
|
||||
"X_AVM-DE_WANAccess": "granted",
|
||||
"X_AVM-DE_Disallow": False,
|
||||
"X_AVM-DE_IsMeshable": "0",
|
||||
"X_AVM-DE_Priority": "0",
|
||||
"X_AVM-DE_FriendlyName": "server",
|
||||
"X_AVM-DE_FriendlyNameIsWriteable": "1",
|
||||
},
|
||||
MOCK_HOST_PRINTER,
|
||||
MOCK_HOST_FRITZBOX,
|
||||
MOCK_HOST_SERVER,
|
||||
]
|
||||
|
||||
MOCK_CALL_DEFLECTION_DATA = {
|
||||
|
||||
@@ -5,6 +5,7 @@ from copy import deepcopy
|
||||
from typing import cast
|
||||
from unittest.mock import AsyncMock, MagicMock, PropertyMock, patch
|
||||
|
||||
from freezegun.api import FrozenDateTimeFactory
|
||||
from fritzconnection.core.exceptions import (
|
||||
FritzActionError,
|
||||
FritzConnectionException,
|
||||
@@ -18,12 +19,15 @@ from homeassistant.components.fritz.const import (
|
||||
DEFAULT_CONF_FEATURE_DEVICE_TRACKING,
|
||||
DEFAULT_SSL,
|
||||
DOMAIN,
|
||||
SCAN_INTERVAL,
|
||||
)
|
||||
from homeassistant.components.fritz.coordinator import (
|
||||
FRITZ_DATA_KEY,
|
||||
AvmWrapper,
|
||||
ClassSetupMissing,
|
||||
FritzBoxTools,
|
||||
FritzConnectionCached,
|
||||
FritzData,
|
||||
)
|
||||
from homeassistant.config_entries import ConfigEntryState
|
||||
from homeassistant.const import (
|
||||
@@ -35,12 +39,18 @@ from homeassistant.const import (
|
||||
)
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.exceptions import HomeAssistantError
|
||||
from homeassistant.helpers import device_registry as dr
|
||||
from homeassistant.helpers import device_registry as dr, entity_registry as er
|
||||
|
||||
from .conftest import FritzConnectionMock, FritzServiceMock
|
||||
from .const import MOCK_SERIAL_NUMBER, MOCK_STATUS_DEVICE_INFO_DATA, MOCK_USER_DATA
|
||||
from .const import (
|
||||
MOCK_HOST_FRITZBOX,
|
||||
MOCK_HOST_PRINTER,
|
||||
MOCK_SERIAL_NUMBER,
|
||||
MOCK_STATUS_DEVICE_INFO_DATA,
|
||||
MOCK_USER_DATA,
|
||||
)
|
||||
|
||||
from tests.common import MockConfigEntry
|
||||
from tests.common import MockConfigEntry, async_fire_time_changed
|
||||
|
||||
|
||||
@pytest.fixture(name="mock_config_entry")
|
||||
@@ -82,6 +92,8 @@ async def fixture_fritz_tools(
|
||||
port=mock_config_entry.data["port"],
|
||||
)
|
||||
|
||||
hass.data.setdefault(FRITZ_DATA_KEY, FritzData())
|
||||
|
||||
await coordinator.async_setup()
|
||||
return coordinator
|
||||
|
||||
@@ -567,6 +579,70 @@ async def test_avmwrapper_passthrough_methods(
|
||||
assert await wrapper.async_wake_on_lan("AA:BB:CC:DD:EE:FF") == {}
|
||||
|
||||
|
||||
async def test_async_trigger_cleanup(
|
||||
hass: HomeAssistant,
|
||||
device_registry: dr.DeviceRegistry,
|
||||
entity_registry: er.EntityRegistry,
|
||||
freezer: FrozenDateTimeFactory,
|
||||
fc_class_mock,
|
||||
fh_class_mock,
|
||||
fs_class_mock,
|
||||
) -> None:
|
||||
"""Test the cleanup of orphan devices."""
|
||||
|
||||
fh_class_mock.get_hosts_attributes.return_value = [
|
||||
MOCK_HOST_PRINTER,
|
||||
MOCK_HOST_FRITZBOX,
|
||||
]
|
||||
|
||||
entry = MockConfigEntry(domain=DOMAIN, data=MOCK_USER_DATA)
|
||||
entry.add_to_hass(hass)
|
||||
assert await hass.config_entries.async_setup(entry.entry_id)
|
||||
await hass.async_block_till_done(wait_background_tasks=True)
|
||||
assert entry.state is ConfigEntryState.LOADED
|
||||
|
||||
# Verify the printer is registered as tracked device
|
||||
assert device_registry.async_get_device(
|
||||
connections={(dr.CONNECTION_NETWORK_MAC, "aa:bb:cc:00:11:22")}
|
||||
)
|
||||
assert entity_registry.async_get("device_tracker.printer")
|
||||
assert entity_registry.async_get("switch.printer_internet_access")
|
||||
|
||||
# remove printer from host list
|
||||
fh_class_mock.get_hosts_attributes.return_value = [MOCK_HOST_FRITZBOX]
|
||||
|
||||
freezer.tick(SCAN_INTERVAL)
|
||||
async_fire_time_changed(hass)
|
||||
await hass.async_block_till_done(wait_background_tasks=True)
|
||||
|
||||
# Verify the printer was removed from tracked devices
|
||||
assert (
|
||||
device_registry.async_get_device(
|
||||
connections={(dr.CONNECTION_NETWORK_MAC, "aa:bb:cc:00:11:22")}
|
||||
)
|
||||
is None
|
||||
)
|
||||
assert entity_registry.async_get("device_tracker.printer") is None
|
||||
assert entity_registry.async_get("switch.printer_internet_access") is None
|
||||
|
||||
# add printer again
|
||||
fh_class_mock.get_hosts_attributes.return_value = [
|
||||
MOCK_HOST_PRINTER,
|
||||
MOCK_HOST_FRITZBOX,
|
||||
]
|
||||
|
||||
freezer.tick(SCAN_INTERVAL)
|
||||
async_fire_time_changed(hass)
|
||||
await hass.async_block_till_done(wait_background_tasks=True)
|
||||
|
||||
# Verify the printer is registered again as tracked device
|
||||
assert device_registry.async_get_device(
|
||||
connections={(dr.CONNECTION_NETWORK_MAC, "aa:bb:cc:00:11:22")}
|
||||
)
|
||||
assert entity_registry.async_get("device_tracker.printer")
|
||||
assert entity_registry.async_get("switch.printer_internet_access")
|
||||
|
||||
|
||||
async def test_async_trigger_cleanup_preserves_fritz_device(
|
||||
hass: HomeAssistant,
|
||||
device_registry: dr.DeviceRegistry,
|
||||
|
||||
Reference in New Issue
Block a user