Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add OZW node config parameters websocket commands #40527

Merged
merged 39 commits into from
Oct 5, 2020
Merged
Show file tree
Hide file tree
Changes from 26 commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
d6434d1
add websocket commands to retrieve and set config parameters for a node
raman325 Sep 24, 2020
7f01e98
move set_config_parameter into generic function and refactor service …
raman325 Sep 24, 2020
fc00b50
add payload to return to make service call behave the same way it did…
raman325 Sep 24, 2020
dbed308
create response class
raman325 Sep 24, 2020
859ad4e
update error message to pass tests
raman325 Sep 24, 2020
1aa7828
move things a bit to reduce LOC
raman325 Sep 24, 2020
c8e4d31
add tests
raman325 Sep 24, 2020
fceba61
handle logging errors better and make new response class more generic…
raman325 Sep 24, 2020
76a96b8
remove unused function parameter
raman325 Sep 24, 2020
43412c2
invert check
raman325 Sep 24, 2020
1b538cd
add additional error checking
raman325 Sep 24, 2020
746d6d4
refactor a bit to remove repeat code
raman325 Sep 24, 2020
9d9ae2f
revert log msg change
raman325 Sep 24, 2020
58b02ae
one more refactor to create generic get_config_parameters function
raman325 Sep 24, 2020
4035bf8
change if logic for consistency
raman325 Sep 24, 2020
0d2a225
fix test
raman325 Sep 24, 2020
97ab42d
add support to provide bool value in set_config_parameter service call
raman325 Sep 24, 2020
9638b08
standardize parameter names on service call
raman325 Sep 24, 2020
388736e
add test coverage
raman325 Sep 24, 2020
d16ab68
fix tests and message sending
raman325 Sep 24, 2020
4f96442
remove unnecessary logging import
raman325 Sep 24, 2020
a3937fc
fix one test to get missing coverage
raman325 Sep 24, 2020
0c2e216
update per martin and kpines reviews
raman325 Sep 25, 2020
b9ab92d
remove false assertion
raman325 Sep 25, 2020
24934a7
string line length
raman325 Sep 25, 2020
e811927
add support for Decimal config param, remove node instance ID as inpu…
raman325 Sep 25, 2020
36a7a43
cast Decimal appropriately
raman325 Sep 25, 2020
cd13694
revert change to support Decimal for config params since they are not…
raman325 Sep 25, 2020
2fbdfbe
revert to using error arguments to make next PR for WS lock commands …
raman325 Sep 27, 2020
3fdbc31
switch to class method and add guard for list Value not being a number
raman325 Sep 30, 2020
a2b87c7
update logic to use new openzwavemqtt util methods
raman325 Oct 1, 2020
43e444b
add support for bitsets
raman325 Oct 1, 2020
559d044
use parent exception class
raman325 Oct 1, 2020
48aba1a
bump openzwavemqtt version, remove node.py from .coveragerc and put f…
raman325 Oct 2, 2020
8ea19d7
add comment
raman325 Oct 2, 2020
7ee8372
improve config validation
raman325 Oct 2, 2020
2f5bf5f
remove bitset support from config validation
raman325 Oct 2, 2020
a89e6a4
re-add bitset support with some additional tests
raman325 Oct 3, 2020
3278138
move send_result out of try block
raman325 Oct 5, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions .coveragerc
Original file line number Diff line number Diff line change
Expand Up @@ -1032,6 +1032,7 @@ omit =
homeassistant/components/zwave/util.py
homeassistant/components/ozw/__init__.py
homeassistant/components/ozw/entity.py
homeassistant/components/ozw/node.py
homeassistant/components/ozw/services.py

[report]
Expand Down
8 changes: 8 additions & 0 deletions homeassistant/components/ozw/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,14 @@
ATTR_SCENE_VALUE_ID = "scene_value_id"
ATTR_SCENE_VALUE_LABEL = "scene_value_label"

# Config parameter specific
ATTR_LABEL = "label"
ATTR_MAX = "max"
ATTR_MIN = "min"
ATTR_OPTIONS = "options"
ATTR_TYPE = "type"
ATTR_VALUE = "value"

# Service specific
SERVICE_ADD_NODE = "add_node"
SERVICE_REMOVE_NODE = "remove_node"
Expand Down
190 changes: 190 additions & 0 deletions homeassistant/components/ozw/node.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,190 @@
"""Node methods and classes for OpenZWave."""
from openzwavemqtt.const import CommandClass, ValueType

from homeassistant.components.websocket_api.const import (
ERR_NOT_FOUND,
ERR_NOT_SUPPORTED,
)

from .const import (
ATTR_CONFIG_PARAMETER,
ATTR_LABEL,
ATTR_MAX,
ATTR_MIN,
ATTR_OPTIONS,
ATTR_TYPE,
ATTR_VALUE,
)


class OZWValidationResponse:
"""Class to hold response for validating an action."""

def __init__(self, success, payload=None, err_type=None, err_msg=None):
"""Initialize OZWValidationResponse."""
self.success = success
self.payload = payload
self.err_type = err_type
self.err_msg = err_msg

@staticmethod
raman325 marked this conversation as resolved.
Show resolved Hide resolved
def process_fail(err_type, err_msg):
"""Process an invalid request."""
return OZWValidationResponse(False, err_type=err_type, err_msg=err_msg)

@staticmethod
def process_fail_on_type(value, new_value):
"""Process an invalid request that fails type validation."""
return OZWValidationResponse.process_fail(
ERR_NOT_SUPPORTED,
(
f"Configuration parameter type {value.type} does "
f"not match the value type {type(new_value)}"
),
)

@staticmethod
def process_success(payload):
"""Process a valid request."""
return OZWValidationResponse(True, payload=payload)


def set_config_parameter(manager, instance_id, node_id, parameter_index, new_value):
raman325 marked this conversation as resolved.
Show resolved Hide resolved
"""Set config parameter to a node."""
instance = manager.get_instance(instance_id)
if not instance:
return OZWValidationResponse.process_fail(
ERR_NOT_FOUND, "OZW Instance not found"
)

node = instance.get_node(node_id)
if not node:
return OZWValidationResponse.process_fail(ERR_NOT_FOUND, "OZW Node not found")

value = node.get_value(CommandClass.CONFIGURATION, parameter_index)
if not value:
return OZWValidationResponse.process_fail(
ERR_NOT_FOUND,
"Configuration parameter for OZW Node Instance not found",
)

# Bool can be passed in as string or bool
if value.type == ValueType.BOOL:
if isinstance(new_value, bool):
value.send_value(new_value)
return OZWValidationResponse.process_success(new_value)
if isinstance(new_value, str):
if new_value.lower() in ("true", "false"):
payload = new_value.lower() == "true"
value.send_value(payload)
return OZWValidationResponse.process_success(payload)

return OZWValidationResponse.process_fail(
ERR_NOT_SUPPORTED,
"Configuration parameter requires true of false",
)

return OZWValidationResponse.process_fail_on_type(value, new_value)

# List value can be passed in as string or int
if value.type == ValueType.LIST:
try:
new_value = int(new_value)
except ValueError:
pass
if not isinstance(new_value, str) and not isinstance(new_value, int):
return OZWValidationResponse.process_fail_on_type(value, new_value)

for option in value.value["List"]:
if new_value not in (option["Label"], option["Value"]):
continue
payload = int(option["Value"])
raman325 marked this conversation as resolved.
Show resolved Hide resolved
value.send_value(payload)
return OZWValidationResponse.process_success(payload)

return OZWValidationResponse.process_fail(
ERR_NOT_SUPPORTED,
f"Invalid value {new_value} for parameter {parameter_index}",
)

# Int, Byte, Short are always passed as int, Decimal should be float
if value.type in (
ValueType.INT,
ValueType.BYTE,
ValueType.SHORT,
ValueType.DECIMAL,
):
try:
if value.type == ValueType.DECIMAL:
new_value = float(new_value)
else:
new_value = int(new_value)
except ValueError:
return OZWValidationResponse.process_fail_on_type(value, new_value)
if (value.max and new_value > value.max) or (
value.min and new_value < value.min
):
return OZWValidationResponse.process_fail(
ERR_NOT_SUPPORTED,
(
f"Value {new_value} out of range for parameter "
f"{parameter_index} (Min: {value.min} Max: {value.max})"
),
)
value.send_value(new_value)
return OZWValidationResponse.process_success(new_value)

# This will catch BUTTON, STRING, and UNKNOWN ValueTypes
return OZWValidationResponse.process_fail(
ERR_NOT_SUPPORTED,
f"Value type of {value.type} for parameter {parameter_index} not supported",
)


def get_config_parameters(node):
"""Get config parameter from a node."""
command_class = node.get_command_class(CommandClass.CONFIGURATION)
if not command_class:
return OZWValidationResponse.process_fail(
ERR_NOT_FOUND, "Configuration parameters for OZW Node Instance not found"
)

values = []

for value in command_class.values():
value_to_return = {}
# BUTTON types aren't supported yet, and STRING and UNKNOWN
# are not valid config parameter types
if value.read_only or value.type in (
ValueType.BUTTON,
ValueType.STRING,
ValueType.UNKNOWN,
):
continue

value_to_return = {
ATTR_LABEL: value.label,
ATTR_TYPE: value.type.value,
ATTR_CONFIG_PARAMETER: value.index.value,
}

if value.type == ValueType.BOOL:
value_to_return[ATTR_VALUE] = value.value

elif value.type == ValueType.LIST:
value_to_return[ATTR_VALUE] = value.value["Selected"]
value_to_return[ATTR_OPTIONS] = value.value["List"]

elif value.type in (
ValueType.INT,
ValueType.BYTE,
ValueType.SHORT,
ValueType.DECIMAL,
raman325 marked this conversation as resolved.
Show resolved Hide resolved
):
value_to_return[ATTR_VALUE] = int(value.value)
value_to_return[ATTR_MAX] = value.max
value_to_return[ATTR_MIN] = value.min

values.append(value_to_return)

return OZWValidationResponse.process_success(values)
57 changes: 7 additions & 50 deletions homeassistant/components/ozw/services.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
"""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
from .node import set_config_parameter

_LOGGER = logging.getLogger(__name__)

Expand Down Expand Up @@ -53,7 +53,7 @@ def async_register(self):
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
bool, vol.Coerce(float), cv.string
),
}
),
Expand All @@ -66,62 +66,19 @@ def async_set_config_parameter(self, service):
node_id = service.data[const.ATTR_NODE_ID]
param = service.data[const.ATTR_CONFIG_PARAMETER]
selection = service.data[const.ATTR_CONFIG_VALUE]
payload = None

value = (
self._manager.get_instance(instance_id)
.get_node(node_id)
.get_value(CommandClass.CONFIGURATION, param)
resp = set_config_parameter(
self._manager, instance_id, node_id, param, selection
)

if value.type == ValueType.BOOL:
payload = selection == "True"
if not resp.success:
raise ValueError(resp.err_msg)

if value.type == ValueType.LIST:
# accept either string from the list value OR the int value
for selected in value.value["List"]:
if selection not in (selected["Label"], selected["Value"]):
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
or value.type == ValueType.SHORT
):
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,
resp.payload,
)

@callback
Expand Down