mirror of
https://github.com/home-assistant/core.git
synced 2026-08-24 10:13:52 -05:00
Fix via_device race in yale_smart_alarm (#177858)
This commit is contained in:
@@ -4,7 +4,7 @@ from homeassistant.components.lock import CONF_DEFAULT_CODE, DOMAIN as LOCK_DOMA
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import CONF_CODE, CONF_NAME
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers import entity_registry as er
|
||||
from homeassistant.helpers import device_registry as dr, entity_registry as er
|
||||
|
||||
from .const import LOGGER, PLATFORMS
|
||||
from .coordinator import YaleDataUpdateCoordinator
|
||||
@@ -19,6 +19,14 @@ async def async_setup_entry(hass: HomeAssistant, entry: YaleConfigEntry) -> bool
|
||||
await coordinator.async_config_entry_first_refresh()
|
||||
entry.runtime_data = coordinator
|
||||
|
||||
# Register the alarm panel device so child devices can link to it via
|
||||
# via_device_id, since the panel is otherwise only registered by a sibling
|
||||
# entity on a concurrently loaded platform.
|
||||
dr.async_get(hass).async_get_or_create(
|
||||
config_entry_id=entry.entry_id,
|
||||
**coordinator.device_info,
|
||||
)
|
||||
|
||||
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
|
||||
|
||||
return True
|
||||
|
||||
@@ -10,12 +10,20 @@ from yalesmartalarmclient.exceptions import AuthenticationError
|
||||
from homeassistant.const import CONF_PASSWORD, CONF_USERNAME
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.exceptions import ConfigEntryAuthFailed
|
||||
from homeassistant.helpers.device_registry import CONNECTION_NETWORK_MAC, DeviceInfo
|
||||
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from . import YaleConfigEntry
|
||||
|
||||
from .const import DEFAULT_SCAN_INTERVAL, DOMAIN, LOGGER, YALE_BASE_ERRORS
|
||||
from .const import (
|
||||
DEFAULT_SCAN_INTERVAL,
|
||||
DOMAIN,
|
||||
LOGGER,
|
||||
MANUFACTURER,
|
||||
MODEL,
|
||||
YALE_BASE_ERRORS,
|
||||
)
|
||||
|
||||
|
||||
class YaleDataUpdateCoordinator(DataUpdateCoordinator[dict[str, Any]]):
|
||||
@@ -36,6 +44,19 @@ class YaleDataUpdateCoordinator(DataUpdateCoordinator[dict[str, Any]]):
|
||||
)
|
||||
self.locks: list[YaleLock] = []
|
||||
|
||||
@property
|
||||
def device_info(self) -> DeviceInfo:
|
||||
"""Return device info for the alarm panel."""
|
||||
panel_info = self.data["panel_info"]
|
||||
return DeviceInfo(
|
||||
identifiers={(DOMAIN, self.config_entry.data[CONF_USERNAME])},
|
||||
connections={(CONNECTION_NETWORK_MAC, panel_info["mac"])},
|
||||
manufacturer=MANUFACTURER,
|
||||
model=MODEL,
|
||||
name=self.config_entry.title,
|
||||
sw_version=panel_info["version"],
|
||||
)
|
||||
|
||||
def _yale_setup(self) -> tuple[YaleSmartAlarmClient, list[YaleLock]]:
|
||||
"""Set up connection to Yale."""
|
||||
yale = YaleSmartAlarmClient(
|
||||
|
||||
@@ -3,7 +3,8 @@
|
||||
from yalesmartalarmclient import YaleLock
|
||||
|
||||
from homeassistant.const import CONF_USERNAME
|
||||
from homeassistant.helpers.device_registry import CONNECTION_NETWORK_MAC, DeviceInfo
|
||||
from homeassistant.helpers import device_registry as dr
|
||||
from homeassistant.helpers.device_registry import DeviceInfo
|
||||
from homeassistant.helpers.entity import Entity
|
||||
from homeassistant.helpers.update_coordinator import CoordinatorEntity
|
||||
|
||||
@@ -25,7 +26,11 @@ class YaleEntity(CoordinatorEntity[YaleDataUpdateCoordinator]):
|
||||
manufacturer=MANUFACTURER,
|
||||
model=MODEL,
|
||||
identifiers={(DOMAIN, data["address"])},
|
||||
via_device=(DOMAIN, coordinator.config_entry.data[CONF_USERNAME]),
|
||||
via_device_id=dr.async_get_device_id_by_identifier(
|
||||
coordinator.hass,
|
||||
(DOMAIN, coordinator.config_entry.data[CONF_USERNAME]),
|
||||
config_entry_id=coordinator.config_entry.entry_id,
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
@@ -43,7 +48,11 @@ class YaleLockEntity(CoordinatorEntity[YaleDataUpdateCoordinator]):
|
||||
manufacturer=MANUFACTURER,
|
||||
model=MODEL,
|
||||
identifiers={(DOMAIN, lock.sid())},
|
||||
via_device=(DOMAIN, coordinator.config_entry.data[CONF_USERNAME]),
|
||||
via_device_id=dr.async_get_device_id_by_identifier(
|
||||
coordinator.hass,
|
||||
(DOMAIN, coordinator.config_entry.data[CONF_USERNAME]),
|
||||
config_entry_id=coordinator.config_entry.entry_id,
|
||||
),
|
||||
)
|
||||
self.lock_data = lock
|
||||
|
||||
@@ -56,12 +65,4 @@ class YaleAlarmEntity(CoordinatorEntity[YaleDataUpdateCoordinator], Entity):
|
||||
def __init__(self, coordinator: YaleDataUpdateCoordinator) -> None:
|
||||
"""Initialize an Yale device."""
|
||||
super().__init__(coordinator)
|
||||
panel_info = coordinator.data["panel_info"]
|
||||
self._attr_device_info = DeviceInfo(
|
||||
identifiers={(DOMAIN, coordinator.config_entry.data[CONF_USERNAME])},
|
||||
manufacturer=MANUFACTURER,
|
||||
model=MODEL,
|
||||
name=coordinator.config_entry.title,
|
||||
connections={(CONNECTION_NETWORK_MAC, panel_info["mac"])},
|
||||
sw_version=panel_info["version"],
|
||||
)
|
||||
self._attr_device_info = coordinator.device_info
|
||||
|
||||
@@ -2,11 +2,14 @@
|
||||
|
||||
from unittest.mock import Mock, patch
|
||||
|
||||
import pytest
|
||||
|
||||
from homeassistant.components.lock import DOMAIN as LOCK_DOMAIN
|
||||
from homeassistant.components.yale_smart_alarm.const import DOMAIN
|
||||
from homeassistant.config_entries import SOURCE_USER, ConfigEntryState
|
||||
from homeassistant.const import Platform
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers import entity_registry as er
|
||||
from homeassistant.helpers import device_registry as dr, entity_registry as er
|
||||
|
||||
from .conftest import ENTRY_CONFIG, OPTIONS_CONFIG
|
||||
|
||||
@@ -44,6 +47,39 @@ async def test_setup_entry(
|
||||
assert entry.state is ConfigEntryState.NOT_LOADED
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("load_platforms", "child_identifier"),
|
||||
[
|
||||
pytest.param([Platform.LOCK], "1111", id="lock"),
|
||||
pytest.param([Platform.BINARY_SENSOR], "RF4", id="binary_sensor"),
|
||||
],
|
||||
)
|
||||
async def test_child_devices_linked_to_panel(
|
||||
hass: HomeAssistant,
|
||||
load_config_entry: tuple[MockConfigEntry, Mock],
|
||||
device_registry: dr.DeviceRegistry,
|
||||
child_identifier: str,
|
||||
) -> None:
|
||||
"""Test child devices link to the alarm panel through via_device_id.
|
||||
|
||||
Only a single platform is loaded per case, whose entities never create the
|
||||
panel device themselves, so the link is established solely by the panel
|
||||
being registered at setup. Covers both YaleEntity and YaleLockEntity.
|
||||
"""
|
||||
config_entry, _ = load_config_entry
|
||||
|
||||
panel_device = device_registry.async_get_device_by_identifier(
|
||||
(DOMAIN, ENTRY_CONFIG["username"]), config_entry.entry_id
|
||||
)
|
||||
assert panel_device is not None
|
||||
|
||||
child_device = device_registry.async_get_device_by_identifier(
|
||||
(DOMAIN, child_identifier), config_entry.entry_id
|
||||
)
|
||||
assert child_device is not None
|
||||
assert child_device.via_device_id == panel_device.id
|
||||
|
||||
|
||||
async def test_migrate_entry(
|
||||
hass: HomeAssistant,
|
||||
get_client: Mock,
|
||||
|
||||
Reference in New Issue
Block a user