diff --git a/homeassistant/components/wemo/__init__.py b/homeassistant/components/wemo/__init__.py index df737f101ba..db380ae11ca 100644 --- a/homeassistant/components/wemo/__init__.py +++ b/homeassistant/components/wemo/__init__.py @@ -1,4 +1,5 @@ """Support for WeMo device discovery.""" +import asyncio import logging import pywemo @@ -15,14 +16,9 @@ from homeassistant.core import HomeAssistant, callback from homeassistant.helpers import config_validation as cv from homeassistant.helpers.dispatcher import async_dispatcher_send from homeassistant.helpers.event import async_call_later -from homeassistant.util.async_ import gather_with_concurrency from .const import DOMAIN -# Max number of devices to initialize at once. This limit is in place to -# avoid tying up too many executor threads with WeMo device setup. -MAX_CONCURRENCY = 3 - # Mapping from Wemo model_name to domain. WEMO_MODEL_DISPATCH = { "Bridge": LIGHT_DOMAIN, @@ -118,12 +114,11 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): static_conf = config.get(CONF_STATIC, []) if static_conf: _LOGGER.debug("Adding statically configured WeMo devices...") - for device in await gather_with_concurrency( - MAX_CONCURRENCY, + for device in await asyncio.gather( *[ hass.async_add_executor_job(validate_static_config, host, port) for host, port in static_conf - ], + ] ): if device: wemo_dispatcher.async_add_unique_device(hass, device) @@ -192,44 +187,15 @@ class WemoDiscovery: self._wemo_dispatcher = wemo_dispatcher self._stop = None self._scan_delay = 0 - self._upnp_entries = set() - - async def async_add_from_upnp_entry(self, entry: pywemo.ssdp.UPNPEntry) -> None: - """Create a WeMoDevice from an UPNPEntry and add it to the dispatcher. - - Uses the self._upnp_entries set to avoid interrogating the same device - multiple times. - """ - if entry in self._upnp_entries: - return - try: - device = await self._hass.async_add_executor_job( - pywemo.discovery.device_from_uuid_and_location, - entry.udn, - entry.location, - ) - except pywemo.PyWeMoException as err: - _LOGGER.error("Unable to setup WeMo %r (%s)", entry, err) - else: - self._wemo_dispatcher.async_add_unique_device(self._hass, device) - self._upnp_entries.add(entry) async def async_discover_and_schedule(self, *_) -> None: """Periodically scan the network looking for WeMo devices.""" _LOGGER.debug("Scanning network for WeMo devices...") try: - # pywemo.ssdp.scan is a light-weight UDP UPnP scan for WeMo devices. - entries = await self._hass.async_add_executor_job(pywemo.ssdp.scan) - - # async_add_from_upnp_entry causes multiple HTTP requests to be sent - # to the WeMo device for the initial setup of the WeMoDevice - # instance. This may take some time to complete. The per-device - # setup work is done in parallel to speed up initial setup for the - # component. - await gather_with_concurrency( - MAX_CONCURRENCY, - *[self.async_add_from_upnp_entry(entry) for entry in entries], - ) + for device in await self._hass.async_add_executor_job( + pywemo.discover_devices + ): + self._wemo_dispatcher.async_add_unique_device(self._hass, device) finally: # Run discovery more frequently after hass has just started. self._scan_delay = min( diff --git a/tests/components/wemo/test_init.py b/tests/components/wemo/test_init.py index 7c2b43dfd8c..374222d8688 100644 --- a/tests/components/wemo/test_init.py +++ b/tests/components/wemo/test_init.py @@ -100,41 +100,28 @@ async def test_static_config_with_invalid_host(hass): async def test_discovery(hass, pywemo_registry): """Verify that discovery dispatches devices to the platform for setup.""" - def create_device(uuid, location): + def create_device(counter): """Create a unique mock Motion detector device for each counter value.""" device = create_autospec(pywemo.Motion, instance=True) - device.host = location - device.port = MOCK_PORT - device.name = f"{MOCK_NAME}_{uuid}" - device.serialnumber = f"{MOCK_SERIAL_NUMBER}_{uuid}" + device.host = f"{MOCK_HOST}_{counter}" + device.port = MOCK_PORT + counter + device.name = f"{MOCK_NAME}_{counter}" + device.serialnumber = f"{MOCK_SERIAL_NUMBER}_{counter}" device.model_name = "Motion" device.get_state.return_value = 0 # Default to Off return device - def create_upnp_entry(counter): - return pywemo.ssdp.UPNPEntry.from_response( - "\r\n".join( - [ - "", - f"LOCATION: http://192.168.1.100:{counter}/setup.xml", - f"USN: uuid:Socket-1_0-SERIAL{counter}::upnp:rootdevice", - "", - ] - ) - ) - - upnp_entries = [create_upnp_entry(0), create_upnp_entry(1)] + pywemo_devices = [create_device(0), create_device(1)] # Setup the component and start discovery. with patch( - "pywemo.discovery.device_from_uuid_and_location", side_effect=create_device - ), patch("pywemo.ssdp.scan", return_value=upnp_entries) as mock_scan: + "pywemo.discover_devices", return_value=pywemo_devices + ) as mock_discovery: assert await async_setup_component( hass, DOMAIN, {DOMAIN: {CONF_DISCOVERY: True}} ) await pywemo_registry.semaphore.acquire() # Returns after platform setup. - mock_scan.assert_called() - # Add two of the same entries to test deduplication. - upnp_entries.extend([create_upnp_entry(2), create_upnp_entry(2)]) + mock_discovery.assert_called() + pywemo_devices.append(create_device(2)) # Test that discovery runs periodically and the async_dispatcher_send code works. async_fire_time_changed(