Migrate existing zwave_js entities if endpoint has changed (#48963)
* Migrate existing zwave_js entities if endpoint has changed * better function name * cleanup code * return as early as we can * use defaultdict instead of setdefault * PR comments * re-add missing logic * set defaultdict outside of for loop * additional cleanup * parametrize tests * fix reinterview logic * test that we skip migration when multiple entities are found * Update tests/components/zwave_js/test_init.py Co-authored-by: Martin Hjelmare <marhje52@gmail.com> Co-authored-by: Martin Hjelmare <marhje52@gmail.com>
This commit is contained in:
parent
de569982a4
commit
fe6d6895aa
3 changed files with 355 additions and 183 deletions
|
@ -2,6 +2,7 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
from collections import defaultdict
|
||||
from typing import Callable
|
||||
|
||||
from async_timeout import timeout
|
||||
|
@ -87,7 +88,7 @@ def register_node_in_dev_reg(
|
|||
dev_reg: device_registry.DeviceRegistry,
|
||||
client: ZwaveClient,
|
||||
node: ZwaveNode,
|
||||
) -> None:
|
||||
) -> device_registry.DeviceEntry:
|
||||
"""Register node in dev reg."""
|
||||
params = {
|
||||
"config_entry_id": entry.entry_id,
|
||||
|
@ -103,6 +104,10 @@ def register_node_in_dev_reg(
|
|||
|
||||
async_dispatcher_send(hass, EVENT_DEVICE_ADDED_TO_REGISTRY, device)
|
||||
|
||||
# We can assert here because we will always get a device
|
||||
assert device
|
||||
return device
|
||||
|
||||
|
||||
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
"""Set up Z-Wave JS from a config entry."""
|
||||
|
@ -120,6 +125,8 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
|||
entry_hass_data[DATA_UNSUBSCRIBE] = unsubscribe_callbacks
|
||||
entry_hass_data[DATA_PLATFORM_SETUP] = {}
|
||||
|
||||
registered_unique_ids: dict[str, dict[str, set[str]]] = defaultdict(dict)
|
||||
|
||||
async def async_on_node_ready(node: ZwaveNode) -> None:
|
||||
"""Handle node ready event."""
|
||||
LOGGER.debug("Processing node %s", node)
|
||||
|
@ -127,26 +134,37 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
|||
platform_setup_tasks = entry_hass_data[DATA_PLATFORM_SETUP]
|
||||
|
||||
# register (or update) node in device registry
|
||||
register_node_in_dev_reg(hass, entry, dev_reg, client, node)
|
||||
device = register_node_in_dev_reg(hass, entry, dev_reg, client, node)
|
||||
# We only want to create the defaultdict once, even on reinterviews
|
||||
if device.id not in registered_unique_ids:
|
||||
registered_unique_ids[device.id] = defaultdict(set)
|
||||
|
||||
# run discovery on all node values and create/update entities
|
||||
for disc_info in async_discover_values(node):
|
||||
platform = disc_info.platform
|
||||
|
||||
# This migration logic was added in 2021.3 to handle a breaking change to
|
||||
# the value_id format. Some time in the future, this call (as well as the
|
||||
# helper functions) can be removed.
|
||||
async_migrate_discovered_value(ent_reg, client, disc_info)
|
||||
if disc_info.platform not in platform_setup_tasks:
|
||||
platform_setup_tasks[disc_info.platform] = hass.async_create_task(
|
||||
hass.config_entries.async_forward_entry_setup(
|
||||
entry, disc_info.platform
|
||||
)
|
||||
async_migrate_discovered_value(
|
||||
hass,
|
||||
ent_reg,
|
||||
registered_unique_ids[device.id][platform],
|
||||
device,
|
||||
client,
|
||||
disc_info,
|
||||
)
|
||||
|
||||
if platform not in platform_setup_tasks:
|
||||
platform_setup_tasks[platform] = hass.async_create_task(
|
||||
hass.config_entries.async_forward_entry_setup(entry, platform)
|
||||
)
|
||||
|
||||
await platform_setup_tasks[disc_info.platform]
|
||||
await platform_setup_tasks[platform]
|
||||
|
||||
LOGGER.debug("Discovered entity: %s", disc_info)
|
||||
async_dispatcher_send(
|
||||
hass, f"{DOMAIN}_{entry.entry_id}_add_{disc_info.platform}", disc_info
|
||||
hass, f"{DOMAIN}_{entry.entry_id}_add_{platform}", disc_info
|
||||
)
|
||||
|
||||
# add listener for stateless node value notification events
|
||||
|
@ -189,6 +207,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
|||
device = dev_reg.async_get_device({dev_id})
|
||||
# note: removal of entity registry entry is handled by core
|
||||
dev_reg.async_remove_device(device.id) # type: ignore
|
||||
registered_unique_ids.pop(device.id, None) # type: ignore
|
||||
|
||||
@callback
|
||||
def async_on_value_notification(notification: ValueNotification) -> None:
|
||||
|
|
|
@ -1,13 +1,20 @@
|
|||
"""Functions used to migrate unique IDs for Z-Wave JS entities."""
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass
|
||||
import logging
|
||||
|
||||
from zwave_js_server.client import Client as ZwaveClient
|
||||
from zwave_js_server.model.value import Value as ZwaveValue
|
||||
|
||||
from homeassistant.core import callback
|
||||
from homeassistant.helpers.entity_registry import EntityRegistry
|
||||
from homeassistant.const import STATE_UNAVAILABLE
|
||||
from homeassistant.core import HomeAssistant, callback
|
||||
from homeassistant.helpers.device_registry import DeviceEntry
|
||||
from homeassistant.helpers.entity_registry import (
|
||||
EntityRegistry,
|
||||
RegistryEntry,
|
||||
async_entries_for_device,
|
||||
)
|
||||
|
||||
from .const import DOMAIN
|
||||
from .discovery import ZwaveDiscoveryInfo
|
||||
|
@ -16,8 +23,88 @@ from .helpers import get_unique_id
|
|||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@dataclass
|
||||
class ValueID:
|
||||
"""Class to represent a Value ID."""
|
||||
|
||||
command_class: str
|
||||
endpoint: str
|
||||
property_: str
|
||||
property_key: str | None = None
|
||||
|
||||
@staticmethod
|
||||
def from_unique_id(unique_id: str) -> ValueID:
|
||||
"""
|
||||
Get a ValueID from a unique ID.
|
||||
|
||||
This also works for Notification CC Binary Sensors which have their own unique ID
|
||||
format.
|
||||
"""
|
||||
return ValueID.from_string_id(unique_id.split(".")[1])
|
||||
|
||||
@staticmethod
|
||||
def from_string_id(value_id_str: str) -> ValueID:
|
||||
"""Get a ValueID from a string representation of the value ID."""
|
||||
parts = value_id_str.split("-")
|
||||
property_key = parts[4] if len(parts) > 4 else None
|
||||
return ValueID(parts[1], parts[2], parts[3], property_key=property_key)
|
||||
|
||||
def is_same_value_different_endpoints(self, other: ValueID) -> bool:
|
||||
"""Return whether two value IDs are the same excluding endpoint."""
|
||||
return (
|
||||
self.command_class == other.command_class
|
||||
and self.property_ == other.property_
|
||||
and self.property_key == other.property_key
|
||||
and self.endpoint != other.endpoint
|
||||
)
|
||||
|
||||
|
||||
@callback
|
||||
def async_migrate_entity(
|
||||
def async_migrate_old_entity(
|
||||
hass: HomeAssistant,
|
||||
ent_reg: EntityRegistry,
|
||||
registered_unique_ids: set[str],
|
||||
platform: str,
|
||||
device: DeviceEntry,
|
||||
unique_id: str,
|
||||
) -> None:
|
||||
"""Migrate existing entity if current one can't be found and an old one exists."""
|
||||
# If we can find an existing entity with this unique ID, there's nothing to migrate
|
||||
if ent_reg.async_get_entity_id(platform, DOMAIN, unique_id):
|
||||
return
|
||||
|
||||
value_id = ValueID.from_unique_id(unique_id)
|
||||
|
||||
# Look for existing entities in the registry that could be the same value but on
|
||||
# a different endpoint
|
||||
existing_entity_entries: list[RegistryEntry] = []
|
||||
for entry in async_entries_for_device(ent_reg, device.id):
|
||||
# If entity is not in the domain for this discovery info or entity has already
|
||||
# been processed, skip it
|
||||
if entry.domain != platform or entry.unique_id in registered_unique_ids:
|
||||
continue
|
||||
|
||||
old_ent_value_id = ValueID.from_unique_id(entry.unique_id)
|
||||
|
||||
if value_id.is_same_value_different_endpoints(old_ent_value_id):
|
||||
existing_entity_entries.append(entry)
|
||||
# We can return early if we get more than one result
|
||||
if len(existing_entity_entries) > 1:
|
||||
return
|
||||
|
||||
# If we couldn't find any results, return early
|
||||
if not existing_entity_entries:
|
||||
return
|
||||
|
||||
entry = existing_entity_entries[0]
|
||||
state = hass.states.get(entry.entity_id)
|
||||
|
||||
if not state or state.state == STATE_UNAVAILABLE:
|
||||
async_migrate_unique_id(ent_reg, platform, entry.unique_id, unique_id)
|
||||
|
||||
|
||||
@callback
|
||||
def async_migrate_unique_id(
|
||||
ent_reg: EntityRegistry, platform: str, old_unique_id: str, new_unique_id: str
|
||||
) -> None:
|
||||
"""Check if entity with old unique ID exists, and if so migrate it to new ID."""
|
||||
|
@ -29,10 +116,7 @@ def async_migrate_entity(
|
|||
new_unique_id,
|
||||
)
|
||||
try:
|
||||
ent_reg.async_update_entity(
|
||||
entity_id,
|
||||
new_unique_id=new_unique_id,
|
||||
)
|
||||
ent_reg.async_update_entity(entity_id, new_unique_id=new_unique_id)
|
||||
except ValueError:
|
||||
_LOGGER.debug(
|
||||
(
|
||||
|
@ -46,43 +130,87 @@ def async_migrate_entity(
|
|||
|
||||
@callback
|
||||
def async_migrate_discovered_value(
|
||||
ent_reg: EntityRegistry, client: ZwaveClient, disc_info: ZwaveDiscoveryInfo
|
||||
hass: HomeAssistant,
|
||||
ent_reg: EntityRegistry,
|
||||
registered_unique_ids: set[str],
|
||||
device: DeviceEntry,
|
||||
client: ZwaveClient,
|
||||
disc_info: ZwaveDiscoveryInfo,
|
||||
) -> None:
|
||||
"""Migrate unique ID for entity/entities tied to discovered value."""
|
||||
|
||||
new_unique_id = get_unique_id(
|
||||
client.driver.controller.home_id,
|
||||
disc_info.primary_value.value_id,
|
||||
)
|
||||
|
||||
# On reinterviews, there is no point in going through this logic again for already
|
||||
# discovered values
|
||||
if new_unique_id in registered_unique_ids:
|
||||
return
|
||||
|
||||
# Migration logic was added in 2021.3 to handle a breaking change to the value_id
|
||||
# format. Some time in the future, the logic to migrate unique IDs can be removed.
|
||||
|
||||
# 2021.2.*, 2021.3.0b0, and 2021.3.0 formats
|
||||
for value_id in get_old_value_ids(disc_info.primary_value):
|
||||
old_unique_id = get_unique_id(
|
||||
old_unique_ids = [
|
||||
get_unique_id(
|
||||
client.driver.controller.home_id,
|
||||
value_id,
|
||||
)
|
||||
# Most entities have the same ID format, but notification binary sensors
|
||||
# have a state key in their ID so we need to handle them differently
|
||||
if (
|
||||
disc_info.platform == "binary_sensor"
|
||||
and disc_info.platform_hint == "notification"
|
||||
):
|
||||
for state_key in disc_info.primary_value.metadata.states:
|
||||
# ignore idle key (0)
|
||||
if state_key == "0":
|
||||
continue
|
||||
for value_id in get_old_value_ids(disc_info.primary_value)
|
||||
]
|
||||
|
||||
async_migrate_entity(
|
||||
if (
|
||||
disc_info.platform == "binary_sensor"
|
||||
and disc_info.platform_hint == "notification"
|
||||
):
|
||||
for state_key in disc_info.primary_value.metadata.states:
|
||||
# ignore idle key (0)
|
||||
if state_key == "0":
|
||||
continue
|
||||
|
||||
new_bin_sensor_unique_id = f"{new_unique_id}.{state_key}"
|
||||
|
||||
# On reinterviews, there is no point in going through this logic again
|
||||
# for already discovered values
|
||||
if new_bin_sensor_unique_id in registered_unique_ids:
|
||||
continue
|
||||
|
||||
# Unique ID migration
|
||||
for old_unique_id in old_unique_ids:
|
||||
async_migrate_unique_id(
|
||||
ent_reg,
|
||||
disc_info.platform,
|
||||
f"{old_unique_id}.{state_key}",
|
||||
f"{new_unique_id}.{state_key}",
|
||||
new_bin_sensor_unique_id,
|
||||
)
|
||||
|
||||
# Once we've iterated through all state keys, we can move on to the
|
||||
# next item
|
||||
continue
|
||||
# Migrate entities in case upstream changes cause endpoint change
|
||||
async_migrate_old_entity(
|
||||
hass,
|
||||
ent_reg,
|
||||
registered_unique_ids,
|
||||
disc_info.platform,
|
||||
device,
|
||||
new_bin_sensor_unique_id,
|
||||
)
|
||||
registered_unique_ids.add(new_bin_sensor_unique_id)
|
||||
|
||||
async_migrate_entity(ent_reg, disc_info.platform, old_unique_id, new_unique_id)
|
||||
# Once we've iterated through all state keys, we are done
|
||||
return
|
||||
|
||||
# Unique ID migration
|
||||
for old_unique_id in old_unique_ids:
|
||||
async_migrate_unique_id(
|
||||
ent_reg, disc_info.platform, old_unique_id, new_unique_id
|
||||
)
|
||||
|
||||
# Migrate entities in case upstream changes cause endpoint change
|
||||
async_migrate_old_entity(
|
||||
hass, ent_reg, registered_unique_ids, disc_info.platform, device, new_unique_id
|
||||
)
|
||||
registered_unique_ids.add(new_unique_id)
|
||||
|
||||
|
||||
@callback
|
||||
|
|
|
@ -162,19 +162,27 @@ async def test_unique_id_migration_dupes(
|
|||
entity_entry = ent_reg.async_get(AIR_TEMPERATURE_SENSOR)
|
||||
new_unique_id = f"{client.driver.controller.home_id}.52-49-0-Air temperature"
|
||||
assert entity_entry.unique_id == new_unique_id
|
||||
|
||||
assert ent_reg.async_get(f"{AIR_TEMPERATURE_SENSOR}_1") is None
|
||||
assert ent_reg.async_get_entity_id("sensor", DOMAIN, old_unique_id_1) is None
|
||||
assert ent_reg.async_get_entity_id("sensor", DOMAIN, old_unique_id_2) is None
|
||||
|
||||
|
||||
async def test_unique_id_migration_v1(hass, multisensor_6_state, client, integration):
|
||||
"""Test unique ID is migrated from old format to new (version 1)."""
|
||||
@pytest.mark.parametrize(
|
||||
"id",
|
||||
[
|
||||
("52.52-49-00-Air temperature-00"),
|
||||
("52.52-49-0-Air temperature-00-00"),
|
||||
("52-49-0-Air temperature-00-00"),
|
||||
],
|
||||
)
|
||||
async def test_unique_id_migration(hass, multisensor_6_state, client, integration, id):
|
||||
"""Test unique ID is migrated from old format to new."""
|
||||
ent_reg = er.async_get(hass)
|
||||
|
||||
# Migrate version 1
|
||||
entity_name = AIR_TEMPERATURE_SENSOR.split(".")[1]
|
||||
|
||||
# Create entity RegistryEntry using old unique ID format
|
||||
old_unique_id = f"{client.driver.controller.home_id}.52.52-49-00-Air temperature-00"
|
||||
old_unique_id = f"{client.driver.controller.home_id}.{id}"
|
||||
entity_entry = ent_reg.async_get_or_create(
|
||||
"sensor",
|
||||
DOMAIN,
|
||||
|
@ -197,157 +205,28 @@ async def test_unique_id_migration_v1(hass, multisensor_6_state, client, integra
|
|||
entity_entry = ent_reg.async_get(AIR_TEMPERATURE_SENSOR)
|
||||
new_unique_id = f"{client.driver.controller.home_id}.52-49-0-Air temperature"
|
||||
assert entity_entry.unique_id == new_unique_id
|
||||
assert ent_reg.async_get_entity_id("sensor", DOMAIN, old_unique_id) is None
|
||||
|
||||
|
||||
async def test_unique_id_migration_v2(hass, multisensor_6_state, client, integration):
|
||||
"""Test unique ID is migrated from old format to new (version 2)."""
|
||||
ent_reg = er.async_get(hass)
|
||||
# Migrate version 2
|
||||
ILLUMINANCE_SENSOR = "sensor.multisensor_6_illuminance"
|
||||
entity_name = ILLUMINANCE_SENSOR.split(".")[1]
|
||||
|
||||
# Create entity RegistryEntry using old unique ID format
|
||||
old_unique_id = f"{client.driver.controller.home_id}.52.52-49-0-Illuminance-00-00"
|
||||
entity_entry = ent_reg.async_get_or_create(
|
||||
"sensor",
|
||||
DOMAIN,
|
||||
old_unique_id,
|
||||
suggested_object_id=entity_name,
|
||||
config_entry=integration,
|
||||
original_name=entity_name,
|
||||
)
|
||||
assert entity_entry.entity_id == ILLUMINANCE_SENSOR
|
||||
assert entity_entry.unique_id == old_unique_id
|
||||
|
||||
# Add a ready node, unique ID should be migrated
|
||||
node = Node(client, multisensor_6_state)
|
||||
event = {"node": node}
|
||||
|
||||
client.driver.controller.emit("node added", event)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
# Check that new RegistryEntry is using new unique ID format
|
||||
entity_entry = ent_reg.async_get(ILLUMINANCE_SENSOR)
|
||||
new_unique_id = f"{client.driver.controller.home_id}.52-49-0-Illuminance"
|
||||
assert entity_entry.unique_id == new_unique_id
|
||||
|
||||
|
||||
async def test_unique_id_migration_v3(hass, multisensor_6_state, client, integration):
|
||||
"""Test unique ID is migrated from old format to new (version 3)."""
|
||||
ent_reg = er.async_get(hass)
|
||||
# Migrate version 2
|
||||
ILLUMINANCE_SENSOR = "sensor.multisensor_6_illuminance"
|
||||
entity_name = ILLUMINANCE_SENSOR.split(".")[1]
|
||||
|
||||
# Create entity RegistryEntry using old unique ID format
|
||||
old_unique_id = f"{client.driver.controller.home_id}.52-49-0-Illuminance-00-00"
|
||||
entity_entry = ent_reg.async_get_or_create(
|
||||
"sensor",
|
||||
DOMAIN,
|
||||
old_unique_id,
|
||||
suggested_object_id=entity_name,
|
||||
config_entry=integration,
|
||||
original_name=entity_name,
|
||||
)
|
||||
assert entity_entry.entity_id == ILLUMINANCE_SENSOR
|
||||
assert entity_entry.unique_id == old_unique_id
|
||||
|
||||
# Add a ready node, unique ID should be migrated
|
||||
node = Node(client, multisensor_6_state)
|
||||
event = {"node": node}
|
||||
|
||||
client.driver.controller.emit("node added", event)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
# Check that new RegistryEntry is using new unique ID format
|
||||
entity_entry = ent_reg.async_get(ILLUMINANCE_SENSOR)
|
||||
new_unique_id = f"{client.driver.controller.home_id}.52-49-0-Illuminance"
|
||||
assert entity_entry.unique_id == new_unique_id
|
||||
|
||||
|
||||
async def test_unique_id_migration_property_key_v1(
|
||||
hass, hank_binary_switch_state, client, integration
|
||||
@pytest.mark.parametrize(
|
||||
"id",
|
||||
[
|
||||
("32.32-50-00-value-W_Consumed"),
|
||||
("32.32-50-0-value-66049-W_Consumed"),
|
||||
("32-50-0-value-66049-W_Consumed"),
|
||||
],
|
||||
)
|
||||
async def test_unique_id_migration_property_key(
|
||||
hass, hank_binary_switch_state, client, integration, id
|
||||
):
|
||||
"""Test unique ID with property key is migrated from old format to new (version 1)."""
|
||||
"""Test unique ID with property key is migrated from old format to new."""
|
||||
ent_reg = er.async_get(hass)
|
||||
|
||||
SENSOR_NAME = "sensor.smart_plug_with_two_usb_ports_value_electric_consumed"
|
||||
entity_name = SENSOR_NAME.split(".")[1]
|
||||
|
||||
# Create entity RegistryEntry using old unique ID format
|
||||
old_unique_id = f"{client.driver.controller.home_id}.32.32-50-00-value-W_Consumed"
|
||||
entity_entry = ent_reg.async_get_or_create(
|
||||
"sensor",
|
||||
DOMAIN,
|
||||
old_unique_id,
|
||||
suggested_object_id=entity_name,
|
||||
config_entry=integration,
|
||||
original_name=entity_name,
|
||||
)
|
||||
assert entity_entry.entity_id == SENSOR_NAME
|
||||
assert entity_entry.unique_id == old_unique_id
|
||||
|
||||
# Add a ready node, unique ID should be migrated
|
||||
node = Node(client, hank_binary_switch_state)
|
||||
event = {"node": node}
|
||||
|
||||
client.driver.controller.emit("node added", event)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
# Check that new RegistryEntry is using new unique ID format
|
||||
entity_entry = ent_reg.async_get(SENSOR_NAME)
|
||||
new_unique_id = f"{client.driver.controller.home_id}.32-50-0-value-66049"
|
||||
assert entity_entry.unique_id == new_unique_id
|
||||
|
||||
|
||||
async def test_unique_id_migration_property_key_v2(
|
||||
hass, hank_binary_switch_state, client, integration
|
||||
):
|
||||
"""Test unique ID with property key is migrated from old format to new (version 2)."""
|
||||
ent_reg = er.async_get(hass)
|
||||
|
||||
SENSOR_NAME = "sensor.smart_plug_with_two_usb_ports_value_electric_consumed"
|
||||
entity_name = SENSOR_NAME.split(".")[1]
|
||||
|
||||
# Create entity RegistryEntry using old unique ID format
|
||||
old_unique_id = (
|
||||
f"{client.driver.controller.home_id}.32.32-50-0-value-66049-W_Consumed"
|
||||
)
|
||||
entity_entry = ent_reg.async_get_or_create(
|
||||
"sensor",
|
||||
DOMAIN,
|
||||
old_unique_id,
|
||||
suggested_object_id=entity_name,
|
||||
config_entry=integration,
|
||||
original_name=entity_name,
|
||||
)
|
||||
assert entity_entry.entity_id == SENSOR_NAME
|
||||
assert entity_entry.unique_id == old_unique_id
|
||||
|
||||
# Add a ready node, unique ID should be migrated
|
||||
node = Node(client, hank_binary_switch_state)
|
||||
event = {"node": node}
|
||||
|
||||
client.driver.controller.emit("node added", event)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
# Check that new RegistryEntry is using new unique ID format
|
||||
entity_entry = ent_reg.async_get(SENSOR_NAME)
|
||||
new_unique_id = f"{client.driver.controller.home_id}.32-50-0-value-66049"
|
||||
assert entity_entry.unique_id == new_unique_id
|
||||
|
||||
|
||||
async def test_unique_id_migration_property_key_v3(
|
||||
hass, hank_binary_switch_state, client, integration
|
||||
):
|
||||
"""Test unique ID with property key is migrated from old format to new (version 3)."""
|
||||
ent_reg = er.async_get(hass)
|
||||
|
||||
SENSOR_NAME = "sensor.smart_plug_with_two_usb_ports_value_electric_consumed"
|
||||
entity_name = SENSOR_NAME.split(".")[1]
|
||||
|
||||
# Create entity RegistryEntry using old unique ID format
|
||||
old_unique_id = f"{client.driver.controller.home_id}.32-50-0-value-66049-W_Consumed"
|
||||
old_unique_id = f"{client.driver.controller.home_id}.{id}"
|
||||
entity_entry = ent_reg.async_get_or_create(
|
||||
"sensor",
|
||||
DOMAIN,
|
||||
|
@ -370,6 +249,7 @@ async def test_unique_id_migration_property_key_v3(
|
|||
entity_entry = ent_reg.async_get(SENSOR_NAME)
|
||||
new_unique_id = f"{client.driver.controller.home_id}.32-50-0-value-66049"
|
||||
assert entity_entry.unique_id == new_unique_id
|
||||
assert ent_reg.async_get_entity_id("sensor", DOMAIN, old_unique_id) is None
|
||||
|
||||
|
||||
async def test_unique_id_migration_notification_binary_sensor(
|
||||
|
@ -404,6 +284,151 @@ async def test_unique_id_migration_notification_binary_sensor(
|
|||
entity_entry = ent_reg.async_get(NOTIFICATION_MOTION_BINARY_SENSOR)
|
||||
new_unique_id = f"{client.driver.controller.home_id}.52-113-0-Home Security-Motion sensor status.8"
|
||||
assert entity_entry.unique_id == new_unique_id
|
||||
assert ent_reg.async_get_entity_id("binary_sensor", DOMAIN, old_unique_id) is None
|
||||
|
||||
|
||||
async def test_old_entity_migration(
|
||||
hass, hank_binary_switch_state, client, integration
|
||||
):
|
||||
"""Test old entity on a different endpoint is migrated to a new one."""
|
||||
node = Node(client, hank_binary_switch_state)
|
||||
|
||||
ent_reg = er.async_get(hass)
|
||||
dev_reg = dr.async_get(hass)
|
||||
device = dev_reg.async_get_or_create(
|
||||
config_entry_id=integration.entry_id, identifiers={get_device_id(client, node)}
|
||||
)
|
||||
|
||||
SENSOR_NAME = "sensor.smart_plug_with_two_usb_ports_value_electric_consumed"
|
||||
entity_name = SENSOR_NAME.split(".")[1]
|
||||
|
||||
# Create entity RegistryEntry using fake endpoint
|
||||
old_unique_id = f"{client.driver.controller.home_id}.32-50-1-value-66049"
|
||||
entity_entry = ent_reg.async_get_or_create(
|
||||
"sensor",
|
||||
DOMAIN,
|
||||
old_unique_id,
|
||||
suggested_object_id=entity_name,
|
||||
config_entry=integration,
|
||||
original_name=entity_name,
|
||||
device_id=device.id,
|
||||
)
|
||||
assert entity_entry.entity_id == SENSOR_NAME
|
||||
assert entity_entry.unique_id == old_unique_id
|
||||
|
||||
# Do this twice to make sure re-interview doesn't do anything weird
|
||||
for i in range(0, 2):
|
||||
# Add a ready node, unique ID should be migrated
|
||||
event = {"node": node}
|
||||
client.driver.controller.emit("node added", event)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
# Check that new RegistryEntry is using new unique ID format
|
||||
entity_entry = ent_reg.async_get(SENSOR_NAME)
|
||||
new_unique_id = f"{client.driver.controller.home_id}.32-50-0-value-66049"
|
||||
assert entity_entry.unique_id == new_unique_id
|
||||
assert ent_reg.async_get_entity_id("sensor", DOMAIN, old_unique_id) is None
|
||||
|
||||
|
||||
async def test_skip_old_entity_migration_for_multiple(
|
||||
hass, hank_binary_switch_state, client, integration
|
||||
):
|
||||
"""Test that multiple entities of the same value but on a different endpoint get skipped."""
|
||||
node = Node(client, hank_binary_switch_state)
|
||||
|
||||
ent_reg = er.async_get(hass)
|
||||
dev_reg = dr.async_get(hass)
|
||||
device = dev_reg.async_get_or_create(
|
||||
config_entry_id=integration.entry_id, identifiers={get_device_id(client, node)}
|
||||
)
|
||||
|
||||
SENSOR_NAME = "sensor.smart_plug_with_two_usb_ports_value_electric_consumed"
|
||||
entity_name = SENSOR_NAME.split(".")[1]
|
||||
|
||||
# Create two entity entrrys using different endpoints
|
||||
old_unique_id_1 = f"{client.driver.controller.home_id}.32-50-1-value-66049"
|
||||
entity_entry = ent_reg.async_get_or_create(
|
||||
"sensor",
|
||||
DOMAIN,
|
||||
old_unique_id_1,
|
||||
suggested_object_id=f"{entity_name}_1",
|
||||
config_entry=integration,
|
||||
original_name=f"{entity_name}_1",
|
||||
device_id=device.id,
|
||||
)
|
||||
assert entity_entry.entity_id == f"{SENSOR_NAME}_1"
|
||||
assert entity_entry.unique_id == old_unique_id_1
|
||||
|
||||
# Create two entity entrrys using different endpoints
|
||||
old_unique_id_2 = f"{client.driver.controller.home_id}.32-50-2-value-66049"
|
||||
entity_entry = ent_reg.async_get_or_create(
|
||||
"sensor",
|
||||
DOMAIN,
|
||||
old_unique_id_2,
|
||||
suggested_object_id=f"{entity_name}_2",
|
||||
config_entry=integration,
|
||||
original_name=f"{entity_name}_2",
|
||||
device_id=device.id,
|
||||
)
|
||||
assert entity_entry.entity_id == f"{SENSOR_NAME}_2"
|
||||
assert entity_entry.unique_id == old_unique_id_2
|
||||
# Add a ready node, unique ID should be migrated
|
||||
event = {"node": node}
|
||||
client.driver.controller.emit("node added", event)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
# Check that new RegistryEntry is created using new unique ID format
|
||||
entity_entry = ent_reg.async_get(SENSOR_NAME)
|
||||
new_unique_id = f"{client.driver.controller.home_id}.32-50-0-value-66049"
|
||||
assert entity_entry.unique_id == new_unique_id
|
||||
|
||||
# Check that the old entities stuck around because we skipped the migration step
|
||||
assert ent_reg.async_get_entity_id("sensor", DOMAIN, old_unique_id_1)
|
||||
assert ent_reg.async_get_entity_id("sensor", DOMAIN, old_unique_id_2)
|
||||
|
||||
|
||||
async def test_old_entity_migration_notification_binary_sensor(
|
||||
hass, multisensor_6_state, client, integration
|
||||
):
|
||||
"""Test old entity on a different endpoint is migrated to a new one for a notification binary sensor."""
|
||||
node = Node(client, multisensor_6_state)
|
||||
|
||||
ent_reg = er.async_get(hass)
|
||||
dev_reg = dr.async_get(hass)
|
||||
device = dev_reg.async_get_or_create(
|
||||
config_entry_id=integration.entry_id, identifiers={get_device_id(client, node)}
|
||||
)
|
||||
|
||||
entity_name = NOTIFICATION_MOTION_BINARY_SENSOR.split(".")[1]
|
||||
|
||||
# Create entity RegistryEntry using old unique ID format
|
||||
old_unique_id = f"{client.driver.controller.home_id}.52-113-1-Home Security-Motion sensor status.8"
|
||||
entity_entry = ent_reg.async_get_or_create(
|
||||
"binary_sensor",
|
||||
DOMAIN,
|
||||
old_unique_id,
|
||||
suggested_object_id=entity_name,
|
||||
config_entry=integration,
|
||||
original_name=entity_name,
|
||||
device_id=device.id,
|
||||
)
|
||||
assert entity_entry.entity_id == NOTIFICATION_MOTION_BINARY_SENSOR
|
||||
assert entity_entry.unique_id == old_unique_id
|
||||
|
||||
# Do this twice to make sure re-interview doesn't do anything weird
|
||||
for _ in range(0, 2):
|
||||
# Add a ready node, unique ID should be migrated
|
||||
event = {"node": node}
|
||||
client.driver.controller.emit("node added", event)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
# Check that new RegistryEntry is using new unique ID format
|
||||
entity_entry = ent_reg.async_get(NOTIFICATION_MOTION_BINARY_SENSOR)
|
||||
new_unique_id = f"{client.driver.controller.home_id}.52-113-0-Home Security-Motion sensor status.8"
|
||||
assert entity_entry.unique_id == new_unique_id
|
||||
assert (
|
||||
ent_reg.async_get_entity_id("binary_sensor", DOMAIN, old_unique_id) is None
|
||||
)
|
||||
|
||||
|
||||
async def test_on_node_added_not_ready(
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue