From 3962dd13a2b4d825cee8c3f87e101ca423299a2b Mon Sep 17 00:00:00 2001 From: Martin Hjelmare Date: Tue, 1 Sep 2026 11:21:54 +0200 Subject: [PATCH] Add MySensors config entry runtime_data (#180993) --- .../components/mysensors/__init__.py | 95 ++++++------------- .../components/mysensors/binary_sensor.py | 8 +- homeassistant/components/mysensors/climate.py | 8 +- homeassistant/components/mysensors/const.py | 4 - homeassistant/components/mysensors/cover.py | 8 +- .../components/mysensors/device_tracker.py | 8 +- homeassistant/components/mysensors/entity.py | 18 ++-- homeassistant/components/mysensors/gateway.py | 50 ++++------ homeassistant/components/mysensors/handler.py | 52 +++++----- homeassistant/components/mysensors/helpers.py | 50 ++-------- homeassistant/components/mysensors/light.py | 8 +- homeassistant/components/mysensors/models.py | 26 +++++ homeassistant/components/mysensors/remote.py | 8 +- homeassistant/components/mysensors/sensor.py | 18 +--- homeassistant/components/mysensors/switch.py | 8 +- homeassistant/components/mysensors/text.py | 8 +- 16 files changed, 154 insertions(+), 223 deletions(-) create mode 100644 homeassistant/components/mysensors/models.py diff --git a/homeassistant/components/mysensors/__init__.py b/homeassistant/components/mysensors/__init__.py index 811eadaa2fe8..cd7676ac369e 100644 --- a/homeassistant/components/mysensors/__init__.py +++ b/homeassistant/components/mysensors/__init__.py @@ -1,36 +1,23 @@ """Connect to a MySensors gateway via pymysensors API.""" -# pylint: disable=home-assistant-use-runtime-data # Uses legacy hass.data[DOMAIN] pattern -from collections.abc import Callable, Mapping +from collections.abc import Mapping import logging -from mysensors import BaseAsyncGateway - -from homeassistant.config_entries import ConfigEntry from homeassistant.const import Platform from homeassistant.core import HomeAssistant, callback from homeassistant.helpers.device_registry import AnyDeviceEntry, DeviceEntry +from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback -from .const import ( - ATTR_DEVICES, - DOMAIN, - MYSENSORS_DISCOVERED_NODES, - MYSENSORS_GATEWAYS, - PLATFORMS, - DevId, - DiscoveryInfo, - SensorType, -) +from .const import ATTR_DEVICES, DOMAIN, PLATFORMS, DevId, DiscoveryInfo, SensorType from .entity import MySensorsChildEntity from .gateway import finish_setup, gw_stop, setup_gateway -from .helpers import get_discovered_dev_ids, remove_gateway_dev_ids, remove_node_dev_ids +from .helpers import remove_node_dev_ids +from .models import MySensorsConfigEntry, MySensorsData _LOGGER = logging.getLogger(__name__) -DATA_HASS_CONFIG = "hass_config" - -async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: +async def async_setup_entry(hass: HomeAssistant, entry: MySensorsConfigEntry) -> bool: """Set up an instance of the MySensors integration. Every instance has a connection to exactly one Gateway. @@ -41,44 +28,34 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: _LOGGER.error("Gateway setup failed for %s", entry.data) return False - mysensors_data = hass.data.setdefault(DOMAIN, {}) - if MYSENSORS_GATEWAYS not in mysensors_data: - mysensors_data[MYSENSORS_GATEWAYS] = {} - mysensors_data[MYSENSORS_GATEWAYS][entry.entry_id] = gateway + entry.runtime_data = MySensorsData(gateway=gateway) await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS) - await finish_setup(hass, entry, gateway) + await finish_setup(hass, entry) return True -async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: +async def async_unload_entry(hass: HomeAssistant, entry: MySensorsConfigEntry) -> bool: """Remove an instance of the MySensors integration.""" - - gateway: BaseAsyncGateway = hass.data[DOMAIN][MYSENSORS_GATEWAYS][entry.entry_id] - unload_ok = await hass.config_entries.async_unload_platforms(entry, PLATFORMS) if not unload_ok: return False - del hass.data[DOMAIN][MYSENSORS_GATEWAYS][entry.entry_id] - hass.data[DOMAIN].pop(MYSENSORS_DISCOVERED_NODES.format(entry.entry_id), None) - remove_gateway_dev_ids(hass, entry.entry_id) - - await gw_stop(hass, entry, gateway) + await gw_stop(entry) return True async def async_remove_config_entry_device( - hass: HomeAssistant, config_entry: ConfigEntry, device_entry: AnyDeviceEntry + hass: HomeAssistant, + config_entry: MySensorsConfigEntry, + device_entry: AnyDeviceEntry, ) -> bool: """Remove a MySensors config entry from a device.""" if not isinstance(device_entry, DeviceEntry): # This integration does not create child devices. return False - gateway: BaseAsyncGateway = hass.data[DOMAIN][MYSENSORS_GATEWAYS][ - config_entry.entry_id - ] + gateway = config_entry.runtime_data.gateway device_id = next( device_id for domain, device_id in device_entry.identifiers if domain == DOMAIN ) @@ -87,42 +64,26 @@ async def async_remove_config_entry_device( gateway.tasks.persistence.need_save = True # remove node from discovered nodes - hass.data[DOMAIN].setdefault( - MYSENSORS_DISCOVERED_NODES.format(config_entry.entry_id), set() - ).discard(node_id) - remove_node_dev_ids(hass, config_entry.entry_id, node_id) + config_entry.runtime_data.discovered_nodes.discard(node_id) + remove_node_dev_ids(config_entry, node_id) return True @callback def setup_mysensors_platform( - hass: HomeAssistant, + config_entry: MySensorsConfigEntry, domain: Platform, # hass platform name discovery_info: DiscoveryInfo, device_class: type[MySensorsChildEntity] | Mapping[SensorType, type[MySensorsChildEntity]], - device_args: ( - tuple | None - ) = None, # extra arguments that will be given to the entity constructor - async_add_entities: Callable | None = None, -) -> list[MySensorsChildEntity] | None: - """Set up a MySensors platform. - - Sets up a bunch of instances of a single platform that is supported by this - integration. - - The function is given a list of device ids, each one describing an instance - to set up. The function is also given a class. - - A new instance of the class is created for every device id, and the device - id is given to the constructor of the class. - """ - if device_args is None: - device_args = () + async_add_entities: AddConfigEntryEntitiesCallback, +) -> None: + """Set up entities for newly discovered devices on a MySensors platform.""" new_devices: list[MySensorsChildEntity] = [] new_dev_ids: list[DevId] = discovery_info[ATTR_DEVICES] - dev_ids = get_discovered_dev_ids(hass, domain) + dev_ids = config_entry.runtime_data.discovered_dev_ids[domain] + gateway = config_entry.runtime_data.gateway for dev_id in new_dev_ids: if dev_id in dev_ids: _LOGGER.debug( @@ -131,8 +92,7 @@ def setup_mysensors_platform( domain, ) continue - gateway_id, node_id, child_id, value_type = dev_id - gateway: BaseAsyncGateway = hass.data[DOMAIN][MYSENSORS_GATEWAYS][gateway_id] + _gateway_id, node_id, child_id, value_type = dev_id if isinstance(device_class, dict): child = gateway.sensors[node_id].children[child_id] @@ -141,11 +101,10 @@ def setup_mysensors_platform( else: device_class_copy = device_class - args_copy = (*device_args, gateway_id, gateway, node_id, child_id, value_type) dev_ids.add(dev_id) - new_devices.append(device_class_copy(*args_copy)) + new_devices.append( + device_class_copy(config_entry, node_id, child_id, value_type) + ) if new_devices: _LOGGER.debug("Adding new devices: %s", new_devices) - if async_add_entities is not None: - async_add_entities(new_devices) - return new_devices + async_add_entities(new_devices) diff --git a/homeassistant/components/mysensors/binary_sensor.py b/homeassistant/components/mysensors/binary_sensor.py index 1ee62af51ca3..05ceb3270b85 100644 --- a/homeassistant/components/mysensors/binary_sensor.py +++ b/homeassistant/components/mysensors/binary_sensor.py @@ -9,7 +9,6 @@ from homeassistant.components.binary_sensor import ( BinarySensorEntity, BinarySensorEntityDescription, ) -from homeassistant.config_entries import ConfigEntry from homeassistant.const import Platform from homeassistant.core import HomeAssistant, callback from homeassistant.helpers.dispatcher import async_dispatcher_connect @@ -18,6 +17,7 @@ from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback from . import setup_mysensors_platform from .const import MYSENSORS_DISCOVERY, DiscoveryInfo from .entity import MySensorsChildEntity +from .models import MySensorsConfigEntry @dataclass(frozen=True) @@ -67,7 +67,7 @@ SENSORS: dict[str, MySensorsBinarySensorDescription] = { async def async_setup_entry( hass: HomeAssistant, - config_entry: ConfigEntry, + config_entry: MySensorsConfigEntry, async_add_entities: AddConfigEntryEntitiesCallback, ) -> None: """Set up this platform for a specific ConfigEntry(==Gateway).""" @@ -76,11 +76,11 @@ async def async_setup_entry( def async_discover(discovery_info: DiscoveryInfo) -> None: """Discover and add a MySensors binary_sensor.""" setup_mysensors_platform( - hass, + config_entry, Platform.BINARY_SENSOR, discovery_info, MySensorsBinarySensor, - async_add_entities=async_add_entities, + async_add_entities, ) config_entry.async_on_unload( diff --git a/homeassistant/components/mysensors/climate.py b/homeassistant/components/mysensors/climate.py index a5759ef7633d..2014c8b94361 100644 --- a/homeassistant/components/mysensors/climate.py +++ b/homeassistant/components/mysensors/climate.py @@ -9,7 +9,6 @@ from homeassistant.components.climate import ( ClimateEntityFeature, HVACMode, ) -from homeassistant.config_entries import ConfigEntry from homeassistant.const import ATTR_TEMPERATURE, Platform, UnitOfTemperature from homeassistant.core import HomeAssistant, callback from homeassistant.helpers.dispatcher import async_dispatcher_connect @@ -19,6 +18,7 @@ from homeassistant.util.unit_system import METRIC_SYSTEM from . import setup_mysensors_platform from .const import MYSENSORS_DISCOVERY, DiscoveryInfo from .entity import MySensorsChildEntity +from .models import MySensorsConfigEntry DICT_HA_TO_MYS = { HVACMode.AUTO: "AutoChangeOver", @@ -39,7 +39,7 @@ OPERATION_LIST = [HVACMode.OFF, HVACMode.AUTO, HVACMode.COOL, HVACMode.HEAT] async def async_setup_entry( hass: HomeAssistant, - config_entry: ConfigEntry, + config_entry: MySensorsConfigEntry, async_add_entities: AddConfigEntryEntitiesCallback, ) -> None: """Set up this platform for a specific ConfigEntry(==Gateway).""" @@ -47,11 +47,11 @@ async def async_setup_entry( async def async_discover(discovery_info: DiscoveryInfo) -> None: """Discover and add a MySensors climate.""" setup_mysensors_platform( - hass, + config_entry, Platform.CLIMATE, discovery_info, MySensorsHVAC, - async_add_entities=async_add_entities, + async_add_entities, ) config_entry.async_on_unload( diff --git a/homeassistant/components/mysensors/const.py b/homeassistant/components/mysensors/const.py index 33dd43fbce0b..38f5c0a9cc05 100644 --- a/homeassistant/components/mysensors/const.py +++ b/homeassistant/components/mysensors/const.py @@ -23,10 +23,6 @@ CONF_GATEWAY_TYPE_TCP: ConfGatewayType = "TCP" CONF_GATEWAY_TYPE_MQTT: ConfGatewayType = "MQTT" DOMAIN: Final = "mysensors" -MYSENSORS_GATEWAY_START_TASK: str = "mysensors_gateway_start_task_{}" -MYSENSORS_GATEWAYS: Final = "mysensors_gateways" -MYSENSORS_DISCOVERED_NODES: Final = "mysensors_discovered_nodes_{}" -MYSENSORS_DISCOVERED_DEV_IDS: Final = "mysensors_discovered_dev_ids_{}" PLATFORM: Final = "platform" SCHEMA: Final = "schema" CHILD_CALLBACK: str = "mysensors_child_callback_{}_{}_{}_{}" diff --git a/homeassistant/components/mysensors/cover.py b/homeassistant/components/mysensors/cover.py index 2b505d19aba7..e6cc065c09cc 100644 --- a/homeassistant/components/mysensors/cover.py +++ b/homeassistant/components/mysensors/cover.py @@ -8,7 +8,6 @@ from homeassistant.components.cover import ( ATTR_TILT_POSITION, CoverEntity, ) -from homeassistant.config_entries import ConfigEntry from homeassistant.const import STATE_ON, Platform from homeassistant.core import HomeAssistant from homeassistant.helpers.dispatcher import async_dispatcher_connect @@ -17,6 +16,7 @@ from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback from . import setup_mysensors_platform from .const import MYSENSORS_DISCOVERY, DiscoveryInfo from .entity import MySensorsChildEntity +from .models import MySensorsConfigEntry @unique @@ -31,7 +31,7 @@ class CoverState(Enum): async def async_setup_entry( hass: HomeAssistant, - config_entry: ConfigEntry, + config_entry: MySensorsConfigEntry, async_add_entities: AddConfigEntryEntitiesCallback, ) -> None: """Set up this platform for a specific ConfigEntry(==Gateway).""" @@ -39,11 +39,11 @@ async def async_setup_entry( async def async_discover(discovery_info: DiscoveryInfo) -> None: """Discover and add a MySensors cover.""" setup_mysensors_platform( - hass, + config_entry, Platform.COVER, discovery_info, MySensorsCover, - async_add_entities=async_add_entities, + async_add_entities, ) config_entry.async_on_unload( diff --git a/homeassistant/components/mysensors/device_tracker.py b/homeassistant/components/mysensors/device_tracker.py index af2a5a00ca05..f195becbe07b 100644 --- a/homeassistant/components/mysensors/device_tracker.py +++ b/homeassistant/components/mysensors/device_tracker.py @@ -3,7 +3,6 @@ from typing import override from homeassistant.components.device_tracker import TrackerEntity -from homeassistant.config_entries import ConfigEntry from homeassistant.const import Platform from homeassistant.core import HomeAssistant, callback from homeassistant.helpers.dispatcher import async_dispatcher_connect @@ -12,11 +11,12 @@ from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback from . import setup_mysensors_platform from .const import MYSENSORS_DISCOVERY, DiscoveryInfo from .entity import MySensorsChildEntity +from .models import MySensorsConfigEntry async def async_setup_entry( hass: HomeAssistant, - config_entry: ConfigEntry, + config_entry: MySensorsConfigEntry, async_add_entities: AddConfigEntryEntitiesCallback, ) -> None: """Set up this platform for a specific ConfigEntry(==Gateway).""" @@ -25,11 +25,11 @@ async def async_setup_entry( def async_discover(discovery_info: DiscoveryInfo) -> None: """Discover and add a MySensors device tracker.""" setup_mysensors_platform( - hass, + config_entry, Platform.DEVICE_TRACKER, discovery_info, MySensorsDeviceTracker, - async_add_entities=async_add_entities, + async_add_entities, ) config_entry.async_on_unload( diff --git a/homeassistant/components/mysensors/entity.py b/homeassistant/components/mysensors/entity.py index 318810cc5f80..ffa9a57a3f73 100644 --- a/homeassistant/components/mysensors/entity.py +++ b/homeassistant/components/mysensors/entity.py @@ -15,6 +15,7 @@ from homeassistant.helpers.dispatcher import async_dispatcher_connect from homeassistant.helpers.entity import Entity from .const import CHILD_CALLBACK, DOMAIN, NODE_CALLBACK, UPDATE_DELAY, DevId, GatewayId +from .models import MySensorsConfigEntry _LOGGER = logging.getLogger(__name__) @@ -30,12 +31,11 @@ class MySensorNodeEntity(Entity): hass: HomeAssistant - def __init__( - self, gateway_id: GatewayId, gateway: BaseAsyncGateway, node_id: int - ) -> None: + def __init__(self, config_entry: MySensorsConfigEntry, node_id: int) -> None: """Set up the MySensors node entity.""" - self.gateway_id: GatewayId = gateway_id - self.gateway: BaseAsyncGateway = gateway + self.config_entry = config_entry + self.gateway_id: GatewayId = config_entry.entry_id + self.gateway: BaseAsyncGateway = config_entry.runtime_data.gateway self.node_id: int = node_id self._debouncer: Debouncer | None = None @@ -127,14 +127,13 @@ class MySensorsChildEntity(MySensorNodeEntity): def __init__( self, - gateway_id: GatewayId, - gateway: BaseAsyncGateway, + config_entry: MySensorsConfigEntry, node_id: int, child_id: int, value_type: int, ) -> None: """Set up the MySensors child entity.""" - super().__init__(gateway_id, gateway, node_id) + super().__init__(config_entry, node_id) self.child_id: int = child_id # value_type as int. string variant can be looked up in gateway consts self.value_type: int = value_type @@ -181,8 +180,7 @@ class MySensorsChildEntity(MySensorNodeEntity): """Return entity and device specific state attributes.""" attr = super().extra_state_attributes - assert self.platform.config_entry - attr[ATTR_DEVICE] = self.platform.config_entry.data[CONF_DEVICE] + attr[ATTR_DEVICE] = self.config_entry.data[CONF_DEVICE] attr[ATTR_CHILD_ID] = self.child_id attr[ATTR_DESCRIPTION] = self._child.description diff --git a/homeassistant/components/mysensors/gateway.py b/homeassistant/components/mysensors/gateway.py index a6c18f2a2490..466826d75d7b 100644 --- a/homeassistant/components/mysensors/gateway.py +++ b/homeassistant/components/mysensors/gateway.py @@ -17,7 +17,6 @@ from homeassistant.components.mqtt import ( async_publish, async_subscribe, ) -from homeassistant.config_entries import ConfigEntry from homeassistant.const import CONF_DEVICE, EVENT_HOMEASSISTANT_STOP from homeassistant.core import Event, HomeAssistant, callback from homeassistant.helpers import config_validation as cv @@ -36,10 +35,7 @@ from .const import ( CONF_TOPIC_IN_PREFIX, CONF_TOPIC_OUT_PREFIX, CONF_VERSION, - DOMAIN, - MYSENSORS_GATEWAY_START_TASK, ConfGatewayType, - GatewayId, ) from .handler import HANDLERS from .helpers import ( @@ -48,6 +44,7 @@ from .helpers import ( validate_child, validate_node, ) +from .models import MySensorsConfigEntry _LOGGER = logging.getLogger(__name__) @@ -123,7 +120,7 @@ async def try_connect( async def setup_gateway( - hass: HomeAssistant, entry: ConfigEntry + hass: HomeAssistant, entry: MySensorsConfigEntry ) -> BaseAsyncGateway | None: """Set up the Gateway for the given ConfigEntry.""" @@ -132,7 +129,7 @@ async def setup_gateway( gateway_type=entry.data[CONF_GATEWAY_TYPE], device=entry.data[CONF_DEVICE], version=entry.data[CONF_VERSION], - event_callback=_gw_callback_factory(hass, entry.entry_id), + event_callback=_gw_callback_factory(hass, entry), persistence_file=entry.data.get( CONF_PERSISTENCE_FILE, f"mysensors_{entry.entry_id}.json" ), @@ -231,23 +228,22 @@ async def _get_gateway( return gateway -async def finish_setup( - hass: HomeAssistant, entry: ConfigEntry, gateway: BaseAsyncGateway -) -> None: +async def finish_setup(hass: HomeAssistant, entry: MySensorsConfigEntry) -> None: """Load any persistent devices and platforms and start gateway.""" - await _discover_persistent_devices(hass, entry, gateway) - await _gw_start(hass, entry, gateway) + await _discover_persistent_devices(hass, entry) + await _gw_start(hass, entry) async def _discover_persistent_devices( - hass: HomeAssistant, entry: ConfigEntry, gateway: BaseAsyncGateway + hass: HomeAssistant, entry: MySensorsConfigEntry ) -> None: """Discover platforms for devices loaded via persistence file.""" + gateway = entry.runtime_data.gateway new_devices = defaultdict(list) for node_id in gateway.sensors: if not validate_node(gateway, node_id): continue - discover_mysensors_node(hass, entry.entry_id, node_id) + discover_mysensors_node(hass, entry, node_id) node: Sensor = gateway.sensors[node_id] for child in node.children.values(): # child is of type ChildSensor validated = validate_child(entry.entry_id, gateway, node_id, child) @@ -258,22 +254,18 @@ async def _discover_persistent_devices( discover_mysensors_platform(hass, entry.entry_id, platform, dev_ids) -async def gw_stop( - hass: HomeAssistant, entry: ConfigEntry, gateway: BaseAsyncGateway -) -> None: +async def gw_stop(entry: MySensorsConfigEntry) -> None: """Stop the gateway.""" - connect_task = hass.data[DOMAIN].pop( - MYSENSORS_GATEWAY_START_TASK.format(entry.entry_id), None - ) + connect_task = entry.runtime_data.gateway_start_task + entry.runtime_data.gateway_start_task = None if connect_task is not None and not connect_task.done(): connect_task.cancel() - await gateway.stop() + await entry.runtime_data.gateway.stop() -async def _gw_start( - hass: HomeAssistant, entry: ConfigEntry, gateway: BaseAsyncGateway -) -> None: +async def _gw_start(hass: HomeAssistant, entry: MySensorsConfigEntry) -> None: """Start the gateway.""" + gateway = entry.runtime_data.gateway gateway_ready = asyncio.Event() def gateway_connected(_: BaseAsyncGateway) -> None: @@ -282,15 +274,11 @@ async def _gw_start( gateway.on_conn_made = gateway_connected # Don't use hass.async_create_task to avoid holding up setup indefinitely. - # Uses legacy hass.data[DOMAIN] pattern - # pylint: disable-next=home-assistant-use-runtime-data - hass.data[DOMAIN][MYSENSORS_GATEWAY_START_TASK.format(entry.entry_id)] = ( - asyncio.create_task(gateway.start()) - ) # store the connect task so it can be cancelled in gw_stop + entry.runtime_data.gateway_start_task = asyncio.create_task(gateway.start()) async def stop_this_gw(_: Event) -> None: """Stop the gateway.""" - await gw_stop(hass, entry, gateway) + await gw_stop(entry) entry.async_on_unload( hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, stop_this_gw), @@ -311,7 +299,7 @@ async def _gw_start( def _gw_callback_factory( - hass: HomeAssistant, gateway_id: GatewayId + hass: HomeAssistant, entry: MySensorsConfigEntry ) -> Callable[[Message], None]: """Return a new callback for the gateway.""" @@ -330,6 +318,6 @@ def _gw_callback_factory( if msg_handler is None: return - msg_handler(hass, gateway_id, msg) + msg_handler(hass, entry, msg) return mysensors_callback diff --git a/homeassistant/components/mysensors/handler.py b/homeassistant/components/mysensors/handler.py index 25eb7f68647c..83b7b8ed4c20 100644 --- a/homeassistant/components/mysensors/handler.py +++ b/homeassistant/components/mysensors/handler.py @@ -10,84 +10,90 @@ from homeassistant.core import HomeAssistant, callback from homeassistant.helpers.dispatcher import async_dispatcher_send from homeassistant.util import decorator -from .const import CHILD_CALLBACK, NODE_CALLBACK, DevId, GatewayId +from .const import CHILD_CALLBACK, NODE_CALLBACK, DevId from .helpers import ( discover_mysensors_node, discover_mysensors_platform, - get_discovered_dev_ids, validate_set_msg, ) +from .models import MySensorsConfigEntry HANDLERS: decorator.Registry[ - str, Callable[[HomeAssistant, GatewayId, Message], None] + str, Callable[[HomeAssistant, MySensorsConfigEntry, Message], None] ] = decorator.Registry() @HANDLERS.register("set") @callback -def handle_set(hass: HomeAssistant, gateway_id: GatewayId, msg: Message) -> None: +def handle_set(hass: HomeAssistant, entry: MySensorsConfigEntry, msg: Message) -> None: """Handle a mysensors set message.""" - validated = validate_set_msg(gateway_id, msg) - _handle_child_update(hass, gateway_id, validated) + validated = validate_set_msg(entry.entry_id, msg) + _handle_child_update(hass, entry, validated) @HANDLERS.register("internal") @callback -def handle_internal(hass: HomeAssistant, gateway_id: GatewayId, msg: Message) -> None: +def handle_internal( + hass: HomeAssistant, entry: MySensorsConfigEntry, msg: Message +) -> None: """Handle a mysensors internal message.""" internal = msg.gateway.const.Internal(msg.sub_type) if (handler := HANDLERS.get(internal.name)) is None: return - handler(hass, gateway_id, msg) + handler(hass, entry, msg) @HANDLERS.register("I_BATTERY_LEVEL") @callback def handle_battery_level( - hass: HomeAssistant, gateway_id: GatewayId, msg: Message + hass: HomeAssistant, entry: MySensorsConfigEntry, msg: Message ) -> None: """Handle an internal battery level message.""" - _handle_node_update(hass, gateway_id, msg) + _handle_node_update(hass, entry, msg) @HANDLERS.register("I_HEARTBEAT_RESPONSE") @callback -def handle_heartbeat(hass: HomeAssistant, gateway_id: GatewayId, msg: Message) -> None: +def handle_heartbeat( + hass: HomeAssistant, entry: MySensorsConfigEntry, msg: Message +) -> None: """Handle an heartbeat.""" - _handle_node_update(hass, gateway_id, msg) + _handle_node_update(hass, entry, msg) @HANDLERS.register("I_SKETCH_NAME") @callback def handle_sketch_name( - hass: HomeAssistant, gateway_id: GatewayId, msg: Message + hass: HomeAssistant, entry: MySensorsConfigEntry, msg: Message ) -> None: """Handle an internal sketch name message.""" - _handle_node_update(hass, gateway_id, msg) + _handle_node_update(hass, entry, msg) @HANDLERS.register("I_SKETCH_VERSION") @callback def handle_sketch_version( - hass: HomeAssistant, gateway_id: GatewayId, msg: Message + hass: HomeAssistant, entry: MySensorsConfigEntry, msg: Message ) -> None: """Handle an internal sketch version message.""" - _handle_node_update(hass, gateway_id, msg) + _handle_node_update(hass, entry, msg) @HANDLERS.register("presentation") @callback def handle_presentation( - hass: HomeAssistant, gateway_id: GatewayId, msg: Message + hass: HomeAssistant, entry: MySensorsConfigEntry, msg: Message ) -> None: """Handle an internal presentation message.""" if msg.child_id == SYSTEM_CHILD_ID: - discover_mysensors_node(hass, gateway_id, msg.node_id) + discover_mysensors_node(hass, entry, msg.node_id) @callback def _handle_child_update( - hass: HomeAssistant, gateway_id: GatewayId, validated: dict[Platform, list[DevId]] + hass: HomeAssistant, + entry: MySensorsConfigEntry, + validated: dict[Platform, list[DevId]], ) -> None: """Handle a child update.""" signals: list[str] = [] @@ -95,7 +101,7 @@ def _handle_child_update( # Update all platforms for the device via dispatcher. # Add/update entity for validated children. for platform, dev_ids in validated.items(): - discovered_dev_ids = get_discovered_dev_ids(hass, platform) + discovered_dev_ids = entry.runtime_data.discovered_dev_ids[platform] new_dev_ids: list[DevId] = [] for dev_id in dev_ids: if dev_id in discovered_dev_ids: @@ -103,7 +109,7 @@ def _handle_child_update( else: new_dev_ids.append(dev_id) if new_dev_ids: - discover_mysensors_platform(hass, gateway_id, platform, new_dev_ids) + discover_mysensors_platform(hass, entry.entry_id, platform, new_dev_ids) for signal in set(signals): # Only one signal per device is needed. # A device can have multiple platforms, ie multiple schemas. @@ -112,8 +118,8 @@ def _handle_child_update( @callback def _handle_node_update( - hass: HomeAssistant, gateway_id: GatewayId, msg: Message + hass: HomeAssistant, entry: MySensorsConfigEntry, msg: Message ) -> None: """Handle a node update.""" - signal = NODE_CALLBACK.format(gateway_id, msg.node_id) + signal = NODE_CALLBACK.format(entry.entry_id, msg.node_id) async_dispatcher_send(hass, signal) diff --git a/homeassistant/components/mysensors/helpers.py b/homeassistant/components/mysensors/helpers.py index 0b03f33a2a7e..0b739065f021 100644 --- a/homeassistant/components/mysensors/helpers.py +++ b/homeassistant/components/mysensors/helpers.py @@ -22,17 +22,15 @@ from .const import ( ATTR_NODE_ID, DOMAIN, FLAT_PLATFORM_TYPES, - MYSENSORS_DISCOVERED_DEV_IDS, - MYSENSORS_DISCOVERED_NODES, MYSENSORS_DISCOVERY, MYSENSORS_NODE_DISCOVERY, - PLATFORM_TYPES, TYPE_TO_PLATFORMS, DevId, GatewayId, SensorType, ValueType, ) +from .models import MySensorsConfigEntry _LOGGER = logging.getLogger(__name__) SCHEMAS: Registry[ @@ -59,61 +57,29 @@ def discover_mysensors_platform( @callback def discover_mysensors_node( - hass: HomeAssistant, gateway_id: GatewayId, node_id: int + hass: HomeAssistant, entry: MySensorsConfigEntry, node_id: int ) -> None: """Discover a MySensors node.""" - # Uses legacy hass.data[DOMAIN] pattern - # pylint: disable-next=home-assistant-use-runtime-data - discovered_nodes = hass.data[DOMAIN].setdefault( - MYSENSORS_DISCOVERED_NODES.format(gateway_id), set() - ) + discovered_nodes = entry.runtime_data.discovered_nodes if node_id not in discovered_nodes: discovered_nodes.add(node_id) async_dispatcher_send( hass, - MYSENSORS_NODE_DISCOVERY.format(gateway_id), + MYSENSORS_NODE_DISCOVERY.format(entry.entry_id), { - ATTR_GATEWAY_ID: gateway_id, + ATTR_GATEWAY_ID: entry.entry_id, ATTR_NODE_ID: node_id, }, ) @callback -def get_discovered_dev_ids(hass: HomeAssistant, platform: Platform) -> set[DevId]: - """Return the dev ids that have been set up for a hass platform.""" - # Uses legacy hass.data[DOMAIN] pattern - # pylint: disable-next=home-assistant-use-runtime-data - dev_ids = hass.data[DOMAIN].setdefault( - MYSENSORS_DISCOVERED_DEV_IDS.format(platform), set() - ) - return cast(set[DevId], dev_ids) - - -@callback -def remove_gateway_dev_ids(hass: HomeAssistant, gateway_id: GatewayId) -> None: - """Remove all discovered dev ids belonging to a gateway.""" - for platform in PLATFORM_TYPES: - dev_ids = get_discovered_dev_ids(hass, platform) - dev_ids.difference_update( - {dev_id for dev_id in dev_ids if dev_id[0] == gateway_id} - ) - - -@callback -def remove_node_dev_ids( - hass: HomeAssistant, gateway_id: GatewayId, node_id: int -) -> None: +def remove_node_dev_ids(entry: MySensorsConfigEntry, node_id: int) -> None: """Remove all discovered dev ids belonging to a node.""" - for platform in PLATFORM_TYPES: - dev_ids = get_discovered_dev_ids(hass, platform) + for dev_ids in entry.runtime_data.discovered_dev_ids.values(): dev_ids.difference_update( - { - dev_id - for dev_id in dev_ids - if dev_id[0] == gateway_id and dev_id[1] == node_id - } + {dev_id for dev_id in dev_ids if dev_id[1] == node_id} ) diff --git a/homeassistant/components/mysensors/light.py b/homeassistant/components/mysensors/light.py index d41d1355ef96..dbcebde792c8 100644 --- a/homeassistant/components/mysensors/light.py +++ b/homeassistant/components/mysensors/light.py @@ -9,7 +9,6 @@ from homeassistant.components.light import ( ColorMode, LightEntity, ) -from homeassistant.config_entries import ConfigEntry from homeassistant.const import STATE_ON, Platform from homeassistant.core import HomeAssistant, callback from homeassistant.helpers.dispatcher import async_dispatcher_connect @@ -19,11 +18,12 @@ from homeassistant.util.color import rgb_hex_to_rgb_list from . import setup_mysensors_platform from .const import MYSENSORS_DISCOVERY, DiscoveryInfo, SensorType from .entity import MySensorsChildEntity +from .models import MySensorsConfigEntry async def async_setup_entry( hass: HomeAssistant, - config_entry: ConfigEntry, + config_entry: MySensorsConfigEntry, async_add_entities: AddConfigEntryEntitiesCallback, ) -> None: """Set up this platform for a specific ConfigEntry(==Gateway).""" @@ -36,11 +36,11 @@ async def async_setup_entry( async def async_discover(discovery_info: DiscoveryInfo) -> None: """Discover and add a MySensors light.""" setup_mysensors_platform( - hass, + config_entry, Platform.LIGHT, discovery_info, device_class_map, - async_add_entities=async_add_entities, + async_add_entities, ) config_entry.async_on_unload( diff --git a/homeassistant/components/mysensors/models.py b/homeassistant/components/mysensors/models.py new file mode 100644 index 000000000000..bbde2a669595 --- /dev/null +++ b/homeassistant/components/mysensors/models.py @@ -0,0 +1,26 @@ +"""Models for the MySensors integration.""" + +from asyncio import Task +from collections import defaultdict +from dataclasses import dataclass, field + +from mysensors import BaseAsyncGateway + +from homeassistant.config_entries import ConfigEntry +from homeassistant.const import Platform + +from .const import DevId + +type MySensorsConfigEntry = ConfigEntry[MySensorsData] + + +@dataclass +class MySensorsData: + """Runtime data for a MySensors gateway.""" + + gateway: BaseAsyncGateway + discovered_nodes: set[int] = field(default_factory=set) + discovered_dev_ids: defaultdict[Platform, set[DevId]] = field( + default_factory=lambda: defaultdict(set) + ) + gateway_start_task: Task[None] | None = None diff --git a/homeassistant/components/mysensors/remote.py b/homeassistant/components/mysensors/remote.py index 2992dee79e41..0a27f75b3679 100644 --- a/homeassistant/components/mysensors/remote.py +++ b/homeassistant/components/mysensors/remote.py @@ -8,7 +8,6 @@ from homeassistant.components.remote import ( RemoteEntity, RemoteEntityFeature, ) -from homeassistant.config_entries import ConfigEntry from homeassistant.const import Platform from homeassistant.core import HomeAssistant, callback from homeassistant.helpers.dispatcher import async_dispatcher_connect @@ -17,11 +16,12 @@ from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback from . import setup_mysensors_platform from .const import MYSENSORS_DISCOVERY, DiscoveryInfo from .entity import MySensorsChildEntity +from .models import MySensorsConfigEntry async def async_setup_entry( hass: HomeAssistant, - config_entry: ConfigEntry, + config_entry: MySensorsConfigEntry, async_add_entities: AddConfigEntryEntitiesCallback, ) -> None: """Set up this platform for a specific ConfigEntry(==Gateway).""" @@ -30,11 +30,11 @@ async def async_setup_entry( def async_discover(discovery_info: DiscoveryInfo) -> None: """Discover and add a MySensors remote.""" setup_mysensors_platform( - hass, + config_entry, Platform.REMOTE, discovery_info, MySensorsRemote, - async_add_entities=async_add_entities, + async_add_entities, ) config_entry.async_on_unload( diff --git a/homeassistant/components/mysensors/sensor.py b/homeassistant/components/mysensors/sensor.py index 9305cfbaa1db..d390d0b37f25 100644 --- a/homeassistant/components/mysensors/sensor.py +++ b/homeassistant/components/mysensors/sensor.py @@ -3,7 +3,6 @@ from typing import Any, override from awesomeversion import AwesomeVersion -from mysensors import BaseAsyncGateway from homeassistant.components.sensor import ( SensorDeviceClass, @@ -11,7 +10,6 @@ from homeassistant.components.sensor import ( SensorEntityDescription, SensorStateClass, ) -from homeassistant.config_entries import ConfigEntry from homeassistant.const import ( DEGREE, LIGHT_LUX, @@ -38,16 +36,14 @@ from homeassistant.util.unit_system import METRIC_SYSTEM from . import setup_mysensors_platform from .const import ( - ATTR_GATEWAY_ID, ATTR_NODE_ID, - DOMAIN, MYSENSORS_DISCOVERY, - MYSENSORS_GATEWAYS, MYSENSORS_NODE_DISCOVERY, DiscoveryInfo, NodeDiscoveryInfo, ) from .entity import MySensorNodeEntity, MySensorsChildEntity +from .models import MySensorsConfigEntry SENSORS: dict[str, SensorEntityDescription] = { "V_TEMP": SensorEntityDescription( @@ -208,7 +204,7 @@ SENSORS: dict[str, SensorEntityDescription] = { async def async_setup_entry( hass: HomeAssistant, - config_entry: ConfigEntry, + config_entry: MySensorsConfigEntry, async_add_entities: AddConfigEntryEntitiesCallback, ) -> None: """Set up this platform for a specific ConfigEntry(==Gateway).""" @@ -216,22 +212,18 @@ async def async_setup_entry( async def async_discover(discovery_info: DiscoveryInfo) -> None: """Discover and add a MySensors sensor.""" setup_mysensors_platform( - hass, + config_entry, Platform.SENSOR, discovery_info, MySensorsSensor, - async_add_entities=async_add_entities, + async_add_entities, ) @callback def async_node_discover(discovery_info: NodeDiscoveryInfo) -> None: """Add battery sensor for each MySensors node.""" - gateway_id = discovery_info[ATTR_GATEWAY_ID] node_id = discovery_info[ATTR_NODE_ID] - # Uses legacy hass.data[DOMAIN] pattern - # pylint: disable-next=home-assistant-use-runtime-data - gateway: BaseAsyncGateway = hass.data[DOMAIN][MYSENSORS_GATEWAYS][gateway_id] - async_add_entities([MyBatterySensor(gateway_id, gateway, node_id)]) + async_add_entities([MyBatterySensor(config_entry, node_id)]) config_entry.async_on_unload( async_dispatcher_connect( diff --git a/homeassistant/components/mysensors/switch.py b/homeassistant/components/mysensors/switch.py index bdbde3a230ba..c1aa0b547fd7 100644 --- a/homeassistant/components/mysensors/switch.py +++ b/homeassistant/components/mysensors/switch.py @@ -3,7 +3,6 @@ from typing import Any, override from homeassistant.components.switch import SwitchEntity -from homeassistant.config_entries import ConfigEntry from homeassistant.const import STATE_ON, Platform from homeassistant.core import HomeAssistant from homeassistant.helpers.dispatcher import async_dispatcher_connect @@ -12,11 +11,12 @@ from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback from . import setup_mysensors_platform from .const import MYSENSORS_DISCOVERY, DiscoveryInfo, SensorType from .entity import MySensorsChildEntity +from .models import MySensorsConfigEntry async def async_setup_entry( hass: HomeAssistant, - config_entry: ConfigEntry, + config_entry: MySensorsConfigEntry, async_add_entities: AddConfigEntryEntitiesCallback, ) -> None: """Set up this platform for a specific ConfigEntry(==Gateway).""" @@ -38,11 +38,11 @@ async def async_setup_entry( async def async_discover(discovery_info: DiscoveryInfo) -> None: """Discover and add a MySensors switch.""" setup_mysensors_platform( - hass, + config_entry, Platform.SWITCH, discovery_info, device_class_map, - async_add_entities=async_add_entities, + async_add_entities, ) config_entry.async_on_unload( diff --git a/homeassistant/components/mysensors/text.py b/homeassistant/components/mysensors/text.py index c5abb2991b07..f88647b0ce92 100644 --- a/homeassistant/components/mysensors/text.py +++ b/homeassistant/components/mysensors/text.py @@ -3,7 +3,6 @@ from typing import override from homeassistant.components.text import TextEntity -from homeassistant.config_entries import ConfigEntry from homeassistant.const import Platform from homeassistant.core import HomeAssistant, callback from homeassistant.helpers.dispatcher import async_dispatcher_connect @@ -12,11 +11,12 @@ from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback from . import setup_mysensors_platform from .const import MYSENSORS_DISCOVERY, DiscoveryInfo from .entity import MySensorsChildEntity +from .models import MySensorsConfigEntry async def async_setup_entry( hass: HomeAssistant, - config_entry: ConfigEntry, + config_entry: MySensorsConfigEntry, async_add_entities: AddConfigEntryEntitiesCallback, ) -> None: """Set up this platform for a specific ConfigEntry(==Gateway).""" @@ -25,11 +25,11 @@ async def async_setup_entry( def async_discover(discovery_info: DiscoveryInfo) -> None: """Discover and add a MySensors text entity.""" setup_mysensors_platform( - hass, + config_entry, Platform.TEXT, discovery_info, MySensorsText, - async_add_entities=async_add_entities, + async_add_entities, ) config_entry.async_on_unload(