From aa8f3ad307e58c7639ec50e0e71146588ab30297 Mon Sep 17 00:00:00 2001 From: Chris Date: Wed, 8 Jul 2020 11:59:52 -0700 Subject: [PATCH] Add OZW support for set_config_parameter service (#37523) * Add support for set_config_parameter service * Adjusted elif to if * More if/else cleanup * More if/else cleanup * Less nesting * End loop properly * Added byte type * Convert break to return --- homeassistant/components/ozw/const.py | 3 + homeassistant/components/ozw/services.py | 98 ++++++++++++++++++++++ homeassistant/components/ozw/services.yaml | 15 ++++ tests/components/ozw/test_services.py | 73 ++++++++++++++++ 4 files changed, 189 insertions(+) create mode 100644 tests/components/ozw/test_services.py diff --git a/homeassistant/components/ozw/const.py b/homeassistant/components/ozw/const.py index 8b18ce707c5..93aa8da4b79 100644 --- a/homeassistant/components/ozw/const.py +++ b/homeassistant/components/ozw/const.py @@ -25,6 +25,8 @@ PLATFORMS = [ TOPIC_OPENZWAVE = "OpenZWave" # Common Attributes +ATTR_CONFIG_PARAMETER = "parameter" +ATTR_CONFIG_VALUE = "value" ATTR_INSTANCE_ID = "instance_id" ATTR_SECURE = "secure" ATTR_NODE_ID = "node_id" @@ -36,6 +38,7 @@ ATTR_SCENE_VALUE_LABEL = "scene_value_label" # Service specific SERVICE_ADD_NODE = "add_node" SERVICE_REMOVE_NODE = "remove_node" +SERVICE_SET_CONFIG_PARAMETER = "set_config_parameter" # Home Assistant Events EVENT_SCENE_ACTIVATED = f"{DOMAIN}.scene_activated" diff --git a/homeassistant/components/ozw/services.py b/homeassistant/components/ozw/services.py index a2f4ca2e553..f950e68d2b4 100644 --- a/homeassistant/components/ozw/services.py +++ b/homeassistant/components/ozw/services.py @@ -1,10 +1,16 @@ """Methods and classes related to executing Z-Wave commands and publishing these to hass.""" +import logging + +from openzwavemqtt.const import CommandClass, ValueType import voluptuous as vol from homeassistant.core import callback +import homeassistant.helpers.config_validation as cv from . import const +_LOGGER = logging.getLogger(__name__) + class ZWaveServices: """Class that holds our services ( Zwave Commands) that should be published to hass.""" @@ -37,6 +43,98 @@ class ZWaveServices: ), ) + self._hass.services.async_register( + const.DOMAIN, + const.SERVICE_SET_CONFIG_PARAMETER, + self.async_set_config_parameter, + schema=vol.Schema( + { + vol.Optional(const.ATTR_INSTANCE_ID, default=1): vol.Coerce(int), + vol.Required(const.ATTR_NODE_ID): vol.Coerce(int), + vol.Required(const.ATTR_CONFIG_PARAMETER): vol.Coerce(int), + vol.Required(const.ATTR_CONFIG_VALUE): vol.Any( + vol.Coerce(int), cv.string + ), + } + ), + ) + + @callback + def async_set_config_parameter(self, service): + """Set a config parameter to a node.""" + instance_id = service.data[const.ATTR_INSTANCE_ID] + node_id = service.data[const.ATTR_NODE_ID] + param = service.data[const.ATTR_CONFIG_PARAMETER] + selection = service.data[const.ATTR_CONFIG_VALUE] + payload = None + + node = self._manager.get_instance(instance_id).get_node(node_id).values() + + for value in node: + if ( + value.command_class != CommandClass.CONFIGURATION + or value.index != param + ): + continue + + if value.type == ValueType.BOOL: + payload = selection == "True" + + if value.type == ValueType.LIST: + # accept either string from the list value OR the int value + if isinstance(selection, int): + if selection > value.max or selection < value.min: + _LOGGER.error( + "Value %s out of range for parameter %s (Min: %s Max: %s)", + selection, + param, + value.min, + value.max, + ) + return + payload = int(selection) + + # iterate list labels to get value + for selected in value.value["List"]: + if selected["Label"] != selection: + continue + payload = int(selected["Value"]) + + if payload is None: + _LOGGER.error( + "Invalid value %s for parameter %s", selection, param, + ) + return + + if value.type == ValueType.BUTTON: + # Unsupported at this time + _LOGGER.info("Button type not supported yet") + return + + if value.type == ValueType.STRING: + payload = selection + + if value.type == ValueType.INT or value.type == ValueType.BYTE: + if selection > value.max or selection < value.min: + _LOGGER.error( + "Value %s out of range for parameter %s (Min: %s Max: %s)", + selection, + param, + value.min, + value.max, + ) + return + payload = int(selection) + + value.send_value(payload) # send the payload + _LOGGER.info( + "Setting configuration parameter %s on Node %s with value %s", + param, + node_id, + payload, + ) + return + @callback def async_add_node(self, service): """Enter inclusion mode on the controller.""" diff --git a/homeassistant/components/ozw/services.yaml b/homeassistant/components/ozw/services.yaml index 92685f1a463..e664a04da6d 100644 --- a/homeassistant/components/ozw/services.yaml +++ b/homeassistant/components/ozw/services.yaml @@ -12,3 +12,18 @@ remove_node: fields: instance_id: description: (Optional) The OZW Instance/Controller to use, defaults to 1. + +set_config_parameter: + description: Set a config parameter to a node on the Z-Wave network. + fields: + node_id: + description: Node id of the device to set config parameter to (integer). + example: 10 + parameter: + description: Parameter number to set (integer). + example: 8 + value: + description: Value to set for parameter. (String value for list and bool parameters, integer for others). + example: 50268673 + instance_id: + description: (Optional) The OZW Instance/Controller to use, defaults to 1. diff --git a/tests/components/ozw/test_services.py b/tests/components/ozw/test_services.py new file mode 100644 index 00000000000..9ea4e4f496b --- /dev/null +++ b/tests/components/ozw/test_services.py @@ -0,0 +1,73 @@ +"""Test Z-Wave Services.""" +from .common import setup_ozw + + +async def test_services(hass, lock_data, sent_messages, lock_msg, caplog): + """Test services on lock.""" + await setup_ozw(hass, fixture=lock_data) + + # Test set_config_parameter list by label + await hass.services.async_call( + "ozw", + "set_config_parameter", + {"node_id": 10, "parameter": 1, "value": "Disabled"}, + blocking=True, + ) + assert len(sent_messages) == 1 + msg = sent_messages[0] + assert msg["topic"] == "OpenZWave/1/command/setvalue/" + assert msg["payload"] == {"Value": 0, "ValueIDKey": 281475154706452} + + # Test set_config_parameter list by index int + await hass.services.async_call( + "ozw", + "set_config_parameter", + {"node_id": 10, "parameter": 1, "value": 0}, + blocking=True, + ) + assert len(sent_messages) == 2 + msg = sent_messages[1] + assert msg["topic"] == "OpenZWave/1/command/setvalue/" + assert msg["payload"] == {"Value": 0, "ValueIDKey": 281475154706452} + + # Test set_config_parameter int + await hass.services.async_call( + "ozw", + "set_config_parameter", + {"node_id": 10, "parameter": 6, "value": 0}, + blocking=True, + ) + assert len(sent_messages) == 3 + msg = sent_messages[2] + assert msg["topic"] == "OpenZWave/1/command/setvalue/" + assert msg["payload"] == {"Value": 0, "ValueIDKey": 1688850038259731} + + # Test set_config_parameter invalid list int + await hass.services.async_call( + "ozw", + "set_config_parameter", + {"node_id": 10, "parameter": 1, "value": 12}, + blocking=True, + ) + assert len(sent_messages) == 3 + assert "Value 12 out of range for parameter 1" in caplog.text + + # Test set_config_parameter invalid list string + await hass.services.async_call( + "ozw", + "set_config_parameter", + {"node_id": 10, "parameter": 1, "value": "Blah"}, + blocking=True, + ) + assert len(sent_messages) == 3 + assert "Invalid value Blah for parameter 1" in caplog.text + + # Test set_config_parameter int out of range + await hass.services.async_call( + "ozw", + "set_config_parameter", + {"node_id": 10, "parameter": 6, "value": 2147483657}, + blocking=True, + ) + assert len(sent_messages) == 3 + assert "Value 12 out of range for parameter 1" in caplog.text