From fb94f8ea189cb18e2b11c5166687e2b9ebb4bd60 Mon Sep 17 00:00:00 2001 From: Michael <35783820+mib1185@users.noreply.github.com> Date: Sat, 3 May 2025 21:04:59 +0200 Subject: [PATCH] Make the network device tracking feature optional in AVM Fritz!Tools (#144149) * make the network device tracking feature optional * fix doc strings * Apply suggestions from code review Co-authored-by: Jan Bouwhuis --------- Co-authored-by: Jan Bouwhuis --- homeassistant/components/fritz/__init__.py | 7 +++++ homeassistant/components/fritz/config_flow.py | 24 +++++++++++++-- homeassistant/components/fritz/const.py | 3 ++ homeassistant/components/fritz/coordinator.py | 30 ++++++++++++++----- homeassistant/components/fritz/strings.json | 22 +++++++++----- tests/components/fritz/test_config_flow.py | 2 ++ 6 files changed, 71 insertions(+), 17 deletions(-) diff --git a/homeassistant/components/fritz/__init__.py b/homeassistant/components/fritz/__init__.py index 05a2a07707f..9610fe4b34d 100644 --- a/homeassistant/components/fritz/__init__.py +++ b/homeassistant/components/fritz/__init__.py @@ -15,6 +15,8 @@ from homeassistant.helpers import config_validation as cv from homeassistant.helpers.typing import ConfigType from .const import ( + CONF_FEATURE_DEVICE_TRACKING, + DEFAULT_CONF_FEATURE_DEVICE_TRACKING, DEFAULT_SSL, DOMAIN, FRITZ_AUTH_EXCEPTIONS, @@ -38,6 +40,7 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: async def async_setup_entry(hass: HomeAssistant, entry: FritzConfigEntry) -> bool: """Set up fritzboxtools from config entry.""" _LOGGER.debug("Setting up FRITZ!Box Tools component") + avm_wrapper = AvmWrapper( hass=hass, config_entry=entry, @@ -46,6 +49,9 @@ async def async_setup_entry(hass: HomeAssistant, entry: FritzConfigEntry) -> boo username=entry.data[CONF_USERNAME], password=entry.data[CONF_PASSWORD], use_tls=entry.data.get(CONF_SSL, DEFAULT_SSL), + device_discovery_enabled=entry.options.get( + CONF_FEATURE_DEVICE_TRACKING, DEFAULT_CONF_FEATURE_DEVICE_TRACKING + ), ) try: @@ -62,6 +68,7 @@ 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 diff --git a/homeassistant/components/fritz/config_flow.py b/homeassistant/components/fritz/config_flow.py index fb17f872cb6..2c22a35c4dd 100644 --- a/homeassistant/components/fritz/config_flow.py +++ b/homeassistant/components/fritz/config_flow.py @@ -35,7 +35,9 @@ from homeassistant.helpers.service_info.ssdp import ( from homeassistant.helpers.typing import VolDictType from .const import ( + CONF_FEATURE_DEVICE_TRACKING, CONF_OLD_DISCOVERY, + DEFAULT_CONF_FEATURE_DEVICE_TRACKING, DEFAULT_CONF_OLD_DISCOVERY, DEFAULT_HOST, DEFAULT_HTTP_PORT, @@ -72,7 +74,8 @@ class FritzBoxToolsFlowHandler(ConfigFlow, domain=DOMAIN): """Initialize FRITZ!Box Tools flow.""" self._name: str = "" self._password: str = "" - self._use_tls: bool = False + self._use_tls: bool = DEFAULT_SSL + self._feature_device_discovery: bool = DEFAULT_CONF_FEATURE_DEVICE_TRACKING self._port: int | None = None self._username: str = "" self._model: str = "" @@ -141,6 +144,7 @@ class FritzBoxToolsFlowHandler(ConfigFlow, domain=DOMAIN): options={ CONF_CONSIDER_HOME: DEFAULT_CONSIDER_HOME.total_seconds(), CONF_OLD_DISCOVERY: DEFAULT_CONF_OLD_DISCOVERY, + CONF_FEATURE_DEVICE_TRACKING: self._feature_device_discovery, }, ) @@ -204,6 +208,7 @@ class FritzBoxToolsFlowHandler(ConfigFlow, domain=DOMAIN): self._username = user_input[CONF_USERNAME] self._password = user_input[CONF_PASSWORD] self._use_tls = user_input[CONF_SSL] + self._feature_device_discovery = user_input[CONF_FEATURE_DEVICE_TRACKING] self._port = self._determine_port(user_input) error = await self.async_fritz_tools_init() @@ -234,6 +239,10 @@ class FritzBoxToolsFlowHandler(ConfigFlow, domain=DOMAIN): vol.Required(CONF_USERNAME): str, vol.Required(CONF_PASSWORD): str, vol.Optional(CONF_SSL, default=DEFAULT_SSL): bool, + vol.Required( + CONF_FEATURE_DEVICE_TRACKING, + default=DEFAULT_CONF_FEATURE_DEVICE_TRACKING, + ): bool, } ), errors=errors or {}, @@ -250,6 +259,10 @@ class FritzBoxToolsFlowHandler(ConfigFlow, domain=DOMAIN): vol.Required(CONF_USERNAME): str, vol.Required(CONF_PASSWORD): str, vol.Optional(CONF_SSL, default=DEFAULT_SSL): bool, + vol.Required( + CONF_FEATURE_DEVICE_TRACKING, + default=DEFAULT_CONF_FEATURE_DEVICE_TRACKING, + ): bool, } ), description_placeholders={"name": self._name}, @@ -405,7 +418,7 @@ class FritzBoxToolsOptionsFlowHandler(OptionsFlow): """Handle options flow.""" if user_input is not None: - return self.async_create_entry(title="", data=user_input) + return self.async_create_entry(data=user_input) options = self.config_entry.options data_schema = vol.Schema( @@ -420,6 +433,13 @@ class FritzBoxToolsOptionsFlowHandler(OptionsFlow): CONF_OLD_DISCOVERY, default=options.get(CONF_OLD_DISCOVERY, DEFAULT_CONF_OLD_DISCOVERY), ): bool, + vol.Optional( + CONF_FEATURE_DEVICE_TRACKING, + default=options.get( + CONF_FEATURE_DEVICE_TRACKING, + DEFAULT_CONF_FEATURE_DEVICE_TRACKING, + ), + ): bool, } ) return self.async_show_form(step_id="init", data_schema=data_schema) diff --git a/homeassistant/components/fritz/const.py b/homeassistant/components/fritz/const.py index 2237823bc3b..32f52e68458 100644 --- a/homeassistant/components/fritz/const.py +++ b/homeassistant/components/fritz/const.py @@ -40,6 +40,9 @@ PLATFORMS = [ CONF_OLD_DISCOVERY = "old_discovery" DEFAULT_CONF_OLD_DISCOVERY = False +CONF_FEATURE_DEVICE_TRACKING = "feature_device_tracking" +DEFAULT_CONF_FEATURE_DEVICE_TRACKING = True + DSL_CONNECTION: Literal["dsl"] = "dsl" DEFAULT_DEVICE_NAME = "Unknown device" diff --git a/homeassistant/components/fritz/coordinator.py b/homeassistant/components/fritz/coordinator.py index d253e9b5b12..e22a66d254f 100644 --- a/homeassistant/components/fritz/coordinator.py +++ b/homeassistant/components/fritz/coordinator.py @@ -39,6 +39,7 @@ from homeassistant.util.hass_dict import HassKey from .const import ( CONF_OLD_DISCOVERY, + DEFAULT_CONF_FEATURE_DEVICE_TRACKING, DEFAULT_CONF_OLD_DISCOVERY, DEFAULT_HOST, DEFAULT_SSL, @@ -175,6 +176,7 @@ class FritzBoxTools(DataUpdateCoordinator[UpdateCoordinatorDataType]): username: str = DEFAULT_USERNAME, host: str = DEFAULT_HOST, use_tls: bool = DEFAULT_SSL, + device_discovery_enabled: bool = DEFAULT_CONF_FEATURE_DEVICE_TRACKING, ) -> None: """Initialize FritzboxTools class.""" super().__init__( @@ -202,6 +204,7 @@ class FritzBoxTools(DataUpdateCoordinator[UpdateCoordinatorDataType]): self.port = port self.username = username self.use_tls = use_tls + self.device_discovery_enabled = device_discovery_enabled self.has_call_deflections: bool = False self._model: str | None = None self._current_firmware: str | None = None @@ -332,10 +335,15 @@ class FritzBoxTools(DataUpdateCoordinator[UpdateCoordinatorDataType]): "entity_states": {}, } try: - await self.async_scan_devices() + await self.async_update_device_info() + + if self.device_discovery_enabled: + await self.async_scan_devices() + entity_data["entity_states"] = await self.hass.async_add_executor_job( self._entity_states_update ) + if self.has_call_deflections: entity_data[ "call_deflections" @@ -551,12 +559,8 @@ class FritzBoxTools(DataUpdateCoordinator[UpdateCoordinatorDataType]): if new_device: async_dispatcher_send(self.hass, self.signal_device_new) - async def async_scan_devices(self, now: datetime | None = None) -> None: - """Scan for new devices and return a list of found device ids.""" - - if self.hass.is_stopping: - _ha_is_stopping("scan devices") - return + async def async_update_device_info(self, now: datetime | None = None) -> None: + """Update own device information.""" _LOGGER.debug("Checking host info for FRITZ!Box device %s", self.host) ( @@ -565,6 +569,13 @@ class FritzBoxTools(DataUpdateCoordinator[UpdateCoordinatorDataType]): self._release_url, ) = await self._async_update_device_info() + async def async_scan_devices(self, now: datetime | None = None) -> None: + """Scan for new network devices.""" + + if self.hass.is_stopping: + _ha_is_stopping("scan devices") + return + _LOGGER.debug("Checking devices for FRITZ!Box device %s", self.host) _default_consider_home = DEFAULT_CONSIDER_HOME.total_seconds() if self._options: @@ -683,7 +694,10 @@ class FritzBoxTools(DataUpdateCoordinator[UpdateCoordinatorDataType]): async def async_trigger_cleanup(self) -> None: """Trigger device trackers cleanup.""" - device_hosts = await self._async_update_hosts_info() + _LOGGER.debug("Device tracker cleanup triggered") + device_hosts = {self.mac: Device(True, "", "", "", "", None)} + if self.device_discovery_enabled: + device_hosts = await self._async_update_hosts_info() entity_reg: er.EntityRegistry = er.async_get(self.hass) config_entry = self.config_entry diff --git a/homeassistant/components/fritz/strings.json b/homeassistant/components/fritz/strings.json index 6191fc524dd..ee23a8cfbef 100644 --- a/homeassistant/components/fritz/strings.json +++ b/homeassistant/components/fritz/strings.json @@ -4,7 +4,9 @@ "data_description_port": "Leave empty to use the default port.", "data_description_username": "Username for the FRITZ!Box.", "data_description_password": "Password for the FRITZ!Box.", - "data_description_ssl": "Use SSL to connect to the FRITZ!Box." + "data_description_ssl": "Use SSL to connect to the FRITZ!Box.", + "data_description_feature_device_tracking": "Enable or disable the network device tracking feature.", + "data_feature_device_tracking": "Enable network device tracking" }, "config": { "flow_title": "{name}", @@ -15,12 +17,14 @@ "data": { "username": "[%key:common::config_flow::data::username%]", "password": "[%key:common::config_flow::data::password%]", - "ssl": "[%key:common::config_flow::data::ssl%]" + "ssl": "[%key:common::config_flow::data::ssl%]", + "feature_device_tracking": "[%key:component::fritz::common::data_feature_device_tracking%]" }, "data_description": { "username": "[%key:component::fritz::common::data_description_username%]", "password": "[%key:component::fritz::common::data_description_password%]", - "ssl": "[%key:component::fritz::common::data_description_ssl%]" + "ssl": "[%key:component::fritz::common::data_description_ssl%]", + "feature_device_tracking": "[%key:component::fritz::common::data_description_feature_device_tracking%]" } }, "reauth_confirm": { @@ -57,14 +61,16 @@ "port": "[%key:common::config_flow::data::port%]", "username": "[%key:common::config_flow::data::username%]", "password": "[%key:common::config_flow::data::password%]", - "ssl": "[%key:common::config_flow::data::ssl%]" + "ssl": "[%key:common::config_flow::data::ssl%]", + "feature_device_tracking": "[%key:component::fritz::common::data_feature_device_tracking%]" }, "data_description": { "host": "[%key:component::fritz::common::data_description_host%]", "port": "[%key:component::fritz::common::data_description_port%]", "username": "[%key:component::fritz::common::data_description_username%]", "password": "[%key:component::fritz::common::data_description_password%]", - "ssl": "[%key:component::fritz::common::data_description_ssl%]" + "ssl": "[%key:component::fritz::common::data_description_ssl%]", + "feature_device_tracking": "[%key:component::fritz::common::data_description_feature_device_tracking%]" } } }, @@ -89,11 +95,13 @@ "init": { "data": { "consider_home": "Seconds to consider a device at 'home'", - "old_discovery": "Enable old discovery method" + "old_discovery": "Enable old discovery method", + "feature_device_tracking": "[%key:component::fritz::common::data_feature_device_tracking%]" }, "data_description": { "consider_home": "Time in seconds to consider a device at home. Default is 180 seconds.", - "old_discovery": "Enable old discovery method. This is needed for some scenarios." + "old_discovery": "Enable old discovery method. This is needed for some scenarios.", + "feature_device_tracking": "[%key:component::fritz::common::data_description_feature_device_tracking%]" } } } diff --git a/tests/components/fritz/test_config_flow.py b/tests/components/fritz/test_config_flow.py index ee3ae881b2c..f790489c341 100644 --- a/tests/components/fritz/test_config_flow.py +++ b/tests/components/fritz/test_config_flow.py @@ -16,6 +16,7 @@ from homeassistant.components.device_tracker import ( DEFAULT_CONSIDER_HOME, ) from homeassistant.components.fritz.const import ( + CONF_FEATURE_DEVICE_TRACKING, CONF_OLD_DISCOVERY, DOMAIN, ERROR_AUTH_INVALID, @@ -744,6 +745,7 @@ async def test_options_flow(hass: HomeAssistant) -> None: assert result["data"] == { CONF_OLD_DISCOVERY: False, CONF_CONSIDER_HOME: 37, + CONF_FEATURE_DEVICE_TRACKING: True, }