Alexa: context + log events (#16023)

This commit is contained in:
Paulus Schoutsen 2018-08-20 14:18:07 +02:00 committed by GitHub
parent 1f0d113688
commit 18d19fde0b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 194 additions and 126 deletions

View File

@ -53,6 +53,7 @@ CONF_DISPLAY_CATEGORIES = 'display_categories'
HANDLERS = Registry() HANDLERS = Registry()
ENTITY_ADAPTERS = Registry() ENTITY_ADAPTERS = Registry()
EVENT_ALEXA_SMART_HOME = 'alexa_smart_home'
class _DisplayCategory: class _DisplayCategory:
@ -703,24 +704,47 @@ class SmartHomeView(http.HomeAssistantView):
return b'' if response is None else self.json(response) return b'' if response is None else self.json(response)
@asyncio.coroutine async def async_handle_message(hass, config, request, context=None):
def async_handle_message(hass, config, message):
"""Handle incoming API messages.""" """Handle incoming API messages."""
assert message[API_DIRECTIVE][API_HEADER]['payloadVersion'] == '3' assert request[API_DIRECTIVE][API_HEADER]['payloadVersion'] == '3'
if context is None:
context = ha.Context()
# Read head data # Read head data
message = message[API_DIRECTIVE] request = request[API_DIRECTIVE]
namespace = message[API_HEADER]['namespace'] namespace = request[API_HEADER]['namespace']
name = message[API_HEADER]['name'] name = request[API_HEADER]['name']
# Do we support this API request? # Do we support this API request?
funct_ref = HANDLERS.get((namespace, name)) funct_ref = HANDLERS.get((namespace, name))
if not funct_ref: if funct_ref:
response = await funct_ref(hass, config, request, context)
else:
_LOGGER.warning( _LOGGER.warning(
"Unsupported API request %s/%s", namespace, name) "Unsupported API request %s/%s", namespace, name)
return api_error(message) response = api_error(request)
return (yield from funct_ref(hass, config, message)) request_info = {
'namespace': namespace,
'name': name,
}
if API_ENDPOINT in request and 'endpointId' in request[API_ENDPOINT]:
request_info['entity_id'] = \
request[API_ENDPOINT]['endpointId'].replace('#', '.')
response_header = response[API_EVENT][API_HEADER]
hass.bus.async_fire(EVENT_ALEXA_SMART_HOME, {
'request': request_info,
'response': {
'namespace': response_header['namespace'],
'name': response_header['name'],
}
}, context=context)
return response
def api_message(request, def api_message(request,
@ -784,8 +808,7 @@ def api_error(request,
@HANDLERS.register(('Alexa.Discovery', 'Discover')) @HANDLERS.register(('Alexa.Discovery', 'Discover'))
@asyncio.coroutine async def async_api_discovery(hass, config, request, context):
def async_api_discovery(hass, config, request):
"""Create a API formatted discovery response. """Create a API formatted discovery response.
Async friendly. Async friendly.
@ -827,8 +850,7 @@ def async_api_discovery(hass, config, request):
def extract_entity(funct): def extract_entity(funct):
"""Decorate for extract entity object from request.""" """Decorate for extract entity object from request."""
@asyncio.coroutine async def async_api_entity_wrapper(hass, config, request, context):
def async_api_entity_wrapper(hass, config, request):
"""Process a turn on request.""" """Process a turn on request."""
entity_id = request[API_ENDPOINT]['endpointId'].replace('#', '.') entity_id = request[API_ENDPOINT]['endpointId'].replace('#', '.')
@ -839,15 +861,14 @@ def extract_entity(funct):
request[API_HEADER]['name'], entity_id) request[API_HEADER]['name'], entity_id)
return api_error(request, error_type='NO_SUCH_ENDPOINT') return api_error(request, error_type='NO_SUCH_ENDPOINT')
return (yield from funct(hass, config, request, entity)) return await funct(hass, config, request, context, entity)
return async_api_entity_wrapper return async_api_entity_wrapper
@HANDLERS.register(('Alexa.PowerController', 'TurnOn')) @HANDLERS.register(('Alexa.PowerController', 'TurnOn'))
@extract_entity @extract_entity
@asyncio.coroutine async def async_api_turn_on(hass, config, request, context, entity):
def async_api_turn_on(hass, config, request, entity):
"""Process a turn on request.""" """Process a turn on request."""
domain = entity.domain domain = entity.domain
if entity.domain == group.DOMAIN: if entity.domain == group.DOMAIN:
@ -857,17 +878,16 @@ def async_api_turn_on(hass, config, request, entity):
if entity.domain == cover.DOMAIN: if entity.domain == cover.DOMAIN:
service = cover.SERVICE_OPEN_COVER service = cover.SERVICE_OPEN_COVER
yield from hass.services.async_call(domain, service, { await hass.services.async_call(domain, service, {
ATTR_ENTITY_ID: entity.entity_id ATTR_ENTITY_ID: entity.entity_id
}, blocking=False) }, blocking=False, context=context)
return api_message(request) return api_message(request)
@HANDLERS.register(('Alexa.PowerController', 'TurnOff')) @HANDLERS.register(('Alexa.PowerController', 'TurnOff'))
@extract_entity @extract_entity
@asyncio.coroutine async def async_api_turn_off(hass, config, request, context, entity):
def async_api_turn_off(hass, config, request, entity):
"""Process a turn off request.""" """Process a turn off request."""
domain = entity.domain domain = entity.domain
if entity.domain == group.DOMAIN: if entity.domain == group.DOMAIN:
@ -877,32 +897,30 @@ def async_api_turn_off(hass, config, request, entity):
if entity.domain == cover.DOMAIN: if entity.domain == cover.DOMAIN:
service = cover.SERVICE_CLOSE_COVER service = cover.SERVICE_CLOSE_COVER
yield from hass.services.async_call(domain, service, { await hass.services.async_call(domain, service, {
ATTR_ENTITY_ID: entity.entity_id ATTR_ENTITY_ID: entity.entity_id
}, blocking=False) }, blocking=False, context=context)
return api_message(request) return api_message(request)
@HANDLERS.register(('Alexa.BrightnessController', 'SetBrightness')) @HANDLERS.register(('Alexa.BrightnessController', 'SetBrightness'))
@extract_entity @extract_entity
@asyncio.coroutine async def async_api_set_brightness(hass, config, request, context, entity):
def async_api_set_brightness(hass, config, request, entity):
"""Process a set brightness request.""" """Process a set brightness request."""
brightness = int(request[API_PAYLOAD]['brightness']) brightness = int(request[API_PAYLOAD]['brightness'])
yield from hass.services.async_call(entity.domain, SERVICE_TURN_ON, { await hass.services.async_call(entity.domain, SERVICE_TURN_ON, {
ATTR_ENTITY_ID: entity.entity_id, ATTR_ENTITY_ID: entity.entity_id,
light.ATTR_BRIGHTNESS_PCT: brightness, light.ATTR_BRIGHTNESS_PCT: brightness,
}, blocking=False) }, blocking=False, context=context)
return api_message(request) return api_message(request)
@HANDLERS.register(('Alexa.BrightnessController', 'AdjustBrightness')) @HANDLERS.register(('Alexa.BrightnessController', 'AdjustBrightness'))
@extract_entity @extract_entity
@asyncio.coroutine async def async_api_adjust_brightness(hass, config, request, context, entity):
def async_api_adjust_brightness(hass, config, request, entity):
"""Process an adjust brightness request.""" """Process an adjust brightness request."""
brightness_delta = int(request[API_PAYLOAD]['brightnessDelta']) brightness_delta = int(request[API_PAYLOAD]['brightnessDelta'])
@ -915,18 +933,17 @@ def async_api_adjust_brightness(hass, config, request, entity):
# set brightness # set brightness
brightness = max(0, brightness_delta + current) brightness = max(0, brightness_delta + current)
yield from hass.services.async_call(entity.domain, SERVICE_TURN_ON, { await hass.services.async_call(entity.domain, SERVICE_TURN_ON, {
ATTR_ENTITY_ID: entity.entity_id, ATTR_ENTITY_ID: entity.entity_id,
light.ATTR_BRIGHTNESS_PCT: brightness, light.ATTR_BRIGHTNESS_PCT: brightness,
}, blocking=False) }, blocking=False, context=context)
return api_message(request) return api_message(request)
@HANDLERS.register(('Alexa.ColorController', 'SetColor')) @HANDLERS.register(('Alexa.ColorController', 'SetColor'))
@extract_entity @extract_entity
@asyncio.coroutine async def async_api_set_color(hass, config, request, context, entity):
def async_api_set_color(hass, config, request, entity):
"""Process a set color request.""" """Process a set color request."""
rgb = color_util.color_hsb_to_RGB( rgb = color_util.color_hsb_to_RGB(
float(request[API_PAYLOAD]['color']['hue']), float(request[API_PAYLOAD]['color']['hue']),
@ -934,25 +951,25 @@ def async_api_set_color(hass, config, request, entity):
float(request[API_PAYLOAD]['color']['brightness']) float(request[API_PAYLOAD]['color']['brightness'])
) )
yield from hass.services.async_call(entity.domain, SERVICE_TURN_ON, { await hass.services.async_call(entity.domain, SERVICE_TURN_ON, {
ATTR_ENTITY_ID: entity.entity_id, ATTR_ENTITY_ID: entity.entity_id,
light.ATTR_RGB_COLOR: rgb, light.ATTR_RGB_COLOR: rgb,
}, blocking=False) }, blocking=False, context=context)
return api_message(request) return api_message(request)
@HANDLERS.register(('Alexa.ColorTemperatureController', 'SetColorTemperature')) @HANDLERS.register(('Alexa.ColorTemperatureController', 'SetColorTemperature'))
@extract_entity @extract_entity
@asyncio.coroutine async def async_api_set_color_temperature(hass, config, request, context,
def async_api_set_color_temperature(hass, config, request, entity): entity):
"""Process a set color temperature request.""" """Process a set color temperature request."""
kelvin = int(request[API_PAYLOAD]['colorTemperatureInKelvin']) kelvin = int(request[API_PAYLOAD]['colorTemperatureInKelvin'])
yield from hass.services.async_call(entity.domain, SERVICE_TURN_ON, { await hass.services.async_call(entity.domain, SERVICE_TURN_ON, {
ATTR_ENTITY_ID: entity.entity_id, ATTR_ENTITY_ID: entity.entity_id,
light.ATTR_KELVIN: kelvin, light.ATTR_KELVIN: kelvin,
}, blocking=False) }, blocking=False, context=context)
return api_message(request) return api_message(request)
@ -960,17 +977,17 @@ def async_api_set_color_temperature(hass, config, request, entity):
@HANDLERS.register( @HANDLERS.register(
('Alexa.ColorTemperatureController', 'DecreaseColorTemperature')) ('Alexa.ColorTemperatureController', 'DecreaseColorTemperature'))
@extract_entity @extract_entity
@asyncio.coroutine async def async_api_decrease_color_temp(hass, config, request, context,
def async_api_decrease_color_temp(hass, config, request, entity): entity):
"""Process a decrease color temperature request.""" """Process a decrease color temperature request."""
current = int(entity.attributes.get(light.ATTR_COLOR_TEMP)) current = int(entity.attributes.get(light.ATTR_COLOR_TEMP))
max_mireds = int(entity.attributes.get(light.ATTR_MAX_MIREDS)) max_mireds = int(entity.attributes.get(light.ATTR_MAX_MIREDS))
value = min(max_mireds, current + 50) value = min(max_mireds, current + 50)
yield from hass.services.async_call(entity.domain, SERVICE_TURN_ON, { await hass.services.async_call(entity.domain, SERVICE_TURN_ON, {
ATTR_ENTITY_ID: entity.entity_id, ATTR_ENTITY_ID: entity.entity_id,
light.ATTR_COLOR_TEMP: value, light.ATTR_COLOR_TEMP: value,
}, blocking=False) }, blocking=False, context=context)
return api_message(request) return api_message(request)
@ -978,31 +995,30 @@ def async_api_decrease_color_temp(hass, config, request, entity):
@HANDLERS.register( @HANDLERS.register(
('Alexa.ColorTemperatureController', 'IncreaseColorTemperature')) ('Alexa.ColorTemperatureController', 'IncreaseColorTemperature'))
@extract_entity @extract_entity
@asyncio.coroutine async def async_api_increase_color_temp(hass, config, request, context,
def async_api_increase_color_temp(hass, config, request, entity): entity):
"""Process an increase color temperature request.""" """Process an increase color temperature request."""
current = int(entity.attributes.get(light.ATTR_COLOR_TEMP)) current = int(entity.attributes.get(light.ATTR_COLOR_TEMP))
min_mireds = int(entity.attributes.get(light.ATTR_MIN_MIREDS)) min_mireds = int(entity.attributes.get(light.ATTR_MIN_MIREDS))
value = max(min_mireds, current - 50) value = max(min_mireds, current - 50)
yield from hass.services.async_call(entity.domain, SERVICE_TURN_ON, { await hass.services.async_call(entity.domain, SERVICE_TURN_ON, {
ATTR_ENTITY_ID: entity.entity_id, ATTR_ENTITY_ID: entity.entity_id,
light.ATTR_COLOR_TEMP: value, light.ATTR_COLOR_TEMP: value,
}, blocking=False) }, blocking=False, context=context)
return api_message(request) return api_message(request)
@HANDLERS.register(('Alexa.SceneController', 'Activate')) @HANDLERS.register(('Alexa.SceneController', 'Activate'))
@extract_entity @extract_entity
@asyncio.coroutine async def async_api_activate(hass, config, request, context, entity):
def async_api_activate(hass, config, request, entity):
"""Process an activate request.""" """Process an activate request."""
domain = entity.domain domain = entity.domain
yield from hass.services.async_call(domain, SERVICE_TURN_ON, { await hass.services.async_call(domain, SERVICE_TURN_ON, {
ATTR_ENTITY_ID: entity.entity_id ATTR_ENTITY_ID: entity.entity_id
}, blocking=False) }, blocking=False, context=context)
payload = { payload = {
'cause': {'type': _Cause.VOICE_INTERACTION}, 'cause': {'type': _Cause.VOICE_INTERACTION},
@ -1019,14 +1035,13 @@ def async_api_activate(hass, config, request, entity):
@HANDLERS.register(('Alexa.SceneController', 'Deactivate')) @HANDLERS.register(('Alexa.SceneController', 'Deactivate'))
@extract_entity @extract_entity
@asyncio.coroutine async def async_api_deactivate(hass, config, request, context, entity):
def async_api_deactivate(hass, config, request, entity):
"""Process a deactivate request.""" """Process a deactivate request."""
domain = entity.domain domain = entity.domain
yield from hass.services.async_call(domain, SERVICE_TURN_OFF, { await hass.services.async_call(domain, SERVICE_TURN_OFF, {
ATTR_ENTITY_ID: entity.entity_id ATTR_ENTITY_ID: entity.entity_id
}, blocking=False) }, blocking=False, context=context)
payload = { payload = {
'cause': {'type': _Cause.VOICE_INTERACTION}, 'cause': {'type': _Cause.VOICE_INTERACTION},
@ -1043,8 +1058,7 @@ def async_api_deactivate(hass, config, request, entity):
@HANDLERS.register(('Alexa.PercentageController', 'SetPercentage')) @HANDLERS.register(('Alexa.PercentageController', 'SetPercentage'))
@extract_entity @extract_entity
@asyncio.coroutine async def async_api_set_percentage(hass, config, request, context, entity):
def async_api_set_percentage(hass, config, request, entity):
"""Process a set percentage request.""" """Process a set percentage request."""
percentage = int(request[API_PAYLOAD]['percentage']) percentage = int(request[API_PAYLOAD]['percentage'])
service = None service = None
@ -1066,16 +1080,15 @@ def async_api_set_percentage(hass, config, request, entity):
service = SERVICE_SET_COVER_POSITION service = SERVICE_SET_COVER_POSITION
data[cover.ATTR_POSITION] = percentage data[cover.ATTR_POSITION] = percentage
yield from hass.services.async_call( await hass.services.async_call(
entity.domain, service, data, blocking=False) entity.domain, service, data, blocking=False, context=context)
return api_message(request) return api_message(request)
@HANDLERS.register(('Alexa.PercentageController', 'AdjustPercentage')) @HANDLERS.register(('Alexa.PercentageController', 'AdjustPercentage'))
@extract_entity @extract_entity
@asyncio.coroutine async def async_api_adjust_percentage(hass, config, request, context, entity):
def async_api_adjust_percentage(hass, config, request, entity):
"""Process an adjust percentage request.""" """Process an adjust percentage request."""
percentage_delta = int(request[API_PAYLOAD]['percentageDelta']) percentage_delta = int(request[API_PAYLOAD]['percentageDelta'])
service = None service = None
@ -1114,20 +1127,19 @@ def async_api_adjust_percentage(hass, config, request, entity):
data[cover.ATTR_POSITION] = max(0, percentage_delta + current) data[cover.ATTR_POSITION] = max(0, percentage_delta + current)
yield from hass.services.async_call( await hass.services.async_call(
entity.domain, service, data, blocking=False) entity.domain, service, data, blocking=False, context=context)
return api_message(request) return api_message(request)
@HANDLERS.register(('Alexa.LockController', 'Lock')) @HANDLERS.register(('Alexa.LockController', 'Lock'))
@extract_entity @extract_entity
@asyncio.coroutine async def async_api_lock(hass, config, request, context, entity):
def async_api_lock(hass, config, request, entity):
"""Process a lock request.""" """Process a lock request."""
yield from hass.services.async_call(entity.domain, SERVICE_LOCK, { await hass.services.async_call(entity.domain, SERVICE_LOCK, {
ATTR_ENTITY_ID: entity.entity_id ATTR_ENTITY_ID: entity.entity_id
}, blocking=False) }, blocking=False, context=context)
# Alexa expects a lockState in the response, we don't know the actual # Alexa expects a lockState in the response, we don't know the actual
# lockState at this point but assume it is locked. It is reported # lockState at this point but assume it is locked. It is reported
@ -1144,20 +1156,18 @@ def async_api_lock(hass, config, request, entity):
# Not supported by Alexa yet # Not supported by Alexa yet
@HANDLERS.register(('Alexa.LockController', 'Unlock')) @HANDLERS.register(('Alexa.LockController', 'Unlock'))
@extract_entity @extract_entity
@asyncio.coroutine async def async_api_unlock(hass, config, request, context, entity):
def async_api_unlock(hass, config, request, entity):
"""Process an unlock request.""" """Process an unlock request."""
yield from hass.services.async_call(entity.domain, SERVICE_UNLOCK, { await hass.services.async_call(entity.domain, SERVICE_UNLOCK, {
ATTR_ENTITY_ID: entity.entity_id ATTR_ENTITY_ID: entity.entity_id
}, blocking=False) }, blocking=False, context=context)
return api_message(request) return api_message(request)
@HANDLERS.register(('Alexa.Speaker', 'SetVolume')) @HANDLERS.register(('Alexa.Speaker', 'SetVolume'))
@extract_entity @extract_entity
@asyncio.coroutine async def async_api_set_volume(hass, config, request, context, entity):
def async_api_set_volume(hass, config, request, entity):
"""Process a set volume request.""" """Process a set volume request."""
volume = round(float(request[API_PAYLOAD]['volume'] / 100), 2) volume = round(float(request[API_PAYLOAD]['volume'] / 100), 2)
@ -1166,17 +1176,16 @@ def async_api_set_volume(hass, config, request, entity):
media_player.ATTR_MEDIA_VOLUME_LEVEL: volume, media_player.ATTR_MEDIA_VOLUME_LEVEL: volume,
} }
yield from hass.services.async_call( await hass.services.async_call(
entity.domain, SERVICE_VOLUME_SET, entity.domain, SERVICE_VOLUME_SET,
data, blocking=False) data, blocking=False, context=context)
return api_message(request) return api_message(request)
@HANDLERS.register(('Alexa.InputController', 'SelectInput')) @HANDLERS.register(('Alexa.InputController', 'SelectInput'))
@extract_entity @extract_entity
@asyncio.coroutine async def async_api_select_input(hass, config, request, context, entity):
def async_api_select_input(hass, config, request, entity):
"""Process a set input request.""" """Process a set input request."""
media_input = request[API_PAYLOAD]['input'] media_input = request[API_PAYLOAD]['input']
@ -1200,17 +1209,16 @@ def async_api_select_input(hass, config, request, entity):
media_player.ATTR_INPUT_SOURCE: media_input, media_player.ATTR_INPUT_SOURCE: media_input,
} }
yield from hass.services.async_call( await hass.services.async_call(
entity.domain, media_player.SERVICE_SELECT_SOURCE, entity.domain, media_player.SERVICE_SELECT_SOURCE,
data, blocking=False) data, blocking=False, context=context)
return api_message(request) return api_message(request)
@HANDLERS.register(('Alexa.Speaker', 'AdjustVolume')) @HANDLERS.register(('Alexa.Speaker', 'AdjustVolume'))
@extract_entity @extract_entity
@asyncio.coroutine async def async_api_adjust_volume(hass, config, request, context, entity):
def async_api_adjust_volume(hass, config, request, entity):
"""Process an adjust volume request.""" """Process an adjust volume request."""
volume_delta = int(request[API_PAYLOAD]['volume']) volume_delta = int(request[API_PAYLOAD]['volume'])
@ -1229,17 +1237,16 @@ def async_api_adjust_volume(hass, config, request, entity):
media_player.ATTR_MEDIA_VOLUME_LEVEL: volume, media_player.ATTR_MEDIA_VOLUME_LEVEL: volume,
} }
yield from hass.services.async_call( await hass.services.async_call(
entity.domain, media_player.SERVICE_VOLUME_SET, entity.domain, media_player.SERVICE_VOLUME_SET,
data, blocking=False) data, blocking=False, context=context)
return api_message(request) return api_message(request)
@HANDLERS.register(('Alexa.StepSpeaker', 'AdjustVolume')) @HANDLERS.register(('Alexa.StepSpeaker', 'AdjustVolume'))
@extract_entity @extract_entity
@asyncio.coroutine async def async_api_adjust_volume_step(hass, config, request, context, entity):
def async_api_adjust_volume_step(hass, config, request, entity):
"""Process an adjust volume step request.""" """Process an adjust volume step request."""
# media_player volume up/down service does not support specifying steps # media_player volume up/down service does not support specifying steps
# each component handles it differently e.g. via config. # each component handles it differently e.g. via config.
@ -1252,13 +1259,13 @@ def async_api_adjust_volume_step(hass, config, request, entity):
} }
if volume_step > 0: if volume_step > 0:
yield from hass.services.async_call( await hass.services.async_call(
entity.domain, media_player.SERVICE_VOLUME_UP, entity.domain, media_player.SERVICE_VOLUME_UP,
data, blocking=False) data, blocking=False, context=context)
elif volume_step < 0: elif volume_step < 0:
yield from hass.services.async_call( await hass.services.async_call(
entity.domain, media_player.SERVICE_VOLUME_DOWN, entity.domain, media_player.SERVICE_VOLUME_DOWN,
data, blocking=False) data, blocking=False, context=context)
return api_message(request) return api_message(request)
@ -1266,8 +1273,7 @@ def async_api_adjust_volume_step(hass, config, request, entity):
@HANDLERS.register(('Alexa.StepSpeaker', 'SetMute')) @HANDLERS.register(('Alexa.StepSpeaker', 'SetMute'))
@HANDLERS.register(('Alexa.Speaker', 'SetMute')) @HANDLERS.register(('Alexa.Speaker', 'SetMute'))
@extract_entity @extract_entity
@asyncio.coroutine async def async_api_set_mute(hass, config, request, context, entity):
def async_api_set_mute(hass, config, request, entity):
"""Process a set mute request.""" """Process a set mute request."""
mute = bool(request[API_PAYLOAD]['mute']) mute = bool(request[API_PAYLOAD]['mute'])
@ -1276,89 +1282,84 @@ def async_api_set_mute(hass, config, request, entity):
media_player.ATTR_MEDIA_VOLUME_MUTED: mute, media_player.ATTR_MEDIA_VOLUME_MUTED: mute,
} }
yield from hass.services.async_call( await hass.services.async_call(
entity.domain, media_player.SERVICE_VOLUME_MUTE, entity.domain, media_player.SERVICE_VOLUME_MUTE,
data, blocking=False) data, blocking=False, context=context)
return api_message(request) return api_message(request)
@HANDLERS.register(('Alexa.PlaybackController', 'Play')) @HANDLERS.register(('Alexa.PlaybackController', 'Play'))
@extract_entity @extract_entity
@asyncio.coroutine async def async_api_play(hass, config, request, context, entity):
def async_api_play(hass, config, request, entity):
"""Process a play request.""" """Process a play request."""
data = { data = {
ATTR_ENTITY_ID: entity.entity_id ATTR_ENTITY_ID: entity.entity_id
} }
yield from hass.services.async_call( await hass.services.async_call(
entity.domain, SERVICE_MEDIA_PLAY, entity.domain, SERVICE_MEDIA_PLAY,
data, blocking=False) data, blocking=False, context=context)
return api_message(request) return api_message(request)
@HANDLERS.register(('Alexa.PlaybackController', 'Pause')) @HANDLERS.register(('Alexa.PlaybackController', 'Pause'))
@extract_entity @extract_entity
@asyncio.coroutine async def async_api_pause(hass, config, request, context, entity):
def async_api_pause(hass, config, request, entity):
"""Process a pause request.""" """Process a pause request."""
data = { data = {
ATTR_ENTITY_ID: entity.entity_id ATTR_ENTITY_ID: entity.entity_id
} }
yield from hass.services.async_call( await hass.services.async_call(
entity.domain, SERVICE_MEDIA_PAUSE, entity.domain, SERVICE_MEDIA_PAUSE,
data, blocking=False) data, blocking=False, context=context)
return api_message(request) return api_message(request)
@HANDLERS.register(('Alexa.PlaybackController', 'Stop')) @HANDLERS.register(('Alexa.PlaybackController', 'Stop'))
@extract_entity @extract_entity
@asyncio.coroutine async def async_api_stop(hass, config, request, context, entity):
def async_api_stop(hass, config, request, entity):
"""Process a stop request.""" """Process a stop request."""
data = { data = {
ATTR_ENTITY_ID: entity.entity_id ATTR_ENTITY_ID: entity.entity_id
} }
yield from hass.services.async_call( await hass.services.async_call(
entity.domain, SERVICE_MEDIA_STOP, entity.domain, SERVICE_MEDIA_STOP,
data, blocking=False) data, blocking=False, context=context)
return api_message(request) return api_message(request)
@HANDLERS.register(('Alexa.PlaybackController', 'Next')) @HANDLERS.register(('Alexa.PlaybackController', 'Next'))
@extract_entity @extract_entity
@asyncio.coroutine async def async_api_next(hass, config, request, context, entity):
def async_api_next(hass, config, request, entity):
"""Process a next request.""" """Process a next request."""
data = { data = {
ATTR_ENTITY_ID: entity.entity_id ATTR_ENTITY_ID: entity.entity_id
} }
yield from hass.services.async_call( await hass.services.async_call(
entity.domain, SERVICE_MEDIA_NEXT_TRACK, entity.domain, SERVICE_MEDIA_NEXT_TRACK,
data, blocking=False) data, blocking=False, context=context)
return api_message(request) return api_message(request)
@HANDLERS.register(('Alexa.PlaybackController', 'Previous')) @HANDLERS.register(('Alexa.PlaybackController', 'Previous'))
@extract_entity @extract_entity
@asyncio.coroutine async def async_api_previous(hass, config, request, context, entity):
def async_api_previous(hass, config, request, entity):
"""Process a previous request.""" """Process a previous request."""
data = { data = {
ATTR_ENTITY_ID: entity.entity_id ATTR_ENTITY_ID: entity.entity_id
} }
yield from hass.services.async_call( await hass.services.async_call(
entity.domain, SERVICE_MEDIA_PREVIOUS_TRACK, entity.domain, SERVICE_MEDIA_PREVIOUS_TRACK,
data, blocking=False) data, blocking=False, context=context)
return api_message(request) return api_message(request)
@ -1405,7 +1406,7 @@ def temperature_from_object(temp_obj, to_unit, interval=False):
@HANDLERS.register(('Alexa.ThermostatController', 'SetTargetTemperature')) @HANDLERS.register(('Alexa.ThermostatController', 'SetTargetTemperature'))
@extract_entity @extract_entity
async def async_api_set_target_temp(hass, config, request, entity): async def async_api_set_target_temp(hass, config, request, context, entity):
"""Process a set target temperature request.""" """Process a set target temperature request."""
unit = entity.attributes[CONF_UNIT_OF_MEASUREMENT] unit = entity.attributes[CONF_UNIT_OF_MEASUREMENT]
min_temp = entity.attributes.get(climate.ATTR_MIN_TEMP) min_temp = entity.attributes.get(climate.ATTR_MIN_TEMP)
@ -1439,14 +1440,15 @@ async def async_api_set_target_temp(hass, config, request, entity):
data[climate.ATTR_TARGET_TEMP_HIGH] = temp_high data[climate.ATTR_TARGET_TEMP_HIGH] = temp_high
await hass.services.async_call( await hass.services.async_call(
entity.domain, climate.SERVICE_SET_TEMPERATURE, data, blocking=False) entity.domain, climate.SERVICE_SET_TEMPERATURE, data, blocking=False,
context=context)
return api_message(request) return api_message(request)
@HANDLERS.register(('Alexa.ThermostatController', 'AdjustTargetTemperature')) @HANDLERS.register(('Alexa.ThermostatController', 'AdjustTargetTemperature'))
@extract_entity @extract_entity
async def async_api_adjust_target_temp(hass, config, request, entity): async def async_api_adjust_target_temp(hass, config, request, context, entity):
"""Process an adjust target temperature request.""" """Process an adjust target temperature request."""
unit = entity.attributes[CONF_UNIT_OF_MEASUREMENT] unit = entity.attributes[CONF_UNIT_OF_MEASUREMENT]
min_temp = entity.attributes.get(climate.ATTR_MIN_TEMP) min_temp = entity.attributes.get(climate.ATTR_MIN_TEMP)
@ -1466,14 +1468,16 @@ async def async_api_adjust_target_temp(hass, config, request, entity):
} }
await hass.services.async_call( await hass.services.async_call(
entity.domain, climate.SERVICE_SET_TEMPERATURE, data, blocking=False) entity.domain, climate.SERVICE_SET_TEMPERATURE, data, blocking=False,
context=context)
return api_message(request) return api_message(request)
@HANDLERS.register(('Alexa.ThermostatController', 'SetThermostatMode')) @HANDLERS.register(('Alexa.ThermostatController', 'SetThermostatMode'))
@extract_entity @extract_entity
async def async_api_set_thermostat_mode(hass, config, request, entity): async def async_api_set_thermostat_mode(hass, config, request, context,
entity):
"""Process a set thermostat mode request.""" """Process a set thermostat mode request."""
mode = request[API_PAYLOAD]['thermostatMode'] mode = request[API_PAYLOAD]['thermostatMode']
mode = mode if isinstance(mode, str) else mode['value'] mode = mode if isinstance(mode, str) else mode['value']
@ -1499,15 +1503,14 @@ async def async_api_set_thermostat_mode(hass, config, request, entity):
await hass.services.async_call( await hass.services.async_call(
entity.domain, climate.SERVICE_SET_OPERATION_MODE, data, entity.domain, climate.SERVICE_SET_OPERATION_MODE, data,
blocking=False) blocking=False, context=context)
return api_message(request) return api_message(request)
@HANDLERS.register(('Alexa', 'ReportState')) @HANDLERS.register(('Alexa', 'ReportState'))
@extract_entity @extract_entity
@asyncio.coroutine async def async_api_reportstate(hass, config, request, context, entity):
def async_api_reportstate(hass, config, request, entity):
"""Process a ReportState request.""" """Process a ReportState request."""
alexa_entity = ENTITY_ADAPTERS[entity.domain](config, entity) alexa_entity = ENTITY_ADAPTERS[entity.domain](config, entity)
properties = [] properties = []

View File

@ -5,6 +5,7 @@ from uuid import uuid4
import pytest import pytest
from homeassistant.core import Context, callback
from homeassistant.const import ( from homeassistant.const import (
TEMP_FAHRENHEIT, STATE_LOCKED, STATE_UNLOCKED, TEMP_FAHRENHEIT, STATE_LOCKED, STATE_UNLOCKED,
STATE_UNKNOWN) STATE_UNKNOWN)
@ -18,6 +19,17 @@ from tests.common import async_mock_service
DEFAULT_CONFIG = smart_home.Config(should_expose=lambda entity_id: True) DEFAULT_CONFIG = smart_home.Config(should_expose=lambda entity_id: True)
@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
def get_new_request(namespace, name, endpoint=None): def get_new_request(namespace, name, endpoint=None):
"""Generate a new API message.""" """Generate a new API message."""
raw_msg = { raw_msg = {
@ -145,7 +157,7 @@ def assert_endpoint_capabilities(endpoint, *interfaces):
@asyncio.coroutine @asyncio.coroutine
def test_switch(hass): def test_switch(hass, events):
"""Test switch discovery.""" """Test switch discovery."""
device = ('switch.test', 'on', {'friendly_name': "Test switch"}) device = ('switch.test', 'on', {'friendly_name': "Test switch"})
appliance = yield from discovery_test(device, hass) appliance = yield from discovery_test(device, hass)
@ -963,23 +975,26 @@ def assert_request_calls_service(
response_type='Response', response_type='Response',
payload=None): payload=None):
"""Assert an API request calls a hass service.""" """Assert an API request calls a hass service."""
context = Context()
request = get_new_request(namespace, name, endpoint) request = get_new_request(namespace, name, endpoint)
if payload: if payload:
request['directive']['payload'] = payload request['directive']['payload'] = payload
domain, service_name = service.split('.') domain, service_name = service.split('.')
call = async_mock_service(hass, domain, service_name) calls = async_mock_service(hass, domain, service_name)
msg = yield from smart_home.async_handle_message( msg = yield from smart_home.async_handle_message(
hass, DEFAULT_CONFIG, request) hass, DEFAULT_CONFIG, request, context)
yield from hass.async_block_till_done() yield from hass.async_block_till_done()
assert len(call) == 1 assert len(calls) == 1
call = calls[0]
assert 'event' in msg assert 'event' in msg
assert call[0].data['entity_id'] == endpoint.replace('#', '.') assert call.data['entity_id'] == endpoint.replace('#', '.')
assert msg['event']['header']['name'] == response_type assert msg['event']['header']['name'] == response_type
assert call.context == context
return call[0], msg return call, msg
@asyncio.coroutine @asyncio.coroutine
@ -1372,3 +1387,53 @@ def test_api_select_input(hass, domain, payload, source_list, idx):
hass, hass,
payload={'input': payload}) payload={'input': payload})
assert call.data['source'] == source_list[idx] assert call.data['source'] == source_list[idx]
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