Add proper S2 support for adding zwave_js nodes (#56516)
Co-authored-by: Martin Hjelmare <marhje52@gmail.com>
This commit is contained in:
parent
db30c27455
commit
e76ddb4b27
3 changed files with 294 additions and 95 deletions
|
@ -10,7 +10,12 @@ from aiohttp import hdrs, web, web_exceptions, web_request
|
||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
from zwave_js_server import dump
|
from zwave_js_server import dump
|
||||||
from zwave_js_server.client import Client
|
from zwave_js_server.client import Client
|
||||||
from zwave_js_server.const import CommandClass, InclusionStrategy, LogLevel
|
from zwave_js_server.const import (
|
||||||
|
CommandClass,
|
||||||
|
InclusionStrategy,
|
||||||
|
LogLevel,
|
||||||
|
SecurityClass,
|
||||||
|
)
|
||||||
from zwave_js_server.exceptions import (
|
from zwave_js_server.exceptions import (
|
||||||
BaseZwaveJSServerError,
|
BaseZwaveJSServerError,
|
||||||
FailedCommand,
|
FailedCommand,
|
||||||
|
@ -19,7 +24,7 @@ from zwave_js_server.exceptions import (
|
||||||
SetValueFailed,
|
SetValueFailed,
|
||||||
)
|
)
|
||||||
from zwave_js_server.firmware import begin_firmware_update
|
from zwave_js_server.firmware import begin_firmware_update
|
||||||
from zwave_js_server.model.controller import ControllerStatistics
|
from zwave_js_server.model.controller import ControllerStatistics, InclusionGrant
|
||||||
from zwave_js_server.model.firmware import (
|
from zwave_js_server.model.firmware import (
|
||||||
FirmwareUpdateFinished,
|
FirmwareUpdateFinished,
|
||||||
FirmwareUpdateProgress,
|
FirmwareUpdateProgress,
|
||||||
|
@ -67,7 +72,8 @@ TYPE = "type"
|
||||||
PROPERTY = "property"
|
PROPERTY = "property"
|
||||||
PROPERTY_KEY = "property_key"
|
PROPERTY_KEY = "property_key"
|
||||||
VALUE = "value"
|
VALUE = "value"
|
||||||
SECURE = "secure"
|
INCLUSION_STRATEGY = "inclusion_strategy"
|
||||||
|
PIN = "pin"
|
||||||
|
|
||||||
# constants for log config commands
|
# constants for log config commands
|
||||||
CONFIG = "config"
|
CONFIG = "config"
|
||||||
|
@ -85,6 +91,10 @@ STATUS = "status"
|
||||||
ENABLED = "enabled"
|
ENABLED = "enabled"
|
||||||
OPTED_IN = "opted_in"
|
OPTED_IN = "opted_in"
|
||||||
|
|
||||||
|
# constants for granting security classes
|
||||||
|
SECURITY_CLASSES = "security_classes"
|
||||||
|
CLIENT_SIDE_AUTH = "client_side_auth"
|
||||||
|
|
||||||
|
|
||||||
def async_get_entry(orig_func: Callable) -> Callable:
|
def async_get_entry(orig_func: Callable) -> Callable:
|
||||||
"""Decorate async function to get entry."""
|
"""Decorate async function to get entry."""
|
||||||
|
@ -171,6 +181,8 @@ def async_register_api(hass: HomeAssistant) -> None:
|
||||||
websocket_api.async_register_command(hass, websocket_node_metadata)
|
websocket_api.async_register_command(hass, websocket_node_metadata)
|
||||||
websocket_api.async_register_command(hass, websocket_ping_node)
|
websocket_api.async_register_command(hass, websocket_ping_node)
|
||||||
websocket_api.async_register_command(hass, websocket_add_node)
|
websocket_api.async_register_command(hass, websocket_add_node)
|
||||||
|
websocket_api.async_register_command(hass, websocket_grant_security_classes)
|
||||||
|
websocket_api.async_register_command(hass, websocket_validate_dsk_and_enter_pin)
|
||||||
websocket_api.async_register_command(hass, websocket_stop_inclusion)
|
websocket_api.async_register_command(hass, websocket_stop_inclusion)
|
||||||
websocket_api.async_register_command(hass, websocket_stop_exclusion)
|
websocket_api.async_register_command(hass, websocket_stop_exclusion)
|
||||||
websocket_api.async_register_command(hass, websocket_remove_node)
|
websocket_api.async_register_command(hass, websocket_remove_node)
|
||||||
|
@ -371,7 +383,9 @@ async def websocket_ping_node(
|
||||||
{
|
{
|
||||||
vol.Required(TYPE): "zwave_js/add_node",
|
vol.Required(TYPE): "zwave_js/add_node",
|
||||||
vol.Required(ENTRY_ID): str,
|
vol.Required(ENTRY_ID): str,
|
||||||
vol.Optional(SECURE, default=False): bool,
|
vol.Optional(INCLUSION_STRATEGY, default=InclusionStrategy.DEFAULT): vol.In(
|
||||||
|
[strategy.value for strategy in InclusionStrategy]
|
||||||
|
),
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@websocket_api.async_response
|
@websocket_api.async_response
|
||||||
|
@ -386,11 +400,7 @@ async def websocket_add_node(
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Add a node to the Z-Wave network."""
|
"""Add a node to the Z-Wave network."""
|
||||||
controller = client.driver.controller
|
controller = client.driver.controller
|
||||||
|
inclusion_strategy = InclusionStrategy(msg[INCLUSION_STRATEGY])
|
||||||
if msg[SECURE]:
|
|
||||||
inclusion_strategy = InclusionStrategy.SECURITY_S0
|
|
||||||
else:
|
|
||||||
inclusion_strategy = InclusionStrategy.INSECURE
|
|
||||||
|
|
||||||
@callback
|
@callback
|
||||||
def async_cleanup() -> None:
|
def async_cleanup() -> None:
|
||||||
|
@ -404,6 +414,26 @@ async def websocket_add_node(
|
||||||
websocket_api.event_message(msg[ID], {"event": event["event"]})
|
websocket_api.event_message(msg[ID], {"event": event["event"]})
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@callback
|
||||||
|
def forward_dsk(event: dict) -> None:
|
||||||
|
connection.send_message(
|
||||||
|
websocket_api.event_message(
|
||||||
|
msg[ID], {"event": event["event"], "dsk": event["dsk"]}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
@callback
|
||||||
|
def forward_requested_grant(event: dict) -> None:
|
||||||
|
connection.send_message(
|
||||||
|
websocket_api.event_message(
|
||||||
|
msg[ID],
|
||||||
|
{
|
||||||
|
"event": event["event"],
|
||||||
|
"requested_grant": event["requested_grant"].to_dict(),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
@callback
|
@callback
|
||||||
def forward_stage(event: dict) -> None:
|
def forward_stage(event: dict) -> None:
|
||||||
connection.send_message(
|
connection.send_message(
|
||||||
|
@ -426,6 +456,7 @@ async def websocket_add_node(
|
||||||
"node_id": node.node_id,
|
"node_id": node.node_id,
|
||||||
"status": node.status,
|
"status": node.status,
|
||||||
"ready": node.ready,
|
"ready": node.ready,
|
||||||
|
"low_security": event["result"].get("lowSecurity", False),
|
||||||
}
|
}
|
||||||
connection.send_message(
|
connection.send_message(
|
||||||
websocket_api.event_message(
|
websocket_api.event_message(
|
||||||
|
@ -452,6 +483,8 @@ async def websocket_add_node(
|
||||||
controller.on("inclusion started", forward_event),
|
controller.on("inclusion started", forward_event),
|
||||||
controller.on("inclusion failed", forward_event),
|
controller.on("inclusion failed", forward_event),
|
||||||
controller.on("inclusion stopped", forward_event),
|
controller.on("inclusion stopped", forward_event),
|
||||||
|
controller.on("validate dsk and enter pin", forward_dsk),
|
||||||
|
controller.on("grant security classes", forward_requested_grant),
|
||||||
controller.on("node added", node_added),
|
controller.on("node added", node_added),
|
||||||
async_dispatcher_connect(
|
async_dispatcher_connect(
|
||||||
hass, EVENT_DEVICE_ADDED_TO_REGISTRY, device_registered
|
hass, EVENT_DEVICE_ADDED_TO_REGISTRY, device_registered
|
||||||
|
@ -465,6 +498,59 @@ async def websocket_add_node(
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@websocket_api.require_admin
|
||||||
|
@websocket_api.websocket_command(
|
||||||
|
{
|
||||||
|
vol.Required(TYPE): "zwave_js/grant_security_classes",
|
||||||
|
vol.Required(ENTRY_ID): str,
|
||||||
|
vol.Required(SECURITY_CLASSES): [
|
||||||
|
vol.In([sec_cls.value for sec_cls in SecurityClass])
|
||||||
|
],
|
||||||
|
vol.Optional(CLIENT_SIDE_AUTH, default=False): bool,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
@websocket_api.async_response
|
||||||
|
@async_handle_failed_command
|
||||||
|
@async_get_entry
|
||||||
|
async def websocket_grant_security_classes(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
connection: ActiveConnection,
|
||||||
|
msg: dict,
|
||||||
|
entry: ConfigEntry,
|
||||||
|
client: Client,
|
||||||
|
) -> None:
|
||||||
|
"""Add a node to the Z-Wave network."""
|
||||||
|
inclusion_grant = InclusionGrant(
|
||||||
|
[SecurityClass(sec_cls) for sec_cls in msg[SECURITY_CLASSES]],
|
||||||
|
msg[CLIENT_SIDE_AUTH],
|
||||||
|
)
|
||||||
|
await client.driver.controller.async_grant_security_classes(inclusion_grant)
|
||||||
|
connection.send_result(msg[ID])
|
||||||
|
|
||||||
|
|
||||||
|
@websocket_api.require_admin
|
||||||
|
@websocket_api.websocket_command(
|
||||||
|
{
|
||||||
|
vol.Required(TYPE): "zwave_js/validate_dsk_and_enter_pin",
|
||||||
|
vol.Required(ENTRY_ID): str,
|
||||||
|
vol.Required(PIN): str,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
@websocket_api.async_response
|
||||||
|
@async_handle_failed_command
|
||||||
|
@async_get_entry
|
||||||
|
async def websocket_validate_dsk_and_enter_pin(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
connection: ActiveConnection,
|
||||||
|
msg: dict,
|
||||||
|
entry: ConfigEntry,
|
||||||
|
client: Client,
|
||||||
|
) -> None:
|
||||||
|
"""Add a node to the Z-Wave network."""
|
||||||
|
await client.driver.controller.async_validate_dsk_and_enter_pin(msg[PIN])
|
||||||
|
connection.send_result(msg[ID])
|
||||||
|
|
||||||
|
|
||||||
@websocket_api.require_admin
|
@websocket_api.require_admin
|
||||||
@websocket_api.websocket_command(
|
@websocket_api.websocket_command(
|
||||||
{
|
{
|
||||||
|
@ -583,7 +669,9 @@ async def websocket_remove_node(
|
||||||
vol.Required(TYPE): "zwave_js/replace_failed_node",
|
vol.Required(TYPE): "zwave_js/replace_failed_node",
|
||||||
vol.Required(ENTRY_ID): str,
|
vol.Required(ENTRY_ID): str,
|
||||||
vol.Required(NODE_ID): int,
|
vol.Required(NODE_ID): int,
|
||||||
vol.Optional(SECURE, default=False): bool,
|
vol.Optional(INCLUSION_STRATEGY, default=InclusionStrategy.DEFAULT): vol.In(
|
||||||
|
[strategy.value for strategy in InclusionStrategy]
|
||||||
|
),
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@websocket_api.async_response
|
@websocket_api.async_response
|
||||||
|
@ -599,11 +687,7 @@ async def websocket_replace_failed_node(
|
||||||
"""Replace a failed node with a new node."""
|
"""Replace a failed node with a new node."""
|
||||||
controller = client.driver.controller
|
controller = client.driver.controller
|
||||||
node_id = msg[NODE_ID]
|
node_id = msg[NODE_ID]
|
||||||
|
inclusion_strategy = InclusionStrategy(msg[INCLUSION_STRATEGY])
|
||||||
if msg[SECURE]:
|
|
||||||
inclusion_strategy = InclusionStrategy.SECURITY_S0
|
|
||||||
else:
|
|
||||||
inclusion_strategy = InclusionStrategy.INSECURE
|
|
||||||
|
|
||||||
@callback
|
@callback
|
||||||
def async_cleanup() -> None:
|
def async_cleanup() -> None:
|
||||||
|
@ -617,6 +701,26 @@ async def websocket_replace_failed_node(
|
||||||
websocket_api.event_message(msg[ID], {"event": event["event"]})
|
websocket_api.event_message(msg[ID], {"event": event["event"]})
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@callback
|
||||||
|
def forward_dsk(event: dict) -> None:
|
||||||
|
connection.send_message(
|
||||||
|
websocket_api.event_message(
|
||||||
|
msg[ID], {"event": event["event"], "dsk": event["dsk"]}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
@callback
|
||||||
|
def forward_requested_grant(event: dict) -> None:
|
||||||
|
connection.send_message(
|
||||||
|
websocket_api.event_message(
|
||||||
|
msg[ID],
|
||||||
|
{
|
||||||
|
"event": event["event"],
|
||||||
|
"requested_grant": event["requested_grant"].to_dict(),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
@callback
|
@callback
|
||||||
def forward_stage(event: dict) -> None:
|
def forward_stage(event: dict) -> None:
|
||||||
connection.send_message(
|
connection.send_message(
|
||||||
|
@ -678,6 +782,8 @@ async def websocket_replace_failed_node(
|
||||||
controller.on("inclusion started", forward_event),
|
controller.on("inclusion started", forward_event),
|
||||||
controller.on("inclusion failed", forward_event),
|
controller.on("inclusion failed", forward_event),
|
||||||
controller.on("inclusion stopped", forward_event),
|
controller.on("inclusion stopped", forward_event),
|
||||||
|
controller.on("validate dsk and enter pin", forward_dsk),
|
||||||
|
controller.on("grant security classes", forward_requested_grant),
|
||||||
controller.on("node removed", node_removed),
|
controller.on("node removed", node_removed),
|
||||||
controller.on("node added", node_added),
|
controller.on("node added", node_added),
|
||||||
async_dispatcher_connect(
|
async_dispatcher_connect(
|
||||||
|
|
|
@ -3,7 +3,12 @@ import json
|
||||||
from unittest.mock import patch
|
from unittest.mock import patch
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
from zwave_js_server.const import CommandClass, InclusionStrategy, LogLevel
|
from zwave_js_server.const import (
|
||||||
|
CommandClass,
|
||||||
|
InclusionStrategy,
|
||||||
|
LogLevel,
|
||||||
|
SecurityClass,
|
||||||
|
)
|
||||||
from zwave_js_server.event import Event
|
from zwave_js_server.event import Event
|
||||||
from zwave_js_server.exceptions import (
|
from zwave_js_server.exceptions import (
|
||||||
FailedCommand,
|
FailedCommand,
|
||||||
|
@ -16,6 +21,7 @@ from zwave_js_server.model.value import _get_value_id_from_dict, get_value_id
|
||||||
|
|
||||||
from homeassistant.components.websocket_api.const import ERR_NOT_FOUND
|
from homeassistant.components.websocket_api.const import ERR_NOT_FOUND
|
||||||
from homeassistant.components.zwave_js.api import (
|
from homeassistant.components.zwave_js.api import (
|
||||||
|
CLIENT_SIDE_AUTH,
|
||||||
COMMAND_CLASS_ID,
|
COMMAND_CLASS_ID,
|
||||||
CONFIG,
|
CONFIG,
|
||||||
ENABLED,
|
ENABLED,
|
||||||
|
@ -24,13 +30,15 @@ from homeassistant.components.zwave_js.api import (
|
||||||
FILENAME,
|
FILENAME,
|
||||||
FORCE_CONSOLE,
|
FORCE_CONSOLE,
|
||||||
ID,
|
ID,
|
||||||
|
INCLUSION_STRATEGY,
|
||||||
LEVEL,
|
LEVEL,
|
||||||
LOG_TO_FILE,
|
LOG_TO_FILE,
|
||||||
NODE_ID,
|
NODE_ID,
|
||||||
OPTED_IN,
|
OPTED_IN,
|
||||||
|
PIN,
|
||||||
PROPERTY,
|
PROPERTY,
|
||||||
PROPERTY_KEY,
|
PROPERTY_KEY,
|
||||||
SECURE,
|
SECURITY_CLASSES,
|
||||||
TYPE,
|
TYPE,
|
||||||
VALUE,
|
VALUE,
|
||||||
)
|
)
|
||||||
|
@ -354,31 +362,6 @@ async def test_ping_node(
|
||||||
assert msg["error"]["code"] == ERR_NOT_LOADED
|
assert msg["error"]["code"] == ERR_NOT_LOADED
|
||||||
|
|
||||||
|
|
||||||
async def test_add_node_secure(
|
|
||||||
hass, nortek_thermostat_added_event, integration, client, hass_ws_client
|
|
||||||
):
|
|
||||||
"""Test the add_node websocket command with secure flag."""
|
|
||||||
entry = integration
|
|
||||||
ws_client = await hass_ws_client(hass)
|
|
||||||
|
|
||||||
client.async_send_command.return_value = {"success": True}
|
|
||||||
|
|
||||||
await ws_client.send_json(
|
|
||||||
{ID: 1, TYPE: "zwave_js/add_node", ENTRY_ID: entry.entry_id, SECURE: True}
|
|
||||||
)
|
|
||||||
|
|
||||||
msg = await ws_client.receive_json()
|
|
||||||
assert msg["success"]
|
|
||||||
|
|
||||||
assert len(client.async_send_command.call_args_list) == 1
|
|
||||||
assert client.async_send_command.call_args[0][0] == {
|
|
||||||
"command": "controller.begin_inclusion",
|
|
||||||
"options": {"strategy": InclusionStrategy.SECURITY_S0},
|
|
||||||
}
|
|
||||||
|
|
||||||
client.async_send_command.reset_mock()
|
|
||||||
|
|
||||||
|
|
||||||
async def test_add_node(
|
async def test_add_node(
|
||||||
hass, nortek_thermostat_added_event, integration, client, hass_ws_client
|
hass, nortek_thermostat_added_event, integration, client, hass_ws_client
|
||||||
):
|
):
|
||||||
|
@ -389,7 +372,12 @@ async def test_add_node(
|
||||||
client.async_send_command.return_value = {"success": True}
|
client.async_send_command.return_value = {"success": True}
|
||||||
|
|
||||||
await ws_client.send_json(
|
await ws_client.send_json(
|
||||||
{ID: 3, TYPE: "zwave_js/add_node", ENTRY_ID: entry.entry_id}
|
{
|
||||||
|
ID: 3,
|
||||||
|
TYPE: "zwave_js/add_node",
|
||||||
|
ENTRY_ID: entry.entry_id,
|
||||||
|
INCLUSION_STRATEGY: InclusionStrategy.DEFAULT.value,
|
||||||
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
msg = await ws_client.receive_json()
|
msg = await ws_client.receive_json()
|
||||||
|
@ -398,7 +386,7 @@ async def test_add_node(
|
||||||
assert len(client.async_send_command.call_args_list) == 1
|
assert len(client.async_send_command.call_args_list) == 1
|
||||||
assert client.async_send_command.call_args[0][0] == {
|
assert client.async_send_command.call_args[0][0] == {
|
||||||
"command": "controller.begin_inclusion",
|
"command": "controller.begin_inclusion",
|
||||||
"options": {"strategy": InclusionStrategy.INSECURE},
|
"options": {"strategy": InclusionStrategy.DEFAULT},
|
||||||
}
|
}
|
||||||
|
|
||||||
event = Event(
|
event = Event(
|
||||||
|
@ -414,6 +402,37 @@ async def test_add_node(
|
||||||
msg = await ws_client.receive_json()
|
msg = await ws_client.receive_json()
|
||||||
assert msg["event"]["event"] == "inclusion started"
|
assert msg["event"]["event"] == "inclusion started"
|
||||||
|
|
||||||
|
event = Event(
|
||||||
|
type="grant security classes",
|
||||||
|
data={
|
||||||
|
"source": "controller",
|
||||||
|
"event": "grant security classes",
|
||||||
|
"requested": {"securityClasses": [0, 1, 2, 7], "clientSideAuth": False},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
client.driver.receive_event(event)
|
||||||
|
|
||||||
|
msg = await ws_client.receive_json()
|
||||||
|
assert msg["event"]["event"] == "grant security classes"
|
||||||
|
assert msg["event"]["requested_grant"] == {
|
||||||
|
"securityClasses": [0, 1, 2, 7],
|
||||||
|
"clientSideAuth": False,
|
||||||
|
}
|
||||||
|
|
||||||
|
event = Event(
|
||||||
|
type="validate dsk and enter pin",
|
||||||
|
data={
|
||||||
|
"source": "controller",
|
||||||
|
"event": "validate dsk and enter pin",
|
||||||
|
"dsk": "test",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
client.driver.receive_event(event)
|
||||||
|
|
||||||
|
msg = await ws_client.receive_json()
|
||||||
|
assert msg["event"]["event"] == "validate dsk and enter pin"
|
||||||
|
assert msg["event"]["dsk"] == "test"
|
||||||
|
|
||||||
client.driver.receive_event(nortek_thermostat_added_event)
|
client.driver.receive_event(nortek_thermostat_added_event)
|
||||||
msg = await ws_client.receive_json()
|
msg = await ws_client.receive_json()
|
||||||
assert msg["event"]["event"] == "node added"
|
assert msg["event"]["event"] == "node added"
|
||||||
|
@ -421,6 +440,7 @@ async def test_add_node(
|
||||||
"node_id": 67,
|
"node_id": 67,
|
||||||
"status": 0,
|
"status": 0,
|
||||||
"ready": False,
|
"ready": False,
|
||||||
|
"low_security": False,
|
||||||
}
|
}
|
||||||
assert msg["event"]["node"] == node_details
|
assert msg["event"]["node"] == node_details
|
||||||
|
|
||||||
|
@ -503,6 +523,94 @@ async def test_add_node(
|
||||||
assert msg["error"]["code"] == ERR_NOT_LOADED
|
assert msg["error"]["code"] == ERR_NOT_LOADED
|
||||||
|
|
||||||
|
|
||||||
|
async def test_grant_security_classes(hass, integration, client, hass_ws_client):
|
||||||
|
"""Test the grant_security_classes websocket command."""
|
||||||
|
entry = integration
|
||||||
|
ws_client = await hass_ws_client(hass)
|
||||||
|
|
||||||
|
client.async_send_command.return_value = {}
|
||||||
|
|
||||||
|
await ws_client.send_json(
|
||||||
|
{
|
||||||
|
ID: 1,
|
||||||
|
TYPE: "zwave_js/grant_security_classes",
|
||||||
|
ENTRY_ID: entry.entry_id,
|
||||||
|
SECURITY_CLASSES: [SecurityClass.S2_UNAUTHENTICATED],
|
||||||
|
CLIENT_SIDE_AUTH: False,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
msg = await ws_client.receive_json()
|
||||||
|
assert msg["success"]
|
||||||
|
|
||||||
|
assert len(client.async_send_command.call_args_list) == 1
|
||||||
|
assert client.async_send_command.call_args[0][0] == {
|
||||||
|
"command": "controller.grant_security_classes",
|
||||||
|
"inclusionGrant": {"securityClasses": [0], "clientSideAuth": False},
|
||||||
|
}
|
||||||
|
|
||||||
|
# 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: 4,
|
||||||
|
TYPE: "zwave_js/grant_security_classes",
|
||||||
|
ENTRY_ID: entry.entry_id,
|
||||||
|
SECURITY_CLASSES: [SecurityClass.S2_UNAUTHENTICATED],
|
||||||
|
CLIENT_SIDE_AUTH: False,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
msg = await ws_client.receive_json()
|
||||||
|
|
||||||
|
assert not msg["success"]
|
||||||
|
assert msg["error"]["code"] == ERR_NOT_LOADED
|
||||||
|
|
||||||
|
|
||||||
|
async def test_validate_dsk_and_enter_pin(hass, integration, client, hass_ws_client):
|
||||||
|
"""Test the validate_dsk_and_enter_pin websocket command."""
|
||||||
|
entry = integration
|
||||||
|
ws_client = await hass_ws_client(hass)
|
||||||
|
|
||||||
|
client.async_send_command.return_value = {}
|
||||||
|
|
||||||
|
await ws_client.send_json(
|
||||||
|
{
|
||||||
|
ID: 1,
|
||||||
|
TYPE: "zwave_js/validate_dsk_and_enter_pin",
|
||||||
|
ENTRY_ID: entry.entry_id,
|
||||||
|
PIN: "test",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
msg = await ws_client.receive_json()
|
||||||
|
assert msg["success"]
|
||||||
|
|
||||||
|
assert len(client.async_send_command.call_args_list) == 1
|
||||||
|
assert client.async_send_command.call_args[0][0] == {
|
||||||
|
"command": "controller.validate_dsk_and_enter_pin",
|
||||||
|
"pin": "test",
|
||||||
|
}
|
||||||
|
|
||||||
|
# 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: 4,
|
||||||
|
TYPE: "zwave_js/validate_dsk_and_enter_pin",
|
||||||
|
ENTRY_ID: entry.entry_id,
|
||||||
|
PIN: "test",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
msg = await ws_client.receive_json()
|
||||||
|
|
||||||
|
assert not msg["success"]
|
||||||
|
assert msg["error"]["code"] == ERR_NOT_LOADED
|
||||||
|
|
||||||
|
|
||||||
async def test_cancel_inclusion_exclusion(hass, integration, client, hass_ws_client):
|
async def test_cancel_inclusion_exclusion(hass, integration, client, hass_ws_client):
|
||||||
"""Test cancelling the inclusion and exclusion process."""
|
"""Test cancelling the inclusion and exclusion process."""
|
||||||
entry = integration
|
entry = integration
|
||||||
|
@ -607,7 +715,6 @@ async def test_remove_node(
|
||||||
data={
|
data={
|
||||||
"source": "controller",
|
"source": "controller",
|
||||||
"event": "exclusion started",
|
"event": "exclusion started",
|
||||||
"secure": False,
|
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
client.driver.receive_event(event)
|
client.driver.receive_event(event)
|
||||||
|
@ -666,52 +773,6 @@ async def test_remove_node(
|
||||||
assert msg["error"]["code"] == ERR_NOT_LOADED
|
assert msg["error"]["code"] == ERR_NOT_LOADED
|
||||||
|
|
||||||
|
|
||||||
async def test_replace_failed_node_secure(
|
|
||||||
hass,
|
|
||||||
nortek_thermostat,
|
|
||||||
integration,
|
|
||||||
client,
|
|
||||||
hass_ws_client,
|
|
||||||
):
|
|
||||||
"""Test the replace_failed_node websocket command with secure flag."""
|
|
||||||
entry = integration
|
|
||||||
ws_client = await hass_ws_client(hass)
|
|
||||||
|
|
||||||
dev_reg = dr.async_get(hass)
|
|
||||||
|
|
||||||
# Create device registry entry for mock node
|
|
||||||
dev_reg.async_get_or_create(
|
|
||||||
config_entry_id=entry.entry_id,
|
|
||||||
identifiers={(DOMAIN, "3245146787-67")},
|
|
||||||
name="Node 67",
|
|
||||||
)
|
|
||||||
|
|
||||||
client.async_send_command.return_value = {"success": True}
|
|
||||||
|
|
||||||
await ws_client.send_json(
|
|
||||||
{
|
|
||||||
ID: 1,
|
|
||||||
TYPE: "zwave_js/replace_failed_node",
|
|
||||||
ENTRY_ID: entry.entry_id,
|
|
||||||
NODE_ID: 67,
|
|
||||||
SECURE: True,
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
msg = await ws_client.receive_json()
|
|
||||||
assert msg["success"]
|
|
||||||
assert msg["result"]
|
|
||||||
|
|
||||||
assert len(client.async_send_command.call_args_list) == 1
|
|
||||||
assert client.async_send_command.call_args[0][0] == {
|
|
||||||
"command": "controller.replace_failed_node",
|
|
||||||
"nodeId": nortek_thermostat.node_id,
|
|
||||||
"options": {"strategy": InclusionStrategy.SECURITY_S0},
|
|
||||||
}
|
|
||||||
|
|
||||||
client.async_send_command.reset_mock()
|
|
||||||
|
|
||||||
|
|
||||||
async def test_replace_failed_node(
|
async def test_replace_failed_node(
|
||||||
hass,
|
hass,
|
||||||
nortek_thermostat,
|
nortek_thermostat,
|
||||||
|
@ -744,6 +805,7 @@ async def test_replace_failed_node(
|
||||||
TYPE: "zwave_js/replace_failed_node",
|
TYPE: "zwave_js/replace_failed_node",
|
||||||
ENTRY_ID: entry.entry_id,
|
ENTRY_ID: entry.entry_id,
|
||||||
NODE_ID: 67,
|
NODE_ID: 67,
|
||||||
|
INCLUSION_STRATEGY: InclusionStrategy.DEFAULT.value,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -755,7 +817,7 @@ async def test_replace_failed_node(
|
||||||
assert client.async_send_command.call_args[0][0] == {
|
assert client.async_send_command.call_args[0][0] == {
|
||||||
"command": "controller.replace_failed_node",
|
"command": "controller.replace_failed_node",
|
||||||
"nodeId": nortek_thermostat.node_id,
|
"nodeId": nortek_thermostat.node_id,
|
||||||
"options": {"strategy": InclusionStrategy.INSECURE},
|
"options": {"strategy": InclusionStrategy.DEFAULT},
|
||||||
}
|
}
|
||||||
|
|
||||||
client.async_send_command.reset_mock()
|
client.async_send_command.reset_mock()
|
||||||
|
@ -773,12 +835,42 @@ async def test_replace_failed_node(
|
||||||
msg = await ws_client.receive_json()
|
msg = await ws_client.receive_json()
|
||||||
assert msg["event"]["event"] == "inclusion started"
|
assert msg["event"]["event"] == "inclusion started"
|
||||||
|
|
||||||
|
event = Event(
|
||||||
|
type="grant security classes",
|
||||||
|
data={
|
||||||
|
"source": "controller",
|
||||||
|
"event": "grant security classes",
|
||||||
|
"requested": {"securityClasses": [0, 1, 2, 7], "clientSideAuth": False},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
client.driver.receive_event(event)
|
||||||
|
|
||||||
|
msg = await ws_client.receive_json()
|
||||||
|
assert msg["event"]["event"] == "grant security classes"
|
||||||
|
assert msg["event"]["requested_grant"] == {
|
||||||
|
"securityClasses": [0, 1, 2, 7],
|
||||||
|
"clientSideAuth": False,
|
||||||
|
}
|
||||||
|
|
||||||
|
event = Event(
|
||||||
|
type="validate dsk and enter pin",
|
||||||
|
data={
|
||||||
|
"source": "controller",
|
||||||
|
"event": "validate dsk and enter pin",
|
||||||
|
"dsk": "test",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
client.driver.receive_event(event)
|
||||||
|
|
||||||
|
msg = await ws_client.receive_json()
|
||||||
|
assert msg["event"]["event"] == "validate dsk and enter pin"
|
||||||
|
assert msg["event"]["dsk"] == "test"
|
||||||
|
|
||||||
event = Event(
|
event = Event(
|
||||||
type="inclusion stopped",
|
type="inclusion stopped",
|
||||||
data={
|
data={
|
||||||
"source": "controller",
|
"source": "controller",
|
||||||
"event": "inclusion stopped",
|
"event": "inclusion stopped",
|
||||||
"secure": False,
|
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
client.driver.receive_event(event)
|
client.driver.receive_event(event)
|
||||||
|
|
|
@ -251,5 +251,6 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
},
|
||||||
|
"result": {}
|
||||||
}
|
}
|
Loading…
Add table
Add a link
Reference in a new issue