Fix zwave_js unique ID migration logic (#47031)

This commit is contained in:
Raman Gupta 2021-02-25 02:14:32 -05:00 committed by GitHub
parent 7dc9071776
commit a43f3c1a0c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 130 additions and 28 deletions

View File

@ -85,6 +85,21 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
dev_reg = await device_registry.async_get_registry(hass) dev_reg = await device_registry.async_get_registry(hass)
ent_reg = entity_registry.async_get(hass) ent_reg = entity_registry.async_get(hass)
@callback
def migrate_entity(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."""
if entity_id := ent_reg.async_get_entity_id(platform, DOMAIN, old_unique_id):
LOGGER.debug(
"Migrating entity %s from old unique ID '%s' to new unique ID '%s'",
entity_id,
old_unique_id,
new_unique_id,
)
ent_reg.async_update_entity(
entity_id,
new_unique_id=new_unique_id,
)
@callback @callback
def async_on_node_ready(node: ZwaveNode) -> None: def async_on_node_ready(node: ZwaveNode) -> None:
"""Handle node ready event.""" """Handle node ready event."""
@ -97,26 +112,49 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
for disc_info in async_discover_values(node): for disc_info in async_discover_values(node):
LOGGER.debug("Discovered entity: %s", disc_info) LOGGER.debug("Discovered entity: %s", disc_info)
# This migration logic was added in 2021.3 to handle breaking change to # This migration logic was added in 2021.3 to handle a breaking change to
# value_id format. Some time in the future, this code block # the value_id format. Some time in the future, this code block
# (and get_old_value_id helper) can be removed. # (as well as get_old_value_id helper and migrate_entity closure) can be
old_value_id = get_old_value_id(disc_info.primary_value) # removed.
old_unique_id = get_unique_id( value_ids = [
client.driver.controller.home_id, old_value_id # 2021.2.* format
get_old_value_id(disc_info.primary_value),
# 2021.3.0b0 format
disc_info.primary_value.value_id,
]
new_unique_id = get_unique_id(
client.driver.controller.home_id,
disc_info.primary_value.value_id,
) )
if entity_id := ent_reg.async_get_entity_id(
disc_info.platform, DOMAIN, old_unique_id for value_id in value_ids:
): old_unique_id = get_unique_id(
LOGGER.debug( client.driver.controller.home_id,
"Entity %s is using old unique ID, migrating to new one", entity_id f"{disc_info.primary_value.node.node_id}.{value_id}",
)
ent_reg.async_update_entity(
entity_id,
new_unique_id=get_unique_id(
client.driver.controller.home_id,
disc_info.primary_value.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
migrate_entity(
disc_info.platform,
f"{old_unique_id}.{state_key}",
f"{new_unique_id}.{state_key}",
)
# Once we've iterated through all state keys, we can move on to the
# next item
continue
migrate_entity(disc_info.platform, old_unique_id, new_unique_id)
async_dispatcher_send( async_dispatcher_send(
hass, f"{DOMAIN}_{entry.entry_id}_add_{disc_info.platform}", disc_info hass, f"{DOMAIN}_{entry.entry_id}_add_{disc_info.platform}", disc_info

View File

@ -24,11 +24,6 @@ class ZwaveDiscoveryInfo:
# hint for the platform about this discovered entity # hint for the platform about this discovered entity
platform_hint: Optional[str] = "" platform_hint: Optional[str] = ""
@property
def value_id(self) -> str:
"""Return the unique value_id belonging to primary value."""
return f"{self.node.node_id}.{self.primary_value.value_id}"
@dataclass @dataclass
class ZWaveValueDiscoverySchema: class ZWaveValueDiscoverySchema:

View File

@ -32,7 +32,7 @@ class ZWaveBaseEntity(Entity):
self.info = info self.info = info
self._name = self.generate_name() self._name = self.generate_name()
self._unique_id = get_unique_id( self._unique_id = get_unique_id(
self.client.driver.controller.home_id, self.info.value_id self.client.driver.controller.home_id, self.info.primary_value.value_id
) )
# entities requiring additional values, can add extra ids to this list # entities requiring additional values, can add extra ids to this list
self.watched_value_ids = {self.info.primary_value.value_id} self.watched_value_ids = {self.info.primary_value.value_id}

View File

@ -18,7 +18,7 @@ from homeassistant.config_entries import (
from homeassistant.const import STATE_UNAVAILABLE from homeassistant.const import STATE_UNAVAILABLE
from homeassistant.helpers import device_registry, entity_registry from homeassistant.helpers import device_registry, entity_registry
from .common import AIR_TEMPERATURE_SENSOR from .common import AIR_TEMPERATURE_SENSOR, NOTIFICATION_MOTION_BINARY_SENSOR
from tests.common import MockConfigEntry from tests.common import MockConfigEntry
@ -124,13 +124,15 @@ async def test_on_node_added_ready(
) )
async def test_unique_id_migration(hass, multisensor_6_state, client, integration): async def test_unique_id_migration_v1(hass, multisensor_6_state, client, integration):
"""Test unique ID is migrated from old format to new.""" """Test unique ID is migrated from old format to new (version 1)."""
ent_reg = entity_registry.async_get(hass) ent_reg = entity_registry.async_get(hass)
# Migrate version 1
entity_name = AIR_TEMPERATURE_SENSOR.split(".")[1] entity_name = AIR_TEMPERATURE_SENSOR.split(".")[1]
# Create entity RegistryEntry using old unique ID format # Create entity RegistryEntry using old unique ID format
old_unique_id = f"{client.driver.controller.home_id}.52-49-00-Air temperature-00" old_unique_id = f"{client.driver.controller.home_id}.52.52-49-00-Air temperature-00"
entity_entry = ent_reg.async_get_or_create( entity_entry = ent_reg.async_get_or_create(
"sensor", "sensor",
DOMAIN, DOMAIN,
@ -155,6 +157,73 @@ async def test_unique_id_migration(hass, multisensor_6_state, client, integratio
assert entity_entry.unique_id == new_unique_id assert entity_entry.unique_id == new_unique_id
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 = entity_registry.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-00-00"
assert entity_entry.unique_id == new_unique_id
async def test_unique_id_migration_notification_binary_sensor(
hass, multisensor_6_state, client, integration
):
"""Test unique ID is migrated from old format to new for a notification binary sensor."""
ent_reg = entity_registry.async_get(hass)
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.52-113-00-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,
)
assert entity_entry.entity_id == NOTIFICATION_MOTION_BINARY_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(NOTIFICATION_MOTION_BINARY_SENSOR)
new_unique_id = f"{client.driver.controller.home_id}.52-113-0-Home Security-Motion sensor status-Motion sensor status.8"
assert entity_entry.unique_id == new_unique_id
async def test_on_node_added_not_ready( async def test_on_node_added_not_ready(
hass, multisensor_6_state, client, integration, device_registry hass, multisensor_6_state, client, integration, device_registry
): ):