mirror of
https://github.com/home-assistant/core.git
synced 2025-07-23 05:07:41 +00:00
Support group entities in zwave_js service calls (#54903)
This commit is contained in:
parent
017b8d3615
commit
3a2afb8bde
@ -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:
|
||||
|
@ -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
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user