From afbd203c004da0813ae2fd9c505a142e3b81c4bb Mon Sep 17 00:00:00 2001 From: "Yanze (David) Wu" <133224895+David-Wu1119@users.noreply.github.com> Date: Mon, 14 Sep 2026 23:43:49 -0700 Subject: [PATCH] Type the motion_blinds multicast listener with a HassKey (#182233) Co-authored-by: Claude Opus 5 --- .../components/motion_blinds/__init__.py | 55 ++++++++++++------- .../components/motion_blinds/const.py | 3 - 2 files changed, 34 insertions(+), 24 deletions(-) diff --git a/homeassistant/components/motion_blinds/__init__.py b/homeassistant/components/motion_blinds/__init__.py index aeb96c5a2659..df2008739500 100644 --- a/homeassistant/components/motion_blinds/__init__.py +++ b/homeassistant/components/motion_blinds/__init__.py @@ -1,24 +1,22 @@ """The motion_blinds component.""" -# pylint: disable=home-assistant-use-runtime-data # Uses legacy hass.data[DOMAIN] pattern import asyncio +from dataclasses import dataclass, field import logging from motionblinds import DEVICE_TYPES_GATEWAY, DEVICE_TYPES_WIFI, AsyncMotionMulticast from homeassistant.const import CONF_API_KEY, CONF_HOST, EVENT_HOMEASSISTANT_STOP -from homeassistant.core import HomeAssistant +from homeassistant.core import CALLBACK_TYPE, HomeAssistant from homeassistant.exceptions import ConfigEntryNotReady from homeassistant.helpers import device_registry as dr +from homeassistant.util.hass_dict import HassKey from .const import ( CONF_BLIND_TYPE_LIST, CONF_INTERFACE, DEFAULT_INTERFACE, DOMAIN, - KEY_MULTICAST_LISTENER, - KEY_SETUP_LOCK, - KEY_UNSUB_STOP, PLATFORMS, ) from .coordinator import DataUpdateCoordinatorMotionBlinds, MotionBlindsConfigEntry @@ -28,20 +26,34 @@ from .gateway import ConnectMotionGateway _LOGGER = logging.getLogger(__name__) +@dataclass +class MotionBlindsData: + """Multicast listener shared by every gateway.""" + + setup_lock: asyncio.Lock = field(default_factory=asyncio.Lock) + multicast: AsyncMotionMulticast | None = None + unsub_stop: CALLBACK_TYPE | None = None + + +# One multicast listener serves every gateway, so it is shared between config +# entries rather than owned by any one of them. +MOTION_BLINDS_DATA: HassKey[MotionBlindsData] = HassKey(DOMAIN) + + async def async_setup_entry( hass: HomeAssistant, entry: MotionBlindsConfigEntry ) -> 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()) + if (motion_data := hass.data.get(MOTION_BLINDS_DATA)) is None: + motion_data = hass.data[MOTION_BLINDS_DATA] = MotionBlindsData() host = entry.data[CONF_HOST] key = entry.data[CONF_API_KEY] multicast_interface = entry.data.get(CONF_INTERFACE, DEFAULT_INTERFACE) blind_type_list = entry.data.get(CONF_BLIND_TYPE_LIST) # Create multicast Listener - async with setup_lock: - if KEY_MULTICAST_LISTENER not in hass.data[DOMAIN]: + async with motion_data.setup_lock: + if (multicast := motion_data.multicast) is None: # check multicast interface check_multicast_class = ConnectMotionGateway( hass, interface=multicast_interface @@ -61,8 +73,9 @@ async def async_setup_entry( working_interface, ) - multicast = AsyncMotionMulticast(interface=working_interface) - hass.data[DOMAIN][KEY_MULTICAST_LISTENER] = multicast + multicast = motion_data.multicast = AsyncMotionMulticast( + interface=working_interface + ) # start listening for local pushes (only once) await multicast.Start_listen() @@ -72,13 +85,11 @@ async def async_setup_entry( _LOGGER.debug("Shutting down Motion Listener") multicast.Stop_listen() - unsub = hass.bus.async_listen_once( + motion_data.unsub_stop = 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] connect_gateway_class = ConnectMotionGateway(hass, multicast) if not await connect_gateway_class.async_connect_gateway( host, key, blind_type_list @@ -130,16 +141,18 @@ async def async_unload_entry( config_entry, PLATFORMS ) - if unload_ok: - multicast = hass.data[DOMAIN][KEY_MULTICAST_LISTENER] - multicast.Unregister_motion_gateway(config_entry.data[CONF_HOST]) + motion_data = hass.data[MOTION_BLINDS_DATA] + if unload_ok and motion_data.multicast is not None: + motion_data.multicast.Unregister_motion_gateway(config_entry.data[CONF_HOST]) if not hass.config_entries.async_loaded_entries(DOMAIN): # No motion gateways left, stop Motion multicast - unsub_stop = hass.data[DOMAIN].pop(KEY_UNSUB_STOP) - unsub_stop() + if motion_data.unsub_stop is not None: + motion_data.unsub_stop() + motion_data.unsub_stop = None _LOGGER.debug("Shutting down Motion Listener") - multicast = hass.data[DOMAIN].pop(KEY_MULTICAST_LISTENER) - multicast.Stop_listen() + if motion_data.multicast is not None: + motion_data.multicast.Stop_listen() + motion_data.multicast = None return unload_ok diff --git a/homeassistant/components/motion_blinds/const.py b/homeassistant/components/motion_blinds/const.py index e95a389c8c9d..79ca8e7776b7 100644 --- a/homeassistant/components/motion_blinds/const.py +++ b/homeassistant/components/motion_blinds/const.py @@ -16,9 +16,6 @@ DEFAULT_INTERFACE = "any" KEY_GATEWAY = "gateway" KEY_API_LOCK = "api_lock" -KEY_MULTICAST_LISTENER = "multicast_listener" -KEY_SETUP_LOCK = "setup_lock" -KEY_UNSUB_STOP = "unsub_stop" ATTR_WIDTH = "width" ATTR_ABSOLUTE_POSITION = "absolute_position"