1
0
mirror of https://github.com/home-assistant/core.git synced 2026-08-06 13:26:29 +01:00

Fix via_device race in isy994 (#177722)

This commit is contained in:
Erik Montnemery
2026-08-03 07:52:30 +02:00
committed by GitHub
parent 4a28fc938b
commit eccb5cac1d
3 changed files with 62 additions and 13 deletions
+23 -9
View File
@@ -138,12 +138,22 @@ async def async_setup_entry(hass: HomeAssistant, entry: IsyConfigEntry) -> bool:
) from err
isy_data = entry.runtime_data = IsyData()
_categorize_nodes(isy_data, isy.nodes, ignore_identifier, sensor_identifier)
isy_data.root = isy
_async_get_or_create_isy_device_in_registry(hass, entry, isy)
via_device_id = dr.async_get_device_id_by_identifier(
hass, (DOMAIN, isy.uuid), config_entry_id=entry.entry_id
)
_categorize_nodes(
isy_data, isy.nodes, ignore_identifier, sensor_identifier, via_device_id
)
_categorize_programs(isy_data, isy.programs)
# Gather ISY Variables to be added.
if isy.variables.children:
isy_data.devices[CONF_VARIABLES] = _create_service_device_info(
isy, name=CONF_VARIABLES.title(), unique_id=CONF_VARIABLES
isy,
name=CONF_VARIABLES.title(),
unique_id=CONF_VARIABLES,
via_device_id=via_device_id,
)
numbers = isy_data.variables[Platform.NUMBER]
for vtype, _, vid in isy.variables.children:
@@ -152,7 +162,10 @@ async def async_setup_entry(hass: HomeAssistant, entry: IsyConfigEntry) -> bool:
isy.conf[CONFIG_NETWORKING] or isy.conf.get(CONFIG_PORTAL)
) and isy.networking.nobjs:
isy_data.devices[CONF_NETWORK] = _create_service_device_info(
isy, name=CONFIG_NETWORKING, unique_id=CONF_NETWORK
isy,
name=CONFIG_NETWORKING,
unique_id=CONF_NETWORK,
via_device_id=via_device_id,
)
for resource in isy.networking.nobjs:
isy_data.net_resources.append(resource)
@@ -160,9 +173,6 @@ async def async_setup_entry(hass: HomeAssistant, entry: IsyConfigEntry) -> bool:
# Dump ISY Clock Information. Future: Add ISY as sensor to Hass with attrs
LOGGER.debug(repr(isy.clock))
isy_data.root = isy
_async_get_or_create_isy_device_in_registry(hass, entry, isy)
# Load platforms for the devices in the ISY controller that we support.
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
@@ -202,9 +212,11 @@ def _async_get_or_create_isy_device_in_registry(
)
def _create_service_device_info(isy: ISY, name: str, unique_id: str) -> DeviceInfo:
def _create_service_device_info(
isy: ISY, name: str, unique_id: str, via_device_id: str | None
) -> DeviceInfo:
"""Create device info for ISY service devices."""
return DeviceInfo(
device_info = DeviceInfo(
identifiers={
(
DOMAIN,
@@ -216,9 +228,11 @@ def _create_service_device_info(isy: ISY, name: str, unique_id: str) -> DeviceIn
model=isy.conf[ISY_CONF_MODEL],
sw_version=isy.conf[ISY_CONF_FIRMWARE],
configuration_url=isy.conn.url,
via_device=(DOMAIN, isy.uuid),
entry_type=DeviceEntryType.SERVICE,
)
if via_device_id is not None:
device_info["via_device_id"] = via_device_id
return device_info
async def async_unload_entry(hass: HomeAssistant, entry: IsyConfigEntry) -> bool:
+9 -4
View File
@@ -288,17 +288,18 @@ def _add_backlight_if_supported(isy_data: IsyData, node: Node) -> None:
isy_data.aux_properties[Platform.NUMBER].append((node, CMD_BACKLIGHT))
def _generate_device_info(node: Node) -> DeviceInfo:
def _generate_device_info(node: Node, via_device_id: str | None) -> DeviceInfo:
"""Generate the device info for a root node device."""
isy = node.isy
device_info = DeviceInfo(
identifiers={(DOMAIN, f"{isy.uuid}_{node.address}")},
manufacturer=node.protocol.title(),
name=node.name,
via_device=(DOMAIN, isy.uuid),
configuration_url=isy.conn.url,
suggested_area=node.folder,
)
if via_device_id is not None:
device_info["via_device_id"] = via_device_id
# ISYv5 Device Types can provide model and manufacturer
model: str = str(node.address).rpartition(" ")[0] or node.address
@@ -328,7 +329,11 @@ def _generate_device_info(node: Node) -> DeviceInfo:
def _categorize_nodes(
isy_data: IsyData, nodes: Nodes, ignore_identifier: str, sensor_identifier: str
isy_data: IsyData,
nodes: Nodes,
ignore_identifier: str,
sensor_identifier: str,
via_device_id: str | None,
) -> None:
"""Sort the nodes to their proper platforms."""
for path, node in nodes:
@@ -339,7 +344,7 @@ def _categorize_nodes(
if hasattr(node, "parent_node") and node.parent_node is None:
# This is a physical device / parent node
isy_data.devices[node.address] = _generate_device_info(node)
isy_data.devices[node.address] = _generate_device_info(node, via_device_id)
isy_data.root_nodes[Platform.BUTTON].append(node)
# Any parent node can have communication errors:
isy_data.aux_properties[Platform.SENSOR].append((node, PROP_COMMS_ERROR))
+30
View File
@@ -1,5 +1,7 @@
"""Test the Universal Devices ISY/IoX integration init."""
from collections.abc import Callable
from typing import Any
from unittest.mock import MagicMock, patch
import pytest
@@ -8,6 +10,7 @@ from homeassistant.components.isy994.const import DOMAIN
from homeassistant.config_entries import ConfigEntryState
from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_USERNAME, CONF_VERIFY_SSL
from homeassistant.core import HomeAssistant
from homeassistant.helpers import device_registry as dr
from tests.common import MockConfigEntry
@@ -71,3 +74,30 @@ async def test_setup_forwards_verify_ssl_to_pyisy(
assert entry.state is ConfigEntryState.LOADED
assert isy_constructor.call_args.kwargs["verify_ssl"] is verify_ssl
async def test_node_device_linked_to_isy_device(
hass: HomeAssistant,
device_registry: dr.DeviceRegistry,
mock_config_entry: MockConfigEntry,
mock_isy: MagicMock,
mock_node: Callable[..., Any],
) -> None:
"""Test a root node's device is linked to the ISY device via via_device_id."""
mock_config_entry.add_to_hass(hass)
node = mock_node(mock_isy, "22 22 22 1", "Test Node", "GenericNode")
mock_isy.nodes.__iter__.return_value = [("Test Node", node)]
assert await hass.config_entries.async_setup(mock_config_entry.entry_id)
await hass.async_block_till_done()
isy_device = device_registry.async_get_device_by_identifier(
(DOMAIN, mock_isy.uuid), mock_config_entry.entry_id
)
node_device = device_registry.async_get_device_by_identifier(
(DOMAIN, f"{mock_isy.uuid}_{node.address}"), mock_config_entry.entry_id
)
assert isy_device is not None
assert node_device is not None
assert node_device.via_device_id == isy_device.id