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
|
import voluptuous as vol
|
||||||
|
|
||||||
from homeassistant.core import CoreState
|
from homeassistant.core import callback, CoreState
|
||||||
from homeassistant.loader import get_platform
|
from homeassistant.loader import get_platform
|
||||||
from homeassistant.helpers import discovery
|
from homeassistant.helpers import discovery
|
||||||
from homeassistant.helpers.entity import generate_entity_id
|
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 .node_entity import ZWaveBaseEntity, ZWaveNodeEntity
|
||||||
from . import workaround
|
from . import workaround
|
||||||
from .discovery_schemas import DISCOVERY_SCHEMAS
|
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']
|
REQUIREMENTS = ['pydispatcher==2.0.5', 'python_openzwave==0.4.3']
|
||||||
|
|
||||||
@ -313,30 +314,22 @@ def setup(hass, config):
|
|||||||
_add_node_to_component()
|
_add_node_to_component()
|
||||||
return
|
return
|
||||||
|
|
||||||
async def _check_node_ready():
|
@callback
|
||||||
"""Wait for node to be parsed."""
|
def _on_ready(sec):
|
||||||
start_time = dt_util.utcnow()
|
_LOGGER.info("Z-Wave node %d ready after %d seconds",
|
||||||
while True:
|
entity.node_id, sec)
|
||||||
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.async_add_job(_add_node_to_component)
|
||||||
|
|
||||||
hass.add_job(_check_node_ready)
|
@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_has_unique_id, entity, _on_ready, _on_timeout,
|
||||||
|
hass.loop)
|
||||||
|
|
||||||
def network_ready():
|
def network_ready():
|
||||||
"""Handle the query of all awake nodes."""
|
"""Handle the query of all awake nodes."""
|
||||||
@ -839,13 +832,35 @@ class ZWaveDeviceEntityValues():
|
|||||||
|
|
||||||
dict_id = id(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):
|
async def discover_device(component, device, dict_id):
|
||||||
"""Put device in a dictionary and call discovery on it."""
|
"""Put device in a dictionary and call discovery on it."""
|
||||||
self._hass.data[DATA_DEVICES][dict_id] = device
|
self._hass.data[DATA_DEVICES][dict_id] = device
|
||||||
await discovery.async_load_platform(
|
await discovery.async_load_platform(
|
||||||
self._hass, component, DOMAIN,
|
self._hass, component, DOMAIN,
|
||||||
{const.DISCOVERY_DEVICE: dict_id}, self._zwave_config)
|
{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):
|
class ZWaveDeviceEntity(ZWaveBaseEntity):
|
||||||
@ -862,8 +877,7 @@ class ZWaveDeviceEntity(ZWaveBaseEntity):
|
|||||||
self.values.primary.set_change_verified(False)
|
self.values.primary.set_change_verified(False)
|
||||||
|
|
||||||
self._name = _value_name(self.values.primary)
|
self._name = _value_name(self.values.primary)
|
||||||
self._unique_id = "{}-{}".format(self.node.node_id,
|
self._unique_id = self._compute_unique_id()
|
||||||
self.values.primary.object_id)
|
|
||||||
self._update_attributes()
|
self._update_attributes()
|
||||||
|
|
||||||
dispatcher.connect(
|
dispatcher.connect(
|
||||||
@ -894,6 +908,11 @@ class ZWaveDeviceEntity(ZWaveBaseEntity):
|
|||||||
def _update_attributes(self):
|
def _update_attributes(self):
|
||||||
"""Update the node attributes. May only be used inside callback."""
|
"""Update the node attributes. May only be used inside callback."""
|
||||||
self.node_id = self.node.node_id
|
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:
|
if self.values.power:
|
||||||
self.power_consumption = round(
|
self.power_consumption = round(
|
||||||
@ -940,3 +959,11 @@ class ZWaveDeviceEntity(ZWaveBaseEntity):
|
|||||||
for value in self.values:
|
for value in self.values:
|
||||||
if value is not None:
|
if value is not None:
|
||||||
self.node.refresh_value(value.value_id)
|
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_NODE_ID, COMMAND_CLASS_WAKE_UP, ATTR_SCENE_ID, ATTR_SCENE_DATA,
|
||||||
ATTR_BASIC_LEVEL, EVENT_NODE_EVENT, EVENT_SCENE_ACTIVATED,
|
ATTR_BASIC_LEVEL, EVENT_NODE_EVENT, EVENT_SCENE_ACTIVATED,
|
||||||
COMMAND_CLASS_CENTRAL_SCENE)
|
COMMAND_CLASS_CENTRAL_SCENE)
|
||||||
from .util import node_name
|
from .util import node_name, is_node_parsed
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
@ -65,6 +65,15 @@ class ZWaveBaseEntity(Entity):
|
|||||||
self._update_scheduled = True
|
self._update_scheduled = True
|
||||||
self.hass.loop.call_later(0.1, do_update)
|
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):
|
class ZWaveNodeEntity(ZWaveBaseEntity):
|
||||||
"""Representation of a Z-Wave node."""
|
"""Representation of a Z-Wave node."""
|
||||||
@ -151,6 +160,9 @@ class ZWaveNodeEntity(ZWaveBaseEntity):
|
|||||||
|
|
||||||
if not self._unique_id:
|
if not self._unique_id:
|
||||||
self._unique_id = self._compute_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()
|
self.maybe_schedule_update()
|
||||||
|
|
||||||
@ -243,6 +255,6 @@ class ZWaveNodeEntity(ZWaveBaseEntity):
|
|||||||
return attrs
|
return attrs
|
||||||
|
|
||||||
def _compute_unique_id(self):
|
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 'node-{}'.format(self.node_id)
|
||||||
return None
|
return None
|
||||||
|
@ -1,6 +1,9 @@
|
|||||||
"""Zwave util methods."""
|
"""Zwave util methods."""
|
||||||
|
import asyncio
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
|
import homeassistant.util.dt as dt_util
|
||||||
|
|
||||||
from . import const
|
from . import const
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
@ -67,3 +70,23 @@ def node_name(node):
|
|||||||
"""Return the name of the node."""
|
"""Return the name of the node."""
|
||||||
return node.name or '{} {}'.format(
|
return node.name or '{} {}'.format(
|
||||||
node.manufacturer_name, node.product_name)
|
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
|
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 = []
|
sleeps = []
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user