From 5740f3d6949ef12a12f086f42611f5438b9f41c4 Mon Sep 17 00:00:00 2001 From: andrey-git Date: Thu, 3 May 2018 00:55:37 +0300 Subject: [PATCH 1/4] When zwave node's info is parsed remove it and re-add back. --- homeassistant/components/zwave/__init__.py | 2 ++ homeassistant/components/zwave/node_entity.py | 10 ++++++++++ 2 files changed, 12 insertions(+) diff --git a/homeassistant/components/zwave/__init__.py b/homeassistant/components/zwave/__init__.py index 01b17023c12519..406c671f852f2f 100644 --- a/homeassistant/components/zwave/__init__.py +++ b/homeassistant/components/zwave/__init__.py @@ -307,6 +307,8 @@ def _add_node_to_component(): "Ignoring node entity %s due to device settings", generated_id) return + if not entity.unique_id: + entity.component = component component.add_entities([entity]) if entity.unique_id: diff --git a/homeassistant/components/zwave/node_entity.py b/homeassistant/components/zwave/node_entity.py index bcddcb0b800547..8d4282edf8eee6 100644 --- a/homeassistant/components/zwave/node_entity.py +++ b/homeassistant/components/zwave/node_entity.py @@ -75,6 +75,7 @@ def __init__(self, node, network): super().__init__() from openzwave.network import ZWaveNetwork from pydispatch import dispatcher + self.component = None self._network = network self.node = node self.node_id = self.node.node_id @@ -151,6 +152,15 @@ def node_changed(self): if not self._unique_id: self._unique_id = self._compute_unique_id() + if self._unique_id and self.component and self.hass: + # Node info parsed. Remove and re-add + async def remove_and_add(): + """Remove this entity and add it back.""" + await self.async_remove() + self.entity_id = None + await self.component.async_add_entities([self]) + self.component = None + self.hass.add_job(remove_and_add) self.maybe_schedule_update() From a9df54c5c21d994861a18f09e916e1dd8bb0b067 Mon Sep 17 00:00:00 2001 From: andrey-git Date: Sun, 6 May 2018 21:52:44 +0300 Subject: [PATCH 2/4] Delay value entity if not ready --- homeassistant/components/zwave/__init__.py | 70 ++++++++++++------- homeassistant/components/zwave/node_entity.py | 19 ++--- homeassistant/components/zwave/util.py | 18 +++++ 3 files changed, 72 insertions(+), 35 deletions(-) diff --git a/homeassistant/components/zwave/__init__.py b/homeassistant/components/zwave/__init__.py index 406c671f852f2f..96d11a2a0e5944 100644 --- a/homeassistant/components/zwave/__init__.py +++ b/homeassistant/components/zwave/__init__.py @@ -31,7 +31,8 @@ from .node_entity import ZWaveBaseEntity, ZWaveNodeEntity from . import workaround from .discovery_schemas import DISCOVERY_SCHEMAS -from .util import check_node_schema, check_value_schema, node_name +from .util import (check_node_schema, check_value_schema, node_name, + check_has_unique_id) REQUIREMENTS = ['pydispatcher==2.0.5', 'python_openzwave==0.4.3'] @@ -307,38 +308,26 @@ def _add_node_to_component(): "Ignoring node entity %s due to device settings", generated_id) return - if not entity.unique_id: - entity.component = component component.add_entities([entity]) if entity.unique_id: _add_node_to_component() return - 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) + def _on_ready(sec): + _LOGGER.info("Z-Wave node %d ready after %d seconds", + entity.node_id, sec) + hass.async_add_job(_add_node_to_component) + def _on_timeout(sec): + _LOGGER.warning( + "Z-Wave node %d not ready after %d seconds, " + "continuing anyway", + entity.node_id, sec) hass.async_add_job(_add_node_to_component) - hass.add_job(_check_node_ready) + hass.add_job(check_has_unique_id, entity, _on_ready, _on_timeout, + hass.loop) def network_ready(): """Handle the query of all awake nodes.""" @@ -841,13 +830,33 @@ def _check_entity_ready(self): dict_id = id(self) + def _on_ready(sec): + _LOGGER.info( + "Z-Wave entity %s (node_id: %d) ready after %d seconds", + device.name, self._node.node_id, sec) + self._hass.async_add_job(discover_device, component, device, + dict_id) + + def _on_timeout(sec): + _LOGGER.warning( + "Z-Wave entity %s (node_id: %d) not ready after %d seconds, " + "continuing anyway", + device.name, self._node.node_id, sec) + self._hass.async_add_job(discover_device, component, device, + dict_id) + async def discover_device(component, device, dict_id): """Put device in a dictionary and call discovery on it.""" self._hass.data[DATA_DEVICES][dict_id] = device await discovery.async_load_platform( self._hass, component, DOMAIN, {const.DISCOVERY_DEVICE: dict_id}, self._zwave_config) - self._hass.add_job(discover_device, component, device, dict_id) + + if device.unique_id: + self._hass.add_job(discover_device, component, device, dict_id) + else: + self._hass.add_job(check_has_unique_id, device, _on_ready, + _on_timeout, self._hass.loop) class ZWaveDeviceEntity(ZWaveBaseEntity): @@ -864,8 +873,7 @@ def __init__(self, values, domain): self.values.primary.set_change_verified(False) self._name = _value_name(self.values.primary) - self._unique_id = "{}-{}".format(self.node.node_id, - self.values.primary.object_id) + self._unique_id = self._compute_unique_id() self._update_attributes() dispatcher.connect( @@ -896,6 +904,9 @@ async def async_added_to_hass(self): def _update_attributes(self): """Update the node attributes. May only be used inside callback.""" self.node_id = self.node.node_id + self._name = _value_name(self.values.primary) + if not self._unique_id: + self._unique_id = self._compute_unique_id() if self.values.power: self.power_consumption = round( @@ -942,3 +953,8 @@ def refresh_from_network(self): for value in self.values: if value is not None: self.node.refresh_value(value.value_id) + + def _compute_unique_id(self): + if not self.node.manufacturer_name or not self.node.product_name: + return None + return "{}-{}".format(self.node.node_id, self.values.primary.object_id) diff --git a/homeassistant/components/zwave/node_entity.py b/homeassistant/components/zwave/node_entity.py index 8d4282edf8eee6..47404d8853923d 100644 --- a/homeassistant/components/zwave/node_entity.py +++ b/homeassistant/components/zwave/node_entity.py @@ -152,15 +152,9 @@ def node_changed(self): if not self._unique_id: self._unique_id = self._compute_unique_id() - if self._unique_id and self.component and self.hass: + if self._unique_id: # Node info parsed. Remove and re-add - async def remove_and_add(): - """Remove this entity and add it back.""" - await self.async_remove() - self.entity_id = None - await self.component.async_add_entities([self]) - self.component = None - self.hass.add_job(remove_and_add) + self.try_remove_and_add() self.maybe_schedule_update() @@ -252,6 +246,15 @@ def device_state_attributes(self): return attrs + def try_remove_and_add(self): + """Remove this entity and add it back.""" + async def _async_remove_and_add(): + await self.async_remove() + self.entity_id = None + await self.platform.async_add_entities([self]) + if self.hass and self.platform: + self.hass.add_job(_async_remove_and_add) + def _compute_unique_id(self): if self._manufacturer_name and self._product_name: return 'node-{}'.format(self.node_id) diff --git a/homeassistant/components/zwave/util.py b/homeassistant/components/zwave/util.py index 8c74b731ad6b04..5e6870612fb8e2 100644 --- a/homeassistant/components/zwave/util.py +++ b/homeassistant/components/zwave/util.py @@ -1,6 +1,9 @@ """Zwave util methods.""" +import asyncio import logging +import homeassistant.util.dt as dt_util + from . import const _LOGGER = logging.getLogger(__name__) @@ -67,3 +70,18 @@ def node_name(node): """Return the name of the node.""" return node.name or '{} {}'.format( node.manufacturer_name, node.product_name) + + +async def check_has_unique_id(entity, ready_callback, timeout_callback, loop): + """Wait for entity to have unique_id.""" + start_time = dt_util.utcnow() + while True: + waited = int((dt_util.utcnow()-start_time).total_seconds()) + if entity.unique_id: + ready_callback(waited) + return + elif waited >= const.NODE_READY_WAIT_SECS: + # Wait up to NODE_READY_WAIT_SECS seconds for unique_id to appear. + timeout_callback(waited) + return + await asyncio.sleep(1, loop=loop) From 763879bf86944dd93aae761737e9ed5dce0fd4aa Mon Sep 17 00:00:00 2001 From: andrey-git Date: Mon, 7 May 2018 22:08:15 +0300 Subject: [PATCH 3/4] If node is ready consider it parsed even if manufacturer/product are missing. --- homeassistant/components/zwave/__init__.py | 13 +++++++---- homeassistant/components/zwave/node_entity.py | 22 +++++++++---------- homeassistant/components/zwave/util.py | 5 +++++ tests/components/zwave/test_init.py | 2 +- 4 files changed, 26 insertions(+), 16 deletions(-) diff --git a/homeassistant/components/zwave/__init__.py b/homeassistant/components/zwave/__init__.py index 96d11a2a0e5944..2e4035c72a6e07 100644 --- a/homeassistant/components/zwave/__init__.py +++ b/homeassistant/components/zwave/__init__.py @@ -32,7 +32,7 @@ from . import workaround from .discovery_schemas import DISCOVERY_SCHEMAS from .util import (check_node_schema, check_value_schema, node_name, - check_has_unique_id) + check_has_unique_id, is_node_parsed) REQUIREMENTS = ['pydispatcher==2.0.5', 'python_openzwave==0.4.3'] @@ -907,6 +907,8 @@ def _update_attributes(self): self._name = _value_name(self.values.primary) if not self._unique_id: self._unique_id = self._compute_unique_id() + if self._unique_id: + self.try_remove_and_add() if self.values.power: self.power_consumption = round( @@ -955,6 +957,9 @@ def refresh_from_network(self): self.node.refresh_value(value.value_id) def _compute_unique_id(self): - if not self.node.manufacturer_name or not self.node.product_name: - return None - return "{}-{}".format(self.node.node_id, self.values.primary.object_id) + if (is_node_parsed(self.node) and + self.values.primary.label != "Unknown") or \ + self.node.is_ready: + return "{}-{}".format(self.node.node_id, + self.values.primary.object_id) + return None diff --git a/homeassistant/components/zwave/node_entity.py b/homeassistant/components/zwave/node_entity.py index 47404d8853923d..095fe61d05dd30 100644 --- a/homeassistant/components/zwave/node_entity.py +++ b/homeassistant/components/zwave/node_entity.py @@ -9,7 +9,7 @@ ATTR_NODE_ID, COMMAND_CLASS_WAKE_UP, ATTR_SCENE_ID, ATTR_SCENE_DATA, ATTR_BASIC_LEVEL, EVENT_NODE_EVENT, EVENT_SCENE_ACTIVATED, COMMAND_CLASS_CENTRAL_SCENE) -from .util import node_name +from .util import node_name, is_node_parsed _LOGGER = logging.getLogger(__name__) @@ -65,6 +65,15 @@ def do_update(): self._update_scheduled = True self.hass.loop.call_later(0.1, do_update) + def try_remove_and_add(self): + """Remove this entity and add it back.""" + async def _async_remove_and_add(): + await self.async_remove() + self.entity_id = None + await self.platform.async_add_entities([self]) + if self.hass and self.platform: + self.hass.add_job(_async_remove_and_add) + class ZWaveNodeEntity(ZWaveBaseEntity): """Representation of a Z-Wave node.""" @@ -246,16 +255,7 @@ def device_state_attributes(self): return attrs - def try_remove_and_add(self): - """Remove this entity and add it back.""" - async def _async_remove_and_add(): - await self.async_remove() - self.entity_id = None - await self.platform.async_add_entities([self]) - if self.hass and self.platform: - self.hass.add_job(_async_remove_and_add) - def _compute_unique_id(self): - if self._manufacturer_name and self._product_name: + if is_node_parsed(self.node) or self.node.is_ready: return 'node-{}'.format(self.node_id) return None diff --git a/homeassistant/components/zwave/util.py b/homeassistant/components/zwave/util.py index 5e6870612fb8e2..1c0bb14f7e5fdc 100644 --- a/homeassistant/components/zwave/util.py +++ b/homeassistant/components/zwave/util.py @@ -85,3 +85,8 @@ async def check_has_unique_id(entity, ready_callback, timeout_callback, loop): timeout_callback(waited) return await asyncio.sleep(1, loop=loop) + + +def is_node_parsed(node): + """Check whether the node has been parsed or still waiting to be parsed.""" + return node.manufacturer_name and node.product_name diff --git a/tests/components/zwave/test_init.py b/tests/components/zwave/test_init.py index faa7357bd8af25..0eba19f03a4ab1 100644 --- a/tests/components/zwave/test_init.py +++ b/tests/components/zwave/test_init.py @@ -237,7 +237,7 @@ def mock_connect(receiver, signal, *args, **kwargs): assert len(mock_receivers) == 1 - node = MockNode(node_id=14, manufacturer_name=None) + node = MockNode(node_id=14, manufacturer_name=None, is_ready=False) sleeps = [] From fd42d073ca06d5843b535c94c5a6803c141e6cd2 Mon Sep 17 00:00:00 2001 From: andrey-git Date: Tue, 8 May 2018 20:41:50 +0300 Subject: [PATCH 4/4] Add annotations --- homeassistant/components/zwave/__init__.py | 6 +++++- homeassistant/components/zwave/node_entity.py | 1 - 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/zwave/__init__.py b/homeassistant/components/zwave/__init__.py index 2e4035c72a6e07..7562ac0ff145ec 100644 --- a/homeassistant/components/zwave/__init__.py +++ b/homeassistant/components/zwave/__init__.py @@ -11,7 +11,7 @@ import voluptuous as vol -from homeassistant.core import CoreState +from homeassistant.core import callback, CoreState from homeassistant.loader import get_platform from homeassistant.helpers import discovery from homeassistant.helpers.entity import generate_entity_id @@ -314,11 +314,13 @@ def _add_node_to_component(): _add_node_to_component() return + @callback def _on_ready(sec): _LOGGER.info("Z-Wave node %d ready after %d seconds", entity.node_id, sec) hass.async_add_job(_add_node_to_component) + @callback def _on_timeout(sec): _LOGGER.warning( "Z-Wave node %d not ready after %d seconds, " @@ -830,6 +832,7 @@ def _check_entity_ready(self): dict_id = id(self) + @callback def _on_ready(sec): _LOGGER.info( "Z-Wave entity %s (node_id: %d) ready after %d seconds", @@ -837,6 +840,7 @@ def _on_ready(sec): self._hass.async_add_job(discover_device, component, device, dict_id) + @callback def _on_timeout(sec): _LOGGER.warning( "Z-Wave entity %s (node_id: %d) not ready after %d seconds, " diff --git a/homeassistant/components/zwave/node_entity.py b/homeassistant/components/zwave/node_entity.py index 095fe61d05dd30..2c6d26802bd141 100644 --- a/homeassistant/components/zwave/node_entity.py +++ b/homeassistant/components/zwave/node_entity.py @@ -84,7 +84,6 @@ def __init__(self, node, network): super().__init__() from openzwave.network import ZWaveNetwork from pydispatch import dispatcher - self.component = None self._network = network self.node = node self.node_id = self.node.node_id