Skip to content

Commit

Permalink
Report properties for some Alexa interfaces
Browse files Browse the repository at this point in the history
Fixes (mostly) #11874.

Other interfaces will need properties implemented as well.

Implementing properties for just PowerController seems sufficient to
eliminate the "There was a problem." error for any device that supports
it, even if other interfaces are supported. Of course the additional
properties will be reported incorrectly in the Alexa app.

Includes a minor bugfix: `reportable` was previously placed incorrectly
in the responses, so Amazon was ignoring it.
  • Loading branch information
bitglue committed Jan 28, 2018
1 parent 80377f6 commit 5eae9d1
Show file tree
Hide file tree
Showing 2 changed files with 142 additions and 6 deletions.
47 changes: 44 additions & 3 deletions homeassistant/components/alexa/smart_home.py
Expand Up @@ -17,7 +17,7 @@
SERVICE_MEDIA_PREVIOUS_TRACK, SERVICE_MEDIA_STOP,
SERVICE_SET_COVER_POSITION, SERVICE_TURN_OFF, SERVICE_TURN_ON,
SERVICE_UNLOCK, SERVICE_VOLUME_SET, TEMP_FAHRENHEIT, TEMP_CELSIUS,
CONF_UNIT_OF_MEASUREMENT)
CONF_UNIT_OF_MEASUREMENT, STATE_LOCKED, STATE_UNLOCKED, STATE_ON)
from .const import CONF_FILTER, CONF_ENTITY_CONFIG

_LOGGER = logging.getLogger(__name__)
Expand Down Expand Up @@ -245,9 +245,8 @@ def serialize_discovery(self):
'properties': {
'supported': self.properties_supported(),
'proactivelyReported': self.properties_proactively_reported(),
'retrievable': self.properties_retrievable(),
},
# XXX this is incorrect, but the tests assert it
'retrievable': self.properties_retrievable(),
}

# pylint: disable=assignment-from-none
Expand All @@ -271,11 +270,41 @@ class _AlexaPowerController(_AlexaInterface):
def name(self):
return 'Alexa.PowerController'

def properties_supported(self):
return [{'name': 'powerState'}]

def properties_retrievable(self):
return True

def get_property(self, name):
if name != 'powerState':
raise _UnsupportedProperty(name)

if self.entity.state == STATE_ON:
return 'ON'
return 'OFF'


class _AlexaLockController(_AlexaInterface):
def name(self):
return 'Alexa.LockController'

def properties_supported(self):
return [{'name': 'lockState'}]

def properties_retrievable(self):
return True

def get_property(self, name):
if name != 'lockState':
raise _UnsupportedProperty(name)

if self.entity.state == STATE_LOCKED:
return 'LOCKED'
elif self.entity.state == STATE_UNLOCKED:
return 'UNLOCKED'
return 'JAMMED'


class _AlexaSceneController(_AlexaInterface):
def __init__(self, entity, supports_deactivation):
Expand All @@ -290,6 +319,18 @@ class _AlexaBrightnessController(_AlexaInterface):
def name(self):
return 'Alexa.BrightnessController'

def properties_supported(self):
return [{'name': 'brightness'}]

def properties_retrievable(self):
return True

def get_property(self, name):
if name != 'brightness':
raise _UnsupportedProperty(name)

return round(self.entity.attributes['brightness'] / 255.0 * 100)


class _AlexaColorController(_AlexaInterface):
def name(self):
Expand Down
101 changes: 98 additions & 3 deletions tests/components/alexa/test_smart_home.py
Expand Up @@ -5,9 +5,11 @@

import pytest

from homeassistant.const import TEMP_FAHRENHEIT, CONF_UNIT_OF_MEASUREMENT
from homeassistant.const import (
TEMP_FAHRENHEIT, CONF_UNIT_OF_MEASUREMENT, STATE_LOCKED, STATE_UNLOCKED,
STATE_UNKNOWN, STATE_ON, STATE_OFF)
from homeassistant.setup import async_setup_component
from homeassistant.components import alexa
from homeassistant.components import alexa, light
from homeassistant.components.alexa import smart_home
from homeassistant.helpers import entityfilter

Expand Down Expand Up @@ -379,8 +381,8 @@ def test_discovery_request(hass):
assert len(appliance['capabilities']) == 1
capability = appliance['capabilities'][0]
assert capability['interface'] == 'Alexa.TemperatureSensor'
assert capability['retrievable'] is True
properties = capability['properties']
assert properties['retrievable'] is True
assert {'name': 'temperature'} in properties['supported']
continue

Expand Down Expand Up @@ -1248,6 +1250,99 @@ def test_api_report_temperature(hass):
assert prop['value'] == {'value': 42.0, 'scale': 'FAHRENHEIT'}


@asyncio.coroutine
def test_report_lock_state(hass):
"""Test LockController implements lockState property."""
hass.states.async_set(
'lock.locked', STATE_LOCKED, {})
hass.states.async_set(
'lock.unlocked', STATE_UNLOCKED, {})
hass.states.async_set(
'lock.unknown', STATE_UNKNOWN, {})

request = get_new_request('Alexa', 'ReportState', 'lock#locked')
msg = yield from smart_home.async_handle_message(
hass, DEFAULT_CONFIG, request)
yield from hass.async_block_till_done()

properties = msg['context']['properties']
assert len(properties) == 1
prop = properties[0]
assert prop['namespace'] == 'Alexa.LockController'
assert prop['name'] == 'lockState'
assert prop['value'] == 'LOCKED'

request = get_new_request('Alexa', 'ReportState', 'lock#unlocked')
msg = yield from smart_home.async_handle_message(
hass, DEFAULT_CONFIG, request)
yield from hass.async_block_till_done()

properties = msg['context']['properties']
prop = properties[0]
assert prop['value'] == 'UNLOCKED'

request = get_new_request('Alexa', 'ReportState', 'lock#unknown')
msg = yield from smart_home.async_handle_message(
hass, DEFAULT_CONFIG, request)
yield from hass.async_block_till_done()

properties = msg['context']['properties']
prop = properties[0]
assert prop['value'] == 'JAMMED'


@asyncio.coroutine
def test_report_power_state(hass):
"""Test PowerController implements powerState property."""
hass.states.async_set(
'switch.on', STATE_ON, {})
hass.states.async_set(
'switch.off', STATE_OFF, {})

request = get_new_request('Alexa', 'ReportState', 'switch#on')
msg = yield from smart_home.async_handle_message(
hass, DEFAULT_CONFIG, request)
yield from hass.async_block_till_done()

properties = msg['context']['properties']
assert len(properties) == 1
prop = properties[0]
assert prop['namespace'] == 'Alexa.PowerController'
assert prop['name'] == 'powerState'
assert prop['value'] == 'ON'

request = get_new_request('Alexa', 'ReportState', 'switch#off')
msg = yield from smart_home.async_handle_message(
hass, DEFAULT_CONFIG, request)
yield from hass.async_block_till_done()


@asyncio.coroutine
def test_report_brightness(hass):
"""Test BrightnessController implements brightness property."""
hass.states.async_set(
'light.test', STATE_ON, {
'brightness': 128,
'supported_features': light.SUPPORT_BRIGHTNESS,
}
)

request = get_new_request('Alexa', 'ReportState', 'light.test')
msg = yield from smart_home.async_handle_message(
hass, DEFAULT_CONFIG, request)
yield from hass.async_block_till_done()

for prop in msg['context']['properties']:
if (
prop['namespace'] == 'Alexa.BrightnessController'
and prop['name'] == 'brightness'
):
assert prop['value'] == 50
break
else:
assert False, 'no brightness property present'


@asyncio.coroutine
def test_entity_config(hass):
"""Test that we can configure things via entity config."""
Expand Down

0 comments on commit 5eae9d1

Please sign in to comment.