mirror of
https://github.com/home-assistant/core.git
synced 2025-07-24 21:57:51 +00:00
commit
073126755c
@ -328,8 +328,9 @@ class _AlexaBrightnessController(_AlexaInterface):
|
|||||||
def get_property(self, name):
|
def get_property(self, name):
|
||||||
if name != 'brightness':
|
if name != 'brightness':
|
||||||
raise _UnsupportedProperty(name)
|
raise _UnsupportedProperty(name)
|
||||||
|
if 'brightness' in self.entity.attributes:
|
||||||
return round(self.entity.attributes['brightness'] / 255.0 * 100)
|
return round(self.entity.attributes['brightness'] / 255.0 * 100)
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
class _AlexaColorController(_AlexaInterface):
|
class _AlexaColorController(_AlexaInterface):
|
||||||
@ -1064,7 +1065,16 @@ def async_api_lock(hass, config, request, entity):
|
|||||||
ATTR_ENTITY_ID: entity.entity_id
|
ATTR_ENTITY_ID: entity.entity_id
|
||||||
}, blocking=False)
|
}, 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
|
# Not supported by Alexa yet
|
||||||
@ -1168,7 +1178,7 @@ def async_api_adjust_volume(hass, config, request, entity):
|
|||||||
@asyncio.coroutine
|
@asyncio.coroutine
|
||||||
def async_api_adjust_volume_step(hass, config, request, entity):
|
def async_api_adjust_volume_step(hass, config, request, entity):
|
||||||
"""Process an adjust volume step request."""
|
"""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)
|
current_level = entity.attributes.get(media_player.ATTR_MEDIA_VOLUME_LEVEL)
|
||||||
|
|
||||||
|
@ -131,8 +131,6 @@ class NetatmoBinarySensor(BinarySensorDevice):
|
|||||||
self._name += ' / ' + module_name
|
self._name += ' / ' + module_name
|
||||||
self._sensor_name = sensor
|
self._sensor_name = sensor
|
||||||
self._name += ' ' + sensor
|
self._name += ' ' + sensor
|
||||||
self._unique_id = data.camera_data.cameraByName(
|
|
||||||
camera=camera_name, home=home)['id']
|
|
||||||
self._cameratype = camera_type
|
self._cameratype = camera_type
|
||||||
self._state = None
|
self._state = None
|
||||||
|
|
||||||
@ -141,11 +139,6 @@ class NetatmoBinarySensor(BinarySensorDevice):
|
|||||||
"""Return the name of the Netatmo device and this sensor."""
|
"""Return the name of the Netatmo device and this sensor."""
|
||||||
return self._name
|
return self._name
|
||||||
|
|
||||||
@property
|
|
||||||
def unique_id(self):
|
|
||||||
"""Return the unique ID for this sensor."""
|
|
||||||
return self._unique_id
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def device_class(self):
|
def device_class(self):
|
||||||
"""Return the class of this sensor, from DEVICE_CLASSES."""
|
"""Return the class of this sensor, from DEVICE_CLASSES."""
|
||||||
|
@ -67,8 +67,6 @@ class NetatmoCamera(Camera):
|
|||||||
self._vpnurl, self._localurl = self._data.camera_data.cameraUrls(
|
self._vpnurl, self._localurl = self._data.camera_data.cameraUrls(
|
||||||
camera=camera_name
|
camera=camera_name
|
||||||
)
|
)
|
||||||
self._unique_id = data.camera_data.cameraByName(
|
|
||||||
camera=camera_name, home=home)['id']
|
|
||||||
self._cameratype = camera_type
|
self._cameratype = camera_type
|
||||||
|
|
||||||
def camera_image(self):
|
def camera_image(self):
|
||||||
@ -112,8 +110,3 @@ class NetatmoCamera(Camera):
|
|||||||
elif self._cameratype == "NACamera":
|
elif self._cameratype == "NACamera":
|
||||||
return "Welcome"
|
return "Welcome"
|
||||||
return None
|
return None
|
||||||
|
|
||||||
@property
|
|
||||||
def unique_id(self):
|
|
||||||
"""Return the unique ID for this camera."""
|
|
||||||
return self._unique_id
|
|
||||||
|
@ -226,7 +226,6 @@ class XiaomiMiioRemote(RemoteDevice):
|
|||||||
_LOGGER.error("Device does not support turn_off, " +
|
_LOGGER.error("Device does not support turn_off, " +
|
||||||
"please use 'remote.send_command' to send commands.")
|
"please use 'remote.send_command' to send commands.")
|
||||||
|
|
||||||
# pylint: enable=R0201
|
|
||||||
def _send_command(self, payload):
|
def _send_command(self, payload):
|
||||||
"""Send a command."""
|
"""Send a command."""
|
||||||
from miio import DeviceException
|
from miio import DeviceException
|
||||||
|
@ -113,18 +113,12 @@ class NetAtmoSensor(Entity):
|
|||||||
module_id = self.netatmo_data.\
|
module_id = self.netatmo_data.\
|
||||||
station_data.moduleByName(module=module_name)['_id']
|
station_data.moduleByName(module=module_name)['_id']
|
||||||
self.module_id = module_id[1]
|
self.module_id = module_id[1]
|
||||||
self._unique_id = '{}-{}'.format(self.module_id, self.type)
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def name(self):
|
def name(self):
|
||||||
"""Return the name of the sensor."""
|
"""Return the name of the sensor."""
|
||||||
return self._name
|
return self._name
|
||||||
|
|
||||||
@property
|
|
||||||
def unique_id(self):
|
|
||||||
"""Return the unique ID for this sensor."""
|
|
||||||
return self._unique_id
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def icon(self):
|
def icon(self):
|
||||||
"""Icon to use in the frontend, if any."""
|
"""Icon to use in the frontend, if any."""
|
||||||
|
@ -18,7 +18,7 @@ from homeassistant.util import slugify
|
|||||||
REQUIREMENTS = [
|
REQUIREMENTS = [
|
||||||
'bellows==0.5.0',
|
'bellows==0.5.0',
|
||||||
'zigpy==0.0.1',
|
'zigpy==0.0.1',
|
||||||
'zigpy-xbee==0.0.1',
|
'zigpy-xbee==0.0.2',
|
||||||
]
|
]
|
||||||
|
|
||||||
DOMAIN = 'zha'
|
DOMAIN = 'zha'
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
"""Constants used by Home Assistant components."""
|
"""Constants used by Home Assistant components."""
|
||||||
MAJOR_VERSION = 0
|
MAJOR_VERSION = 0
|
||||||
MINOR_VERSION = 63
|
MINOR_VERSION = 63
|
||||||
PATCH_VERSION = '0'
|
PATCH_VERSION = '1'
|
||||||
__short_version__ = '{}.{}'.format(MAJOR_VERSION, MINOR_VERSION)
|
__short_version__ = '{}.{}'.format(MAJOR_VERSION, MINOR_VERSION)
|
||||||
__version__ = '{}.{}'.format(__short_version__, PATCH_VERSION)
|
__version__ = '{}.{}'.format(__short_version__, PATCH_VERSION)
|
||||||
REQUIRED_PYTHON_VER = (3, 4, 2)
|
REQUIRED_PYTHON_VER = (3, 4, 2)
|
||||||
|
@ -80,6 +80,9 @@ class Entity(object):
|
|||||||
# Process updates in parallel
|
# Process updates in parallel
|
||||||
parallel_updates = None
|
parallel_updates = None
|
||||||
|
|
||||||
|
# Name in the entity registry
|
||||||
|
registry_name = None
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def should_poll(self) -> bool:
|
def should_poll(self) -> bool:
|
||||||
"""Return True if entity has to be polled for state.
|
"""Return True if entity has to be polled for state.
|
||||||
@ -225,7 +228,7 @@ class Entity(object):
|
|||||||
if unit_of_measurement is not None:
|
if unit_of_measurement is not None:
|
||||||
attr[ATTR_UNIT_OF_MEASUREMENT] = unit_of_measurement
|
attr[ATTR_UNIT_OF_MEASUREMENT] = unit_of_measurement
|
||||||
|
|
||||||
name = self.name
|
name = self.registry_name or self.name
|
||||||
if name is not None:
|
if name is not None:
|
||||||
attr[ATTR_FRIENDLY_NAME] = name
|
attr[ATTR_FRIENDLY_NAME] = name
|
||||||
|
|
||||||
|
@ -40,19 +40,19 @@ class EntityComponent(object):
|
|||||||
self.config = None
|
self.config = None
|
||||||
|
|
||||||
self._platforms = {
|
self._platforms = {
|
||||||
'core': EntityPlatform(
|
domain: EntityPlatform(
|
||||||
hass=hass,
|
hass=hass,
|
||||||
logger=logger,
|
logger=logger,
|
||||||
domain=domain,
|
domain=domain,
|
||||||
platform_name='core',
|
platform_name=domain,
|
||||||
scan_interval=self.scan_interval,
|
scan_interval=self.scan_interval,
|
||||||
parallel_updates=0,
|
parallel_updates=0,
|
||||||
entity_namespace=None,
|
entity_namespace=None,
|
||||||
async_entities_added_callback=self._async_update_group,
|
async_entities_added_callback=self._async_update_group,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
self.async_add_entities = self._platforms['core'].async_add_entities
|
self.async_add_entities = self._platforms[domain].async_add_entities
|
||||||
self.add_entities = self._platforms['core'].add_entities
|
self.add_entities = self._platforms[domain].add_entities
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def entities(self):
|
def entities(self):
|
||||||
@ -190,7 +190,7 @@ class EntityComponent(object):
|
|||||||
yield from asyncio.wait(tasks, loop=self.hass.loop)
|
yield from asyncio.wait(tasks, loop=self.hass.loop)
|
||||||
|
|
||||||
self._platforms = {
|
self._platforms = {
|
||||||
'core': self._platforms['core']
|
self.domain: self._platforms[self.domain]
|
||||||
}
|
}
|
||||||
self.config = None
|
self.config = None
|
||||||
|
|
||||||
|
@ -209,10 +209,15 @@ class EntityPlatform(object):
|
|||||||
else:
|
else:
|
||||||
suggested_object_id = entity.name
|
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(
|
entry = registry.async_get_or_create(
|
||||||
self.domain, self.platform_name, entity.unique_id,
|
self.domain, self.platform_name, entity.unique_id,
|
||||||
suggested_object_id=suggested_object_id)
|
suggested_object_id=suggested_object_id)
|
||||||
entity.entity_id = entry.entity_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 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
|
# We will however make sure that platform cannot pick a registered ID
|
||||||
@ -239,8 +244,12 @@ class EntityPlatform(object):
|
|||||||
raise HomeAssistantError(
|
raise HomeAssistantError(
|
||||||
'Invalid entity id: {}'.format(entity.entity_id))
|
'Invalid entity id: {}'.format(entity.entity_id))
|
||||||
elif entity.entity_id in component_entities:
|
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(
|
raise HomeAssistantError(
|
||||||
'Entity id already exists: {}'.format(entity.entity_id))
|
msg)
|
||||||
|
|
||||||
self.entities[entity.entity_id] = entity
|
self.entities[entity.entity_id] = entity
|
||||||
component_entities.add(entity.entity_id)
|
component_entities.add(entity.entity_id)
|
||||||
|
@ -11,22 +11,37 @@ After initializing, call EntityRegistry.async_ensure_loaded to load the data
|
|||||||
from disk.
|
from disk.
|
||||||
"""
|
"""
|
||||||
import asyncio
|
import asyncio
|
||||||
from collections import namedtuple, OrderedDict
|
from collections import OrderedDict
|
||||||
from itertools import chain
|
from itertools import chain
|
||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
|
|
||||||
|
import attr
|
||||||
|
|
||||||
from ..core import callback, split_entity_id
|
from ..core import callback, split_entity_id
|
||||||
from ..util import ensure_unique_string, slugify
|
from ..util import ensure_unique_string, slugify
|
||||||
from ..util.yaml import load_yaml, save_yaml
|
from ..util.yaml import load_yaml, save_yaml
|
||||||
|
|
||||||
PATH_REGISTRY = 'entity_registry.yaml'
|
PATH_REGISTRY = 'entity_registry.yaml'
|
||||||
SAVE_DELAY = 10
|
SAVE_DELAY = 10
|
||||||
Entry = namedtuple('EntityRegistryEntry',
|
|
||||||
'entity_id,unique_id,platform,domain')
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_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 EntityRegistry:
|
||||||
"""Class to hold a registry of entities."""
|
"""Class to hold a registry of entities."""
|
||||||
|
|
||||||
@ -65,11 +80,10 @@ class EntityRegistry:
|
|||||||
|
|
||||||
entity_id = self.async_generate_entity_id(
|
entity_id = self.async_generate_entity_id(
|
||||||
domain, suggested_object_id or '{}_{}'.format(platform, unique_id))
|
domain, suggested_object_id or '{}_{}'.format(platform, unique_id))
|
||||||
entity = Entry(
|
entity = RegistryEntry(
|
||||||
entity_id=entity_id,
|
entity_id=entity_id,
|
||||||
unique_id=unique_id,
|
unique_id=unique_id,
|
||||||
platform=platform,
|
platform=platform,
|
||||||
domain=domain,
|
|
||||||
)
|
)
|
||||||
self.entities[entity_id] = entity
|
self.entities[entity_id] = entity
|
||||||
_LOGGER.info('Registered new %s.%s entity: %s',
|
_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)
|
data = yield from self.hass.async_add_job(load_yaml, path)
|
||||||
|
|
||||||
for entity_id, info in data.items():
|
for entity_id, info in data.items():
|
||||||
entities[entity_id] = Entry(
|
entities[entity_id] = RegistryEntry(
|
||||||
domain=split_entity_id(entity_id)[0],
|
|
||||||
entity_id=entity_id,
|
entity_id=entity_id,
|
||||||
unique_id=info['unique_id'],
|
unique_id=info['unique_id'],
|
||||||
platform=info['platform']
|
platform=info['platform'],
|
||||||
|
name=info.get('name')
|
||||||
)
|
)
|
||||||
|
|
||||||
self.entities = entities
|
self.entities = entities
|
||||||
|
@ -11,6 +11,7 @@ async_timeout==2.0.0
|
|||||||
chardet==3.0.4
|
chardet==3.0.4
|
||||||
astral==1.5
|
astral==1.5
|
||||||
certifi>=2017.4.17
|
certifi>=2017.4.17
|
||||||
|
attrs==17.4.0
|
||||||
|
|
||||||
# Breaks Python 3.6 and is not needed for our supported Pythons
|
# Breaks Python 3.6 and is not needed for our supported Pythons
|
||||||
enum34==1000000000.0.0
|
enum34==1000000000.0.0
|
||||||
|
@ -206,7 +206,7 @@ def async_prepare_setup_platform(hass: core.HomeAssistant, config, domain: str,
|
|||||||
return platform
|
return platform
|
||||||
|
|
||||||
try:
|
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:
|
except HomeAssistantError as err:
|
||||||
log_error(str(err))
|
log_error(str(err))
|
||||||
return None
|
return None
|
||||||
|
@ -12,6 +12,7 @@ async_timeout==2.0.0
|
|||||||
chardet==3.0.4
|
chardet==3.0.4
|
||||||
astral==1.5
|
astral==1.5
|
||||||
certifi>=2017.4.17
|
certifi>=2017.4.17
|
||||||
|
attrs==17.4.0
|
||||||
|
|
||||||
# homeassistant.components.nuimo_controller
|
# homeassistant.components.nuimo_controller
|
||||||
--only-binary=all https://github.com/getSenic/nuimo-linux-python/archive/29fc42987f74d8090d0e2382e8f248ff5990b8c9.zip#nuimo==1.0.0
|
--only-binary=all https://github.com/getSenic/nuimo-linux-python/archive/29fc42987f74d8090d0e2382e8f248ff5990b8c9.zip#nuimo==1.0.0
|
||||||
@ -1275,7 +1276,7 @@ zeroconf==0.19.1
|
|||||||
ziggo-mediabox-xl==1.0.0
|
ziggo-mediabox-xl==1.0.0
|
||||||
|
|
||||||
# homeassistant.components.zha
|
# homeassistant.components.zha
|
||||||
zigpy-xbee==0.0.1
|
zigpy-xbee==0.0.2
|
||||||
|
|
||||||
# homeassistant.components.zha
|
# homeassistant.components.zha
|
||||||
zigpy==0.0.1
|
zigpy==0.0.1
|
||||||
|
1
setup.py
1
setup.py
@ -61,6 +61,7 @@ REQUIRES = [
|
|||||||
'chardet==3.0.4',
|
'chardet==3.0.4',
|
||||||
'astral==1.5',
|
'astral==1.5',
|
||||||
'certifi>=2017.4.17',
|
'certifi>=2017.4.17',
|
||||||
|
'attrs==17.4.0',
|
||||||
]
|
]
|
||||||
|
|
||||||
MIN_PY_VERSION = '.'.join(map(
|
MIN_PY_VERSION = '.'.join(map(
|
||||||
|
@ -317,10 +317,10 @@ def mock_component(hass, component):
|
|||||||
hass.config.components.add(component)
|
hass.config.components.add(component)
|
||||||
|
|
||||||
|
|
||||||
def mock_registry(hass):
|
def mock_registry(hass, mock_entries=None):
|
||||||
"""Mock the Entity Registry."""
|
"""Mock the Entity Registry."""
|
||||||
registry = entity_registry.EntityRegistry(hass)
|
registry = entity_registry.EntityRegistry(hass)
|
||||||
registry.entities = {}
|
registry.entities = mock_entries or {}
|
||||||
hass.data[entity_platform.DATA_REGISTRY] = registry
|
hass.data[entity_platform.DATA_REGISTRY] = registry
|
||||||
return registry
|
return registry
|
||||||
|
|
||||||
|
@ -401,11 +401,17 @@ def test_lock(hass):
|
|||||||
assert appliance['friendlyName'] == "Test lock"
|
assert appliance['friendlyName'] == "Test lock"
|
||||||
assert_endpoint_capabilities(appliance, 'Alexa.LockController')
|
assert_endpoint_capabilities(appliance, 'Alexa.LockController')
|
||||||
|
|
||||||
yield from assert_request_calls_service(
|
_, msg = yield from assert_request_calls_service(
|
||||||
'Alexa.LockController', 'Lock', 'lock#test',
|
'Alexa.LockController', 'Lock', 'lock#test',
|
||||||
'lock.lock',
|
'lock.lock',
|
||||||
hass)
|
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
|
@asyncio.coroutine
|
||||||
def test_media_player(hass):
|
def test_media_player(hass):
|
||||||
@ -511,14 +517,14 @@ def test_media_player(hass):
|
|||||||
'Alexa.StepSpeaker', 'AdjustVolume', 'media_player#test',
|
'Alexa.StepSpeaker', 'AdjustVolume', 'media_player#test',
|
||||||
'media_player.volume_set',
|
'media_player.volume_set',
|
||||||
hass,
|
hass,
|
||||||
payload={'volume': 20})
|
payload={'volumeSteps': 20})
|
||||||
assert call.data['volume_level'] == 0.95
|
assert call.data['volume_level'] == 0.95
|
||||||
|
|
||||||
call, _ = yield from assert_request_calls_service(
|
call, _ = yield from assert_request_calls_service(
|
||||||
'Alexa.StepSpeaker', 'AdjustVolume', 'media_player#test',
|
'Alexa.StepSpeaker', 'AdjustVolume', 'media_player#test',
|
||||||
'media_player.volume_set',
|
'media_player.volume_set',
|
||||||
hass,
|
hass,
|
||||||
payload={'volume': -20})
|
payload={'volumeSteps': -20})
|
||||||
assert call.data['volume_level'] == 0.55
|
assert call.data['volume_level'] == 0.55
|
||||||
|
|
||||||
|
|
||||||
@ -1095,6 +1101,23 @@ def test_report_lock_state(hass):
|
|||||||
properties.assert_equal('Alexa.LockController', 'lockState', 'JAMMED')
|
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
|
@asyncio.coroutine
|
||||||
def reported_properties(hass, endpoint):
|
def reported_properties(hass, endpoint):
|
||||||
"""Use ReportState to get properties and return them.
|
"""Use ReportState to get properties and return them.
|
||||||
@ -1118,7 +1141,7 @@ class _ReportedProperties(object):
|
|||||||
for prop in self.properties:
|
for prop in self.properties:
|
||||||
if prop['namespace'] == namespace and prop['name'] == name:
|
if prop['namespace'] == namespace and prop['name'] == name:
|
||||||
assert prop['value'] == value
|
assert prop['value'] == value
|
||||||
return prop
|
return prop
|
||||||
|
|
||||||
assert False, 'property %s:%s not in %r' % (
|
assert False, 'property %s:%s not in %r' % (
|
||||||
namespace,
|
namespace,
|
||||||
|
@ -12,7 +12,7 @@ import homeassistant.loader as loader
|
|||||||
from homeassistant.exceptions import PlatformNotReady
|
from homeassistant.exceptions import PlatformNotReady
|
||||||
from homeassistant.components import group
|
from homeassistant.components import group
|
||||||
from homeassistant.helpers.entity_component import EntityComponent
|
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
|
from homeassistant.helpers import discovery
|
||||||
import homeassistant.util.dt as dt_util
|
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)
|
extracted = component.async_extract_from_service(call, expand_group=False)
|
||||||
assert extracted == [test_group]
|
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
|
||||||
|
@ -9,7 +9,7 @@ import homeassistant.loader as loader
|
|||||||
from homeassistant.helpers.entity import generate_entity_id
|
from homeassistant.helpers.entity import generate_entity_id
|
||||||
from homeassistant.helpers.entity_component import (
|
from homeassistant.helpers.entity_component import (
|
||||||
EntityComponent, DEFAULT_SCAN_INTERVAL)
|
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
|
import homeassistant.util.dt as dt_util
|
||||||
|
|
||||||
@ -21,6 +21,32 @@ _LOGGER = logging.getLogger(__name__)
|
|||||||
DOMAIN = "test_domain"
|
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):
|
class TestHelpersEntityPlatform(unittest.TestCase):
|
||||||
"""Test homeassistant.helpers.entity_component module."""
|
"""Test homeassistant.helpers.entity_component module."""
|
||||||
|
|
||||||
@ -433,3 +459,34 @@ def test_entity_with_name_and_entity_id_getting_registered(hass):
|
|||||||
MockEntity(unique_id='1234', name='bla',
|
MockEntity(unique_id='1234', name='bla',
|
||||||
entity_id='test_domain.world')])
|
entity_id='test_domain.world')])
|
||||||
assert 'test_domain.world' in hass.states.async_entity_ids()
|
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'
|
||||||
|
|
||||||
|
|
||||||
|
@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'
|
||||||
|
@ -9,6 +9,9 @@ from homeassistant.helpers import entity_registry
|
|||||||
from tests.common import mock_registry
|
from tests.common import mock_registry
|
||||||
|
|
||||||
|
|
||||||
|
YAML__OPEN_PATH = 'homeassistant.util.yaml.open'
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def registry(hass):
|
def registry(hass):
|
||||||
"""Return an empty, loaded, registry."""
|
"""Return an empty, loaded, registry."""
|
||||||
@ -82,13 +85,12 @@ def test_save_timer_reset_on_subsequent_save(hass, registry):
|
|||||||
@asyncio.coroutine
|
@asyncio.coroutine
|
||||||
def test_loading_saving_data(hass, registry):
|
def test_loading_saving_data(hass, registry):
|
||||||
"""Test that we load/save data correctly."""
|
"""Test that we load/save data correctly."""
|
||||||
yaml_path = 'homeassistant.util.yaml.open'
|
|
||||||
orig_entry1 = registry.async_get_or_create('light', 'hue', '1234')
|
orig_entry1 = registry.async_get_or_create('light', 'hue', '1234')
|
||||||
orig_entry2 = registry.async_get_or_create('light', 'hue', '5678')
|
orig_entry2 = registry.async_get_or_create('light', 'hue', '5678')
|
||||||
|
|
||||||
assert len(registry.entities) == 2
|
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()
|
yield from registry._async_save()
|
||||||
|
|
||||||
# Mock open calls are: open file, context enter, write, context leave
|
# 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)
|
registry2 = entity_registry.EntityRegistry(hass)
|
||||||
|
|
||||||
with patch('os.path.isfile', return_value=True), \
|
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()
|
yield from registry2._async_load()
|
||||||
|
|
||||||
# Ensure same order
|
# Ensure same order
|
||||||
@ -133,3 +135,30 @@ def test_is_registered(registry):
|
|||||||
entry = registry.async_get_or_create('light', 'hue', '1234')
|
entry = registry.async_get_or_create('light', 'hue', '1234')
|
||||||
assert registry.async_is_registered(entry.entity_id)
|
assert registry.async_is_registered(entry.entity_id)
|
||||||
assert not registry.async_is_registered('light.non_existing')
|
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
|
||||||
|
Loading…
x
Reference in New Issue
Block a user