Fix Motion Blinds checking interface for multiple gateways (#71474)

* fix checking interface for multiple gateways

* fix black

* setup lock for shared multicast listener

* fix black

* bump motionblinds to 0.6.7

* compensate for extra Lock_key

* unregister gateway when unloading

* unsubscribe stop listener

* fix black

* only unsubscribe listener on last gateway remove

* catch OSError for invalid interfaces

* test coverage

* make stop listen on last config entry more robust

* also check ConfigEntryState

* fix black
This commit is contained in:
starkillerOG 2022-05-19 10:36:22 +02:00 committed by GitHub
parent 1d6e404512
commit 9be7b02613
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 58 additions and 30 deletions

View file

@ -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()

View file

@ -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"

View file

@ -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(

View file

@ -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