Support group entities in zwave_js service calls (#54903)

This commit is contained in:
Raman Gupta 2021-08-20 05:18:19 -04:00 committed by GitHub
parent 017b8d3615
commit 3a2afb8bde
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 213 additions and 4 deletions

View File

@ -17,6 +17,7 @@ from zwave_js_server.util.node import (
async_set_config_parameter,
)
from homeassistant.components.group import expand_entity_ids
from homeassistant.const import ATTR_DEVICE_ID, ATTR_ENTITY_ID
from homeassistant.core import HomeAssistant, ServiceCall, callback
import homeassistant.helpers.config_validation as cv
@ -95,7 +96,7 @@ class ZWaveServices:
def get_nodes_from_service_data(val: dict[str, Any]) -> dict[str, Any]:
"""Get nodes set from service data."""
nodes: set[ZwaveNode] = set()
for entity_id in val.pop(ATTR_ENTITY_ID, []):
for entity_id in expand_entity_ids(self._hass, val.pop(ATTR_ENTITY_ID, [])):
try:
nodes.add(
async_get_node_from_entity_id(
@ -152,6 +153,7 @@ class ZWaveServices:
@callback
def validate_entities(val: dict[str, Any]) -> dict[str, Any]:
"""Validate entities exist and are from the zwave_js platform."""
val[ATTR_ENTITY_ID] = expand_entity_ids(self._hass, val[ATTR_ENTITY_ID])
for entity_id in val[ATTR_ENTITY_ID]:
entry = self._ent_reg.async_get(entity_id)
if entry is None or entry.platform != const.DOMAIN:

View File

@ -5,6 +5,7 @@ import pytest
import voluptuous as vol
from zwave_js_server.exceptions import SetValueFailed
from homeassistant.components.group import Group
from homeassistant.components.zwave_js.const import (
ATTR_BROADCAST,
ATTR_COMMAND_CLASS,
@ -31,6 +32,7 @@ from homeassistant.helpers.device_registry import (
async_get as async_get_dev_reg,
)
from homeassistant.helpers.entity_registry import async_get as async_get_ent_reg
from homeassistant.setup import async_setup_component
from .common import (
AEON_SMART_SWITCH_LIGHT_ENTITY,
@ -268,6 +270,52 @@ async def test_set_config_parameter(hass, client, multisensor_6, integration):
client.async_send_command_no_wait.reset_mock()
# Test groups get expanded
assert await async_setup_component(hass, "group", {})
await Group.async_create_group(hass, "test", [AIR_TEMPERATURE_SENSOR])
await hass.services.async_call(
DOMAIN,
SERVICE_SET_CONFIG_PARAMETER,
{
ATTR_ENTITY_ID: "group.test",
ATTR_CONFIG_PARAMETER: 102,
ATTR_CONFIG_PARAMETER_BITMASK: "0x01",
ATTR_CONFIG_VALUE: 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"] == {
"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 that we can't include a bitmask value if parameter is a string
with pytest.raises(vol.Invalid):
await hass.services.async_call(
@ -550,11 +598,43 @@ async def test_bulk_set_config_parameters(hass, client, multisensor_6, integrati
client.async_send_command.reset_mock()
# Test groups get expanded
assert await async_setup_component(hass, "group", {})
await Group.async_create_group(hass, "test", [AIR_TEMPERATURE_SENSOR])
await hass.services.async_call(
DOMAIN,
SERVICE_BULK_SET_PARTIAL_CONFIG_PARAMETERS,
{
ATTR_ENTITY_ID: "group.test",
ATTR_CONFIG_PARAMETER: 102,
ATTR_CONFIG_VALUE: {
1: 1,
16: 1,
32: 1,
64: 1,
128: 1,
},
},
blocking=True,
)
async def test_poll_value(
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_refresh_value(
hass, client, climate_radio_thermostat_ct100_plus_different_endpoints, integration
):
"""Test the poll_value service."""
"""Test the refresh_value service."""
# Test polling the primary value
client.async_send_command.return_value = {"result": 2}
await hass.services.async_call(
@ -620,6 +700,25 @@ async def test_poll_value(
)
assert len(client.async_send_command.call_args_list) == 8
client.async_send_command.reset_mock()
# Test groups get expanded
assert await async_setup_component(hass, "group", {})
await Group.async_create_group(hass, "test", [CLIMATE_RADIO_THERMOSTAT_ENTITY])
client.async_send_command.return_value = {"result": 2}
await hass.services.async_call(
DOMAIN,
SERVICE_REFRESH_VALUE,
{
ATTR_ENTITY_ID: "group.test",
ATTR_REFRESH_ALL_VALUES: "true",
},
blocking=True,
)
assert len(client.async_send_command.call_args_list) == 8
client.async_send_command.reset_mock()
# Test polling against an invalid entity raises MultipleInvalid
with pytest.raises(vol.MultipleInvalid):
await hass.services.async_call(
@ -709,6 +808,46 @@ async def test_set_value(hass, client, climate_danfoss_lc_13, integration):
client.async_send_command.reset_mock()
# Test groups get expanded
assert await async_setup_component(hass, "group", {})
await Group.async_create_group(hass, "test", [CLIMATE_DANFOSS_LC13_ENTITY])
await hass.services.async_call(
DOMAIN,
SERVICE_SET_VALUE,
{
ATTR_ENTITY_ID: "group.test",
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}
@ -878,6 +1017,40 @@ async def test_multicast_set_value(
client.async_send_command.reset_mock()
# Test groups get expanded for multicast call
assert await async_setup_component(hass, "group", {})
await Group.async_create_group(
hass, "test", [CLIMATE_DANFOSS_LC13_ENTITY, CLIMATE_EUROTRONICS_SPIRIT_Z_ENTITY]
)
await hass.services.async_call(
DOMAIN,
SERVICE_MULTICAST_SET_VALUE,
{
ATTR_ENTITY_ID: "group.test",
ATTR_COMMAND_CLASS: 67,
ATTR_PROPERTY: "setpoint",
ATTR_PROPERTY_KEY: 1,
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_eurotronic_spirit_z.node_id,
climate_danfoss_lc_13.node_id,
]
assert args["valueId"] == {
"commandClass": 67,
"property": "setpoint",
"propertyKey": 1,
}
assert args["value"] == 2
client.async_send_command.reset_mock()
# Test successful broadcast call
await hass.services.async_call(
DOMAIN,
@ -1070,8 +1243,42 @@ async def test_ping(
blocking=True,
)
# assert client.async_send_command.call_args_list is None
assert len(client.async_send_command.call_args_list) == 2
args = client.async_send_command.call_args[0][0]
args = client.async_send_command.call_args_list[0][0][0]
assert args["command"] == "node.ping"
assert (
args["nodeId"]
== climate_radio_thermostat_ct100_plus_different_endpoints.node_id
)
args = client.async_send_command.call_args_list[1][0][0]
assert args["command"] == "node.ping"
assert args["nodeId"] == climate_danfoss_lc_13.node_id
client.async_send_command.reset_mock()
# Test groups get expanded for multicast call
assert await async_setup_component(hass, "group", {})
await Group.async_create_group(
hass, "test", [CLIMATE_DANFOSS_LC13_ENTITY, CLIMATE_RADIO_THERMOSTAT_ENTITY]
)
await hass.services.async_call(
DOMAIN,
SERVICE_PING,
{
ATTR_ENTITY_ID: "group.test",
},
blocking=True,
)
assert len(client.async_send_command.call_args_list) == 2
args = client.async_send_command.call_args_list[0][0][0]
assert args["command"] == "node.ping"
assert (
args["nodeId"]
== climate_radio_thermostat_ct100_plus_different_endpoints.node_id
)
args = client.async_send_command.call_args_list[1][0][0]
assert args["command"] == "node.ping"
assert args["nodeId"] == climate_danfoss_lc_13.node_id