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 38 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
6 changes: 3 additions & 3 deletions .coveragerc
Original file line number Diff line number Diff line change
Expand Up @@ -639,6 +639,9 @@ omit =
homeassistant/components/ovo_energy/__init__.py
homeassistant/components/ovo_energy/const.py
homeassistant/components/ovo_energy/sensor.py
homeassistant/components/ozw/__init__.py
homeassistant/components/ozw/entity.py
homeassistant/components/ozw/services.py
homeassistant/components/panasonic_bluray/media_player.py
homeassistant/components/panasonic_viera/media_player.py
homeassistant/components/pandora/media_player.py
Expand Down Expand Up @@ -1030,9 +1033,6 @@ omit =
homeassistant/components/ziggo_mediabox_xl/media_player.py
homeassistant/components/supla/*
homeassistant/components/zwave/util.py
homeassistant/components/ozw/__init__.py
homeassistant/components/ozw/entity.py
homeassistant/components/ozw/services.py

[report]
# Regexes for lines to exclude from consideration
Expand Down
2 changes: 1 addition & 1 deletion homeassistant/components/ozw/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
"config_flow": true,
"documentation": "https://www.home-assistant.io/integrations/ozw",
"requirements": [
"python-openzwave-mqtt==1.0.5"
"python-openzwave-mqtt==1.2.0"
],
"after_dependencies": [
"mqtt"
Expand Down
75 changes: 24 additions & 51 deletions homeassistant/components/ozw/services.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
"""Methods and classes related to executing Z-Wave commands and publishing these to hass."""
import logging

from openzwavemqtt.const import CommandClass, ValueType
from openzwavemqtt.const import ATTR_LABEL, ATTR_POSITION, ATTR_VALUE
from openzwavemqtt.util.node import get_node_from_manager, set_config_parameter
import voluptuous as vol

from homeassistant.core import callback
Expand Down Expand Up @@ -53,7 +54,24 @@ 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
vol.All(
cv.ensure_list,
[
vol.All(
{
vol.Exclusive(ATTR_LABEL, "bit"): cv.string,
vol.Exclusive(ATTR_POSITION, "bit"): vol.Coerce(
int
),
vol.Required(ATTR_VALUE): bool,
},
cv.has_at_least_one_key(ATTR_LABEL, ATTR_POSITION),
)
],
),
vol.Coerce(int),
bool,
cv.string,
),
}
),
Expand All @@ -66,57 +84,12 @@ 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)
)

if value.type == ValueType.BOOL:
payload = selection == "True"

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)
# These function calls may raise an exception but that's ok because
# the exception will show in the UI to the user
node = get_node_from_manager(self._manager, instance_id, node_id)
payload = set_config_parameter(node, param, selection)

value.send_value(payload) # send the payload
_LOGGER.info(
"Setting configuration parameter %s on Node %s with value %s",
param,
Expand Down
150 changes: 127 additions & 23 deletions homeassistant/components/ozw/websocket_api.py
Original file line number Diff line number Diff line change
@@ -1,21 +1,31 @@
"""Web socket API for OpenZWave."""

import logging

from openzwavemqtt.const import EVENT_NODE_ADDED, EVENT_NODE_CHANGED
from openzwavemqtt.const import (
ATTR_LABEL,
ATTR_POSITION,
ATTR_VALUE,
EVENT_NODE_ADDED,
EVENT_NODE_CHANGED,
)
from openzwavemqtt.exceptions import NotFoundError, NotSupportedError
from openzwavemqtt.util.node import (
get_config_parameters,
get_node_from_manager,
set_config_parameter,
)
import voluptuous as vol

from homeassistant.components import websocket_api
from homeassistant.core import callback
from homeassistant.helpers import config_validation as cv

from .const import DOMAIN, MANAGER, OPTIONS

_LOGGER = logging.getLogger(__name__)
from .const import ATTR_CONFIG_PARAMETER, ATTR_CONFIG_VALUE, DOMAIN, MANAGER, OPTIONS

TYPE = "type"
ID = "id"
OZW_INSTANCE = "ozw_instance"
NODE_ID = "node_id"
PARAMETER = ATTR_CONFIG_PARAMETER
VALUE = ATTR_CONFIG_VALUE

ATTR_NODE_QUERY_STAGE = "node_query_stage"
ATTR_IS_ZWAVE_PLUS = "is_zwave_plus"
Expand Down Expand Up @@ -45,6 +55,8 @@ def async_register_api(hass):
websocket_api.async_register_command(hass, websocket_node_status)
websocket_api.async_register_command(hass, websocket_node_statistics)
websocket_api.async_register_command(hass, websocket_refresh_node_info)
websocket_api.async_register_command(hass, websocket_get_config_parameters)
websocket_api.async_register_command(hass, websocket_set_config_parameter)


@websocket_api.websocket_command({vol.Required(TYPE): "ozw/get_instances"})
Expand Down Expand Up @@ -102,6 +114,96 @@ def websocket_get_nodes(hass, connection, msg):
)


@websocket_api.websocket_command(
{
vol.Required(TYPE): "ozw/get_config_parameters",
vol.Required(NODE_ID): vol.Coerce(int),
vol.Optional(OZW_INSTANCE, default=1): vol.Coerce(int),
}
)
def websocket_get_config_parameters(hass, connection, msg):
"""Get a list of configuration parameters for an OZW node instance."""
try:
node = get_node_from_manager(
hass.data[DOMAIN][MANAGER], msg[OZW_INSTANCE], msg[NODE_ID]
)
except NotFoundError as err:
connection.send_error(
msg[ID],
websocket_api.const.ERR_NOT_FOUND,
err.args[0],
)
return

connection.send_result(
msg[ID],
get_config_parameters(node),
)


@websocket_api.websocket_command(
{
vol.Required(TYPE): "ozw/set_config_parameter",
vol.Required(NODE_ID): vol.Coerce(int),
vol.Optional(OZW_INSTANCE, default=1): vol.Coerce(int),
vol.Required(PARAMETER): vol.Coerce(int),
vol.Required(VALUE): vol.Any(
vol.All(
cv.ensure_list,
[
vol.All(
{
vol.Exclusive(ATTR_LABEL, "bit"): cv.string,
vol.Exclusive(ATTR_POSITION, "bit"): vol.Coerce(int),
vol.Required(ATTR_VALUE): bool,
},
cv.has_at_least_one_key(ATTR_LABEL, ATTR_POSITION),
)
],
),
vol.Coerce(int),
bool,
cv.string,
),
}
)
def websocket_set_config_parameter(hass, connection, msg):
"""Set a config parameter to a node."""
try:
node = get_node_from_manager(
hass.data[DOMAIN][MANAGER], msg[OZW_INSTANCE], msg[NODE_ID]
)
except NotFoundError as err:
connection.send_error(
msg[ID],
websocket_api.const.ERR_NOT_FOUND,
err.args[0],
)
return

try:
set_config_parameter(
node,
msg[PARAMETER],
msg[VALUE],
)
connection.send_result(msg[ID])
raman325 marked this conversation as resolved.
Show resolved Hide resolved
except NotFoundError as err:
connection.send_error(
msg[ID],
websocket_api.const.ERR_NOT_FOUND,
err.args[0],
)
return
except NotSupportedError as err:
connection.send_error(
msg[ID],
websocket_api.const.ERR_NOT_SUPPORTED,
err.args[0],
)
return


@websocket_api.websocket_command(
{
vol.Required(TYPE): "ozw/network_status",
Expand Down Expand Up @@ -148,14 +250,15 @@ def websocket_network_statistics(hass, connection, msg):
)
def websocket_node_status(hass, connection, msg):
"""Get the status for a Z-Wave node."""
manager = hass.data[DOMAIN][MANAGER]
node = manager.get_instance(msg[OZW_INSTANCE]).get_node(msg[NODE_ID])

if not node:
connection.send_message(
websocket_api.error_message(
msg[ID], websocket_api.const.ERR_NOT_FOUND, "OZW Node not found"
)
try:
node = get_node_from_manager(
hass.data[DOMAIN][MANAGER], msg[OZW_INSTANCE], msg[NODE_ID]
)
except NotFoundError as err:
connection.send_error(
msg[ID],
websocket_api.const.ERR_NOT_FOUND,
err.args[0],
)
return

Expand Down Expand Up @@ -192,14 +295,15 @@ def websocket_node_status(hass, connection, msg):
)
def websocket_node_metadata(hass, connection, msg):
"""Get the metadata for a Z-Wave node."""
manager = hass.data[DOMAIN][MANAGER]
node = manager.get_instance(msg[OZW_INSTANCE]).get_node(msg[NODE_ID])

if not node:
connection.send_message(
websocket_api.error_message(
msg[ID], websocket_api.const.ERR_NOT_FOUND, "OZW Node not found"
)
try:
node = get_node_from_manager(
hass.data[DOMAIN][MANAGER], msg[OZW_INSTANCE], msg[NODE_ID]
)
except NotFoundError as err:
connection.send_error(
msg[ID],
websocket_api.const.ERR_NOT_FOUND,
err.args[0],
)
return

Expand Down
2 changes: 1 addition & 1 deletion requirements_all.txt
Original file line number Diff line number Diff line change
Expand Up @@ -1746,7 +1746,7 @@ python-nest==4.1.0
python-nmap==0.6.1

# homeassistant.components.ozw
python-openzwave-mqtt==1.0.5
python-openzwave-mqtt==1.2.0

# homeassistant.components.qbittorrent
python-qbittorrent==0.4.1
Expand Down
2 changes: 1 addition & 1 deletion requirements_test_all.txt
Original file line number Diff line number Diff line change
Expand Up @@ -827,7 +827,7 @@ python-miio==0.5.3
python-nest==4.1.0

# homeassistant.components.ozw
python-openzwave-mqtt==1.0.5
python-openzwave-mqtt==1.2.0

# homeassistant.components.songpal
python-songpal==0.12
Expand Down