From 374e71f7ce3cebec6ae8e193d96016a548c9dfa7 Mon Sep 17 00:00:00 2001 From: Adam Mills Date: Fri, 26 May 2017 14:51:13 -0400 Subject: [PATCH 1/9] Use standard entity_ids for zwave entities --- homeassistant/components/light/zwave.py | 6 +- homeassistant/components/zwave/__init__.py | 64 +++---------- homeassistant/components/zwave/node_entity.py | 45 ++++++++-- tests/components/zwave/test_init.py | 85 +++--------------- tests/components/zwave/test_node_entity.py | 89 ++++++++++++++++++- 5 files changed, 154 insertions(+), 135 deletions(-) diff --git a/homeassistant/components/light/zwave.py b/homeassistant/components/light/zwave.py index 9488ad38a59c4c..752928f71a4fe3 100644 --- a/homeassistant/components/light/zwave.py +++ b/homeassistant/components/light/zwave.py @@ -47,11 +47,11 @@ def get_device(node, values, node_config, **kwargs): """Create Z-Wave entity device.""" - name = '{}.{}'.format(DOMAIN, zwave.object_id(values.primary)) refresh = node_config.get(zwave.CONF_REFRESH_VALUE) delay = node_config.get(zwave.CONF_REFRESH_DELAY) - _LOGGER.debug("name=%s node_config=%s CONF_REFRESH_VALUE=%s" - " CONF_REFRESH_DELAY=%s", name, node_config, refresh, delay) + _LOGGER.debug("node=%d value=%d node_config=%s CONF_REFRESH_VALUE=%s" + " CONF_REFRESH_DELAY=%s", node.node_id, + values.primary.value_id, node_config, refresh, delay) if node.has_command_class(zwave.const.COMMAND_CLASS_SWITCH_COLOR): return ZwaveColorLight(values, refresh, delay) diff --git a/homeassistant/components/zwave/__init__.py b/homeassistant/components/zwave/__init__.py index e82ce286e01e4d..45931dc5faf284 100755 --- a/homeassistant/components/zwave/__init__.py +++ b/homeassistant/components/zwave/__init__.py @@ -16,12 +16,13 @@ from homeassistant.core import CoreState from homeassistant.loader import get_platform from homeassistant.helpers import discovery +from homeassistant.helpers.entity import generate_entity_id from homeassistant.helpers.entity_component import EntityComponent from homeassistant.const import ( ATTR_ENTITY_ID, EVENT_HOMEASSISTANT_START, EVENT_HOMEASSISTANT_STOP) from homeassistant.helpers.entity_values import EntityValues from homeassistant.helpers.event import track_time_change -from homeassistant.util import convert, slugify +from homeassistant.util import convert import homeassistant.config as conf_util import homeassistant.helpers.config_validation as cv from homeassistant.helpers.dispatcher import ( @@ -162,28 +163,7 @@ def _obj_to_dict(obj): def _value_name(value): """Return the name of the value.""" - return '{} {}'.format(node_name(value.node), value.label) - - -def _node_object_id(node): - """Return the object_id of the node.""" - node_object_id = '{}_{}'.format(slugify(node_name(node)), node.node_id) - return node_object_id - - -def object_id(value): - """Return the object_id of the device value. - - The object_id contains node_id and value instance id - to not collide with other entity_ids. - """ - _object_id = "{}_{}_{}".format(slugify(_value_name(value)), - value.node.node_id, value.index) - - # Add the instance id if there is more than one instance for the value - if value.instance > 1: - return '{}_{}'.format(_object_id, value.instance) - return _object_id + return '{} {}'.format(node_name(value.node), value.label).strip() def nice_print_node(node): @@ -312,29 +292,16 @@ def value_added(node, value): def node_added(node): """Handle a new node on the network.""" entity = ZWaveNodeEntity(node, network) - node_config = device_config.get(entity.entity_id) + name = node_name(node) + generated_id = generate_entity_id(DOMAIN + '.{}', name, []) + node_config = device_config.get(generated_id) if node_config.get(CONF_IGNORED): _LOGGER.info( "Ignoring node entity %s due to device settings", - entity.entity_id) + generated_id) return component.add_entities([entity]) - def scene_activated(node, scene_id): - """Handle an activated scene on any node in the network.""" - hass.bus.fire(const.EVENT_SCENE_ACTIVATED, { - ATTR_ENTITY_ID: _node_object_id(node), - const.ATTR_OBJECT_ID: _node_object_id(node), - const.ATTR_SCENE_ID: scene_id - }) - - def node_event_activated(node, value): - """Handle a nodeevent on any node in the network.""" - hass.bus.fire(const.EVENT_NODE_EVENT, { - const.ATTR_OBJECT_ID: _node_object_id(node), - const.ATTR_BASIC_LEVEL: value - }) - def network_ready(): """Handle the query of all awake nodes.""" _LOGGER.info("Zwave network is ready for use. All awake nodes " @@ -352,10 +319,6 @@ def network_complete(): value_added, ZWaveNetwork.SIGNAL_VALUE_ADDED, weak=False) dispatcher.connect( node_added, ZWaveNetwork.SIGNAL_NODE_ADDED, weak=False) - dispatcher.connect( - scene_activated, ZWaveNetwork.SIGNAL_SCENE_EVENT, weak=False) - dispatcher.connect( - node_event_activated, ZWaveNetwork.SIGNAL_NODE_EVENT, weak=False) dispatcher.connect( network_ready, ZWaveNetwork.SIGNAL_AWAKE_NODES_QUERIED, weak=False) dispatcher.connect( @@ -757,9 +720,8 @@ def _check_entity_ready(self): self.primary) if workaround_component and workaround_component != component: if workaround_component == workaround.WORKAROUND_IGNORE: - _LOGGER.info("Ignoring device %s due to workaround.", - "{}.{}".format( - component, object_id(self.primary))) + _LOGGER.info("Ignoring Node %d Value %d due to workaround.", + self.primary.node.node_id, self.primary.value_id) # No entity will be created for this value self._workaround_ignore = True return @@ -767,8 +729,9 @@ def _check_entity_ready(self): workaround_component, component) component = workaround_component - name = "{}.{}".format(component, object_id(self.primary)) - node_config = self._device_config.get(name) + value_name = _value_name(self.primary) + generated_id = generate_entity_id(component + '.{}', value_name, []) + node_config = self._device_config.get(generated_id) # Configure node _LOGGER.debug("Adding Node_id=%s Generic_command_class=%s, " @@ -781,7 +744,7 @@ def _check_entity_ready(self): if node_config.get(CONF_IGNORED): _LOGGER.info( - "Ignoring entity %s due to device settings", name) + "Ignoring entity %s due to device settings", generated_id) # No entity will be created for this value self._workaround_ignore = True return @@ -828,7 +791,6 @@ def __init__(self, values, domain): self.values = values self.node = values.primary.node self.values.primary.set_change_verified(False) - self.entity_id = "{}.{}".format(domain, object_id(values.primary)) self._name = _value_name(self.values.primary) self._unique_id = "ZWAVE-{}-{}".format(self.node.node_id, diff --git a/homeassistant/components/zwave/node_entity.py b/homeassistant/components/zwave/node_entity.py index 5a441114f55f82..7f9cc1706d5a72 100644 --- a/homeassistant/components/zwave/node_entity.py +++ b/homeassistant/components/zwave/node_entity.py @@ -2,11 +2,12 @@ import logging from homeassistant.core import callback -from homeassistant.const import ATTR_BATTERY_LEVEL, ATTR_WAKEUP +from homeassistant.const import ATTR_BATTERY_LEVEL, ATTR_WAKEUP, ATTR_ENTITY_ID from homeassistant.helpers.entity import Entity -from homeassistant.util import slugify -from .const import ATTR_NODE_ID, DOMAIN, COMMAND_CLASS_WAKE_UP +from .const import ( + ATTR_NODE_ID, COMMAND_CLASS_WAKE_UP, ATTR_SCENE_ID, ATTR_BASIC_LEVEL, + EVENT_NODE_EVENT, EVENT_SCENE_ACTIVATED) from .util import node_name _LOGGER = logging.getLogger(__name__) @@ -84,8 +85,6 @@ def __init__(self, node, network): self._name = node_name(self.node) self._product_name = node.product_name self._manufacturer_name = node.manufacturer_name - self.entity_id = "{}.{}_{}".format( - DOMAIN, slugify(self._name), self.node_id) self._attributes = {} self.wakeup_interval = None self.location = None @@ -95,6 +94,10 @@ def __init__(self, node, network): dispatcher.connect(self.network_node_changed, ZWaveNetwork.SIGNAL_NODE) dispatcher.connect( self.network_node_changed, ZWaveNetwork.SIGNAL_NOTIFICATION) + dispatcher.connect( + self.network_node_event, ZWaveNetwork.SIGNAL_NODE_EVENT) + dispatcher.connect( + self.network_scene_activated, ZWaveNetwork.SIGNAL_SCENE_EVENT) def network_node_changed(self, node=None, args=None): """Handle a changed node on the network.""" @@ -134,6 +137,38 @@ def node_changed(self): self.maybe_schedule_update() + def network_node_event(self, node, value): + """Handle a node activated event on the network.""" + if node.node_id == self.node.node_id: + self.node_event(value) + + def node_event(self, value): + """Handle a node activated event for this node.""" + if self.hass is None: + return + + self.hass.bus.fire(EVENT_NODE_EVENT, { + ATTR_ENTITY_ID: self.entity_id, + ATTR_NODE_ID: self.node.node_id, + ATTR_BASIC_LEVEL: value + }) + + def network_scene_activated(self, node, scene_id): + """Handle a scene activated event on the network.""" + if node.node_id == self.node.node_id: + self.scene_activated(scene_id) + + def scene_activated(self, scene_id): + """Handle an activated scene for this node.""" + if self.hass is None: + return + + self.hass.bus.fire(EVENT_SCENE_ACTIVATED, { + ATTR_ENTITY_ID: self.entity_id, + ATTR_NODE_ID: self.node.node_id, + ATTR_SCENE_ID: scene_id + }) + @property def state(self): """Return the state.""" diff --git a/tests/components/zwave/test_init.py b/tests/components/zwave/test_init.py index eac33168fb7706..3d96de47b94a76 100644 --- a/tests/components/zwave/test_init.py +++ b/tests/components/zwave/test_init.py @@ -222,7 +222,7 @@ def mock_connect(receiver, signal, *args, **kwargs): hass.async_add_job(mock_receivers[0], node) yield from hass.async_block_till_done() - assert hass.states.get('zwave.mock_node_14').state is 'unknown' + assert hass.states.get('zwave.mock_node').state is 'unknown' @asyncio.coroutine @@ -237,7 +237,7 @@ def mock_connect(receiver, signal, *args, **kwargs): with patch('pydispatch.dispatcher.connect', new=mock_connect): yield from async_setup_component(hass, 'zwave', {'zwave': { 'device_config': { - 'zwave.mock_node_14': { + 'zwave.mock_node': { 'ignored': True, }}}}) @@ -247,7 +247,7 @@ def mock_connect(receiver, signal, *args, **kwargs): hass.async_add_job(mock_receivers[0], node) yield from hass.async_block_till_done() - assert hass.states.get('zwave.mock_node_14') is None + assert hass.states.get('zwave.mock_node') is None @asyncio.coroutine @@ -272,7 +272,7 @@ def mock_connect(receiver, signal, *args, **kwargs): yield from hass.async_block_till_done() assert hass.states.get( - 'binary_sensor.mock_node_mock_value_11_12_13').state is 'off' + 'binary_sensor.mock_node_mock_value').state is 'off' @asyncio.coroutine @@ -297,9 +297,9 @@ def mock_connect(receiver, signal, *args, **kwargs): hass.async_add_job(mock_receivers[0], node, setpoint) yield from hass.async_block_till_done() - assert hass.states.get('climate.mock_node_mock_value_11_12_13').attributes[ + assert hass.states.get('climate.mock_node_mock_value').attributes[ 'temperature'] == 22.0 - assert hass.states.get('climate.mock_node_mock_value_11_12_13').attributes[ + assert hass.states.get('climate.mock_node_mock_value').attributes[ 'current_temperature'] is None def mock_update(self): @@ -314,75 +314,12 @@ def mock_update(self): hass.async_add_job(mock_receivers[0], node, temperature) yield from hass.async_block_till_done() - assert hass.states.get('climate.mock_node_mock_value_11_12_13').attributes[ + assert hass.states.get('climate.mock_node_mock_value').attributes[ 'temperature'] == 22.0 - assert hass.states.get('climate.mock_node_mock_value_11_12_13').attributes[ + assert hass.states.get('climate.mock_node_mock_value').attributes[ 'current_temperature'] == 23.5 -@asyncio.coroutine -def test_scene_activated(hass, mock_openzwave): - """Test scene activated event.""" - mock_receivers = [] - - def mock_connect(receiver, signal, *args, **kwargs): - if signal == MockNetwork.SIGNAL_SCENE_EVENT: - mock_receivers.append(receiver) - - with patch('pydispatch.dispatcher.connect', new=mock_connect): - yield from async_setup_component(hass, 'zwave', {'zwave': {}}) - - assert len(mock_receivers) == 1 - - events = [] - - def listener(event): - events.append(event) - - hass.bus.async_listen(const.EVENT_SCENE_ACTIVATED, listener) - - node = MockNode(node_id=11) - scene_id = 123 - hass.async_add_job(mock_receivers[0], node, scene_id) - yield from hass.async_block_till_done() - - assert len(events) == 1 - assert events[0].data[ATTR_ENTITY_ID] == "mock_node_11" - assert events[0].data[const.ATTR_OBJECT_ID] == "mock_node_11" - assert events[0].data[const.ATTR_SCENE_ID] == scene_id - - -@asyncio.coroutine -def test_node_event_activated(hass, mock_openzwave): - """Test Node event activated event.""" - mock_receivers = [] - - def mock_connect(receiver, signal, *args, **kwargs): - if signal == MockNetwork.SIGNAL_NODE_EVENT: - mock_receivers.append(receiver) - - with patch('pydispatch.dispatcher.connect', new=mock_connect): - yield from async_setup_component(hass, 'zwave', {'zwave': {}}) - - assert len(mock_receivers) == 1 - - events = [] - - def listener(event): - events.append(event) - - hass.bus.async_listen(const.EVENT_NODE_EVENT, listener) - - node = MockNode(node_id=11) - value = 234 - hass.async_add_job(mock_receivers[0], node, value) - yield from hass.async_block_till_done() - - assert len(events) == 1 - assert events[0].data[const.ATTR_OBJECT_ID] == "mock_node_11" - assert events[0].data[const.ATTR_BASIC_LEVEL] == value - - @asyncio.coroutine def test_network_ready(hass, mock_openzwave): """Test Node network ready event.""" @@ -478,8 +415,7 @@ def setUp(self): self.no_match_value = MockValue( command_class='mock_bad_class', node=self.node) - self.entity_id = '{}.{}'.format('mock_component', - zwave.object_id(self.primary)) + self.entity_id = 'mock_component.mock_node_mock_value' self.zwave_config = {} self.device_config = {self.entity_id: {}} @@ -616,8 +552,7 @@ def test_entity_workaround_component(self, discovery, get_platform): self.node.manufacturer_id = '010f' self.node.product_type = '0b00' self.primary.command_class = const.COMMAND_CLASS_SENSOR_ALARM - self.entity_id = '{}.{}'.format('binary_sensor', - zwave.object_id(self.primary)) + self.entity_id = 'binary_sensor.mock_node_mock_value' self.device_config = {self.entity_id: {}} self.mock_schema = { diff --git a/tests/components/zwave/test_node_entity.py b/tests/components/zwave/test_node_entity.py index 73e8e163096c88..b822be9638dc35 100644 --- a/tests/components/zwave/test_node_entity.py +++ b/tests/components/zwave/test_node_entity.py @@ -4,7 +4,8 @@ from unittest.mock import patch, MagicMock import tests.mock.zwave as mock_zwave import pytest -from homeassistant.components.zwave import node_entity +from homeassistant.components.zwave import node_entity, const +from homeassistant.const import ATTR_ENTITY_ID @asyncio.coroutine @@ -30,6 +31,92 @@ def test_maybe_schedule_update(hass, mock_openzwave): assert len(mock_call_later.mock_calls) == 2 +@asyncio.coroutine +def test_node_event_activated(hass, mock_openzwave): + """Test Node event activated event.""" + mock_receivers = [] + + def mock_connect(receiver, signal, *args, **kwargs): + if signal == mock_zwave.MockNetwork.SIGNAL_NODE_EVENT: + mock_receivers.append(receiver) + + node = mock_zwave.MockNode(node_id=11) + + with patch('pydispatch.dispatcher.connect', new=mock_connect): + entity = node_entity.ZWaveNodeEntity(node, mock_openzwave) + + assert len(mock_receivers) == 1 + + events = [] + + def listener(event): + events.append(event) + + hass.bus.async_listen(const.EVENT_NODE_EVENT, listener) + + # Test event before entity added to hass + value = 234 + hass.async_add_job(mock_receivers[0], node, value) + yield from hass.async_block_till_done() + assert len(events) == 0 + + # Add entity to hass + entity.hass = hass + entity.entity_id = 'zwave.mock_node' + + value = 234 + hass.async_add_job(mock_receivers[0], node, value) + yield from hass.async_block_till_done() + + assert len(events) == 1 + assert events[0].data[ATTR_ENTITY_ID] == "zwave.mock_node" + assert events[0].data[const.ATTR_NODE_ID] == 11 + assert events[0].data[const.ATTR_BASIC_LEVEL] == value + + +@asyncio.coroutine +def test_scene_activated(hass, mock_openzwave): + """Test scene activated event.""" + mock_receivers = [] + + def mock_connect(receiver, signal, *args, **kwargs): + if signal == mock_zwave.MockNetwork.SIGNAL_SCENE_EVENT: + mock_receivers.append(receiver) + + node = mock_zwave.MockNode(node_id=11) + + with patch('pydispatch.dispatcher.connect', new=mock_connect): + entity = node_entity.ZWaveNodeEntity(node, mock_openzwave) + + assert len(mock_receivers) == 1 + + events = [] + + def listener(event): + events.append(event) + + hass.bus.async_listen(const.EVENT_SCENE_ACTIVATED, listener) + + # Test event before entity added to hass + scene_id = 123 + hass.async_add_job(mock_receivers[0], node, scene_id) + yield from hass.async_block_till_done() + assert len(events) == 0 + + # Add entity to hass + entity.hass = hass + entity.entity_id = 'zwave.mock_node' + + scene_id = 123 + hass.async_add_job(mock_receivers[0], node, scene_id) + yield from hass.async_block_till_done() + + assert len(events) == 1 + assert events[0].data[ATTR_ENTITY_ID] == "zwave.mock_node" + assert events[0].data[const.ATTR_NODE_ID] == 11 + assert events[0].data[const.ATTR_SCENE_ID] == scene_id + + @pytest.mark.usefixtures('mock_openzwave') class TestZWaveNodeEntity(unittest.TestCase): """Class to test ZWaveNodeEntity.""" From 342f0ebb7e9509dff055d67963b686bc1802b3d6 Mon Sep 17 00:00:00 2001 From: Adam Mills Date: Wed, 14 Jun 2017 20:14:08 -0400 Subject: [PATCH 2/9] Include temporary opt-in for new entity ids --- homeassistant/components/zwave/__init__.py | 49 +++++++++++++++++-- homeassistant/components/zwave/node_entity.py | 8 ++- 2 files changed, 51 insertions(+), 6 deletions(-) diff --git a/homeassistant/components/zwave/__init__.py b/homeassistant/components/zwave/__init__.py index 45931dc5faf284..360da06d10e1d4 100755 --- a/homeassistant/components/zwave/__init__.py +++ b/homeassistant/components/zwave/__init__.py @@ -22,7 +22,7 @@ ATTR_ENTITY_ID, EVENT_HOMEASSISTANT_START, EVENT_HOMEASSISTANT_STOP) from homeassistant.helpers.entity_values import EntityValues from homeassistant.helpers.event import track_time_change -from homeassistant.util import convert +from homeassistant.util import convert, slugify import homeassistant.config as conf_util import homeassistant.helpers.config_validation as cv from homeassistant.helpers.dispatcher import ( @@ -56,6 +56,7 @@ CONF_DEVICE_CONFIG_GLOB = 'device_config_glob' CONF_DEVICE_CONFIG_DOMAIN = 'device_config_domain' CONF_NETWORK_KEY = 'network_key' +CONF_NEW_ENTITY_IDS = 'new_entity_ids' ATTR_POWER = 'power_consumption' @@ -150,6 +151,7 @@ cv.positive_int, vol.Optional(CONF_USB_STICK_PATH, default=DEFAULT_CONF_USB_STICK_PATH): cv.string, + vol.Optional(CONF_NEW_ENTITY_IDS, default=False): cv.boolean, }), }, extra=vol.ALLOW_EXTRA) @@ -166,6 +168,27 @@ def _value_name(value): return '{} {}'.format(node_name(value.node), value.label).strip() +def _node_object_id(node): + """Return the object_id of the node.""" + node_object_id = '{}_{}'.format(slugify(node_name(node)), node.node_id) + return node_object_id + + +def object_id(value): + """Return the object_id of the device value. + + The object_id contains node_id and value instance id + to not collide with other entity_ids. + """ + _object_id = "{}_{}_{}".format(slugify(_value_name(value)), + value.node.node_id, value.index) + + # Add the instance id if there is more than one instance for the value + if value.instance > 1: + return '{}_{}'.format(_object_id, value.instance) + return _object_id + + def nice_print_node(node): """Print a nice formatted node to the output (debug method).""" node_dict = _obj_to_dict(node) @@ -230,6 +253,13 @@ def setup(hass, config): config[DOMAIN][CONF_DEVICE_CONFIG], config[DOMAIN][CONF_DEVICE_CONFIG_DOMAIN], config[DOMAIN][CONF_DEVICE_CONFIG_GLOB]) + new_entity_ids = config[DOMAIN][CONF_NEW_ENTITY_IDS] + if not new_entity_ids: + _LOGGER.warning( + "ZWave entity_ids will soon be changing. To opt in to new " + "entity_ids now, set `new_entity_ids: true` under zwave in your " + "configuration.yaml. See the following blog post for details: " + "") # Setup options options = ZWaveOption( @@ -291,9 +321,12 @@ def value_added(node, value): def node_added(node): """Handle a new node on the network.""" - entity = ZWaveNodeEntity(node, network) + entity = ZWaveNodeEntity(node, network, new_entity_ids) name = node_name(node) - generated_id = generate_entity_id(DOMAIN + '.{}', name, []) + if new_entity_ids: + generated_id = generate_entity_id(DOMAIN + '.{}', name, []) + else: + generated_id = entity.entity_id node_config = device_config.get(generated_id) if node_config.get(CONF_IGNORED): _LOGGER.info( @@ -730,7 +763,11 @@ def _check_entity_ready(self): component = workaround_component value_name = _value_name(self.primary) - generated_id = generate_entity_id(component + '.{}', value_name, []) + if self._zwave_config[DOMAIN][CONF_NEW_ENTITY_IDS]: + generated_id = generate_entity_id( + component + '.{}', value_name, []) + else: + generated_id = "{}.{}".format(component, object_id(self.primary)) node_config = self._device_config.get(generated_id) # Configure node @@ -765,6 +802,10 @@ def _check_entity_ready(self): self._workaround_ignore = True return + if not self._zwave_config[DOMAIN][CONF_NEW_ENTITY_IDS]: + device.entity_id = "{}.{}".format( + component, object_id(self.primary)) + self._entity = device dict_id = id(self) diff --git a/homeassistant/components/zwave/node_entity.py b/homeassistant/components/zwave/node_entity.py index 7f9cc1706d5a72..84bf388e49dad1 100644 --- a/homeassistant/components/zwave/node_entity.py +++ b/homeassistant/components/zwave/node_entity.py @@ -4,10 +4,11 @@ from homeassistant.core import callback from homeassistant.const import ATTR_BATTERY_LEVEL, ATTR_WAKEUP, ATTR_ENTITY_ID from homeassistant.helpers.entity import Entity +from homeassistant.util import slugify from .const import ( ATTR_NODE_ID, COMMAND_CLASS_WAKE_UP, ATTR_SCENE_ID, ATTR_BASIC_LEVEL, - EVENT_NODE_EVENT, EVENT_SCENE_ACTIVATED) + EVENT_NODE_EVENT, EVENT_SCENE_ACTIVATED, DOMAIN) from .util import node_name _LOGGER = logging.getLogger(__name__) @@ -73,7 +74,7 @@ def sub_status(status, stage): class ZWaveNodeEntity(ZWaveBaseEntity): """Representation of a Z-Wave node.""" - def __init__(self, node, network): + def __init__(self, node, network, new_entity_ids): """Initialize node.""" # pylint: disable=import-error super().__init__() @@ -85,6 +86,9 @@ def __init__(self, node, network): self._name = node_name(self.node) self._product_name = node.product_name self._manufacturer_name = node.manufacturer_name + if not new_entity_ids: + self.entity_id = "{}.{}_{}".format( + DOMAIN, slugify(self._name), self.node_id) self._attributes = {} self.wakeup_interval = None self.location = None From d4d794a28505ac565b3f2c9ad7563f080c533af7 Mon Sep 17 00:00:00 2001 From: Adam Mills Date: Wed, 14 Jun 2017 22:21:55 -0400 Subject: [PATCH 3/9] Update link to blog post --- homeassistant/components/zwave/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/zwave/__init__.py b/homeassistant/components/zwave/__init__.py index 360da06d10e1d4..6b8b96ba72b2d0 100755 --- a/homeassistant/components/zwave/__init__.py +++ b/homeassistant/components/zwave/__init__.py @@ -259,7 +259,7 @@ def setup(hass, config): "ZWave entity_ids will soon be changing. To opt in to new " "entity_ids now, set `new_entity_ids: true` under zwave in your " "configuration.yaml. See the following blog post for details: " - "") + "https://home-assistant.io/blog/2017/06/15/zwave-entity-ids/") # Setup options options = ZWaveOption( From 4fedc439d33006f2e1df8f8086d89a019103be2f Mon Sep 17 00:00:00 2001 From: Adam Mills Date: Wed, 14 Jun 2017 22:28:31 -0400 Subject: [PATCH 4/9] Update tests --- tests/components/zwave/test_init.py | 33 ++++++++++++++++------ tests/components/zwave/test_node_entity.py | 6 ++-- 2 files changed, 28 insertions(+), 11 deletions(-) diff --git a/tests/components/zwave/test_init.py b/tests/components/zwave/test_init.py index 3d96de47b94a76..38b1ce9fa8fe0c 100644 --- a/tests/components/zwave/test_init.py +++ b/tests/components/zwave/test_init.py @@ -214,7 +214,9 @@ def mock_connect(receiver, signal, *args, **kwargs): mock_receivers.append(receiver) with patch('pydispatch.dispatcher.connect', new=mock_connect): - yield from async_setup_component(hass, 'zwave', {'zwave': {}}) + yield from async_setup_component(hass, 'zwave', {'zwave': { + 'new_entity_ids': True, + }}) assert len(mock_receivers) == 1 @@ -236,6 +238,7 @@ def mock_connect(receiver, signal, *args, **kwargs): with patch('pydispatch.dispatcher.connect', new=mock_connect): yield from async_setup_component(hass, 'zwave', {'zwave': { + 'new_entity_ids': True, 'device_config': { 'zwave.mock_node': { 'ignored': True, @@ -260,7 +263,9 @@ def mock_connect(receiver, signal, *args, **kwargs): mock_receivers.append(receiver) with patch('pydispatch.dispatcher.connect', new=mock_connect): - yield from async_setup_component(hass, 'zwave', {'zwave': {}}) + yield from async_setup_component(hass, 'zwave', {'zwave': { + 'new_entity_ids': True, + }}) assert len(mock_receivers) == 1 @@ -285,7 +290,9 @@ def mock_connect(receiver, signal, *args, **kwargs): mock_receivers.append(receiver) with patch('pydispatch.dispatcher.connect', new=mock_connect): - yield from async_setup_component(hass, 'zwave', {'zwave': {}}) + yield from async_setup_component(hass, 'zwave', {'zwave': { + 'new_entity_ids': True, + }}) assert len(mock_receivers) == 1 @@ -330,7 +337,9 @@ def mock_connect(receiver, signal, *args, **kwargs): mock_receivers.append(receiver) with patch('pydispatch.dispatcher.connect', new=mock_connect): - yield from async_setup_component(hass, 'zwave', {'zwave': {}}) + yield from async_setup_component(hass, 'zwave', {'zwave': { + 'new_entity_ids': True, + }}) assert len(mock_receivers) == 1 @@ -357,7 +366,9 @@ def mock_connect(receiver, signal, *args, **kwargs): mock_receivers.append(receiver) with patch('pydispatch.dispatcher.connect', new=mock_connect): - yield from async_setup_component(hass, 'zwave', {'zwave': {}}) + yield from async_setup_component(hass, 'zwave', {'zwave': { + 'new_entity_ids': True, + }}) assert len(mock_receivers) == 1 @@ -387,7 +398,9 @@ def setUp(self): self.hass = get_test_home_assistant() self.hass.start() - setup_component(self.hass, 'zwave', {'zwave': {}}) + setup_component(self.hass, 'zwave', {'zwave': { + 'new_entity_ids': True, + }}) self.hass.block_till_done() self.node = MockNode() @@ -416,7 +429,9 @@ def setUp(self): command_class='mock_bad_class', node=self.node) self.entity_id = 'mock_component.mock_node_mock_value' - self.zwave_config = {} + self.zwave_config = {'zwave': { + 'new_entity_ids': True, + }} self.device_config = {self.entity_id: {}} def tearDown(self): # pylint: disable=invalid-name @@ -705,7 +720,9 @@ def setUp(self): self.hass.start() # Initialize zwave - setup_component(self.hass, 'zwave', {'zwave': {}}) + setup_component(self.hass, 'zwave', {'zwave': { + 'new_entity_ids': True, + }}) self.hass.block_till_done() self.zwave_network = self.hass.data[DATA_NETWORK] self.zwave_network.state = MockNetwork.STATE_READY diff --git a/tests/components/zwave/test_node_entity.py b/tests/components/zwave/test_node_entity.py index b822be9638dc35..aa49d395e59d14 100644 --- a/tests/components/zwave/test_node_entity.py +++ b/tests/components/zwave/test_node_entity.py @@ -43,7 +43,7 @@ def mock_connect(receiver, signal, *args, **kwargs): node = mock_zwave.MockNode(node_id=11) with patch('pydispatch.dispatcher.connect', new=mock_connect): - entity = node_entity.ZWaveNodeEntity(node, mock_openzwave) + entity = node_entity.ZWaveNodeEntity(node, mock_openzwave, True) assert len(mock_receivers) == 1 @@ -86,7 +86,7 @@ def mock_connect(receiver, signal, *args, **kwargs): node = mock_zwave.MockNode(node_id=11) with patch('pydispatch.dispatcher.connect', new=mock_connect): - entity = node_entity.ZWaveNodeEntity(node, mock_openzwave) + entity = node_entity.ZWaveNodeEntity(node, mock_openzwave, True) assert len(mock_receivers) == 1 @@ -131,7 +131,7 @@ def setUp(self): self.node.manufacturer_name = 'Test Manufacturer' self.node.product_name = 'Test Product' self.entity = node_entity.ZWaveNodeEntity(self.node, - self.zwave_network) + self.zwave_network, True) def test_network_node_changed_from_value(self): """Test for network_node_changed.""" From cdb18eb97b24915e3b45f05aaa6fa5b08c9df872 Mon Sep 17 00:00:00 2001 From: Adam Mills Date: Thu, 15 Jun 2017 18:14:53 -0400 Subject: [PATCH 5/9] Add old entity_id as state attribute --- homeassistant/components/zwave/__init__.py | 6 ++++-- homeassistant/components/zwave/node_entity.py | 7 +++++-- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/zwave/__init__.py b/homeassistant/components/zwave/__init__.py index 6b8b96ba72b2d0..31f446587968e7 100755 --- a/homeassistant/components/zwave/__init__.py +++ b/homeassistant/components/zwave/__init__.py @@ -802,9 +802,10 @@ def _check_entity_ready(self): self._workaround_ignore = True return + device._old_entity_id = "{}.{}".format( + component, object_id(self.primary)) if not self._zwave_config[DOMAIN][CONF_NEW_ENTITY_IDS]: - device.entity_id = "{}.{}".format( - component, object_id(self.primary)) + device.entity_id = device._old_entity_id self._entity = device @@ -898,6 +899,7 @@ def device_state_attributes(self): """Return the device specific state attributes.""" attrs = { const.ATTR_NODE_ID: self.node_id, + 'old_entity_id': self._old_entity_id, } if self.power_consumption is not None: diff --git a/homeassistant/components/zwave/node_entity.py b/homeassistant/components/zwave/node_entity.py index 84bf388e49dad1..2dc7df72ff52d7 100644 --- a/homeassistant/components/zwave/node_entity.py +++ b/homeassistant/components/zwave/node_entity.py @@ -40,6 +40,7 @@ class ZWaveBaseEntity(Entity): def __init__(self): """Initialize the base Z-Wave class.""" self._update_scheduled = False + self._old_entity_id = None def maybe_schedule_update(self): """Maybe schedule state update. @@ -86,9 +87,10 @@ def __init__(self, node, network, new_entity_ids): self._name = node_name(self.node) self._product_name = node.product_name self._manufacturer_name = node.manufacturer_name + self._old_entity_id = "{}.{}_{}".format( + DOMAIN, slugify(self._name), self.node_id) if not new_entity_ids: - self.entity_id = "{}.{}_{}".format( - DOMAIN, slugify(self._name), self.node_id) + self.entity_id = self._old_entity_id self._attributes = {} self.wakeup_interval = None self.location = None @@ -208,6 +210,7 @@ def device_state_attributes(self): ATTR_NODE_NAME: self._name, ATTR_MANUFACTURER_NAME: self._manufacturer_name, ATTR_PRODUCT_NAME: self._product_name, + 'old_entity_id': self._old_entity_id, } attrs.update(self._attributes) if self.battery_level is not None: From a1d5be46b548d6ca56e740477f76f122aeebd28c Mon Sep 17 00:00:00 2001 From: Adam Mills Date: Thu, 15 Jun 2017 18:47:49 -0400 Subject: [PATCH 6/9] Expose ZWave value details --- homeassistant/components/zwave/__init__.py | 8 +++++--- homeassistant/components/zwave/api.py | 2 ++ homeassistant/components/zwave/const.py | 2 ++ homeassistant/components/zwave/node_entity.py | 8 ++++---- 4 files changed, 13 insertions(+), 7 deletions(-) diff --git a/homeassistant/components/zwave/__init__.py b/homeassistant/components/zwave/__init__.py index 31f446587968e7..6bc840bf10fd04 100755 --- a/homeassistant/components/zwave/__init__.py +++ b/homeassistant/components/zwave/__init__.py @@ -802,10 +802,10 @@ def _check_entity_ready(self): self._workaround_ignore = True return - device._old_entity_id = "{}.{}".format( + device.old_entity_id = "{}.{}".format( component, object_id(self.primary)) if not self._zwave_config[DOMAIN][CONF_NEW_ENTITY_IDS]: - device.entity_id = device._old_entity_id + device.entity_id = device.old_entity_id self._entity = device @@ -899,7 +899,9 @@ def device_state_attributes(self): """Return the device specific state attributes.""" attrs = { const.ATTR_NODE_ID: self.node_id, - 'old_entity_id': self._old_entity_id, + const.ATTR_VALUE_INDEX: self.values.primary.index, + const.ATTR_VALUE_INSTANCE: self.values.primary.instance, + 'old_entity_id': self.old_entity_id, } if self.power_consumption is not None: diff --git a/homeassistant/components/zwave/api.py b/homeassistant/components/zwave/api.py index 85e7b9c0f8fdd9..181ab4ae18c23c 100644 --- a/homeassistant/components/zwave/api.py +++ b/homeassistant/components/zwave/api.py @@ -31,6 +31,8 @@ def get(self, request, node_id): values_data[entity_values.primary.value_id] = { 'label': entity_values.primary.label, + 'index': entity_values.primary.index, + 'instance': entity_values.primary.instance, } return self.json(values_data) diff --git a/homeassistant/components/zwave/const.py b/homeassistant/components/zwave/const.py index 57e77fe49cb17c..4b18ef464750d4 100644 --- a/homeassistant/components/zwave/const.py +++ b/homeassistant/components/zwave/const.py @@ -14,6 +14,8 @@ ATTR_CONFIG_PARAMETER = "parameter" ATTR_CONFIG_SIZE = "size" ATTR_CONFIG_VALUE = "value" +ATTR_VALUE_INDEX = "value_index" +ATTR_VALUE_INSTANCE = "value_instance" NETWORK_READY_WAIT_SECS = 30 DISCOVERY_DEVICE = 'device' diff --git a/homeassistant/components/zwave/node_entity.py b/homeassistant/components/zwave/node_entity.py index 2dc7df72ff52d7..3bd8a11dd3c235 100644 --- a/homeassistant/components/zwave/node_entity.py +++ b/homeassistant/components/zwave/node_entity.py @@ -40,7 +40,7 @@ class ZWaveBaseEntity(Entity): def __init__(self): """Initialize the base Z-Wave class.""" self._update_scheduled = False - self._old_entity_id = None + self.old_entity_id = None def maybe_schedule_update(self): """Maybe schedule state update. @@ -87,10 +87,10 @@ def __init__(self, node, network, new_entity_ids): self._name = node_name(self.node) self._product_name = node.product_name self._manufacturer_name = node.manufacturer_name - self._old_entity_id = "{}.{}_{}".format( + self.old_entity_id = "{}.{}_{}".format( DOMAIN, slugify(self._name), self.node_id) if not new_entity_ids: - self.entity_id = self._old_entity_id + self.entity_id = self.old_entity_id self._attributes = {} self.wakeup_interval = None self.location = None @@ -210,7 +210,7 @@ def device_state_attributes(self): ATTR_NODE_NAME: self._name, ATTR_MANUFACTURER_NAME: self._manufacturer_name, ATTR_PRODUCT_NAME: self._product_name, - 'old_entity_id': self._old_entity_id, + 'old_entity_id': self.old_entity_id, } attrs.update(self._attributes) if self.battery_level is not None: From c263886293bc18d99b20cf48aa00af12758d9ec3 Mon Sep 17 00:00:00 2001 From: Adam Mills Date: Thu, 15 Jun 2017 19:56:07 -0400 Subject: [PATCH 7/9] Update tests --- tests/components/zwave/test_api.py | 5 ++++- tests/components/zwave/test_node_entity.py | 2 ++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/tests/components/zwave/test_api.py b/tests/components/zwave/test_api.py index cf597f4104c1b4..5fae8b0f317e50 100644 --- a/tests/components/zwave/test_api.py +++ b/tests/components/zwave/test_api.py @@ -16,7 +16,8 @@ def test_get_values(hass, test_client): ZWaveNodeValueView().register(app.router) node = MockNode(node_id=1) - value = MockValue(value_id=123456, node=node, label='Test Label') + value = MockValue(value_id=123456, node=node, label='Test Label', + instance=1, index=2) values = MockEntityValues(primary=value) node2 = MockNode(node_id=2) value2 = MockValue(value_id=234567, node=node2, label='Test Label 2') @@ -33,6 +34,8 @@ def test_get_values(hass, test_client): assert result == { '123456': { 'label': 'Test Label', + 'instance': 1, + 'index': 2, } } diff --git a/tests/components/zwave/test_node_entity.py b/tests/components/zwave/test_node_entity.py index aa49d395e59d14..673946241edfcd 100644 --- a/tests/components/zwave/test_node_entity.py +++ b/tests/components/zwave/test_node_entity.py @@ -172,6 +172,7 @@ def test_node_changed(self): {'node_id': self.node.node_id, 'node_name': 'Mock Node', 'manufacturer_name': 'Test Manufacturer', + 'old_entity_id': 'zwave.mock_node_567', 'product_name': 'Test Product'}, self.entity.device_state_attributes) @@ -230,6 +231,7 @@ def test_node_changed(self): {'node_id': self.node.node_id, 'node_name': 'Mock Node', 'manufacturer_name': 'Test Manufacturer', + 'old_entity_id': 'zwave.mock_node_567', 'product_name': 'Test Product', 'query_stage': 'Dynamic', 'is_awake': True, From 6eed4e1fbd082b9a18280f3eedb78bcd56e4e896 Mon Sep 17 00:00:00 2001 From: Adam Mills Date: Fri, 16 Jun 2017 08:53:22 -0400 Subject: [PATCH 8/9] Also show new_entity_id --- homeassistant/components/zwave/__init__.py | 2 ++ homeassistant/components/zwave/node_entity.py | 3 +++ tests/components/zwave/test_node_entity.py | 2 ++ 3 files changed, 7 insertions(+) diff --git a/homeassistant/components/zwave/__init__.py b/homeassistant/components/zwave/__init__.py index 6bc840bf10fd04..a79a986dc7def2 100755 --- a/homeassistant/components/zwave/__init__.py +++ b/homeassistant/components/zwave/__init__.py @@ -804,6 +804,7 @@ def _check_entity_ready(self): device.old_entity_id = "{}.{}".format( component, object_id(self.primary)) + device.new_entity_id = "{}.{}".format(component, slugify(device.name)) if not self._zwave_config[DOMAIN][CONF_NEW_ENTITY_IDS]: device.entity_id = device.old_entity_id @@ -902,6 +903,7 @@ def device_state_attributes(self): const.ATTR_VALUE_INDEX: self.values.primary.index, const.ATTR_VALUE_INSTANCE: self.values.primary.instance, 'old_entity_id': self.old_entity_id, + 'new_entity_id': self.new_entity_id, } if self.power_consumption is not None: diff --git a/homeassistant/components/zwave/node_entity.py b/homeassistant/components/zwave/node_entity.py index 3bd8a11dd3c235..3a810d00d2d4e9 100644 --- a/homeassistant/components/zwave/node_entity.py +++ b/homeassistant/components/zwave/node_entity.py @@ -41,6 +41,7 @@ def __init__(self): """Initialize the base Z-Wave class.""" self._update_scheduled = False self.old_entity_id = None + self.new_entity_id = None def maybe_schedule_update(self): """Maybe schedule state update. @@ -89,6 +90,7 @@ def __init__(self, node, network, new_entity_ids): self._manufacturer_name = node.manufacturer_name self.old_entity_id = "{}.{}_{}".format( DOMAIN, slugify(self._name), self.node_id) + self.new_entity_id = "{}.{}".format(DOMAIN, slugify(self._name)) if not new_entity_ids: self.entity_id = self.old_entity_id self._attributes = {} @@ -211,6 +213,7 @@ def device_state_attributes(self): ATTR_MANUFACTURER_NAME: self._manufacturer_name, ATTR_PRODUCT_NAME: self._product_name, 'old_entity_id': self.old_entity_id, + 'new_entity_id': self.new_entity_id, } attrs.update(self._attributes) if self.battery_level is not None: diff --git a/tests/components/zwave/test_node_entity.py b/tests/components/zwave/test_node_entity.py index 673946241edfcd..b7148dd982ee85 100644 --- a/tests/components/zwave/test_node_entity.py +++ b/tests/components/zwave/test_node_entity.py @@ -173,6 +173,7 @@ def test_node_changed(self): 'node_name': 'Mock Node', 'manufacturer_name': 'Test Manufacturer', 'old_entity_id': 'zwave.mock_node_567', + 'new_entity_id': 'zwave.mock_node', 'product_name': 'Test Product'}, self.entity.device_state_attributes) @@ -232,6 +233,7 @@ def test_node_changed(self): 'node_name': 'Mock Node', 'manufacturer_name': 'Test Manufacturer', 'old_entity_id': 'zwave.mock_node_567', + 'new_entity_id': 'zwave.mock_node', 'product_name': 'Test Product', 'query_stage': 'Dynamic', 'is_awake': True, From 0c92558b58f002db84e8e269ccd01786a7d888f1 Mon Sep 17 00:00:00 2001 From: Adam Mills Date: Fri, 16 Jun 2017 10:13:07 -0400 Subject: [PATCH 9/9] Just can't win with this one --- tests/components/zwave/test_init.py | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/tests/components/zwave/test_init.py b/tests/components/zwave/test_init.py index 38b1ce9fa8fe0c..0baa299c27c269 100644 --- a/tests/components/zwave/test_init.py +++ b/tests/components/zwave/test_init.py @@ -442,6 +442,11 @@ def tearDown(self): # pylint: disable=invalid-name @patch.object(zwave, 'discovery') def test_entity_discovery(self, discovery, get_platform): """Test the creation of a new entity.""" + mock_platform = MagicMock() + get_platform.return_value = mock_platform + mock_device = MagicMock() + mock_device.name = 'test_device' + mock_platform.get_device.return_value = mock_device values = zwave.ZWaveDeviceEntityValues( hass=self.hass, schema=self.mock_schema, @@ -501,6 +506,11 @@ def test_entity_discovery(self, discovery, get_platform): @patch.object(zwave, 'discovery') def test_entity_existing_values(self, discovery, get_platform): """Test the loading of already discovered values.""" + mock_platform = MagicMock() + get_platform.return_value = mock_platform + mock_device = MagicMock() + mock_device.name = 'test_device' + mock_platform.get_device.return_value = mock_device self.node.values = { self.primary.value_id: self.primary, self.secondary.value_id: self.secondary, @@ -564,6 +574,11 @@ def test_node_schema_mismatch(self, discovery, get_platform): @patch.object(zwave, 'discovery') def test_entity_workaround_component(self, discovery, get_platform): """Test ignore workaround.""" + mock_platform = MagicMock() + get_platform.return_value = mock_platform + mock_device = MagicMock() + mock_device.name = 'test_device' + mock_platform.get_device.return_value = mock_device self.node.manufacturer_id = '010f' self.node.product_type = '0b00' self.primary.command_class = const.COMMAND_CLASS_SENSOR_ALARM @@ -671,6 +686,11 @@ def test_entity_platform_ignore(self, discovery, get_platform): @patch.object(zwave, 'discovery') def test_config_polling_intensity(self, discovery, get_platform): """Test polling intensity.""" + mock_platform = MagicMock() + get_platform.return_value = mock_platform + mock_device = MagicMock() + mock_device.name = 'test_device' + mock_platform.get_device.return_value = mock_device self.node.values = { self.primary.value_id: self.primary, self.secondary.value_id: self.secondary,