mirror of
https://github.com/home-assistant/core.git
synced 2025-07-23 13:17:32 +00:00
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:
parent
8244887bb3
commit
4119d3198a
@ -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,
|
||||
|
@ -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:
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
),
|
||||
|
@ -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.
|
||||
|
@ -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"]
|
||||
|
@ -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)",
|
||||
|
@ -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": {},
|
||||
},
|
||||
{
|
||||
|
@ -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(
|
||||
|
Loading…
x
Reference in New Issue
Block a user