From 574cb03acc6e1479923b161eb5356f08aa6cb1f5 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 11 Jul 2021 11:03:48 -1000 Subject: [PATCH] Send ssdp requests to ipv4 broadcast as well (#52760) * Send ssdp requests to 255.255.255.255 as well - This matches pysonos behavior and may fix reports of inability to discover some sonos devices https://github.com/amelchio/pysonos/blob/master/pysonos/discovery.py#L120 * Update homeassistant/components/ssdp/__init__.py --- homeassistant/components/ssdp/__init__.py | 17 ++++++++++++- tests/components/ssdp/test_init.py | 31 +++++++++++++---------- 2 files changed, 34 insertions(+), 14 deletions(-) diff --git a/homeassistant/components/ssdp/__init__.py b/homeassistant/components/ssdp/__init__.py index d03f8967311..9896ec4177e 100644 --- a/homeassistant/components/ssdp/__init__.py +++ b/homeassistant/components/ssdp/__init__.py @@ -29,6 +29,8 @@ from .flow import FlowDispatcher, SSDPFlow DOMAIN = "ssdp" SCAN_INTERVAL = timedelta(seconds=60) +IPV4_BROADCAST = IPv4Address("255.255.255.255") + # Attributes for accessing info from SSDP response ATTR_SSDP_LOCATION = "ssdp_location" ATTR_SSDP_ST = "ssdp_st" @@ -236,7 +238,20 @@ class Scanner: async_callback=self._async_process_entry, source_ip=source_ip ) ) - + try: + IPv4Address(source_ip) + except ValueError: + continue + # Some sonos devices only seem to respond if we send to the broadcast + # address. This matches pysonos' behavior + # https://github.com/amelchio/pysonos/blob/d4329b4abb657d106394ae69357805269708c996/pysonos/discovery.py#L120 + self._ssdp_listeners.append( + SSDPListener( + async_callback=self._async_process_entry, + source_ip=source_ip, + target_ip=IPV4_BROADCAST, + ) + ) self.hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, self.async_stop) self.hass.bus.async_listen_once( EVENT_HOMEASSISTANT_STARTED, self.flow_dispatcher.async_start diff --git a/tests/components/ssdp/test_init.py b/tests/components/ssdp/test_init.py index 6c019f1f311..568a2261fee 100644 --- a/tests/components/ssdp/test_init.py +++ b/tests/components/ssdp/test_init.py @@ -295,15 +295,15 @@ async def test_start_stop_scanner(async_start_mock, async_search_mock, hass): await hass.async_block_till_done() async_fire_time_changed(hass, dt_util.utcnow() + timedelta(seconds=200)) await hass.async_block_till_done() - assert async_start_mock.call_count == 1 - assert async_search_mock.call_count == 1 + assert async_start_mock.call_count == 2 + assert async_search_mock.call_count == 2 hass.bus.async_fire(EVENT_HOMEASSISTANT_STOP) await hass.async_block_till_done() async_fire_time_changed(hass, dt_util.utcnow() + timedelta(seconds=200)) await hass.async_block_till_done() - assert async_start_mock.call_count == 1 - assert async_search_mock.call_count == 1 + assert async_start_mock.call_count == 2 + assert async_search_mock.call_count == 2 async def test_unexpected_exception_while_fetching(hass, aioclient_mock, caplog): @@ -459,11 +459,11 @@ async def test_scan_with_registered_callback(hass, aioclient_mock, caplog): await hass.async_block_till_done() assert hass.state == CoreState.running - assert len(integration_callbacks) == 3 - assert len(integration_callbacks_from_cache) == 3 - assert len(integration_match_all_callbacks) == 3 + assert len(integration_callbacks) == 5 + assert len(integration_callbacks_from_cache) == 5 + assert len(integration_match_all_callbacks) == 5 assert len(integration_match_all_not_present_callbacks) == 0 - assert len(match_any_callbacks) == 3 + assert len(match_any_callbacks) == 5 assert len(not_matching_integration_callbacks) == 0 assert integration_callbacks[0] == { ssdp.ATTR_UPNP_DEVICE_TYPE: "Paulus", @@ -546,7 +546,7 @@ async def test_unsolicited_ssdp_registered_callback(hass, aioclient_mock, caplog assert hass.state == CoreState.running assert ( - len(integration_callbacks) == 2 + len(integration_callbacks) == 4 ) # unsolicited callbacks without st are not cached assert integration_callbacks[0] == { "UDN": "uuid:RINCON_1111BB963FD801400", @@ -635,7 +635,7 @@ async def test_scan_second_hit(hass, aioclient_mock, caplog): async_fire_time_changed(hass, dt_util.utcnow() + timedelta(seconds=200)) await hass.async_block_till_done() - assert len(integration_callbacks) == 2 + assert len(integration_callbacks) == 4 assert integration_callbacks[0] == { ssdp.ATTR_UPNP_DEVICE_TYPE: "Paulus", ssdp.ATTR_SSDP_EXT: "", @@ -781,7 +781,12 @@ async def test_async_detect_interfaces_setting_empty_route(hass): hass.bus.async_fire(EVENT_HOMEASSISTANT_STARTED) await hass.async_block_till_done() - assert {create_args[0][1]["source_ip"], create_args[1][1]["source_ip"]} == { - IPv4Address("192.168.1.5"), - IPv6Address("2001:db8::"), + argset = set() + for argmap in create_args: + argset.add((argmap[1].get("source_ip"), argmap[1].get("target_ip"))) + + assert argset == { + (IPv6Address("2001:db8::"), None), + (IPv4Address("192.168.1.5"), IPv4Address("255.255.255.255")), + (IPv4Address("192.168.1.5"), None), }