Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
79 changes: 53 additions & 26 deletions homeassistant/components/zwave/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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, is_node_parsed)

REQUIREMENTS = ['pydispatcher==2.0.5', 'python_openzwave==0.4.3']

Expand Down Expand Up @@ -313,30 +314,22 @@ def _add_node_to_component():
_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)
@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, "
"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."""
Expand Down Expand Up @@ -839,13 +832,35 @@ 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",
device.name, self._node.node_id, 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, "
"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):
Expand All @@ -862,8 +877,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(
Expand Down Expand Up @@ -894,6 +908,11 @@ 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._unique_id:
self.try_remove_and_add()

if self.values.power:
self.power_consumption = round(
Expand Down Expand Up @@ -940,3 +959,11 @@ 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 (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
16 changes: 14 additions & 2 deletions homeassistant/components/zwave/node_entity.py
Original file line number Diff line number Diff line change
Expand Up @@ -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__)

Expand Down Expand Up @@ -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."""
Expand Down Expand Up @@ -151,6 +160,9 @@ def node_changed(self):

if not self._unique_id:
self._unique_id = self._compute_unique_id()
if self._unique_id:
# Node info parsed. Remove and re-add
self.try_remove_and_add()

self.maybe_schedule_update()

Expand Down Expand Up @@ -243,6 +255,6 @@ def device_state_attributes(self):
return attrs

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
23 changes: 23 additions & 0 deletions homeassistant/components/zwave/util.py
Original file line number Diff line number Diff line change
@@ -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__)
Expand Down Expand Up @@ -67,3 +70,23 @@ 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)


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
2 changes: 1 addition & 1 deletion tests/components/zwave/test_init.py
Original file line number Diff line number Diff line change
Expand Up @@ -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 = []

Expand Down