Fix via_device race in tractive (#177775)

This commit is contained in:
Erik Montnemery
2026-08-03 16:54:53 +02:00
committed by GitHub
parent 9c9225e301
commit 7eb093530b
7 changed files with 52 additions and 9 deletions
+16 -1
View File
@@ -19,7 +19,7 @@ from homeassistant.const import (
)
from homeassistant.core import Event, HomeAssistant
from homeassistant.exceptions import ConfigEntryAuthFailed, ConfigEntryNotReady
from homeassistant.helpers import entity_registry as er
from homeassistant.helpers import device_registry as dr, entity_registry as er
from homeassistant.helpers.aiohttp_client import async_get_clientsession
from homeassistant.helpers.dispatcher import async_dispatcher_send
@@ -120,6 +120,21 @@ async def async_setup_entry(hass: HomeAssistant, entry: TractiveConfigEntry) ->
entry.runtime_data = TractiveData(tractive, filtered_trackables)
# Register the tracker devices so entities on the pet devices can resolve
# their via_device link at construction time.
device_registry = dr.async_get(hass)
for item in filtered_trackables:
device_registry.async_get_or_create(
config_entry_id=entry.entry_id,
configuration_url="https://my.tractive.com/",
identifiers={(DOMAIN, item.tracker_details["_id"])},
translation_key="tracker",
translation_placeholders={"id": item.tracker_details["_id"]},
manufacturer="Tractive GmbH",
sw_version=item.tracker_details["fw_version"],
model_id=item.tracker_details["model_number"],
)
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
# Send initial health overview data to sensors after platforms are set up
@@ -23,12 +23,16 @@ class TractiveBinarySensor(TractiveEntity, BinarySensorEntity):
def __init__(
self,
hass: HomeAssistant,
entry: TractiveConfigEntry,
client: TractiveClient,
item: Trackables,
description: TractiveBinarySensorEntityDescription,
) -> None:
"""Initialize sensor entity."""
super().__init__(
hass,
entry,
client,
item.trackable,
item.tracker_details,
@@ -80,7 +84,7 @@ async def async_setup_entry(
trackables = entry.runtime_data.trackables
entities = [
TractiveBinarySensor(client, item, description)
TractiveBinarySensor(hass, entry, client, item, description)
for description in SENSOR_TYPES
for item in trackables
if description.supported(item.tracker_details)
@@ -21,7 +21,7 @@ async def async_setup_entry(
client = entry.runtime_data.client
trackables = entry.runtime_data.trackables
entities = [TractiveDeviceTracker(client, item) for item in trackables]
entities = [TractiveDeviceTracker(hass, entry, client, item) for item in trackables]
async_add_entities(entities)
@@ -32,9 +32,17 @@ class TractiveDeviceTracker(TractiveEntity, TrackerEntity):
_attr_translation_key = "tracker"
_attr_name = None
def __init__(self, client: TractiveClient, item: Trackables) -> None:
def __init__(
self,
hass: HomeAssistant,
entry: TractiveConfigEntry,
client: TractiveClient,
item: Trackables,
) -> None:
"""Initialize tracker entity."""
super().__init__(
hass,
entry,
client,
item.trackable,
item.tracker_details,
+10 -3
View File
@@ -2,12 +2,13 @@
from typing import Any, override
from homeassistant.core import callback
from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers import device_registry as dr
from homeassistant.helpers.device_registry import DeviceEntryType, DeviceInfo
from homeassistant.helpers.dispatcher import async_dispatcher_connect
from homeassistant.helpers.entity import Entity
from . import TractiveClient
from . import TractiveClient, TractiveConfigEntry
from .const import DOMAIN, SERVER_UNAVAILABLE
@@ -18,6 +19,8 @@ class TractiveEntity(Entity):
def __init__(
self,
hass: HomeAssistant,
entry: TractiveConfigEntry,
client: TractiveClient,
trackable: dict[str, Any],
tracker_details: dict[str, Any],
@@ -39,7 +42,11 @@ class TractiveEntity(Entity):
self._attr_device_info = DeviceInfo(
identifiers={(DOMAIN, trackable["_id"])},
name=trackable["details"]["name"],
via_device=(DOMAIN, tracker_details["_id"]),
via_device_id=dr.async_get_device_id_by_identifier(
hass,
(DOMAIN, tracker_details["_id"]),
config_entry_id=entry.entry_id,
),
entry_type=DeviceEntryType.SERVICE,
)
+5 -1
View File
@@ -51,6 +51,8 @@ class TractiveSensor(TractiveEntity, SensorEntity):
def __init__(
self,
hass: HomeAssistant,
entry: TractiveConfigEntry,
client: TractiveClient,
item: Trackables,
description: TractiveSensorEntityDescription,
@@ -63,6 +65,8 @@ class TractiveSensor(TractiveEntity, SensorEntity):
else:
dispatcher_signal = f"{description.signal_prefix}-{item.trackable['_id']}"
super().__init__(
hass,
entry,
client,
item.trackable,
item.tracker_details,
@@ -156,7 +160,7 @@ async def async_setup_entry(
trackables = entry.runtime_data.trackables
entities = [
TractiveSensor(client, item, description)
TractiveSensor(hass, entry, client, item, description)
for description in SENSOR_TYPES
for item in trackables
]
+5 -1
View File
@@ -65,7 +65,7 @@ async def async_setup_entry(
trackables = entry.runtime_data.trackables
entities = [
TractiveSwitch(client, item, description)
TractiveSwitch(hass, entry, client, item, description)
for description in SWITCH_TYPES
for item in trackables
]
@@ -80,12 +80,16 @@ class TractiveSwitch(TractiveEntity, SwitchEntity):
def __init__(
self,
hass: HomeAssistant,
entry: TractiveConfigEntry,
client: TractiveClient,
item: Trackables,
description: TractiveSwitchEntityDescription,
) -> None:
"""Initialize switch entity."""
super().__init__(
hass,
entry,
client,
item.trackable,
item.tracker_details,
+1
View File
@@ -49,6 +49,7 @@ async def test_sensor_device_assignment(
pet_device = device_registry.async_get_device(identifiers={(DOMAIN, "pet_id_123")})
assert pet_device is not None
assert pet_device.via_device_id == tracker_device.id
for entity_id in (
"sensor.tracker_device_id_123_battery",