Migrate entity unique IDs in System Bridge integration (#171799)

This commit is contained in:
Manu
2026-07-27 15:53:36 +02:00
committed by GitHub
parent cc9d364379
commit c5c5c7bd6f
9 changed files with 138 additions and 63 deletions
@@ -2,6 +2,7 @@
import asyncio
import logging
from typing import TYPE_CHECKING
from systembridgeconnector.exceptions import (
AuthenticationException,
@@ -11,7 +12,6 @@ from systembridgeconnector.exceptions import (
)
from systembridgeconnector.version import Version
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import (
CONF_API_KEY,
CONF_ENTITY_ID,
@@ -23,7 +23,11 @@ from homeassistant.const import (
)
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import ConfigEntryAuthFailed, ConfigEntryNotReady
from homeassistant.helpers import config_validation as cv, discovery
from homeassistant.helpers import (
config_validation as cv,
discovery,
entity_registry as er,
)
from homeassistant.helpers.aiohttp_client import async_get_clientsession
from homeassistant.helpers.issue_registry import IssueSeverity, async_create_issue
from homeassistant.helpers.typing import ConfigType
@@ -206,7 +210,9 @@ async def async_reload_entry(
await hass.config_entries.async_reload(entry.entry_id)
async def async_migrate_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> bool:
async def async_migrate_entry(
hass: HomeAssistant, config_entry: SystemBridgeConfigEntry
) -> bool:
"""Migrate old entry."""
_LOGGER.debug(
"Migrating from version %s.%s",
@@ -216,6 +222,7 @@ async def async_migrate_entry(hass: HomeAssistant, config_entry: ConfigEntry) ->
if config_entry.minor_version < 2:
# Migrate to CONF_TOKEN, which was added in 1.2
new_data = dict(config_entry.data)
new_data.setdefault(CONF_TOKEN, config_entry.data.get(CONF_API_KEY))
@@ -231,4 +238,27 @@ async def async_migrate_entry(hass: HomeAssistant, config_entry: ConfigEntry) ->
config_entry.minor_version,
)
if config_entry.minor_version < 3:
# Migrate entity unique id
if TYPE_CHECKING:
assert config_entry.unique_id
ent_reg = er.async_get(hass)
for entity_entry in er.async_entries_for_config_entry(
ent_reg, config_entry.entry_id
):
if not entity_entry.unique_id.startswith(config_entry.data[CONF_HOST]):
continue
ent_reg.async_update_entity(
entity_entry.entity_id,
new_unique_id=(
config_entry.unique_id
+ entity_entry.unique_id[len(config_entry.data[CONF_HOST]) :]
),
)
hass.config_entries.async_update_entry(config_entry, minor_version=3)
return True
@@ -116,7 +116,7 @@ class SystemBridgeConfigFlow(
"""Handle a config flow for System Bridge."""
VERSION = 1
MINOR_VERSION = 2
MINOR_VERSION = 3
_name: str
@@ -1,7 +1,5 @@
"""Base entity for the system bridge integration."""
from typing import override
from homeassistant.helpers import device_registry as dr
from homeassistant.helpers.device_registry import DeviceInfo
from homeassistant.helpers.update_coordinator import CoordinatorEntity
@@ -25,7 +23,11 @@ class SystemBridgeEntity(CoordinatorEntity[SystemBridgeDataUpdateCoordinator]):
super().__init__(coordinator)
self._hostname = coordinator.data.system.hostname
self._key = f"{self._hostname}_{key}" if key is not None else self._hostname
self._attr_unique_id = (
f"{coordinator.data.system.uuid}_{key}"
if key is not None
else coordinator.data.system.uuid
)
self._configuration_url = (
f"http://{self._hostname}:{api_port}/app/settings.html"
)
@@ -33,17 +35,7 @@ class SystemBridgeEntity(CoordinatorEntity[SystemBridgeDataUpdateCoordinator]):
self._uuid = coordinator.data.system.uuid
self._version = coordinator.data.system.version
@property
@override
def unique_id(self) -> str:
"""Return the unique ID for this entity."""
return self._key
@property
@override
def device_info(self) -> DeviceInfo:
"""Return device information about this System Bridge instance."""
return DeviceInfo(
self._attr_device_info = DeviceInfo(
configuration_url=self._configuration_url,
connections={(dr.CONNECTION_NETWORK_MAC, self._mac_address)},
identifiers={(DOMAIN, self._uuid)},
@@ -32,7 +32,7 @@
'suggested_object_id': None,
'supported_features': 0,
'translation_key': 'camera_in_use',
'unique_id': 'hostname_camera_in_use',
'unique_id': 'uuid_camera_in_use',
'unit_of_measurement': None,
})
# ---
@@ -82,7 +82,7 @@
'suggested_object_id': None,
'supported_features': 0,
'translation_key': None,
'unique_id': 'hostname_battery_is_charging',
'unique_id': 'uuid_battery_is_charging',
'unit_of_measurement': None,
})
# ---
@@ -133,7 +133,7 @@
'suggested_object_id': None,
'supported_features': 0,
'translation_key': 'pending_reboot',
'unique_id': 'hostname_pending_reboot',
'unique_id': 'uuid_pending_reboot',
'unit_of_measurement': None,
})
# ---
@@ -183,7 +183,7 @@
'suggested_object_id': None,
'supported_features': 0,
'translation_key': None,
'unique_id': 'hostname_version_available',
'unique_id': 'uuid_version_available',
'unit_of_measurement': None,
})
# ---
@@ -32,7 +32,7 @@
'suggested_object_id': None,
'supported_features': <NotifyEntityFeature: 1>,
'translation_key': None,
'unique_id': 'hostname',
'unique_id': 'uuid',
'unit_of_measurement': None,
})
# ---
@@ -37,7 +37,7 @@
'suggested_object_id': None,
'supported_features': 0,
'translation_key': None,
'unique_id': 'hostname_battery',
'unique_id': 'uuid_battery',
'unit_of_measurement': '%',
})
# ---
@@ -90,7 +90,7 @@
'suggested_object_id': None,
'supported_features': 0,
'translation_key': 'battery_time_remaining',
'unique_id': 'hostname_battery_time_remaining',
'unique_id': 'uuid_battery_time_remaining',
'unit_of_measurement': None,
})
# ---
@@ -141,7 +141,7 @@
'suggested_object_id': None,
'supported_features': 0,
'translation_key': 'boot_time',
'unique_id': 'hostname_boot_time',
'unique_id': 'uuid_boot_time',
'unit_of_measurement': None,
})
# ---
@@ -197,7 +197,7 @@
'suggested_object_id': None,
'supported_features': 0,
'translation_key': 'cpu_power_core',
'unique_id': 'hostname_cpu_power_core_0',
'unique_id': 'uuid_cpu_power_core_0',
'unit_of_measurement': <UnitOfPower.WATT: 'W'>,
})
# ---
@@ -254,7 +254,7 @@
'suggested_object_id': None,
'supported_features': 0,
'translation_key': 'cpu_power_package',
'unique_id': 'hostname_cpu_power_package',
'unique_id': 'uuid_cpu_power_package',
'unit_of_measurement': <UnitOfPower.WATT: 'W'>,
})
# ---
@@ -311,7 +311,7 @@
'suggested_object_id': None,
'supported_features': 0,
'translation_key': 'cpu_speed',
'unique_id': 'hostname_cpu_speed',
'unique_id': 'uuid_cpu_speed',
'unit_of_measurement': <UnitOfFrequency.GIGAHERTZ: 'GHz'>,
})
# ---
@@ -369,7 +369,7 @@
'suggested_object_id': None,
'supported_features': 0,
'translation_key': 'cpu_temperature',
'unique_id': 'hostname_cpu_temperature',
'unique_id': 'uuid_cpu_temperature',
'unit_of_measurement': <UnitOfTemperature.CELSIUS: '°C'>,
})
# ---
@@ -427,7 +427,7 @@
'suggested_object_id': None,
'supported_features': 0,
'translation_key': 'cpu_voltage',
'unique_id': 'hostname_cpu_voltage',
'unique_id': 'uuid_cpu_voltage',
'unit_of_measurement': <UnitOfElectricPotential.VOLT: 'V'>,
})
# ---
@@ -485,7 +485,7 @@
'suggested_object_id': None,
'supported_features': 0,
'translation_key': 'display_refresh_rate',
'unique_id': 'hostname_display_abc123_refresh_rate',
'unique_id': 'uuid_display_abc123_refresh_rate',
'unit_of_measurement': <UnitOfFrequency.HERTZ: 'Hz'>,
})
# ---
@@ -540,7 +540,7 @@
'suggested_object_id': None,
'supported_features': 0,
'translation_key': 'display_resolution_x',
'unique_id': 'hostname_display_abc123_resolution_x',
'unique_id': 'uuid_display_abc123_resolution_x',
'unit_of_measurement': 'px',
})
# ---
@@ -594,7 +594,7 @@
'suggested_object_id': None,
'supported_features': 0,
'translation_key': 'display_resolution_y',
'unique_id': 'hostname_display_abc123_resolution_y',
'unique_id': 'uuid_display_abc123_resolution_y',
'unit_of_measurement': 'px',
})
# ---
@@ -648,7 +648,7 @@
'suggested_object_id': None,
'supported_features': 0,
'translation_key': 'displays_connected',
'unique_id': 'hostname_displays_connected',
'unique_id': 'uuid_displays_connected',
'unit_of_measurement': None,
})
# ---
@@ -699,7 +699,7 @@
'suggested_object_id': None,
'supported_features': 0,
'translation_key': 'kernel',
'unique_id': 'hostname_kernel',
'unique_id': 'uuid_kernel',
'unit_of_measurement': None,
})
# ---
@@ -749,7 +749,7 @@
'suggested_object_id': None,
'supported_features': 0,
'translation_key': 'version_latest',
'unique_id': 'hostname_version_latest',
'unique_id': 'uuid_version_latest',
'unit_of_measurement': None,
})
# ---
@@ -804,7 +804,7 @@
'suggested_object_id': None,
'supported_features': 0,
'translation_key': 'load',
'unique_id': 'hostname_processes_load',
'unique_id': 'uuid_processes_load',
'unit_of_measurement': '%',
})
# ---
@@ -861,7 +861,7 @@
'suggested_object_id': None,
'supported_features': 0,
'translation_key': 'processes_load_cpu',
'unique_id': 'hostname_processes_load_cpu_0',
'unique_id': 'uuid_processes_load_cpu_0',
'unit_of_measurement': '%',
})
# ---
@@ -918,7 +918,7 @@
'suggested_object_id': None,
'supported_features': 0,
'translation_key': 'memory_free',
'unique_id': 'hostname_memory_free',
'unique_id': 'uuid_memory_free',
'unit_of_measurement': <UnitOfInformation.GIGABYTES: 'GB'>,
})
# ---
@@ -976,7 +976,7 @@
'suggested_object_id': None,
'supported_features': 0,
'translation_key': 'memory_used_percentage',
'unique_id': 'hostname_memory_used_percentage',
'unique_id': 'uuid_memory_used_percentage',
'unit_of_measurement': '%',
})
# ---
@@ -1033,7 +1033,7 @@
'suggested_object_id': None,
'supported_features': 0,
'translation_key': 'memory_used',
'unique_id': 'hostname_memory_used',
'unique_id': 'uuid_memory_used',
'unit_of_measurement': <UnitOfInformation.GIGABYTES: 'GB'>,
})
# ---
@@ -1091,7 +1091,7 @@
'suggested_object_id': None,
'supported_features': 0,
'translation_key': 'space_used',
'unique_id': 'hostname_filesystem_mountpoint',
'unique_id': 'uuid_filesystem_mountpoint',
'unit_of_measurement': '%',
})
# ---
@@ -1148,7 +1148,7 @@
'suggested_object_id': None,
'supported_features': 0,
'translation_key': 'gpu_core_clock_speed',
'unique_id': 'hostname_gpu_abc123_core_clock_speed',
'unique_id': 'uuid_gpu_abc123_core_clock_speed',
'unit_of_measurement': <UnitOfFrequency.MEGAHERTZ: 'MHz'>,
})
# ---
@@ -1203,7 +1203,7 @@
'suggested_object_id': None,
'supported_features': 0,
'translation_key': 'gpu_fan_speed',
'unique_id': 'hostname_gpu_abc123_fan_speed',
'unique_id': 'uuid_gpu_abc123_fan_speed',
'unit_of_measurement': 'rpm',
})
# ---
@@ -1260,7 +1260,7 @@
'suggested_object_id': None,
'supported_features': 0,
'translation_key': 'gpu_memory_clock_speed',
'unique_id': 'hostname_gpu_abc123_memory_clock_speed',
'unique_id': 'uuid_gpu_abc123_memory_clock_speed',
'unit_of_measurement': <UnitOfFrequency.MEGAHERTZ: 'MHz'>,
})
# ---
@@ -1318,7 +1318,7 @@
'suggested_object_id': None,
'supported_features': 0,
'translation_key': 'gpu_memory_free',
'unique_id': 'hostname_gpu_abc123_memory_free',
'unique_id': 'uuid_gpu_abc123_memory_free',
'unit_of_measurement': <UnitOfInformation.MEGABYTES: 'MB'>,
})
# ---
@@ -1376,7 +1376,7 @@
'suggested_object_id': None,
'supported_features': 0,
'translation_key': 'gpu_memory_used_percentage',
'unique_id': 'hostname_gpu_abc123_memory_used_percentage',
'unique_id': 'uuid_gpu_abc123_memory_used_percentage',
'unit_of_measurement': '%',
})
# ---
@@ -1433,7 +1433,7 @@
'suggested_object_id': None,
'supported_features': 0,
'translation_key': 'gpu_memory_used',
'unique_id': 'hostname_gpu_abc123_memory_used',
'unique_id': 'uuid_gpu_abc123_memory_used',
'unit_of_measurement': <UnitOfInformation.MEGABYTES: 'MB'>,
})
# ---
@@ -1488,7 +1488,7 @@
'suggested_object_id': None,
'supported_features': 0,
'translation_key': 'gpu_power_usage',
'unique_id': 'hostname_gpu_abc123_power_usage',
'unique_id': 'uuid_gpu_abc123_power_usage',
'unit_of_measurement': <UnitOfPower.WATT: 'W'>,
})
# ---
@@ -1545,7 +1545,7 @@
'suggested_object_id': None,
'supported_features': 0,
'translation_key': 'gpu_temperature',
'unique_id': 'hostname_gpu_abc123_temperature',
'unique_id': 'uuid_gpu_abc123_temperature',
'unit_of_measurement': <UnitOfTemperature.CELSIUS: '°C'>,
})
# ---
@@ -1603,7 +1603,7 @@
'suggested_object_id': None,
'supported_features': 0,
'translation_key': 'gpu_usage_percentage',
'unique_id': 'hostname_gpu_abc123_usage_percentage',
'unique_id': 'uuid_gpu_abc123_usage_percentage',
'unit_of_measurement': '%',
})
# ---
@@ -1655,7 +1655,7 @@
'suggested_object_id': None,
'supported_features': 0,
'translation_key': 'os',
'unique_id': 'hostname_os',
'unique_id': 'uuid_os',
'unit_of_measurement': None,
})
# ---
@@ -1710,7 +1710,7 @@
'suggested_object_id': None,
'supported_features': 0,
'translation_key': 'power_usage',
'unique_id': 'hostname_power_usage',
'unique_id': 'uuid_power_usage',
'unit_of_measurement': <UnitOfPower.WATT: 'W'>,
})
# ---
@@ -1765,7 +1765,7 @@
'suggested_object_id': None,
'supported_features': 0,
'translation_key': 'processes',
'unique_id': 'hostname_processes_count',
'unique_id': 'uuid_processes_count',
'unit_of_measurement': None,
})
# ---
@@ -1816,7 +1816,7 @@
'suggested_object_id': None,
'supported_features': 0,
'translation_key': 'version',
'unique_id': 'hostname_version',
'unique_id': 'uuid_version',
'unit_of_measurement': None,
})
# ---
@@ -32,7 +32,7 @@
'suggested_object_id': None,
'supported_features': 0,
'translation_key': None,
'unique_id': 'hostname_update',
'unique_id': 'uuid_update',
'unit_of_measurement': None,
})
# ---
@@ -375,14 +375,13 @@ async def test_reauth_connection_closed_error(hass: HomeAssistant) -> None:
assert result2["errors"] == {"base": "cannot_connect"}
async def test_reauth_flow(hass: HomeAssistant) -> None:
async def test_reauth_flow(
hass: HomeAssistant, mock_config_entry: MockConfigEntry
) -> None:
"""Test reauth flow."""
mock_config = MockConfigEntry(
domain=DOMAIN, unique_id=FIXTURE_UUID, data=FIXTURE_USER_INPUT
)
mock_config.add_to_hass(hass)
mock_config_entry.add_to_hass(hass)
result = await mock_config.start_reauth_flow(hass)
result = await mock_config_entry.start_reauth_flow(hass)
assert result["type"] is FlowResultType.FORM
assert result["step_id"] == "authenticate"
@@ -9,6 +9,7 @@ from homeassistant.components.system_bridge.const import DOMAIN
from homeassistant.config_entries import ConfigEntryState
from homeassistant.const import CONF_API_KEY, CONF_HOST, CONF_PORT, CONF_TOKEN
from homeassistant.core import HomeAssistant
from homeassistant.helpers import entity_registry as er
from . import FIXTURE_USER_INPUT, FIXTURE_UUID
@@ -68,6 +69,59 @@ async def test_migration_minor_1_to_2(hass: HomeAssistant) -> None:
assert config_entry.state is ConfigEntryState.LOADED
@pytest.mark.usefixtures("mock_version", "mock_websocket_client")
async def test_migration_minor_2_to_3(
hass: HomeAssistant,
entity_registry: er.EntityRegistry,
) -> None:
"""Test migration of entity unique ids."""
config_entry = MockConfigEntry(
domain=DOMAIN,
unique_id=FIXTURE_UUID,
data={
CONF_TOKEN: FIXTURE_USER_INPUT[CONF_TOKEN],
CONF_HOST: "hostname",
CONF_PORT: FIXTURE_USER_INPUT[CONF_PORT],
},
version=1,
minor_version=2,
)
config_entry.add_to_hass(hass)
assert config_entry.minor_version == 2
sensor = entity_registry.async_get_or_create(
domain="sensor",
platform=DOMAIN,
unique_id="hostname_cpu_speed",
config_entry=config_entry,
original_name="hostname CPU speed",
)
notifier = entity_registry.async_get_or_create(
domain="notify",
platform=DOMAIN,
unique_id="hostname",
config_entry=config_entry,
original_name="hostname",
)
await hass.config_entries.async_setup(config_entry.entry_id)
await hass.async_block_till_done()
assert config_entry.version == 1
assert config_entry.minor_version == 3
assert (
entity_registry.async_get(sensor.entity_id).unique_id
== f"{FIXTURE_UUID}_cpu_speed"
)
assert entity_registry.async_get(notifier.entity_id).unique_id == FIXTURE_UUID
assert config_entry.state is ConfigEntryState.LOADED
async def test_migration_minor_future_version(hass: HomeAssistant) -> None:
"""Test migration."""
config_entry_data = {