Fix zwave_js device actions (#63769)
parent
b5bb692fe4
commit
7b3e5fdf9d
|
@ -14,7 +14,14 @@ from zwave_js_server.util.command_class.meter import get_meter_type
|
|||
|
||||
from homeassistant.components.lock import DOMAIN as LOCK_DOMAIN
|
||||
from homeassistant.components.sensor import DOMAIN as SENSOR_DOMAIN
|
||||
from homeassistant.const import CONF_DEVICE_ID, CONF_DOMAIN, CONF_ENTITY_ID, CONF_TYPE
|
||||
from homeassistant.const import (
|
||||
ATTR_DEVICE_ID,
|
||||
ATTR_DOMAIN,
|
||||
CONF_DEVICE_ID,
|
||||
CONF_DOMAIN,
|
||||
CONF_ENTITY_ID,
|
||||
CONF_TYPE,
|
||||
)
|
||||
from homeassistant.core import Context, HomeAssistant
|
||||
from homeassistant.exceptions import HomeAssistantError
|
||||
from homeassistant.helpers import entity_registry
|
||||
|
@ -227,7 +234,22 @@ async def async_call_action_from_config(
|
|||
if action_type not in ACTION_TYPES:
|
||||
raise HomeAssistantError(f"Unhandled action type {action_type}")
|
||||
|
||||
service_data = {k: v for k, v in config.items() if v not in (None, "")}
|
||||
# Don't include domain, subtype or any null/empty values in the service call
|
||||
service_data = {
|
||||
k: v
|
||||
for k, v in config.items()
|
||||
if k not in (ATTR_DOMAIN, CONF_SUBTYPE) and v not in (None, "")
|
||||
}
|
||||
|
||||
# Entity services (including refresh value which is a fake entity service) expects
|
||||
# just an entity ID
|
||||
if action_type in (
|
||||
SERVICE_REFRESH_VALUE,
|
||||
SERVICE_SET_LOCK_USERCODE,
|
||||
SERVICE_CLEAR_LOCK_USERCODE,
|
||||
SERVICE_RESET_METER,
|
||||
):
|
||||
service_data.pop(ATTR_DEVICE_ID)
|
||||
await hass.services.async_call(
|
||||
DOMAIN, service, service_data, blocking=True, context=context
|
||||
)
|
||||
|
@ -283,7 +305,10 @@ async def async_get_action_capabilities(
|
|||
"extra_fields": vol.Schema(
|
||||
{
|
||||
vol.Required(ATTR_COMMAND_CLASS): vol.In(
|
||||
{cc.value: cc.name for cc in CommandClass}
|
||||
{
|
||||
CommandClass(cc.id).value: cc.name
|
||||
for cc in sorted(node.command_classes, key=lambda cc: cc.name) # type: ignore[no-any-return]
|
||||
}
|
||||
),
|
||||
vol.Required(ATTR_PROPERTY): cv.string,
|
||||
vol.Optional(ATTR_PROPERTY_KEY): cv.string,
|
||||
|
|
|
@ -57,7 +57,128 @@
|
|||
},
|
||||
{ "nodeId": 13, "index": 2 }
|
||||
],
|
||||
"commandClasses": [],
|
||||
"commandClasses": [
|
||||
{
|
||||
"id": 49,
|
||||
"name": "Multilevel Sensor",
|
||||
"version": 5,
|
||||
"isSecure": false
|
||||
},
|
||||
{
|
||||
"id": 64,
|
||||
"name": "Thermostat Mode",
|
||||
"version": 2,
|
||||
"isSecure": false
|
||||
},
|
||||
{
|
||||
"id": 66,
|
||||
"name": "Thermostat Operating State",
|
||||
"version": 2,
|
||||
"isSecure": false
|
||||
},
|
||||
{
|
||||
"id": 67,
|
||||
"name": "Thermostat Setpoint",
|
||||
"version": 2,
|
||||
"isSecure": false
|
||||
},
|
||||
{
|
||||
"id": 68,
|
||||
"name": "Thermostat Fan Mode",
|
||||
"version": 1,
|
||||
"isSecure": false
|
||||
},
|
||||
{
|
||||
"id": 69,
|
||||
"name": "Thermostat Fan State",
|
||||
"version": 1,
|
||||
"isSecure": false
|
||||
},
|
||||
{
|
||||
"id": 89,
|
||||
"name": "Association Group Information",
|
||||
"version": 1,
|
||||
"isSecure": false
|
||||
},
|
||||
{
|
||||
"id": 90,
|
||||
"name": "Device Reset Locally",
|
||||
"version": 1,
|
||||
"isSecure": false
|
||||
},
|
||||
{
|
||||
"id": 94,
|
||||
"name": "Z-Wave Plus Info",
|
||||
"version": 2,
|
||||
"isSecure": false
|
||||
},
|
||||
{
|
||||
"id": 96,
|
||||
"name": "Multi Channel",
|
||||
"version": 4,
|
||||
"isSecure": false
|
||||
},
|
||||
{
|
||||
"id": 112,
|
||||
"name": "Configuration",
|
||||
"version": 1,
|
||||
"isSecure": false
|
||||
},
|
||||
{
|
||||
"id": 114,
|
||||
"name": "Manufacturer Specific",
|
||||
"version": 2,
|
||||
"isSecure": false
|
||||
},
|
||||
{
|
||||
"id": 115,
|
||||
"name": "Powerlevel",
|
||||
"version": 1,
|
||||
"isSecure": false
|
||||
},
|
||||
{
|
||||
"id": 122,
|
||||
"name": "Firmware Update Meta Data",
|
||||
"version": 3,
|
||||
"isSecure": false
|
||||
},
|
||||
{
|
||||
"id": 128,
|
||||
"name": "Battery",
|
||||
"version": 1,
|
||||
"isSecure": false
|
||||
},
|
||||
{
|
||||
"id": 129,
|
||||
"name": "Clock",
|
||||
"version": 1,
|
||||
"isSecure": false
|
||||
},
|
||||
{
|
||||
"id": 133,
|
||||
"name": "Association",
|
||||
"version": 2,
|
||||
"isSecure": false
|
||||
},
|
||||
{
|
||||
"id": 134,
|
||||
"name": "Version",
|
||||
"version": 2,
|
||||
"isSecure": false
|
||||
},
|
||||
{
|
||||
"id": 135,
|
||||
"name": "Indicator",
|
||||
"version": 1,
|
||||
"isSecure": false
|
||||
},
|
||||
{
|
||||
"id": 142,
|
||||
"name": "Multi Channel Association",
|
||||
"version": 3,
|
||||
"isSecure": false
|
||||
}
|
||||
],
|
||||
"values": [
|
||||
{
|
||||
"commandClassName": "Manufacturer Specific",
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
"""The tests for Z-Wave JS device actions."""
|
||||
from unittest.mock import patch
|
||||
|
||||
import pytest
|
||||
import voluptuous_serialize
|
||||
from zwave_js_server.client import Client
|
||||
|
@ -15,7 +17,7 @@ from homeassistant.exceptions import HomeAssistantError
|
|||
from homeassistant.helpers import config_validation as cv, device_registry
|
||||
from homeassistant.setup import async_setup_component
|
||||
|
||||
from tests.common import async_get_device_automations, async_mock_service
|
||||
from tests.common import async_get_device_automations
|
||||
|
||||
|
||||
async def test_get_actions(
|
||||
|
@ -92,8 +94,130 @@ async def test_get_actions_meter(
|
|||
assert len(filtered_actions) > 0
|
||||
|
||||
|
||||
async def test_action(hass: HomeAssistant) -> None:
|
||||
"""Test for turn_on and turn_off actions."""
|
||||
async def test_actions(
|
||||
hass: HomeAssistant,
|
||||
client: Client,
|
||||
climate_radio_thermostat_ct100_plus: Node,
|
||||
integration: ConfigEntry,
|
||||
) -> None:
|
||||
"""Test actions."""
|
||||
node = climate_radio_thermostat_ct100_plus
|
||||
device_id = get_device_id(client, node)
|
||||
dev_reg = device_registry.async_get(hass)
|
||||
device = dev_reg.async_get_device({device_id})
|
||||
assert device
|
||||
|
||||
assert await async_setup_component(
|
||||
hass,
|
||||
automation.DOMAIN,
|
||||
{
|
||||
automation.DOMAIN: [
|
||||
{
|
||||
"trigger": {
|
||||
"platform": "event",
|
||||
"event_type": "test_event_refresh_value",
|
||||
},
|
||||
"action": {
|
||||
"domain": DOMAIN,
|
||||
"type": "refresh_value",
|
||||
"device_id": device.id,
|
||||
"entity_id": "climate.z_wave_thermostat",
|
||||
},
|
||||
},
|
||||
{
|
||||
"trigger": {
|
||||
"platform": "event",
|
||||
"event_type": "test_event_ping",
|
||||
},
|
||||
"action": {
|
||||
"domain": DOMAIN,
|
||||
"type": "ping",
|
||||
"device_id": device.id,
|
||||
},
|
||||
},
|
||||
{
|
||||
"trigger": {
|
||||
"platform": "event",
|
||||
"event_type": "test_event_set_value",
|
||||
},
|
||||
"action": {
|
||||
"domain": DOMAIN,
|
||||
"type": "set_value",
|
||||
"device_id": device.id,
|
||||
"command_class": 112,
|
||||
"property": 1,
|
||||
"value": 1,
|
||||
},
|
||||
},
|
||||
{
|
||||
"trigger": {
|
||||
"platform": "event",
|
||||
"event_type": "test_event_set_config_parameter",
|
||||
},
|
||||
"action": {
|
||||
"domain": DOMAIN,
|
||||
"type": "set_config_parameter",
|
||||
"device_id": device.id,
|
||||
"parameter": 1,
|
||||
"bitmask": None,
|
||||
"subtype": "2-112-0-3 (Beeper)",
|
||||
"value": 1,
|
||||
},
|
||||
},
|
||||
]
|
||||
},
|
||||
)
|
||||
|
||||
with patch("zwave_js_server.model.node.Node.async_poll_value") as mock_call:
|
||||
hass.bus.async_fire("test_event_refresh_value")
|
||||
await hass.async_block_till_done()
|
||||
mock_call.assert_called_once()
|
||||
args = mock_call.call_args_list[0][0]
|
||||
assert len(args) == 1
|
||||
assert args[0].value_id == "13-64-1-mode"
|
||||
|
||||
with patch("zwave_js_server.model.node.Node.async_ping") as mock_call:
|
||||
hass.bus.async_fire("test_event_ping")
|
||||
await hass.async_block_till_done()
|
||||
mock_call.assert_called_once()
|
||||
args = mock_call.call_args_list[0][0]
|
||||
assert len(args) == 0
|
||||
|
||||
with patch("zwave_js_server.model.node.Node.async_set_value") as mock_call:
|
||||
hass.bus.async_fire("test_event_set_value")
|
||||
await hass.async_block_till_done()
|
||||
mock_call.assert_called_once()
|
||||
args = mock_call.call_args_list[0][0]
|
||||
assert len(args) == 2
|
||||
assert args[0] == "13-112-0-1"
|
||||
assert args[1] == 1
|
||||
|
||||
with patch(
|
||||
"homeassistant.components.zwave_js.services.async_set_config_parameter"
|
||||
) as mock_call:
|
||||
hass.bus.async_fire("test_event_set_config_parameter")
|
||||
await hass.async_block_till_done()
|
||||
mock_call.assert_called_once()
|
||||
args = mock_call.call_args_list[0][0]
|
||||
assert len(args) == 3
|
||||
assert args[0].node_id == 13
|
||||
assert args[1] == 1
|
||||
assert args[2] == 1
|
||||
|
||||
|
||||
async def test_lock_actions(
|
||||
hass: HomeAssistant,
|
||||
client: Client,
|
||||
lock_schlage_be469: Node,
|
||||
integration: ConfigEntry,
|
||||
) -> None:
|
||||
"""Test actions for locks."""
|
||||
node = lock_schlage_be469
|
||||
device_id = get_device_id(client, node)
|
||||
dev_reg = device_registry.async_get(hass)
|
||||
device = dev_reg.async_get_device({device_id})
|
||||
assert device
|
||||
|
||||
assert await async_setup_component(
|
||||
hass,
|
||||
automation.DOMAIN,
|
||||
|
@ -107,7 +231,7 @@ async def test_action(hass: HomeAssistant) -> None:
|
|||
"action": {
|
||||
"domain": DOMAIN,
|
||||
"type": "clear_lock_usercode",
|
||||
"device_id": "fake",
|
||||
"device_id": device.id,
|
||||
"entity_id": "lock.touchscreen_deadbolt",
|
||||
"code_slot": 1,
|
||||
},
|
||||
|
@ -120,97 +244,80 @@ async def test_action(hass: HomeAssistant) -> None:
|
|||
"action": {
|
||||
"domain": DOMAIN,
|
||||
"type": "set_lock_usercode",
|
||||
"device_id": "fake",
|
||||
"device_id": device.id,
|
||||
"entity_id": "lock.touchscreen_deadbolt",
|
||||
"code_slot": 1,
|
||||
"usercode": "1234",
|
||||
},
|
||||
},
|
||||
{
|
||||
"trigger": {
|
||||
"platform": "event",
|
||||
"event_type": "test_event_refresh_value",
|
||||
},
|
||||
"action": {
|
||||
"domain": DOMAIN,
|
||||
"type": "refresh_value",
|
||||
"device_id": "fake",
|
||||
"entity_id": "lock.touchscreen_deadbolt",
|
||||
},
|
||||
},
|
||||
{
|
||||
"trigger": {
|
||||
"platform": "event",
|
||||
"event_type": "test_event_ping",
|
||||
},
|
||||
"action": {
|
||||
"domain": DOMAIN,
|
||||
"type": "ping",
|
||||
"device_id": "fake",
|
||||
},
|
||||
},
|
||||
{
|
||||
"trigger": {
|
||||
"platform": "event",
|
||||
"event_type": "test_event_set_value",
|
||||
},
|
||||
"action": {
|
||||
"domain": DOMAIN,
|
||||
"type": "set_value",
|
||||
"device_id": "fake",
|
||||
"command_class": 112,
|
||||
"property": "test",
|
||||
"value": 1,
|
||||
},
|
||||
},
|
||||
{
|
||||
"trigger": {
|
||||
"platform": "event",
|
||||
"event_type": "test_event_set_config_parameter",
|
||||
},
|
||||
"action": {
|
||||
"domain": DOMAIN,
|
||||
"type": "set_config_parameter",
|
||||
"device_id": "fake",
|
||||
"parameter": 3,
|
||||
"bitmask": None,
|
||||
"subtype": "2-112-0-3 (Beeper)",
|
||||
"value": 255,
|
||||
},
|
||||
},
|
||||
]
|
||||
},
|
||||
)
|
||||
|
||||
clear_lock_usercode = async_mock_service(hass, "zwave_js", "clear_lock_usercode")
|
||||
hass.bus.async_fire("test_event_clear_lock_usercode")
|
||||
await hass.async_block_till_done()
|
||||
assert len(clear_lock_usercode) == 1
|
||||
with patch("homeassistant.components.zwave_js.lock.clear_usercode") as mock_call:
|
||||
hass.bus.async_fire("test_event_clear_lock_usercode")
|
||||
await hass.async_block_till_done()
|
||||
mock_call.assert_called_once()
|
||||
args = mock_call.call_args_list[0][0]
|
||||
assert len(args) == 2
|
||||
assert args[0].node_id == node.node_id
|
||||
assert args[1] == 1
|
||||
|
||||
set_lock_usercode = async_mock_service(hass, "zwave_js", "set_lock_usercode")
|
||||
hass.bus.async_fire("test_event_set_lock_usercode")
|
||||
await hass.async_block_till_done()
|
||||
assert len(set_lock_usercode) == 1
|
||||
with patch("homeassistant.components.zwave_js.lock.set_usercode") as mock_call:
|
||||
hass.bus.async_fire("test_event_set_lock_usercode")
|
||||
await hass.async_block_till_done()
|
||||
mock_call.assert_called_once()
|
||||
args = mock_call.call_args_list[0][0]
|
||||
assert len(args) == 3
|
||||
assert args[0].node_id == node.node_id
|
||||
assert args[1] == 1
|
||||
assert args[2] == "1234"
|
||||
|
||||
refresh_value = async_mock_service(hass, "zwave_js", "refresh_value")
|
||||
hass.bus.async_fire("test_event_refresh_value")
|
||||
await hass.async_block_till_done()
|
||||
assert len(refresh_value) == 1
|
||||
|
||||
ping = async_mock_service(hass, "zwave_js", "ping")
|
||||
hass.bus.async_fire("test_event_ping")
|
||||
await hass.async_block_till_done()
|
||||
assert len(ping) == 1
|
||||
async def test_reset_meter_action(
|
||||
hass: HomeAssistant,
|
||||
client: Client,
|
||||
aeon_smart_switch_6: Node,
|
||||
integration: ConfigEntry,
|
||||
) -> None:
|
||||
"""Test reset_meter action."""
|
||||
node = aeon_smart_switch_6
|
||||
device_id = get_device_id(client, node)
|
||||
dev_reg = device_registry.async_get(hass)
|
||||
device = dev_reg.async_get_device({device_id})
|
||||
assert device
|
||||
|
||||
set_value = async_mock_service(hass, "zwave_js", "set_value")
|
||||
hass.bus.async_fire("test_event_set_value")
|
||||
await hass.async_block_till_done()
|
||||
assert len(set_value) == 1
|
||||
assert await async_setup_component(
|
||||
hass,
|
||||
automation.DOMAIN,
|
||||
{
|
||||
automation.DOMAIN: [
|
||||
{
|
||||
"trigger": {
|
||||
"platform": "event",
|
||||
"event_type": "test_event_reset_meter",
|
||||
},
|
||||
"action": {
|
||||
"domain": DOMAIN,
|
||||
"type": "reset_meter",
|
||||
"device_id": device.id,
|
||||
"entity_id": "sensor.smart_switch_6_electric_consumed_kwh",
|
||||
},
|
||||
},
|
||||
]
|
||||
},
|
||||
)
|
||||
|
||||
set_config_parameter = async_mock_service(hass, "zwave_js", "set_config_parameter")
|
||||
hass.bus.async_fire("test_event_set_config_parameter")
|
||||
await hass.async_block_till_done()
|
||||
assert len(set_config_parameter) == 1
|
||||
with patch(
|
||||
"zwave_js_server.model.endpoint.Endpoint.async_invoke_cc_api"
|
||||
) as mock_call:
|
||||
hass.bus.async_fire("test_event_reset_meter")
|
||||
await hass.async_block_till_done()
|
||||
mock_call.assert_called_once()
|
||||
args = mock_call.call_args_list[0][0]
|
||||
assert len(args) == 2
|
||||
assert args[0] == CommandClass.METER
|
||||
assert args[1] == "reset"
|
||||
|
||||
|
||||
async def test_get_action_capabilities(
|
||||
|
@ -266,7 +373,28 @@ async def test_get_action_capabilities(
|
|||
)
|
||||
assert capabilities and "extra_fields" in capabilities
|
||||
|
||||
cc_options = [(cc.value, cc.name) for cc in CommandClass]
|
||||
cc_options = [
|
||||
(133, "Association"),
|
||||
(89, "Association Group Information"),
|
||||
(128, "Battery"),
|
||||
(129, "Clock"),
|
||||
(112, "Configuration"),
|
||||
(90, "Device Reset Locally"),
|
||||
(122, "Firmware Update Meta Data"),
|
||||
(135, "Indicator"),
|
||||
(114, "Manufacturer Specific"),
|
||||
(96, "Multi Channel"),
|
||||
(142, "Multi Channel Association"),
|
||||
(49, "Multilevel Sensor"),
|
||||
(115, "Powerlevel"),
|
||||
(68, "Thermostat Fan Mode"),
|
||||
(69, "Thermostat Fan State"),
|
||||
(64, "Thermostat Mode"),
|
||||
(66, "Thermostat Operating State"),
|
||||
(67, "Thermostat Setpoint"),
|
||||
(134, "Version"),
|
||||
(94, "Z-Wave Plus Info"),
|
||||
]
|
||||
|
||||
assert voluptuous_serialize.convert(
|
||||
capabilities["extra_fields"], custom_serializer=cv.custom_serializer
|
||||
|
|
Loading…
Reference in New Issue