Resume adding Z-Wave device if the page is refreshed (#129081)

* ZwaveJS: Resume adding a device if the page is refreshed

* add test

* address PR comments
This commit is contained in:
Petar Petrov 2024-10-25 12:08:07 +03:00 committed by GitHub
parent 0acb95bbd5
commit 47bf0ebb47
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 97 additions and 37 deletions

View File

@ -13,8 +13,10 @@ from zwave_js_server.client import Client
from zwave_js_server.const import (
CommandClass,
ExclusionStrategy,
InclusionState,
InclusionStrategy,
LogLevel,
NodeStatus,
Protocols,
ProvisioningEntryStatus,
QRCodeVersion,
@ -693,6 +695,30 @@ async def websocket_add_node(
)
)
@callback
def forward_node_added(
node: Node, low_security: bool, low_security_reason: str | None
) -> None:
interview_unsubs = [
node.on("interview started", forward_event),
node.on("interview completed", forward_event),
node.on("interview stage completed", forward_stage),
node.on("interview failed", forward_event),
]
unsubs.extend(interview_unsubs)
node_details = {
"node_id": node.node_id,
"status": node.status,
"ready": node.ready,
"low_security": low_security,
"low_security_reason": low_security_reason,
}
connection.send_message(
websocket_api.event_message(
msg[ID], {"event": "node added", "node": node_details}
)
)
@callback
def forward_requested_grant(event: dict) -> None:
connection.send_message(
@ -727,25 +753,10 @@ async def websocket_add_node(
@callback
def node_added(event: dict) -> None:
node = event["node"]
interview_unsubs = [
node.on("interview started", forward_event),
node.on("interview completed", forward_event),
node.on("interview stage completed", forward_stage),
node.on("interview failed", forward_event),
]
unsubs.extend(interview_unsubs)
node_details = {
"node_id": node.node_id,
"status": node.status,
"ready": node.ready,
"low_security": event["result"].get("lowSecurity", False),
"low_security_reason": event["result"].get("lowSecurityReason"),
}
connection.send_message(
websocket_api.event_message(
msg[ID], {"event": "node added", "node": node_details}
)
forward_node_added(
event["node"],
event["result"].get("lowSecurity", False),
event["result"].get("lowSecurityReason"),
)
@callback
@ -777,25 +788,39 @@ async def websocket_add_node(
]
msg[DATA_UNSUBSCRIBE] = unsubs
try:
result = await controller.async_begin_inclusion(
INCLUSION_STRATEGY_NOT_SMART_START[inclusion_strategy.value],
force_security=force_security,
provisioning=provisioning,
dsk=dsk,
)
except ValueError as err:
connection.send_error(
if controller.inclusion_state == InclusionState.INCLUDING:
connection.send_result(
msg[ID],
ERR_INVALID_FORMAT,
err.args[0],
True, # Inclusion is already in progress
)
return
# Check for nodes that have been added but not fully included
for node in controller.nodes.values():
if node.status != NodeStatus.DEAD and not node.ready:
forward_node_added(
node,
not node.is_secure,
None,
)
else:
try:
result = await controller.async_begin_inclusion(
INCLUSION_STRATEGY_NOT_SMART_START[inclusion_strategy.value],
force_security=force_security,
provisioning=provisioning,
dsk=dsk,
)
except ValueError as err:
connection.send_error(
msg[ID],
ERR_INVALID_FORMAT,
err.args[0],
)
return
connection.send_result(
msg[ID],
result,
)
connection.send_result(
msg[ID],
result,
)
@websocket_api.require_admin

View File

@ -5,7 +5,7 @@ from http import HTTPStatus
from io import BytesIO
import json
from typing import Any
from unittest.mock import patch
from unittest.mock import PropertyMock, patch
import pytest
from zwave_js_server.const import (
@ -489,6 +489,7 @@ async def test_node_alerts(
async def test_add_node(
hass: HomeAssistant,
nortek_thermostat,
nortek_thermostat_added_event,
integration,
client,
@ -936,12 +937,46 @@ async def test_add_node(
assert msg["error"]["code"] == "zwave_error"
assert msg["error"]["message"] == "zwave_error: Z-Wave error 1 - error message"
# Test inclusion already in progress
client.async_send_command.reset_mock()
type(client.driver.controller).inclusion_state = PropertyMock(
return_value=InclusionState.INCLUDING
)
# Create a node that's not ready
node_data = deepcopy(nortek_thermostat.data) # Copy to allow modification in tests.
node_data["ready"] = False
node_data["values"] = {}
node_data["endpoints"] = {}
node = Node(client, node_data)
client.driver.controller.nodes[node.node_id] = node
await ws_client.send_json(
{
ID: 11,
TYPE: "zwave_js/add_node",
ENTRY_ID: entry.entry_id,
INCLUSION_STRATEGY: InclusionStrategy.DEFAULT.value,
}
)
msg = await ws_client.receive_json()
assert msg["success"]
# Verify no command was sent since inclusion is already in progress
assert len(client.async_send_command.call_args_list) == 0
# Verify we got a node added event
msg = await ws_client.receive_json()
assert msg["event"]["event"] == "node added"
assert msg["event"]["node"]["node_id"] == node.node_id
# Test sending command with not loaded entry fails
await hass.config_entries.async_unload(entry.entry_id)
await hass.async_block_till_done()
await ws_client.send_json(
{ID: 11, TYPE: "zwave_js/add_node", ENTRY_ID: entry.entry_id}
{ID: 12, TYPE: "zwave_js/add_node", ENTRY_ID: entry.entry_id}
)
msg = await ws_client.receive_json()