diff --git a/homeassistant/components/zwave/__init__.py b/homeassistant/components/zwave/__init__.py index 640db9d4939..f3507211907 100755 --- a/homeassistant/components/zwave/__init__.py +++ b/homeassistant/components/zwave/__init__.py @@ -30,7 +30,7 @@ from homeassistant.components.frontend import register_built_in_panel from . import api from . import const -from .const import DOMAIN, DATA_DEVICES, DATA_NETWORK +from .const import DOMAIN, DATA_DEVICES, DATA_NETWORK, DATA_ENTITY_VALUES from .node_entity import ZWaveBaseEntity, ZWaveNodeEntity from . import workaround from .discovery_schemas import DISCOVERY_SCHEMAS @@ -74,12 +74,20 @@ RENAME_NODE_SCHEMA = vol.Schema({ vol.Required(const.ATTR_NODE_ID): vol.Coerce(int), vol.Required(const.ATTR_NAME): cv.string, }) + +RENAME_VALUE_SCHEMA = vol.Schema({ + vol.Required(const.ATTR_NODE_ID): vol.Coerce(int), + vol.Required(const.ATTR_VALUE_ID): vol.Coerce(int), + vol.Required(const.ATTR_NAME): cv.string, +}) + SET_CONFIG_PARAMETER_SCHEMA = vol.Schema({ 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.Optional(const.ATTR_CONFIG_SIZE, default=2): vol.Coerce(int) }) + PRINT_CONFIG_PARAMETER_SCHEMA = vol.Schema({ vol.Required(const.ATTR_NODE_ID): vol.Coerce(int), vol.Required(const.ATTR_CONFIG_PARAMETER): vol.Coerce(int), @@ -258,6 +266,7 @@ def setup(hass, config): network = hass.data[DATA_NETWORK] = ZWaveNetwork(options, autostart=False) hass.data[DATA_DEVICES] = {} + hass.data[DATA_ENTITY_VALUES] = [] if use_debug: # pragma: no cover def log_all(signal, value=None): @@ -276,12 +285,10 @@ def setup(hass, config): dispatcher.connect(log_all, weak=False) - discovered_values = [] - def value_added(node, value): """Handle new added value to a node on the network.""" # Check if this value should be tracked by an existing entity - for values in discovered_values: + for values in hass.data[DATA_ENTITY_VALUES]: values.check_value(value) for schema in DISCOVERY_SCHEMAS: @@ -294,7 +301,11 @@ def setup(hass, config): values = ZWaveDeviceEntityValues( hass, schema, value, config, device_config) - discovered_values.append(values) + + # We create a new list and update the reference here so that + # the list can be safely iterated over in the main thread + new_values = hass.data[DATA_ENTITY_VALUES] + [values] + hass.data[DATA_ENTITY_VALUES] = new_values component = EntityComponent(_LOGGER, DOMAIN, hass) @@ -401,6 +412,18 @@ def setup(hass, config): _LOGGER.info( "Renamed Z-Wave node %d to %s", node_id, name) + def rename_value(service): + """Rename a node value.""" + node_id = service.data.get(const.ATTR_NODE_ID) + value_id = service.data.get(const.ATTR_VALUE_ID) + node = network.nodes[node_id] + value = node.values[value_id] + name = service.data.get(const.ATTR_NAME) + value.label = name + _LOGGER.info( + "Renamed Z-Wave value (Node %d Value %d) to %s", + node_id, value_id, name) + def remove_failed_node(service): """Remove failed node.""" node_id = service.data.get(const.ATTR_NODE_ID) @@ -585,6 +608,10 @@ def setup(hass, config): hass.services.register(DOMAIN, const.SERVICE_RENAME_NODE, rename_node, descriptions[const.SERVICE_RENAME_NODE], schema=RENAME_NODE_SCHEMA) + hass.services.register(DOMAIN, const.SERVICE_RENAME_VALUE, + rename_value, + descriptions[const.SERVICE_RENAME_VALUE], + schema=RENAME_VALUE_SCHEMA) hass.services.register(DOMAIN, const.SERVICE_SET_CONFIG_PARAMETER, set_config_parameter, descriptions[ @@ -644,6 +671,7 @@ 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.ZWaveNodeValueView) hass.http.register_view(api.ZWaveNodeGroupView) hass.http.register_view(api.ZWaveNodeConfigView) hass.http.register_view(api.ZWaveUserCodeView) diff --git a/homeassistant/components/zwave/api.py b/homeassistant/components/zwave/api.py index 1cf3b38b26f..85e7b9c0f8f 100644 --- a/homeassistant/components/zwave/api.py +++ b/homeassistant/components/zwave/api.py @@ -9,6 +9,32 @@ from . import const _LOGGER = logging.getLogger(__name__) +class ZWaveNodeValueView(HomeAssistantView): + """View to return the node values.""" + + url = r"/api/zwave/values/{node_id:\d+}" + name = "api:zwave:values" + + @ha.callback + def get(self, request, node_id): + """Retrieve groups of node.""" + nodeid = int(node_id) + hass = request.app['hass'] + values_list = hass.data[const.DATA_ENTITY_VALUES] + + values_data = {} + # Return a list of values for this node that are used as a + # primary value for an entity + for entity_values in values_list: + if entity_values.primary.node.node_id != nodeid: + continue + + values_data[entity_values.primary.value_id] = { + 'label': entity_values.primary.label, + } + return self.json(values_data) + + class ZWaveNodeGroupView(HomeAssistantView): """View to return the nodes group configuration.""" diff --git a/homeassistant/components/zwave/const.py b/homeassistant/components/zwave/const.py index dd88dccaa80..6f8c7d1fe75 100644 --- a/homeassistant/components/zwave/const.py +++ b/homeassistant/components/zwave/const.py @@ -20,6 +20,7 @@ DISCOVERY_DEVICE = 'device' DATA_DEVICES = 'zwave_devices' DATA_NETWORK = 'zwave_network' +DATA_ENTITY_VALUES = 'zwave_entity_values' SERVICE_CHANGE_ASSOCIATION = "change_association" SERVICE_ADD_NODE = "add_node" @@ -38,6 +39,7 @@ SERVICE_SET_WAKEUP = "set_wakeup" SERVICE_STOP_NETWORK = "stop_network" SERVICE_START_NETWORK = "start_network" SERVICE_RENAME_NODE = "rename_node" +SERVICE_RENAME_VALUE = "rename_value" SERVICE_REFRESH_ENTITY = "refresh_entity" SERVICE_REFRESH_NODE = "refresh_node" SERVICE_RESET_NODE_METERS = "reset_node_meters" diff --git a/homeassistant/components/zwave/services.yaml b/homeassistant/components/zwave/services.yaml index 6f61dec5cc7..4a7379fbf56 100644 --- a/homeassistant/components/zwave/services.yaml +++ b/homeassistant/components/zwave/services.yaml @@ -109,6 +109,19 @@ rename_node: description: New Name example: 'kitchen' +rename_value: + description: Set the name of a node value. Value IDs can be queried from /api/zwave/values/{node_id} + fields: + node_id: + description: ID of the node to rename. + example: 10 + value_id: + description: ID of the value to rename. + example: 72037594255792737 + name: + description: New Name + example: 'Luminosity' + reset_node_meters: description: Resets the meter counters of a node. fields: diff --git a/tests/components/zwave/test_api.py b/tests/components/zwave/test_api.py index c1b2022c5e1..cf597f4104c 100644 --- a/tests/components/zwave/test_api.py +++ b/tests/components/zwave/test_api.py @@ -3,9 +3,38 @@ import asyncio from unittest.mock import MagicMock from homeassistant.components.zwave import DATA_NETWORK, const from homeassistant.components.zwave.api import ( - ZWaveNodeGroupView, ZWaveNodeConfigView, ZWaveUserCodeView) + ZWaveNodeValueView, ZWaveNodeGroupView, ZWaveNodeConfigView, + ZWaveUserCodeView) from tests.common import mock_http_component_app -from tests.mock.zwave import MockNode, MockValue +from tests.mock.zwave import MockNode, MockValue, MockEntityValues + + +@asyncio.coroutine +def test_get_values(hass, test_client): + """Test getting values on node.""" + app = mock_http_component_app(hass) + ZWaveNodeValueView().register(app.router) + + node = MockNode(node_id=1) + value = MockValue(value_id=123456, node=node, label='Test Label') + values = MockEntityValues(primary=value) + node2 = MockNode(node_id=2) + value2 = MockValue(value_id=234567, node=node2, label='Test Label 2') + values2 = MockEntityValues(primary=value2) + hass.data[const.DATA_ENTITY_VALUES] = [values, values2] + + client = yield from test_client(app) + + resp = yield from client.get('/api/zwave/values/1') + + assert resp.status == 200 + result = yield from resp.json() + + assert result == { + '123456': { + 'label': 'Test Label', + } + } @asyncio.coroutine diff --git a/tests/components/zwave/test_init.py b/tests/components/zwave/test_init.py index 49ec1ea6a95..8b539d3a7c9 100644 --- a/tests/components/zwave/test_init.py +++ b/tests/components/zwave/test_init.py @@ -868,6 +868,23 @@ class TestZWaveServices(unittest.TestCase): assert self.zwave_network.nodes[11].name == 'test_name' + def test_rename_value(self): + """Test zwave rename_value service.""" + node = MockNode(node_id=14) + value = MockValue(index=12, value_id=123456, label="Old Label") + node.values = {123456: value} + self.zwave_network.nodes = {11: node} + + assert value.label == "Old Label" + self.hass.services.call('zwave', 'rename_value', { + const.ATTR_NODE_ID: 11, + const.ATTR_VALUE_ID: 123456, + const.ATTR_NAME: "New Label", + }) + self.hass.block_till_done() + + assert value.label == "New Label" + def test_remove_failed_node(self): """Test zwave remove_failed_node service.""" self.hass.services.call('zwave', 'remove_failed_node', {