From 1eaec8f4063b569678d4644886214ede86686099 Mon Sep 17 00:00:00 2001 From: John Arild Berentsen Date: Wed, 10 May 2017 03:56:41 +0200 Subject: [PATCH] Zwave panel api (#7456) * # This is a combination of 3 commits. # The first commit's message is: Add seperate zwave panel # The 2nd commit message will be skipped: # unused import # The 3rd commit message will be skipped: # Use get for config * Add seperate zwave panel * more info * Add usercodeview * Improve api * Improve api * Separate api into own file. * disable missing import * review changes * Tests 1 * Verify that we fetch data from groups * Tests groups * config 1 * usercode 1 * Api mods * Tweak API * docstrings * 100% api testing --- homeassistant/components/zwave/__init__.py | 12 +- homeassistant/components/zwave/api.py | 95 ++++++++ tests/components/zwave/test_api.py | 260 +++++++++++++++++++++ 3 files changed, 365 insertions(+), 2 deletions(-) create mode 100644 homeassistant/components/zwave/api.py create mode 100644 tests/components/zwave/test_api.py diff --git a/homeassistant/components/zwave/__init__.py b/homeassistant/components/zwave/__init__.py index 4033e195be0..c49983b3178 100755 --- a/homeassistant/components/zwave/__init__.py +++ b/homeassistant/components/zwave/__init__.py @@ -28,6 +28,7 @@ from homeassistant.helpers.dispatcher import ( async_dispatcher_connect, async_dispatcher_send) from homeassistant.components.frontend import register_built_in_panel +from . import api from . import const from .const import DOMAIN from .node_entity import ZWaveBaseEntity, ZWaveNodeEntity @@ -66,6 +67,8 @@ DEFAULT_CONF_REFRESH_VALUE = False DEFAULT_CONF_REFRESH_DELAY = 5 DATA_ZWAVE_DICT = 'zwave_devices' +OZW_LOG_FILENAME = 'OZW_Log.txt' +URL_API_OZW_LOG = '/api/zwave/ozwlog' ZWAVE_NETWORK = 'zwave_network' RENAME_NODE_SCHEMA = vol.Schema({ @@ -383,7 +386,7 @@ def setup(hass, config): def rename_node(service): """Rename a node.""" node_id = service.data.get(const.ATTR_NODE_ID) - node = hass.data[ZWAVE_NETWORK].nodes[node_id] + node = network.nodes[node_id] name = service.data.get(const.ATTR_NAME) node.name = name _LOGGER.info( @@ -501,7 +504,7 @@ def setup(hass, config): # to be ready. for i in range(const.NETWORK_READY_WAIT_SECS): _LOGGER.debug( - "network state: %d %s", hass.data[ZWAVE_NETWORK].state, + "network state: %d %s", network.state, network.state_str) if network.state >= network.STATE_AWAKED: _LOGGER.info("Z-Wave ready after %d seconds", i) @@ -607,6 +610,11 @@ def setup(hass, config): if 'frontend' in hass.config.components: register_built_in_panel(hass, 'zwave', 'Z-Wave', 'mdi:nfc') + hass.http.register_view(api.ZWaveNodeGroupView) + hass.http.register_view(api.ZWaveNodeConfigView) + hass.http.register_view(api.ZWaveUserCodeView) + hass.http.register_static_path( + URL_API_OZW_LOG, hass.config.path(OZW_LOG_FILENAME), False) return True diff --git a/homeassistant/components/zwave/api.py b/homeassistant/components/zwave/api.py new file mode 100644 index 00000000000..9e3066f91c5 --- /dev/null +++ b/homeassistant/components/zwave/api.py @@ -0,0 +1,95 @@ +"""API class to give info to the Z-Wave panel.""" + +import logging +import homeassistant.core as ha +from homeassistant.components.http import HomeAssistantView +from homeassistant.const import HTTP_NOT_FOUND +from . import const + +_LOGGER = logging.getLogger(__name__) + +ZWAVE_NETWORK = 'zwave_network' + + +class ZWaveNodeGroupView(HomeAssistantView): + """View to return the nodes group configuration.""" + + url = r"/api/zwave/groups/{node_id:\d+}" + name = "api:zwave:groups" + + @ha.callback + def get(self, request, node_id): + """Retrieve groups of node.""" + nodeid = int(node_id) + hass = request.app['hass'] + network = hass.data.get(ZWAVE_NETWORK) + node = network.nodes.get(nodeid) + if node is None: + return self.json_message('Node not found', HTTP_NOT_FOUND) + groupdata = node.groups + groups = {} + for key, value in groupdata.items(): + groups[key] = {'associations': value.associations, + 'association_instances': + value.associations_instances, + 'label': value.label, + 'max_associations': value.max_associations} + return self.json(groups) + + +class ZWaveNodeConfigView(HomeAssistantView): + """View to return the nodes configuration options.""" + + url = r"/api/zwave/config/{node_id:\d+}" + name = "api:zwave:config" + + @ha.callback + def get(self, request, node_id): + """Retrieve configurations of node.""" + nodeid = int(node_id) + hass = request.app['hass'] + network = hass.data.get(ZWAVE_NETWORK) + node = network.nodes.get(nodeid) + if node is None: + return self.json_message('Node not found', HTTP_NOT_FOUND) + config = {} + for value in ( + node.get_values(class_id=const.COMMAND_CLASS_CONFIGURATION) + .values()): + config[value.index] = {'label': value.label, + 'type': value.type, + 'help': value.help, + 'data_items': value.data_items, + 'data': value.data, + 'max': value.max, + 'min': value.min} + return self.json(config) + + +class ZWaveUserCodeView(HomeAssistantView): + """View to return the nodes usercode configuration.""" + + url = r"/api/zwave/usercodes/{node_id:\d+}" + name = "api:zwave:usercodes" + + @ha.callback + def get(self, request, node_id): + """Retrieve usercodes of node.""" + nodeid = int(node_id) + hass = request.app['hass'] + network = hass.data.get(ZWAVE_NETWORK) + node = network.nodes.get(nodeid) + if node is None: + return self.json_message('Node not found', HTTP_NOT_FOUND) + usercodes = {} + if not node.has_command_class(const.COMMAND_CLASS_USER_CODE): + return self.json(usercodes) + for value in ( + node.get_values(class_id=const.COMMAND_CLASS_USER_CODE) + .values()): + if value.genre != const.GENRE_USER: + continue + usercodes[value.index] = {'code': value.data, + 'label': value.label, + 'length': len(value.data)} + return self.json(usercodes) diff --git a/tests/components/zwave/test_api.py b/tests/components/zwave/test_api.py new file mode 100644 index 00000000000..aabfd39024c --- /dev/null +++ b/tests/components/zwave/test_api.py @@ -0,0 +1,260 @@ +"""Test Z-Wave config panel.""" +import asyncio +from unittest.mock import MagicMock +from homeassistant.components.zwave import ZWAVE_NETWORK, const +from homeassistant.components.zwave.api import ( + ZWaveNodeGroupView, ZWaveNodeConfigView, ZWaveUserCodeView) +from tests.common import mock_http_component_app +from tests.mock.zwave import MockNode, MockValue + + +@asyncio.coroutine +def test_get_groups(hass, test_client): + """Test getting groupdata on node.""" + app = mock_http_component_app(hass) + ZWaveNodeGroupView().register(app.router) + + network = hass.data[ZWAVE_NETWORK] = MagicMock() + node = MockNode(node_id=2) + node.groups.associations = 'assoc' + node.groups.associations_instances = 'inst' + node.groups.label = 'the label' + node.groups.max_associations = 'max' + node.groups = {1: node.groups} + network.nodes = {2: node} + + client = yield from test_client(app) + + resp = yield from client.get('/api/zwave/groups/2') + + assert resp.status == 200 + result = yield from resp.json() + + assert result == { + '1': { + 'association_instances': 'inst', + 'associations': 'assoc', + 'label': 'the label', + 'max_associations': 'max' + } + } + + +@asyncio.coroutine +def test_get_groups_nogroups(hass, test_client): + """Test getting groupdata on node with no groups.""" + app = mock_http_component_app(hass) + ZWaveNodeGroupView().register(app.router) + + network = hass.data[ZWAVE_NETWORK] = MagicMock() + node = MockNode(node_id=2) + + network.nodes = {2: node} + + client = yield from test_client(app) + + resp = yield from client.get('/api/zwave/groups/2') + + assert resp.status == 200 + result = yield from resp.json() + + assert result == {} + + +@asyncio.coroutine +def test_get_groups_nonode(hass, test_client): + """Test getting groupdata on nonexisting node.""" + app = mock_http_component_app(hass) + ZWaveNodeGroupView().register(app.router) + + network = hass.data[ZWAVE_NETWORK] = MagicMock() + network.nodes = {1: 1, 5: 5} + + client = yield from test_client(app) + + resp = yield from client.get('/api/zwave/groups/2') + + assert resp.status == 404 + result = yield from resp.json() + + assert result == {'message': 'Node not found'} + + +@asyncio.coroutine +def test_get_config(hass, test_client): + """Test getting config on node.""" + app = mock_http_component_app(hass) + ZWaveNodeConfigView().register(app.router) + + network = hass.data[ZWAVE_NETWORK] = MagicMock() + node = MockNode(node_id=2) + value = MockValue( + index=12, + command_class=const.COMMAND_CLASS_CONFIGURATION) + value.label = 'label' + value.help = 'help' + value.type = 'type' + value.data = 'data' + value.data_items = ['item1', 'item2'] + value.max = 'max' + value.min = 'min' + node.values = {12: value} + network.nodes = {2: node} + node.get_values.return_value = node.values + + client = yield from test_client(app) + + resp = yield from client.get('/api/zwave/config/2') + + assert resp.status == 200 + result = yield from resp.json() + + assert result == {'12': {'data': 'data', + 'data_items': ['item1', 'item2'], + 'help': 'help', + 'label': 'label', + 'max': 'max', + 'min': 'min', + 'type': 'type'}} + + +@asyncio.coroutine +def test_get_config_noconfig_node(hass, test_client): + """Test getting config on node without config.""" + app = mock_http_component_app(hass) + ZWaveNodeConfigView().register(app.router) + + network = hass.data[ZWAVE_NETWORK] = MagicMock() + node = MockNode(node_id=2) + + network.nodes = {2: node} + node.get_values.return_value = node.values + + client = yield from test_client(app) + + resp = yield from client.get('/api/zwave/config/2') + + assert resp.status == 200 + result = yield from resp.json() + + assert result == {} + + +@asyncio.coroutine +def test_get_config_nonode(hass, test_client): + """Test getting config on nonexisting node.""" + app = mock_http_component_app(hass) + ZWaveNodeConfigView().register(app.router) + + network = hass.data[ZWAVE_NETWORK] = MagicMock() + network.nodes = {1: 1, 5: 5} + + client = yield from test_client(app) + + resp = yield from client.get('/api/zwave/config/2') + + assert resp.status == 404 + result = yield from resp.json() + + assert result == {'message': 'Node not found'} + + +@asyncio.coroutine +def test_get_usercodes_nonode(hass, test_client): + """Test getting usercodes on nonexisting node.""" + app = mock_http_component_app(hass) + ZWaveUserCodeView().register(app.router) + + network = hass.data[ZWAVE_NETWORK] = MagicMock() + network.nodes = {1: 1, 5: 5} + + client = yield from test_client(app) + + resp = yield from client.get('/api/zwave/usercodes/2') + + assert resp.status == 404 + result = yield from resp.json() + + assert result == {'message': 'Node not found'} + + +@asyncio.coroutine +def test_get_usercodes(hass, test_client): + """Test getting usercodes on node.""" + app = mock_http_component_app(hass) + ZWaveUserCodeView().register(app.router) + + network = hass.data[ZWAVE_NETWORK] = MagicMock() + node = MockNode(node_id=18, + command_classes=[const.COMMAND_CLASS_USER_CODE]) + value = MockValue( + index=0, + command_class=const.COMMAND_CLASS_USER_CODE) + value.genre = const.GENRE_USER + value.label = 'label' + value.data = '1234' + node.values = {0: value} + network.nodes = {18: node} + node.get_values.return_value = node.values + + client = yield from test_client(app) + + resp = yield from client.get('/api/zwave/usercodes/18') + + assert resp.status == 200 + result = yield from resp.json() + + assert result == {'0': {'code': '1234', + 'label': 'label', + 'length': 4}} + + +@asyncio.coroutine +def test_get_usercode_nousercode_node(hass, test_client): + """Test getting usercodes on node without usercodes.""" + app = mock_http_component_app(hass) + ZWaveUserCodeView().register(app.router) + + network = hass.data[ZWAVE_NETWORK] = MagicMock() + node = MockNode(node_id=18) + + network.nodes = {18: node} + node.get_values.return_value = node.values + + client = yield from test_client(app) + + resp = yield from client.get('/api/zwave/usercodes/18') + + assert resp.status == 200 + result = yield from resp.json() + + assert result == {} + + +@asyncio.coroutine +def test_get_usercodes_no_genreuser(hass, test_client): + """Test getting usercodes on node missing genre user.""" + app = mock_http_component_app(hass) + ZWaveUserCodeView().register(app.router) + + network = hass.data[ZWAVE_NETWORK] = MagicMock() + node = MockNode(node_id=18, + command_classes=[const.COMMAND_CLASS_USER_CODE]) + value = MockValue( + index=0, + command_class=const.COMMAND_CLASS_USER_CODE) + value.genre = const.GENRE_SYSTEM + value.label = 'label' + value.data = '1234' + node.values = {0: value} + network.nodes = {18: node} + node.get_values.return_value = node.values + + client = yield from test_client(app) + + resp = yield from client.get('/api/zwave/usercodes/18') + + assert resp.status == 200 + result = yield from resp.json() + + assert result == {}