diff --git a/homeassistant/components/dlna_dmr/data.py b/homeassistant/components/dlna_dmr/data.py index 07046ba4acc..7afe4304581 100644 --- a/homeassistant/components/dlna_dmr/data.py +++ b/homeassistant/components/dlna_dmr/data.py @@ -46,7 +46,9 @@ class DlnaDmrData: """Clean up resources when Home Assistant is stopped.""" LOGGER.debug("Cleaning resources in DlnaDmrData") async with self.lock: - tasks = (server.stop_server() for server in self.event_notifiers.values()) + tasks = ( + server.async_stop_server() for server in self.event_notifiers.values() + ) asyncio.gather(*tasks) self.event_notifiers = {} self.event_notifier_refs = defaultdict(int) @@ -76,14 +78,14 @@ class DlnaDmrData: return self.event_notifiers[listen_addr].event_handler # Start event handler + source = (listen_addr.host or "0.0.0.0", listen_addr.port) server = AiohttpNotifyServer( requester=self.requester, - listen_port=listen_addr.port, - listen_host=listen_addr.host, + source=source, callback_url=listen_addr.callback_url, loop=hass.loop, ) - await server.start_server() + await server.async_start_server() LOGGER.debug("Started event handler at %s", server.callback_url) self.event_notifiers[listen_addr] = server @@ -103,7 +105,7 @@ class DlnaDmrData: # Shutdown the server when it has no more users if self.event_notifier_refs[listen_addr] == 0: server = self.event_notifiers.pop(listen_addr) - await server.stop_server() + await server.async_stop_server() # Remove the cleanup listener when there's nothing left to cleanup if not self.event_notifiers: diff --git a/homeassistant/components/dlna_dmr/manifest.json b/homeassistant/components/dlna_dmr/manifest.json index 4001fc9dddc..76bf8ac051c 100644 --- a/homeassistant/components/dlna_dmr/manifest.json +++ b/homeassistant/components/dlna_dmr/manifest.json @@ -3,7 +3,7 @@ "name": "DLNA Digital Media Renderer", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/dlna_dmr", - "requirements": ["async-upnp-client==0.23.5"], + "requirements": ["async-upnp-client==0.25.0"], "dependencies": ["ssdp"], "after_dependencies": ["media_source"], "ssdp": [ diff --git a/homeassistant/components/dlna_dms/dms.py b/homeassistant/components/dlna_dms/dms.py index d3a65448f84..74774a84821 100644 --- a/homeassistant/components/dlna_dms/dms.py +++ b/homeassistant/components/dlna_dms/dms.py @@ -7,7 +7,12 @@ from dataclasses import dataclass import functools from typing import Any, TypeVar, cast -from async_upnp_client import UpnpEventHandler, UpnpFactory, UpnpRequester +from async_upnp_client import ( + UpnpEventHandler, + UpnpFactory, + UpnpNotifyServer, + UpnpRequester, +) from async_upnp_client.aiohttp import AiohttpSessionRequester from async_upnp_client.const import NotificationSubType from async_upnp_client.exceptions import UpnpActionError, UpnpConnectionError, UpnpError @@ -68,7 +73,7 @@ class DlnaDmsData: self.upnp_factory = UpnpFactory(self.requester, non_strict=True) # NOTE: event_handler is not actually used, and is only created to # satisfy the DmsDevice.__init__ signature - self.event_handler = UpnpEventHandler("", self.requester) + self.event_handler = UpnpEventHandler(UpnpNotifyServer(), self.requester) self.devices = {} self.sources = {} diff --git a/homeassistant/components/dlna_dms/manifest.json b/homeassistant/components/dlna_dms/manifest.json index 84cfc2e69fd..9f4d02d4462 100644 --- a/homeassistant/components/dlna_dms/manifest.json +++ b/homeassistant/components/dlna_dms/manifest.json @@ -3,7 +3,7 @@ "name": "DLNA Digital Media Server", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/dlna_dms", - "requirements": ["async-upnp-client==0.23.5"], + "requirements": ["async-upnp-client==0.25.0"], "dependencies": ["ssdp"], "after_dependencies": ["media_source"], "ssdp": [ diff --git a/homeassistant/components/network/__init__.py b/homeassistant/components/network/__init__.py index b3ef88e7ab2..8c1a0020cfb 100644 --- a/homeassistant/components/network/__init__.py +++ b/homeassistant/components/network/__init__.py @@ -60,12 +60,14 @@ async def async_get_enabled_source_ips( if not adapter["enabled"]: continue if adapter["ipv4"]: - sources.extend(IPv4Address(ipv4["address"]) for ipv4 in adapter["ipv4"]) + addrs_ipv4 = [IPv4Address(ipv4["address"]) for ipv4 in adapter["ipv4"]] + sources.extend(addrs_ipv4) if adapter["ipv6"]: - # With python 3.9 add scope_ids can be - # added by enumerating adapter["ipv6"]s - # IPv6Address(f"::%{ipv6['scope_id']}") - sources.extend(IPv6Address(ipv6["address"]) for ipv6 in adapter["ipv6"]) + addrs_ipv6 = [ + IPv6Address(f"{ipv6['address']}%{ipv6['scope_id']}") + for ipv6 in adapter["ipv6"] + ] + sources.extend(addrs_ipv6) return sources diff --git a/homeassistant/components/ssdp/__init__.py b/homeassistant/components/ssdp/__init__.py index b1b06832918..80d9a716df0 100644 --- a/homeassistant/components/ssdp/__init__.py +++ b/homeassistant/components/ssdp/__init__.py @@ -11,10 +11,15 @@ import logging from typing import Any from async_upnp_client.aiohttp import AiohttpSessionRequester -from async_upnp_client.const import DeviceOrServiceType, SsdpHeaders, SsdpSource +from async_upnp_client.const import ( + AddressTupleVXType, + DeviceOrServiceType, + SsdpHeaders, + SsdpSource, +) from async_upnp_client.description_cache import DescriptionCache -from async_upnp_client.ssdp import SSDP_PORT -from async_upnp_client.ssdp_listener import SsdpDevice, SsdpListener +from async_upnp_client.ssdp import SSDP_PORT, determine_source_target, is_ipv4_address +from async_upnp_client.ssdp_listener import SsdpDevice, SsdpDeviceTracker, SsdpListener from async_upnp_client.utils import CaseInsensitiveDict from homeassistant import config_entries @@ -372,17 +377,10 @@ class Scanner: async def _async_build_source_set(self) -> set[IPv4Address | IPv6Address]: """Build the list of ssdp sources.""" - adapters = await network.async_get_adapters(self.hass) - sources: set[IPv4Address | IPv6Address] = set() - if network.async_only_default_interface_enabled(adapters): - sources.add(IPv4Address("0.0.0.0")) - return sources - return { source_ip for source_ip in await network.async_get_enabled_source_ips(self.hass) - if not source_ip.is_loopback - and not (isinstance(source_ip, IPv6Address) and source_ip.is_global) + if not source_ip.is_loopback and not source_ip.is_global } async def async_scan(self, *_: Any) -> None: @@ -401,11 +399,8 @@ class Scanner: # address. This matches pysonos' behavior # https://github.com/amelchio/pysonos/blob/d4329b4abb657d106394ae69357805269708c996/pysonos/discovery.py#L120 for listener in self._ssdp_listeners: - try: - IPv4Address(listener.source_ip) - except ValueError: - continue - await listener.async_search((str(IPV4_BROADCAST), SSDP_PORT)) + if is_ipv4_address(listener.source): + await listener.async_search((str(IPV4_BROADCAST), SSDP_PORT)) async def async_start(self) -> None: """Start the scanners.""" @@ -425,11 +420,26 @@ class Scanner: async def _async_start_ssdp_listeners(self) -> None: """Start the SSDP Listeners.""" + # Devices are shared between all sources. + device_tracker = SsdpDeviceTracker() for source_ip in await self._async_build_source_set(): + source_ip_str = str(source_ip) + if source_ip.version == 6: + source_tuple: AddressTupleVXType = ( + source_ip_str, + 0, + 0, + int(getattr(source_ip, "scope_id")), + ) + else: + source_tuple = (source_ip_str, 0) + source, target = determine_source_target(source_tuple) self._ssdp_listeners.append( SsdpListener( async_callback=self._ssdp_listener_callback, - source_ip=source_ip, + source=source, + target=target, + device_tracker=device_tracker, ) ) results = await asyncio.gather( @@ -441,7 +451,7 @@ class Scanner: if isinstance(result, Exception): _LOGGER.warning( "Failed to setup listener for %s: %s", - self._ssdp_listeners[idx].source_ip, + self._ssdp_listeners[idx].source, result, ) failed_listeners.append(self._ssdp_listeners[idx]) diff --git a/homeassistant/components/ssdp/manifest.json b/homeassistant/components/ssdp/manifest.json index 93512d08238..a4e02759989 100644 --- a/homeassistant/components/ssdp/manifest.json +++ b/homeassistant/components/ssdp/manifest.json @@ -2,7 +2,7 @@ "domain": "ssdp", "name": "Simple Service Discovery Protocol (SSDP)", "documentation": "https://www.home-assistant.io/integrations/ssdp", - "requirements": ["async-upnp-client==0.23.5"], + "requirements": ["async-upnp-client==0.25.0"], "dependencies": ["network"], "after_dependencies": ["zeroconf"], "codeowners": [], diff --git a/homeassistant/components/upnp/__init__.py b/homeassistant/components/upnp/__init__.py index 5180932080c..a3490ad8037 100644 --- a/homeassistant/components/upnp/__init__.py +++ b/homeassistant/components/upnp/__init__.py @@ -177,8 +177,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: await coordinator.async_config_entry_first_refresh() - # Create sensors. - LOGGER.debug("Enabling sensors") + # Setup platforms, creating sensors/binary_sensors. hass.config_entries.async_setup_platforms(entry, PLATFORMS) return True @@ -188,7 +187,7 @@ async def async_unload_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> """Unload a UPnP/IGD device from a config entry.""" LOGGER.debug("Unloading config entry: %s", config_entry.unique_id) - LOGGER.debug("Deleting sensors") + # Unload platforms. return await hass.config_entries.async_unload_platforms(config_entry, PLATFORMS) diff --git a/homeassistant/components/upnp/binary_sensor.py b/homeassistant/components/upnp/binary_sensor.py index 1ff1164cc58..d848aff5a88 100644 --- a/homeassistant/components/upnp/binary_sensor.py +++ b/homeassistant/components/upnp/binary_sensor.py @@ -28,8 +28,6 @@ async def async_setup_entry( """Set up the UPnP/IGD sensors.""" coordinator = hass.data[DOMAIN][config_entry.entry_id] - LOGGER.debug("Adding binary sensor") - entities = [ UpnpStatusBinarySensor( coordinator=coordinator, @@ -38,7 +36,7 @@ async def async_setup_entry( for entity_description in BINARYSENSOR_ENTITY_DESCRIPTIONS if coordinator.data.get(entity_description.key) is not None ] - LOGGER.debug("Adding entities: %s", entities) + LOGGER.debug("Adding binary_sensor entities: %s", entities) async_add_entities(entities) diff --git a/homeassistant/components/upnp/config_flow.py b/homeassistant/components/upnp/config_flow.py index 74acb88983b..1b8507d25bf 100644 --- a/homeassistant/components/upnp/config_flow.py +++ b/homeassistant/components/upnp/config_flow.py @@ -222,7 +222,9 @@ class UpnpFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): unique_id = discovery_info.ssdp_usn await self.async_set_unique_id(unique_id) hostname = discovery_info.ssdp_headers["_host"] - self._abort_if_unique_id_configured(updates={CONFIG_ENTRY_HOSTNAME: hostname}) + self._abort_if_unique_id_configured( + updates={CONFIG_ENTRY_HOSTNAME: hostname}, reload_on_update=False + ) # Handle devices changing their UDN, only allow a single host. existing_entries = self._async_current_entries() diff --git a/homeassistant/components/upnp/device.py b/homeassistant/components/upnp/device.py index 3231d34a342..be0ae725396 100644 --- a/homeassistant/components/upnp/device.py +++ b/homeassistant/components/upnp/device.py @@ -80,6 +80,10 @@ class Device: if service_info.ssdp_location is None: return + if change == SsdpChange.ALIVE: + # We care only about updates. + return + device = self._igd_device.device if service_info.ssdp_location == device.device_url: return diff --git a/homeassistant/components/upnp/manifest.json b/homeassistant/components/upnp/manifest.json index 5b630973f67..19b0e4529a4 100644 --- a/homeassistant/components/upnp/manifest.json +++ b/homeassistant/components/upnp/manifest.json @@ -3,7 +3,7 @@ "name": "UPnP/IGD", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/upnp", - "requirements": ["async-upnp-client==0.23.5"], + "requirements": ["async-upnp-client==0.25.0"], "dependencies": ["network", "ssdp"], "codeowners": ["@StevenLooman","@ehendrix23"], "ssdp": [ diff --git a/homeassistant/components/upnp/sensor.py b/homeassistant/components/upnp/sensor.py index 54176715b84..595a14a5a9f 100644 --- a/homeassistant/components/upnp/sensor.py +++ b/homeassistant/components/upnp/sensor.py @@ -136,7 +136,7 @@ async def async_setup_entry( ] ) - LOGGER.debug("Adding entities: %s", entities) + LOGGER.debug("Adding sensor entities: %s", entities) async_add_entities(entities) diff --git a/homeassistant/components/yeelight/manifest.json b/homeassistant/components/yeelight/manifest.json index 7aa46881968..b6defca2060 100644 --- a/homeassistant/components/yeelight/manifest.json +++ b/homeassistant/components/yeelight/manifest.json @@ -2,7 +2,7 @@ "domain": "yeelight", "name": "Yeelight", "documentation": "https://www.home-assistant.io/integrations/yeelight", - "requirements": ["yeelight==0.7.9", "async-upnp-client==0.23.5"], + "requirements": ["yeelight==0.7.9", "async-upnp-client==0.25.0"], "codeowners": ["@zewelor", "@shenxn", "@starkillerOG", "@alexyao2015"], "config_flow": true, "dependencies": ["network"], diff --git a/homeassistant/components/yeelight/scanner.py b/homeassistant/components/yeelight/scanner.py index 1756fbe865c..6876b93a0eb 100644 --- a/homeassistant/components/yeelight/scanner.py +++ b/homeassistant/components/yeelight/scanner.py @@ -67,12 +67,13 @@ class YeelightScanner: return _async_connected + source = (str(source_ip), 0) self._listeners.append( SsdpSearchListener( async_callback=self._async_process_entry, service_type=SSDP_ST, target=SSDP_TARGET, - source_ip=source_ip, + source=source, async_connect_callback=_wrap_async_connected_idx(idx), ) ) @@ -87,7 +88,7 @@ class YeelightScanner: continue _LOGGER.warning( "Failed to setup listener for %s: %s", - self._listeners[idx].source_ip, + self._listeners[idx].source, result, ) failed_listeners.append(self._listeners[idx]) diff --git a/homeassistant/package_constraints.txt b/homeassistant/package_constraints.txt index a36f21efd6b..a333d603a33 100644 --- a/homeassistant/package_constraints.txt +++ b/homeassistant/package_constraints.txt @@ -4,7 +4,7 @@ aiodiscover==1.4.8 aiohttp==3.8.1 aiohttp_cors==0.7.0 astral==2.2 -async-upnp-client==0.23.5 +async-upnp-client==0.25.0 async_timeout==4.0.2 atomicwrites==1.4.0 attrs==21.2.0 diff --git a/requirements_all.txt b/requirements_all.txt index 923c8f45956..df781d28836 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -327,7 +327,7 @@ asterisk_mbox==0.5.0 # homeassistant.components.ssdp # homeassistant.components.upnp # homeassistant.components.yeelight -async-upnp-client==0.23.5 +async-upnp-client==0.25.0 # homeassistant.components.supla asyncpysupla==0.0.5 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 86482356bfd..ff12e10ee01 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -256,7 +256,7 @@ arcam-fmj==0.12.0 # homeassistant.components.ssdp # homeassistant.components.upnp # homeassistant.components.yeelight -async-upnp-client==0.23.5 +async-upnp-client==0.25.0 # homeassistant.components.sleepiq asyncsleepiq==1.1.0 diff --git a/tests/components/dlna_dmr/test_data.py b/tests/components/dlna_dmr/test_data.py index 469cfcece88..2a86070ea72 100644 --- a/tests/components/dlna_dmr/test_data.py +++ b/tests/components/dlna_dmr/test_data.py @@ -37,7 +37,10 @@ def aiohttp_notify_servers_mock() -> Iterable[Mock]: # Every server must be stopped if it was started for server in servers: - assert server.start_server.call_count == server.stop_server.call_count + assert ( + server.async_start_server.call_count + == server.async_stop_server.call_count + ) async def test_get_domain_data(hass: HomeAssistant) -> None: @@ -60,7 +63,7 @@ async def test_event_notifier( # Check that the parameters were passed through to the AiohttpNotifyServer aiohttp_notify_servers_mock.assert_called_with( - requester=ANY, listen_port=0, listen_host=None, callback_url=None, loop=ANY + requester=ANY, source=("0.0.0.0", 0), callback_url=None, loop=ANY ) # Same address should give same notifier @@ -79,8 +82,7 @@ async def test_event_notifier( # Check that the parameters were passed through to the AiohttpNotifyServer aiohttp_notify_servers_mock.assert_called_with( requester=ANY, - listen_port=9999, - listen_host="192.88.99.4", + source=("192.88.99.4", 9999), callback_url="http://192.88.99.4:9999/notify", loop=ANY, ) diff --git a/tests/components/ssdp/test_init.py b/tests/components/ssdp/test_init.py index 758f8fb79ba..b947ce4269a 100644 --- a/tests/components/ssdp/test_init.py +++ b/tests/components/ssdp/test_init.py @@ -2,7 +2,7 @@ # pylint: disable=protected-access from datetime import datetime, timedelta -from ipaddress import IPv4Address, IPv6Address +from ipaddress import IPv4Address from unittest.mock import ANY, AsyncMock, patch from async_upnp_client.ssdp import udn_from_headers @@ -25,7 +25,7 @@ from tests.common import async_fire_time_changed def _ssdp_headers(headers): - ssdp_headers = CaseInsensitiveDict(headers, _timestamp=datetime(2021, 1, 1, 12, 00)) + ssdp_headers = CaseInsensitiveDict(headers, _timestamp=datetime.now()) ssdp_headers["_udn"] = udn_from_headers(ssdp_headers) return ssdp_headers @@ -386,7 +386,7 @@ async def test_discovery_from_advertisement_sets_ssdp_st( # End compatibility checks -@patch( # XXX TODO: Isn't this duplicate with mock_get_source_ip? +@patch( "homeassistant.components.ssdp.Scanner._async_build_source_set", return_value={IPv4Address("192.168.1.1")}, ) @@ -486,7 +486,7 @@ async def test_scan_with_registered_callback( mock_call_data.ssdp_usn == "uuid:TIVRTLSR7ANF-D6E-1557809135086-RETAIL::mock-st" ) assert mock_call_data.ssdp_headers["x-rincon-bootseq"] == "55" - assert mock_call_data.ssdp_udn == ANY + assert mock_call_data.ssdp_udn == "uuid:TIVRTLSR7ANF-D6E-1557809135086-RETAIL" assert mock_call_data.ssdp_headers["_timestamp"] == ANY assert mock_call_data.x_homeassistant_matching_domains == set() assert mock_call_data.upnp == { @@ -711,7 +711,7 @@ _ADAPTERS_WITH_MANUAL_CONFIG = [ @patch( "homeassistant.components.ssdp.network.async_get_adapters", return_value=_ADAPTERS_WITH_MANUAL_CONFIG, -) # XXX TODO: Isn't this duplicate with mock_get_source_ip? +) async def test_async_detect_interfaces_setting_empty_route( mock_get_adapters, mock_get_ssdp, hass ): @@ -719,8 +719,8 @@ async def test_async_detect_interfaces_setting_empty_route( await init_ssdp_component(hass) ssdp_listeners = hass.data[ssdp.DOMAIN]._ssdp_listeners - source_ips = {ssdp_listener.source_ip for ssdp_listener in ssdp_listeners} - assert source_ips == {IPv6Address("2001:db8::"), IPv4Address("192.168.1.5")} + sources = {ssdp_listener.source for ssdp_listener in ssdp_listeners} + assert sources == {("2001:db8::%1", 0, 0, 1), ("192.168.1.5", 0)} @pytest.mark.usefixtures("mock_get_source_ip") @@ -737,14 +737,14 @@ async def test_async_detect_interfaces_setting_empty_route( @patch( "homeassistant.components.ssdp.network.async_get_adapters", return_value=_ADAPTERS_WITH_MANUAL_CONFIG, -) # XXX TODO: Isn't this duplicate with mock_get_source_ip? +) async def test_bind_failure_skips_adapter( mock_get_adapters, mock_get_ssdp, hass, caplog ): """Test that an adapter with a bind failure is skipped.""" async def _async_start(self): - if self.source_ip == IPv6Address("2001:db8::"): + if self.source == ("2001:db8::%1", 0, 0, 1): raise OSError SsdpListener.async_start = _async_start @@ -753,10 +753,8 @@ async def test_bind_failure_skips_adapter( assert "Failed to setup listener for" in caplog.text ssdp_listeners = hass.data[ssdp.DOMAIN]._ssdp_listeners - source_ips = {ssdp_listener.source_ip for ssdp_listener in ssdp_listeners} - assert source_ips == { - IPv4Address("192.168.1.5") - } # Note no SsdpListener for IPv6 address. + sources = {ssdp_listener.source for ssdp_listener in ssdp_listeners} + assert sources == {("192.168.1.5", 0)} # Note no SsdpListener for IPv6 address. @pytest.mark.usefixtures("mock_get_source_ip") @@ -773,7 +771,7 @@ async def test_bind_failure_skips_adapter( @patch( "homeassistant.components.ssdp.network.async_get_adapters", return_value=_ADAPTERS_WITH_MANUAL_CONFIG, -) # XXX TODO: Isn't this duplicate with mock_get_source_ip? +) async def test_ipv4_does_additional_search_for_sonos( mock_get_adapters, mock_get_ssdp, hass ): diff --git a/tests/components/yeelight/__init__.py b/tests/components/yeelight/__init__.py index d0112c5a544..96f8846b174 100644 --- a/tests/components/yeelight/__init__.py +++ b/tests/components/yeelight/__init__.py @@ -1,7 +1,6 @@ """Tests for the Yeelight integration.""" import asyncio from datetime import timedelta -from ipaddress import IPv4Address from unittest.mock import AsyncMock, MagicMock, patch from async_upnp_client.search import SsdpSearchListener @@ -162,7 +161,7 @@ def _patched_ssdp_listener(info: ssdp.SsdpHeaders, *args, **kwargs): listener = SsdpSearchListener(*args, **kwargs) async def _async_callback(*_): - if kwargs["source_ip"] == IPv4Address(FAIL_TO_BIND_IP): + if kwargs["source"][0] == FAIL_TO_BIND_IP: raise OSError await listener.async_connect_callback() diff --git a/tests/components/yeelight/test_init.py b/tests/components/yeelight/test_init.py index dc3d602edeb..cba40eb0262 100644 --- a/tests/components/yeelight/test_init.py +++ b/tests/components/yeelight/test_init.py @@ -241,7 +241,7 @@ async def test_setup_discovery_with_manually_configured_network_adapter_one_fail assert hass.states.get(ENTITY_BINARY_SENSOR) is None assert hass.states.get(ENTITY_LIGHT) is None - assert f"Failed to setup listener for {FAIL_TO_BIND_IP}" in caplog.text + assert f"Failed to setup listener for ('{FAIL_TO_BIND_IP}', 0)" in caplog.text async def test_setup_import(hass: HomeAssistant): diff --git a/tests/components/zeroconf/test_init.py b/tests/components/zeroconf/test_init.py index b7e99991fdd..28a4f5d969f 100644 --- a/tests/components/zeroconf/test_init.py +++ b/tests/components/zeroconf/test_init.py @@ -955,11 +955,11 @@ async def test_async_detect_interfaces_setting_empty_route_linux( await hass.async_block_till_done() assert mock_zc.mock_calls[0] == call( interfaces=[ - "2001:db8::", - "fe80::1234:5678:9abc:def0", + "2001:db8::%1", + "fe80::1234:5678:9abc:def0%1", "192.168.1.5", "172.16.1.5", - "fe80::dead:beef:dead:beef", + "fe80::dead:beef:dead:beef%3", ], ip_version=IPVersion.All, ) @@ -1053,7 +1053,7 @@ async def test_async_detect_interfaces_explicitly_set_ipv6_linux( await hass.async_block_till_done() assert mock_zc.mock_calls[0] == call( - interfaces=["192.168.1.5", "fe80::dead:beef:dead:beef"], + interfaces=["192.168.1.5", "fe80::dead:beef:dead:beef%3"], ip_version=IPVersion.All, )