From 7471211b60c7144ec98550dfdd0b3fbbe59354b1 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Sun, 11 Feb 2018 23:27:38 -0800 Subject: [PATCH 1/9] Version bump to 0.63.1 --- homeassistant/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/const.py b/homeassistant/const.py index 1c923a35936..6af7b885561 100644 --- a/homeassistant/const.py +++ b/homeassistant/const.py @@ -2,7 +2,7 @@ """Constants used by Home Assistant components.""" MAJOR_VERSION = 0 MINOR_VERSION = 63 -PATCH_VERSION = '0' +PATCH_VERSION = '1' __short_version__ = '{}.{}'.format(MAJOR_VERSION, MINOR_VERSION) __version__ = '{}.{}'.format(__short_version__, PATCH_VERSION) REQUIRED_PYTHON_VER = (3, 4, 2) From dc8a0205ee6f18723c79733c1015582aafe0e69c Mon Sep 17 00:00:00 2001 From: Richard Lucas Date: Sun, 11 Feb 2018 11:25:05 -0800 Subject: [PATCH 2/9] Fix Alexa Step Volume (#12314) --- homeassistant/components/alexa/smart_home.py | 2 +- tests/components/alexa/test_smart_home.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/alexa/smart_home.py b/homeassistant/components/alexa/smart_home.py index 354a612c4b8..e9b1b845d3d 100644 --- a/homeassistant/components/alexa/smart_home.py +++ b/homeassistant/components/alexa/smart_home.py @@ -1168,7 +1168,7 @@ def async_api_adjust_volume(hass, config, request, entity): @asyncio.coroutine def async_api_adjust_volume_step(hass, config, request, entity): """Process an adjust volume step request.""" - volume_step = round(float(request[API_PAYLOAD]['volume'] / 100), 2) + volume_step = round(float(request[API_PAYLOAD]['volumeSteps'] / 100), 2) current_level = entity.attributes.get(media_player.ATTR_MEDIA_VOLUME_LEVEL) diff --git a/tests/components/alexa/test_smart_home.py b/tests/components/alexa/test_smart_home.py index 71485231150..487ff301c18 100644 --- a/tests/components/alexa/test_smart_home.py +++ b/tests/components/alexa/test_smart_home.py @@ -511,14 +511,14 @@ def test_media_player(hass): 'Alexa.StepSpeaker', 'AdjustVolume', 'media_player#test', 'media_player.volume_set', hass, - payload={'volume': 20}) + payload={'volumeSteps': 20}) assert call.data['volume_level'] == 0.95 call, _ = yield from assert_request_calls_service( 'Alexa.StepSpeaker', 'AdjustVolume', 'media_player#test', 'media_player.volume_set', hass, - payload={'volume': -20}) + payload={'volumeSteps': -20}) assert call.data['volume_level'] == 0.55 From 34ccfae565eb5d4ef379347b6383c046d7d3c7a1 Mon Sep 17 00:00:00 2001 From: Russell Cloran Date: Sun, 11 Feb 2018 14:45:47 -0800 Subject: [PATCH 3/9] zha: Update zigpy-xbee to 0.0.2 0.0.2 implements auto_form, so that configuring the radio to be a controller is done automatically. --- homeassistant/components/zha/__init__.py | 2 +- requirements_all.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/zha/__init__.py b/homeassistant/components/zha/__init__.py index 58d44b31994..3729ce8a153 100644 --- a/homeassistant/components/zha/__init__.py +++ b/homeassistant/components/zha/__init__.py @@ -18,7 +18,7 @@ from homeassistant.util import slugify REQUIREMENTS = [ 'bellows==0.5.0', 'zigpy==0.0.1', - 'zigpy-xbee==0.0.1', + 'zigpy-xbee==0.0.2', ] DOMAIN = 'zha' diff --git a/requirements_all.txt b/requirements_all.txt index 3ca5b9fc763..a6777657227 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1275,7 +1275,7 @@ zeroconf==0.19.1 ziggo-mediabox-xl==1.0.0 # homeassistant.components.zha -zigpy-xbee==0.0.1 +zigpy-xbee==0.0.2 # homeassistant.components.zha zigpy==0.0.1 From 56b185f7ab0ca1f4ab6efa15af4baf7d16c33c47 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Sun, 11 Feb 2018 19:33:37 -0800 Subject: [PATCH 4/9] Remove unique ID from netatmo (#12317) * Remove unique ID from netatmo * Shame platform in error message --- homeassistant/components/binary_sensor/netatmo.py | 7 ------- homeassistant/components/camera/netatmo.py | 7 ------- homeassistant/components/sensor/netatmo.py | 6 ------ homeassistant/helpers/entity_platform.py | 6 +++++- 4 files changed, 5 insertions(+), 21 deletions(-) diff --git a/homeassistant/components/binary_sensor/netatmo.py b/homeassistant/components/binary_sensor/netatmo.py index 4d8aaa7d0d9..dd7e0ee8d50 100644 --- a/homeassistant/components/binary_sensor/netatmo.py +++ b/homeassistant/components/binary_sensor/netatmo.py @@ -131,8 +131,6 @@ class NetatmoBinarySensor(BinarySensorDevice): self._name += ' / ' + module_name self._sensor_name = sensor self._name += ' ' + sensor - self._unique_id = data.camera_data.cameraByName( - camera=camera_name, home=home)['id'] self._cameratype = camera_type self._state = None @@ -141,11 +139,6 @@ class NetatmoBinarySensor(BinarySensorDevice): """Return the name of the Netatmo device and this sensor.""" return self._name - @property - def unique_id(self): - """Return the unique ID for this sensor.""" - return self._unique_id - @property def device_class(self): """Return the class of this sensor, from DEVICE_CLASSES.""" diff --git a/homeassistant/components/camera/netatmo.py b/homeassistant/components/camera/netatmo.py index 0a9a3fbdca4..48f2710ce2e 100644 --- a/homeassistant/components/camera/netatmo.py +++ b/homeassistant/components/camera/netatmo.py @@ -67,8 +67,6 @@ class NetatmoCamera(Camera): self._vpnurl, self._localurl = self._data.camera_data.cameraUrls( camera=camera_name ) - self._unique_id = data.camera_data.cameraByName( - camera=camera_name, home=home)['id'] self._cameratype = camera_type def camera_image(self): @@ -112,8 +110,3 @@ class NetatmoCamera(Camera): elif self._cameratype == "NACamera": return "Welcome" return None - - @property - def unique_id(self): - """Return the unique ID for this camera.""" - return self._unique_id diff --git a/homeassistant/components/sensor/netatmo.py b/homeassistant/components/sensor/netatmo.py index c20e0a59408..8ec6de60fb9 100644 --- a/homeassistant/components/sensor/netatmo.py +++ b/homeassistant/components/sensor/netatmo.py @@ -113,18 +113,12 @@ class NetAtmoSensor(Entity): module_id = self.netatmo_data.\ station_data.moduleByName(module=module_name)['_id'] self.module_id = module_id[1] - self._unique_id = '{}-{}'.format(self.module_id, self.type) @property def name(self): """Return the name of the sensor.""" return self._name - @property - def unique_id(self): - """Return the unique ID for this sensor.""" - return self._unique_id - @property def icon(self): """Icon to use in the frontend, if any.""" diff --git a/homeassistant/helpers/entity_platform.py b/homeassistant/helpers/entity_platform.py index 3362f1e3b3f..014ec3c5009 100644 --- a/homeassistant/helpers/entity_platform.py +++ b/homeassistant/helpers/entity_platform.py @@ -239,8 +239,12 @@ class EntityPlatform(object): raise HomeAssistantError( 'Invalid entity id: {}'.format(entity.entity_id)) elif entity.entity_id in component_entities: + msg = 'Entity id already exists: {}'.format(entity.entity_id) + if entity.unique_id is not None: + msg += '. Platform {} does not generate unique IDs'.format( + self.platform_name) raise HomeAssistantError( - 'Entity id already exists: {}'.format(entity.entity_id)) + msg) self.entities[entity.entity_id] = entity component_entities.add(entity.entity_id) From bfd9a5a863211304dabd291c60f2b184c9cdc7ed Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Sun, 11 Feb 2018 09:16:01 -0800 Subject: [PATCH 5/9] Allow overriding name via entity registry (#12292) * Allow overriding name via entity registry * Update requirements --- .../components/remote/xiaomi_miio.py | 1 - homeassistant/helpers/entity.py | 5 ++- homeassistant/helpers/entity_component.py | 10 +++--- homeassistant/helpers/entity_platform.py | 1 + homeassistant/helpers/entity_registry.py | 30 +++++++++++----- homeassistant/package_constraints.txt | 1 + requirements_all.txt | 1 + setup.py | 1 + tests/common.py | 4 +-- tests/helpers/test_entity_platform.py | 23 +++++++++++- tests/helpers/test_entity_registry.py | 35 +++++++++++++++++-- 11 files changed, 91 insertions(+), 21 deletions(-) diff --git a/homeassistant/components/remote/xiaomi_miio.py b/homeassistant/components/remote/xiaomi_miio.py index aa05246c9cd..32fde57b61a 100644 --- a/homeassistant/components/remote/xiaomi_miio.py +++ b/homeassistant/components/remote/xiaomi_miio.py @@ -226,7 +226,6 @@ class XiaomiMiioRemote(RemoteDevice): _LOGGER.error("Device does not support turn_off, " + "please use 'remote.send_command' to send commands.") - # pylint: enable=R0201 def _send_command(self, payload): """Send a command.""" from miio import DeviceException diff --git a/homeassistant/helpers/entity.py b/homeassistant/helpers/entity.py index c7653d5d5b9..6b882d2fdad 100644 --- a/homeassistant/helpers/entity.py +++ b/homeassistant/helpers/entity.py @@ -80,6 +80,9 @@ class Entity(object): # Process updates in parallel parallel_updates = None + # Name in the entity registry + registry_name = None + @property def should_poll(self) -> bool: """Return True if entity has to be polled for state. @@ -225,7 +228,7 @@ class Entity(object): if unit_of_measurement is not None: attr[ATTR_UNIT_OF_MEASUREMENT] = unit_of_measurement - name = self.name + name = self.registry_name or self.name if name is not None: attr[ATTR_FRIENDLY_NAME] = name diff --git a/homeassistant/helpers/entity_component.py b/homeassistant/helpers/entity_component.py index 9dfbe580c16..2dcde6fdeda 100644 --- a/homeassistant/helpers/entity_component.py +++ b/homeassistant/helpers/entity_component.py @@ -40,19 +40,19 @@ class EntityComponent(object): self.config = None self._platforms = { - 'core': EntityPlatform( + domain: EntityPlatform( hass=hass, logger=logger, domain=domain, - platform_name='core', + platform_name=domain, scan_interval=self.scan_interval, parallel_updates=0, entity_namespace=None, async_entities_added_callback=self._async_update_group, ) } - self.async_add_entities = self._platforms['core'].async_add_entities - self.add_entities = self._platforms['core'].add_entities + self.async_add_entities = self._platforms[domain].async_add_entities + self.add_entities = self._platforms[domain].add_entities @property def entities(self): @@ -190,7 +190,7 @@ class EntityComponent(object): yield from asyncio.wait(tasks, loop=self.hass.loop) self._platforms = { - 'core': self._platforms['core'] + self.domain: self._platforms[self.domain] } self.config = None diff --git a/homeassistant/helpers/entity_platform.py b/homeassistant/helpers/entity_platform.py index 014ec3c5009..9035973e015 100644 --- a/homeassistant/helpers/entity_platform.py +++ b/homeassistant/helpers/entity_platform.py @@ -213,6 +213,7 @@ class EntityPlatform(object): self.domain, self.platform_name, entity.unique_id, suggested_object_id=suggested_object_id) entity.entity_id = entry.entity_id + entity.registry_name = entry.name # We won't generate an entity ID if the platform has already set one # We will however make sure that platform cannot pick a registered ID diff --git a/homeassistant/helpers/entity_registry.py b/homeassistant/helpers/entity_registry.py index 350c8273232..d33ca93f290 100644 --- a/homeassistant/helpers/entity_registry.py +++ b/homeassistant/helpers/entity_registry.py @@ -11,22 +11,37 @@ After initializing, call EntityRegistry.async_ensure_loaded to load the data from disk. """ import asyncio -from collections import namedtuple, OrderedDict +from collections import OrderedDict from itertools import chain import logging import os +import attr + from ..core import callback, split_entity_id from ..util import ensure_unique_string, slugify from ..util.yaml import load_yaml, save_yaml PATH_REGISTRY = 'entity_registry.yaml' SAVE_DELAY = 10 -Entry = namedtuple('EntityRegistryEntry', - 'entity_id,unique_id,platform,domain') _LOGGER = logging.getLogger(__name__) +@attr.s(slots=True, frozen=True) +class RegistryEntry: + """Entity Registry Entry.""" + + entity_id = attr.ib(type=str) + unique_id = attr.ib(type=str) + platform = attr.ib(type=str) + name = attr.ib(type=str, default=None) + domain = attr.ib(type=str, default=None, init=False, repr=False) + + def __attrs_post_init__(self): + """Computed properties.""" + object.__setattr__(self, "domain", split_entity_id(self.entity_id)[0]) + + class EntityRegistry: """Class to hold a registry of entities.""" @@ -65,11 +80,10 @@ class EntityRegistry: entity_id = self.async_generate_entity_id( domain, suggested_object_id or '{}_{}'.format(platform, unique_id)) - entity = Entry( + entity = RegistryEntry( entity_id=entity_id, unique_id=unique_id, platform=platform, - domain=domain, ) self.entities[entity_id] = entity _LOGGER.info('Registered new %s.%s entity: %s', @@ -98,11 +112,11 @@ class EntityRegistry: data = yield from self.hass.async_add_job(load_yaml, path) for entity_id, info in data.items(): - entities[entity_id] = Entry( - domain=split_entity_id(entity_id)[0], + entities[entity_id] = RegistryEntry( entity_id=entity_id, unique_id=info['unique_id'], - platform=info['platform'] + platform=info['platform'], + name=info.get('name') ) self.entities = entities diff --git a/homeassistant/package_constraints.txt b/homeassistant/package_constraints.txt index ee3a37bbd53..7d182aebfa3 100644 --- a/homeassistant/package_constraints.txt +++ b/homeassistant/package_constraints.txt @@ -11,6 +11,7 @@ async_timeout==2.0.0 chardet==3.0.4 astral==1.5 certifi>=2017.4.17 +attrs==17.4.0 # Breaks Python 3.6 and is not needed for our supported Pythons enum34==1000000000.0.0 diff --git a/requirements_all.txt b/requirements_all.txt index a6777657227..2a5f68791e0 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -12,6 +12,7 @@ async_timeout==2.0.0 chardet==3.0.4 astral==1.5 certifi>=2017.4.17 +attrs==17.4.0 # homeassistant.components.nuimo_controller --only-binary=all https://github.com/getSenic/nuimo-linux-python/archive/29fc42987f74d8090d0e2382e8f248ff5990b8c9.zip#nuimo==1.0.0 diff --git a/setup.py b/setup.py index 5af84fc8e0e..0a454f9eb4d 100755 --- a/setup.py +++ b/setup.py @@ -61,6 +61,7 @@ REQUIRES = [ 'chardet==3.0.4', 'astral==1.5', 'certifi>=2017.4.17', + 'attrs==17.4.0', ] MIN_PY_VERSION = '.'.join(map( diff --git a/tests/common.py b/tests/common.py index 22af8ecb8a3..511d59dbdfe 100644 --- a/tests/common.py +++ b/tests/common.py @@ -317,10 +317,10 @@ def mock_component(hass, component): hass.config.components.add(component) -def mock_registry(hass): +def mock_registry(hass, mock_entries=None): """Mock the Entity Registry.""" registry = entity_registry.EntityRegistry(hass) - registry.entities = {} + registry.entities = mock_entries or {} hass.data[entity_platform.DATA_REGISTRY] = registry return registry diff --git a/tests/helpers/test_entity_platform.py b/tests/helpers/test_entity_platform.py index 4c27cc45a00..e398349cf7a 100644 --- a/tests/helpers/test_entity_platform.py +++ b/tests/helpers/test_entity_platform.py @@ -9,7 +9,7 @@ import homeassistant.loader as loader from homeassistant.helpers.entity import generate_entity_id from homeassistant.helpers.entity_component import ( EntityComponent, DEFAULT_SCAN_INTERVAL) -from homeassistant.helpers import entity_platform +from homeassistant.helpers import entity_platform, entity_registry import homeassistant.util.dt as dt_util @@ -433,3 +433,24 @@ def test_entity_with_name_and_entity_id_getting_registered(hass): MockEntity(unique_id='1234', name='bla', entity_id='test_domain.world')]) assert 'test_domain.world' in hass.states.async_entity_ids() + + +@asyncio.coroutine +def test_overriding_name_from_registry(hass): + """Test that we can override a name via the Entity Registry.""" + component = EntityComponent(_LOGGER, DOMAIN, hass) + mock_registry(hass, { + 'test_domain.world': entity_registry.RegistryEntry( + entity_id='test_domain.world', + unique_id='1234', + # Using component.async_add_entities is equal to platform "domain" + platform='test_domain', + name='Overridden' + ) + }) + yield from component.async_add_entities([ + MockEntity(unique_id='1234', name='Device Name')]) + + state = hass.states.get('test_domain.world') + assert state is not None + assert state.name == 'Overridden' diff --git a/tests/helpers/test_entity_registry.py b/tests/helpers/test_entity_registry.py index d19a3f3fe49..7e1150638c1 100644 --- a/tests/helpers/test_entity_registry.py +++ b/tests/helpers/test_entity_registry.py @@ -9,6 +9,9 @@ from homeassistant.helpers import entity_registry from tests.common import mock_registry +YAML__OPEN_PATH = 'homeassistant.util.yaml.open' + + @pytest.fixture def registry(hass): """Return an empty, loaded, registry.""" @@ -82,13 +85,12 @@ def test_save_timer_reset_on_subsequent_save(hass, registry): @asyncio.coroutine def test_loading_saving_data(hass, registry): """Test that we load/save data correctly.""" - yaml_path = 'homeassistant.util.yaml.open' orig_entry1 = registry.async_get_or_create('light', 'hue', '1234') orig_entry2 = registry.async_get_or_create('light', 'hue', '5678') assert len(registry.entities) == 2 - with patch(yaml_path, mock_open(), create=True) as mock_write: + with patch(YAML__OPEN_PATH, mock_open(), create=True) as mock_write: yield from registry._async_save() # Mock open calls are: open file, context enter, write, context leave @@ -98,7 +100,7 @@ def test_loading_saving_data(hass, registry): registry2 = entity_registry.EntityRegistry(hass) with patch('os.path.isfile', return_value=True), \ - patch(yaml_path, mock_open(read_data=written), create=True): + patch(YAML__OPEN_PATH, mock_open(read_data=written), create=True): yield from registry2._async_load() # Ensure same order @@ -133,3 +135,30 @@ def test_is_registered(registry): entry = registry.async_get_or_create('light', 'hue', '1234') assert registry.async_is_registered(entry.entity_id) assert not registry.async_is_registered('light.non_existing') + + +@asyncio.coroutine +def test_loading_extra_values(hass): + """Test we load extra data from the registry.""" + written = """ +test.named: + platform: super_platform + unique_id: with-name + name: registry override +test.no_name: + platform: super_platform + unique_id: without-name +""" + + registry = entity_registry.EntityRegistry(hass) + + with patch('os.path.isfile', return_value=True), \ + patch(YAML__OPEN_PATH, mock_open(read_data=written), create=True): + yield from registry._async_load() + + entry_with_name = registry.async_get_or_create( + 'test', 'super_platform', 'with-name') + entry_without_name = registry.async_get_or_create( + 'test', 'super_platform', 'without-name') + assert entry_with_name.name == 'registry override' + assert entry_without_name.name is None From ead158b68c4fd60d26503dd63fd6749d4e7ec50b Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Sun, 11 Feb 2018 20:55:38 -0800 Subject: [PATCH 6/9] Respect entity namespace for entity registry (#12313) * Respect entity namespace for entity registry * Lint --- homeassistant/helpers/entity_platform.py | 4 +++ tests/helpers/test_entity_platform.py | 36 ++++++++++++++++++++++++ 2 files changed, 40 insertions(+) diff --git a/homeassistant/helpers/entity_platform.py b/homeassistant/helpers/entity_platform.py index 9035973e015..6cf58212c8e 100644 --- a/homeassistant/helpers/entity_platform.py +++ b/homeassistant/helpers/entity_platform.py @@ -209,6 +209,10 @@ class EntityPlatform(object): else: suggested_object_id = entity.name + if self.entity_namespace is not None: + suggested_object_id = '{} {}'.format( + self.entity_namespace, suggested_object_id) + entry = registry.async_get_or_create( self.domain, self.platform_name, entity.unique_id, suggested_object_id=suggested_object_id) diff --git a/tests/helpers/test_entity_platform.py b/tests/helpers/test_entity_platform.py index e398349cf7a..a54a6de511a 100644 --- a/tests/helpers/test_entity_platform.py +++ b/tests/helpers/test_entity_platform.py @@ -21,6 +21,32 @@ _LOGGER = logging.getLogger(__name__) DOMAIN = "test_domain" +class MockEntityPlatform(entity_platform.EntityPlatform): + """Mock class with some mock defaults.""" + + def __init__( + self, *, hass, + logger=None, + domain='test', + platform_name='test_platform', + scan_interval=timedelta(seconds=15), + parallel_updates=0, + entity_namespace=None, + async_entities_added_callback=lambda: None + ): + """Initialize a mock entity platform.""" + super().__init__( + hass=hass, + logger=logger, + domain=domain, + platform_name=platform_name, + scan_interval=scan_interval, + parallel_updates=parallel_updates, + entity_namespace=entity_namespace, + async_entities_added_callback=async_entities_added_callback, + ) + + class TestHelpersEntityPlatform(unittest.TestCase): """Test homeassistant.helpers.entity_component module.""" @@ -454,3 +480,13 @@ def test_overriding_name_from_registry(hass): state = hass.states.get('test_domain.world') assert state is not None assert state.name == 'Overridden' + + +@asyncio.coroutine +def test_registry_respect_entity_namespace(hass): + """Test that the registry respects entity namespace.""" + mock_registry(hass) + platform = MockEntityPlatform(hass=hass, entity_namespace='ns') + entity = MockEntity(unique_id='1234', name='Device Name') + yield from platform.async_add_entities([entity]) + assert entity.entity_id == 'test.ns_device_name' From 2a0bd8d330d9e2788fc1165e68bf72c76f4da9ad Mon Sep 17 00:00:00 2001 From: Richard Lucas Date: Sun, 11 Feb 2018 22:36:22 -0800 Subject: [PATCH 7/9] Fix Report State for Alexa Brightness Controller (#12318) * Fix Report State for Alexa Brightness Controller * Lint --- homeassistant/components/alexa/smart_home.py | 5 +++-- tests/components/alexa/test_smart_home.py | 19 ++++++++++++++++++- 2 files changed, 21 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/alexa/smart_home.py b/homeassistant/components/alexa/smart_home.py index e9b1b845d3d..c0a42ef8aa6 100644 --- a/homeassistant/components/alexa/smart_home.py +++ b/homeassistant/components/alexa/smart_home.py @@ -328,8 +328,9 @@ class _AlexaBrightnessController(_AlexaInterface): def get_property(self, name): if name != 'brightness': raise _UnsupportedProperty(name) - - return round(self.entity.attributes['brightness'] / 255.0 * 100) + if 'brightness' in self.entity.attributes: + return round(self.entity.attributes['brightness'] / 255.0 * 100) + return 0 class _AlexaColorController(_AlexaInterface): diff --git a/tests/components/alexa/test_smart_home.py b/tests/components/alexa/test_smart_home.py index 487ff301c18..96e16544438 100644 --- a/tests/components/alexa/test_smart_home.py +++ b/tests/components/alexa/test_smart_home.py @@ -1095,6 +1095,23 @@ def test_report_lock_state(hass): properties.assert_equal('Alexa.LockController', 'lockState', 'JAMMED') +@asyncio.coroutine +def test_report_dimmable_light_state(hass): + """Test BrightnessController reports brightness correctly.""" + hass.states.async_set( + 'light.test_on', 'on', {'friendly_name': "Test light On", + 'brightness': 128, 'supported_features': 1}) + hass.states.async_set( + 'light.test_off', 'off', {'friendly_name': "Test light Off", + 'supported_features': 1}) + + properties = yield from reported_properties(hass, 'light.test_on') + properties.assert_equal('Alexa.BrightnessController', 'brightness', 50) + + properties = yield from reported_properties(hass, 'light.test_off') + properties.assert_equal('Alexa.BrightnessController', 'brightness', 0) + + @asyncio.coroutine def reported_properties(hass, endpoint): """Use ReportState to get properties and return them. @@ -1118,7 +1135,7 @@ class _ReportedProperties(object): for prop in self.properties: if prop['namespace'] == namespace and prop['name'] == name: assert prop['value'] == value - return prop + return prop assert False, 'property %s:%s not in %r' % ( namespace, From a06000c76dd81066c7a2a030a1115bf02eaf0028 Mon Sep 17 00:00:00 2001 From: Richard Lucas Date: Sun, 11 Feb 2018 23:20:54 -0800 Subject: [PATCH 8/9] Always return lockState == LOCKED when handling Alexa.LockController (#12328) --- homeassistant/components/alexa/smart_home.py | 11 ++++++++++- tests/components/alexa/test_smart_home.py | 8 +++++++- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/alexa/smart_home.py b/homeassistant/components/alexa/smart_home.py index c0a42ef8aa6..a4f0225d22d 100644 --- a/homeassistant/components/alexa/smart_home.py +++ b/homeassistant/components/alexa/smart_home.py @@ -1065,7 +1065,16 @@ def async_api_lock(hass, config, request, entity): ATTR_ENTITY_ID: entity.entity_id }, blocking=False) - return api_message(request) + # Alexa expects a lockState in the response, we don't know the actual + # lockState at this point but assume it is locked. It is reported + # correctly later when ReportState is called. The alt. to this approach + # is to implement DeferredResponse + properties = [{ + 'name': 'lockState', + 'namespace': 'Alexa.LockController', + 'value': 'LOCKED' + }] + return api_message(request, context={'properties': properties}) # Not supported by Alexa yet diff --git a/tests/components/alexa/test_smart_home.py b/tests/components/alexa/test_smart_home.py index 96e16544438..9654c667c5f 100644 --- a/tests/components/alexa/test_smart_home.py +++ b/tests/components/alexa/test_smart_home.py @@ -401,11 +401,17 @@ def test_lock(hass): assert appliance['friendlyName'] == "Test lock" assert_endpoint_capabilities(appliance, 'Alexa.LockController') - yield from assert_request_calls_service( + _, msg = yield from assert_request_calls_service( 'Alexa.LockController', 'Lock', 'lock#test', 'lock.lock', hass) + # always return LOCKED for now + properties = msg['context']['properties'][0] + assert properties['name'] == 'lockState' + assert properties['namespace'] == 'Alexa.LockController' + assert properties['value'] == 'LOCKED' + @asyncio.coroutine def test_media_player(hass): From 7a9ceb6f54d2d546c38fd0ba7fa9d5d1204ce4c4 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Sun, 11 Feb 2018 23:26:52 -0800 Subject: [PATCH 9/9] Fix platform dependencies (#12330) --- homeassistant/setup.py | 2 +- tests/helpers/test_entity_component.py | 30 +++++++++++++++++++++++++- 2 files changed, 30 insertions(+), 2 deletions(-) diff --git a/homeassistant/setup.py b/homeassistant/setup.py index 3221ea35d48..5cff2cbc6f5 100644 --- a/homeassistant/setup.py +++ b/homeassistant/setup.py @@ -206,7 +206,7 @@ def async_prepare_setup_platform(hass: core.HomeAssistant, config, domain: str, return platform try: - yield from _process_deps_reqs(hass, config, platform_name, platform) + yield from _process_deps_reqs(hass, config, platform_path, platform) except HomeAssistantError as err: log_error(str(err)) return None diff --git a/tests/helpers/test_entity_component.py b/tests/helpers/test_entity_component.py index ef92da3172b..d8dac11f6a0 100644 --- a/tests/helpers/test_entity_component.py +++ b/tests/helpers/test_entity_component.py @@ -12,7 +12,7 @@ import homeassistant.loader as loader from homeassistant.exceptions import PlatformNotReady from homeassistant.components import group from homeassistant.helpers.entity_component import EntityComponent -from homeassistant.setup import setup_component +from homeassistant.setup import setup_component, async_setup_component from homeassistant.helpers import discovery import homeassistant.util.dt as dt_util @@ -305,3 +305,31 @@ def test_extract_from_service_no_group_expand(hass): extracted = component.async_extract_from_service(call, expand_group=False) assert extracted == [test_group] + + +@asyncio.coroutine +def test_setup_dependencies_platform(hass): + """Test we setup the dependencies of a platform. + + We're explictely testing that we process dependencies even if a component + with the same name has already been loaded. + """ + loader.set_component('test_component', MockModule('test_component')) + loader.set_component('test_component2', MockModule('test_component2')) + loader.set_component( + 'test_domain.test_component', + MockPlatform(dependencies=['test_component', 'test_component2'])) + + component = EntityComponent(_LOGGER, DOMAIN, hass) + + yield from async_setup_component(hass, 'test_component', {}) + + yield from component.async_setup({ + DOMAIN: { + 'platform': 'test_component', + } + }) + + assert 'test_component' in hass.config.components + assert 'test_component2' in hass.config.components + assert 'test_domain.test_component' in hass.config.components