mirror of
https://github.com/home-assistant/core.git
synced 2025-07-21 12:17:07 +00:00
Support bitmask as a value (#51892)
* Support bitmask as a value * Fix value schema and add tests * fix test * revert change to value schema
This commit is contained in:
parent
31db3fcb23
commit
a250343c55
@ -51,6 +51,7 @@ from .const import (
|
||||
EVENT_DEVICE_ADDED_TO_REGISTRY,
|
||||
)
|
||||
from .helpers import async_enable_statistics, update_data_collection_preference
|
||||
from .services import BITMASK_SCHEMA
|
||||
|
||||
# general API constants
|
||||
ID = "id"
|
||||
@ -925,7 +926,7 @@ async def websocket_refresh_node_cc_values(
|
||||
vol.Required(NODE_ID): int,
|
||||
vol.Required(PROPERTY): int,
|
||||
vol.Optional(PROPERTY_KEY): int,
|
||||
vol.Required(VALUE): int,
|
||||
vol.Required(VALUE): vol.Any(int, BITMASK_SCHEMA),
|
||||
}
|
||||
)
|
||||
@websocket_api.async_response
|
||||
|
@ -66,6 +66,14 @@ BITMASK_SCHEMA = vol.All(
|
||||
lambda value: int(value, 16),
|
||||
)
|
||||
|
||||
VALUE_SCHEMA = vol.Any(
|
||||
bool,
|
||||
vol.Coerce(int),
|
||||
vol.Coerce(float),
|
||||
BITMASK_SCHEMA,
|
||||
cv.string,
|
||||
)
|
||||
|
||||
|
||||
class ZWaveServices:
|
||||
"""Class that holds our services (Zwave Commands) that should be published to hass."""
|
||||
@ -177,7 +185,7 @@ class ZWaveServices:
|
||||
vol.Coerce(int), BITMASK_SCHEMA
|
||||
),
|
||||
vol.Required(const.ATTR_CONFIG_VALUE): vol.Any(
|
||||
vol.Coerce(int), cv.string
|
||||
vol.Coerce(int), BITMASK_SCHEMA, cv.string
|
||||
),
|
||||
},
|
||||
cv.has_at_least_one_key(ATTR_DEVICE_ID, ATTR_ENTITY_ID),
|
||||
@ -204,7 +212,7 @@ class ZWaveServices:
|
||||
{
|
||||
vol.Any(
|
||||
vol.Coerce(int), BITMASK_SCHEMA, cv.string
|
||||
): vol.Any(vol.Coerce(int), cv.string)
|
||||
): vol.Any(vol.Coerce(int), BITMASK_SCHEMA, cv.string)
|
||||
},
|
||||
),
|
||||
},
|
||||
@ -224,7 +232,7 @@ class ZWaveServices:
|
||||
vol.Required(ATTR_ENTITY_ID): cv.entity_ids,
|
||||
vol.Optional(
|
||||
const.ATTR_REFRESH_ALL_VALUES, default=False
|
||||
): bool,
|
||||
): cv.boolean,
|
||||
},
|
||||
validate_entities,
|
||||
)
|
||||
@ -250,10 +258,8 @@ class ZWaveServices:
|
||||
vol.Coerce(int), str
|
||||
),
|
||||
vol.Optional(const.ATTR_ENDPOINT): vol.Coerce(int),
|
||||
vol.Required(const.ATTR_VALUE): vol.Any(
|
||||
bool, vol.Coerce(int), vol.Coerce(float), cv.string
|
||||
),
|
||||
vol.Optional(const.ATTR_WAIT_FOR_RESULT): vol.Coerce(bool),
|
||||
vol.Required(const.ATTR_VALUE): VALUE_SCHEMA,
|
||||
vol.Optional(const.ATTR_WAIT_FOR_RESULT): cv.boolean,
|
||||
},
|
||||
cv.has_at_least_one_key(ATTR_DEVICE_ID, ATTR_ENTITY_ID),
|
||||
get_nodes_from_service_data,
|
||||
@ -281,9 +287,7 @@ class ZWaveServices:
|
||||
vol.Coerce(int), str
|
||||
),
|
||||
vol.Optional(const.ATTR_ENDPOINT): vol.Coerce(int),
|
||||
vol.Required(const.ATTR_VALUE): vol.Any(
|
||||
bool, vol.Coerce(int), vol.Coerce(float), cv.string
|
||||
),
|
||||
vol.Required(const.ATTR_VALUE): VALUE_SCHEMA,
|
||||
},
|
||||
vol.Any(
|
||||
cv.has_at_least_one_key(ATTR_DEVICE_ID, ATTR_ENTITY_ID),
|
||||
|
@ -1179,13 +1179,62 @@ async def test_set_config_parameter(
|
||||
|
||||
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,
|
||||
TYPE: "zwave_js/set_config_parameter",
|
||||
ENTRY_ID: entry.entry_id,
|
||||
NODE_ID: 52,
|
||||
PROPERTY: 102,
|
||||
PROPERTY_KEY: 1,
|
||||
VALUE: "0x1",
|
||||
}
|
||||
)
|
||||
|
||||
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"] == {
|
||||
"commandClassName": "Configuration",
|
||||
"commandClass": 112,
|
||||
"endpoint": 0,
|
||||
"property": 102,
|
||||
"propertyName": "Group 2: Send battery reports",
|
||||
"propertyKey": 1,
|
||||
"metadata": {
|
||||
"type": "number",
|
||||
"readable": True,
|
||||
"writeable": True,
|
||||
"valueSize": 4,
|
||||
"min": 0,
|
||||
"max": 1,
|
||||
"default": 1,
|
||||
"format": 0,
|
||||
"allowManualEntry": True,
|
||||
"label": "Group 2: Send battery reports",
|
||||
"description": "Include battery information in periodic reports to Group 2",
|
||||
"isFromConfig": True,
|
||||
},
|
||||
"value": 0,
|
||||
}
|
||||
assert args["value"] == 1
|
||||
|
||||
client.async_send_command_no_wait.reset_mock()
|
||||
|
||||
with patch(
|
||||
"homeassistant.components.zwave_js.api.async_set_config_parameter",
|
||||
) as set_param_mock:
|
||||
set_param_mock.side_effect = InvalidNewValue("test")
|
||||
await ws_client.send_json(
|
||||
{
|
||||
ID: 2,
|
||||
ID: 3,
|
||||
TYPE: "zwave_js/set_config_parameter",
|
||||
ENTRY_ID: entry.entry_id,
|
||||
NODE_ID: 52,
|
||||
@ -1205,7 +1254,7 @@ async def test_set_config_parameter(
|
||||
set_param_mock.side_effect = NotFoundError("test")
|
||||
await ws_client.send_json(
|
||||
{
|
||||
ID: 3,
|
||||
ID: 4,
|
||||
TYPE: "zwave_js/set_config_parameter",
|
||||
ENTRY_ID: entry.entry_id,
|
||||
NODE_ID: 52,
|
||||
@ -1225,7 +1274,7 @@ async def test_set_config_parameter(
|
||||
set_param_mock.side_effect = SetValueFailed("test")
|
||||
await ws_client.send_json(
|
||||
{
|
||||
ID: 4,
|
||||
ID: 5,
|
||||
TYPE: "zwave_js/set_config_parameter",
|
||||
ENTRY_ID: entry.entry_id,
|
||||
NODE_ID: 52,
|
||||
@ -1245,7 +1294,7 @@ async def test_set_config_parameter(
|
||||
# Test getting non-existent node fails
|
||||
await ws_client.send_json(
|
||||
{
|
||||
ID: 5,
|
||||
ID: 6,
|
||||
TYPE: "zwave_js/set_config_parameter",
|
||||
ENTRY_ID: entry.entry_id,
|
||||
NODE_ID: 9999,
|
||||
@ -1264,7 +1313,7 @@ async def test_set_config_parameter(
|
||||
|
||||
await ws_client.send_json(
|
||||
{
|
||||
ID: 6,
|
||||
ID: 7,
|
||||
TYPE: "zwave_js/set_config_parameter",
|
||||
ENTRY_ID: entry.entry_id,
|
||||
NODE_ID: 52,
|
||||
|
@ -89,6 +89,50 @@ async def test_set_config_parameter(hass, client, multisensor_6, integration):
|
||||
|
||||
client.async_send_command_no_wait.reset_mock()
|
||||
|
||||
# Test setting config parameter value in hex
|
||||
await hass.services.async_call(
|
||||
DOMAIN,
|
||||
SERVICE_SET_CONFIG_PARAMETER,
|
||||
{
|
||||
ATTR_ENTITY_ID: AIR_TEMPERATURE_SENSOR,
|
||||
ATTR_CONFIG_PARAMETER: 102,
|
||||
ATTR_CONFIG_PARAMETER_BITMASK: 1,
|
||||
ATTR_CONFIG_VALUE: "0x1",
|
||||
},
|
||||
blocking=True,
|
||||
)
|
||||
|
||||
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"] == {
|
||||
"commandClassName": "Configuration",
|
||||
"commandClass": 112,
|
||||
"endpoint": 0,
|
||||
"property": 102,
|
||||
"propertyName": "Group 2: Send battery reports",
|
||||
"propertyKey": 1,
|
||||
"metadata": {
|
||||
"type": "number",
|
||||
"readable": True,
|
||||
"writeable": True,
|
||||
"valueSize": 4,
|
||||
"min": 0,
|
||||
"max": 1,
|
||||
"default": 1,
|
||||
"format": 0,
|
||||
"allowManualEntry": True,
|
||||
"label": "Group 2: Send battery reports",
|
||||
"description": "Include battery information in periodic reports to Group 2",
|
||||
"isFromConfig": True,
|
||||
},
|
||||
"value": 0,
|
||||
}
|
||||
assert args["value"] == 1
|
||||
|
||||
client.async_send_command_no_wait.reset_mock()
|
||||
|
||||
# Test setting parameter by property name
|
||||
await hass.services.async_call(
|
||||
DOMAIN,
|
||||
@ -419,6 +463,36 @@ async def test_bulk_set_config_parameters(hass, client, multisensor_6, integrati
|
||||
|
||||
client.async_send_command_no_wait.reset_mock()
|
||||
|
||||
# Test using hex values for config parameter values
|
||||
await hass.services.async_call(
|
||||
DOMAIN,
|
||||
SERVICE_BULK_SET_PARTIAL_CONFIG_PARAMETERS,
|
||||
{
|
||||
ATTR_ENTITY_ID: AIR_TEMPERATURE_SENSOR,
|
||||
ATTR_CONFIG_PARAMETER: 102,
|
||||
ATTR_CONFIG_VALUE: {
|
||||
1: "0x1",
|
||||
16: "0x1",
|
||||
32: "0x1",
|
||||
64: "0x1",
|
||||
128: "0x1",
|
||||
},
|
||||
},
|
||||
blocking=True,
|
||||
)
|
||||
|
||||
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,
|
||||
"property": 102,
|
||||
}
|
||||
assert args["value"] == 241
|
||||
|
||||
client.async_send_command_no_wait.reset_mock()
|
||||
|
||||
await hass.services.async_call(
|
||||
DOMAIN,
|
||||
SERVICE_BULK_SET_PARTIAL_CONFIG_PARAMETERS,
|
||||
@ -535,6 +609,21 @@ async def test_poll_value(
|
||||
)
|
||||
assert len(client.async_send_command.call_args_list) == 8
|
||||
|
||||
client.async_send_command.reset_mock()
|
||||
|
||||
# Test polling all watched values using string for boolean
|
||||
client.async_send_command.return_value = {"result": 2}
|
||||
await hass.services.async_call(
|
||||
DOMAIN,
|
||||
SERVICE_REFRESH_VALUE,
|
||||
{
|
||||
ATTR_ENTITY_ID: CLIMATE_RADIO_THERMOSTAT_ENTITY,
|
||||
ATTR_REFRESH_ALL_VALUES: "true",
|
||||
},
|
||||
blocking=True,
|
||||
)
|
||||
assert len(client.async_send_command.call_args_list) == 8
|
||||
|
||||
# Test polling against an invalid entity raises MultipleInvalid
|
||||
with pytest.raises(vol.MultipleInvalid):
|
||||
await hass.services.async_call(
|
||||
@ -586,6 +675,44 @@ async def test_set_value(hass, client, climate_danfoss_lc_13, integration):
|
||||
|
||||
client.async_send_command_no_wait.reset_mock()
|
||||
|
||||
# Test bitmask as value and non bool as bool
|
||||
await hass.services.async_call(
|
||||
DOMAIN,
|
||||
SERVICE_SET_VALUE,
|
||||
{
|
||||
ATTR_ENTITY_ID: CLIMATE_DANFOSS_LC13_ENTITY,
|
||||
ATTR_COMMAND_CLASS: 117,
|
||||
ATTR_PROPERTY: "local",
|
||||
ATTR_VALUE: "0x2",
|
||||
ATTR_WAIT_FOR_RESULT: 1,
|
||||
},
|
||||
blocking=True,
|
||||
)
|
||||
|
||||
assert len(client.async_send_command.call_args_list) == 1
|
||||
args = client.async_send_command.call_args[0][0]
|
||||
assert args["command"] == "node.set_value"
|
||||
assert args["nodeId"] == 5
|
||||
assert args["valueId"] == {
|
||||
"commandClassName": "Protection",
|
||||
"commandClass": 117,
|
||||
"endpoint": 0,
|
||||
"property": "local",
|
||||
"propertyName": "local",
|
||||
"ccVersion": 2,
|
||||
"metadata": {
|
||||
"type": "number",
|
||||
"readable": True,
|
||||
"writeable": True,
|
||||
"label": "Local protection state",
|
||||
"states": {"0": "Unprotected", "2": "NoOperationPossible"},
|
||||
},
|
||||
"value": 0,
|
||||
}
|
||||
assert args["value"] == 2
|
||||
|
||||
client.async_send_command.reset_mock()
|
||||
|
||||
# Test that when a command fails we raise an exception
|
||||
client.async_send_command.return_value = {"success": False}
|
||||
|
||||
@ -680,6 +807,37 @@ async def test_multicast_set_value(
|
||||
|
||||
client.async_send_command.reset_mock()
|
||||
|
||||
# Test successful multicast call with hex value
|
||||
await hass.services.async_call(
|
||||
DOMAIN,
|
||||
SERVICE_MULTICAST_SET_VALUE,
|
||||
{
|
||||
ATTR_ENTITY_ID: [
|
||||
CLIMATE_DANFOSS_LC13_ENTITY,
|
||||
CLIMATE_RADIO_THERMOSTAT_ENTITY,
|
||||
],
|
||||
ATTR_COMMAND_CLASS: 117,
|
||||
ATTR_PROPERTY: "local",
|
||||
ATTR_VALUE: "0x2",
|
||||
},
|
||||
blocking=True,
|
||||
)
|
||||
|
||||
assert len(client.async_send_command.call_args_list) == 1
|
||||
args = client.async_send_command.call_args[0][0]
|
||||
assert args["command"] == "multicast_group.set_value"
|
||||
assert args["nodeIDs"] == [
|
||||
climate_radio_thermostat_ct100_plus_different_endpoints.node_id,
|
||||
climate_danfoss_lc_13.node_id,
|
||||
]
|
||||
assert args["valueId"] == {
|
||||
"commandClass": 117,
|
||||
"property": "local",
|
||||
}
|
||||
assert args["value"] == 2
|
||||
|
||||
client.async_send_command.reset_mock()
|
||||
|
||||
# Test successful broadcast call
|
||||
await hass.services.async_call(
|
||||
DOMAIN,
|
||||
|
Loading…
x
Reference in New Issue
Block a user