mirror of
https://github.com/home-assistant/core.git
synced 2025-07-17 10:17:09 +00:00
Fix Z-Wave controller hard reset (#144389)
This commit is contained in:
parent
c5ef8659a7
commit
0b1875de14
@ -2,7 +2,9 @@
|
|||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import asyncio
|
||||||
from collections.abc import Callable, Coroutine
|
from collections.abc import Callable, Coroutine
|
||||||
|
from contextlib import suppress
|
||||||
import dataclasses
|
import dataclasses
|
||||||
from functools import partial, wraps
|
from functools import partial, wraps
|
||||||
from typing import Any, Concatenate, Literal, cast
|
from typing import Any, Concatenate, Literal, cast
|
||||||
@ -182,6 +184,8 @@ STRATEGY = "strategy"
|
|||||||
# https://github.com/zwave-js/node-zwave-js/blob/master/packages/core/src/security/QR.ts#L41
|
# https://github.com/zwave-js/node-zwave-js/blob/master/packages/core/src/security/QR.ts#L41
|
||||||
MINIMUM_QR_STRING_LENGTH = 52
|
MINIMUM_QR_STRING_LENGTH = 52
|
||||||
|
|
||||||
|
HARD_RESET_CONTROLLER_DRIVER_READY_TIMEOUT = 60
|
||||||
|
|
||||||
|
|
||||||
# Helper schemas
|
# Helper schemas
|
||||||
PLANNED_PROVISIONING_ENTRY_SCHEMA = vol.All(
|
PLANNED_PROVISIONING_ENTRY_SCHEMA = vol.All(
|
||||||
@ -2816,6 +2820,7 @@ async def websocket_hard_reset_controller(
|
|||||||
driver: Driver,
|
driver: Driver,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Hard reset controller."""
|
"""Hard reset controller."""
|
||||||
|
unsubs: list[Callable[[], None]]
|
||||||
|
|
||||||
@callback
|
@callback
|
||||||
def async_cleanup() -> None:
|
def async_cleanup() -> None:
|
||||||
@ -2831,13 +2836,28 @@ async def websocket_hard_reset_controller(
|
|||||||
connection.send_result(msg[ID], device.id)
|
connection.send_result(msg[ID], device.id)
|
||||||
async_cleanup()
|
async_cleanup()
|
||||||
|
|
||||||
|
@callback
|
||||||
|
def set_driver_ready(event: dict) -> None:
|
||||||
|
"Set the driver ready event."
|
||||||
|
wait_driver_ready.set()
|
||||||
|
|
||||||
|
wait_driver_ready = asyncio.Event()
|
||||||
|
|
||||||
msg[DATA_UNSUBSCRIBE] = unsubs = [
|
msg[DATA_UNSUBSCRIBE] = unsubs = [
|
||||||
async_dispatcher_connect(
|
async_dispatcher_connect(
|
||||||
hass, EVENT_DEVICE_ADDED_TO_REGISTRY, _handle_device_added
|
hass, EVENT_DEVICE_ADDED_TO_REGISTRY, _handle_device_added
|
||||||
)
|
),
|
||||||
|
driver.once("driver ready", set_driver_ready),
|
||||||
]
|
]
|
||||||
|
|
||||||
await driver.async_hard_reset()
|
await driver.async_hard_reset()
|
||||||
|
|
||||||
|
with suppress(TimeoutError):
|
||||||
|
async with asyncio.timeout(HARD_RESET_CONTROLLER_DRIVER_READY_TIMEOUT):
|
||||||
|
await wait_driver_ready.wait()
|
||||||
|
|
||||||
|
await hass.config_entries.async_reload(entry.entry_id)
|
||||||
|
|
||||||
|
|
||||||
@websocket_api.websocket_command(
|
@websocket_api.websocket_command(
|
||||||
{
|
{
|
||||||
|
@ -5,7 +5,7 @@ from http import HTTPStatus
|
|||||||
from io import BytesIO
|
from io import BytesIO
|
||||||
import json
|
import json
|
||||||
from typing import Any
|
from typing import Any
|
||||||
from unittest.mock import AsyncMock, MagicMock, PropertyMock, patch
|
from unittest.mock import AsyncMock, MagicMock, PropertyMock, call, patch
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
from zwave_js_server.const import (
|
from zwave_js_server.const import (
|
||||||
@ -5078,53 +5078,97 @@ async def test_subscribe_node_statistics(
|
|||||||
assert msg["error"]["code"] == ERR_NOT_LOADED
|
assert msg["error"]["code"] == ERR_NOT_LOADED
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.skip(
|
|
||||||
reason="The test needs to be updated to reflect what happens when resetting the controller"
|
|
||||||
)
|
|
||||||
async def test_hard_reset_controller(
|
async def test_hard_reset_controller(
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
device_registry: dr.DeviceRegistry,
|
device_registry: dr.DeviceRegistry,
|
||||||
client,
|
client: MagicMock,
|
||||||
integration,
|
integration: MockConfigEntry,
|
||||||
listen_block,
|
|
||||||
hass_ws_client: WebSocketGenerator,
|
hass_ws_client: WebSocketGenerator,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Test that the hard_reset_controller WS API call works."""
|
"""Test that the hard_reset_controller WS API call works."""
|
||||||
entry = integration
|
entry = integration
|
||||||
ws_client = await hass_ws_client(hass)
|
ws_client = await hass_ws_client(hass)
|
||||||
|
|
||||||
device = device_registry.async_get_device(
|
async def async_send_command_driver_ready(
|
||||||
identifiers={get_device_id(client.driver, client.driver.controller.nodes[1])}
|
message: dict[str, Any],
|
||||||
)
|
require_schema: int | None = None,
|
||||||
|
) -> dict:
|
||||||
|
"""Send a command and get a response."""
|
||||||
|
client.driver.emit(
|
||||||
|
"driver ready", {"event": "driver ready", "source": "driver"}
|
||||||
|
)
|
||||||
|
return {}
|
||||||
|
|
||||||
client.async_send_command.return_value = {}
|
client.async_send_command.side_effect = async_send_command_driver_ready
|
||||||
await ws_client.send_json(
|
|
||||||
|
await ws_client.send_json_auto_id(
|
||||||
{
|
{
|
||||||
ID: 1,
|
|
||||||
TYPE: "zwave_js/hard_reset_controller",
|
TYPE: "zwave_js/hard_reset_controller",
|
||||||
ENTRY_ID: entry.entry_id,
|
ENTRY_ID: entry.entry_id,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
listen_block.set()
|
|
||||||
listen_block.clear()
|
|
||||||
await hass.async_block_till_done()
|
|
||||||
|
|
||||||
msg = await ws_client.receive_json()
|
msg = await ws_client.receive_json()
|
||||||
|
|
||||||
|
device = device_registry.async_get_device(
|
||||||
|
identifiers={get_device_id(client.driver, client.driver.controller.nodes[1])}
|
||||||
|
)
|
||||||
|
assert device is not None
|
||||||
assert msg["result"] == device.id
|
assert msg["result"] == device.id
|
||||||
assert msg["success"]
|
assert msg["success"]
|
||||||
|
|
||||||
assert len(client.async_send_command.call_args_list) == 1
|
assert client.async_send_command.call_count == 3
|
||||||
assert client.async_send_command.call_args[0][0] == {"command": "driver.hard_reset"}
|
# The first call is the relevant hard reset command.
|
||||||
|
# 25 is the require_schema parameter.
|
||||||
|
assert client.async_send_command.call_args_list[0] == call(
|
||||||
|
{"command": "driver.hard_reset"}, 25
|
||||||
|
)
|
||||||
|
|
||||||
|
client.async_send_command.reset_mock()
|
||||||
|
|
||||||
|
# Test sending command with driver not ready and timeout.
|
||||||
|
|
||||||
|
async def async_send_command_no_driver_ready(
|
||||||
|
message: dict[str, Any],
|
||||||
|
require_schema: int | None = None,
|
||||||
|
) -> dict:
|
||||||
|
"""Send a command and get a response."""
|
||||||
|
return {}
|
||||||
|
|
||||||
|
client.async_send_command.side_effect = async_send_command_no_driver_ready
|
||||||
|
|
||||||
|
with patch(
|
||||||
|
"homeassistant.components.zwave_js.api.HARD_RESET_CONTROLLER_DRIVER_READY_TIMEOUT",
|
||||||
|
new=0,
|
||||||
|
):
|
||||||
|
await ws_client.send_json_auto_id(
|
||||||
|
{
|
||||||
|
TYPE: "zwave_js/hard_reset_controller",
|
||||||
|
ENTRY_ID: entry.entry_id,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
msg = await ws_client.receive_json()
|
||||||
|
|
||||||
|
device = device_registry.async_get_device(
|
||||||
|
identifiers={get_device_id(client.driver, client.driver.controller.nodes[1])}
|
||||||
|
)
|
||||||
|
assert device is not None
|
||||||
|
assert msg["result"] == device.id
|
||||||
|
assert msg["success"]
|
||||||
|
|
||||||
|
assert client.async_send_command.call_count == 3
|
||||||
|
# The first call is the relevant hard reset command.
|
||||||
|
# 25 is the require_schema parameter.
|
||||||
|
assert client.async_send_command.call_args_list[0] == call(
|
||||||
|
{"command": "driver.hard_reset"}, 25
|
||||||
|
)
|
||||||
|
|
||||||
# Test FailedZWaveCommand is caught
|
# Test FailedZWaveCommand is caught
|
||||||
with patch(
|
with patch(
|
||||||
"zwave_js_server.model.driver.Driver.async_hard_reset",
|
"zwave_js_server.model.driver.Driver.async_hard_reset",
|
||||||
side_effect=FailedZWaveCommand("failed_command", 1, "error message"),
|
side_effect=FailedZWaveCommand("failed_command", 1, "error message"),
|
||||||
):
|
):
|
||||||
await ws_client.send_json(
|
await ws_client.send_json_auto_id(
|
||||||
{
|
{
|
||||||
ID: 2,
|
|
||||||
TYPE: "zwave_js/hard_reset_controller",
|
TYPE: "zwave_js/hard_reset_controller",
|
||||||
ENTRY_ID: entry.entry_id,
|
ENTRY_ID: entry.entry_id,
|
||||||
}
|
}
|
||||||
@ -5139,9 +5183,8 @@ async def test_hard_reset_controller(
|
|||||||
await hass.config_entries.async_unload(entry.entry_id)
|
await hass.config_entries.async_unload(entry.entry_id)
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
await ws_client.send_json(
|
await ws_client.send_json_auto_id(
|
||||||
{
|
{
|
||||||
ID: 3,
|
|
||||||
TYPE: "zwave_js/hard_reset_controller",
|
TYPE: "zwave_js/hard_reset_controller",
|
||||||
ENTRY_ID: entry.entry_id,
|
ENTRY_ID: entry.entry_id,
|
||||||
}
|
}
|
||||||
@ -5151,9 +5194,8 @@ async def test_hard_reset_controller(
|
|||||||
assert not msg["success"]
|
assert not msg["success"]
|
||||||
assert msg["error"]["code"] == ERR_NOT_LOADED
|
assert msg["error"]["code"] == ERR_NOT_LOADED
|
||||||
|
|
||||||
await ws_client.send_json(
|
await ws_client.send_json_auto_id(
|
||||||
{
|
{
|
||||||
ID: 4,
|
|
||||||
TYPE: "zwave_js/hard_reset_controller",
|
TYPE: "zwave_js/hard_reset_controller",
|
||||||
ENTRY_ID: "INVALID",
|
ENTRY_ID: "INVALID",
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user