8000 Repair Z-Wave unknown controller by MartinHjelmare · Pull Request #144738 · home-assistant/core · GitHub
[go: up one dir, main page]
More Web Proxy on the site http://driver.im/
Skip to content

Repair Z-Wave unknown controller #144738

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
May 12, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 33 additions & 0 deletions homeassistant/components/zwave_js/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -278,6 +278,39 @@ async def handle_ha_shutdown(event: Event) -> None:
# and we'll handle the clean up below.
await driver_events.setup(driver)

if (old_unique_id := entry.unique_id) is not None and old_unique_id != (
new_unique_id := str(driver.controller.home_id)
):
device_registry = dr.async_get(hass)
controller_model = "Unknown model"
if (
(own_node := driver.controller.own_node)
and (
controller_device_entry := device_registry.async_get_device(
identifiers={get_device_id(driver, own_node)}
)
)
and ( 10000 model := controller_device_entry.model)
):
controller_model = model
async_create_issue(
hass,
DOMAIN,
f"migrate_unique_id.{entry.entry_id}",
data={
"config_entry_id": entry.entry_id,
"config_entry_title": entry.title,
"controller_model": controller_model,
"new_unique_id": new_unique_id,
"old_unique_id": old_unique_id,
},
is_fixable=True,
severity=IssueSeverity.ERROR,
translation_key="migrate_unique_id",
)
else:
async_delete_issue(hass, DOMAIN, f"migrate_unique_id.{entry.entry_id}")

# If the listen task is already failed, we need to raise ConfigEntryNotReady
if listen_task.done():
listen_error, error_message = _get_listen_task_error(listen_task)
Expand Down
44 changes: 44 additions & 0 deletions homeassistant/components/zwave_js/repairs.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,47 @@ async def async_step_ignore(
)


class MigrateUniqueIDFlow(RepairsFlow):
"""Handler for an issue fixing flow."""

def __init__(self, data: dict[str, str]) -> None:
"""Initialize."""
self.description_placeholders: dict[str, str] = {
"config_entry_title": data["config_entry_title"],
"controller_model": data["controller_model"],
"new_unique_id": data["new_unique_id"],
"old_unique_id": data["old_unique_id"],
}
self._config_entry_id: str = data["config_entry_id"]

async def async_step_init(
self, user_input: dict[str, str] | None = None
) -> data_entry_flow.FlowResult:
"""Handle the first step of a fix flow."""
return await self.async_step_confirm()

async def async_step_confirm(
self, user_input: dict[str, str] | None = None
) -> data_entry_flow.FlowResult:
"""Handle the confirm step of a fix flow."""
if user_input is not None:
config_entry = self.hass.config_entries.async_get_entry(
self._config_entry_id
)
# If config entry was removed, we can ignore the issue.
if config_entry is not None:
self.hass.config_entries.async_update_entry(
config_entry,
unique_id=self.description_placeholders["new_unique_id"],
)
return self.async_create_entry(data={})

return self.async_show_form(
step_id="confirm",
description_placeholders=self.description_placeholders,
)


async def async_create_fix_flow(
hass: HomeAssistant, issue_id: str, data: dict[str, str] | None
) -> RepairsFlow:
Expand All @@ -65,4 +106,7 @@ async def async_create_fix_flow(
if issue_id.split(".")[0] == "device_config_file_changed":
assert data
return DeviceConfigFileChangedFlow(data)
if issue_id.split(".")[0] == "migrate_unique_id":
assert data
return MigrateUniqueIDFlow(data)
return ConfirmRepairFlow()
11 changes: 11 additions & 0 deletions homeassistant/components/zwave_js/strings.json
Original file line number Diff line number Diff line change
Expand Up @@ -273,6 +273,17 @@
"invalid_server_version": {
"description": "The version of Z-Wave Server you are currently running is too old for this version of Home Assistant. Please update the Z-Wave Server to the latest version to fix this issue.",
"title": "Newer version of Z-Wave Server needed"
},
"migrate_unique_id": {
"fix_flow": {
"step": {
"confirm": {
"description": "A Z-Wave controller of model {controller_model} with a different ID ({new_unique_id}) than the previously connected controller ({old_unique_id}) was connected to the {config_entry_title} configuration entry.\n\nReasons for a different controller ID could be:\n\n1. The controller was factory reset, with a 3rd party application.\n2. A controller Non Volatile Memory (NVM) backup was restored to the controller, with a 3rd party application.\n3. A different controller was connected to this configuration entry.\n\nIf a different controller was connected, you should instead set up a new configuration entry for the new controller.\n\nIf you are sure that the current controller is the correct controller you can confirm this by pressing Submit, and the configuration entry will remember the new controller ID.",
"title": "An unknown controller was detected"
}
}
},
"title": "An unknown controller was detected"
}
},
"services": {
Expand Down
6 changes: 5 additions & 1 deletion tests/components/zwave_js/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -843,7 +843,11 @@ async def integration_fixture(
platforms: list[Platform],
) -> MockConfigEntry:
"""Set up the zwave_js integration."""
entry = MockConfigEntry(domain="zwave_js", data={"url": "ws://test.org"})
entry = MockConfigEntry(
domain="zwave_js",
data={"url": "ws://test.org"},
unique_id=str(client.driver.controller.home_id),
)
entry.add_to_hass(hass)
with patch("homeassistant.components.zwave_js.PLATFORMS", platforms):
await hass.config_entries.async_setup(entry.entry_id)
Expand Down
116 changes: 116 additions & 0 deletions tests/components/zwave_js/test_repairs.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
from homeassistant.core import HomeAssistant
from homeassistant.helpers import device_registry as dr, issue_registry as ir

from tests.common import MockConfigEntry
from tests.components.repairs import (
async_process_repairs_platforms,
process_repair_fix_flow,
Expand Down Expand Up @@ -268,3 +269,118 @@ async def test_abort_confirm(
assert data["type"] == "abort"
assert data["reason"] == "cannot_connect"
assert data["description_placeholders"] == {"device_name": device.name}


@pytest.mark.usefixtures("client")
async def test_migrate_unique_id(
hass: HomeAssistant,
hass_client: ClientSessionGenerator,
hass_ws_client: WebSocketGenerator,
) -> None:
"""Test the migrate unique id flow."""
old_unique_id = "123456789"
config_entry = MockConfigEntry(
domain=DOMAIN,
title="Z-Wave JS",
data={
"url": "ws://test.org",
},
unique_id=old_unique_id,
)
config_entry.add_to_hass(hass)

await hass.config_entries.async_setup(config_entry.entry_id)

await async_process_repairs_platforms(hass)
ws_client = await hass_ws_client(hass)
http_client = await hass_client()

# Assert the issue is present
await ws_client.send_json_auto_id({"type": "repairs/list_issues"})
msg = await ws_client.receive_json()
assert msg["success"]
assert len(msg["result"]["issues"]) == 1
issue = msg["result"]["issues"][0]
issue_id = issue["issue_id"]
assert issue_id == f"migrate_unique_id.{config_entry.entry_id}"

data = await start_repair_fix_flow(http_client, DOMAIN, issue_id)

flow_id = data["flow_id"]
assert data["step_id"] == "confirm"
assert data["description_placeholders"] == {
"config_entry_title": "Z-Wave JS",
"controller_model": "ZW090",
"new_unique_id": "3245146787",
"old_unique_id": old_unique_id,
}

# Apply fix
data = await process_repair_fix_flow(http_client, flow_id)

assert data["type"] == "create_entry"
assert config_entry.unique_id == "3245146787"

await ws_client.send_json_auto_id({"type": "repairs/list_issues"})
msg = await ws_client.receive_json()
assert msg["success"]
assert len(msg["result"]["issues"]) == 0


@pytest.mark.usefixtures("client")
async def test_migrate_unique_id_missing_config_entry(
hass: HomeAssistant,
hass_client: ClientSessionGenerator,
hass_ws_client: WebSocketGenerator,
) -> None:
"""Test the migrate unique id flow with missing config entry."""
old_unique_id = "123456789"
config_entry = MockConfigEntry(
domain=DOMAIN,
title="Z-Wave JS",
data={
"url": "ws://test.org",
},
unique_id=old_unique_id,
)
config_entry.add_to_hass(hass)

await hass.config_entries.async_setup(config_entry.entry_id)

await async_process_repairs_platforms(hass)
ws_client = await hass_ws_client(hass)
http_client = await hass_client()

# Assert the issue is present
await ws_client.send_json_auto_id({"type": "repairs/list_issues"})
msg = await ws_client.receive_json()
assert msg["success"]
assert len(msg["result"]["issues"]) == 1
issue = msg["result"]["issues"][0]
issue_id = issue["issue_id"]
assert issue_id == f"migrate_unique_id.{config_entry.entry_id}"

await hass.config_entries.async_remove(config_entry.entry_id)

assert not hass.config_entries.async_get_entry(config_entry.entry_id)

data = await start_repair_fix_flow(http_client, DOMAIN, issue_id)

flow_id = data["flow_id"]
assert data["step_id"] == "confirm"
assert data["description_placeholders"] == {
"config_entry_title": "Z-Wave JS",
"controller_model": "ZW090",
"new_unique_id": "3245146787",
"old_unique_id": old_unique_id,
}

# Apply fix
data = await process_repair_fix_flow(http_client, flow_id)

assert data["type"] == "create_entry"

await ws_client.send_json_auto_id({"type": "repairs/list_issues"})
msg = await ws_client.receive_json()
assert msg["success"]
assert len(msg["result"]["issues"]) == 0
0