Fix Z-Wave duplicate provisioned device (#150008)

This commit is contained in:
Martin Hjelmare
2025-08-05 08:50:42 +02:00
committed by GitHub
parent 68faa897ad
commit 4c5cf028d7
2 changed files with 87 additions and 33 deletions

View File

@@ -509,7 +509,7 @@ class ControllerEvents:
)
)
await self.async_check_preprovisioned_device(node)
await self.async_check_pre_provisioned_device(node)
if node.is_controller_node:
# Create a controller status sensor for each device
@@ -637,8 +637,8 @@ class ControllerEvents:
f"{DOMAIN}.identify_controller.{dev_id[1]}",
)
async def async_check_preprovisioned_device(self, node: ZwaveNode) -> None:
"""Check if the node was preprovisioned and update the device registry."""
async def async_check_pre_provisioned_device(self, node: ZwaveNode) -> None:
"""Check if the node was pre-provisioned and update the device registry."""
provisioning_entry = (
await self.driver_events.driver.controller.async_get_provisioning_entry(
node.node_id
@@ -648,29 +648,37 @@ class ControllerEvents:
provisioning_entry
and provisioning_entry.additional_properties
and "device_id" in provisioning_entry.additional_properties
):
preprovisioned_device = self.dev_reg.async_get(
provisioning_entry.additional_properties["device_id"]
and (
pre_provisioned_device := self.dev_reg.async_get(
provisioning_entry.additional_properties["device_id"]
)
)
and (dsk_identifier := (DOMAIN, f"provision_{provisioning_entry.dsk}"))
in pre_provisioned_device.identifiers
):
driver = self.driver_events.driver
device_id = get_device_id(driver, node)
device_id_ext = get_device_id_ext(driver, node)
new_identifiers = pre_provisioned_device.identifiers.copy()
new_identifiers.remove(dsk_identifier)
new_identifiers.add(device_id)
if device_id_ext:
new_identifiers.add(device_id_ext)
if preprovisioned_device:
dsk = provisioning_entry.dsk
dsk_identifier = (DOMAIN, f"provision_{dsk}")
# If the pre-provisioned device has the DSK identifier, remove it
if dsk_identifier in preprovisioned_device.identifiers:
driver = self.driver_events.driver
device_id = get_device_id(driver, node)
device_id_ext = get_device_id_ext(driver, node)
new_identifiers = preprovisioned_device.identifiers.copy()
new_identifiers.remove(dsk_identifier)
new_identifiers.add(device_id)
if device_id_ext:
new_identifiers.add(device_id_ext)
self.dev_reg.async_update_device(
preprovisioned_device.id,
new_identifiers=new_identifiers,
)
if self.dev_reg.async_get_device(identifiers=new_identifiers):
# If a device entry is registered with the node ID based identifiers,
# just remove the device entry with the DSK identifier.
self.dev_reg.async_update_device(
pre_provisioned_device.id,
remove_config_entry_id=self.config_entry.entry_id,
)
else:
# Add the node ID based identifiers to the device entry
# with the DSK identifier and remove the DSK identifier.
self.dev_reg.async_update_device(
pre_provisioned_device.id,
new_identifiers=new_identifiers,
)
async def async_register_node_in_dev_reg(self, node: ZwaveNode) -> dr.DeviceEntry:
"""Register node in dev reg."""

View File

@@ -497,17 +497,17 @@ async def test_on_node_added_ready(
)
async def test_on_node_added_preprovisioned(
async def test_check_pre_provisioned_device_update_device(
hass: HomeAssistant,
device_registry: dr.DeviceRegistry,
multisensor_6_state,
client,
integration,
multisensor_6_state: NodeDataType,
client: MagicMock,
integration: MockConfigEntry,
) -> None:
"""Test node added event with a preprovisioned device."""
"""Test check pre-provisioned device that should update the device."""
dsk = "test"
node = Node(client, deepcopy(multisensor_6_state))
device = device_registry.async_get_or_create(
pre_provisioned_device = device_registry.async_get_or_create(
config_entry_id=integration.entry_id,
identifiers={(DOMAIN, f"provision_{dsk}")},
)
@@ -515,7 +515,7 @@ async def test_on_node_added_preprovisioned(
{
"dsk": dsk,
"securityClasses": [SecurityClass.S2_UNAUTHENTICATED],
"device_id": device.id,
"device_id": pre_provisioned_device.id,
}
)
with patch(
@@ -526,14 +526,60 @@ async def test_on_node_added_preprovisioned(
client.driver.controller.emit("node added", event)
await hass.async_block_till_done()
device = device_registry.async_get(device.id)
device = device_registry.async_get(pre_provisioned_device.id)
assert device
assert device.identifiers == {
get_device_id(client.driver, node),
get_device_id_ext(client.driver, node),
}
assert device.sw_version == node.firmware_version
# There should only be the controller and the preprovisioned device
# There should only be the controller and the pre-provisioned device
assert len(device_registry.devices) == 2
async def test_check_pre_provisioned_device_remove_device(
hass: HomeAssistant,
device_registry: dr.DeviceRegistry,
multisensor_6_state: NodeDataType,
client: MagicMock,
integration: MockConfigEntry,
) -> None:
"""Test check pre-provisioned device that should remove the device."""
dsk = "test"
driver = client.driver
node = Node(client, deepcopy(multisensor_6_state))
pre_provisioned_device = device_registry.async_get_or_create(
config_entry_id=integration.entry_id,
identifiers={(DOMAIN, f"provision_{dsk}")},
)
extended_identifier = get_device_id_ext(driver, node)
assert extended_identifier
existing_device = device_registry.async_get_or_create(
config_entry_id=integration.entry_id,
identifiers={
get_device_id(driver, node),
extended_identifier,
},
)
provisioning_entry = ProvisioningEntry.from_dict(
{
"dsk": dsk,
"securityClasses": [SecurityClass.S2_UNAUTHENTICATED],
"device_id": pre_provisioned_device.id,
}
)
with patch(
f"{CONTROLLER_PATCH_PREFIX}.async_get_provisioning_entry",
side_effect=lambda id: provisioning_entry if id == node.node_id else None,
):
event = {"node": node}
client.driver.controller.emit("node added", event)
await hass.async_block_till_done()
assert not device_registry.async_get(pre_provisioned_device.id)
assert device_registry.async_get(existing_device.id)
# There should only be the controller and the existing device
assert len(device_registry.devices) == 2