From d1228d5cf4fc4296c58c9417dde4b2ca3f3203a6 Mon Sep 17 00:00:00 2001 From: Andrey Date: Sun, 13 May 2018 00:45:36 +0300 Subject: [PATCH] Look at registry before pulling zwave config values (#14408) * Look at registry before deciding on ID for zwave values * Reuse the new function --- homeassistant/components/zwave/__init__.py | 35 ++++++++++++------ homeassistant/helpers/entity_registry.py | 16 ++++++-- tests/components/zwave/test_init.py | 43 +++++++++++++++++++++- tests/helpers/test_entity_registry.py | 10 +++++ tests/mock/zwave.py | 1 + 5 files changed, 87 insertions(+), 18 deletions(-) diff --git a/homeassistant/components/zwave/__init__.py b/homeassistant/components/zwave/__init__.py index 7562ac0ff14..a8ba5e4a6d3 100644 --- a/homeassistant/components/zwave/__init__.py +++ b/homeassistant/components/zwave/__init__.py @@ -16,10 +16,11 @@ from homeassistant.loader import get_platform from homeassistant.helpers import discovery from homeassistant.helpers.entity import generate_entity_id from homeassistant.helpers.entity_component import EntityComponent +from homeassistant.helpers.entity_registry import async_get_registry from homeassistant.const import ( ATTR_ENTITY_ID, EVENT_HOMEASSISTANT_START, EVENT_HOMEASSISTANT_STOP) from homeassistant.helpers.entity_values import EntityValues -from homeassistant.helpers.event import track_time_change +from homeassistant.helpers.event import async_track_time_change from homeassistant.util import convert import homeassistant.util.dt as dt_util import homeassistant.helpers.config_validation as cv @@ -218,7 +219,7 @@ async def async_setup_platform(hass, config, async_add_devices, # pylint: disable=R0914 -def setup(hass, config): +async def async_setup(hass, config): """Set up Z-Wave. Will automatically load components to support devices found on the network. @@ -286,7 +287,7 @@ def setup(hass, config): continue values = ZWaveDeviceEntityValues( - hass, schema, value, config, device_config) + hass, schema, value, config, device_config, registry) # We create a new list and update the reference here so that # the list can be safely iterated over in the main thread @@ -294,6 +295,7 @@ def setup(hass, config): hass.data[DATA_ENTITY_VALUES] = new_values component = EntityComponent(_LOGGER, DOMAIN, hass) + registry = await async_get_registry(hass) def node_added(node): """Handle a new node on the network.""" @@ -702,9 +704,9 @@ def setup(hass, config): # Setup autoheal if autoheal: _LOGGER.info("Z-Wave network autoheal is enabled") - track_time_change(hass, heal_network, hour=0, minute=0, second=0) + async_track_time_change(hass, heal_network, hour=0, minute=0, second=0) - hass.bus.listen_once(EVENT_HOMEASSISTANT_START, start_zwave) + hass.bus.async_listen_once(EVENT_HOMEASSISTANT_START, start_zwave) return True @@ -713,7 +715,7 @@ class ZWaveDeviceEntityValues(): """Manages entity access to the underlying zwave value objects.""" def __init__(self, hass, schema, primary_value, zwave_config, - device_config): + device_config, registry): """Initialize the values object with the passed entity schema.""" self._hass = hass self._zwave_config = zwave_config @@ -722,6 +724,7 @@ class ZWaveDeviceEntityValues(): self._values = {} self._entity = None self._workaround_ignore = False + self._registry = registry for name in self._schema[const.DISC_VALUES].keys(): self._values[name] = None @@ -794,9 +797,13 @@ class ZWaveDeviceEntityValues(): workaround_component, component) component = workaround_component - value_name = _value_name(self.primary) - generated_id = generate_entity_id(component + '.{}', value_name, []) - node_config = self._device_config.get(generated_id) + entity_id = self._registry.async_get_entity_id( + component, DOMAIN, + compute_value_unique_id(self._node, self.primary)) + if entity_id is None: + value_name = _value_name(self.primary) + entity_id = generate_entity_id(component + '.{}', value_name, []) + node_config = self._device_config.get(entity_id) # Configure node _LOGGER.debug("Adding Node_id=%s Generic_command_class=%s, " @@ -809,7 +816,7 @@ class ZWaveDeviceEntityValues(): if node_config.get(CONF_IGNORED): _LOGGER.info( - "Ignoring entity %s due to device settings", generated_id) + "Ignoring entity %s due to device settings", entity_id) # No entity will be created for this value self._workaround_ignore = True return @@ -964,6 +971,10 @@ class ZWaveDeviceEntity(ZWaveBaseEntity): 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 compute_value_unique_id(self.node, self.values.primary) return None + + +def compute_value_unique_id(node, value): + """Compute unique_id a value would get if it were to get one.""" + return "{}-{}".format(node.node_id, value.object_id) diff --git a/homeassistant/helpers/entity_registry.py b/homeassistant/helpers/entity_registry.py index b5a9c309119..35cc1015aaf 100644 --- a/homeassistant/helpers/entity_registry.py +++ b/homeassistant/helpers/entity_registry.py @@ -83,6 +83,15 @@ class EntityRegistry: """Check if an entity_id is currently registered.""" return entity_id in self.entities + @callback + def async_get_entity_id(self, domain: str, platform: str, unique_id: str): + """Check if an entity_id is currently registered.""" + for entity in self.entities.values(): + if entity.domain == domain and entity.platform == platform and \ + entity.unique_id == unique_id: + return entity.entity_id + return None + @callback def async_generate_entity_id(self, domain, suggested_object_id): """Generate an entity ID that does not conflict. @@ -99,10 +108,9 @@ class EntityRegistry: def async_get_or_create(self, domain, platform, unique_id, *, suggested_object_id=None): """Get entity. Create if it doesn't exist.""" - for entity in self.entities.values(): - if entity.domain == domain and entity.platform == platform and \ - entity.unique_id == unique_id: - return entity + entity_id = self.async_get_entity_id(domain, platform, unique_id) + if entity_id: + return self.entities[entity_id] entity_id = self.async_generate_entity_id( domain, suggested_object_id or '{}_{}'.format(platform, unique_id)) diff --git a/tests/components/zwave/test_init.py b/tests/components/zwave/test_init.py index 0eba19f03a4..a25b725e500 100644 --- a/tests/components/zwave/test_init.py +++ b/tests/components/zwave/test_init.py @@ -13,6 +13,7 @@ from homeassistant.components.binary_sensor.zwave import get_device from homeassistant.components.zwave import ( const, CONFIG_SCHEMA, CONF_DEVICE_CONFIG_GLOB, DATA_NETWORK) from homeassistant.setup import setup_component +from tests.common import mock_registry import pytest @@ -468,6 +469,7 @@ class TestZWaveDeviceEntityValues(unittest.TestCase): """Initialize values for this testcase class.""" self.hass = get_test_home_assistant() self.hass.start() + self.registry = mock_registry(self.hass) setup_component(self.hass, 'zwave', {'zwave': {}}) self.hass.block_till_done() @@ -487,7 +489,7 @@ class TestZWaveDeviceEntityValues(unittest.TestCase): const.DISC_OPTIONAL: True, }}} self.primary = MockValue( - command_class='mock_primary_class', node=self.node) + command_class='mock_primary_class', node=self.node, value_id=1000) self.secondary = MockValue( command_class='mock_secondary_class', node=self.node) self.duplicate_secondary = MockValue( @@ -521,6 +523,7 @@ class TestZWaveDeviceEntityValues(unittest.TestCase): primary_value=self.primary, zwave_config=self.zwave_config, device_config=self.device_config, + registry=self.registry ) assert values.primary is self.primary @@ -592,6 +595,7 @@ class TestZWaveDeviceEntityValues(unittest.TestCase): primary_value=self.primary, zwave_config=self.zwave_config, device_config=self.device_config, + registry=self.registry ) self.hass.block_till_done() @@ -630,6 +634,7 @@ class TestZWaveDeviceEntityValues(unittest.TestCase): primary_value=self.primary, zwave_config=self.zwave_config, device_config=self.device_config, + registry=self.registry ) values._check_entity_ready() self.hass.block_till_done() @@ -639,7 +644,7 @@ class TestZWaveDeviceEntityValues(unittest.TestCase): @patch.object(zwave, 'get_platform') @patch.object(zwave, 'discovery') def test_entity_workaround_component(self, discovery, get_platform): - """Test ignore workaround.""" + """Test component workaround.""" discovery.async_load_platform.return_value = mock_coro() mock_platform = MagicMock() get_platform.return_value = mock_platform @@ -666,6 +671,7 @@ class TestZWaveDeviceEntityValues(unittest.TestCase): primary_value=self.primary, zwave_config=self.zwave_config, device_config=self.device_config, + registry=self.registry ) values._check_entity_ready() self.hass.block_till_done() @@ -697,6 +703,7 @@ class TestZWaveDeviceEntityValues(unittest.TestCase): primary_value=self.primary, zwave_config=self.zwave_config, device_config=self.device_config, + registry=self.registry ) values._check_entity_ready() self.hass.block_till_done() @@ -720,12 +727,42 @@ class TestZWaveDeviceEntityValues(unittest.TestCase): primary_value=self.primary, zwave_config=self.zwave_config, device_config=self.device_config, + registry=self.registry ) values._check_entity_ready() self.hass.block_till_done() assert not discovery.async_load_platform.called + @patch.object(zwave, 'get_platform') + @patch.object(zwave, 'discovery') + def test_entity_config_ignore_with_registry(self, discovery, get_platform): + """Test ignore config. + + The case when the device is in entity registry. + """ + self.node.values = { + self.primary.value_id: self.primary, + self.secondary.value_id: self.secondary, + } + self.device_config = {'mock_component.registry_id': { + zwave.CONF_IGNORED: True + }} + self.registry.async_get_or_create( + 'mock_component', zwave.DOMAIN, '567-1000', + suggested_object_id='registry_id') + zwave.ZWaveDeviceEntityValues( + hass=self.hass, + schema=self.mock_schema, + primary_value=self.primary, + zwave_config=self.zwave_config, + device_config=self.device_config, + registry=self.registry + ) + self.hass.block_till_done() + + assert not discovery.async_load_platform.called + @patch.object(zwave, 'get_platform') @patch.object(zwave, 'discovery') def test_entity_platform_ignore(self, discovery, get_platform): @@ -743,6 +780,7 @@ class TestZWaveDeviceEntityValues(unittest.TestCase): primary_value=self.primary, zwave_config=self.zwave_config, device_config=self.device_config, + registry=self.registry ) self.hass.block_till_done() @@ -770,6 +808,7 @@ class TestZWaveDeviceEntityValues(unittest.TestCase): primary_value=self.primary, zwave_config=self.zwave_config, device_config=self.device_config, + registry=self.registry ) values._check_entity_ready() self.hass.block_till_done() diff --git a/tests/helpers/test_entity_registry.py b/tests/helpers/test_entity_registry.py index cb8703d1fe6..492b97f6387 100644 --- a/tests/helpers/test_entity_registry.py +++ b/tests/helpers/test_entity_registry.py @@ -180,3 +180,13 @@ test.disabled_hass: assert entry_disabled_hass.disabled_by == entity_registry.DISABLED_HASS assert entry_disabled_user.disabled assert entry_disabled_user.disabled_by == entity_registry.DISABLED_USER + + +@asyncio.coroutine +def test_async_get_entity_id(registry): + """Test that entity_id is returned.""" + entry = registry.async_get_or_create('light', 'hue', '1234') + assert entry.entity_id == 'light.hue_1234' + assert registry.async_get_entity_id( + 'light', 'hue', '1234') == 'light.hue_1234' + assert registry.async_get_entity_id('light', 'hue', '123') is None diff --git a/tests/mock/zwave.py b/tests/mock/zwave.py index 67bfb590c3f..59d97ddb621 100644 --- a/tests/mock/zwave.py +++ b/tests/mock/zwave.py @@ -178,6 +178,7 @@ class MockValue(MagicMock): MockValue._mock_value_id += 1 value_id = MockValue._mock_value_id self.value_id = value_id + self.object_id = value_id for attr_name in kwargs: setattr(self, attr_name, kwargs[attr_name])