Add MySensors config entry runtime_data (#180993)

This commit is contained in:
Martin Hjelmare
2026-09-01 11:21:54 +02:00
committed by GitHub
parent b78775e343
commit 3962dd13a2
16 changed files with 154 additions and 223 deletions
+27 -68
View File
@@ -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)
@@ -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(
@@ -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(
@@ -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_{}_{}_{}_{}"
+4 -4
View File
@@ -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(
@@ -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(
+8 -10
View File
@@ -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
+19 -31
View File
@@ -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
+29 -23
View File
@@ -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)
+8 -42
View File
@@ -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}
)
+4 -4
View File
@@ -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(
@@ -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
+4 -4
View File
@@ -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(
+5 -13
View File
@@ -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(
+4 -4
View File
@@ -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(
+4 -4
View File
@@ -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(