Fix via_device race in directv (#177712)

This commit is contained in:
Erik Montnemery
2026-08-01 11:03:49 +02:00
committed by GitHub
parent 5951420117
commit 8c6bc8a68f
5 changed files with 95 additions and 8 deletions
@@ -8,8 +8,11 @@ from homeassistant.config_entries import ConfigEntry
from homeassistant.const import CONF_HOST, Platform
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import ConfigEntryNotReady
from homeassistant.helpers import device_registry as dr
from homeassistant.helpers.aiohttp_client import async_get_clientsession
from .const import DOMAIN
PLATFORMS = [Platform.MEDIA_PLAYER, Platform.REMOTE]
SCAN_INTERVAL = timedelta(seconds=30)
@@ -28,6 +31,23 @@ async def async_setup_entry(hass: HomeAssistant, entry: DirecTVConfigEntry) -> b
entry.runtime_data = dtv
# Register the receiver device so client entities can link to it via_device_id.
device_registry = dr.async_get(hass)
device_registry.async_get_or_create(
config_entry_id=entry.entry_id,
identifiers={(DOMAIN, dtv.device.info.receiver_id)},
manufacturer=dtv.device.info.brand,
name=next(
(
str.title(location.name)
for location in dtv.device.locations
if not location.client
),
None,
),
sw_version=dtv.device.info.version,
)
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
return True
+23 -4
View File
@@ -2,9 +2,12 @@
from directv import DIRECTV
from homeassistant.core import HomeAssistant
from homeassistant.helpers import device_registry as dr
from homeassistant.helpers.device_registry import DeviceInfo
from homeassistant.helpers.entity import Entity
from . import DirecTVConfigEntry
from .const import DOMAIN
@@ -14,16 +17,32 @@ class DIRECTVEntity(Entity):
_attr_has_entity_name = True
_attr_name = None
def __init__(self, *, dtv: DIRECTV, name: str, address: str = "0") -> None:
def __init__(
self,
*,
hass: HomeAssistant,
dtv: DIRECTV,
entry: DirecTVConfigEntry,
name: str,
address: str = "0",
) -> None:
"""Initialize the DirecTV entity."""
self._address = address
self._device_id = address if address != "0" else dtv.device.info.receiver_id
self._is_client = address != "0"
self.dtv = dtv
via_device_id: str | None = None
if self._is_client:
via_device_id = dr.async_get_device_id_by_identifier(
hass,
(DOMAIN, dtv.device.info.receiver_id),
config_entry_id=entry.entry_id,
)
self._attr_device_info = DeviceInfo(
identifiers={(DOMAIN, self._device_id)},
manufacturer=self.dtv.device.info.brand,
manufacturer=dtv.device.info.brand,
name=name,
sw_version=self.dtv.device.info.version,
via_device=(DOMAIN, self.dtv.device.info.receiver_id),
sw_version=dtv.device.info.version,
)
if via_device_id is not None:
self._attr_device_info["via_device_id"] = via_device_id
@@ -61,7 +61,9 @@ async def async_setup_entry(
async_add_entities(
(
DIRECTVMediaPlayer(
hass=hass,
dtv=dtv,
entry=entry,
name=str.title(location.name),
address=location.address,
)
@@ -74,10 +76,20 @@ async def async_setup_entry(
class DIRECTVMediaPlayer(DIRECTVEntity, MediaPlayerEntity):
"""Representation of a DirecTV receiver on the network."""
def __init__(self, *, dtv: DIRECTV, name: str, address: str = "0") -> None:
def __init__(
self,
*,
hass: HomeAssistant,
dtv: DIRECTV,
entry: DirecTVConfigEntry,
name: str,
address: str = "0",
) -> None:
"""Initialize DirecTV media player."""
super().__init__(
hass=hass,
dtv=dtv,
entry=entry,
name=name,
address=address,
)
+13 -1
View File
@@ -29,7 +29,9 @@ async def async_setup_entry(
async_add_entities(
(
DIRECTVRemote(
hass=hass,
dtv=dtv,
entry=entry,
name=str.title(location.name),
address=location.address,
)
@@ -42,10 +44,20 @@ async def async_setup_entry(
class DIRECTVRemote(DIRECTVEntity, RemoteEntity):
"""Device that sends commands to a DirecTV receiver."""
def __init__(self, *, dtv: DIRECTV, name: str, address: str = "0") -> None:
def __init__(
self,
*,
hass: HomeAssistant,
dtv: DIRECTV,
entry: DirecTVConfigEntry,
name: str,
address: str = "0",
) -> None:
"""Initialize DirecTV remote."""
super().__init__(
hass=hass,
dtv=dtv,
entry=entry,
name=name,
address=address,
)
+26 -2
View File
@@ -6,6 +6,7 @@ from unittest.mock import patch
from freezegun.api import FrozenDateTimeFactory
import pytest
from homeassistant.components.directv.const import DOMAIN
from homeassistant.components.directv.media_player import (
ATTR_MEDIA_CURRENTLY_RECORDING,
ATTR_MEDIA_RATING,
@@ -46,10 +47,10 @@ from homeassistant.const import (
STATE_UNAVAILABLE,
)
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 homeassistant.util import dt as dt_util
from . import setup_integration
from . import RECEIVER_ID, setup_integration
from tests.test_util.aiohttp import AiohttpClientMocker
@@ -162,6 +163,29 @@ async def test_unique_id(
assert unavailable_client.unique_id == "9XXXXXXXXXX9"
async def test_client_device_via_device_id(
hass: HomeAssistant,
device_registry: dr.DeviceRegistry,
entity_registry: er.EntityRegistry,
aioclient_mock: AiohttpClientMocker,
) -> None:
"""Test a client's device links to the receiver device via via_device_id."""
entry = await setup_integration(hass, aioclient_mock)
receiver_device = device_registry.async_get_device_by_identifier(
(DOMAIN, RECEIVER_ID), entry.entry_id
)
assert receiver_device is not None
client_entity = entity_registry.async_get(CLIENT_ENTITY_ID)
assert client_entity is not None
assert client_entity.device_id is not None
client_device = device_registry.async_get(client_entity.device_id)
assert client_device is not None
assert client_device.via_device_id == receiver_device.id
async def test_supported_features(
hass: HomeAssistant, aioclient_mock: AiohttpClientMocker
) -> None: