2017-09-16 19:35:28 +00:00
|
|
|
"""Test for smart home alexa support."""
|
2018-01-23 18:45:28 +00:00
|
|
|
import json
|
2017-10-07 20:31:57 +00:00
|
|
|
from uuid import uuid4
|
2017-09-16 19:35:28 +00:00
|
|
|
|
|
|
|
import pytest
|
|
|
|
|
2018-08-20 12:18:07 +00:00
|
|
|
from homeassistant.core import Context, callback
|
2018-01-29 01:00:34 +00:00
|
|
|
from homeassistant.const import (
|
2018-08-22 07:17:29 +00:00
|
|
|
TEMP_CELSIUS, TEMP_FAHRENHEIT, STATE_LOCKED,
|
|
|
|
STATE_UNLOCKED, STATE_UNKNOWN)
|
2018-01-23 18:45:28 +00:00
|
|
|
from homeassistant.setup import async_setup_component
|
2018-01-30 04:33:39 +00:00
|
|
|
from homeassistant.components import alexa
|
2017-09-16 19:35:28 +00:00
|
|
|
from homeassistant.components.alexa import smart_home
|
2019-01-03 21:28:43 +00:00
|
|
|
from homeassistant.components.alexa.auth import Auth
|
2017-11-18 05:10:24 +00:00
|
|
|
from homeassistant.helpers import entityfilter
|
2017-09-16 19:35:28 +00:00
|
|
|
|
|
|
|
from tests.common import async_mock_service
|
|
|
|
|
2019-01-03 21:28:43 +00:00
|
|
|
|
|
|
|
async def get_access_token():
|
|
|
|
"""Return a test access token."""
|
|
|
|
return "thisisnotanacesstoken"
|
|
|
|
|
|
|
|
|
|
|
|
TEST_URL = "https://api.amazonalexa.com/v3/events"
|
|
|
|
TEST_TOKEN_URL = "https://api.amazon.com/auth/o2/token"
|
|
|
|
|
|
|
|
DEFAULT_CONFIG = smart_home.Config(
|
|
|
|
endpoint=TEST_URL,
|
|
|
|
async_get_access_token=get_access_token,
|
|
|
|
should_expose=lambda entity_id: True)
|
2017-11-18 05:10:24 +00:00
|
|
|
|
2017-09-16 19:35:28 +00:00
|
|
|
|
2018-08-20 12:18:07 +00:00
|
|
|
@pytest.fixture
|
|
|
|
def events(hass):
|
|
|
|
"""Fixture that catches alexa events."""
|
|
|
|
events = []
|
|
|
|
hass.bus.async_listen(
|
|
|
|
smart_home.EVENT_ALEXA_SMART_HOME,
|
|
|
|
callback(lambda e: events.append(e))
|
|
|
|
)
|
|
|
|
yield events
|
|
|
|
|
|
|
|
|
2017-10-07 20:31:57 +00:00
|
|
|
def get_new_request(namespace, name, endpoint=None):
|
|
|
|
"""Generate a new API message."""
|
|
|
|
raw_msg = {
|
|
|
|
'directive': {
|
|
|
|
'header': {
|
|
|
|
'namespace': namespace,
|
|
|
|
'name': name,
|
|
|
|
'messageId': str(uuid4()),
|
|
|
|
'correlationToken': str(uuid4()),
|
|
|
|
'payloadVersion': '3',
|
|
|
|
},
|
|
|
|
'endpoint': {
|
|
|
|
'scope': {
|
|
|
|
'type': 'BearerToken',
|
|
|
|
'token': str(uuid4()),
|
|
|
|
},
|
|
|
|
'endpointId': endpoint,
|
|
|
|
},
|
|
|
|
'payload': {},
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if not endpoint:
|
|
|
|
raw_msg['directive'].pop('endpoint')
|
|
|
|
|
|
|
|
return raw_msg
|
|
|
|
|
|
|
|
|
Refactor Alexa API, fix thermostats (#17969)
* Refactor Alexa API to use objects for requests
This introduces _AlexaDirective to stand in for the previous model of passing
basic dict and list data structures to and from handlers. This gives a more
expressive platform for functionality common to most or all handlers.
I had two use cases in mind:
1) Most responses should include current properties. In the case of locks and
thermostats, the response must include the properties or Alexa will give the
user a vague error like "Hmm, $device is not responding." Locks currently work,
but thermostats do not. I wanted a way to automatically include properties in
all responses. This is implemented in a subsequent commit.
2) The previous model had a 1:1 mapping between Alexa endpoints and Home
Assistant entities. This works most of the time, but sometimes it's not so
great. For example, my Z-wave thermostat shows as three devices in Alexa: one
for the temperature sensor, one for the heat, and one for the AC. I'd like to
merge these into one device from Alexa's perspective. I believe this will be
facilitated with the `endpoint` attribute on `_AlexaDirective`.
* Include properties in all Alexa responses
The added _AlexaResponse class provides a richer vocabulary for handlers.
Among that vocabulary is .merge_context_properties(), which is invoked
automatically for any request directed at an endpoint. This adds all supported
properties to the response as recommended by the Alexa API docs, and in some
cases (locks, thermostats at least) the user will get an error "Hmm, $device is
not responding" if properties are not provided in the response.
* Fix setting temperature with Alexa thermostats
Fixes https://github.com/home-assistant/home-assistant/issues/16577
2018-10-30 02:16:35 +00:00
|
|
|
def test_create_api_message_defaults(hass):
|
2017-10-07 20:31:57 +00:00
|
|
|
"""Create a API message response of a request with defaults."""
|
|
|
|
request = get_new_request('Alexa.PowerController', 'TurnOn', 'switch#xy')
|
Refactor Alexa API, fix thermostats (#17969)
* Refactor Alexa API to use objects for requests
This introduces _AlexaDirective to stand in for the previous model of passing
basic dict and list data structures to and from handlers. This gives a more
expressive platform for functionality common to most or all handlers.
I had two use cases in mind:
1) Most responses should include current properties. In the case of locks and
thermostats, the response must include the properties or Alexa will give the
user a vague error like "Hmm, $device is not responding." Locks currently work,
but thermostats do not. I wanted a way to automatically include properties in
all responses. This is implemented in a subsequent commit.
2) The previous model had a 1:1 mapping between Alexa endpoints and Home
Assistant entities. This works most of the time, but sometimes it's not so
great. For example, my Z-wave thermostat shows as three devices in Alexa: one
for the temperature sensor, one for the heat, and one for the AC. I'd like to
merge these into one device from Alexa's perspective. I believe this will be
facilitated with the `endpoint` attribute on `_AlexaDirective`.
* Include properties in all Alexa responses
The added _AlexaResponse class provides a richer vocabulary for handlers.
Among that vocabulary is .merge_context_properties(), which is invoked
automatically for any request directed at an endpoint. This adds all supported
properties to the response as recommended by the Alexa API docs, and in some
cases (locks, thermostats at least) the user will get an error "Hmm, $device is
not responding" if properties are not provided in the response.
* Fix setting temperature with Alexa thermostats
Fixes https://github.com/home-assistant/home-assistant/issues/16577
2018-10-30 02:16:35 +00:00
|
|
|
directive_header = request['directive']['header']
|
|
|
|
directive = smart_home._AlexaDirective(request)
|
2017-10-07 20:31:57 +00:00
|
|
|
|
Refactor Alexa API, fix thermostats (#17969)
* Refactor Alexa API to use objects for requests
This introduces _AlexaDirective to stand in for the previous model of passing
basic dict and list data structures to and from handlers. This gives a more
expressive platform for functionality common to most or all handlers.
I had two use cases in mind:
1) Most responses should include current properties. In the case of locks and
thermostats, the response must include the properties or Alexa will give the
user a vague error like "Hmm, $device is not responding." Locks currently work,
but thermostats do not. I wanted a way to automatically include properties in
all responses. This is implemented in a subsequent commit.
2) The previous model had a 1:1 mapping between Alexa endpoints and Home
Assistant entities. This works most of the time, but sometimes it's not so
great. For example, my Z-wave thermostat shows as three devices in Alexa: one
for the temperature sensor, one for the heat, and one for the AC. I'd like to
merge these into one device from Alexa's perspective. I believe this will be
facilitated with the `endpoint` attribute on `_AlexaDirective`.
* Include properties in all Alexa responses
The added _AlexaResponse class provides a richer vocabulary for handlers.
Among that vocabulary is .merge_context_properties(), which is invoked
automatically for any request directed at an endpoint. This adds all supported
properties to the response as recommended by the Alexa API docs, and in some
cases (locks, thermostats at least) the user will get an error "Hmm, $device is
not responding" if properties are not provided in the response.
* Fix setting temperature with Alexa thermostats
Fixes https://github.com/home-assistant/home-assistant/issues/16577
2018-10-30 02:16:35 +00:00
|
|
|
msg = directive.response(payload={'test': 3})._response
|
2017-10-07 20:31:57 +00:00
|
|
|
|
|
|
|
assert 'event' in msg
|
|
|
|
msg = msg['event']
|
2017-09-16 19:35:28 +00:00
|
|
|
|
|
|
|
assert msg['header']['messageId'] is not None
|
Refactor Alexa API, fix thermostats (#17969)
* Refactor Alexa API to use objects for requests
This introduces _AlexaDirective to stand in for the previous model of passing
basic dict and list data structures to and from handlers. This gives a more
expressive platform for functionality common to most or all handlers.
I had two use cases in mind:
1) Most responses should include current properties. In the case of locks and
thermostats, the response must include the properties or Alexa will give the
user a vague error like "Hmm, $device is not responding." Locks currently work,
but thermostats do not. I wanted a way to automatically include properties in
all responses. This is implemented in a subsequent commit.
2) The previous model had a 1:1 mapping between Alexa endpoints and Home
Assistant entities. This works most of the time, but sometimes it's not so
great. For example, my Z-wave thermostat shows as three devices in Alexa: one
for the temperature sensor, one for the heat, and one for the AC. I'd like to
merge these into one device from Alexa's perspective. I believe this will be
facilitated with the `endpoint` attribute on `_AlexaDirective`.
* Include properties in all Alexa responses
The added _AlexaResponse class provides a richer vocabulary for handlers.
Among that vocabulary is .merge_context_properties(), which is invoked
automatically for any request directed at an endpoint. This adds all supported
properties to the response as recommended by the Alexa API docs, and in some
cases (locks, thermostats at least) the user will get an error "Hmm, $device is
not responding" if properties are not provided in the response.
* Fix setting temperature with Alexa thermostats
Fixes https://github.com/home-assistant/home-assistant/issues/16577
2018-10-30 02:16:35 +00:00
|
|
|
assert msg['header']['messageId'] != directive_header['messageId']
|
2017-10-07 20:31:57 +00:00
|
|
|
assert msg['header']['correlationToken'] == \
|
Refactor Alexa API, fix thermostats (#17969)
* Refactor Alexa API to use objects for requests
This introduces _AlexaDirective to stand in for the previous model of passing
basic dict and list data structures to and from handlers. This gives a more
expressive platform for functionality common to most or all handlers.
I had two use cases in mind:
1) Most responses should include current properties. In the case of locks and
thermostats, the response must include the properties or Alexa will give the
user a vague error like "Hmm, $device is not responding." Locks currently work,
but thermostats do not. I wanted a way to automatically include properties in
all responses. This is implemented in a subsequent commit.
2) The previous model had a 1:1 mapping between Alexa endpoints and Home
Assistant entities. This works most of the time, but sometimes it's not so
great. For example, my Z-wave thermostat shows as three devices in Alexa: one
for the temperature sensor, one for the heat, and one for the AC. I'd like to
merge these into one device from Alexa's perspective. I believe this will be
facilitated with the `endpoint` attribute on `_AlexaDirective`.
* Include properties in all Alexa responses
The added _AlexaResponse class provides a richer vocabulary for handlers.
Among that vocabulary is .merge_context_properties(), which is invoked
automatically for any request directed at an endpoint. This adds all supported
properties to the response as recommended by the Alexa API docs, and in some
cases (locks, thermostats at least) the user will get an error "Hmm, $device is
not responding" if properties are not provided in the response.
* Fix setting temperature with Alexa thermostats
Fixes https://github.com/home-assistant/home-assistant/issues/16577
2018-10-30 02:16:35 +00:00
|
|
|
directive_header['correlationToken']
|
2017-10-07 20:31:57 +00:00
|
|
|
assert msg['header']['name'] == 'Response'
|
|
|
|
assert msg['header']['namespace'] == 'Alexa'
|
|
|
|
assert msg['header']['payloadVersion'] == '3'
|
|
|
|
|
|
|
|
assert 'test' in msg['payload']
|
|
|
|
assert msg['payload']['test'] == 3
|
|
|
|
|
Refactor Alexa API, fix thermostats (#17969)
* Refactor Alexa API to use objects for requests
This introduces _AlexaDirective to stand in for the previous model of passing
basic dict and list data structures to and from handlers. This gives a more
expressive platform for functionality common to most or all handlers.
I had two use cases in mind:
1) Most responses should include current properties. In the case of locks and
thermostats, the response must include the properties or Alexa will give the
user a vague error like "Hmm, $device is not responding." Locks currently work,
but thermostats do not. I wanted a way to automatically include properties in
all responses. This is implemented in a subsequent commit.
2) The previous model had a 1:1 mapping between Alexa endpoints and Home
Assistant entities. This works most of the time, but sometimes it's not so
great. For example, my Z-wave thermostat shows as three devices in Alexa: one
for the temperature sensor, one for the heat, and one for the AC. I'd like to
merge these into one device from Alexa's perspective. I believe this will be
facilitated with the `endpoint` attribute on `_AlexaDirective`.
* Include properties in all Alexa responses
The added _AlexaResponse class provides a richer vocabulary for handlers.
Among that vocabulary is .merge_context_properties(), which is invoked
automatically for any request directed at an endpoint. This adds all supported
properties to the response as recommended by the Alexa API docs, and in some
cases (locks, thermostats at least) the user will get an error "Hmm, $device is
not responding" if properties are not provided in the response.
* Fix setting temperature with Alexa thermostats
Fixes https://github.com/home-assistant/home-assistant/issues/16577
2018-10-30 02:16:35 +00:00
|
|
|
assert msg['endpoint'] == request['directive']['endpoint']
|
|
|
|
assert msg['endpoint'] is not request['directive']['endpoint']
|
2017-10-07 20:31:57 +00:00
|
|
|
|
|
|
|
|
|
|
|
def test_create_api_message_special():
|
|
|
|
"""Create a API message response of a request with non defaults."""
|
|
|
|
request = get_new_request('Alexa.PowerController', 'TurnOn')
|
Refactor Alexa API, fix thermostats (#17969)
* Refactor Alexa API to use objects for requests
This introduces _AlexaDirective to stand in for the previous model of passing
basic dict and list data structures to and from handlers. This gives a more
expressive platform for functionality common to most or all handlers.
I had two use cases in mind:
1) Most responses should include current properties. In the case of locks and
thermostats, the response must include the properties or Alexa will give the
user a vague error like "Hmm, $device is not responding." Locks currently work,
but thermostats do not. I wanted a way to automatically include properties in
all responses. This is implemented in a subsequent commit.
2) The previous model had a 1:1 mapping between Alexa endpoints and Home
Assistant entities. This works most of the time, but sometimes it's not so
great. For example, my Z-wave thermostat shows as three devices in Alexa: one
for the temperature sensor, one for the heat, and one for the AC. I'd like to
merge these into one device from Alexa's perspective. I believe this will be
facilitated with the `endpoint` attribute on `_AlexaDirective`.
* Include properties in all Alexa responses
The added _AlexaResponse class provides a richer vocabulary for handlers.
Among that vocabulary is .merge_context_properties(), which is invoked
automatically for any request directed at an endpoint. This adds all supported
properties to the response as recommended by the Alexa API docs, and in some
cases (locks, thermostats at least) the user will get an error "Hmm, $device is
not responding" if properties are not provided in the response.
* Fix setting temperature with Alexa thermostats
Fixes https://github.com/home-assistant/home-assistant/issues/16577
2018-10-30 02:16:35 +00:00
|
|
|
directive_header = request['directive']['header']
|
|
|
|
directive_header.pop('correlationToken')
|
|
|
|
directive = smart_home._AlexaDirective(request)
|
2017-10-07 20:31:57 +00:00
|
|
|
|
Refactor Alexa API, fix thermostats (#17969)
* Refactor Alexa API to use objects for requests
This introduces _AlexaDirective to stand in for the previous model of passing
basic dict and list data structures to and from handlers. This gives a more
expressive platform for functionality common to most or all handlers.
I had two use cases in mind:
1) Most responses should include current properties. In the case of locks and
thermostats, the response must include the properties or Alexa will give the
user a vague error like "Hmm, $device is not responding." Locks currently work,
but thermostats do not. I wanted a way to automatically include properties in
all responses. This is implemented in a subsequent commit.
2) The previous model had a 1:1 mapping between Alexa endpoints and Home
Assistant entities. This works most of the time, but sometimes it's not so
great. For example, my Z-wave thermostat shows as three devices in Alexa: one
for the temperature sensor, one for the heat, and one for the AC. I'd like to
merge these into one device from Alexa's perspective. I believe this will be
facilitated with the `endpoint` attribute on `_AlexaDirective`.
* Include properties in all Alexa responses
The added _AlexaResponse class provides a richer vocabulary for handlers.
Among that vocabulary is .merge_context_properties(), which is invoked
automatically for any request directed at an endpoint. This adds all supported
properties to the response as recommended by the Alexa API docs, and in some
cases (locks, thermostats at least) the user will get an error "Hmm, $device is
not responding" if properties are not provided in the response.
* Fix setting temperature with Alexa thermostats
Fixes https://github.com/home-assistant/home-assistant/issues/16577
2018-10-30 02:16:35 +00:00
|
|
|
msg = directive.response('testName', 'testNameSpace')._response
|
2017-10-07 20:31:57 +00:00
|
|
|
|
|
|
|
assert 'event' in msg
|
|
|
|
msg = msg['event']
|
|
|
|
|
|
|
|
assert msg['header']['messageId'] is not None
|
Refactor Alexa API, fix thermostats (#17969)
* Refactor Alexa API to use objects for requests
This introduces _AlexaDirective to stand in for the previous model of passing
basic dict and list data structures to and from handlers. This gives a more
expressive platform for functionality common to most or all handlers.
I had two use cases in mind:
1) Most responses should include current properties. In the case of locks and
thermostats, the response must include the properties or Alexa will give the
user a vague error like "Hmm, $device is not responding." Locks currently work,
but thermostats do not. I wanted a way to automatically include properties in
all responses. This is implemented in a subsequent commit.
2) The previous model had a 1:1 mapping between Alexa endpoints and Home
Assistant entities. This works most of the time, but sometimes it's not so
great. For example, my Z-wave thermostat shows as three devices in Alexa: one
for the temperature sensor, one for the heat, and one for the AC. I'd like to
merge these into one device from Alexa's perspective. I believe this will be
facilitated with the `endpoint` attribute on `_AlexaDirective`.
* Include properties in all Alexa responses
The added _AlexaResponse class provides a richer vocabulary for handlers.
Among that vocabulary is .merge_context_properties(), which is invoked
automatically for any request directed at an endpoint. This adds all supported
properties to the response as recommended by the Alexa API docs, and in some
cases (locks, thermostats at least) the user will get an error "Hmm, $device is
not responding" if properties are not provided in the response.
* Fix setting temperature with Alexa thermostats
Fixes https://github.com/home-assistant/home-assistant/issues/16577
2018-10-30 02:16:35 +00:00
|
|
|
assert msg['header']['messageId'] != directive_header['messageId']
|
2017-10-07 20:31:57 +00:00
|
|
|
assert 'correlationToken' not in msg['header']
|
2017-09-16 19:35:28 +00:00
|
|
|
assert msg['header']['name'] == 'testName'
|
|
|
|
assert msg['header']['namespace'] == 'testNameSpace'
|
2017-10-07 20:31:57 +00:00
|
|
|
assert msg['header']['payloadVersion'] == '3'
|
|
|
|
|
2017-09-16 19:35:28 +00:00
|
|
|
assert msg['payload'] == {}
|
2017-10-07 20:31:57 +00:00
|
|
|
assert 'endpoint' not in msg
|
2017-09-16 19:35:28 +00:00
|
|
|
|
|
|
|
|
2018-10-29 21:57:27 +00:00
|
|
|
async def test_wrong_version(hass):
|
2017-09-16 19:35:28 +00:00
|
|
|
"""Test with wrong version."""
|
2017-10-07 20:31:57 +00:00
|
|
|
msg = get_new_request('Alexa.PowerController', 'TurnOn')
|
|
|
|
msg['directive']['header']['payloadVersion'] = '2'
|
2017-09-16 19:35:28 +00:00
|
|
|
|
|
|
|
with pytest.raises(AssertionError):
|
2018-10-29 21:57:27 +00:00
|
|
|
await smart_home.async_handle_message(hass, DEFAULT_CONFIG, msg)
|
2017-09-16 19:35:28 +00:00
|
|
|
|
|
|
|
|
2018-10-29 21:57:27 +00:00
|
|
|
async def discovery_test(device, hass, expected_endpoints=1):
|
2017-09-16 19:35:28 +00:00
|
|
|
"""Test alexa discovery request."""
|
2017-10-07 20:31:57 +00:00
|
|
|
request = get_new_request('Alexa.Discovery', 'Discover')
|
2017-09-16 19:35:28 +00:00
|
|
|
|
2017-11-17 17:14:22 +00:00
|
|
|
# setup test devices
|
2018-01-30 04:33:39 +00:00
|
|
|
hass.states.async_set(*device)
|
2017-09-16 19:35:28 +00:00
|
|
|
|
2018-10-29 21:57:27 +00:00
|
|
|
msg = await smart_home.async_handle_message(
|
2018-01-30 04:33:39 +00:00
|
|
|
hass, DEFAULT_CONFIG, request)
|
2017-09-16 19:35:28 +00:00
|
|
|
|
2018-01-30 04:33:39 +00:00
|
|
|
assert 'event' in msg
|
|
|
|
msg = msg['event']
|
2017-11-16 05:44:27 +00:00
|
|
|
|
2018-01-30 04:33:39 +00:00
|
|
|
assert msg['header']['name'] == 'Discover.Response'
|
|
|
|
assert msg['header']['namespace'] == 'Alexa.Discovery'
|
|
|
|
endpoints = msg['payload']['endpoints']
|
|
|
|
assert len(endpoints) == expected_endpoints
|
2017-11-17 17:14:22 +00:00
|
|
|
|
2018-01-30 04:33:39 +00:00
|
|
|
if expected_endpoints == 1:
|
|
|
|
return endpoints[0]
|
2018-07-23 08:16:05 +00:00
|
|
|
if expected_endpoints > 1:
|
2018-01-30 04:33:39 +00:00
|
|
|
return endpoints
|
|
|
|
return None
|
2017-11-17 17:14:22 +00:00
|
|
|
|
|
|
|
|
2019-01-10 23:52:21 +00:00
|
|
|
def get_capability(capabilities, capability_name):
|
|
|
|
"""Search a set of capabilities for a specific one."""
|
|
|
|
for capability in capabilities:
|
|
|
|
if capability['interface'] == capability_name:
|
|
|
|
return capability
|
|
|
|
|
|
|
|
return None
|
|
|
|
|
|
|
|
|
2018-01-30 04:33:39 +00:00
|
|
|
def assert_endpoint_capabilities(endpoint, *interfaces):
|
|
|
|
"""Assert the endpoint supports the given interfaces.
|
2017-11-17 17:14:22 +00:00
|
|
|
|
2018-01-30 04:33:39 +00:00
|
|
|
Returns a set of capabilities, in case you want to assert more things about
|
|
|
|
them.
|
|
|
|
"""
|
|
|
|
capabilities = endpoint['capabilities']
|
|
|
|
supported = set(
|
|
|
|
feature['interface']
|
|
|
|
for feature in capabilities)
|
2017-11-17 17:14:22 +00:00
|
|
|
|
2018-01-30 04:33:39 +00:00
|
|
|
assert supported == set(interfaces)
|
|
|
|
return capabilities
|
2017-11-17 17:14:22 +00:00
|
|
|
|
|
|
|
|
2018-10-29 21:57:27 +00:00
|
|
|
async def test_switch(hass, events):
|
2018-01-30 04:33:39 +00:00
|
|
|
"""Test switch discovery."""
|
|
|
|
device = ('switch.test', 'on', {'friendly_name': "Test switch"})
|
2018-10-29 21:57:27 +00:00
|
|
|
appliance = await discovery_test(device, hass)
|
2017-11-17 17:14:22 +00:00
|
|
|
|
2018-01-30 04:33:39 +00:00
|
|
|
assert appliance['endpointId'] == 'switch#test'
|
|
|
|
assert appliance['displayCategories'][0] == "SWITCH"
|
|
|
|
assert appliance['friendlyName'] == "Test switch"
|
2019-01-10 23:52:21 +00:00
|
|
|
assert_endpoint_capabilities(
|
|
|
|
appliance,
|
|
|
|
'Alexa.PowerController',
|
|
|
|
'Alexa.EndpointHealth',
|
|
|
|
)
|
2017-11-17 17:14:22 +00:00
|
|
|
|
2018-10-29 21:57:27 +00:00
|
|
|
await assert_power_controller_works(
|
2018-01-30 04:33:39 +00:00
|
|
|
'switch#test',
|
|
|
|
'switch.turn_on',
|
|
|
|
'switch.turn_off',
|
|
|
|
hass)
|
2017-11-17 17:14:22 +00:00
|
|
|
|
2018-10-29 21:57:27 +00:00
|
|
|
properties = await reported_properties(hass, 'switch#test')
|
2018-01-30 04:33:39 +00:00
|
|
|
properties.assert_equal('Alexa.PowerController', 'powerState', 'ON')
|
2018-01-26 18:40:39 +00:00
|
|
|
|
2018-01-30 04:33:39 +00:00
|
|
|
|
2018-10-29 21:57:27 +00:00
|
|
|
async def test_light(hass):
|
2018-01-30 04:33:39 +00:00
|
|
|
"""Test light discovery."""
|
|
|
|
device = ('light.test_1', 'on', {'friendly_name': "Test light 1"})
|
2018-10-29 21:57:27 +00:00
|
|
|
appliance = await discovery_test(device, hass)
|
2018-01-30 04:33:39 +00:00
|
|
|
|
|
|
|
assert appliance['endpointId'] == 'light#test_1'
|
|
|
|
assert appliance['displayCategories'][0] == "LIGHT"
|
|
|
|
assert appliance['friendlyName'] == "Test light 1"
|
2019-01-10 23:52:21 +00:00
|
|
|
assert_endpoint_capabilities(
|
|
|
|
appliance,
|
|
|
|
'Alexa.PowerController',
|
|
|
|
'Alexa.EndpointHealth',
|
|
|
|
)
|
2018-01-30 04:33:39 +00:00
|
|
|
|
2018-10-29 21:57:27 +00:00
|
|
|
await assert_power_controller_works(
|
2018-01-30 04:33:39 +00:00
|
|
|
'light#test_1',
|
|
|
|
'light.turn_on',
|
|
|
|
'light.turn_off',
|
|
|
|
hass)
|
|
|
|
|
|
|
|
|
2018-10-29 21:57:27 +00:00
|
|
|
async def test_dimmable_light(hass):
|
2018-01-30 04:33:39 +00:00
|
|
|
"""Test dimmable light discovery."""
|
|
|
|
device = (
|
|
|
|
'light.test_2', 'on', {
|
|
|
|
'brightness': 128,
|
|
|
|
'friendly_name': "Test light 2", 'supported_features': 1
|
2018-01-26 18:40:39 +00:00
|
|
|
})
|
2018-10-29 21:57:27 +00:00
|
|
|
appliance = await discovery_test(device, hass)
|
2018-01-26 18:40:39 +00:00
|
|
|
|
2018-01-30 04:33:39 +00:00
|
|
|
assert appliance['endpointId'] == 'light#test_2'
|
|
|
|
assert appliance['displayCategories'][0] == "LIGHT"
|
|
|
|
assert appliance['friendlyName'] == "Test light 2"
|
2017-10-07 20:31:57 +00:00
|
|
|
|
2018-01-30 04:33:39 +00:00
|
|
|
assert_endpoint_capabilities(
|
|
|
|
appliance,
|
|
|
|
'Alexa.BrightnessController',
|
|
|
|
'Alexa.PowerController',
|
2019-01-10 23:52:21 +00:00
|
|
|
'Alexa.EndpointHealth',
|
2018-01-30 04:33:39 +00:00
|
|
|
)
|
2017-09-16 19:35:28 +00:00
|
|
|
|
2018-10-29 21:57:27 +00:00
|
|
|
properties = await reported_properties(hass, 'light#test_2')
|
2018-01-30 04:33:39 +00:00
|
|
|
properties.assert_equal('Alexa.PowerController', 'powerState', 'ON')
|
|
|
|
properties.assert_equal('Alexa.BrightnessController', 'brightness', 50)
|
2017-09-16 19:35:28 +00:00
|
|
|
|
2018-10-29 21:57:27 +00:00
|
|
|
call, _ = await assert_request_calls_service(
|
2018-01-30 04:33:39 +00:00
|
|
|
'Alexa.BrightnessController', 'SetBrightness', 'light#test_2',
|
|
|
|
'light.turn_on',
|
|
|
|
hass,
|
|
|
|
payload={'brightness': '50'})
|
|
|
|
assert call.data['brightness_pct'] == 50
|
|
|
|
|
|
|
|
|
2018-10-29 21:57:27 +00:00
|
|
|
async def test_color_light(hass):
|
2018-01-30 04:33:39 +00:00
|
|
|
"""Test color light discovery."""
|
|
|
|
device = (
|
|
|
|
'light.test_3',
|
|
|
|
'on',
|
|
|
|
{
|
|
|
|
'friendly_name': "Test light 3",
|
|
|
|
'supported_features': 19,
|
|
|
|
'min_mireds': 142,
|
|
|
|
'color_temp': '333',
|
|
|
|
}
|
|
|
|
)
|
2018-10-29 21:57:27 +00:00
|
|
|
appliance = await discovery_test(device, hass)
|
2018-01-30 04:33:39 +00:00
|
|
|
|
|
|
|
assert appliance['endpointId'] == 'light#test_3'
|
|
|
|
assert appliance['displayCategories'][0] == "LIGHT"
|
|
|
|
assert appliance['friendlyName'] == "Test light 3"
|
|
|
|
|
|
|
|
assert_endpoint_capabilities(
|
|
|
|
appliance,
|
|
|
|
'Alexa.BrightnessController',
|
|
|
|
'Alexa.PowerController',
|
|
|
|
'Alexa.ColorController',
|
|
|
|
'Alexa.ColorTemperatureController',
|
2019-01-10 23:52:21 +00:00
|
|
|
'Alexa.EndpointHealth',
|
2018-01-30 04:33:39 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
# IncreaseColorTemperature and DecreaseColorTemperature have their own
|
|
|
|
# tests
|
|
|
|
|
|
|
|
|
2018-10-29 21:57:27 +00:00
|
|
|
async def test_script(hass):
|
2018-01-30 04:33:39 +00:00
|
|
|
"""Test script discovery."""
|
|
|
|
device = ('script.test', 'off', {'friendly_name': "Test script"})
|
2018-10-29 21:57:27 +00:00
|
|
|
appliance = await discovery_test(device, hass)
|
2018-01-30 04:33:39 +00:00
|
|
|
|
|
|
|
assert appliance['endpointId'] == 'script#test'
|
|
|
|
assert appliance['displayCategories'][0] == "ACTIVITY_TRIGGER"
|
|
|
|
assert appliance['friendlyName'] == "Test script"
|
|
|
|
|
|
|
|
(capability,) = assert_endpoint_capabilities(
|
|
|
|
appliance,
|
2019-01-10 23:52:21 +00:00
|
|
|
'Alexa.SceneController',
|
|
|
|
)
|
2018-01-30 04:33:39 +00:00
|
|
|
assert not capability['supportsDeactivation']
|
|
|
|
|
2018-10-29 21:57:27 +00:00
|
|
|
await assert_scene_controller_works(
|
2018-01-29 00:43:27 +00:00
|
|
|
'script#test',
|
2018-01-30 04:33:39 +00:00
|
|
|
'script.turn_on',
|
|
|
|
None,
|
|
|
|
hass)
|
|
|
|
|
|
|
|
|
2018-10-29 21:57:27 +00:00
|
|
|
async def test_cancelable_script(hass):
|
2018-01-30 04:33:39 +00:00
|
|
|
"""Test cancalable script discovery."""
|
|
|
|
device = (
|
|
|
|
'script.test_2',
|
|
|
|
'off',
|
|
|
|
{'friendly_name': "Test script 2", 'can_cancel': True},
|
|
|
|
)
|
2018-10-29 21:57:27 +00:00
|
|
|
appliance = await discovery_test(device, hass)
|
2018-01-30 04:33:39 +00:00
|
|
|
|
|
|
|
assert appliance['endpointId'] == 'script#test_2'
|
|
|
|
(capability,) = assert_endpoint_capabilities(
|
|
|
|
appliance,
|
2019-01-10 23:52:21 +00:00
|
|
|
'Alexa.SceneController',
|
|
|
|
)
|
2018-01-30 04:33:39 +00:00
|
|
|
assert capability['supportsDeactivation']
|
|
|
|
|
2018-10-29 21:57:27 +00:00
|
|
|
await assert_scene_controller_works(
|
2018-01-29 00:43:27 +00:00
|
|
|
'script#test_2',
|
2018-01-30 04:33:39 +00:00
|
|
|
'script.turn_on',
|
|
|
|
'script.turn_off',
|
|
|
|
hass)
|
|
|
|
|
|
|
|
|
2018-10-29 21:57:27 +00:00
|
|
|
async def test_input_boolean(hass):
|
2018-01-30 04:33:39 +00:00
|
|
|
"""Test input boolean discovery."""
|
|
|
|
device = (
|
|
|
|
'input_boolean.test',
|
|
|
|
'off',
|
|
|
|
{'friendly_name': "Test input boolean"},
|
|
|
|
)
|
2018-10-29 21:57:27 +00:00
|
|
|
appliance = await discovery_test(device, hass)
|
2018-01-30 04:33:39 +00:00
|
|
|
|
|
|
|
assert appliance['endpointId'] == 'input_boolean#test'
|
|
|
|
assert appliance['displayCategories'][0] == "OTHER"
|
|
|
|
assert appliance['friendlyName'] == "Test input boolean"
|
2019-01-10 23:52:21 +00:00
|
|
|
assert_endpoint_capabilities(
|
|
|
|
appliance,
|
|
|
|
'Alexa.PowerController',
|
|
|
|
'Alexa.EndpointHealth',
|
|
|
|
)
|
2018-01-30 04:33:39 +00:00
|
|
|
|
2018-10-29 21:57:27 +00:00
|
|
|
await assert_power_controller_works(
|
2018-01-29 00:43:27 +00:00
|
|
|
'input_boolean#test',
|
2018-01-30 04:33:39 +00:00
|
|
|
'input_boolean.turn_on',
|
|
|
|
'input_boolean.turn_off',
|
|
|
|
hass)
|
|
|
|
|
|
|
|
|
2018-10-29 21:57:27 +00:00
|
|
|
async def test_scene(hass):
|
2018-01-30 04:33:39 +00:00
|
|
|
"""Test scene discovery."""
|
|
|
|
device = ('scene.test', 'off', {'friendly_name': "Test scene"})
|
2018-10-29 21:57:27 +00:00
|
|
|
appliance = await discovery_test(device, hass)
|
2018-01-30 04:33:39 +00:00
|
|
|
|
|
|
|
assert appliance['endpointId'] == 'scene#test'
|
|
|
|
assert appliance['displayCategories'][0] == "SCENE_TRIGGER"
|
|
|
|
assert appliance['friendlyName'] == "Test scene"
|
|
|
|
|
|
|
|
(capability,) = assert_endpoint_capabilities(
|
|
|
|
appliance,
|
2019-01-10 23:52:21 +00:00
|
|
|
'Alexa.SceneController'
|
|
|
|
)
|
2018-01-30 04:33:39 +00:00
|
|
|
assert not capability['supportsDeactivation']
|
|
|
|
|
2018-10-29 21:57:27 +00:00
|
|
|
await assert_scene_controller_works(
|
2018-01-29 00:43:27 +00:00
|
|
|
'scene#test',
|
2018-01-30 04:33:39 +00:00
|
|
|
'scene.turn_on',
|
|
|
|
None,
|
|
|
|
hass)
|
|
|
|
|
|
|
|
|
2018-10-29 21:57:27 +00:00
|
|
|
async def test_fan(hass):
|
2018-01-30 04:33:39 +00:00
|
|
|
"""Test fan discovery."""
|
|
|
|
device = ('fan.test_1', 'off', {'friendly_name': "Test fan 1"})
|
2018-10-29 21:57:27 +00:00
|
|
|
appliance = await discovery_test(device, hass)
|
2018-01-30 04:33:39 +00:00
|
|
|
|
|
|
|
assert appliance['endpointId'] == 'fan#test_1'
|
|
|
|
assert appliance['displayCategories'][0] == "OTHER"
|
|
|
|
assert appliance['friendlyName'] == "Test fan 1"
|
2019-01-10 23:52:21 +00:00
|
|
|
assert_endpoint_capabilities(
|
|
|
|
appliance,
|
|
|
|
'Alexa.PowerController',
|
|
|
|
'Alexa.EndpointHealth',
|
|
|
|
)
|
2018-01-30 04:33:39 +00:00
|
|
|
|
|
|
|
|
2018-10-29 21:57:27 +00:00
|
|
|
async def test_variable_fan(hass):
|
2018-01-30 04:33:39 +00:00
|
|
|
"""Test fan discovery.
|
|
|
|
|
|
|
|
This one has variable speed.
|
|
|
|
"""
|
|
|
|
device = (
|
|
|
|
'fan.test_2',
|
|
|
|
'off', {
|
|
|
|
'friendly_name': "Test fan 2",
|
|
|
|
'supported_features': 1,
|
|
|
|
'speed_list': ['low', 'medium', 'high'],
|
|
|
|
'speed': 'high',
|
|
|
|
}
|
|
|
|
)
|
2018-10-29 21:57:27 +00:00
|
|
|
appliance = await discovery_test(device, hass)
|
2018-01-30 04:33:39 +00:00
|
|
|
|
|
|
|
assert appliance['endpointId'] == 'fan#test_2'
|
|
|
|
assert appliance['displayCategories'][0] == "OTHER"
|
|
|
|
assert appliance['friendlyName'] == "Test fan 2"
|
|
|
|
|
|
|
|
assert_endpoint_capabilities(
|
|
|
|
appliance,
|
|
|
|
'Alexa.PercentageController',
|
|
|
|
'Alexa.PowerController',
|
2019-01-10 23:52:21 +00:00
|
|
|
'Alexa.EndpointHealth',
|
2018-01-30 04:33:39 +00:00
|
|
|
)
|
|
|
|
|
2018-10-29 21:57:27 +00:00
|
|
|
call, _ = await assert_request_calls_service(
|
2018-01-30 04:33:39 +00:00
|
|
|
'Alexa.PercentageController', 'SetPercentage', 'fan#test_2',
|
|
|
|
'fan.set_speed',
|
|
|
|
hass,
|
|
|
|
payload={'percentage': '50'})
|
|
|
|
assert call.data['speed'] == 'medium'
|
|
|
|
|
2018-10-29 21:57:27 +00:00
|
|
|
await assert_percentage_changes(
|
2018-01-30 04:33:39 +00:00
|
|
|
hass,
|
|
|
|
[('high', '-5'), ('off', '5'), ('low', '-80')],
|
|
|
|
'Alexa.PercentageController', 'AdjustPercentage', 'fan#test_2',
|
|
|
|
'percentageDelta',
|
|
|
|
'fan.set_speed',
|
|
|
|
'speed')
|
|
|
|
|
|
|
|
|
2018-10-29 21:57:27 +00:00
|
|
|
async def test_lock(hass):
|
2018-01-30 04:33:39 +00:00
|
|
|
"""Test lock discovery."""
|
|
|
|
device = ('lock.test', 'off', {'friendly_name': "Test lock"})
|
2018-10-29 21:57:27 +00:00
|
|
|
appliance = await discovery_test(device, hass)
|
2018-01-30 04:33:39 +00:00
|
|
|
|
|
|
|
assert appliance['endpointId'] == 'lock#test'
|
|
|
|
assert appliance['displayCategories'][0] == "SMARTLOCK"
|
|
|
|
assert appliance['friendlyName'] == "Test lock"
|
2019-01-10 23:52:21 +00:00
|
|
|
assert_endpoint_capabilities(
|
|
|
|
appliance,
|
|
|
|
'Alexa.LockController',
|
|
|
|
'Alexa.EndpointHealth',
|
|
|
|
)
|
2018-01-30 04:33:39 +00:00
|
|
|
|
2018-10-29 21:57:27 +00:00
|
|
|
_, msg = await assert_request_calls_service(
|
2018-01-30 04:33:39 +00:00
|
|
|
'Alexa.LockController', 'Lock', 'lock#test',
|
|
|
|
'lock.lock',
|
|
|
|
hass)
|
|
|
|
|
2018-02-12 07:20:54 +00:00
|
|
|
# always return LOCKED for now
|
|
|
|
properties = msg['context']['properties'][0]
|
|
|
|
assert properties['name'] == 'lockState'
|
|
|
|
assert properties['namespace'] == 'Alexa.LockController'
|
|
|
|
assert properties['value'] == 'LOCKED'
|
|
|
|
|
2018-01-30 04:33:39 +00:00
|
|
|
|
2018-10-29 21:57:27 +00:00
|
|
|
async def test_media_player(hass):
|
2018-01-30 04:33:39 +00:00
|
|
|
"""Test media player discovery."""
|
|
|
|
device = (
|
|
|
|
'media_player.test',
|
|
|
|
'off', {
|
|
|
|
'friendly_name': "Test media player",
|
|
|
|
'supported_features': 0x59bd,
|
|
|
|
'volume_level': 0.75
|
|
|
|
}
|
|
|
|
)
|
2018-10-29 21:57:27 +00:00
|
|
|
appliance = await discovery_test(device, hass)
|
2018-01-30 04:33:39 +00:00
|
|
|
|
|
|
|
assert appliance['endpointId'] == 'media_player#test'
|
|
|
|
assert appliance['displayCategories'][0] == "TV"
|
|
|
|
assert appliance['friendlyName'] == "Test media player"
|
|
|
|
|
|
|
|
assert_endpoint_capabilities(
|
|
|
|
appliance,
|
|
|
|
'Alexa.InputController',
|
|
|
|
'Alexa.PowerController',
|
|
|
|
'Alexa.Speaker',
|
2018-02-06 00:02:08 +00:00
|
|
|
'Alexa.StepSpeaker',
|
2018-01-30 04:33:39 +00:00
|
|
|
'Alexa.PlaybackController',
|
2019-01-10 23:52:21 +00:00
|
|
|
'Alexa.EndpointHealth',
|
2018-01-30 04:33:39 +00:00
|
|
|
)
|
|
|
|
|
2018-10-29 21:57:27 +00:00
|
|
|
await assert_power_controller_works(
|
2018-01-29 00:43:27 +00:00
|
|
|
'media_player#test',
|
2018-01-30 04:33:39 +00:00
|
|
|
'media_player.turn_on',
|
|
|
|
'media_player.turn_off',
|
|
|
|
hass)
|
|
|
|
|
2018-10-29 21:57:27 +00:00
|
|
|
await assert_request_calls_service(
|
2018-01-30 04:33:39 +00:00
|
|
|
'Alexa.PlaybackController', 'Play', 'media_player#test',
|
|
|
|
'media_player.media_play',
|
|
|
|
hass)
|
|
|
|
|
2018-10-29 21:57:27 +00:00
|
|
|
await assert_request_calls_service(
|
2018-01-30 04:33:39 +00:00
|
|
|
'Alexa.PlaybackController', 'Pause', 'media_player#test',
|
|
|
|
'media_player.media_pause',
|
|
|
|
hass)
|
|
|
|
|
2018-10-29 21:57:27 +00:00
|
|
|
await assert_request_calls_service(
|
2018-01-30 04:33:39 +00:00
|
|
|
'Alexa.PlaybackController', 'Stop', 'media_player#test',
|
|
|
|
'media_player.media_stop',
|
|
|
|
hass)
|
|
|
|
|
2018-10-29 21:57:27 +00:00
|
|
|
await assert_request_calls_service(
|
2018-01-30 04:33:39 +00:00
|
|
|
'Alexa.PlaybackController', 'Next', 'media_player#test',
|
|
|
|
'media_player.media_next_track',
|
|
|
|
hass)
|
|
|
|
|
2018-10-29 21:57:27 +00:00
|
|
|
await assert_request_calls_service(
|
2018-01-30 04:33:39 +00:00
|
|
|
'Alexa.PlaybackController', 'Previous', 'media_player#test',
|
|
|
|
'media_player.media_previous_track',
|
|
|
|
hass)
|
|
|
|
|
2018-10-29 21:57:27 +00:00
|
|
|
call, _ = await assert_request_calls_service(
|
2018-01-30 04:33:39 +00:00
|
|
|
'Alexa.Speaker', 'SetVolume', 'media_player#test',
|
|
|
|
'media_player.volume_set',
|
|
|
|
hass,
|
|
|
|
payload={'volume': 50})
|
|
|
|
assert call.data['volume_level'] == 0.5
|
|
|
|
|
2018-10-29 21:57:27 +00:00
|
|
|
call, _ = await assert_request_calls_service(
|
2018-01-30 04:33:39 +00:00
|
|
|
'Alexa.Speaker', 'SetMute', 'media_player#test',
|
|
|
|
'media_player.volume_mute',
|
|
|
|
hass,
|
|
|
|
payload={'mute': True})
|
|
|
|
assert call.data['is_volume_muted']
|
|
|
|
|
2018-10-29 21:57:27 +00:00
|
|
|
call, _, = await assert_request_calls_service(
|
2018-01-30 04:33:39 +00:00
|
|
|
'Alexa.Speaker', 'SetMute', 'media_player#test',
|
|
|
|
'media_player.volume_mute',
|
|
|
|
hass,
|
|
|
|
payload={'mute': False})
|
|
|
|
assert not call.data['is_volume_muted']
|
|
|
|
|
2018-10-29 21:57:27 +00:00
|
|
|
await assert_percentage_changes(
|
2018-01-30 04:33:39 +00:00
|
|
|
hass,
|
|
|
|
[(0.7, '-5'), (0.8, '5'), (0, '-80')],
|
|
|
|
'Alexa.Speaker', 'AdjustVolume', 'media_player#test',
|
|
|
|
'volume',
|
|
|
|
'media_player.volume_set',
|
|
|
|
'volume_level')
|
|
|
|
|
2018-10-29 21:57:27 +00:00
|
|
|
call, _ = await assert_request_calls_service(
|
2018-02-06 00:02:08 +00:00
|
|
|
'Alexa.StepSpeaker', 'SetMute', 'media_player#test',
|
|
|
|
'media_player.volume_mute',
|
|
|
|
hass,
|
|
|
|
payload={'mute': True})
|
|
|
|
assert call.data['is_volume_muted']
|
|
|
|
|
2018-10-29 21:57:27 +00:00
|
|
|
call, _, = await assert_request_calls_service(
|
2018-02-06 00:02:08 +00:00
|
|
|
'Alexa.StepSpeaker', 'SetMute', 'media_player#test',
|
|
|
|
'media_player.volume_mute',
|
|
|
|
hass,
|
|
|
|
payload={'mute': False})
|
|
|
|
assert not call.data['is_volume_muted']
|
|
|
|
|
2018-10-29 21:57:27 +00:00
|
|
|
call, _ = await assert_request_calls_service(
|
2018-02-06 00:02:08 +00:00
|
|
|
'Alexa.StepSpeaker', 'AdjustVolume', 'media_player#test',
|
2018-02-17 13:54:15 +00:00
|
|
|
'media_player.volume_up',
|
2018-02-06 00:02:08 +00:00
|
|
|
hass,
|
2018-02-11 19:25:05 +00:00
|
|
|
payload={'volumeSteps': 20})
|
2018-02-06 00:02:08 +00:00
|
|
|
|
2018-10-29 21:57:27 +00:00
|
|
|
call, _ = await assert_request_calls_service(
|
2018-02-06 00:02:08 +00:00
|
|
|
'Alexa.StepSpeaker', 'AdjustVolume', 'media_player#test',
|
2018-02-17 13:54:15 +00:00
|
|
|
'media_player.volume_down',
|
2018-02-06 00:02:08 +00:00
|
|
|
hass,
|
2018-02-11 19:25:05 +00:00
|
|
|
payload={'volumeSteps': -20})
|
2018-02-06 00:02:08 +00:00
|
|
|
|
2018-01-30 04:33:39 +00:00
|
|
|
|
2018-10-29 21:57:27 +00:00
|
|
|
async def test_alert(hass):
|
2018-01-30 04:33:39 +00:00
|
|
|
"""Test alert discovery."""
|
|
|
|
device = ('alert.test', 'off', {'friendly_name': "Test alert"})
|
2018-10-29 21:57:27 +00:00
|
|
|
appliance = await discovery_test(device, hass)
|
2018-01-30 04:33:39 +00:00
|
|
|
|
|
|
|
assert appliance['endpointId'] == 'alert#test'
|
|
|
|
assert appliance['displayCategories'][0] == "OTHER"
|
|
|
|
assert appliance['friendlyName'] == "Test alert"
|
2019-01-10 23:52:21 +00:00
|
|
|
assert_endpoint_capabilities(
|
|
|
|
appliance,
|
|
|
|
'Alexa.PowerController',
|
|
|
|
'Alexa.EndpointHealth',
|
|
|
|
)
|
2018-01-30 04:33:39 +00:00
|
|
|
|
2018-10-29 21:57:27 +00:00
|
|
|
await assert_power_controller_works(
|
2018-01-29 00:43:27 +00:00
|
|
|
'alert#test',
|
2018-01-30 04:33:39 +00:00
|
|
|
'alert.turn_on',
|
|
|
|
'alert.turn_off',
|
|
|
|
hass)
|
|
|
|
|
|
|
|
|
2018-10-29 21:57:27 +00:00
|
|
|
async def test_automation(hass):
|
2018-01-30 04:33:39 +00:00
|
|
|
"""Test automation discovery."""
|
|
|
|
device = ('automation.test', 'off', {'friendly_name': "Test automation"})
|
2018-10-29 21:57:27 +00:00
|
|
|
appliance = await discovery_test(device, hass)
|
2018-01-30 04:33:39 +00:00
|
|
|
|
|
|
|
assert appliance['endpointId'] == 'automation#test'
|
|
|
|
assert appliance['displayCategories'][0] == "OTHER"
|
|
|
|
assert appliance['friendlyName'] == "Test automation"
|
2019-01-10 23:52:21 +00:00
|
|
|
assert_endpoint_capabilities(
|
|
|
|
appliance,
|
|
|
|
'Alexa.PowerController',
|
|
|
|
'Alexa.EndpointHealth',
|
|
|
|
)
|
2018-01-30 04:33:39 +00:00
|
|
|
|
2018-10-29 21:57:27 +00:00
|
|
|
await assert_power_controller_works(
|
2018-01-29 00:43:27 +00:00
|
|
|
'automation#test',
|
2018-01-30 04:33:39 +00:00
|
|
|
'automation.turn_on',
|
|
|
|
'automation.turn_off',
|
|
|
|
hass)
|
|
|
|
|
|
|
|
|
2018-10-29 21:57:27 +00:00
|
|
|
async def test_group(hass):
|
2018-01-30 04:33:39 +00:00
|
|
|
"""Test group discovery."""
|
|
|
|
device = ('group.test', 'off', {'friendly_name': "Test group"})
|
2018-10-29 21:57:27 +00:00
|
|
|
appliance = await discovery_test(device, hass)
|
2018-01-30 04:33:39 +00:00
|
|
|
|
|
|
|
assert appliance['endpointId'] == 'group#test'
|
2018-02-22 07:42:23 +00:00
|
|
|
assert appliance['displayCategories'][0] == "OTHER"
|
2018-01-30 04:33:39 +00:00
|
|
|
assert appliance['friendlyName'] == "Test group"
|
2019-01-10 23:52:21 +00:00
|
|
|
assert_endpoint_capabilities(
|
|
|
|
appliance,
|
|
|
|
'Alexa.PowerController',
|
|
|
|
'Alexa.EndpointHealth',
|
|
|
|
)
|
2018-01-30 04:33:39 +00:00
|
|
|
|
2018-10-29 21:57:27 +00:00
|
|
|
await assert_power_controller_works(
|
2018-01-29 00:43:27 +00:00
|
|
|
'group#test',
|
2018-01-30 04:33:39 +00:00
|
|
|
'homeassistant.turn_on',
|
|
|
|
'homeassistant.turn_off',
|
|
|
|
hass)
|
|
|
|
|
|
|
|
|
2018-10-29 21:57:27 +00:00
|
|
|
async def test_cover(hass):
|
2018-01-30 04:33:39 +00:00
|
|
|
"""Test cover discovery."""
|
|
|
|
device = (
|
|
|
|
'cover.test',
|
|
|
|
'off', {
|
|
|
|
'friendly_name': "Test cover",
|
|
|
|
'supported_features': 255,
|
|
|
|
'position': 30,
|
|
|
|
}
|
|
|
|
)
|
2018-10-29 21:57:27 +00:00
|
|
|
appliance = await discovery_test(device, hass)
|
2018-01-30 04:33:39 +00:00
|
|
|
|
|
|
|
assert appliance['endpointId'] == 'cover#test'
|
|
|
|
assert appliance['displayCategories'][0] == "DOOR"
|
|
|
|
assert appliance['friendlyName'] == "Test cover"
|
|
|
|
|
|
|
|
assert_endpoint_capabilities(
|
|
|
|
appliance,
|
|
|
|
'Alexa.PercentageController',
|
|
|
|
'Alexa.PowerController',
|
2019-01-10 23:52:21 +00:00
|
|
|
'Alexa.EndpointHealth',
|
2018-01-30 04:33:39 +00:00
|
|
|
)
|
|
|
|
|
2018-10-29 21:57:27 +00:00
|
|
|
await assert_power_controller_works(
|
2018-01-29 00:43:27 +00:00
|
|
|
'cover#test',
|
2018-01-30 04:33:39 +00:00
|
|
|
'cover.open_cover',
|
|
|
|
'cover.close_cover',
|
|
|
|
hass)
|
|
|
|
|
2018-10-29 21:57:27 +00:00
|
|
|
call, _ = await assert_request_calls_service(
|
2018-01-30 04:33:39 +00:00
|
|
|
'Alexa.PercentageController', 'SetPercentage', 'cover#test',
|
|
|
|
'cover.set_cover_position',
|
|
|
|
hass,
|
|
|
|
payload={'percentage': '50'})
|
|
|
|
assert call.data['position'] == 50
|
|
|
|
|
2018-10-29 21:57:27 +00:00
|
|
|
await assert_percentage_changes(
|
2018-01-30 04:33:39 +00:00
|
|
|
hass,
|
|
|
|
[(25, '-5'), (35, '5'), (0, '-80')],
|
|
|
|
'Alexa.PercentageController', 'AdjustPercentage', 'cover#test',
|
|
|
|
'percentageDelta',
|
|
|
|
'cover.set_cover_position',
|
|
|
|
'position')
|
|
|
|
|
|
|
|
|
2018-10-29 21:57:27 +00:00
|
|
|
async def assert_percentage_changes(
|
2018-01-30 04:33:39 +00:00
|
|
|
hass,
|
|
|
|
adjustments,
|
|
|
|
namespace,
|
|
|
|
name,
|
|
|
|
endpoint,
|
|
|
|
parameter,
|
|
|
|
service,
|
|
|
|
changed_parameter):
|
|
|
|
"""Assert an API request making percentage changes works.
|
|
|
|
|
|
|
|
AdjustPercentage, AdjustBrightness, etc. are examples of such requests.
|
|
|
|
"""
|
|
|
|
for result_volume, adjustment in adjustments:
|
|
|
|
if parameter:
|
|
|
|
payload = {parameter: adjustment}
|
|
|
|
else:
|
|
|
|
payload = {}
|
|
|
|
|
2018-10-29 21:57:27 +00:00
|
|
|
call, _ = await assert_request_calls_service(
|
2018-01-30 04:33:39 +00:00
|
|
|
namespace, name, endpoint, service,
|
|
|
|
hass,
|
|
|
|
payload=payload)
|
|
|
|
assert call.data[changed_parameter] == result_volume
|
|
|
|
|
|
|
|
|
2018-10-29 21:57:27 +00:00
|
|
|
async def test_temp_sensor(hass):
|
2018-01-30 04:33:39 +00:00
|
|
|
"""Test temperature sensor discovery."""
|
|
|
|
device = (
|
|
|
|
'sensor.test_temp',
|
|
|
|
'42',
|
|
|
|
{
|
|
|
|
'friendly_name': "Test Temp Sensor",
|
|
|
|
'unit_of_measurement': TEMP_FAHRENHEIT,
|
|
|
|
}
|
|
|
|
)
|
2018-10-29 21:57:27 +00:00
|
|
|
appliance = await discovery_test(device, hass)
|
2018-01-30 04:33:39 +00:00
|
|
|
|
|
|
|
assert appliance['endpointId'] == 'sensor#test_temp'
|
|
|
|
assert appliance['displayCategories'][0] == 'TEMPERATURE_SENSOR'
|
|
|
|
assert appliance['friendlyName'] == 'Test Temp Sensor'
|
|
|
|
|
2019-01-10 23:52:21 +00:00
|
|
|
capabilities = assert_endpoint_capabilities(
|
2018-01-30 04:33:39 +00:00
|
|
|
appliance,
|
2019-01-10 23:52:21 +00:00
|
|
|
'Alexa.TemperatureSensor',
|
|
|
|
'Alexa.EndpointHealth',
|
|
|
|
)
|
|
|
|
|
|
|
|
temp_sensor_capability = get_capability(capabilities,
|
|
|
|
'Alexa.TemperatureSensor')
|
|
|
|
assert temp_sensor_capability is not None
|
|
|
|
properties = temp_sensor_capability['properties']
|
2018-01-30 04:33:39 +00:00
|
|
|
assert properties['retrievable'] is True
|
|
|
|
assert {'name': 'temperature'} in properties['supported']
|
|
|
|
|
2018-10-29 21:57:27 +00:00
|
|
|
properties = await reported_properties(hass, 'sensor#test_temp')
|
2018-01-30 04:33:39 +00:00
|
|
|
properties.assert_equal('Alexa.TemperatureSensor', 'temperature',
|
|
|
|
{'value': 42.0, 'scale': 'FAHRENHEIT'})
|
2018-01-29 00:43:27 +00:00
|
|
|
|
2018-01-30 04:33:39 +00:00
|
|
|
|
2018-10-29 21:57:27 +00:00
|
|
|
async def test_contact_sensor(hass):
|
2018-10-25 14:46:43 +00:00
|
|
|
"""Test contact sensor discovery."""
|
|
|
|
device = (
|
|
|
|
'binary_sensor.test_contact',
|
|
|
|
'on',
|
|
|
|
{
|
|
|
|
'friendly_name': "Test Contact Sensor",
|
|
|
|
'device_class': 'door',
|
|
|
|
}
|
|
|
|
)
|
2018-10-29 21:57:27 +00:00
|
|
|
appliance = await discovery_test(device, hass)
|
2018-10-25 14:46:43 +00:00
|
|
|
|
|
|
|
assert appliance['endpointId'] == 'binary_sensor#test_contact'
|
|
|
|
assert appliance['displayCategories'][0] == 'CONTACT_SENSOR'
|
|
|
|
assert appliance['friendlyName'] == 'Test Contact Sensor'
|
|
|
|
|
2019-01-10 23:52:21 +00:00
|
|
|
capabilities = assert_endpoint_capabilities(
|
2018-10-25 14:46:43 +00:00
|
|
|
appliance,
|
2019-01-10 23:52:21 +00:00
|
|
|
'Alexa.ContactSensor',
|
|
|
|
'Alexa.EndpointHealth',
|
|
|
|
)
|
|
|
|
|
|
|
|
contact_sensor_capability = get_capability(capabilities,
|
|
|
|
'Alexa.ContactSensor')
|
|
|
|
assert contact_sensor_capability is not None
|
|
|
|
properties = contact_sensor_capability['properties']
|
2018-10-25 14:46:43 +00:00
|
|
|
assert properties['retrievable'] is True
|
|
|
|
assert {'name': 'detectionState'} in properties['supported']
|
|
|
|
|
2018-10-29 21:57:27 +00:00
|
|
|
properties = await reported_properties(hass,
|
|
|
|
'binary_sensor#test_contact')
|
2018-10-25 14:46:43 +00:00
|
|
|
properties.assert_equal('Alexa.ContactSensor', 'detectionState',
|
|
|
|
'DETECTED')
|
|
|
|
|
2019-01-10 23:52:21 +00:00
|
|
|
properties.assert_equal('Alexa.EndpointHealth', 'connectivity',
|
|
|
|
{'value': 'OK'})
|
|
|
|
|
2018-10-25 14:46:43 +00:00
|
|
|
|
2018-10-29 21:57:27 +00:00
|
|
|
async def test_motion_sensor(hass):
|
2018-10-26 21:43:31 +00:00
|
|
|
"""Test motion sensor discovery."""
|
|
|
|
device = (
|
|
|
|
'binary_sensor.test_motion',
|
|
|
|
'on',
|
|
|
|
{
|
|
|
|
'friendly_name': "Test Motion Sensor",
|
|
|
|
'device_class': 'motion',
|
|
|
|
}
|
|
|
|
)
|
2018-10-29 21:57:27 +00:00
|
|
|
appliance = await discovery_test(device, hass)
|
2018-10-26 21:43:31 +00:00
|
|
|
|
|
|
|
assert appliance['endpointId'] == 'binary_sensor#test_motion'
|
|
|
|
assert appliance['displayCategories'][0] == 'MOTION_SENSOR'
|
|
|
|
assert appliance['friendlyName'] == 'Test Motion Sensor'
|
|
|
|
|
2019-01-10 23:52:21 +00:00
|
|
|
capabilities = assert_endpoint_capabilities(
|
2018-10-26 21:43:31 +00:00
|
|
|
appliance,
|
2019-01-10 23:52:21 +00:00
|
|
|
'Alexa.MotionSensor',
|
|
|
|
'Alexa.EndpointHealth',
|
|
|
|
)
|
|
|
|
|
|
|
|
motion_sensor_capability = get_capability(capabilities,
|
|
|
|
'Alexa.MotionSensor')
|
|
|
|
assert motion_sensor_capability is not None
|
|
|
|
properties = motion_sensor_capability['properties']
|
2018-10-26 21:43:31 +00:00
|
|
|
assert properties['retrievable'] is True
|
|
|
|
assert {'name': 'detectionState'} in properties['supported']
|
|
|
|
|
2018-10-29 21:57:27 +00:00
|
|
|
properties = await reported_properties(hass,
|
|
|
|
'binary_sensor#test_motion')
|
2018-10-26 21:43:31 +00:00
|
|
|
properties.assert_equal('Alexa.MotionSensor', 'detectionState',
|
|
|
|
'DETECTED')
|
|
|
|
|
|
|
|
|
2018-10-29 21:57:27 +00:00
|
|
|
async def test_unknown_sensor(hass):
|
2018-01-30 04:33:39 +00:00
|
|
|
"""Test sensors of unknown quantities are not discovered."""
|
|
|
|
device = (
|
|
|
|
'sensor.test_sickness', '0.1', {
|
|
|
|
'friendly_name': "Test Space Sickness Sensor",
|
|
|
|
'unit_of_measurement': 'garn',
|
|
|
|
})
|
2018-10-29 21:57:27 +00:00
|
|
|
await discovery_test(device, hass, expected_endpoints=0)
|
2017-09-16 19:35:28 +00:00
|
|
|
|
|
|
|
|
2018-03-30 06:49:08 +00:00
|
|
|
async def test_thermostat(hass):
|
|
|
|
"""Test thermostat discovery."""
|
2018-08-22 07:17:29 +00:00
|
|
|
hass.config.units.temperature_unit = TEMP_FAHRENHEIT
|
2018-03-30 06:49:08 +00:00
|
|
|
device = (
|
|
|
|
'climate.test_thermostat',
|
|
|
|
'cool',
|
|
|
|
{
|
|
|
|
'operation_mode': 'cool',
|
|
|
|
'temperature': 70.0,
|
|
|
|
'target_temp_high': 80.0,
|
|
|
|
'target_temp_low': 60.0,
|
|
|
|
'current_temperature': 75.0,
|
|
|
|
'friendly_name': "Test Thermostat",
|
|
|
|
'supported_features': 1 | 2 | 4 | 128,
|
|
|
|
'operation_list': ['heat', 'cool', 'auto', 'off'],
|
|
|
|
'min_temp': 50,
|
|
|
|
'max_temp': 90,
|
|
|
|
}
|
|
|
|
)
|
|
|
|
appliance = await discovery_test(device, hass)
|
|
|
|
|
|
|
|
assert appliance['endpointId'] == 'climate#test_thermostat'
|
|
|
|
assert appliance['displayCategories'][0] == 'THERMOSTAT'
|
|
|
|
assert appliance['friendlyName'] == "Test Thermostat"
|
|
|
|
|
|
|
|
assert_endpoint_capabilities(
|
|
|
|
appliance,
|
|
|
|
'Alexa.ThermostatController',
|
|
|
|
'Alexa.TemperatureSensor',
|
2019-01-10 23:52:21 +00:00
|
|
|
'Alexa.EndpointHealth',
|
2018-03-30 06:49:08 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
properties = await reported_properties(
|
|
|
|
hass, 'climate#test_thermostat')
|
|
|
|
properties.assert_equal(
|
|
|
|
'Alexa.ThermostatController', 'thermostatMode', 'COOL')
|
|
|
|
properties.assert_equal(
|
|
|
|
'Alexa.ThermostatController', 'targetSetpoint',
|
|
|
|
{'value': 70.0, 'scale': 'FAHRENHEIT'})
|
|
|
|
properties.assert_equal(
|
|
|
|
'Alexa.TemperatureSensor', 'temperature',
|
|
|
|
{'value': 75.0, 'scale': 'FAHRENHEIT'})
|
|
|
|
|
Refactor Alexa API, fix thermostats (#17969)
* Refactor Alexa API to use objects for requests
This introduces _AlexaDirective to stand in for the previous model of passing
basic dict and list data structures to and from handlers. This gives a more
expressive platform for functionality common to most or all handlers.
I had two use cases in mind:
1) Most responses should include current properties. In the case of locks and
thermostats, the response must include the properties or Alexa will give the
user a vague error like "Hmm, $device is not responding." Locks currently work,
but thermostats do not. I wanted a way to automatically include properties in
all responses. This is implemented in a subsequent commit.
2) The previous model had a 1:1 mapping between Alexa endpoints and Home
Assistant entities. This works most of the time, but sometimes it's not so
great. For example, my Z-wave thermostat shows as three devices in Alexa: one
for the temperature sensor, one for the heat, and one for the AC. I'd like to
merge these into one device from Alexa's perspective. I believe this will be
facilitated with the `endpoint` attribute on `_AlexaDirective`.
* Include properties in all Alexa responses
The added _AlexaResponse class provides a richer vocabulary for handlers.
Among that vocabulary is .merge_context_properties(), which is invoked
automatically for any request directed at an endpoint. This adds all supported
properties to the response as recommended by the Alexa API docs, and in some
cases (locks, thermostats at least) the user will get an error "Hmm, $device is
not responding" if properties are not provided in the response.
* Fix setting temperature with Alexa thermostats
Fixes https://github.com/home-assistant/home-assistant/issues/16577
2018-10-30 02:16:35 +00:00
|
|
|
call, msg = await assert_request_calls_service(
|
2018-03-30 06:49:08 +00:00
|
|
|
'Alexa.ThermostatController', 'SetTargetTemperature',
|
|
|
|
'climate#test_thermostat', 'climate.set_temperature',
|
|
|
|
hass,
|
|
|
|
payload={'targetSetpoint': {'value': 69.0, 'scale': 'FAHRENHEIT'}}
|
|
|
|
)
|
|
|
|
assert call.data['temperature'] == 69.0
|
Refactor Alexa API, fix thermostats (#17969)
* Refactor Alexa API to use objects for requests
This introduces _AlexaDirective to stand in for the previous model of passing
basic dict and list data structures to and from handlers. This gives a more
expressive platform for functionality common to most or all handlers.
I had two use cases in mind:
1) Most responses should include current properties. In the case of locks and
thermostats, the response must include the properties or Alexa will give the
user a vague error like "Hmm, $device is not responding." Locks currently work,
but thermostats do not. I wanted a way to automatically include properties in
all responses. This is implemented in a subsequent commit.
2) The previous model had a 1:1 mapping between Alexa endpoints and Home
Assistant entities. This works most of the time, but sometimes it's not so
great. For example, my Z-wave thermostat shows as three devices in Alexa: one
for the temperature sensor, one for the heat, and one for the AC. I'd like to
merge these into one device from Alexa's perspective. I believe this will be
facilitated with the `endpoint` attribute on `_AlexaDirective`.
* Include properties in all Alexa responses
The added _AlexaResponse class provides a richer vocabulary for handlers.
Among that vocabulary is .merge_context_properties(), which is invoked
automatically for any request directed at an endpoint. This adds all supported
properties to the response as recommended by the Alexa API docs, and in some
cases (locks, thermostats at least) the user will get an error "Hmm, $device is
not responding" if properties are not provided in the response.
* Fix setting temperature with Alexa thermostats
Fixes https://github.com/home-assistant/home-assistant/issues/16577
2018-10-30 02:16:35 +00:00
|
|
|
properties = _ReportedProperties(msg['context']['properties'])
|
|
|
|
properties.assert_equal(
|
|
|
|
'Alexa.ThermostatController', 'targetSetpoint',
|
|
|
|
{'value': 69.0, 'scale': 'FAHRENHEIT'})
|
2018-03-30 06:49:08 +00:00
|
|
|
|
|
|
|
msg = await assert_request_fails(
|
|
|
|
'Alexa.ThermostatController', 'SetTargetTemperature',
|
|
|
|
'climate#test_thermostat', 'climate.set_temperature',
|
|
|
|
hass,
|
|
|
|
payload={'targetSetpoint': {'value': 0.0, 'scale': 'CELSIUS'}}
|
|
|
|
)
|
|
|
|
assert msg['event']['payload']['type'] == 'TEMPERATURE_VALUE_OUT_OF_RANGE'
|
|
|
|
|
Refactor Alexa API, fix thermostats (#17969)
* Refactor Alexa API to use objects for requests
This introduces _AlexaDirective to stand in for the previous model of passing
basic dict and list data structures to and from handlers. This gives a more
expressive platform for functionality common to most or all handlers.
I had two use cases in mind:
1) Most responses should include current properties. In the case of locks and
thermostats, the response must include the properties or Alexa will give the
user a vague error like "Hmm, $device is not responding." Locks currently work,
but thermostats do not. I wanted a way to automatically include properties in
all responses. This is implemented in a subsequent commit.
2) The previous model had a 1:1 mapping between Alexa endpoints and Home
Assistant entities. This works most of the time, but sometimes it's not so
great. For example, my Z-wave thermostat shows as three devices in Alexa: one
for the temperature sensor, one for the heat, and one for the AC. I'd like to
merge these into one device from Alexa's perspective. I believe this will be
facilitated with the `endpoint` attribute on `_AlexaDirective`.
* Include properties in all Alexa responses
The added _AlexaResponse class provides a richer vocabulary for handlers.
Among that vocabulary is .merge_context_properties(), which is invoked
automatically for any request directed at an endpoint. This adds all supported
properties to the response as recommended by the Alexa API docs, and in some
cases (locks, thermostats at least) the user will get an error "Hmm, $device is
not responding" if properties are not provided in the response.
* Fix setting temperature with Alexa thermostats
Fixes https://github.com/home-assistant/home-assistant/issues/16577
2018-10-30 02:16:35 +00:00
|
|
|
call, msg = await assert_request_calls_service(
|
2018-03-30 06:49:08 +00:00
|
|
|
'Alexa.ThermostatController', 'SetTargetTemperature',
|
|
|
|
'climate#test_thermostat', 'climate.set_temperature',
|
|
|
|
hass,
|
|
|
|
payload={
|
|
|
|
'targetSetpoint': {'value': 70.0, 'scale': 'FAHRENHEIT'},
|
|
|
|
'lowerSetpoint': {'value': 293.15, 'scale': 'KELVIN'},
|
|
|
|
'upperSetpoint': {'value': 30.0, 'scale': 'CELSIUS'},
|
|
|
|
}
|
|
|
|
)
|
|
|
|
assert call.data['temperature'] == 70.0
|
|
|
|
assert call.data['target_temp_low'] == 68.0
|
|
|
|
assert call.data['target_temp_high'] == 86.0
|
Refactor Alexa API, fix thermostats (#17969)
* Refactor Alexa API to use objects for requests
This introduces _AlexaDirective to stand in for the previous model of passing
basic dict and list data structures to and from handlers. This gives a more
expressive platform for functionality common to most or all handlers.
I had two use cases in mind:
1) Most responses should include current properties. In the case of locks and
thermostats, the response must include the properties or Alexa will give the
user a vague error like "Hmm, $device is not responding." Locks currently work,
but thermostats do not. I wanted a way to automatically include properties in
all responses. This is implemented in a subsequent commit.
2) The previous model had a 1:1 mapping between Alexa endpoints and Home
Assistant entities. This works most of the time, but sometimes it's not so
great. For example, my Z-wave thermostat shows as three devices in Alexa: one
for the temperature sensor, one for the heat, and one for the AC. I'd like to
merge these into one device from Alexa's perspective. I believe this will be
facilitated with the `endpoint` attribute on `_AlexaDirective`.
* Include properties in all Alexa responses
The added _AlexaResponse class provides a richer vocabulary for handlers.
Among that vocabulary is .merge_context_properties(), which is invoked
automatically for any request directed at an endpoint. This adds all supported
properties to the response as recommended by the Alexa API docs, and in some
cases (locks, thermostats at least) the user will get an error "Hmm, $device is
not responding" if properties are not provided in the response.
* Fix setting temperature with Alexa thermostats
Fixes https://github.com/home-assistant/home-assistant/issues/16577
2018-10-30 02:16:35 +00:00
|
|
|
properties = _ReportedProperties(msg['context']['properties'])
|
|
|
|
properties.assert_equal(
|
|
|
|
'Alexa.ThermostatController', 'targetSetpoint',
|
|
|
|
{'value': 70.0, 'scale': 'FAHRENHEIT'})
|
|
|
|
properties.assert_equal(
|
|
|
|
'Alexa.ThermostatController', 'lowerSetpoint',
|
|
|
|
{'value': 68.0, 'scale': 'FAHRENHEIT'})
|
|
|
|
properties.assert_equal(
|
|
|
|
'Alexa.ThermostatController', 'upperSetpoint',
|
|
|
|
{'value': 86.0, 'scale': 'FAHRENHEIT'})
|
2018-03-30 06:49:08 +00:00
|
|
|
|
|
|
|
msg = await assert_request_fails(
|
|
|
|
'Alexa.ThermostatController', 'SetTargetTemperature',
|
|
|
|
'climate#test_thermostat', 'climate.set_temperature',
|
|
|
|
hass,
|
|
|
|
payload={
|
|
|
|
'lowerSetpoint': {'value': 273.15, 'scale': 'KELVIN'},
|
|
|
|
'upperSetpoint': {'value': 75.0, 'scale': 'FAHRENHEIT'},
|
|
|
|
}
|
|
|
|
)
|
|
|
|
assert msg['event']['payload']['type'] == 'TEMPERATURE_VALUE_OUT_OF_RANGE'
|
|
|
|
|
|
|
|
msg = await assert_request_fails(
|
|
|
|
'Alexa.ThermostatController', 'SetTargetTemperature',
|
|
|
|
'climate#test_thermostat', 'climate.set_temperature',
|
|
|
|
hass,
|
|
|
|
payload={
|
|
|
|
'lowerSetpoint': {'value': 293.15, 'scale': 'FAHRENHEIT'},
|
|
|
|
'upperSetpoint': {'value': 75.0, 'scale': 'CELSIUS'},
|
|
|
|
}
|
|
|
|
)
|
|
|
|
assert msg['event']['payload']['type'] == 'TEMPERATURE_VALUE_OUT_OF_RANGE'
|
|
|
|
|
Refactor Alexa API, fix thermostats (#17969)
* Refactor Alexa API to use objects for requests
This introduces _AlexaDirective to stand in for the previous model of passing
basic dict and list data structures to and from handlers. This gives a more
expressive platform for functionality common to most or all handlers.
I had two use cases in mind:
1) Most responses should include current properties. In the case of locks and
thermostats, the response must include the properties or Alexa will give the
user a vague error like "Hmm, $device is not responding." Locks currently work,
but thermostats do not. I wanted a way to automatically include properties in
all responses. This is implemented in a subsequent commit.
2) The previous model had a 1:1 mapping between Alexa endpoints and Home
Assistant entities. This works most of the time, but sometimes it's not so
great. For example, my Z-wave thermostat shows as three devices in Alexa: one
for the temperature sensor, one for the heat, and one for the AC. I'd like to
merge these into one device from Alexa's perspective. I believe this will be
facilitated with the `endpoint` attribute on `_AlexaDirective`.
* Include properties in all Alexa responses
The added _AlexaResponse class provides a richer vocabulary for handlers.
Among that vocabulary is .merge_context_properties(), which is invoked
automatically for any request directed at an endpoint. This adds all supported
properties to the response as recommended by the Alexa API docs, and in some
cases (locks, thermostats at least) the user will get an error "Hmm, $device is
not responding" if properties are not provided in the response.
* Fix setting temperature with Alexa thermostats
Fixes https://github.com/home-assistant/home-assistant/issues/16577
2018-10-30 02:16:35 +00:00
|
|
|
call, msg = await assert_request_calls_service(
|
2018-03-30 06:49:08 +00:00
|
|
|
'Alexa.ThermostatController', 'AdjustTargetTemperature',
|
|
|
|
'climate#test_thermostat', 'climate.set_temperature',
|
|
|
|
hass,
|
|
|
|
payload={'targetSetpointDelta': {'value': -10.0, 'scale': 'KELVIN'}}
|
|
|
|
)
|
|
|
|
assert call.data['temperature'] == 52.0
|
Refactor Alexa API, fix thermostats (#17969)
* Refactor Alexa API to use objects for requests
This introduces _AlexaDirective to stand in for the previous model of passing
basic dict and list data structures to and from handlers. This gives a more
expressive platform for functionality common to most or all handlers.
I had two use cases in mind:
1) Most responses should include current properties. In the case of locks and
thermostats, the response must include the properties or Alexa will give the
user a vague error like "Hmm, $device is not responding." Locks currently work,
but thermostats do not. I wanted a way to automatically include properties in
all responses. This is implemented in a subsequent commit.
2) The previous model had a 1:1 mapping between Alexa endpoints and Home
Assistant entities. This works most of the time, but sometimes it's not so
great. For example, my Z-wave thermostat shows as three devices in Alexa: one
for the temperature sensor, one for the heat, and one for the AC. I'd like to
merge these into one device from Alexa's perspective. I believe this will be
facilitated with the `endpoint` attribute on `_AlexaDirective`.
* Include properties in all Alexa responses
The added _AlexaResponse class provides a richer vocabulary for handlers.
Among that vocabulary is .merge_context_properties(), which is invoked
automatically for any request directed at an endpoint. This adds all supported
properties to the response as recommended by the Alexa API docs, and in some
cases (locks, thermostats at least) the user will get an error "Hmm, $device is
not responding" if properties are not provided in the response.
* Fix setting temperature with Alexa thermostats
Fixes https://github.com/home-assistant/home-assistant/issues/16577
2018-10-30 02:16:35 +00:00
|
|
|
properties = _ReportedProperties(msg['context']['properties'])
|
|
|
|
properties.assert_equal(
|
|
|
|
'Alexa.ThermostatController', 'targetSetpoint',
|
|
|
|
{'value': 52.0, 'scale': 'FAHRENHEIT'})
|
2018-03-30 06:49:08 +00:00
|
|
|
|
|
|
|
msg = await assert_request_fails(
|
|
|
|
'Alexa.ThermostatController', 'AdjustTargetTemperature',
|
|
|
|
'climate#test_thermostat', 'climate.set_temperature',
|
|
|
|
hass,
|
|
|
|
payload={'targetSetpointDelta': {'value': 20.0, 'scale': 'CELSIUS'}}
|
|
|
|
)
|
|
|
|
assert msg['event']['payload']['type'] == 'TEMPERATURE_VALUE_OUT_OF_RANGE'
|
|
|
|
|
2018-10-31 15:09:13 +00:00
|
|
|
# Setting mode, the payload can be an object with a value attribute...
|
|
|
|
call, msg = await assert_request_calls_service(
|
2018-03-30 06:49:08 +00:00
|
|
|
'Alexa.ThermostatController', 'SetThermostatMode',
|
|
|
|
'climate#test_thermostat', 'climate.set_operation_mode',
|
|
|
|
hass,
|
2018-04-18 18:19:05 +00:00
|
|
|
payload={'thermostatMode': {'value': 'HEAT'}}
|
2018-03-30 06:49:08 +00:00
|
|
|
)
|
|
|
|
assert call.data['operation_mode'] == 'heat'
|
2018-10-31 15:09:13 +00:00
|
|
|
properties = _ReportedProperties(msg['context']['properties'])
|
|
|
|
properties.assert_equal(
|
|
|
|
'Alexa.ThermostatController', 'thermostatMode', 'HEAT')
|
2018-03-30 06:49:08 +00:00
|
|
|
|
2018-10-31 15:09:13 +00:00
|
|
|
call, msg = await assert_request_calls_service(
|
2018-04-18 18:19:05 +00:00
|
|
|
'Alexa.ThermostatController', 'SetThermostatMode',
|
|
|
|
'climate#test_thermostat', 'climate.set_operation_mode',
|
|
|
|
hass,
|
2018-10-31 15:09:13 +00:00
|
|
|
payload={'thermostatMode': {'value': 'COOL'}}
|
2018-04-18 18:19:05 +00:00
|
|
|
)
|
2018-10-31 15:09:13 +00:00
|
|
|
assert call.data['operation_mode'] == 'cool'
|
|
|
|
properties = _ReportedProperties(msg['context']['properties'])
|
|
|
|
properties.assert_equal(
|
|
|
|
'Alexa.ThermostatController', 'thermostatMode', 'COOL')
|
2018-04-18 18:19:05 +00:00
|
|
|
|
2018-10-31 15:09:13 +00:00
|
|
|
# ...it can also be just the mode.
|
|
|
|
call, msg = await assert_request_calls_service(
|
|
|
|
'Alexa.ThermostatController', 'SetThermostatMode',
|
|
|
|
'climate#test_thermostat', 'climate.set_operation_mode',
|
|
|
|
hass,
|
|
|
|
payload={'thermostatMode': 'HEAT'}
|
|
|
|
)
|
2018-04-18 18:19:05 +00:00
|
|
|
assert call.data['operation_mode'] == 'heat'
|
2018-10-31 15:09:13 +00:00
|
|
|
properties = _ReportedProperties(msg['context']['properties'])
|
|
|
|
properties.assert_equal(
|
|
|
|
'Alexa.ThermostatController', 'thermostatMode', 'HEAT')
|
|
|
|
|
2018-03-30 06:49:08 +00:00
|
|
|
msg = await assert_request_fails(
|
|
|
|
'Alexa.ThermostatController', 'SetThermostatMode',
|
|
|
|
'climate#test_thermostat', 'climate.set_operation_mode',
|
|
|
|
hass,
|
2018-04-18 18:19:05 +00:00
|
|
|
payload={'thermostatMode': {'value': 'INVALID'}}
|
2018-03-30 06:49:08 +00:00
|
|
|
)
|
|
|
|
assert msg['event']['payload']['type'] == 'UNSUPPORTED_THERMOSTAT_MODE'
|
2018-08-22 07:17:29 +00:00
|
|
|
hass.config.units.temperature_unit = TEMP_CELSIUS
|
2018-03-30 06:49:08 +00:00
|
|
|
|
2018-10-29 19:52:34 +00:00
|
|
|
call, _ = await assert_request_calls_service(
|
|
|
|
'Alexa.ThermostatController', 'SetThermostatMode',
|
|
|
|
'climate#test_thermostat', 'climate.set_operation_mode',
|
|
|
|
hass,
|
|
|
|
payload={'thermostatMode': 'OFF'}
|
|
|
|
)
|
|
|
|
assert call.data['operation_mode'] == 'off'
|
|
|
|
|
2018-03-30 06:49:08 +00:00
|
|
|
|
2018-10-29 21:57:27 +00:00
|
|
|
async def test_exclude_filters(hass):
|
2017-11-18 05:10:24 +00:00
|
|
|
"""Test exclusion filters."""
|
|
|
|
request = get_new_request('Alexa.Discovery', 'Discover')
|
|
|
|
|
|
|
|
# setup test devices
|
|
|
|
hass.states.async_set(
|
|
|
|
'switch.test', 'on', {'friendly_name': "Test switch"})
|
|
|
|
|
|
|
|
hass.states.async_set(
|
|
|
|
'script.deny', 'off', {'friendly_name': "Blocked script"})
|
|
|
|
|
|
|
|
hass.states.async_set(
|
|
|
|
'cover.deny', 'off', {'friendly_name': "Blocked cover"})
|
|
|
|
|
2019-01-03 21:28:43 +00:00
|
|
|
config = smart_home.Config(
|
|
|
|
endpoint=None,
|
|
|
|
async_get_access_token=None,
|
|
|
|
should_expose=entityfilter.generate_filter(
|
|
|
|
include_domains=[],
|
|
|
|
include_entities=[],
|
|
|
|
exclude_domains=['script'],
|
|
|
|
exclude_entities=['cover.deny'],
|
|
|
|
))
|
2017-11-18 05:10:24 +00:00
|
|
|
|
2018-10-29 21:57:27 +00:00
|
|
|
msg = await smart_home.async_handle_message(hass, config, request)
|
|
|
|
await hass.async_block_till_done()
|
2017-11-18 05:10:24 +00:00
|
|
|
|
|
|
|
msg = msg['event']
|
|
|
|
|
|
|
|
assert len(msg['payload']['endpoints']) == 1
|
|
|
|
|
|
|
|
|
2018-10-29 21:57:27 +00:00
|
|
|
async def test_include_filters(hass):
|
2017-11-18 05:10:24 +00:00
|
|
|
"""Test inclusion filters."""
|
|
|
|
request = get_new_request('Alexa.Discovery', 'Discover')
|
|
|
|
|
|
|
|
# setup test devices
|
|
|
|
hass.states.async_set(
|
|
|
|
'switch.deny', 'on', {'friendly_name': "Blocked switch"})
|
|
|
|
|
|
|
|
hass.states.async_set(
|
|
|
|
'script.deny', 'off', {'friendly_name': "Blocked script"})
|
|
|
|
|
|
|
|
hass.states.async_set(
|
|
|
|
'automation.allow', 'off', {'friendly_name': "Allowed automation"})
|
|
|
|
|
|
|
|
hass.states.async_set(
|
|
|
|
'group.allow', 'off', {'friendly_name': "Allowed group"})
|
|
|
|
|
2019-01-03 21:28:43 +00:00
|
|
|
config = smart_home.Config(
|
|
|
|
endpoint=None,
|
|
|
|
async_get_access_token=None,
|
|
|
|
should_expose=entityfilter.generate_filter(
|
|
|
|
include_domains=['automation', 'group'],
|
|
|
|
include_entities=['script.deny'],
|
|
|
|
exclude_domains=[],
|
|
|
|
exclude_entities=[],
|
|
|
|
))
|
2017-11-18 05:10:24 +00:00
|
|
|
|
2018-10-29 21:57:27 +00:00
|
|
|
msg = await smart_home.async_handle_message(hass, config, request)
|
|
|
|
await hass.async_block_till_done()
|
2017-11-18 05:10:24 +00:00
|
|
|
|
|
|
|
msg = msg['event']
|
|
|
|
|
|
|
|
assert len(msg['payload']['endpoints']) == 3
|
|
|
|
|
|
|
|
|
Add support for locks in google assistant component (#18233)
* Add support for locks in google assistant component
This is supported by the smarthome API, but there is no documentation
for it. This work is based on an article I found with screenshots of
documentation that was erroneously uploaded:
https://www.androidpolice.com/2018/01/17/google-assistant-home-can-now-natively-control-smart-locks-august-vivint-first-supported/
Google Assistant now supports unlocking certain locks - Nest and August
come to mind - via this API, and this commit allows Home Assistant to
do so as well.
Notably, I've added a config option `allow_unlock` that controls
whether we actually honor requests to unlock a lock via the google
assistant. It defaults to false.
Additionally, we add the functionNotSupported error, which makes a
little more sense when we're unable to execute the desired state
transition.
https://developers.google.com/actions/reference/smarthome/errors-exceptions#exception_list
* Fix linter warnings
* Ensure that certain groups are never exposed to cloud entities
For example, the group.all_locks entity - we should probably never
expose this to third party cloud integrations. It's risky.
This is not configurable, but can be extended by adding to the
cloud.const.NEVER_EXPOSED_ENTITIES array.
It's implemented in a modestly hacky fashion, because we determine
whether or not a entity should be excluded/included in several ways.
Notably, we define this array in the top level const.py, to avoid
circular import problems between the cloud/alexa components.
2018-11-06 09:39:10 +00:00
|
|
|
async def test_never_exposed_entities(hass):
|
|
|
|
"""Test never exposed locks do not get discovered."""
|
|
|
|
request = get_new_request('Alexa.Discovery', 'Discover')
|
|
|
|
|
|
|
|
# setup test devices
|
|
|
|
hass.states.async_set(
|
|
|
|
'group.all_locks', 'on', {'friendly_name': "Blocked locks"})
|
|
|
|
|
|
|
|
hass.states.async_set(
|
|
|
|
'group.allow', 'off', {'friendly_name': "Allowed group"})
|
|
|
|
|
2019-01-03 21:28:43 +00:00
|
|
|
config = smart_home.Config(
|
|
|
|
endpoint=None,
|
|
|
|
async_get_access_token=None,
|
|
|
|
should_expose=entityfilter.generate_filter(
|
|
|
|
include_domains=['group'],
|
|
|
|
include_entities=[],
|
|
|
|
exclude_domains=[],
|
|
|
|
exclude_entities=[],
|
|
|
|
))
|
Add support for locks in google assistant component (#18233)
* Add support for locks in google assistant component
This is supported by the smarthome API, but there is no documentation
for it. This work is based on an article I found with screenshots of
documentation that was erroneously uploaded:
https://www.androidpolice.com/2018/01/17/google-assistant-home-can-now-natively-control-smart-locks-august-vivint-first-supported/
Google Assistant now supports unlocking certain locks - Nest and August
come to mind - via this API, and this commit allows Home Assistant to
do so as well.
Notably, I've added a config option `allow_unlock` that controls
whether we actually honor requests to unlock a lock via the google
assistant. It defaults to false.
Additionally, we add the functionNotSupported error, which makes a
little more sense when we're unable to execute the desired state
transition.
https://developers.google.com/actions/reference/smarthome/errors-exceptions#exception_list
* Fix linter warnings
* Ensure that certain groups are never exposed to cloud entities
For example, the group.all_locks entity - we should probably never
expose this to third party cloud integrations. It's risky.
This is not configurable, but can be extended by adding to the
cloud.const.NEVER_EXPOSED_ENTITIES array.
It's implemented in a modestly hacky fashion, because we determine
whether or not a entity should be excluded/included in several ways.
Notably, we define this array in the top level const.py, to avoid
circular import problems between the cloud/alexa components.
2018-11-06 09:39:10 +00:00
|
|
|
|
|
|
|
msg = await smart_home.async_handle_message(hass, config, request)
|
|
|
|
await hass.async_block_till_done()
|
|
|
|
|
|
|
|
msg = msg['event']
|
|
|
|
|
|
|
|
assert len(msg['payload']['endpoints']) == 1
|
|
|
|
|
|
|
|
|
2018-10-29 21:57:27 +00:00
|
|
|
async def test_api_entity_not_exists(hass):
|
2017-09-16 19:35:28 +00:00
|
|
|
"""Test api turn on process without entity."""
|
2017-10-07 20:31:57 +00:00
|
|
|
request = get_new_request('Alexa.PowerController', 'TurnOn', 'switch#test')
|
2017-09-16 19:35:28 +00:00
|
|
|
|
|
|
|
call_switch = async_mock_service(hass, 'switch', 'turn_on')
|
|
|
|
|
2018-10-29 21:57:27 +00:00
|
|
|
msg = await smart_home.async_handle_message(
|
2017-11-18 05:10:24 +00:00
|
|
|
hass, DEFAULT_CONFIG, request)
|
2018-10-29 21:57:27 +00:00
|
|
|
await hass.async_block_till_done()
|
2017-10-07 20:31:57 +00:00
|
|
|
|
|
|
|
assert 'event' in msg
|
|
|
|
msg = msg['event']
|
|
|
|
|
2018-01-30 04:33:39 +00:00
|
|
|
assert not call_switch
|
2017-10-07 20:31:57 +00:00
|
|
|
assert msg['header']['name'] == 'ErrorResponse'
|
|
|
|
assert msg['header']['namespace'] == 'Alexa'
|
|
|
|
assert msg['payload']['type'] == 'NO_SUCH_ENDPOINT'
|
|
|
|
|
|
|
|
|
2018-10-29 21:57:27 +00:00
|
|
|
async def test_api_function_not_implemented(hass):
|
2017-10-07 20:31:57 +00:00
|
|
|
"""Test api call that is not implemented to us."""
|
|
|
|
request = get_new_request('Alexa.HAHAAH', 'Sweet')
|
2018-10-29 21:57:27 +00:00
|
|
|
msg = await smart_home.async_handle_message(
|
2017-11-18 05:10:24 +00:00
|
|
|
hass, DEFAULT_CONFIG, request)
|
2017-10-07 20:31:57 +00:00
|
|
|
|
|
|
|
assert 'event' in msg
|
|
|
|
msg = msg['event']
|
|
|
|
|
|
|
|
assert msg['header']['name'] == 'ErrorResponse'
|
|
|
|
assert msg['header']['namespace'] == 'Alexa'
|
|
|
|
assert msg['payload']['type'] == 'INTERNAL_ERROR'
|
2017-09-16 19:35:28 +00:00
|
|
|
|
|
|
|
|
2018-10-29 21:57:27 +00:00
|
|
|
async def assert_request_fails(
|
2018-01-30 04:33:39 +00:00
|
|
|
namespace,
|
|
|
|
name,
|
|
|
|
endpoint,
|
|
|
|
service_not_called,
|
|
|
|
hass,
|
|
|
|
payload=None):
|
|
|
|
"""Assert an API request returns an ErrorResponse."""
|
|
|
|
request = get_new_request(namespace, name, endpoint)
|
|
|
|
if payload:
|
|
|
|
request['directive']['payload'] = payload
|
|
|
|
|
|
|
|
domain, service_name = service_not_called.split('.')
|
|
|
|
call = async_mock_service(hass, domain, service_name)
|
2017-09-16 19:35:28 +00:00
|
|
|
|
2018-10-29 21:57:27 +00:00
|
|
|
msg = await smart_home.async_handle_message(
|
2017-11-18 05:10:24 +00:00
|
|
|
hass, DEFAULT_CONFIG, request)
|
2018-10-29 21:57:27 +00:00
|
|
|
await hass.async_block_till_done()
|
2017-10-07 20:31:57 +00:00
|
|
|
|
2018-01-30 04:33:39 +00:00
|
|
|
assert not call
|
2017-10-07 20:31:57 +00:00
|
|
|
assert 'event' in msg
|
2018-01-30 04:33:39 +00:00
|
|
|
assert msg['event']['header']['name'] == 'ErrorResponse'
|
2017-10-07 20:31:57 +00:00
|
|
|
|
2018-01-30 04:33:39 +00:00
|
|
|
return msg
|
2017-09-16 19:35:28 +00:00
|
|
|
|
|
|
|
|
2018-10-29 21:57:27 +00:00
|
|
|
async def assert_request_calls_service(
|
2018-01-30 04:33:39 +00:00
|
|
|
namespace,
|
|
|
|
name,
|
|
|
|
endpoint,
|
|
|
|
service,
|
|
|
|
hass,
|
|
|
|
response_type='Response',
|
|
|
|
payload=None):
|
|
|
|
"""Assert an API request calls a hass service."""
|
2018-08-20 12:18:07 +00:00
|
|
|
context = Context()
|
2018-01-30 04:33:39 +00:00
|
|
|
request = get_new_request(namespace, name, endpoint)
|
|
|
|
if payload:
|
|
|
|
request['directive']['payload'] = payload
|
|
|
|
|
|
|
|
domain, service_name = service.split('.')
|
2018-08-20 12:18:07 +00:00
|
|
|
calls = async_mock_service(hass, domain, service_name)
|
2017-09-16 19:35:28 +00:00
|
|
|
|
2018-10-29 21:57:27 +00:00
|
|
|
msg = await smart_home.async_handle_message(
|
2018-08-20 12:18:07 +00:00
|
|
|
hass, DEFAULT_CONFIG, request, context)
|
2018-10-29 21:57:27 +00:00
|
|
|
await hass.async_block_till_done()
|
2017-10-07 20:31:57 +00:00
|
|
|
|
2018-08-20 12:18:07 +00:00
|
|
|
assert len(calls) == 1
|
|
|
|
call = calls[0]
|
2017-10-07 20:31:57 +00:00
|
|
|
assert 'event' in msg
|
2018-08-20 12:18:07 +00:00
|
|
|
assert call.data['entity_id'] == endpoint.replace('#', '.')
|
2018-01-30 04:33:39 +00:00
|
|
|
assert msg['event']['header']['name'] == response_type
|
2018-08-20 12:18:07 +00:00
|
|
|
assert call.context == context
|
2017-10-07 20:31:57 +00:00
|
|
|
|
2018-08-20 12:18:07 +00:00
|
|
|
return call, msg
|
2017-09-16 19:35:28 +00:00
|
|
|
|
|
|
|
|
2018-10-29 21:57:27 +00:00
|
|
|
async def assert_power_controller_works(
|
|
|
|
endpoint,
|
|
|
|
on_service,
|
|
|
|
off_service,
|
|
|
|
hass
|
|
|
|
):
|
2018-01-30 04:33:39 +00:00
|
|
|
"""Assert PowerController API requests work."""
|
2018-10-29 21:57:27 +00:00
|
|
|
await assert_request_calls_service(
|
2018-01-30 04:33:39 +00:00
|
|
|
'Alexa.PowerController', 'TurnOn', endpoint,
|
|
|
|
on_service, hass)
|
2017-09-16 19:35:28 +00:00
|
|
|
|
2018-10-29 21:57:27 +00:00
|
|
|
await assert_request_calls_service(
|
2018-01-30 04:33:39 +00:00
|
|
|
'Alexa.PowerController', 'TurnOff', endpoint,
|
|
|
|
off_service, hass)
|
2017-10-07 20:31:57 +00:00
|
|
|
|
|
|
|
|
2018-10-29 21:57:27 +00:00
|
|
|
async def assert_scene_controller_works(
|
2018-01-30 04:33:39 +00:00
|
|
|
endpoint,
|
|
|
|
activate_service,
|
|
|
|
deactivate_service,
|
|
|
|
hass):
|
|
|
|
"""Assert SceneController API requests work."""
|
2018-10-29 21:57:27 +00:00
|
|
|
_, response = await assert_request_calls_service(
|
2018-01-30 04:33:39 +00:00
|
|
|
'Alexa.SceneController', 'Activate', endpoint,
|
|
|
|
activate_service, hass,
|
|
|
|
response_type='ActivationStarted')
|
|
|
|
assert response['event']['payload']['cause']['type'] == 'VOICE_INTERACTION'
|
|
|
|
assert 'timestamp' in response['event']['payload']
|
|
|
|
|
|
|
|
if deactivate_service:
|
2018-10-29 21:57:27 +00:00
|
|
|
await assert_request_calls_service(
|
2018-01-30 04:33:39 +00:00
|
|
|
'Alexa.SceneController', 'Deactivate', endpoint,
|
|
|
|
deactivate_service, hass,
|
|
|
|
response_type='DeactivationStarted')
|
|
|
|
cause_type = response['event']['payload']['cause']['type']
|
|
|
|
assert cause_type == 'VOICE_INTERACTION'
|
|
|
|
assert 'timestamp' in response['event']['payload']
|
2017-11-01 03:28:17 +00:00
|
|
|
|
|
|
|
|
2017-11-01 11:16:05 +00:00
|
|
|
@pytest.mark.parametrize(
|
|
|
|
"result,adjust", [(25, '-5'), (35, '5'), (0, '-80')])
|
2018-10-29 21:57:27 +00:00
|
|
|
async def test_api_adjust_brightness(hass, result, adjust):
|
2017-11-01 03:28:17 +00:00
|
|
|
"""Test api adjust brightness process."""
|
|
|
|
request = get_new_request(
|
|
|
|
'Alexa.BrightnessController', 'AdjustBrightness', 'light#test')
|
|
|
|
|
|
|
|
# add payload
|
|
|
|
request['directive']['payload']['brightnessDelta'] = adjust
|
|
|
|
|
2017-11-17 17:14:22 +00:00
|
|
|
# setup test devices
|
2017-11-01 03:28:17 +00:00
|
|
|
hass.states.async_set(
|
|
|
|
'light.test', 'off', {
|
|
|
|
'friendly_name': "Test light", 'brightness': '77'
|
|
|
|
})
|
|
|
|
|
|
|
|
call_light = async_mock_service(hass, 'light', 'turn_on')
|
|
|
|
|
2018-10-29 21:57:27 +00:00
|
|
|
msg = await smart_home.async_handle_message(
|
2017-11-18 05:10:24 +00:00
|
|
|
hass, DEFAULT_CONFIG, request)
|
2018-10-29 21:57:27 +00:00
|
|
|
await hass.async_block_till_done()
|
2017-11-01 03:28:17 +00:00
|
|
|
|
|
|
|
assert 'event' in msg
|
|
|
|
msg = msg['event']
|
|
|
|
|
|
|
|
assert len(call_light) == 1
|
|
|
|
assert call_light[0].data['entity_id'] == 'light.test'
|
|
|
|
assert call_light[0].data['brightness_pct'] == result
|
|
|
|
assert msg['header']['name'] == 'Response'
|
|
|
|
|
|
|
|
|
2018-10-29 21:57:27 +00:00
|
|
|
async def test_api_set_color_rgb(hass):
|
2017-11-01 03:28:17 +00:00
|
|
|
"""Test api set color process."""
|
|
|
|
request = get_new_request(
|
|
|
|
'Alexa.ColorController', 'SetColor', 'light#test')
|
|
|
|
|
|
|
|
# add payload
|
|
|
|
request['directive']['payload']['color'] = {
|
|
|
|
'hue': '120',
|
|
|
|
'saturation': '0.612',
|
|
|
|
'brightness': '0.342',
|
|
|
|
}
|
|
|
|
|
2017-11-17 17:14:22 +00:00
|
|
|
# setup test devices
|
2017-11-01 03:28:17 +00:00
|
|
|
hass.states.async_set(
|
2017-11-01 11:16:05 +00:00
|
|
|
'light.test', 'off', {
|
|
|
|
'friendly_name': "Test light",
|
|
|
|
'supported_features': 16,
|
|
|
|
})
|
2017-11-01 03:28:17 +00:00
|
|
|
|
|
|
|
call_light = async_mock_service(hass, 'light', 'turn_on')
|
|
|
|
|
2018-10-29 21:57:27 +00:00
|
|
|
msg = await smart_home.async_handle_message(
|
2017-11-18 05:10:24 +00:00
|
|
|
hass, DEFAULT_CONFIG, request)
|
2018-10-29 21:57:27 +00:00
|
|
|
await hass.async_block_till_done()
|
2017-11-01 03:28:17 +00:00
|
|
|
|
|
|
|
assert 'event' in msg
|
|
|
|
msg = msg['event']
|
|
|
|
|
|
|
|
assert len(call_light) == 1
|
|
|
|
assert call_light[0].data['entity_id'] == 'light.test'
|
|
|
|
assert call_light[0].data['rgb_color'] == (33, 87, 33)
|
|
|
|
assert msg['header']['name'] == 'Response'
|
|
|
|
|
|
|
|
|
2018-10-29 21:57:27 +00:00
|
|
|
async def test_api_set_color_temperature(hass):
|
2017-11-01 03:28:17 +00:00
|
|
|
"""Test api set color temperature process."""
|
|
|
|
request = get_new_request(
|
|
|
|
'Alexa.ColorTemperatureController', 'SetColorTemperature',
|
|
|
|
'light#test')
|
|
|
|
|
|
|
|
# add payload
|
|
|
|
request['directive']['payload']['colorTemperatureInKelvin'] = '7500'
|
|
|
|
|
2017-11-17 17:14:22 +00:00
|
|
|
# setup test devices
|
2017-11-01 03:28:17 +00:00
|
|
|
hass.states.async_set(
|
|
|
|
'light.test', 'off', {'friendly_name': "Test light"})
|
|
|
|
|
|
|
|
call_light = async_mock_service(hass, 'light', 'turn_on')
|
|
|
|
|
2018-10-29 21:57:27 +00:00
|
|
|
msg = await smart_home.async_handle_message(
|
2017-11-18 05:10:24 +00:00
|
|
|
hass, DEFAULT_CONFIG, request)
|
2018-10-29 21:57:27 +00:00
|
|
|
await hass.async_block_till_done()
|
2017-11-01 03:28:17 +00:00
|
|
|
|
|
|
|
assert 'event' in msg
|
|
|
|
msg = msg['event']
|
|
|
|
|
|
|
|
assert len(call_light) == 1
|
|
|
|
assert call_light[0].data['entity_id'] == 'light.test'
|
|
|
|
assert call_light[0].data['kelvin'] == 7500
|
|
|
|
assert msg['header']['name'] == 'Response'
|
|
|
|
|
|
|
|
|
|
|
|
@pytest.mark.parametrize("result,initial", [(383, '333'), (500, '500')])
|
2018-10-29 21:57:27 +00:00
|
|
|
async def test_api_decrease_color_temp(hass, result, initial):
|
2017-11-01 03:28:17 +00:00
|
|
|
"""Test api decrease color temp process."""
|
|
|
|
request = get_new_request(
|
|
|
|
'Alexa.ColorTemperatureController', 'DecreaseColorTemperature',
|
|
|
|
'light#test')
|
|
|
|
|
2017-11-17 17:14:22 +00:00
|
|
|
# setup test devices
|
2017-11-01 03:28:17 +00:00
|
|
|
hass.states.async_set(
|
|
|
|
'light.test', 'off', {
|
|
|
|
'friendly_name': "Test light", 'color_temp': initial,
|
|
|
|
'max_mireds': 500,
|
|
|
|
})
|
|
|
|
|
|
|
|
call_light = async_mock_service(hass, 'light', 'turn_on')
|
|
|
|
|
2018-10-29 21:57:27 +00:00
|
|
|
msg = await smart_home.async_handle_message(
|
2017-11-18 05:10:24 +00:00
|
|
|
hass, DEFAULT_CONFIG, request)
|
2018-10-29 21:57:27 +00:00
|
|
|
await hass.async_block_till_done()
|
2017-11-01 03:28:17 +00:00
|
|
|
|
|
|
|
assert 'event' in msg
|
|
|
|
msg = msg['event']
|
|
|
|
|
|
|
|
assert len(call_light) == 1
|
|
|
|
assert call_light[0].data['entity_id'] == 'light.test'
|
|
|
|
assert call_light[0].data['color_temp'] == result
|
|
|
|
assert msg['header']['name'] == 'Response'
|
|
|
|
|
|
|
|
|
|
|
|
@pytest.mark.parametrize("result,initial", [(283, '333'), (142, '142')])
|
2018-10-29 21:57:27 +00:00
|
|
|
async def test_api_increase_color_temp(hass, result, initial):
|
2017-11-01 03:28:17 +00:00
|
|
|
"""Test api increase color temp process."""
|
|
|
|
request = get_new_request(
|
|
|
|
'Alexa.ColorTemperatureController', 'IncreaseColorTemperature',
|
|
|
|
'light#test')
|
|
|
|
|
2017-11-17 17:14:22 +00:00
|
|
|
# setup test devices
|
2017-11-01 03:28:17 +00:00
|
|
|
hass.states.async_set(
|
|
|
|
'light.test', 'off', {
|
|
|
|
'friendly_name': "Test light", 'color_temp': initial,
|
|
|
|
'min_mireds': 142,
|
|
|
|
})
|
|
|
|
|
|
|
|
call_light = async_mock_service(hass, 'light', 'turn_on')
|
|
|
|
|
2018-10-29 21:57:27 +00:00
|
|
|
msg = await smart_home.async_handle_message(
|
2017-11-18 05:10:24 +00:00
|
|
|
hass, DEFAULT_CONFIG, request)
|
2018-10-29 21:57:27 +00:00
|
|
|
await hass.async_block_till_done()
|
2017-11-01 03:28:17 +00:00
|
|
|
|
|
|
|
assert 'event' in msg
|
|
|
|
msg = msg['event']
|
|
|
|
|
|
|
|
assert len(call_light) == 1
|
|
|
|
assert call_light[0].data['entity_id'] == 'light.test'
|
|
|
|
assert call_light[0].data['color_temp'] == result
|
2017-10-07 20:31:57 +00:00
|
|
|
assert msg['header']['name'] == 'Response'
|
2017-11-17 17:14:22 +00:00
|
|
|
|
|
|
|
|
2019-01-03 21:28:43 +00:00
|
|
|
async def test_api_accept_grant(hass):
|
|
|
|
"""Test api AcceptGrant process."""
|
|
|
|
request = get_new_request("Alexa.Authorization", "AcceptGrant")
|
|
|
|
|
|
|
|
# add payload
|
|
|
|
request['directive']['payload'] = {
|
|
|
|
'grant': {
|
|
|
|
'type': 'OAuth2.AuthorizationCode',
|
|
|
|
'code': 'VGhpcyBpcyBhbiBhdXRob3JpemF0aW9uIGNvZGUuIDotKQ=='
|
|
|
|
},
|
|
|
|
'grantee': {
|
|
|
|
'type': 'BearerToken',
|
|
|
|
'token': 'access-token-from-skill'
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
# setup test devices
|
|
|
|
msg = await smart_home.async_handle_message(
|
|
|
|
hass, DEFAULT_CONFIG, request)
|
|
|
|
await hass.async_block_till_done()
|
|
|
|
|
|
|
|
assert 'event' in msg
|
|
|
|
msg = msg['event']
|
|
|
|
|
|
|
|
assert msg['header']['name'] == 'AcceptGrant.Response'
|
|
|
|
|
|
|
|
|
2018-10-29 21:57:27 +00:00
|
|
|
async def test_report_lock_state(hass):
|
2018-01-30 04:33:39 +00:00
|
|
|
"""Test LockController implements lockState property."""
|
2017-11-17 17:14:22 +00:00
|
|
|
hass.states.async_set(
|
2018-01-30 04:33:39 +00:00
|
|
|
'lock.locked', STATE_LOCKED, {})
|
|
|
|
hass.states.async_set(
|
|
|
|
'lock.unlocked', STATE_UNLOCKED, {})
|
|
|
|
hass.states.async_set(
|
|
|
|
'lock.unknown', STATE_UNKNOWN, {})
|
|
|
|
|
2018-10-29 21:57:27 +00:00
|
|
|
properties = await reported_properties(hass, 'lock.locked')
|
2018-01-30 04:33:39 +00:00
|
|
|
properties.assert_equal('Alexa.LockController', 'lockState', 'LOCKED')
|
|
|
|
|
2018-10-29 21:57:27 +00:00
|
|
|
properties = await reported_properties(hass, 'lock.unlocked')
|
2018-01-30 04:33:39 +00:00
|
|
|
properties.assert_equal('Alexa.LockController', 'lockState', 'UNLOCKED')
|
|
|
|
|
2018-10-29 21:57:27 +00:00
|
|
|
properties = await reported_properties(hass, 'lock.unknown')
|
2018-01-30 04:33:39 +00:00
|
|
|
properties.assert_equal('Alexa.LockController', 'lockState', 'JAMMED')
|
2017-11-17 17:14:22 +00:00
|
|
|
|
2018-01-26 05:06:57 +00:00
|
|
|
|
2018-10-29 21:57:27 +00:00
|
|
|
async def test_report_dimmable_light_state(hass):
|
2018-02-12 06:36:22 +00:00
|
|
|
"""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})
|
|
|
|
|
2018-10-29 21:57:27 +00:00
|
|
|
properties = await reported_properties(hass, 'light.test_on')
|
2018-02-12 06:36:22 +00:00
|
|
|
properties.assert_equal('Alexa.BrightnessController', 'brightness', 50)
|
|
|
|
|
2018-10-29 21:57:27 +00:00
|
|
|
properties = await reported_properties(hass, 'light.test_off')
|
2018-02-12 06:36:22 +00:00
|
|
|
properties.assert_equal('Alexa.BrightnessController', 'brightness', 0)
|
|
|
|
|
|
|
|
|
2018-11-11 16:43:01 +00:00
|
|
|
async def test_report_colored_light_state(hass):
|
|
|
|
"""Test ColorController reports color correctly."""
|
|
|
|
hass.states.async_set(
|
|
|
|
'light.test_on', 'on', {'friendly_name': "Test light On",
|
|
|
|
'hs_color': (180, 75),
|
|
|
|
'brightness': 128,
|
|
|
|
'supported_features': 17})
|
|
|
|
hass.states.async_set(
|
|
|
|
'light.test_off', 'off', {'friendly_name': "Test light Off",
|
|
|
|
'supported_features': 17})
|
|
|
|
|
|
|
|
properties = await reported_properties(hass, 'light.test_on')
|
|
|
|
properties.assert_equal('Alexa.ColorController', 'color', {
|
|
|
|
'hue': 180,
|
|
|
|
'saturation': 0.75,
|
|
|
|
'brightness': 128 / 255.0,
|
|
|
|
})
|
|
|
|
|
|
|
|
properties = await reported_properties(hass, 'light.test_off')
|
|
|
|
properties.assert_equal('Alexa.ColorController', 'color', {
|
|
|
|
'hue': 0,
|
|
|
|
'saturation': 0,
|
|
|
|
'brightness': 0,
|
|
|
|
})
|
|
|
|
|
|
|
|
|
2018-12-06 16:05:15 +00:00
|
|
|
async def test_report_colored_temp_light_state(hass):
|
|
|
|
"""Test ColorTemperatureController reports color temp correctly."""
|
|
|
|
hass.states.async_set(
|
|
|
|
'light.test_on', 'on', {'friendly_name': "Test light On",
|
|
|
|
'color_temp': 240,
|
|
|
|
'supported_features': 2})
|
|
|
|
hass.states.async_set(
|
|
|
|
'light.test_off', 'off', {'friendly_name': "Test light Off",
|
|
|
|
'supported_features': 2})
|
|
|
|
|
|
|
|
properties = await reported_properties(hass, 'light.test_on')
|
|
|
|
properties.assert_equal('Alexa.ColorTemperatureController',
|
|
|
|
'colorTemperatureInKelvin', 4166)
|
|
|
|
|
|
|
|
properties = await reported_properties(hass, 'light.test_off')
|
|
|
|
properties.assert_equal('Alexa.ColorTemperatureController',
|
|
|
|
'colorTemperatureInKelvin', 0)
|
|
|
|
|
|
|
|
|
2018-10-29 21:57:27 +00:00
|
|
|
async def reported_properties(hass, endpoint):
|
2018-01-30 04:33:39 +00:00
|
|
|
"""Use ReportState to get properties and return them.
|
2017-11-17 17:14:22 +00:00
|
|
|
|
2018-01-30 04:33:39 +00:00
|
|
|
The result is a _ReportedProperties instance, which has methods to make
|
|
|
|
assertions about the properties.
|
|
|
|
"""
|
|
|
|
request = get_new_request('Alexa', 'ReportState', endpoint)
|
2018-10-29 21:57:27 +00:00
|
|
|
msg = await smart_home.async_handle_message(
|
2017-11-18 05:10:24 +00:00
|
|
|
hass, DEFAULT_CONFIG, request)
|
2018-10-29 21:57:27 +00:00
|
|
|
await hass.async_block_till_done()
|
2018-01-30 04:33:39 +00:00
|
|
|
return _ReportedProperties(msg['context']['properties'])
|
2017-11-17 17:14:22 +00:00
|
|
|
|
|
|
|
|
2018-07-20 08:45:20 +00:00
|
|
|
class _ReportedProperties:
|
2018-01-30 04:33:39 +00:00
|
|
|
def __init__(self, properties):
|
|
|
|
self.properties = properties
|
|
|
|
|
|
|
|
def assert_equal(self, namespace, name, value):
|
|
|
|
"""Assert a property is equal to a given value."""
|
|
|
|
for prop in self.properties:
|
|
|
|
if prop['namespace'] == namespace and prop['name'] == name:
|
|
|
|
assert prop['value'] == value
|
2018-02-12 06:36:22 +00:00
|
|
|
return prop
|
2018-01-30 04:33:39 +00:00
|
|
|
|
|
|
|
assert False, 'property %s:%s not in %r' % (
|
|
|
|
namespace,
|
|
|
|
name,
|
|
|
|
self.properties,
|
|
|
|
)
|
2017-11-17 17:14:22 +00:00
|
|
|
|
|
|
|
|
2018-10-29 21:57:27 +00:00
|
|
|
async def test_entity_config(hass):
|
2018-01-30 04:33:39 +00:00
|
|
|
"""Test that we can configure things via entity config."""
|
|
|
|
request = get_new_request('Alexa.Discovery', 'Discover')
|
2018-01-26 05:06:57 +00:00
|
|
|
|
|
|
|
hass.states.async_set(
|
2018-01-30 04:33:39 +00:00
|
|
|
'light.test_1', 'on', {'friendly_name': "Test light 1"})
|
2018-01-26 05:06:57 +00:00
|
|
|
|
2018-01-30 04:33:39 +00:00
|
|
|
config = smart_home.Config(
|
2019-01-03 21:28:43 +00:00
|
|
|
endpoint=None,
|
|
|
|
async_get_access_token=None,
|
2018-01-30 04:33:39 +00:00
|
|
|
should_expose=lambda entity_id: True,
|
|
|
|
entity_config={
|
|
|
|
'light.test_1': {
|
|
|
|
'name': 'Config name',
|
|
|
|
'display_categories': 'SWITCH',
|
|
|
|
'description': 'Config description'
|
|
|
|
}
|
|
|
|
}
|
|
|
|
)
|
2018-01-26 05:06:57 +00:00
|
|
|
|
2018-10-29 21:57:27 +00:00
|
|
|
msg = await smart_home.async_handle_message(
|
2018-01-30 04:33:39 +00:00
|
|
|
hass, config, request)
|
2018-01-26 05:06:57 +00:00
|
|
|
|
|
|
|
assert 'event' in msg
|
|
|
|
msg = msg['event']
|
|
|
|
|
2018-01-30 04:33:39 +00:00
|
|
|
assert len(msg['payload']['endpoints']) == 1
|
2018-01-05 20:33:22 +00:00
|
|
|
|
|
|
|
appliance = msg['payload']['endpoints'][0]
|
|
|
|
assert appliance['endpointId'] == 'light#test_1'
|
|
|
|
assert appliance['displayCategories'][0] == "SWITCH"
|
|
|
|
assert appliance['friendlyName'] == "Config name"
|
|
|
|
assert appliance['description'] == "Config description"
|
2019-01-10 23:52:21 +00:00
|
|
|
assert_endpoint_capabilities(
|
|
|
|
appliance,
|
|
|
|
'Alexa.PowerController',
|
|
|
|
'Alexa.EndpointHealth',
|
|
|
|
)
|
2018-01-23 18:45:28 +00:00
|
|
|
|
|
|
|
|
2018-10-29 21:57:27 +00:00
|
|
|
async def test_unsupported_domain(hass):
|
2018-01-26 05:06:57 +00:00
|
|
|
"""Discovery ignores entities of unknown domains."""
|
|
|
|
request = get_new_request('Alexa.Discovery', 'Discover')
|
|
|
|
|
|
|
|
hass.states.async_set(
|
|
|
|
'woz.boop', 'on', {'friendly_name': "Boop Woz"})
|
|
|
|
|
2018-10-29 21:57:27 +00:00
|
|
|
msg = await smart_home.async_handle_message(
|
2018-01-26 05:06:57 +00:00
|
|
|
hass, DEFAULT_CONFIG, request)
|
|
|
|
|
|
|
|
assert 'event' in msg
|
|
|
|
msg = msg['event']
|
|
|
|
|
2018-01-30 04:33:39 +00:00
|
|
|
assert not msg['payload']['endpoints']
|
2018-01-26 05:06:57 +00:00
|
|
|
|
|
|
|
|
2018-11-27 09:41:44 +00:00
|
|
|
async def do_http_discovery(config, hass, hass_client):
|
2018-01-23 18:45:28 +00:00
|
|
|
"""Submit a request to the Smart Home HTTP API."""
|
2018-10-29 21:57:27 +00:00
|
|
|
await async_setup_component(hass, alexa.DOMAIN, config)
|
2018-11-27 09:41:44 +00:00
|
|
|
http_client = await hass_client()
|
2018-01-23 18:45:28 +00:00
|
|
|
|
|
|
|
request = get_new_request('Alexa.Discovery', 'Discover')
|
2018-10-29 21:57:27 +00:00
|
|
|
response = await http_client.post(
|
2018-01-23 18:45:28 +00:00
|
|
|
smart_home.SMART_HOME_HTTP_ENDPOINT,
|
|
|
|
data=json.dumps(request),
|
|
|
|
headers={'content-type': 'application/json'})
|
|
|
|
return response
|
|
|
|
|
|
|
|
|
2018-11-27 09:41:44 +00:00
|
|
|
async def test_http_api(hass, hass_client):
|
2018-01-23 18:45:28 +00:00
|
|
|
"""With `smart_home:` HTTP API is exposed."""
|
|
|
|
config = {
|
|
|
|
'alexa': {
|
|
|
|
'smart_home': None
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-11-27 09:41:44 +00:00
|
|
|
response = await do_http_discovery(config, hass, hass_client)
|
2018-10-29 21:57:27 +00:00
|
|
|
response_data = await response.json()
|
2018-01-23 18:45:28 +00:00
|
|
|
|
|
|
|
# Here we're testing just the HTTP view glue -- details of discovery are
|
|
|
|
# covered in other tests.
|
|
|
|
assert response_data['event']['header']['name'] == 'Discover.Response'
|
|
|
|
|
|
|
|
|
2018-11-27 09:41:44 +00:00
|
|
|
async def test_http_api_disabled(hass, hass_client):
|
2018-01-23 18:45:28 +00:00
|
|
|
"""Without `smart_home:`, the HTTP API is disabled."""
|
|
|
|
config = {
|
|
|
|
'alexa': {}
|
|
|
|
}
|
2018-11-27 09:41:44 +00:00
|
|
|
response = await do_http_discovery(config, hass, hass_client)
|
2018-01-23 18:45:28 +00:00
|
|
|
|
|
|
|
assert response.status == 404
|
2018-01-30 04:33:39 +00:00
|
|
|
|
|
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
|
|
"domain,payload,source_list,idx", [
|
|
|
|
('media_player', 'GAME CONSOLE', ['tv', 'game console'], 1),
|
|
|
|
('media_player', 'SATELLITE TV', ['satellite-tv', 'game console'], 0),
|
|
|
|
('media_player', 'SATELLITE TV', ['satellite_tv', 'game console'], 0),
|
|
|
|
('media_player', 'BAD DEVICE', ['satellite_tv', 'game console'], None),
|
|
|
|
]
|
|
|
|
)
|
2018-10-29 21:57:27 +00:00
|
|
|
async def test_api_select_input(hass, domain, payload, source_list, idx):
|
2018-01-30 04:33:39 +00:00
|
|
|
"""Test api set input process."""
|
|
|
|
hass.states.async_set(
|
|
|
|
'media_player.test', 'off', {
|
|
|
|
'friendly_name': "Test media player",
|
|
|
|
'source': 'unknown',
|
|
|
|
'source_list': source_list,
|
|
|
|
})
|
|
|
|
|
|
|
|
# test where no source matches
|
|
|
|
if idx is None:
|
2018-10-29 21:57:27 +00:00
|
|
|
await assert_request_fails(
|
2018-01-30 04:33:39 +00:00
|
|
|
'Alexa.InputController', 'SelectInput', 'media_player#test',
|
|
|
|
'media_player.select_source',
|
|
|
|
hass,
|
|
|
|
payload={'input': payload})
|
|
|
|
return
|
|
|
|
|
2018-10-29 21:57:27 +00:00
|
|
|
call, _ = await assert_request_calls_service(
|
2018-01-30 04:33:39 +00:00
|
|
|
'Alexa.InputController', 'SelectInput', 'media_player#test',
|
|
|
|
'media_player.select_source',
|
|
|
|
hass,
|
|
|
|
payload={'input': payload})
|
|
|
|
assert call.data['source'] == source_list[idx]
|
2018-08-20 12:18:07 +00:00
|
|
|
|
|
|
|
|
|
|
|
async def test_logging_request(hass, events):
|
|
|
|
"""Test that we log requests."""
|
|
|
|
context = Context()
|
|
|
|
request = get_new_request('Alexa.Discovery', 'Discover')
|
|
|
|
await smart_home.async_handle_message(
|
|
|
|
hass, DEFAULT_CONFIG, request, context)
|
|
|
|
|
|
|
|
# To trigger event listener
|
|
|
|
await hass.async_block_till_done()
|
|
|
|
|
|
|
|
assert len(events) == 1
|
|
|
|
event = events[0]
|
|
|
|
|
|
|
|
assert event.data['request'] == {
|
|
|
|
'namespace': 'Alexa.Discovery',
|
|
|
|
'name': 'Discover',
|
|
|
|
}
|
|
|
|
assert event.data['response'] == {
|
|
|
|
'namespace': 'Alexa.Discovery',
|
|
|
|
'name': 'Discover.Response'
|
|
|
|
}
|
|
|
|
assert event.context == context
|
|
|
|
|
|
|
|
|
|
|
|
async def test_logging_request_with_entity(hass, events):
|
|
|
|
"""Test that we log requests."""
|
|
|
|
context = Context()
|
|
|
|
request = get_new_request('Alexa.PowerController', 'TurnOn', 'switch#xy')
|
|
|
|
await smart_home.async_handle_message(
|
|
|
|
hass, DEFAULT_CONFIG, request, context)
|
|
|
|
|
|
|
|
# To trigger event listener
|
|
|
|
await hass.async_block_till_done()
|
|
|
|
|
|
|
|
assert len(events) == 1
|
|
|
|
event = events[0]
|
|
|
|
|
|
|
|
assert event.data['request'] == {
|
|
|
|
'namespace': 'Alexa.PowerController',
|
|
|
|
'name': 'TurnOn',
|
|
|
|
'entity_id': 'switch.xy'
|
|
|
|
}
|
|
|
|
# Entity doesn't exist
|
|
|
|
assert event.data['response'] == {
|
|
|
|
'namespace': 'Alexa',
|
|
|
|
'name': 'ErrorResponse'
|
|
|
|
}
|
|
|
|
assert event.context == context
|
Refactor Alexa API, fix thermostats (#17969)
* Refactor Alexa API to use objects for requests
This introduces _AlexaDirective to stand in for the previous model of passing
basic dict and list data structures to and from handlers. This gives a more
expressive platform for functionality common to most or all handlers.
I had two use cases in mind:
1) Most responses should include current properties. In the case of locks and
thermostats, the response must include the properties or Alexa will give the
user a vague error like "Hmm, $device is not responding." Locks currently work,
but thermostats do not. I wanted a way to automatically include properties in
all responses. This is implemented in a subsequent commit.
2) The previous model had a 1:1 mapping between Alexa endpoints and Home
Assistant entities. This works most of the time, but sometimes it's not so
great. For example, my Z-wave thermostat shows as three devices in Alexa: one
for the temperature sensor, one for the heat, and one for the AC. I'd like to
merge these into one device from Alexa's perspective. I believe this will be
facilitated with the `endpoint` attribute on `_AlexaDirective`.
* Include properties in all Alexa responses
The added _AlexaResponse class provides a richer vocabulary for handlers.
Among that vocabulary is .merge_context_properties(), which is invoked
automatically for any request directed at an endpoint. This adds all supported
properties to the response as recommended by the Alexa API docs, and in some
cases (locks, thermostats at least) the user will get an error "Hmm, $device is
not responding" if properties are not provided in the response.
* Fix setting temperature with Alexa thermostats
Fixes https://github.com/home-assistant/home-assistant/issues/16577
2018-10-30 02:16:35 +00:00
|
|
|
|
|
|
|
|
|
|
|
async def test_disabled(hass):
|
|
|
|
"""When enabled=False, everything fails."""
|
|
|
|
hass.states.async_set(
|
|
|
|
'switch.test', 'on', {'friendly_name': "Test switch"})
|
|
|
|
request = get_new_request('Alexa.PowerController', 'TurnOn', 'switch#test')
|
|
|
|
|
|
|
|
call_switch = async_mock_service(hass, 'switch', 'turn_on')
|
|
|
|
|
|
|
|
msg = await smart_home.async_handle_message(
|
|
|
|
hass, DEFAULT_CONFIG, request, enabled=False)
|
|
|
|
await hass.async_block_till_done()
|
|
|
|
|
|
|
|
assert 'event' in msg
|
|
|
|
msg = msg['event']
|
|
|
|
|
|
|
|
assert not call_switch
|
|
|
|
assert msg['header']['name'] == 'ErrorResponse'
|
|
|
|
assert msg['header']['namespace'] == 'Alexa'
|
|
|
|
assert msg['payload']['type'] == 'BRIDGE_UNREACHABLE'
|
2019-01-03 21:28:43 +00:00
|
|
|
|
|
|
|
|
2019-01-10 23:52:21 +00:00
|
|
|
async def test_endpoint_good_health(hass):
|
|
|
|
"""Test endpoint health reporting."""
|
|
|
|
device = (
|
|
|
|
'binary_sensor.test_contact',
|
|
|
|
'on',
|
|
|
|
{
|
|
|
|
'friendly_name': "Test Contact Sensor",
|
|
|
|
'device_class': 'door',
|
|
|
|
}
|
|
|
|
)
|
|
|
|
await discovery_test(device, hass)
|
|
|
|
properties = await reported_properties(hass, 'binary_sensor#test_contact')
|
|
|
|
properties.assert_equal('Alexa.EndpointHealth', 'connectivity',
|
|
|
|
{'value': 'OK'})
|
|
|
|
|
|
|
|
|
|
|
|
async def test_endpoint_bad_health(hass):
|
|
|
|
"""Test endpoint health reporting."""
|
|
|
|
device = (
|
|
|
|
'binary_sensor.test_contact',
|
|
|
|
'unavailable',
|
|
|
|
{
|
|
|
|
'friendly_name': "Test Contact Sensor",
|
|
|
|
'device_class': 'door',
|
|
|
|
}
|
|
|
|
)
|
|
|
|
await discovery_test(device, hass)
|
|
|
|
properties = await reported_properties(hass, 'binary_sensor#test_contact')
|
|
|
|
properties.assert_equal('Alexa.EndpointHealth', 'connectivity',
|
|
|
|
{'value': 'UNREACHABLE'})
|
|
|
|
|
|
|
|
|
2019-01-03 21:28:43 +00:00
|
|
|
async def test_report_state(hass, aioclient_mock):
|
|
|
|
"""Test proactive state reports."""
|
|
|
|
aioclient_mock.post(TEST_URL, json={'data': 'is irrelevant'})
|
|
|
|
|
|
|
|
hass.states.async_set(
|
|
|
|
'binary_sensor.test_contact',
|
|
|
|
'on',
|
|
|
|
{
|
|
|
|
'friendly_name': "Test Contact Sensor",
|
|
|
|
'device_class': 'door',
|
|
|
|
}
|
|
|
|
)
|
|
|
|
|
|
|
|
await smart_home.async_enable_proactive_mode(hass, DEFAULT_CONFIG)
|
|
|
|
|
|
|
|
hass.states.async_set(
|
|
|
|
'binary_sensor.test_contact',
|
|
|
|
'off',
|
|
|
|
{
|
|
|
|
'friendly_name': "Test Contact Sensor",
|
|
|
|
'device_class': 'door',
|
|
|
|
}
|
|
|
|
)
|
|
|
|
|
|
|
|
# To trigger event listener
|
|
|
|
await hass.async_block_till_done()
|
|
|
|
|
|
|
|
assert len(aioclient_mock.mock_calls) == 1
|
|
|
|
call = aioclient_mock.mock_calls
|
|
|
|
|
|
|
|
call_json = json.loads(call[0][2])
|
|
|
|
assert call_json["event"]["payload"]["change"]["properties"][0][
|
|
|
|
"value"] == "NOT_DETECTED"
|
|
|
|
assert call_json["event"]["endpoint"][
|
|
|
|
"endpointId"] == "binary_sensor#test_contact"
|
|
|
|
|
|
|
|
|
|
|
|
async def run_auth_get_access_token(hass, aioclient_mock, expires_in,
|
|
|
|
client_id, client_secret,
|
|
|
|
accept_grant_code, refresh_token):
|
|
|
|
"""Do auth and request a new token for tests."""
|
|
|
|
aioclient_mock.post(TEST_TOKEN_URL,
|
|
|
|
json={'access_token': 'the_access_token',
|
|
|
|
'refresh_token': refresh_token,
|
|
|
|
'expires_in': expires_in})
|
|
|
|
|
|
|
|
auth = Auth(hass, client_id, client_secret)
|
|
|
|
await auth.async_do_auth(accept_grant_code)
|
|
|
|
await auth.async_get_access_token()
|
|
|
|
|
|
|
|
|
|
|
|
async def test_auth_get_access_token_expired(hass, aioclient_mock):
|
|
|
|
"""Test the auth get access token function."""
|
|
|
|
client_id = "client123"
|
|
|
|
client_secret = "shhhhh"
|
|
|
|
accept_grant_code = "abcdefg"
|
|
|
|
refresh_token = "refresher"
|
|
|
|
|
|
|
|
await run_auth_get_access_token(hass, aioclient_mock, -5,
|
|
|
|
client_id, client_secret,
|
|
|
|
accept_grant_code, refresh_token)
|
|
|
|
|
|
|
|
assert len(aioclient_mock.mock_calls) == 2
|
|
|
|
calls = aioclient_mock.mock_calls
|
|
|
|
|
|
|
|
auth_call_json = calls[0][2]
|
|
|
|
token_call_json = calls[1][2]
|
|
|
|
|
|
|
|
assert auth_call_json["grant_type"] == "authorization_code"
|
|
|
|
assert auth_call_json["code"] == accept_grant_code
|
|
|
|
assert auth_call_json["client_id"] == client_id
|
|
|
|
assert auth_call_json["client_secret"] == client_secret
|
|
|
|
|
|
|
|
assert token_call_json["grant_type"] == "refresh_token"
|
|
|
|
assert token_call_json["refresh_token"] == refresh_token
|
|
|
|
assert token_call_json["client_id"] == client_id
|
|
|
|
assert token_call_json["client_secret"] == client_secret
|
|
|
|
|
|
|
|
|
|
|
|
async def test_auth_get_access_token_not_expired(hass, aioclient_mock):
|
|
|
|
"""Test the auth get access token function."""
|
|
|
|
client_id = "client123"
|
|
|
|
client_secret = "shhhhh"
|
|
|
|
accept_grant_code = "abcdefg"
|
|
|
|
refresh_token = "refresher"
|
|
|
|
|
|
|
|
await run_auth_get_access_token(hass, aioclient_mock, 555,
|
|
|
|
client_id, client_secret,
|
|
|
|
accept_grant_code, refresh_token)
|
|
|
|
|
|
|
|
assert len(aioclient_mock.mock_calls) == 1
|
|
|
|
call = aioclient_mock.mock_calls
|
|
|
|
|
|
|
|
auth_call_json = call[0][2]
|
|
|
|
|
|
|
|
assert auth_call_json["grant_type"] == "authorization_code"
|
|
|
|
assert auth_call_json["code"] == accept_grant_code
|
|
|
|
assert auth_call_json["client_id"] == client_id
|
|
|
|
assert auth_call_json["client_secret"] == client_secret
|