diff --git a/homeassistant/components/system_bridge/__init__.py b/homeassistant/components/system_bridge/__init__.py index c2bf39f51732..034ac51ddfa9 100644 --- a/homeassistant/components/system_bridge/__init__.py +++ b/homeassistant/components/system_bridge/__init__.py @@ -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 diff --git a/homeassistant/components/system_bridge/config_flow.py b/homeassistant/components/system_bridge/config_flow.py index d2db3a57e978..097c84d1bc4f 100644 --- a/homeassistant/components/system_bridge/config_flow.py +++ b/homeassistant/components/system_bridge/config_flow.py @@ -116,7 +116,7 @@ class SystemBridgeConfigFlow( """Handle a config flow for System Bridge.""" VERSION = 1 - MINOR_VERSION = 2 + MINOR_VERSION = 3 _name: str diff --git a/homeassistant/components/system_bridge/entity.py b/homeassistant/components/system_bridge/entity.py index 6c9ffd03a00c..f2b89789e603 100644 --- a/homeassistant/components/system_bridge/entity.py +++ b/homeassistant/components/system_bridge/entity.py @@ -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)}, diff --git a/tests/components/system_bridge/snapshots/test_binary_sensor.ambr b/tests/components/system_bridge/snapshots/test_binary_sensor.ambr index 464440261a0f..8a5a47702d45 100644 --- a/tests/components/system_bridge/snapshots/test_binary_sensor.ambr +++ b/tests/components/system_bridge/snapshots/test_binary_sensor.ambr @@ -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, }) # --- diff --git a/tests/components/system_bridge/snapshots/test_notify.ambr b/tests/components/system_bridge/snapshots/test_notify.ambr index 4e13f5f7fd26..cf70f4a8a2de 100644 --- a/tests/components/system_bridge/snapshots/test_notify.ambr +++ b/tests/components/system_bridge/snapshots/test_notify.ambr @@ -32,7 +32,7 @@ 'suggested_object_id': None, 'supported_features': , 'translation_key': None, - 'unique_id': 'hostname', + 'unique_id': 'uuid', 'unit_of_measurement': None, }) # --- diff --git a/tests/components/system_bridge/snapshots/test_sensor.ambr b/tests/components/system_bridge/snapshots/test_sensor.ambr index 5c54045ab793..9079fb1fcd5c 100644 --- a/tests/components/system_bridge/snapshots/test_sensor.ambr +++ b/tests/components/system_bridge/snapshots/test_sensor.ambr @@ -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': , }) # --- @@ -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': , }) # --- @@ -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': , }) # --- @@ -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': , }) # --- @@ -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': , }) # --- @@ -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': , }) # --- @@ -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': , }) # --- @@ -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': , }) # --- @@ -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': , }) # --- @@ -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': , }) # --- @@ -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': , }) # --- @@ -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': , }) # --- @@ -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': , }) # --- @@ -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': , }) # --- @@ -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': , }) # --- @@ -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, }) # --- diff --git a/tests/components/system_bridge/snapshots/test_update.ambr b/tests/components/system_bridge/snapshots/test_update.ambr index c61740232ecd..bb36270eb6d8 100644 --- a/tests/components/system_bridge/snapshots/test_update.ambr +++ b/tests/components/system_bridge/snapshots/test_update.ambr @@ -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, }) # --- diff --git a/tests/components/system_bridge/test_config_flow.py b/tests/components/system_bridge/test_config_flow.py index ada44de2d12b..1367744c02f0 100644 --- a/tests/components/system_bridge/test_config_flow.py +++ b/tests/components/system_bridge/test_config_flow.py @@ -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" diff --git a/tests/components/system_bridge/test_init.py b/tests/components/system_bridge/test_init.py index a129818afe11..72296da8bd41 100644 --- a/tests/components/system_bridge/test_init.py +++ b/tests/components/system_bridge/test_init.py @@ -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 = {