mirror of
https://github.com/home-assistant/core.git
synced 2025-07-16 17:57:11 +00:00
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:
parent
351e8921fa
commit
f72d568374
@ -297,15 +297,46 @@ def setup(hass, config):
|
|||||||
def node_added(node):
|
def node_added(node):
|
||||||
"""Handle a new node on the network."""
|
"""Handle a new node on the network."""
|
||||||
entity = ZWaveNodeEntity(node, network)
|
entity = ZWaveNodeEntity(node, network)
|
||||||
name = node_name(node)
|
|
||||||
generated_id = generate_entity_id(DOMAIN + '.{}', name, [])
|
def _add_node_to_component():
|
||||||
node_config = device_config.get(generated_id)
|
name = node_name(node)
|
||||||
if node_config.get(CONF_IGNORED):
|
generated_id = generate_entity_id(DOMAIN + '.{}', name, [])
|
||||||
_LOGGER.info(
|
node_config = device_config.get(generated_id)
|
||||||
"Ignoring node entity %s due to device settings",
|
if node_config.get(CONF_IGNORED):
|
||||||
generated_id)
|
_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
|
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():
|
def network_ready():
|
||||||
"""Handle the query of all awake nodes."""
|
"""Handle the query of all awake nodes."""
|
||||||
|
@ -20,6 +20,7 @@ ATTR_POLL_INTENSITY = "poll_intensity"
|
|||||||
ATTR_VALUE_INDEX = "value_index"
|
ATTR_VALUE_INDEX = "value_index"
|
||||||
ATTR_VALUE_INSTANCE = "value_instance"
|
ATTR_VALUE_INSTANCE = "value_instance"
|
||||||
NETWORK_READY_WAIT_SECS = 300
|
NETWORK_READY_WAIT_SECS = 300
|
||||||
|
NODE_READY_WAIT_SECS = 30
|
||||||
|
|
||||||
DISCOVERY_DEVICE = 'device'
|
DISCOVERY_DEVICE = 'device'
|
||||||
|
|
||||||
|
@ -81,6 +81,7 @@ class ZWaveNodeEntity(ZWaveBaseEntity):
|
|||||||
self._name = node_name(self.node)
|
self._name = node_name(self.node)
|
||||||
self._product_name = node.product_name
|
self._product_name = node.product_name
|
||||||
self._manufacturer_name = node.manufacturer_name
|
self._manufacturer_name = node.manufacturer_name
|
||||||
|
self._unique_id = self._compute_unique_id()
|
||||||
self._attributes = {}
|
self._attributes = {}
|
||||||
self.wakeup_interval = None
|
self.wakeup_interval = None
|
||||||
self.location = None
|
self.location = None
|
||||||
@ -95,6 +96,11 @@ class ZWaveNodeEntity(ZWaveBaseEntity):
|
|||||||
dispatcher.connect(
|
dispatcher.connect(
|
||||||
self.network_scene_activated, ZWaveNetwork.SIGNAL_SCENE_EVENT)
|
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):
|
def network_node_changed(self, node=None, value=None, args=None):
|
||||||
"""Handle a changed node on the network."""
|
"""Handle a changed node on the network."""
|
||||||
if node and node.node_id != self.node_id:
|
if node and node.node_id != self.node_id:
|
||||||
@ -138,8 +144,14 @@ class ZWaveNodeEntity(ZWaveBaseEntity):
|
|||||||
self.wakeup_interval = None
|
self.wakeup_interval = None
|
||||||
|
|
||||||
self.battery_level = self.node.get_battery_level()
|
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
|
self._attributes = attributes
|
||||||
|
|
||||||
|
if not self._unique_id:
|
||||||
|
self._unique_id = self._compute_unique_id()
|
||||||
|
|
||||||
self.maybe_schedule_update()
|
self.maybe_schedule_update()
|
||||||
|
|
||||||
def network_node_event(self, node, value):
|
def network_node_event(self, node, value):
|
||||||
@ -229,3 +241,8 @@ class ZWaveNodeEntity(ZWaveBaseEntity):
|
|||||||
attrs[ATTR_WAKEUP] = self.wakeup_interval
|
attrs[ATTR_WAKEUP] = self.wakeup_interval
|
||||||
|
|
||||||
return attrs
|
return attrs
|
||||||
|
|
||||||
|
def _compute_unique_id(self):
|
||||||
|
if self._manufacturer_name and self._product_name:
|
||||||
|
return 'node-{}'.format(self.node_id)
|
||||||
|
return None
|
||||||
|
@ -224,6 +224,47 @@ def test_node_discovery(hass, mock_openzwave):
|
|||||||
assert hass.states.get('zwave.mock_node').state is 'unknown'
|
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
|
@asyncio.coroutine
|
||||||
def test_node_ignored(hass, mock_openzwave):
|
def test_node_ignored(hass, mock_openzwave):
|
||||||
"""Test discovery of a node."""
|
"""Test discovery of a node."""
|
||||||
|
@ -182,8 +182,6 @@ class TestZWaveNodeEntity(unittest.TestCase):
|
|||||||
query_stage='Dynamic', is_awake=True, is_ready=False,
|
query_stage='Dynamic', is_awake=True, is_ready=False,
|
||||||
is_failed=False, is_info_received=True, max_baud_rate=40000,
|
is_failed=False, is_info_received=True, max_baud_rate=40000,
|
||||||
is_zwave_plus=False, capabilities=[], neighbors=[], location=None)
|
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.entity = node_entity.ZWaveNodeEntity(self.node,
|
||||||
self.zwave_network)
|
self.zwave_network)
|
||||||
|
|
||||||
@ -357,3 +355,14 @@ class TestZWaveNodeEntity(unittest.TestCase):
|
|||||||
def test_not_polled(self):
|
def test_not_polled(self):
|
||||||
"""Test should_poll property."""
|
"""Test should_poll property."""
|
||||||
self.assertFalse(self.entity.should_poll)
|
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)
|
||||||
|
@ -119,6 +119,8 @@ class MockNode(MagicMock):
|
|||||||
product_type='678',
|
product_type='678',
|
||||||
command_classes=None,
|
command_classes=None,
|
||||||
can_wake_up_value=True,
|
can_wake_up_value=True,
|
||||||
|
manufacturer_name='Test Manufacturer',
|
||||||
|
product_name='Test Product',
|
||||||
network=None,
|
network=None,
|
||||||
**kwargs):
|
**kwargs):
|
||||||
"""Initialize a Z-Wave mock node."""
|
"""Initialize a Z-Wave mock node."""
|
||||||
@ -128,6 +130,8 @@ class MockNode(MagicMock):
|
|||||||
self.manufacturer_id = manufacturer_id
|
self.manufacturer_id = manufacturer_id
|
||||||
self.product_id = product_id
|
self.product_id = product_id
|
||||||
self.product_type = product_type
|
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.can_wake_up_value = can_wake_up_value
|
||||||
self._command_classes = command_classes or []
|
self._command_classes = command_classes or []
|
||||||
if network is not None:
|
if network is not None:
|
||||||
|
Loading…
x
Reference in New Issue
Block a user