mirror of
https://github.com/home-assistant/core.git
synced 2026-08-03 20:24:55 +02:00
Fix via_device race in victron_gx
This commit is contained in:
@@ -28,7 +28,7 @@ from homeassistant.const import (
|
||||
)
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.exceptions import ConfigEntryAuthFailed, ConfigEntryNotReady
|
||||
from homeassistant.helpers.device_registry import DeviceInfo
|
||||
from homeassistant.helpers import device_registry as dr
|
||||
from homeassistant.helpers.redact import async_redact_data
|
||||
|
||||
from .const import CONF_INSTALLATION_ID, CONF_SERIAL, DOMAIN
|
||||
@@ -40,7 +40,7 @@ TO_REDACT = {CONF_USERNAME, CONF_PASSWORD}
|
||||
type VictronGxConfigEntry = ConfigEntry[Hub]
|
||||
|
||||
type NewMetricCallback = Callable[
|
||||
[VictronVenusDevice, VictronVenusMetric, DeviceInfo, str], None
|
||||
[VictronVenusDevice, VictronVenusMetric, dr.DeviceInfo, str], None
|
||||
]
|
||||
|
||||
|
||||
@@ -64,6 +64,8 @@ class Hub:
|
||||
config = {**entry.data, **entry.options}
|
||||
self.hass = hass
|
||||
self.host = config[CONF_HOST]
|
||||
self._config_entry_id = entry.entry_id
|
||||
self._device_registry = dr.async_get(hass)
|
||||
|
||||
self._hub = VictronVenusHub(
|
||||
host=self.host,
|
||||
@@ -120,15 +122,39 @@ class Hub:
|
||||
if TYPE_CHECKING:
|
||||
assert hub.installation_id is not None
|
||||
device_info = Hub._map_device_info(device, hub.installation_id)
|
||||
if device.parent_device is not None:
|
||||
device_info["via_device_id"] = self._ensure_device_registered(
|
||||
device.parent_device, hub.installation_id
|
||||
)
|
||||
callback = self.new_metric_callbacks.get(metric.metric_kind)
|
||||
if callback is not None:
|
||||
callback(device, metric, device_info, hub.installation_id)
|
||||
|
||||
def _ensure_device_registered(
|
||||
self, device: VictronVenusDevice, installation_id: str
|
||||
) -> str:
|
||||
"""Register a device and its ancestors (parents first), returning its id.
|
||||
|
||||
Devices are discovered lazily as their metrics arrive, so a child's parent
|
||||
may not be in the registry yet; registering the ancestor chain here lets
|
||||
children link to it via via_device_id instead of the deprecated via_device.
|
||||
"""
|
||||
device_info = Hub._map_device_info(device, installation_id)
|
||||
if device.parent_device is not None:
|
||||
device_info["via_device_id"] = self._ensure_device_registered(
|
||||
device.parent_device, installation_id
|
||||
)
|
||||
device_entry = self._device_registry.async_get_or_create(
|
||||
config_entry_id=self._config_entry_id,
|
||||
**device_info,
|
||||
)
|
||||
return device_entry.id
|
||||
|
||||
@staticmethod
|
||||
def _map_device_info(
|
||||
device: VictronVenusDevice, installation_id: str
|
||||
) -> DeviceInfo:
|
||||
device_info = DeviceInfo(
|
||||
) -> dr.DeviceInfo:
|
||||
return dr.DeviceInfo(
|
||||
identifiers={(DOMAIN, f"{installation_id}_{device.unique_id}")},
|
||||
manufacturer=(
|
||||
device.manufacturer
|
||||
@@ -139,13 +165,6 @@ class Hub:
|
||||
model=device.model,
|
||||
serial_number=device.serial_number,
|
||||
)
|
||||
# Set via_device based on parent_device relationship
|
||||
if device.parent_device is not None:
|
||||
device_info["via_device"] = (
|
||||
DOMAIN,
|
||||
f"{installation_id}_{device.parent_device.unique_id}",
|
||||
)
|
||||
return device_info
|
||||
|
||||
def is_device_connected(self, device_identifiers: set[tuple[str, str]]) -> bool:
|
||||
"""Check if a device is currently known to the hub."""
|
||||
|
||||
@@ -154,13 +154,13 @@ async def test_hub_start_success(
|
||||
assert victron_hub.installation_id == MOCK_INSTALLATION_ID
|
||||
|
||||
|
||||
async def test_child_device_via_device_links_to_parent_in_registry(
|
||||
async def test_device_via_device_links(
|
||||
hass: HomeAssistant,
|
||||
init_integration: tuple[VictronVenusHub, MockConfigEntry],
|
||||
device_registry: dr.DeviceRegistry,
|
||||
) -> None:
|
||||
"""Test non-root device is linked to its parent device in the HA device registry."""
|
||||
victron_hub, _mock_config_entry = init_integration
|
||||
"""Test a child device links to its registered parent via via_device_id."""
|
||||
victron_hub, mock_config_entry = init_integration
|
||||
|
||||
# Inject a system metric first so system_0 is registered as the gateway device.
|
||||
await inject_message(
|
||||
@@ -177,15 +177,15 @@ async def test_child_device_via_device_links_to_parent_in_registry(
|
||||
await finalize_injection(victron_hub)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
system_device = device_registry.async_get_device(
|
||||
identifiers={(DOMAIN, f"{MOCK_INSTALLATION_ID}_system_0")}
|
||||
system_device = device_registry.async_get_device_by_identifier(
|
||||
(DOMAIN, f"{MOCK_INSTALLATION_ID}_system_0"), mock_config_entry.entry_id
|
||||
)
|
||||
assert system_device is not None
|
||||
# The GX gateway has no parent — it IS the root.
|
||||
assert system_device.via_device_id is None
|
||||
|
||||
battery_device = device_registry.async_get_device(
|
||||
identifiers={(DOMAIN, f"{MOCK_INSTALLATION_ID}_battery_0")}
|
||||
battery_device = device_registry.async_get_device_by_identifier(
|
||||
(DOMAIN, f"{MOCK_INSTALLATION_ID}_battery_0"), mock_config_entry.entry_id
|
||||
)
|
||||
assert battery_device is not None
|
||||
# Battery is a child of the GX gateway, not an orphan.
|
||||
|
||||
Reference in New Issue
Block a user