Skip to content
Merged

0.63.1 #12334

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 14 additions & 4 deletions homeassistant/components/alexa/smart_home.py
Original file line number Diff line number Diff line change
Expand Up @@ -328,8 +328,9 @@ def properties_retrievable(self):
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):
Expand Down Expand Up @@ -1064,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
Expand Down Expand Up @@ -1168,7 +1178,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)

Expand Down
7 changes: 0 additions & 7 deletions homeassistant/components/binary_sensor/netatmo.py
Original file line number Diff line number Diff line change
Expand Up @@ -131,8 +131,6 @@ def __init__(self, data, camera_name, module_name, home,
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

Expand All @@ -141,11 +139,6 @@ def name(self):
"""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."""
Expand Down
7 changes: 0 additions & 7 deletions homeassistant/components/camera/netatmo.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,8 +67,6 @@ def __init__(self, data, camera_name, home, camera_type, verify_ssl):
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):
Expand Down Expand Up @@ -112,8 +110,3 @@ def model(self):
elif self._cameratype == "NACamera":
return "Welcome"
return None

@property
def unique_id(self):
"""Return the unique ID for this camera."""
return self._unique_id
1 change: 0 additions & 1 deletion homeassistant/components/remote/xiaomi_miio.py
Original file line number Diff line number Diff line change
Expand Up @@ -226,7 +226,6 @@ def async_turn_off(self, **kwargs):
_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
Expand Down
6 changes: 0 additions & 6 deletions homeassistant/components/sensor/netatmo.py
Original file line number Diff line number Diff line change
Expand Up @@ -113,18 +113,12 @@ def __init__(self, netatmo_data, module_name, sensor_type):
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."""
Expand Down
2 changes: 1 addition & 1 deletion homeassistant/components/zha/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
REQUIREMENTS = [
'bellows==0.5.0',
'zigpy==0.0.1',
'zigpy-xbee==0.0.1',
'zigpy-xbee==0.0.2',
]

DOMAIN = 'zha'
Expand Down
2 changes: 1 addition & 1 deletion homeassistant/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
5 changes: 4 additions & 1 deletion homeassistant/helpers/entity.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -225,7 +228,7 @@ def async_update_ha_state(self, force_refresh=False):
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

Expand Down
10 changes: 5 additions & 5 deletions homeassistant/helpers/entity_component.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,19 +40,19 @@ def __init__(self, logger, domain, hass,
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):
Expand Down Expand Up @@ -190,7 +190,7 @@ def _async_reset(self):
yield from asyncio.wait(tasks, loop=self.hass.loop)

self._platforms = {
'core': self._platforms['core']
self.domain: self._platforms[self.domain]
}
self.config = None

Expand Down
11 changes: 10 additions & 1 deletion homeassistant/helpers/entity_platform.py
Original file line number Diff line number Diff line change
Expand Up @@ -209,10 +209,15 @@ def _async_add_entity(self, entity, update_before_add, component_entities,
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)
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
Expand All @@ -239,8 +244,12 @@ def _async_add_entity(self, entity, update_before_add, component_entities,
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)
Expand Down
30 changes: 22 additions & 8 deletions homeassistant/helpers/entity_registry.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,22 +11,37 @@
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."""

Expand Down Expand Up @@ -65,11 +80,10 @@ def async_get_or_create(self, domain, platform, unique_id, *,

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',
Expand Down Expand Up @@ -98,11 +112,11 @@ def _async_load(self):
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
Expand Down
1 change: 1 addition & 0 deletions homeassistant/package_constraints.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
2 changes: 1 addition & 1 deletion homeassistant/setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -206,7 +206,7 @@ def log_error(msg):
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
Expand Down
3 changes: 2 additions & 1 deletion requirements_all.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -1275,7 +1276,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
1 change: 1 addition & 0 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@
'chardet==3.0.4',
'astral==1.5',
'certifi>=2017.4.17',
'attrs==17.4.0',
]

MIN_PY_VERSION = '.'.join(map(
Expand Down
4 changes: 2 additions & 2 deletions tests/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
31 changes: 27 additions & 4 deletions tests/components/alexa/test_smart_home.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down Expand Up @@ -511,14 +517,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


Expand Down Expand Up @@ -1095,6 +1101,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.
Expand All @@ -1118,7 +1141,7 @@ def assert_equal(self, namespace, name, value):
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,
Expand Down
Loading