Skip to content

Instantly share code, notes, and snippets.

@awarecan
Last active March 14, 2024 19:26
Show Gist options
  • Select an option

  • Save awarecan/630510a9742f5f8901b5ab284c25e912 to your computer and use it in GitHub Desktop.

Select an option

Save awarecan/630510a9742f5f8901b5ab284c25e912 to your computer and use it in GitHub Desktop.
Alexa Smart Home Skill Adapter for Home Assistant
"""
Copyright 2019 Jason Hu <awaregit at gmail.com>
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
"""
import os
import json
import logging
import urllib3
_debug = bool(os.environ.get('DEBUG'))
_logger = logging.getLogger('HomeAssistant-SmartHome')
_logger.setLevel(logging.DEBUG if _debug else logging.INFO)
def lambda_handler(event, context):
"""Handle incoming Alexa directive."""
_logger.debug('Event: %s', event)
base_url = os.environ.get('BASE_URL')
assert base_url is not None, 'Please set BASE_URL environment variable'
directive = event.get('directive')
assert directive is not None, 'Malformatted request - missing directive'
assert directive.get('header', {}).get('payloadVersion') == '3', \
'Only support payloadVersion == 3'
scope = directive.get('endpoint', {}).get('scope')
if scope is None:
# token is in payload.scope for Discovery directive
scope = directive.get('payload', {}).get('scope')
if scope is None:
# token is in payload.grantee for AcceptGrant directive
scope = directive.get('payload', {}).get('grantee')
assert scope is not None, 'Malformatted request - missing endpoint.scope'
assert scope.get('type') == 'BearerToken', 'Only support BearerToken'
token = scope.get('token')
if token is None and _debug:
token = os.environ.get('LONG_LIVED_ACCESS_TOKEN') # only for debug purpose
verify_ssl = not bool(os.environ.get('NOT_VERIFY_SSL'))
http = urllib3.PoolManager(
cert_reqs='CERT_REQUIRED' if verify_ssl else 'CERT_NONE',
timeout=urllib3.Timeout(connect=2.0, read=10.0)
)
response = http.request(
'POST',
'{}/api/alexa/smart_home'.format(base_url),
headers={
'Authorization': 'Bearer {}'.format(token),
'Content-Type': 'application/json',
},
body=json.dumps(event).encode('utf-8'),
)
if response.status >= 400:
return {
'event': {
'payload': {
'type': 'INVALID_AUTHORIZATION_CREDENTIAL'
if response.status in (401, 403) else 'INTERNAL_ERROR',
'message': response.data.decode("utf-8"),
}
}
}
return json.loads(response.data.decode('utf-8'))
@ronmichel
Copy link

Found something: see here.

In addition, if you enable the Send Alexa Events permission for the skill, your Lambda function must handle the AcceptGrant directive. If your skill does not handle this directive, account linking fails when the user attempts to enable your skill. See Authenticate a Customer to Alexa with Permissions

@ronmichel
Copy link

@awarecan: Can you update the lambda function to handle this directive? Thanks!

@awarecan
Copy link
Author

Updated per community suggestion - have not tested by myself yet.

@markbooth73
Copy link

Hi,

Tried this several times with different combinations of credentials.

My Log from HA.

Log Details (ERROR)
Logger: aiohttp.server
Source: components/alexa/capabilities.py:1478
First occurred: 13:52:58 (2 occurrences)
Last logged: 13:53:17

Error handling request
Traceback (most recent call last):
  File "/usr/local/lib/python3.7/site-packages/aiohttp/web_protocol.py", line 418, in start
    resp = await task
  File "/usr/local/lib/python3.7/site-packages/aiohttp/web_app.py", line 458, in _handle
    resp = await handler(request)
  File "/usr/local/lib/python3.7/site-packages/aiohttp/web_middlewares.py", line 119, in impl
    return await handler(request)
  File "/usr/src/homeassistant/homeassistant/components/http/real_ip.py", line 39, in real_ip_middleware
    return await handler(request)
  File "/usr/src/homeassistant/homeassistant/components/http/ban.py", line 73, in ban_middleware
    return await handler(request)
  File "/usr/src/homeassistant/homeassistant/components/http/auth.py", line 127, in auth_middleware
    return await handler(request)
  File "/usr/src/homeassistant/homeassistant/components/http/view.py", line 125, in handle
    result = await result
  File "/usr/src/homeassistant/homeassistant/components/alexa/smart_home_http.py", line 120, in post
    hass, self.smart_home_config, message, context=core.Context(user_id=user.id)
  File "/usr/src/homeassistant/homeassistant/components/alexa/smart_home.py", line 39, in async_handle_message
    response = await funct_ref(hass, config, directive, context)
  File "/usr/src/homeassistant/homeassistant/components/alexa/handlers.py", line 84, in async_api_discovery
    for alexa_entity in async_get_entities(hass, config)
  File "/usr/src/homeassistant/homeassistant/components/alexa/handlers.py", line 85, in <listcomp>
    if config.should_expose(alexa_entity.entity_id)
  File "/usr/src/homeassistant/homeassistant/components/alexa/entities.py", line 285, in serialize_discovery
    for i in self.interfaces()
  File "/usr/src/homeassistant/homeassistant/components/alexa/entities.py", line 286, in <listcomp>
    if locale in i.supported_locales
  File "/usr/src/homeassistant/homeassistant/components/alexa/capabilities.py", line 205, in serialize_discovery
    capability_resources = self.capability_resources()
  File "/usr/src/homeassistant/homeassistant/components/alexa/capabilities.py", line 1478, in capability_resources
    min_value = float(self.entity.attributes[input_number.ATTR_MIN])
KeyError: 'min'

And my output from Lambda

{
  "event": {
    "payload": {
      "type": "INTERNAL_ERROR",
      "message": "500 Internal Server Error\n\nServer got itself in trouble"
    }
  }
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment