mirror of
https://github.com/home-assistant/core.git
synced 2025-07-24 21:57:51 +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,
|
async_set_config_parameter,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
from homeassistant.components.group import expand_entity_ids
|
||||||
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
|
||||||
import homeassistant.helpers.config_validation as cv
|
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]:
|
def get_nodes_from_service_data(val: dict[str, Any]) -> dict[str, Any]:
|
||||||
"""Get nodes set from service data."""
|
"""Get nodes set from service data."""
|
||||||
nodes: set[ZwaveNode] = set()
|
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:
|
try:
|
||||||
nodes.add(
|
nodes.add(
|
||||||
async_get_node_from_entity_id(
|
async_get_node_from_entity_id(
|
||||||
@ -152,6 +153,7 @@ class ZWaveServices:
|
|||||||
@callback
|
@callback
|
||||||
def validate_entities(val: dict[str, Any]) -> dict[str, Any]:
|
def validate_entities(val: dict[str, Any]) -> dict[str, Any]:
|
||||||
"""Validate entities exist and are from the zwave_js platform."""
|
"""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]:
|
for entity_id in val[ATTR_ENTITY_ID]:
|
||||||
entry = self._ent_reg.async_get(entity_id)
|
entry = self._ent_reg.async_get(entity_id)
|
||||||
if entry is None or entry.platform != const.DOMAIN:
|
if entry is None or entry.platform != const.DOMAIN:
|
||||||
|
@ -5,6 +5,7 @@ import pytest
|
|||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
from zwave_js_server.exceptions import SetValueFailed
|
from zwave_js_server.exceptions import SetValueFailed
|
||||||
|
|
||||||
|
from homeassistant.components.group import Group
|
||||||
from homeassistant.components.zwave_js.const import (
|
from homeassistant.components.zwave_js.const import (
|
||||||
ATTR_BROADCAST,
|
ATTR_BROADCAST,
|
||||||
ATTR_COMMAND_CLASS,
|
ATTR_COMMAND_CLASS,
|
||||||
@ -31,6 +32,7 @@ from homeassistant.helpers.device_registry import (
|
|||||||
async_get as async_get_dev_reg,
|
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 homeassistant.setup import async_setup_component
|
||||||
|
|
||||||
from .common import (
|
from .common import (
|
||||||
AEON_SMART_SWITCH_LIGHT_ENTITY,
|
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()
|
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
|
# Test that we can't include a bitmask value if parameter is a string
|
||||||
with pytest.raises(vol.Invalid):
|
with pytest.raises(vol.Invalid):
|
||||||
await hass.services.async_call(
|
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()
|
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
|
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
|
# Test polling the primary value
|
||||||
client.async_send_command.return_value = {"result": 2}
|
client.async_send_command.return_value = {"result": 2}
|
||||||
await hass.services.async_call(
|
await hass.services.async_call(
|
||||||
@ -620,6 +700,25 @@ async def test_poll_value(
|
|||||||
)
|
)
|
||||||
assert len(client.async_send_command.call_args_list) == 8
|
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
|
# Test polling against an invalid entity raises MultipleInvalid
|
||||||
with pytest.raises(vol.MultipleInvalid):
|
with pytest.raises(vol.MultipleInvalid):
|
||||||
await hass.services.async_call(
|
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()
|
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
|
# Test that when a command fails we raise an exception
|
||||||
client.async_send_command.return_value = {"success": False}
|
client.async_send_command.return_value = {"success": False}
|
||||||
|
|
||||||
@ -878,6 +1017,40 @@ async def test_multicast_set_value(
|
|||||||
|
|
||||||
client.async_send_command.reset_mock()
|
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
|
# Test successful broadcast call
|
||||||
await hass.services.async_call(
|
await hass.services.async_call(
|
||||||
DOMAIN,
|
DOMAIN,
|
||||||
@ -1070,8 +1243,42 @@ async def test_ping(
|
|||||||
blocking=True,
|
blocking=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# assert client.async_send_command.call_args_list is None
|
||||||
assert len(client.async_send_command.call_args_list) == 2
|
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["command"] == "node.ping"
|
||||||
assert args["nodeId"] == climate_danfoss_lc_13.node_id
|
assert args["nodeId"] == climate_danfoss_lc_13.node_id
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user