diff --git a/homeassistant/components/motion_blinds/__init__.py b/homeassistant/components/motion_blinds/__init__.py index 63c80d33dba..95ac1c5fd44 100644 --- a/homeassistant/components/motion_blinds/__init__.py +++ b/homeassistant/components/motion_blinds/__init__.py @@ -7,7 +7,7 @@ from typing import TYPE_CHECKING from motionblinds import DEVICE_TYPES_WIFI, AsyncMotionMulticast, ParseException -from homeassistant.config_entries import ConfigEntry +from homeassistant.config_entries import ConfigEntry, ConfigEntryState from homeassistant.const import CONF_API_KEY, CONF_HOST, EVENT_HOMEASSISTANT_STOP from homeassistant.core import HomeAssistant from homeassistant.exceptions import ConfigEntryNotReady @@ -25,6 +25,8 @@ from .const import ( KEY_COORDINATOR, KEY_GATEWAY, KEY_MULTICAST_LISTENER, + KEY_SETUP_LOCK, + KEY_UNSUB_STOP, KEY_VERSION, MANUFACTURER, PLATFORMS, @@ -106,6 +108,7 @@ class DataUpdateCoordinatorMotionBlinds(DataUpdateCoordinator): async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: """Set up the motion_blinds components from a config entry.""" hass.data.setdefault(DOMAIN, {}) + setup_lock = hass.data[DOMAIN].setdefault(KEY_SETUP_LOCK, asyncio.Lock()) host = entry.data[CONF_HOST] key = entry.data[CONF_API_KEY] multicast_interface = entry.data.get(CONF_INTERFACE, DEFAULT_INTERFACE) @@ -113,33 +116,41 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: entry.async_on_unload(entry.add_update_listener(update_listener)) - # check multicast interface - check_multicast_class = ConnectMotionGateway(hass, interface=multicast_interface) - working_interface = await check_multicast_class.async_check_interface(host, key) - if working_interface != multicast_interface: - data = {**entry.data, CONF_INTERFACE: working_interface} - hass.config_entries.async_update_entry(entry, data=data) - _LOGGER.debug( - "Motion Blinds interface updated from %s to %s, " - "this should only occur after a network change", - multicast_interface, - working_interface, - ) - # Create multicast Listener - if KEY_MULTICAST_LISTENER not in hass.data[DOMAIN]: - multicast = AsyncMotionMulticast(interface=working_interface) - hass.data[DOMAIN][KEY_MULTICAST_LISTENER] = multicast - # start listening for local pushes (only once) - await multicast.Start_listen() + async with setup_lock: + if KEY_MULTICAST_LISTENER not in hass.data[DOMAIN]: + # check multicast interface + check_multicast_class = ConnectMotionGateway( + hass, interface=multicast_interface + ) + working_interface = await check_multicast_class.async_check_interface( + host, key + ) + if working_interface != multicast_interface: + data = {**entry.data, CONF_INTERFACE: working_interface} + hass.config_entries.async_update_entry(entry, data=data) + _LOGGER.debug( + "Motion Blinds interface updated from %s to %s, " + "this should only occur after a network change", + multicast_interface, + working_interface, + ) - # register stop callback to shutdown listening for local pushes - def stop_motion_multicast(event): - """Stop multicast thread.""" - _LOGGER.debug("Shutting down Motion Listener") - multicast.Stop_listen() + multicast = AsyncMotionMulticast(interface=working_interface) + hass.data[DOMAIN][KEY_MULTICAST_LISTENER] = multicast + # start listening for local pushes (only once) + await multicast.Start_listen() - hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, stop_motion_multicast) + # register stop callback to shutdown listening for local pushes + def stop_motion_multicast(event): + """Stop multicast thread.""" + _LOGGER.debug("Shutting down Motion Listener") + multicast.Stop_listen() + + unsub = hass.bus.async_listen_once( + EVENT_HOMEASSISTANT_STOP, stop_motion_multicast + ) + hass.data[DOMAIN][KEY_UNSUB_STOP] = unsub # Connect to motion gateway multicast = hass.data[DOMAIN][KEY_MULTICAST_LISTENER] @@ -205,10 +216,19 @@ async def async_unload_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> ) if unload_ok: + multicast = hass.data[DOMAIN][KEY_MULTICAST_LISTENER] + multicast.Unregister_motion_gateway(config_entry.data[CONF_HOST]) hass.data[DOMAIN].pop(config_entry.entry_id) - if len(hass.data[DOMAIN]) == 1: + loaded_entries = [ + entry + for entry in hass.config_entries.async_entries(DOMAIN) + if entry.state == ConfigEntryState.LOADED + ] + if len(loaded_entries) == 1: # No motion gateways left, stop Motion multicast + unsub_stop = hass.data[DOMAIN].pop(KEY_UNSUB_STOP) + unsub_stop() _LOGGER.debug("Shutting down Motion Listener") multicast = hass.data[DOMAIN].pop(KEY_MULTICAST_LISTENER) multicast.Stop_listen() diff --git a/homeassistant/components/motion_blinds/const.py b/homeassistant/components/motion_blinds/const.py index a35aeb6cd89..332a30a5e5f 100644 --- a/homeassistant/components/motion_blinds/const.py +++ b/homeassistant/components/motion_blinds/const.py @@ -16,6 +16,8 @@ KEY_GATEWAY = "gateway" KEY_API_LOCK = "api_lock" KEY_COORDINATOR = "coordinator" KEY_MULTICAST_LISTENER = "multicast_listener" +KEY_SETUP_LOCK = "setup_lock" +KEY_UNSUB_STOP = "unsub_stop" KEY_VERSION = "version" ATTR_WIDTH = "width" diff --git a/homeassistant/components/motion_blinds/gateway.py b/homeassistant/components/motion_blinds/gateway.py index 1775f7deb7d..218da9f625c 100644 --- a/homeassistant/components/motion_blinds/gateway.py +++ b/homeassistant/components/motion_blinds/gateway.py @@ -101,6 +101,8 @@ class ConnectMotionGateway: await check_multicast.Start_listen() except socket.gaierror: continue + except OSError: + continue # trigger test multicast self._gateway_device = MotionGateway( diff --git a/tests/components/motion_blinds/test_config_flow.py b/tests/components/motion_blinds/test_config_flow.py index 57ad3c20779..57ab45d9dbb 100644 --- a/tests/components/motion_blinds/test_config_flow.py +++ b/tests/components/motion_blinds/test_config_flow.py @@ -336,10 +336,14 @@ async def test_dhcp_flow(hass): assert result["step_id"] == "connect" assert result["errors"] == {} - result = await hass.config_entries.flow.async_configure( - result["flow_id"], - {CONF_API_KEY: TEST_API_KEY}, - ) + with patch( + "homeassistant.components.motion_blinds.gateway.AsyncMotionMulticast.Start_listen", + side_effect=OSError, + ): + result = await hass.config_entries.flow.async_configure( + result["flow_id"], + {CONF_API_KEY: TEST_API_KEY}, + ) assert result["type"] == "create_entry" assert result["title"] == DEFAULT_GATEWAY_NAME