mirror of
https://github.com/home-assistant/core.git
synced 2025-07-30 10:48:01 +02:00
handle device_id being changed
This commit is contained in:
@ -33,12 +33,7 @@ from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
|||||||
from .const import DOMAIN
|
from .const import DOMAIN
|
||||||
|
|
||||||
# Import config flow so that it's added to the registry
|
# Import config flow so that it's added to the registry
|
||||||
from .entry_data import (
|
from .entry_data import ESPHomeConfigEntry, RuntimeEntryData, build_device_unique_id
|
||||||
ESPHomeConfigEntry,
|
|
||||||
RuntimeEntryData,
|
|
||||||
build_device_unique_id,
|
|
||||||
build_unique_id,
|
|
||||||
)
|
|
||||||
from .enum_mapper import EsphomeEnumMapper
|
from .enum_mapper import EsphomeEnumMapper
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
@ -85,30 +80,24 @@ def async_static_info_updated(
|
|||||||
|
|
||||||
# Entity has switched devices, need to migrate unique_id
|
# Entity has switched devices, need to migrate unique_id
|
||||||
old_unique_id = build_device_unique_id(device_info.mac_address, old_info)
|
old_unique_id = build_device_unique_id(device_info.mac_address, old_info)
|
||||||
new_unique_id = build_device_unique_id(device_info.mac_address, info)
|
|
||||||
entity_id = ent_reg.async_get_entity_id(platform.domain, DOMAIN, old_unique_id)
|
entity_id = ent_reg.async_get_entity_id(platform.domain, DOMAIN, old_unique_id)
|
||||||
|
|
||||||
# If not found by exact match, search for entity with base unique_id
|
# If entity not found in registry, re-add it
|
||||||
# This happens when the YAML config was modified and renamed the device_id
|
# This happens when the device_id changed and the old device was deleted
|
||||||
if entity_id is None:
|
if entity_id is None:
|
||||||
base_unique_id = build_unique_id(device_info.mac_address, info)
|
_LOGGER.info(
|
||||||
# Search all entities for this config entry
|
"Entity with old unique_id %s not found in registry after device_id "
|
||||||
for entry in ent_reg.entities.get_entries_for_config_entry_id(
|
"changed from %s to %s, re-adding entity",
|
||||||
entry_data.entry_id
|
old_unique_id,
|
||||||
):
|
old_info.device_id,
|
||||||
if entry.platform != DOMAIN or entry.domain != platform.domain:
|
info.device_id,
|
||||||
continue
|
)
|
||||||
# Check if it's the exact base unique_id or starts with base_unique_id@
|
entity = entity_type(entry_data, platform.domain, info, state_type)
|
||||||
if entry.unique_id == base_unique_id or (
|
add_entities.append(entity)
|
||||||
entry.unique_id and entry.unique_id.startswith(f"{base_unique_id}@")
|
continue
|
||||||
):
|
|
||||||
entity_id = entry.entity_id
|
|
||||||
break
|
|
||||||
|
|
||||||
# Entity must exist in registry since we found it in current_infos
|
|
||||||
assert entity_id is not None
|
|
||||||
|
|
||||||
updates: dict[str, Any] = {}
|
updates: dict[str, Any] = {}
|
||||||
|
new_unique_id = build_device_unique_id(device_info.mac_address, info)
|
||||||
|
|
||||||
# Update unique_id if it changed
|
# Update unique_id if it changed
|
||||||
if old_unique_id != new_unique_id:
|
if old_unique_id != new_unique_id:
|
||||||
|
@ -1471,3 +1471,129 @@ async def test_unique_id_migration_between_sub_devices(
|
|||||||
)
|
)
|
||||||
assert bedroom_device is not None
|
assert bedroom_device is not None
|
||||||
assert entity_entry.device_id == bedroom_device.id
|
assert entity_entry.device_id == bedroom_device.id
|
||||||
|
|
||||||
|
|
||||||
|
async def test_entity_device_id_rename_in_yaml(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
entity_registry: er.EntityRegistry,
|
||||||
|
device_registry: dr.DeviceRegistry,
|
||||||
|
mock_client: APIClient,
|
||||||
|
mock_esphome_device: MockESPHomeDeviceType,
|
||||||
|
) -> None:
|
||||||
|
"""Test that entities are re-added as new when user renames device_id in YAML config."""
|
||||||
|
# Initial setup: entity on sub-device with device_id 11111111
|
||||||
|
sub_devices = [
|
||||||
|
SubDeviceInfo(device_id=11111111, name="old_device", area_id=0),
|
||||||
|
]
|
||||||
|
|
||||||
|
device_info = {
|
||||||
|
"name": "test",
|
||||||
|
"devices": sub_devices,
|
||||||
|
}
|
||||||
|
|
||||||
|
# Entity on sub-device
|
||||||
|
entity_info = [
|
||||||
|
BinarySensorInfo(
|
||||||
|
object_id="sensor",
|
||||||
|
key=1,
|
||||||
|
name="Sensor",
|
||||||
|
unique_id="unused",
|
||||||
|
device_id=11111111,
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
||||||
|
states = [
|
||||||
|
BinarySensorState(key=1, state=True, missing_state=False),
|
||||||
|
]
|
||||||
|
|
||||||
|
device = await mock_esphome_device(
|
||||||
|
mock_client=mock_client,
|
||||||
|
device_info=device_info,
|
||||||
|
entity_info=entity_info,
|
||||||
|
states=states,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Verify initial entity setup
|
||||||
|
state = hass.states.get("binary_sensor.old_device_sensor")
|
||||||
|
assert state is not None
|
||||||
|
assert state.state == STATE_ON
|
||||||
|
|
||||||
|
# Wait for entity to be registered
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
# Get the entity from registry
|
||||||
|
entity_entry = entity_registry.async_get("binary_sensor.old_device_sensor")
|
||||||
|
assert entity_entry is not None
|
||||||
|
initial_unique_id = entity_entry.unique_id
|
||||||
|
# Should have @11111111 suffix
|
||||||
|
assert "@11111111" in initial_unique_id
|
||||||
|
|
||||||
|
# Simulate user renaming device_id in YAML config
|
||||||
|
# The device_id hash changes from 11111111 to 99999999
|
||||||
|
# This is treated as a completely new device
|
||||||
|
renamed_sub_devices = [
|
||||||
|
SubDeviceInfo(device_id=99999999, name="renamed_device", area_id=0),
|
||||||
|
]
|
||||||
|
|
||||||
|
# Get the config entry from hass
|
||||||
|
entries = hass.config_entries.async_entries(DOMAIN)
|
||||||
|
assert len(entries) == 1
|
||||||
|
entry = entries[0]
|
||||||
|
|
||||||
|
# Update device_id_to_name mapping
|
||||||
|
entry_data = entry.runtime_data
|
||||||
|
entry_data.device_id_to_name = {
|
||||||
|
sub_device.device_id: sub_device.name for sub_device in renamed_sub_devices
|
||||||
|
}
|
||||||
|
|
||||||
|
# Create new DeviceInfo with renamed device
|
||||||
|
current_device_info = mock_client.device_info.return_value
|
||||||
|
device_info_dict = asdict(current_device_info)
|
||||||
|
device_info_dict["devices"] = renamed_sub_devices
|
||||||
|
new_device_info = DeviceInfo(**device_info_dict)
|
||||||
|
mock_client.device_info.return_value = new_device_info
|
||||||
|
|
||||||
|
# Entity info now has the new device_id
|
||||||
|
new_entity_info = [
|
||||||
|
BinarySensorInfo(
|
||||||
|
object_id="sensor", # Same object_id
|
||||||
|
key=1, # Same key
|
||||||
|
name="Sensor",
|
||||||
|
unique_id="unused",
|
||||||
|
device_id=99999999, # New device_id after rename
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
||||||
|
# Update the entity info
|
||||||
|
mock_client.list_entities_services = AsyncMock(return_value=(new_entity_info, []))
|
||||||
|
|
||||||
|
# Trigger a reconnect to simulate the YAML config change
|
||||||
|
await device.mock_disconnect(expected_disconnect=False)
|
||||||
|
await device.mock_connect()
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
# The old entity should be gone (device was deleted)
|
||||||
|
state = hass.states.get("binary_sensor.old_device_sensor")
|
||||||
|
assert state is None
|
||||||
|
|
||||||
|
# A new entity should exist with a new entity_id based on the new device name
|
||||||
|
# This is a completely new entity, not a migrated one
|
||||||
|
state = hass.states.get("binary_sensor.renamed_device_sensor")
|
||||||
|
assert state is not None
|
||||||
|
assert state.state == STATE_ON
|
||||||
|
|
||||||
|
# Get the new entity from registry
|
||||||
|
entity_entry = entity_registry.async_get("binary_sensor.renamed_device_sensor")
|
||||||
|
assert entity_entry is not None
|
||||||
|
|
||||||
|
# Unique ID should have the new device_id
|
||||||
|
base_unique_id = initial_unique_id.replace("@11111111", "")
|
||||||
|
expected_unique_id = f"{base_unique_id}@99999999"
|
||||||
|
assert entity_entry.unique_id == expected_unique_id
|
||||||
|
|
||||||
|
# Entity should be associated with the new device
|
||||||
|
renamed_device = device_registry.async_get_device(
|
||||||
|
identifiers={(DOMAIN, f"{device.device_info.mac_address}_99999999")}
|
||||||
|
)
|
||||||
|
assert renamed_device is not None
|
||||||
|
assert entity_entry.device_id == renamed_device.id
|
||||||
|
Reference in New Issue
Block a user