Alexa Connected Devices > Development Resources > Smart Home Skill Tutorial > Step 2: Add an AWS Lambda Function
You’ll notice a required field of “Default endpoint”, which you will create now. The backend code for your smart home skill is hosted as a Lambda function on AWS, so you will need an AWS account to complete this section. If you do not have an AWS account, you can create one here. AWS Lambda is a service that lets you run code in the cloud without managing servers. Alexa sends your skill requests and your code inspects the request, takes any necessary actions such as communicating with the device cloud for that customer, and then sends back a response.
This tutorial builds resources in the AWS region of “N. Virginia” (us-east-1). The region is displayed in the upper right corner as shown above. Typically, you will build resources in a region closest to the majority of your users. For this tutorial, please build your resource(s) in the “N. Virginia” region.
1. Navigate to the IAM Management Console policies at https://console.aws.amazon.com/iam/home?region=us-east-1#/policies.
2. Click the Create policy button.
3. Select the JSON tab and then copy & paste the following policy into the text area:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"logs:CreateLogStream",
"dynamodb:UpdateItem",
"logs:CreateLogGroup",
"logs:PutLogEvents"
],
"Resource": "*"
}
]
}
4. Click the Review policy button.
5. In the Review policy section, set the Name of the policy to “smart_home_skill”. You can leave the Description blank.
6. Click the Create policy button to create the policy.
1. Navigate to the IAM Management Console roles at https://us-east-1.console.aws.amazon.com/iam/home?region=us-east-1#/roles.
2. Click the Create role button.
3. On the Create role page, select “AWS Service” for the type of trusted entity and then select Lambda from the list of use cases.
4. Click the Next: Permissions button.
5. Under Attach permissions policies, filter and find the previously created “smart_home_skill“ policy and select its check box.
6. Click the Next: Tags button. You may leave the tags blank.
7. Click the Next: Review button.
8. In the Review section, set the Role name to “lambda_smart_home_skill”
9. Click Create role to create the execution role.
1. Navigate to the AWS Lambda Console at https://console.aws.amazon.com/lambda/home?region=us-east-1#/functions
2. Click Create function button
3. Select Author from scratch, and enter the following information:
4. Under *Basic Information,* enter the following information:
5. Role: Under Permissions, click the “Choose or create an execution role” and select Use an existing role as the Execution role and choose the “lambda_smart_home_skill” role you created previously.
6. Click Create function. Your function should be created and you will move to Configuration.
7. On the Configuration designer tab, under click Add Trigger
8. Under Trigger configuration, type alexa to filter out the triggers, and then choose Alexa Smart Home
9. Application ID: Paste the Skill ID we copied while creating the Smart Home Skill in the Amazon Developer Console into the Application ID box.
10. Leave Enable trigger checked. This enables the Amazon Alexa service to call your Lambda function. If you don't enable it when you create, you will not be able to enable it in the console later.
11. Click Add.
12. Under Configuration → Designer, click on the name of your Lambda function to display the function code.
13. In the Function code section make sure Edit code inline is selected. Leave the Runtime and Handler set to their defaults.
14. Paste in the following code, completely replacing the code in lambda_function.py
# -*- coding: utf-8 -*-
# Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved.
#
# Licensed under the Amazon Software License (the "License"). You may not use this file except in
# compliance with the License. A copy of the License is located at
#
# http://aws.amazon.com/asl/
#
# or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific
# language governing permissions and limitations under the License.
import boto3
import json
import random
import uuid
import time
aws_dynamodb = boto3.client('dynamodb')
def lambda_handler(request, context):
# Dump the request for logging - check the CloudWatch logs
print('lambda_handler request -----')
print(json.dumps(request))
if context is not None:
print('lambda_handler context -----')
print(context)
# Validate we have an Alexa directive
if 'directive' not in request:
aer = AlexaResponse(
name='ErrorResponse',
payload={'type': 'INVALID_DIRECTIVE',
'message': 'Missing key: directive, Is the request a valid Alexa Directive?'})
return send_response(aer.get())
# Check the payload version
payload_version = request['directive']['header']['payloadVersion']
if payload_version != '3':
aer = AlexaResponse(
name='ErrorResponse',
payload={'type': 'INTERNAL_ERROR',
'message': 'This skill only supports Smart Home API version 3'})
return send_response(aer.get())
# Crack open the request and see what is being requested
name = request['directive']['header']['name']
namespace = request['directive']['header']['namespace']
# Handle the incoming request from Alexa based on the namespace
if namespace == 'Alexa.Authorization':
if name == 'AcceptGrant':
# Note: This sample accepts any grant request
# In your implementation you would use the code and token to get and store access tokens
grant_code = request['directive']['payload']['grant']['code']
grantee_token = request['directive']['payload']['grantee']['token']
aar = AlexaResponse(namespace='Alexa.Authorization', name='AcceptGrant.Response')
return send_response(aar.get())
if namespace == 'Alexa.Discovery':
if name == 'Discover':
adr = AlexaResponse(namespace='Alexa.Discovery', name='Discover.Response')
capability_alexa = adr.create_payload_endpoint_capability()
capability_alexa_powercontroller = adr.create_payload_endpoint_capability(
interface='Alexa.PowerController',
supported=[{'name': 'powerState'}])
adr.add_payload_endpoint(
friendly_name='Sample Switch',
endpoint_id='sample-switch-01',
capabilities=[capability_alexa, capability_alexa_powercontroller])
return send_response(adr.get())
if namespace == 'Alexa.PowerController':
# Note: This sample always returns a success response for either a request to TurnOff or TurnOn
endpoint_id = request['directive']['endpoint']['endpointId']
power_state_value = 'OFF' if name == 'TurnOff' else 'ON'
correlation_token = request['directive']['header']['correlationToken']
# Check for an error when setting the state
state_set = set_device_state(endpoint_id=endpoint_id, state='powerState', value=power_state_value)
if not state_set:
return AlexaResponse(
name='ErrorResponse',
payload={'type': 'ENDPOINT_UNREACHABLE', 'message': 'Unable to reach endpoint database.'}).get()
apcr = AlexaResponse(correlation_token=correlation_token)
apcr.add_context_property(namespace='Alexa.PowerController', name='powerState', value=power_state_value)
return send_response(apcr.get())
def send_response(response):
print('lambda_handler response -----')
print(json.dumps(response))
return response
def set_device_state(endpoint_id, state, value):
attribute_key = state + 'Value'
response = aws_dynamodb.update_item(
TableName='SampleSmartHome',
Key={'ItemId': {'S': endpoint_id}},
AttributeUpdates={attribute_key: {'Action': 'PUT', 'Value': {'S': value}}})
print(response)
if response['ResponseMetadata']['HTTPStatusCode'] == 200:
return True
else:
return False
def get_utc_timestamp(seconds=None):
return time.strftime('%Y-%m-%dT%H:%M:%S.00Z', time.gmtime(seconds))
class AlexaResponse:
def __init__(self, **kwargs):
self.context_properties = []
self.payload_endpoints = []
# Set up the response structure
self.context = {}
self.event = {
'header': {
'namespace': kwargs.get('namespace', 'Alexa'),
'name': kwargs.get('name', 'Response'),
'messageId': str(uuid.uuid4()),
'payloadVersion': kwargs.get('payload_version', '3')
# 'correlation_token': kwargs.get('correlation_token', 'INVALID')
},
'endpoint': {
"scope": {
"type": "BearerToken",
"token": kwargs.get('token', 'INVALID')
},
"endpointId": kwargs.get('endpoint_id', 'INVALID')
},
'payload': kwargs.get('payload', {})
}
if 'correlation_token' in kwargs:
self.event['header']['correlation_token'] = kwargs.get('correlation_token', 'INVALID')
if 'cookie' in kwargs:
self.event['endpoint']['cookie'] = kwargs.get('cookie', '{}')
# No endpoint in an AcceptGrant or Discover request
if self.event['header']['name'] == 'AcceptGrant.Response' or self.event['header']['name'] == 'Discover.Response':
self.event.pop('endpoint')
def add_context_property(self, **kwargs):
self.context_properties.append(self.create_context_property(**kwargs))
def add_cookie(self, key, value):
if "cookies" in self is None:
self.cookies = {}
self.cookies[key] = value
def add_payload_endpoint(self, **kwargs):
self.payload_endpoints.append(self.create_payload_endpoint(**kwargs))
def create_context_property(self, **kwargs):
return {
'namespace': kwargs.get('namespace', 'Alexa.EndpointHealth'),
'name': kwargs.get('name', 'connectivity'),
'value': kwargs.get('value', {'value': 'OK'}),
'timeOfSample': get_utc_timestamp(),
'uncertaintyInMilliseconds': kwargs.get('uncertainty_in_milliseconds', 0)
}
def create_payload_endpoint(self, **kwargs):
# Return the proper structure expected for the endpoint
endpoint = {
'capabilities': kwargs.get('capabilities', []),
'description': kwargs.get('description', 'Sample Endpoint Description'),
'displayCategories': kwargs.get('display_categories', ['OTHER']),
'endpointId': kwargs.get('endpoint_id', 'endpoint_' + "%0.6d" % random.randint(0, 999999)),
'friendlyName': kwargs.get('friendly_name', 'Sample Endpoint'),
'manufacturerName': kwargs.get('manufacturer_name', 'Sample Manufacturer')
}
if 'cookie' in kwargs:
endpoint['cookie'] = kwargs.get('cookie', {})
return endpoint
def create_payload_endpoint_capability(self, **kwargs):
capability = {
'type': kwargs.get('type', 'AlexaInterface'),
'interface': kwargs.get('interface', 'Alexa'),
'version': kwargs.get('version', '3')
}
supported = kwargs.get('supported', None)
if supported:
capability['properties'] = {}
capability['properties']['supported'] = supported
capability['properties']['proactivelyReported'] = kwargs.get('proactively_reported', False)
capability['properties']['retrievable'] = kwargs.get('retrievable', False)
return capability
def get(self, remove_empty=True):
response = {
'context': self.context,
'event': self.event
}
if len(self.context_properties) > 0:
response['context']['properties'] = self.context_properties
if len(self.payload_endpoints) > 0:
response['event']['payload']['endpoints'] = self.payload_endpoints
if remove_empty:
if len(response['context']) < 1:
response.pop('context')
return response
def set_payload(self, payload):
self.event['payload'] = payload
def set_payload_endpoint(self, payload_endpoints):
self.payload_endpoints = payload_endpoints
def set_payload_endpoints(self, payload_endpoints):
if 'endpoints' not in self.event['payload']:
self.event['payload']['endpoints'] = []
self.event['payload']['endpoints'] = payload_endpoints
15. Click Save
1. Now we are going to test your Lambda function with a discovery directive
2. Click on Test. The Configure test event page appears.
3. Leave Create new test event selected.
4. For Event template, leave the default Hello World template.
5. In the Event name, enter DiscoveryTest and replace the entire contents in the editor with the following test input code
{
"directive": {
"header": {
"namespace": "Alexa.Discovery",
"name": "Discover",
"payloadVersion": "3",
"messageId": "1bd5d003-31b9-476f-ad03-71d471922820"
},
"payload": {
"scope": {
"type": "BearerToken",
"token": "access-token-from-skill"
}
}
}
}
6. Click Create
7. Click Test with DiscoveryTest selected.
8. If successful, you should get a message confirming that the test succeeded,
with the Execution Result similar to the following:
{
"event": {
"header": {
"namespace": "Alexa.Discovery",
"name": "Discover.Response",
"messageId": "b5a1d155-3a97-479e-80fa-913b4afee758",
"payloadVersion": "3"
},
"payload": {
"endpoints": [
{
"capabilities": [
{
"type": "AlexaInterface",
"interface": "Alexa",
"version": "3"
},
{
"type": "AlexaInterface",
"interface": "Alexa.PowerController",
"version": "3",
"properties": {
"supported": [
{
"name": "powerState"
}
],
"proactivelyReported": false,
"retrievable": false
}
}
],
"description": "Sample Endpoint Description",
"displayCategories": [
"OTHER"
],
"endpointId": "sample-switch-01",
"friendlyName": "Sample Switch",
"manufacturerName": "Sample Manufacturer"
}
]
}
}
}