mirror of
https://github.com/home-assistant/core.git
synced 2025-07-28 15:47:12 +00:00
Make sure zwave nodes/entities enter the registry is proper state. (#14251)
* When zwave node's info is parsed remove it and re-add back. * Delay value entity if not ready * If node is ready consider it parsed even if manufacturer/product are missing. * Add annotations
This commit is contained in:
parent
e12994a0cd
commit
10505d542a
@ -11,7 +11,7 @@ from pprint import pprint
|
||||
|
||||
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
|
||||
@ -31,7 +31,8 @@ from .const import DOMAIN, DATA_DEVICES, DATA_NETWORK, DATA_ENTITY_VALUES
|
||||
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']
|
||||
|
||||
@ -313,30 +314,22 @@ def setup(hass, config):
|
||||
_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:
|
||||
@callback
|
||||
def _on_ready(sec):
|
||||
_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.
|
||||
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, waited)
|
||||
break
|
||||
else:
|
||||
await asyncio.sleep(1, loop=hass.loop)
|
||||
|
||||
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."""
|
||||
@ -839,13 +832,35 @@ class ZWaveDeviceEntityValues():
|
||||
|
||||
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)
|
||||
|
||||
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):
|
||||
@ -862,8 +877,7 @@ class ZWaveDeviceEntity(ZWaveBaseEntity):
|
||||
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(
|
||||
@ -894,6 +908,11 @@ class ZWaveDeviceEntity(ZWaveBaseEntity):
|
||||
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(
|
||||
@ -940,3 +959,11 @@ class ZWaveDeviceEntity(ZWaveBaseEntity):
|
||||
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
|
||||
|
@ -9,7 +9,7 @@ from .const import (
|
||||
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 @@ class ZWaveBaseEntity(Entity):
|
||||
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."""
|
||||
@ -151,6 +160,9 @@ class ZWaveNodeEntity(ZWaveBaseEntity):
|
||||
|
||||
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()
|
||||
|
||||
@ -243,6 +255,6 @@ class ZWaveNodeEntity(ZWaveBaseEntity):
|
||||
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
|
||||
|
@ -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,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
|
||||
|
@ -237,7 +237,7 @@ async def test_unparsed_node_discovery(hass, mock_openzwave):
|
||||
|
||||
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 = []
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user