Support zwave config parameters not on endpoint 0 (#93383)

* Support zwave config parameters not on endpoint 0

* Update device automation logic

* Make endpoint required

* Update homeassistant/components/zwave_js/services.py

Co-authored-by: Martin Hjelmare <marhje52@gmail.com>

* Update homeassistant/components/zwave_js/services.py

Co-authored-by: Martin Hjelmare <marhje52@gmail.com>

---------

Co-authored-by: Martin Hjelmare <marhje52@gmail.com>
This commit is contained in:
Raman Gupta 2023-05-30 23:52:12 -04:00 committed by GitHub
parent 8244887bb3
commit 4119d3198a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 102 additions and 21 deletions

View File

@ -98,6 +98,7 @@ COMMAND_CLASS_ID = "command_class_id"
TYPE = "type"
PROPERTY = "property"
PROPERTY_KEY = "property_key"
ENDPOINT = "endpoint"
VALUE = "value"
# constants for log config commands
@ -1608,6 +1609,7 @@ async def websocket_refresh_node_cc_values(
vol.Required(TYPE): "zwave_js/set_config_parameter",
vol.Required(DEVICE_ID): str,
vol.Required(PROPERTY): int,
vol.Optional(ENDPOINT, default=0): int,
vol.Optional(PROPERTY_KEY): int,
vol.Required(VALUE): vol.Any(int, BITMASK_SCHEMA),
}
@ -1623,12 +1625,13 @@ async def websocket_set_config_parameter(
) -> None:
"""Set a config parameter value for a Z-Wave node."""
property_ = msg[PROPERTY]
endpoint = msg[ENDPOINT]
property_key = msg.get(PROPERTY_KEY)
value = msg[VALUE]
try:
zwave_value, cmd_status = await async_set_config_parameter(
node, value, property_, property_key=property_key
node, value, property_, property_key=property_key, endpoint=endpoint
)
except (InvalidNewValue, NotFoundError, NotImplementedError, SetValueFailed) as err:
code = ERR_UNKNOWN_ERROR
@ -1673,6 +1676,7 @@ async def websocket_get_config_parameters(
result[value_id] = {
"property": zwave_value.property_,
"property_key": zwave_value.property_key,
"endpoint": zwave_value.endpoint,
"configuration_value_type": zwave_value.configuration_value_type.value,
"metadata": {
"description": metadata.description,

View File

@ -101,6 +101,7 @@ RESET_METER_SCHEMA = cv.DEVICE_ACTION_BASE_SCHEMA.extend(
SET_CONFIG_PARAMETER_SCHEMA = cv.DEVICE_ACTION_BASE_SCHEMA.extend(
{
vol.Required(CONF_TYPE): SERVICE_SET_CONFIG_PARAMETER,
vol.Required(ATTR_ENDPOINT): vol.Coerce(int),
vol.Required(ATTR_CONFIG_PARAMETER): vol.Any(int, str),
vol.Required(ATTR_CONFIG_PARAMETER_BITMASK): vol.Any(None, int, str),
vol.Required(ATTR_VALUE): vol.Coerce(int),
@ -168,6 +169,7 @@ async def async_get_actions(
{
**base_action,
CONF_TYPE: SERVICE_SET_CONFIG_PARAMETER,
ATTR_ENDPOINT: config_value.endpoint,
ATTR_CONFIG_PARAMETER: config_value.property_,
ATTR_CONFIG_PARAMETER_BITMASK: config_value.property_key,
CONF_SUBTYPE: generate_config_parameter_subtype(config_value),
@ -347,6 +349,7 @@ async def async_get_action_capabilities(
CommandClass.CONFIGURATION,
config[ATTR_CONFIG_PARAMETER],
property_key=config[ATTR_CONFIG_PARAMETER_BITMASK],
endpoint=config[ATTR_ENDPOINT],
)
value_schema = get_config_parameter_value_schema(node, value_id)
if value_schema is None:

View File

@ -47,9 +47,15 @@ def generate_config_parameter_subtype(config_value: ConfigurationValue) -> str:
if config_value.property_key:
# Property keys for config values are always an int
assert isinstance(config_value.property_key, int)
parameter = f"{parameter}[{hex(config_value.property_key)}]"
parameter = (
f"{parameter}[{hex(config_value.property_key)}] on endpoint "
f"{config_value.endpoint}"
)
return f"{parameter} ({config_value.property_name})"
return (
f"{parameter} ({config_value.property_name}) on endpoint "
f"{config_value.endpoint}"
)
@callback

View File

@ -213,6 +213,7 @@ class ZWaveServices:
cv.ensure_list, [cv.string]
),
vol.Optional(ATTR_ENTITY_ID): cv.entity_ids,
vol.Optional(const.ATTR_ENDPOINT, default=0): vol.Coerce(int),
vol.Required(const.ATTR_CONFIG_PARAMETER): vol.Any(
vol.Coerce(int), cv.string
),
@ -247,6 +248,7 @@ class ZWaveServices:
cv.ensure_list, [cv.string]
),
vol.Optional(ATTR_ENTITY_ID): cv.entity_ids,
vol.Optional(const.ATTR_ENDPOINT, default=0): vol.Coerce(int),
vol.Required(const.ATTR_CONFIG_PARAMETER): vol.Coerce(int),
vol.Required(const.ATTR_CONFIG_VALUE): vol.Any(
vol.Coerce(int),
@ -413,6 +415,7 @@ class ZWaveServices:
async def async_set_config_parameter(self, service: ServiceCall) -> None:
"""Set a config value on a node."""
nodes: set[ZwaveNode] = service.data[const.ATTR_NODES]
endpoint = service.data[const.ATTR_ENDPOINT]
property_or_property_name = service.data[const.ATTR_CONFIG_PARAMETER]
property_key = service.data.get(const.ATTR_CONFIG_PARAMETER_BITMASK)
new_value = service.data[const.ATTR_CONFIG_VALUE]
@ -424,6 +427,7 @@ class ZWaveServices:
new_value,
property_or_property_name,
property_key=property_key,
endpoint=endpoint,
)
for node in nodes
),
@ -448,6 +452,7 @@ class ZWaveServices:
) -> None:
"""Bulk set multiple partial config values on a node."""
nodes: set[ZwaveNode] = service.data[const.ATTR_NODES]
endpoint = service.data[const.ATTR_ENDPOINT]
property_ = service.data[const.ATTR_CONFIG_PARAMETER]
new_value = service.data[const.ATTR_CONFIG_VALUE]
@ -457,6 +462,7 @@ class ZWaveServices:
node,
property_,
new_value,
endpoint=endpoint,
)
for node in nodes
),

View File

@ -46,6 +46,14 @@ set_config_parameter:
entity:
integration: zwave_js
fields:
endpoint:
name: Endpoint
description: The configuration parameter's endpoint.
example: 1
default: 0
required: false
selector:
text:
parameter:
name: Parameter
description: The (name or id of the) configuration parameter you want to configure.
@ -53,6 +61,12 @@ set_config_parameter:
required: true
selector:
text:
bitmask:
name: Bitmask
description: Target a specific bitmask (see the documentation for more information).
advanced: true
selector:
text:
value:
name: Value
description: The new value to set for this configuration parameter.
@ -60,12 +74,6 @@ set_config_parameter:
required: true
selector:
text:
bitmask:
name: Bitmask
description: Target a specific bitmask (see the documentation for more information).
advanced: true
selector:
text:
bulk_set_partial_config_parameters:
name: Bulk set partial configuration parameters for a Z-Wave device (Advanced).
@ -74,6 +82,14 @@ bulk_set_partial_config_parameters:
entity:
integration: zwave_js
fields:
endpoint:
name: Endpoint
description: The configuration parameter's endpoint.
example: 1
default: 0
required: false
selector:
text:
parameter:
name: Parameter
description: The id of the configuration parameter you want to configure.

View File

@ -32,6 +32,7 @@ from zwave_js_server.model.controller import (
from zwave_js_server.model.controller.firmware import ControllerFirmwareUpdateData
from zwave_js_server.model.node import Node
from zwave_js_server.model.node.firmware import NodeFirmwareUpdateData
from zwave_js_server.model.value import ConfigurationValue, get_value_id_str
from homeassistant.components.websocket_api import ERR_INVALID_FORMAT, ERR_NOT_FOUND
from homeassistant.components.zwave_js.api import (
@ -43,6 +44,7 @@ from homeassistant.components.zwave_js.api import (
DEVICE_ID,
DSK,
ENABLED,
ENDPOINT,
ENTRY_ID,
ERR_NOT_LOADED,
FEATURE,
@ -2756,6 +2758,12 @@ async def test_set_config_parameter(
entry = integration
ws_client = await hass_ws_client(hass)
device = get_device(hass, multisensor_6)
new_value_data = multisensor_6.values[
get_value_id_str(multisensor_6, 112, 102, 0, 1)
].data.copy()
new_value_data["endpoint"] = 1
new_value = ConfigurationValue(multisensor_6, new_value_data)
multisensor_6.values[get_value_id_str(multisensor_6, 112, 102, 1, 1)] = new_value
client.async_send_command_no_wait.return_value = None
@ -2787,12 +2795,44 @@ async def test_set_config_parameter(
client.async_send_command_no_wait.reset_mock()
client.async_send_command_no_wait.return_value = None
# Test using a different endpoint
await ws_client.send_json(
{
ID: 2,
TYPE: "zwave_js/set_config_parameter",
DEVICE_ID: device.id,
ENDPOINT: 1,
PROPERTY: 102,
PROPERTY_KEY: 1,
VALUE: 1,
}
)
msg = await ws_client.receive_json()
assert msg["success"]
assert len(client.async_send_command_no_wait.call_args_list) == 1
args = client.async_send_command_no_wait.call_args[0][0]
assert args["command"] == "node.set_value"
assert args["nodeId"] == 52
assert args["valueId"] == {
"commandClass": 112,
"endpoint": 1,
"property": 102,
"propertyKey": 1,
}
assert args["value"] == 1
client.async_send_command_no_wait.reset_mock()
# Test that hex strings are accepted and converted as expected
client.async_send_command_no_wait.return_value = None
await ws_client.send_json(
{
ID: 2,
ID: 3,
TYPE: "zwave_js/set_config_parameter",
DEVICE_ID: device.id,
PROPERTY: 102,
@ -2824,7 +2864,7 @@ async def test_set_config_parameter(
set_param_mock.side_effect = InvalidNewValue("test")
await ws_client.send_json(
{
ID: 3,
ID: 4,
TYPE: "zwave_js/set_config_parameter",
DEVICE_ID: device.id,
PROPERTY: 102,
@ -2843,7 +2883,7 @@ async def test_set_config_parameter(
set_param_mock.side_effect = NotFoundError("test")
await ws_client.send_json(
{
ID: 4,
ID: 5,
TYPE: "zwave_js/set_config_parameter",
DEVICE_ID: device.id,
PROPERTY: 102,
@ -2862,7 +2902,7 @@ async def test_set_config_parameter(
set_param_mock.side_effect = SetValueFailed("test")
await ws_client.send_json(
{
ID: 5,
ID: 6,
TYPE: "zwave_js/set_config_parameter",
DEVICE_ID: device.id,
PROPERTY: 102,
@ -2881,7 +2921,7 @@ async def test_set_config_parameter(
# Test getting non-existent node fails
await ws_client.send_json(
{
ID: 6,
ID: 7,
TYPE: "zwave_js/set_config_parameter",
DEVICE_ID: "fake_device",
PROPERTY: 102,
@ -2900,7 +2940,7 @@ async def test_set_config_parameter(
):
await ws_client.send_json(
{
ID: 7,
ID: 8,
TYPE: "zwave_js/set_config_parameter",
DEVICE_ID: device.id,
PROPERTY: 102,
@ -2920,7 +2960,7 @@ async def test_set_config_parameter(
await ws_client.send_json(
{
ID: 8,
ID: 9,
TYPE: "zwave_js/set_config_parameter",
DEVICE_ID: device.id,
PROPERTY: 102,
@ -2959,6 +2999,7 @@ async def test_get_config_parameters(
key = "52-112-0-2"
assert result[key]["property"] == 2
assert result[key]["property_key"] is None
assert result[key]["endpoint"] == 0
assert result[key]["metadata"]["type"] == "number"
assert result[key]["configuration_value_type"] == "enumerated"
assert result[key]["metadata"]["states"]

View File

@ -79,9 +79,10 @@ async def test_get_actions(
"domain": DOMAIN,
"type": "set_config_parameter",
"device_id": device.id,
"endpoint": 0,
"parameter": 3,
"bitmask": None,
"subtype": "3 (Beeper)",
"subtype": "3 (Beeper) on endpoint 0",
"metadata": {},
},
]
@ -188,6 +189,7 @@ async def test_actions(
"domain": DOMAIN,
"type": "set_config_parameter",
"device_id": device.id,
"endpoint": 0,
"parameter": 1,
"bitmask": None,
"subtype": "3 (Beeper)",
@ -510,6 +512,7 @@ async def test_get_action_capabilities(
"domain": DOMAIN,
"device_id": device.id,
"type": "set_config_parameter",
"endpoint": 0,
"parameter": 1,
"bitmask": None,
"subtype": "1 (Temperature Reporting Threshold)",
@ -542,6 +545,7 @@ async def test_get_action_capabilities(
"domain": DOMAIN,
"device_id": device.id,
"type": "set_config_parameter",
"endpoint": 0,
"parameter": 10,
"bitmask": None,
"subtype": "10 (Temperature Reporting Filter)",
@ -569,6 +573,7 @@ async def test_get_action_capabilities(
"domain": DOMAIN,
"device_id": device.id,
"type": "set_config_parameter",
"endpoint": 0,
"parameter": 2,
"bitmask": None,
"subtype": "2 (HVAC Settings)",

View File

@ -28,7 +28,7 @@ from tests.common import async_get_device_automations, async_mock_service
@pytest.fixture
def calls(hass):
def calls(hass: HomeAssistant):
"""Track calls to a mock service."""
return async_mock_service(hass, "test", "automation")
@ -63,7 +63,7 @@ async def test_get_conditions(
"type": "config_parameter",
"device_id": device.id,
"value_id": value_id,
"subtype": f"{config_value.property_} ({name})",
"subtype": f"{config_value.property_} ({name}) on endpoint 0",
"metadata": {},
},
{

View File

@ -32,7 +32,7 @@ from tests.common import (
@pytest.fixture
def calls(hass):
def calls(hass: HomeAssistant):
"""Track calls to a mock service."""
return async_mock_service(hass, "test", "automation")
@ -1292,7 +1292,7 @@ async def test_get_value_updated_config_parameter_triggers(
"property_key": None,
"endpoint": 0,
"command_class": CommandClass.CONFIGURATION.value,
"subtype": "3 (Beeper)",
"subtype": "3 (Beeper) on endpoint 0",
"metadata": {},
}
triggers = await async_get_device_automations(