mirror of
https://github.com/home-assistant/core.git
synced 2025-08-07 06:35:10 +02:00
handle device switching
This commit is contained in:
@@ -24,6 +24,7 @@ from homeassistant.helpers import (
|
|||||||
config_validation as cv,
|
config_validation as cv,
|
||||||
device_registry as dr,
|
device_registry as dr,
|
||||||
entity_platform,
|
entity_platform,
|
||||||
|
entity_registry as er,
|
||||||
)
|
)
|
||||||
from homeassistant.helpers.device_registry import DeviceInfo
|
from homeassistant.helpers.device_registry import DeviceInfo
|
||||||
from homeassistant.helpers.entity import Entity
|
from homeassistant.helpers.entity import Entity
|
||||||
@@ -53,21 +54,51 @@ def async_static_info_updated(
|
|||||||
) -> None:
|
) -> None:
|
||||||
"""Update entities of this platform when entities are listed."""
|
"""Update entities of this platform when entities are listed."""
|
||||||
current_infos = entry_data.info[info_type]
|
current_infos = entry_data.info[info_type]
|
||||||
|
device_info = entry_data.device_info
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
assert device_info is not None
|
||||||
new_infos: dict[int, EntityInfo] = {}
|
new_infos: dict[int, EntityInfo] = {}
|
||||||
add_entities: list[_EntityT] = []
|
add_entities: list[_EntityT] = []
|
||||||
|
|
||||||
|
ent_reg = er.async_get(hass)
|
||||||
|
dev_reg = dr.async_get(hass)
|
||||||
|
|
||||||
for info in infos:
|
for info in infos:
|
||||||
if not current_infos.pop(info.key, None):
|
new_infos[info.key] = info
|
||||||
# Create new entity
|
|
||||||
|
# Create new entity if it doesn't exist
|
||||||
|
if not (old_info := current_infos.pop(info.key, None)):
|
||||||
entity = entity_type(entry_data, platform.domain, info, state_type)
|
entity = entity_type(entry_data, platform.domain, info, state_type)
|
||||||
add_entities.append(entity)
|
add_entities.append(entity)
|
||||||
new_infos[info.key] = info
|
continue
|
||||||
|
|
||||||
|
# Entity exists - check if device_id has changed
|
||||||
|
if old_info.device_id == info.device_id:
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Entity has switched devices, update its device assignment
|
||||||
|
unique_id = build_unique_id(device_info.mac_address, info)
|
||||||
|
entity_id = ent_reg.async_get_entity_id(platform.domain, DOMAIN, unique_id)
|
||||||
|
if not entity_id:
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Determine the new device
|
||||||
|
if info.device_id:
|
||||||
|
# Entity now belongs to a sub device
|
||||||
|
new_device = dev_reg.async_get_device(
|
||||||
|
identifiers={(DOMAIN, f"{device_info.mac_address}_{info.device_id}")}
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
# Entity now belongs to the main device
|
||||||
|
new_device = dev_reg.async_get_device(
|
||||||
|
connections={(dr.CONNECTION_NETWORK_MAC, device_info.mac_address)}
|
||||||
|
)
|
||||||
|
|
||||||
|
if new_device:
|
||||||
|
ent_reg.async_update_entity(entity_id, device_id=new_device.id)
|
||||||
|
|
||||||
# Anything still in current_infos is now gone
|
# Anything still in current_infos is now gone
|
||||||
if current_infos:
|
if current_infos:
|
||||||
device_info = entry_data.device_info
|
|
||||||
if TYPE_CHECKING:
|
|
||||||
assert device_info is not None
|
|
||||||
entry_data.async_remove_entities(
|
entry_data.async_remove_entities(
|
||||||
hass, current_infos.values(), device_info.mac_address
|
hass, current_infos.values(), device_info.mac_address
|
||||||
)
|
)
|
||||||
|
@@ -901,3 +901,124 @@ async def test_entity_friendly_names_with_empty_device_names(
|
|||||||
state_4 = hass.states.get("binary_sensor.test_main_status")
|
state_4 = hass.states.get("binary_sensor.test_main_status")
|
||||||
assert state_4 is not None
|
assert state_4 is not None
|
||||||
assert state_4.attributes[ATTR_FRIENDLY_NAME] == "Main Device Main Status"
|
assert state_4.attributes[ATTR_FRIENDLY_NAME] == "Main Device Main Status"
|
||||||
|
|
||||||
|
|
||||||
|
async def test_entity_switches_between_devices(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
entity_registry: er.EntityRegistry,
|
||||||
|
device_registry: dr.DeviceRegistry,
|
||||||
|
mock_client: APIClient,
|
||||||
|
mock_esphome_device: MockESPHomeDeviceType,
|
||||||
|
) -> None:
|
||||||
|
"""Test that entities can switch between devices correctly."""
|
||||||
|
# Define sub devices
|
||||||
|
sub_devices = [
|
||||||
|
SubDeviceInfo(device_id=11111111, name="Sub Device 1", area_id=0),
|
||||||
|
SubDeviceInfo(device_id=22222222, name="Sub Device 2", area_id=0),
|
||||||
|
]
|
||||||
|
|
||||||
|
device_info = {
|
||||||
|
"devices": sub_devices,
|
||||||
|
}
|
||||||
|
|
||||||
|
# Create initial entity assigned to main device (no device_id)
|
||||||
|
entity_info = [
|
||||||
|
BinarySensorInfo(
|
||||||
|
object_id="sensor",
|
||||||
|
key=1,
|
||||||
|
name="Test Sensor",
|
||||||
|
unique_id="sensor",
|
||||||
|
# device_id omitted - entity belongs to main device
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
||||||
|
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 entity is on main device
|
||||||
|
main_device = device_registry.async_get_device(
|
||||||
|
connections={(dr.CONNECTION_NETWORK_MAC, device.device_info.mac_address)}
|
||||||
|
)
|
||||||
|
assert main_device is not None
|
||||||
|
|
||||||
|
sensor_entity = entity_registry.async_get("binary_sensor.test_sensor")
|
||||||
|
assert sensor_entity is not None
|
||||||
|
assert sensor_entity.device_id == main_device.id
|
||||||
|
|
||||||
|
# Test 1: Main device → Sub device 1
|
||||||
|
updated_entity_info = [
|
||||||
|
BinarySensorInfo(
|
||||||
|
object_id="sensor",
|
||||||
|
key=1,
|
||||||
|
name="Test Sensor",
|
||||||
|
unique_id="sensor",
|
||||||
|
device_id=11111111, # Now on sub device 1
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
||||||
|
# Update the entity info (this would normally come from the ESP device)
|
||||||
|
entry_data = device.entry.runtime_data
|
||||||
|
callbacks = entry_data.entity_info_callbacks.get(BinarySensorInfo, [])
|
||||||
|
for callback_ in callbacks:
|
||||||
|
callback_(updated_entity_info)
|
||||||
|
|
||||||
|
# Verify entity is now on sub device 1
|
||||||
|
sub_device_1 = device_registry.async_get_device(
|
||||||
|
identifiers={(DOMAIN, f"{device.device_info.mac_address}_11111111")}
|
||||||
|
)
|
||||||
|
assert sub_device_1 is not None
|
||||||
|
|
||||||
|
sensor_entity = entity_registry.async_get("binary_sensor.test_sensor")
|
||||||
|
assert sensor_entity is not None
|
||||||
|
assert sensor_entity.device_id == sub_device_1.id
|
||||||
|
|
||||||
|
# Test 2: Sub device 1 → Sub device 2
|
||||||
|
updated_entity_info = [
|
||||||
|
BinarySensorInfo(
|
||||||
|
object_id="sensor",
|
||||||
|
key=1,
|
||||||
|
name="Test Sensor",
|
||||||
|
unique_id="sensor",
|
||||||
|
device_id=22222222, # Now on sub device 2
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
||||||
|
for callback_ in callbacks:
|
||||||
|
callback_(updated_entity_info)
|
||||||
|
|
||||||
|
# Verify entity is now on sub device 2
|
||||||
|
sub_device_2 = device_registry.async_get_device(
|
||||||
|
identifiers={(DOMAIN, f"{device.device_info.mac_address}_22222222")}
|
||||||
|
)
|
||||||
|
assert sub_device_2 is not None
|
||||||
|
|
||||||
|
sensor_entity = entity_registry.async_get("binary_sensor.test_sensor")
|
||||||
|
assert sensor_entity is not None
|
||||||
|
assert sensor_entity.device_id == sub_device_2.id
|
||||||
|
|
||||||
|
# Test 3: Sub device 2 → Main device
|
||||||
|
updated_entity_info = [
|
||||||
|
BinarySensorInfo(
|
||||||
|
object_id="sensor",
|
||||||
|
key=1,
|
||||||
|
name="Test Sensor",
|
||||||
|
unique_id="sensor",
|
||||||
|
# device_id omitted - back to main device
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
||||||
|
for callback_ in callbacks:
|
||||||
|
callback_(updated_entity_info)
|
||||||
|
|
||||||
|
# Verify entity is back on main device
|
||||||
|
sensor_entity = entity_registry.async_get("binary_sensor.test_sensor")
|
||||||
|
assert sensor_entity is not None
|
||||||
|
assert sensor_entity.device_id == main_device.id
|
||||||
|
Reference in New Issue
Block a user