mirror of
https://github.com/home-assistant/core.git
synced 2025-07-24 21:57:51 +00:00
Add zwave_js.bulk_set_partial_config_parameters service (#48306)
* Add zwave_js.bulk_set_partial_config_parameters service * update to handle command status * add test for awake node * test using a device in service call
This commit is contained in:
parent
114a97bf52
commit
9a75019a65
@ -52,6 +52,7 @@ ATTR_DATA_TYPE = "data_type"
|
|||||||
|
|
||||||
# service constants
|
# service constants
|
||||||
SERVICE_SET_CONFIG_PARAMETER = "set_config_parameter"
|
SERVICE_SET_CONFIG_PARAMETER = "set_config_parameter"
|
||||||
|
SERVICE_BULK_SET_PARTIAL_CONFIG_PARAMETERS = "bulk_set_partial_config_parameters"
|
||||||
|
|
||||||
ATTR_CONFIG_PARAMETER = "parameter"
|
ATTR_CONFIG_PARAMETER = "parameter"
|
||||||
ATTR_CONFIG_PARAMETER_BITMASK = "bitmask"
|
ATTR_CONFIG_PARAMETER_BITMASK = "bitmask"
|
||||||
|
@ -6,7 +6,10 @@ import logging
|
|||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
from zwave_js_server.const import CommandStatus
|
from zwave_js_server.const import CommandStatus
|
||||||
from zwave_js_server.model.node import Node as ZwaveNode
|
from zwave_js_server.model.node import Node as ZwaveNode
|
||||||
from zwave_js_server.util.node import async_set_config_parameter
|
from zwave_js_server.util.node import (
|
||||||
|
async_bulk_set_partial_config_parameters,
|
||||||
|
async_set_config_parameter,
|
||||||
|
)
|
||||||
|
|
||||||
from homeassistant.const import ATTR_DEVICE_ID, ATTR_ENTITY_ID
|
from homeassistant.const import ATTR_DEVICE_ID, ATTR_ENTITY_ID
|
||||||
from homeassistant.core import HomeAssistant, ServiceCall, callback
|
from homeassistant.core import HomeAssistant, ServiceCall, callback
|
||||||
@ -37,7 +40,13 @@ def parameter_name_does_not_need_bitmask(
|
|||||||
# Validates that a bitmask is provided in hex form and converts it to decimal
|
# Validates that a bitmask is provided in hex form and converts it to decimal
|
||||||
# int equivalent since that's what the library uses
|
# int equivalent since that's what the library uses
|
||||||
BITMASK_SCHEMA = vol.All(
|
BITMASK_SCHEMA = vol.All(
|
||||||
cv.string, vol.Lower, vol.Match(r"^(0x)?[0-9a-f]+$"), lambda value: int(value, 16)
|
cv.string,
|
||||||
|
vol.Lower,
|
||||||
|
vol.Match(
|
||||||
|
r"^(0x)?[0-9a-f]+$",
|
||||||
|
msg="Must provide an integer (e.g. 255) or a bitmask in hex form (e.g. 0xff)",
|
||||||
|
),
|
||||||
|
lambda value: int(value, 16),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@ -75,6 +84,30 @@ class ZWaveServices:
|
|||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
self._hass.services.async_register(
|
||||||
|
const.DOMAIN,
|
||||||
|
const.SERVICE_BULK_SET_PARTIAL_CONFIG_PARAMETERS,
|
||||||
|
self.async_bulk_set_partial_config_parameters,
|
||||||
|
schema=vol.All(
|
||||||
|
{
|
||||||
|
vol.Optional(ATTR_DEVICE_ID): vol.All(cv.ensure_list, [cv.string]),
|
||||||
|
vol.Optional(ATTR_ENTITY_ID): cv.entity_ids,
|
||||||
|
vol.Required(const.ATTR_CONFIG_PARAMETER): vol.Any(
|
||||||
|
vol.Coerce(int), cv.string
|
||||||
|
),
|
||||||
|
vol.Required(const.ATTR_CONFIG_VALUE): vol.Any(
|
||||||
|
vol.Coerce(int),
|
||||||
|
{
|
||||||
|
vol.Any(vol.Coerce(int), BITMASK_SCHEMA): vol.Any(
|
||||||
|
vol.Coerce(int), cv.string
|
||||||
|
)
|
||||||
|
},
|
||||||
|
),
|
||||||
|
},
|
||||||
|
cv.has_at_least_one_key(ATTR_DEVICE_ID, ATTR_ENTITY_ID),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
self._hass.services.async_register(
|
self._hass.services.async_register(
|
||||||
const.DOMAIN,
|
const.DOMAIN,
|
||||||
const.SERVICE_REFRESH_VALUE,
|
const.SERVICE_REFRESH_VALUE,
|
||||||
@ -122,6 +155,41 @@ class ZWaveServices:
|
|||||||
|
|
||||||
_LOGGER.info(msg, zwave_value, node, new_value)
|
_LOGGER.info(msg, zwave_value, node, new_value)
|
||||||
|
|
||||||
|
async def async_bulk_set_partial_config_parameters(
|
||||||
|
self, service: ServiceCall
|
||||||
|
) -> None:
|
||||||
|
"""Bulk set multiple partial config values on a node."""
|
||||||
|
nodes: set[ZwaveNode] = set()
|
||||||
|
if ATTR_ENTITY_ID in service.data:
|
||||||
|
nodes |= {
|
||||||
|
async_get_node_from_entity_id(self._hass, entity_id)
|
||||||
|
for entity_id in service.data[ATTR_ENTITY_ID]
|
||||||
|
}
|
||||||
|
if ATTR_DEVICE_ID in service.data:
|
||||||
|
nodes |= {
|
||||||
|
async_get_node_from_device_id(self._hass, device_id)
|
||||||
|
for device_id in service.data[ATTR_DEVICE_ID]
|
||||||
|
}
|
||||||
|
property_ = service.data[const.ATTR_CONFIG_PARAMETER]
|
||||||
|
new_value = service.data[const.ATTR_CONFIG_VALUE]
|
||||||
|
|
||||||
|
for node in nodes:
|
||||||
|
cmd_status = await async_bulk_set_partial_config_parameters(
|
||||||
|
node,
|
||||||
|
property_,
|
||||||
|
new_value,
|
||||||
|
)
|
||||||
|
|
||||||
|
if cmd_status == CommandStatus.ACCEPTED:
|
||||||
|
msg = "Bulk set partials for configuration parameter %s on Node %s"
|
||||||
|
else:
|
||||||
|
msg = (
|
||||||
|
"Added command to queue to bulk set partials for configuration "
|
||||||
|
"parameter %s on Node %s"
|
||||||
|
)
|
||||||
|
|
||||||
|
_LOGGER.info(msg, property_, node)
|
||||||
|
|
||||||
async def async_poll_value(self, service: ServiceCall) -> None:
|
async def async_poll_value(self, service: ServiceCall) -> None:
|
||||||
"""Poll value on a node."""
|
"""Poll value on a node."""
|
||||||
for entity_id in service.data[ATTR_ENTITY_ID]:
|
for entity_id in service.data[ATTR_ENTITY_ID]:
|
||||||
|
@ -59,11 +59,37 @@ set_config_parameter:
|
|||||||
example: 5
|
example: 5
|
||||||
required: true
|
required: true
|
||||||
selector:
|
selector:
|
||||||
object:
|
text:
|
||||||
bitmask:
|
bitmask:
|
||||||
name: Bitmask
|
name: Bitmask
|
||||||
description: Target a specific bitmask (see the documentation for more information).
|
description: Target a specific bitmask (see the documentation for more information).
|
||||||
advanced: true
|
advanced: true
|
||||||
|
selector:
|
||||||
|
text:
|
||||||
|
|
||||||
|
bulk_set_partial_config_parameters:
|
||||||
|
name: Bulk set partial configuration parameters for a Z-Wave device (Advanced).
|
||||||
|
description: Allow for bulk setting partial parameters. Useful when multiple partial parameters have to be set at the same time.
|
||||||
|
target:
|
||||||
|
entity:
|
||||||
|
integration: zwave_js
|
||||||
|
fields:
|
||||||
|
parameter:
|
||||||
|
name: Parameter
|
||||||
|
description: The id of the configuration parameter you want to configure.
|
||||||
|
example: 9
|
||||||
|
required: true
|
||||||
|
selector:
|
||||||
|
text:
|
||||||
|
value:
|
||||||
|
name: Value
|
||||||
|
description: The new value(s) to set for this configuration parameter. Can either be a raw integer value to represent the bulk change or a mapping where the key is the bitmask (either in hex or integer form) and the value is the new value you want to set for that partial parameter.
|
||||||
|
example:
|
||||||
|
"0x1": 1
|
||||||
|
"0x10": 1
|
||||||
|
"0x20": 1
|
||||||
|
"0x40": 1
|
||||||
|
required: true
|
||||||
selector:
|
selector:
|
||||||
object:
|
object:
|
||||||
|
|
||||||
|
@ -8,11 +8,15 @@ from homeassistant.components.zwave_js.const import (
|
|||||||
ATTR_CONFIG_VALUE,
|
ATTR_CONFIG_VALUE,
|
||||||
ATTR_REFRESH_ALL_VALUES,
|
ATTR_REFRESH_ALL_VALUES,
|
||||||
DOMAIN,
|
DOMAIN,
|
||||||
|
SERVICE_BULK_SET_PARTIAL_CONFIG_PARAMETERS,
|
||||||
SERVICE_REFRESH_VALUE,
|
SERVICE_REFRESH_VALUE,
|
||||||
SERVICE_SET_CONFIG_PARAMETER,
|
SERVICE_SET_CONFIG_PARAMETER,
|
||||||
)
|
)
|
||||||
from homeassistant.const import ATTR_DEVICE_ID, ATTR_ENTITY_ID
|
from homeassistant.const import ATTR_DEVICE_ID, ATTR_ENTITY_ID
|
||||||
from homeassistant.helpers.device_registry import async_get as async_get_dev_reg
|
from homeassistant.helpers.device_registry import (
|
||||||
|
async_entries_for_config_entry,
|
||||||
|
async_get as async_get_dev_reg,
|
||||||
|
)
|
||||||
from homeassistant.helpers.entity_registry import async_get as async_get_ent_reg
|
from homeassistant.helpers.entity_registry import async_get as async_get_ent_reg
|
||||||
|
|
||||||
from .common import AIR_TEMPERATURE_SENSOR, CLIMATE_RADIO_THERMOSTAT_ENTITY
|
from .common import AIR_TEMPERATURE_SENSOR, CLIMATE_RADIO_THERMOSTAT_ENTITY
|
||||||
@ -343,6 +347,125 @@ async def test_set_config_parameter(hass, client, multisensor_6, integration):
|
|||||||
client.async_send_command.reset_mock()
|
client.async_send_command.reset_mock()
|
||||||
|
|
||||||
|
|
||||||
|
async def test_bulk_set_config_parameters(hass, client, multisensor_6, integration):
|
||||||
|
"""Test the bulk_set_partial_config_parameters service."""
|
||||||
|
dev_reg = async_get_dev_reg(hass)
|
||||||
|
device = async_entries_for_config_entry(dev_reg, integration.entry_id)[0]
|
||||||
|
# Test setting config parameter by property and property_key
|
||||||
|
await hass.services.async_call(
|
||||||
|
DOMAIN,
|
||||||
|
SERVICE_BULK_SET_PARTIAL_CONFIG_PARAMETERS,
|
||||||
|
{
|
||||||
|
ATTR_DEVICE_ID: device.id,
|
||||||
|
ATTR_CONFIG_PARAMETER: 102,
|
||||||
|
ATTR_CONFIG_VALUE: 241,
|
||||||
|
},
|
||||||
|
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,
|
||||||
|
{
|
||||||
|
ATTR_ENTITY_ID: AIR_TEMPERATURE_SENSOR,
|
||||||
|
ATTR_CONFIG_PARAMETER: 102,
|
||||||
|
ATTR_CONFIG_VALUE: {
|
||||||
|
1: 1,
|
||||||
|
16: 1,
|
||||||
|
32: 1,
|
||||||
|
64: 1,
|
||||||
|
128: 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
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,
|
||||||
|
{
|
||||||
|
ATTR_ENTITY_ID: AIR_TEMPERATURE_SENSOR,
|
||||||
|
ATTR_CONFIG_PARAMETER: 102,
|
||||||
|
ATTR_CONFIG_VALUE: {
|
||||||
|
"0x1": 1,
|
||||||
|
"0x10": 1,
|
||||||
|
"0x20": 1,
|
||||||
|
"0x40": 1,
|
||||||
|
"0x80": 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
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()
|
||||||
|
|
||||||
|
# Test that when a device is awake, we call async_send_command instead of
|
||||||
|
# async_send_command_no_wait
|
||||||
|
multisensor_6.handle_wake_up(None)
|
||||||
|
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: 1,
|
||||||
|
16: 1,
|
||||||
|
32: 1,
|
||||||
|
64: 1,
|
||||||
|
128: 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"] == 52
|
||||||
|
assert args["valueId"] == {
|
||||||
|
"commandClass": 112,
|
||||||
|
"property": 102,
|
||||||
|
}
|
||||||
|
assert args["value"] == 241
|
||||||
|
|
||||||
|
client.async_send_command.reset_mock()
|
||||||
|
|
||||||
|
|
||||||
async def test_poll_value(
|
async def test_poll_value(
|
||||||
hass, client, climate_radio_thermostat_ct100_plus_different_endpoints, integration
|
hass, client, climate_radio_thermostat_ct100_plus_different_endpoints, integration
|
||||||
):
|
):
|
||||||
|
Loading…
x
Reference in New Issue
Block a user