Add unique_id to zwave node entity (#14201)

* Add unique_id to zwave node entity

* Wait 30s before adding zwave node if its unique_id is not ready

* Use only node_id in unique_id. Update name, manufacturer, and product attributes on node update.
This commit is contained in:
Andrey 2018-05-02 23:10:26 +03:00 committed by Paulus Schoutsen
parent 351e8921fa
commit f72d568374
6 changed files with 113 additions and 10 deletions

View file

@ -297,15 +297,46 @@ def setup(hass, config):
def node_added(node):
"""Handle a new node on the network."""
entity = ZWaveNodeEntity(node, network)
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",
generated_id)
def _add_node_to_component():
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",
generated_id)
return
component.add_entities([entity])
if entity.unique_id:
_add_node_to_component()
return
component.add_entities([entity])
async def _check_node_ready():
"""Wait for node to be parsed."""
start_time = dt_util.utcnow()
while True:
waited = int((dt_util.utcnow()-start_time).total_seconds())
if entity.unique_id:
_LOGGER.info("Z-Wave node %d ready after %d seconds",
entity.node_id, waited)
break
elif waited >= const.NODE_READY_WAIT_SECS:
# Wait up to NODE_READY_WAIT_SECS seconds for the Z-Wave
# node to be ready.
_LOGGER.warning(
"Z-Wave node %d not ready after %d seconds, "
"continuing anyway",
entity.node_id, waited)
break
else:
await asyncio.sleep(1, loop=hass.loop)
hass.async_add_job(_add_node_to_component)
hass.add_job(_check_node_ready)
def network_ready():
"""Handle the query of all awake nodes."""

View file

@ -20,6 +20,7 @@ ATTR_POLL_INTENSITY = "poll_intensity"
ATTR_VALUE_INDEX = "value_index"
ATTR_VALUE_INSTANCE = "value_instance"
NETWORK_READY_WAIT_SECS = 300
NODE_READY_WAIT_SECS = 30
DISCOVERY_DEVICE = 'device'

View file

@ -81,6 +81,7 @@ class ZWaveNodeEntity(ZWaveBaseEntity):
self._name = node_name(self.node)
self._product_name = node.product_name
self._manufacturer_name = node.manufacturer_name
self._unique_id = self._compute_unique_id()
self._attributes = {}
self.wakeup_interval = None
self.location = None
@ -95,6 +96,11 @@ class ZWaveNodeEntity(ZWaveBaseEntity):
dispatcher.connect(
self.network_scene_activated, ZWaveNetwork.SIGNAL_SCENE_EVENT)
@property
def unique_id(self):
"""Unique ID of Z-wave node."""
return self._unique_id
def network_node_changed(self, node=None, value=None, args=None):
"""Handle a changed node on the network."""
if node and node.node_id != self.node_id:
@ -138,8 +144,14 @@ class ZWaveNodeEntity(ZWaveBaseEntity):
self.wakeup_interval = None
self.battery_level = self.node.get_battery_level()
self._product_name = self.node.product_name
self._manufacturer_name = self.node.manufacturer_name
self._name = node_name(self.node)
self._attributes = attributes
if not self._unique_id:
self._unique_id = self._compute_unique_id()
self.maybe_schedule_update()
def network_node_event(self, node, value):
@ -229,3 +241,8 @@ class ZWaveNodeEntity(ZWaveBaseEntity):
attrs[ATTR_WAKEUP] = self.wakeup_interval
return attrs
def _compute_unique_id(self):
if self._manufacturer_name and self._product_name:
return 'node-{}'.format(self.node_id)
return None

View file

@ -224,6 +224,47 @@ def test_node_discovery(hass, mock_openzwave):
assert hass.states.get('zwave.mock_node').state is 'unknown'
async def test_unparsed_node_discovery(hass, mock_openzwave):
"""Test discovery of a node."""
mock_receivers = []
def mock_connect(receiver, signal, *args, **kwargs):
if signal == MockNetwork.SIGNAL_NODE_ADDED:
mock_receivers.append(receiver)
with patch('pydispatch.dispatcher.connect', new=mock_connect):
await async_setup_component(hass, 'zwave', {'zwave': {}})
assert len(mock_receivers) == 1
node = MockNode(node_id=14, manufacturer_name=None)
sleeps = []
def utcnow():
return datetime.fromtimestamp(len(sleeps))
asyncio_sleep = asyncio.sleep
async def sleep(duration, loop):
if duration > 0:
sleeps.append(duration)
await asyncio_sleep(0, loop=loop)
with patch('homeassistant.components.zwave.dt_util.utcnow', new=utcnow):
with patch('asyncio.sleep', new=sleep):
with patch.object(zwave, '_LOGGER') as mock_logger:
hass.async_add_job(mock_receivers[0], node)
await hass.async_block_till_done()
assert len(sleeps) == const.NODE_READY_WAIT_SECS
assert mock_logger.warning.called
assert len(mock_logger.warning.mock_calls) == 1
assert mock_logger.warning.mock_calls[0][1][1:] == \
(14, const.NODE_READY_WAIT_SECS)
assert hass.states.get('zwave.mock_node').state is 'unknown'
@asyncio.coroutine
def test_node_ignored(hass, mock_openzwave):
"""Test discovery of a node."""

View file

@ -182,8 +182,6 @@ class TestZWaveNodeEntity(unittest.TestCase):
query_stage='Dynamic', is_awake=True, is_ready=False,
is_failed=False, is_info_received=True, max_baud_rate=40000,
is_zwave_plus=False, capabilities=[], neighbors=[], location=None)
self.node.manufacturer_name = 'Test Manufacturer'
self.node.product_name = 'Test Product'
self.entity = node_entity.ZWaveNodeEntity(self.node,
self.zwave_network)
@ -357,3 +355,14 @@ class TestZWaveNodeEntity(unittest.TestCase):
def test_not_polled(self):
"""Test should_poll property."""
self.assertFalse(self.entity.should_poll)
def test_unique_id(self):
"""Test unique_id."""
self.assertEqual('node-567', self.entity.unique_id)
def test_unique_id_missing_data(self):
"""Test unique_id."""
self.node.manufacturer_name = None
entity = node_entity.ZWaveNodeEntity(self.node, self.zwave_network)
self.assertIsNone(entity.unique_id)

View file

@ -119,6 +119,8 @@ class MockNode(MagicMock):
product_type='678',
command_classes=None,
can_wake_up_value=True,
manufacturer_name='Test Manufacturer',
product_name='Test Product',
network=None,
**kwargs):
"""Initialize a Z-Wave mock node."""
@ -128,6 +130,8 @@ class MockNode(MagicMock):
self.manufacturer_id = manufacturer_id
self.product_id = product_id
self.product_type = product_type
self.manufacturer_name = manufacturer_name
self.product_name = product_name
self.can_wake_up_value = can_wake_up_value
self._command_classes = command_classes or []
if network is not None: