From 38474ec82c2ff2eca65fb9a3ba165e046e01d48c Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Wed, 5 Aug 2026 10:04:05 +0200 Subject: [PATCH] Fix via_device in hive linking to itself (#178190) Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- homeassistant/components/hive/__init__.py | 7 +++- homeassistant/components/hive/entity.py | 14 ++++--- tests/components/hive/test_init.py | 48 +++++++++++++++++++++++ 3 files changed, 63 insertions(+), 6 deletions(-) diff --git a/homeassistant/components/hive/__init__.py b/homeassistant/components/hive/__init__.py index 2ffce1f45d0b..7bc513a10167 100644 --- a/homeassistant/components/hive/__init__.py +++ b/homeassistant/components/hive/__init__.py @@ -50,7 +50,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: HiveConfigEntry) -> bool connections.add((dr.CONNECTION_NETWORK_MAC, mac)) device_registry = dr.async_get(hass) - device_registry.async_get_or_create( + hub_device = device_registry.async_get_or_create( config_entry_id=entry.entry_id, identifiers={(DOMAIN, hub_data["device_id"])}, connections=connections, @@ -59,6 +59,11 @@ async def async_setup_entry(hass: HomeAssistant, entry: HiveConfigEntry) -> bool sw_version=hub_data["deviceData"]["version"], manufacturer=hub_data["deviceData"]["manufacturer"], ) + if hub_device.via_device_id is not None: + # Older versions linked the hub's own diagnostic sensor to the hub itself; + # clear the stale self-reference since async_get_or_create leaves + # via_device_id untouched when it's not passed. + device_registry.async_update_device(hub_device.id, via_device_id=None) await hass.config_entries.async_forward_entry_setups( entry, diff --git a/homeassistant/components/hive/entity.py b/homeassistant/components/hive/entity.py index 1b0c85cc4332..381fae43f80e 100644 --- a/homeassistant/components/hive/entity.py +++ b/homeassistant/components/hive/entity.py @@ -31,18 +31,22 @@ class HiveEntity(Entity): self.device = hive_device self._attr_name = self.device["haName"] self._attr_unique_id = f"{self.device['hiveID']}-{self.device['hiveType']}" - self._attr_device_info = DeviceInfo( - identifiers={(DOMAIN, self.device["device_id"])}, + device_id = self.device["device_id"] + device_info = DeviceInfo( + identifiers={(DOMAIN, device_id)}, model=self.device["deviceData"]["model"], manufacturer=self.device["deviceData"]["manufacturer"], name=self.device["device_name"], sw_version=self.device["deviceData"]["version"], - via_device_id=dr.async_get_device_id_by_identifier( + ) + # Hive reports the hub itself as its parent. + if self.device["parentDevice"] != device_id: + device_info["via_device_id"] = dr.async_get_device_id_by_identifier( hass, (DOMAIN, self.device["parentDevice"]), config_entry_id=entry.entry_id, - ), - ) + ) + self._attr_device_info = device_info self.attributes: dict[str, Any] = {} @override diff --git a/tests/components/hive/test_init.py b/tests/components/hive/test_init.py index 2e505af81b3f..6467c661858f 100644 --- a/tests/components/hive/test_init.py +++ b/tests/components/hive/test_init.py @@ -50,6 +50,25 @@ _CHILD_BINARY_SENSOR = { "status": {"state": True}, } +# The hub's own diagnostic sensor reports the hub as its own parent +# (parentDevice == device_id), which would link the hub device to itself. +_HUB_BINARY_SENSOR = { + "device_id": "hive-hub-id", + "hiveID": "hive-hub-id", + "hiveName": "Hive Hub Status", + "haName": "Hive Hub Status", + "device_name": "Hive Hub", + "hiveType": "Connectivity", + "parentDevice": "hive-hub-id", + "deviceData": { + "model": "Hub", + "version": "1.2.3", + "manufacturer": "Hive", + "online": True, + }, + "status": {"state": True}, +} + def _make_mock_hive( hub_extra: dict, extra_devices: dict[str, list[dict[str, Any]]] | None = None @@ -143,3 +162,32 @@ async def test_child_device_links_to_hub_via_device_id( assert hub_device is not None assert child_device is not None assert child_device.via_device_id == hub_device.id + + +async def test_hub_diagnostic_sensor_not_linked_to_itself( + hass: HomeAssistant, + device_registry: dr.DeviceRegistry, +) -> None: + """The hub's own diagnostic sensor must not link the hub device to itself.""" + entry = MockConfigEntry(domain=DOMAIN, data=_ENTRY_DATA) + entry.add_to_hass(hass) + + mock_hive = _make_mock_hive( + {"macAddress": "00:1C:2B:1C:2E:68"}, + {"binary_sensor": [_HUB_BINARY_SENSOR], "sensor": []}, + ) + mock_hive.session.updateData = AsyncMock() + mock_hive.sensor.getSensor = AsyncMock(side_effect=lambda device: device) + + with patch( + "homeassistant.components.hive.Hive", + return_value=mock_hive, + ): + await hass.config_entries.async_setup(entry.entry_id) + await hass.async_block_till_done() + + hub_device = device_registry.async_get_device_by_identifier( + (DOMAIN, "hive-hub-id"), entry.entry_id + ) + assert hub_device is not None + assert hub_device.via_device_id is None