In the previous step, you created a new smart home skill in the Alexa developer console. Now you implement your smart home skill code as an Amazon Web Services (AWS) Lambda function. This function consists of your skill code, configuration information, and an Identity and Access Management (IAM) role that allows Lambda to run the skill code on your behalf. Optionally, Lambda functions can store data in AWS DynamoDB and write logs to AWS CloudWatch.
You can write your code in the AWS Lambda console directly or in a development environment appropriate for the programming language that you plan to use to code your skill. You can author a Lambda function in Node.js, Java, Python, C#, Go, Ruby, or PowerShell. For simplicity, this tutorial step uses the AWS Lambda console code editor and provides code examples in Python and Node.js.
On the IAM Identity Center navigation pane, under Access management, choose Roles.
On the IAM > Roles page, choose Create role.
On the IAM > Roles > Create role page, enter the following information:
For Trusted entity type, select AWS service.
Under Use cases, select Lambda, and then choose Next.
Under Add permissions, use the filter to find the policy called AWSLambdaBasicExecutionRole.
Select the check box next to the AWSLambdaBasicExecutionRole policy and AWS managed type, and then choose Next.
Under Name, review, and create page, for Role name, enter lambda_smart_home_skill_role, and then, at the bottom of the page, choose Create role.
Now, you can see the lambda_smart_home_skill_role in the list of available roles.
Substep 2.2: Create a Lambda function
In this substep, you create a Lambda function and add the IAM role that allows Lambda to run your code on your behalf. Then, you add the example skill code.
To create an AWS Lambda function in the AWS developer console
Navigate to the Lambda console.
If you've created Lambda functions, the console displays a list of your functions.
On the Functions page, choose Create function.
Note: In the upper-right corner, the console displays the AWS region where the Lambda function resides. This tutorial uses the North American region, AWS N. Virginia (us-east-1) region, and English (US). If you want to run your skill outside of North America, select a different AWS region and language. To learn more, see AWS Lambda regions and Deploy your Lambda function to multiple regions.
Select Author from scratch.
In the Basic information pane, enter the following information:
For Function name, enter my-smart-home-skill.
For Runtime, choose a version of Python or Node.js.
For Architecture, leave the default.
For Permissions > Change default execution role, select Use an existing role.
For Existing role, select lambda_smart_home_skill_role, and then choose Create function.
On the Lambda > Functions > my-smart-home-skill page, under Function overview, choose + Add trigger.
On the Add trigger page, enter the following information to trigger your skill:
For Trigger configuration, select Alexa.
For Choose an Alexa product, select Alexa Smart Home.
To add the trigger to invoke your skill, choose Add.
Substep 2.3: Add skill code
Now, you add the example skill code to your Lambda function. The example code contains a discovery response to model the light bulb and an Alexa response to control the light bulb. On receipt of a directive from Alexa, the Lambda function executes your skill code.
The discovery response identifies the light bulb endpoint and includes as much identifying information about the endpoint as possible, such as manufacturer, model, and serial number. In addition, the discovery response identifies the capabilities of the light bulb. In this example, the response includes the Alexa.PowerController interface to indicate that the customer can turn the light bulb on and off. For more details about discovery, see About Alexa Discovery.
After the customer asks Alexa to turn the light bulb on, Alexa sends a TurnOn directive to the skill. In response, the skill calls the appropriate interface in the device cloud to turn on the bulb, and then responds to Alexa with the current state of the light bulb endpoint.
A fully functional smart home skill might include a combination of capability interfaces that best represent the features of the endpoint device. And, the skill usually supports state and change reporting to keep Alexa up-to-date on the status of the device. For available Smart Home interfaces that you can use to model your device type, see List of Alexa Interfaces and Supported Languages.
Add the skill code to your Lambda function
On the Lambda > Functions > my-smart-home-skill page, to view the code editor, select the Code tab.
Choose the language for your skill code. For Node.js, your skill code resides in the file, index.js or index.mjs. For the tutorial, rename index.mjs to index.js.
For Python, your skill code resides in the file, lambda_function.py.
Open index.js (Node.js) or lambda_function.py (Python).
In the Code source pane, paste the Node.js or Python code into the code editor, replacing any existing code.
The example smart home skill code supports the AcceptGrant, Discovery, TurnOn, and TurnOff directives for the virtual light bulb.
// -*- coding: utf-8 -*-// Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved.//// SPDX-License-Identifier: LicenseRef-.amazon.com.-AmznSL-1.0// 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///// 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.exports.handler=function(request,context){if(request.directive.header.namespace==='Alexa.Discovery'&&request.directive.header.name==='Discover'){log("DEBUG:","Discover request",JSON.stringify(request));returnhandleDiscovery(request,context,"");}elseif(request.directive.header.namespace==='Alexa.PowerController'){if(request.directive.header.name==='TurnOn'||request.directive.header.name==='TurnOff'){log("DEBUG:","TurnOn or TurnOff Request",JSON.stringify(request));returnhandlePowerControl(request,context);}}elseif(request.directive.header.namespace==='Alexa.Authorization'&&request.directive.header.name==='AcceptGrant'){returnhandleAuthorization(request,context)}functionhandleAuthorization(request,context){// Send the AcceptGrant responsevarpayload={};varheader=request.directive.header;header.name="AcceptGrant.Response";log("DEBUG","AcceptGrant Response: ",JSON.stringify({header:header,payload:payload}));return{event:{header:header,payload:payload}};}functionhandleDiscovery(request,context){// Send the discovery responsevarpayload={"endpoints":[{"endpointId":"sample-bulb-01","manufacturerName":"Smart Device Company","friendlyName":"Livingroom lamp","description":"Virtual smart light bulb","displayCategories":["LIGHT"],"additionalAttributes":{"manufacturer":"Sample Manufacturer","model":"Sample Model","serialNumber":"U11112233456","firmwareVersion":"1.24.2546","softwareVersion":"1.036","customIdentifier":"Sample custom ID"},"cookie":{"key1":"arbitrary key/value pairs for skill to reference this endpoint.","key2":"There can be multiple entries","key3":"but they should only be used for reference purposes.","key4":"This is not a suitable place to maintain current endpoint state."},"capabilities":[{"interface":"Alexa.PowerController","version":"3","type":"AlexaInterface","properties":{"supported":[{"name":"powerState"}],"retrievable":true}},{"type":"AlexaInterface","interface":"Alexa.EndpointHealth","version":"3.2","properties":{"supported":[{"name":"connectivity"}],"retrievable":true}},{"type":"AlexaInterface","interface":"Alexa","version":"3"}]}]};varheader=request.directive.header;header.name="Discover.Response";log("DEBUG","Discovery Response: ",JSON.stringify({header:header,payload:payload}));return{event:{header:header,payload:payload}};}functionlog(message,message1,message2){console.log(message+message1+message2);}functionhandlePowerControl(request,context){// get device ID passed in during discoveryvarrequestMethod=request.directive.header.name;varresponseHeader=request.directive.header;responseHeader.namespace="Alexa";responseHeader.name="Response";responseHeader.messageId=responseHeader.messageId+"-R";// get user token pass in requestvarrequestToken=request.directive.endpoint.scope.token;varpowerResult;if(requestMethod==="TurnOn"){// Make the call to your device cloud for control// powerResult = stubControlFunctionToYourCloud(endpointId, token, request);powerResult="ON";}elseif(requestMethod==="TurnOff"){// Make the call to your device cloud for control and check for success// powerResult = stubControlFunctionToYourCloud(endpointId, token, request);powerResult="OFF";}// Return the updated powerState. Always include EndpointHealth in your Alexa.Response// Datetime format for timeOfSample is ISO 8601, `YYYY-MM-DDThh:mm:ssZ`.varcontextResult={"properties":[{"namespace":"Alexa.PowerController","name":"powerState","value":powerResult,"timeOfSample":"2022-09-03T16:20:50.52Z",//retrieve from result."uncertaintyInMilliseconds":50},{"namespace":"Alexa.EndpointHealth","name":"connectivity","value":{"value":"OK"},"timeOfSample":"2022-09-03T22:43:17.877738+00:00","uncertaintyInMilliseconds":0}]};varresponse={context:contextResult,event:{header:responseHeader,endpoint:{scope:{type:"BearerToken",token:requestToken},endpointId:"sample-bulb-01"},payload:{}}};log("DEBUG","Alexa.PowerController ",JSON.stringify(response));returnresponse;}};
Copied to clipboard.
# -*- coding: utf-8 -*-# Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved.## SPDX-License-Identifier: LicenseRef-.amazon.com.-AmznSL-1.0# 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/## 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.importjsonimportmathimportrandomimportuuidimportloggingimportdatetimefromdatetimeimportdatetime,timezonelogger=logging.getLogger(__name__)logger.setLevel(logging.INFO)deflambda_handler(request,context):# Dump the request for logging - check the CloudWatch logs.print('lambda_handler request -----')print(json.dumps(request))ifcontextisnotNone:print('lambda_handler context -----')print(context)# Validate the request is an Alexa smart home directive.if'directive'notinrequest:alexa_response=AlexaResponse(name='ErrorResponse',payload={'type':'INVALID_DIRECTIVE','message':'Missing key: directive, Is the request a valid Alexa Directive?'})returnsend_response(alexa_response.get())# Check the payload version.payload_version=request['directive']['header']['payloadVersion']ifpayload_version!='3':alexa_response=AlexaResponse(name='ErrorResponse',payload={'type':'INTERNAL_ERROR','message':'This skill only supports Smart Home API version 3'})returnsend_response(alexa_response.get())# Crack open the request to see the request.name=request['directive']['header']['name']namespace=request['directive']['header']['namespace']# Handle the incoming request from Alexa based on the namespace.ifnamespace=='Alexa.Authorization':ifname=='AcceptGrant':# Note: This example code accepts any grant request.# In your implementation, invoke Login With Amazon with the grant code to get access and refresh tokens.grant_code=request['directive']['payload']['grant']['code']grantee_token=request['directive']['payload']['grantee']['token']auth_response=AlexaResponse(namespace='Alexa.Authorization',name='AcceptGrant.Response')returnsend_response(auth_response.get())ifnamespace=='Alexa.Discovery':ifname=='Discover':# The request to discover the devices the skill controls.discovery_response=AlexaResponse(namespace='Alexa.Discovery',name='Discover.Response')# Create the response and add the light bulb capabilities.capability_alexa=discovery_response.create_payload_endpoint_capability()capability_alexa_powercontroller=discovery_response.create_payload_endpoint_capability(interface='Alexa.PowerController',supported=[{'name':'powerState'}])capability_alexa_endpointhealth=discovery_response.create_payload_endpoint_capability(interface='Alexa.EndpointHealth',supported=[{'name':'connectivity'}])discovery_response.add_payload_endpoint(friendly_name='Sample Light Bulb',endpoint_id='sample-bulb-01',capabilities=[capability_alexa,capability_alexa_endpointhealth,capability_alexa_powercontroller])returnsend_response(discovery_response.get())ifnamespace=='Alexa.PowerController':# The directive to TurnOff or TurnOn the light bulb.# Note: This example code always returns a success response.endpoint_id=request['directive']['endpoint']['endpointId']power_state_value='OFF'ifname=='TurnOff'else'ON'correlation_token=request['directive']['header']['correlationToken']# Check for an error when setting the state.device_set=update_device_state(endpoint_id=endpoint_id,state='powerState',value=power_state_value)ifnotdevice_set:returnAlexaResponse(name='ErrorResponse',payload={'type':'ENDPOINT_UNREACHABLE','message':'Unable to reach endpoint database.'}).get()directive_response=AlexaResponse(correlation_token=correlation_token)directive_response.add_context_property(namespace='Alexa.PowerController',name='powerState',value=power_state_value)returnsend_response(directive_response.get())# Send the responsedefsend_response(response):print('lambda_handler response -----')print(json.dumps(response))returnresponse# Make the call to your device cloud for controldefupdate_device_state(endpoint_id,state,value):attribute_key=state+'Value'# result = stubControlFunctionToYourCloud(endpointId, token, request);returnTrue# Datetime format for timeOfSample is ISO 8601, `YYYY-MM-DDThh:mm:ssZ`.defget_utc_timestamp(seconds=None):returndatetime.now(timezone.utc).isoformat()classAlexaResponse: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')},'endpoint':{"scope":{"type":"BearerToken","token":kwargs.get('token','INVALID')},"endpointId":kwargs.get('endpoint_id','INVALID')},'payload':kwargs.get('payload',{})}if'correlation_token'inkwargs:self.event['header']['correlation_token']=kwargs.get('correlation_token','INVALID')if'cookie'inkwargs:self.event['endpoint']['cookie']=kwargs.get('cookie','{}')# No endpoint property in an AcceptGrant or Discover request.ifself.event['header']['name']=='AcceptGrant.Response'orself.event['header']['name']=='Discover.Response':self.event.pop('endpoint')defadd_context_property(self,**kwargs):self.context_properties.append(self.create_context_property(**kwargs))self.context_properties.append(self.create_context_property())defadd_cookie(self,key,value):if"cookies"inselfisNone:self.cookies={}self.cookies[key]=valuedefadd_payload_endpoint(self,**kwargs):self.payload_endpoints.append(self.create_payload_endpoint(**kwargs))defcreate_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)}defcreate_payload_endpoint(self,**kwargs):# Return the proper structure expected for the endpoint.# All discovery responses must include the additionAttributesadditionalAttributes={'manufacturer':kwargs.get('manufacturer','Sample Manufacturer'),'model':kwargs.get('model_name','Sample Model'),'serialNumber':kwargs.get('serial_number','U11112233456'),'firmwareVersion':kwargs.get('firmware_version','1.24.2546'),'softwareVersion':kwargs.get('software_version','1.036'),'customIdentifier':kwargs.get('custom_identifier','Sample custom ID')}endpoint={'capabilities':kwargs.get('capabilities',[]),'description':kwargs.get('description','Smart Home Tutorial: Virtual smart light bulb'),'displayCategories':kwargs.get('display_categories',['LIGHT']),'endpointId':kwargs.get('endpoint_id','endpoint_'+"%0.6d"%random.randint(0,999999)),'friendlyName':kwargs.get('friendly_name','Sample light'),'manufacturerName':kwargs.get('manufacturer_name','Sample Manufacturer')}endpoint['additionalAttributes']=kwargs.get('additionalAttributes',additionalAttributes)if'cookie'inkwargs:endpoint['cookie']=kwargs.get('cookie',{})returnendpointdefcreate_payload_endpoint_capability(self,**kwargs):# All discovery responses must include the Alexa interfacecapability={'type':kwargs.get('type','AlexaInterface'),'interface':kwargs.get('interface','Alexa'),'version':kwargs.get('version','3')}supported=kwargs.get('supported',None)ifsupported:capability['properties']={}capability['properties']['supported']=supportedcapability['properties']['proactivelyReported']=kwargs.get('proactively_reported',False)capability['properties']['retrievable']=kwargs.get('retrievable',False)returncapabilitydefget(self,remove_empty=True):response={'context':self.context,'event':self.event}iflen(self.context_properties)>0:response['context']['properties']=self.context_propertiesiflen(self.payload_endpoints)>0:response['event']['payload']['endpoints']=self.payload_endpointsifremove_empty:iflen(response['context'])<1:response.pop('context')returnresponsedefset_payload(self,payload):self.event['payload']=payloaddefset_payload_endpoint(self,payload_endpoints):self.payload_endpoints=payload_endpointsdefset_payload_endpoints(self,payload_endpoints):if'endpoints'notinself.event['payload']:self.event['payload']['endpoints']=[]self.event['payload']['endpoints']=payload_endpoints
To build and load the function, under DEPLOY, choose Deploy.
Make sure to deploy the function after each update that you make.
In the green box at the top of the Lambda > Functions > my-smart-home-skill page, you should see a message that says that the function updated successfully.
Substep 2.4: Test the Lambda function
After you deploy your skill code, you can invoke the Lambda function and view the results in the AWS developer console. The following code examples send an Alexa.Discovery request to report the endpoint capabilities and an Alexa.PowerController request to turn on the light.
Define a discovery test
On the Lambda > Functions > my-smart-home-skill page, choose the Test tab.
Under Test event action, select Create new event.
For Event name, enter DiscoveryTest.
For Template, expand the dropdown list, and then select Alexa Smart Home Discovery Request.
In the Event JSON code editor, replace the existing code with the following test code.