Preregister nexia zone devices to avoid via_device race in sub-sub-devices (#179942)

This commit is contained in:
John Hillery
2026-08-25 16:47:23 +02:00
committed by GitHub
parent f1db0d3fa8
commit 734ad1ee35
3 changed files with 61 additions and 14 deletions
+21 -8
View File
@@ -62,20 +62,33 @@ async def async_setup_entry(hass: HomeAssistant, entry: NexiaConfigEntry) -> boo
await coordinator.async_config_entry_first_refresh()
entry.runtime_data = coordinator
# Register thermostat devices before forwarding platforms so zone sub-devices
# can resolve their via_device_id regardless of platform setup order.
device_registry = dr.async_get(hass)
for thermostat_id in nexia_home.get_thermostat_ids():
device_registry.async_get_or_create(
config_entry_id=entry.entry_id,
identifiers={(DOMAIN, thermostat_id)}, # type: ignore[arg-type] # until fix issue #139773
)
_preregister_devices(hass, entry, nexia_home)
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
return True
def _preregister_devices(
hass: HomeAssistant, entry: NexiaConfigEntry, nexia_home: NexiaHome
) -> None:
"""Register devices before forwarding platforms so sub-devices resolve via_device_id regardless of setup order."""
device_registry = dr.async_get(hass)
for thermostat_id in nexia_home.get_thermostat_ids():
thermostat = nexia_home.get_thermostat_by_id(thermostat_id)
device_registry.async_get_or_create(
config_entry_id=entry.entry_id,
identifiers={(DOMAIN, thermostat_id)}, # type: ignore[arg-type] # until fix issue #139773
)
for zone_id in thermostat.get_zone_ids():
zone = thermostat.get_zone_by_id(zone_id)
device_registry.async_get_or_create(
config_entry_id=entry.entry_id,
identifiers={(DOMAIN, zone_id)}, # type: ignore[arg-type] # until fix issue #139773
suggested_area=zone.get_name(),
)
async def async_unload_entry(hass: HomeAssistant, entry: NexiaConfigEntry) -> bool:
"""Unload a config entry."""
return await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
+2 -4
View File
@@ -5,7 +5,7 @@ from typing import TYPE_CHECKING, override
from nexia.thermostat import NexiaThermostat
from nexia.zone import NexiaThermostatZone
from homeassistant.const import ATTR_IDENTIFIERS, ATTR_NAME, ATTR_SUGGESTED_AREA
from homeassistant.const import ATTR_IDENTIFIERS, ATTR_NAME
from homeassistant.helpers import device_registry as dr
from homeassistant.helpers.device_registry import DeviceInfo
from homeassistant.helpers.dispatcher import (
@@ -103,13 +103,11 @@ class NexiaThermostatZoneEntity(NexiaThermostatEntity):
"""Initialize the entity."""
super().__init__(coordinator, zone.thermostat, unique_id)
self._zone = zone
zone_name = self._zone.get_name()
if TYPE_CHECKING:
assert self._attr_device_info is not None
self._attr_device_info |= {
ATTR_IDENTIFIERS: {(DOMAIN, zone.zone_id)}, # type: ignore[arg-type] # until fix issue #139773
ATTR_NAME: zone_name,
ATTR_SUGGESTED_AREA: zone_name,
ATTR_NAME: zone.get_name(),
"via_device_id": dr.async_get_device_id_by_identifier(
self.coordinator.hass,
(DOMAIN, zone.thermostat.thermostat_id), # type: ignore[arg-type] # until fix issue #139773
+38 -2
View File
@@ -5,12 +5,14 @@ from unittest.mock import NonCallableMock, patch
import aiohttp
from nexia.home import NexiaHome
from homeassistant.components.nexia import _preregister_devices
from homeassistant.components.nexia.const import DOMAIN
from homeassistant.config_entries import ConfigEntryState
from homeassistant.const import CONF_PASSWORD, CONF_USERNAME
from homeassistant.core import HomeAssistant
from homeassistant.helpers import device_registry as dr, entity_registry as er
from homeassistant.setup import async_setup_component
from homeassistant.util import slugify
from .conftest import setup_integration
@@ -77,6 +79,39 @@ async def test_migrate_entry_minor_version_1_2(hass: HomeAssistant) -> None:
assert entry.unique_id == "123456"
async def test_device_preregistration(
hass: HomeAssistant, mock_nexia_home: NexiaHome, device_registry: dr.DeviceRegistry
) -> None:
"""Test all thermostat and zone devices are preregistered."""
entry = MockConfigEntry(domain=DOMAIN)
entry.add_to_hass(hass)
_preregister_devices(hass, entry, mock_nexia_home)
thermostat_ids = mock_nexia_home.get_thermostat_ids()
assert len(thermostat_ids) > 0
for thermostat_id in thermostat_ids:
thermostat = mock_nexia_home.get_thermostat_by_id(thermostat_id)
device = device_registry.async_get_device_by_identifier(
(DOMAIN, thermostat.thermostat_id), # type: ignore[arg-type] # until fix issue #139773
entry.entry_id,
)
assert device is not None
zone_ids = thermostat.get_zone_ids()
assert len(zone_ids) > 0
for zone_id in zone_ids:
zone = thermostat.get_zone_by_id(zone_id)
device = device_registry.async_get_device_by_identifier(
(DOMAIN, zone.zone_id), # type: ignore[arg-type] # until fix issue #139773
entry.entry_id,
)
assert device is not None
assert device.area_id == slugify(zone.get_name())
async def test_device_via_device_links(
hass: HomeAssistant,
patch_nexia_home: NexiaHome,
@@ -86,14 +121,15 @@ async def test_device_via_device_links(
config_entry = await setup_integration(hass, patch_nexia_home)
thermostat_device = device_registry.async_get_device_by_identifier(
(DOMAIN, 2000000), # type: ignore[arg-type] # until fix issue #139773
(DOMAIN, 2000004), # type: ignore[arg-type] # until fix issue #139773
config_entry.entry_id,
)
assert thermostat_device is not None
zone_device = device_registry.async_get_device_by_identifier(
(DOMAIN, 100), # type: ignore[arg-type] # until fix issue #139773
(DOMAIN, 500), # type: ignore[arg-type] # until fix issue #139773
config_entry.entry_id,
)
assert zone_device is not None
assert zone_device.via_device_id == thermostat_device.id
assert zone_device.area_id == "center_nativezone"