Note: Sign in to the developer console to build or publish your skill.
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 dashboard, from the left menu under Access management, click Roles.
On the IAM > Roles page, click Create role.
For IAM > Roles > Create role page, enter the following information:
Under Select trusted entity, for Trusted entity type, select AWS service.
Under Use cases, select Lambda, and then click Next.
Under Add permissions, filter to find the policy called, AWSLambdaBasicExecutionRole.
Select the check box next to the AWSLambdaBasicExecutionRole policy and AWS managed type, and then click Next.
Under Name, review, and create page, for Role name, enter lambda_smart_home_skill_role, and then, at the bottom of the page, click 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 dashboard.
If you've created Lambda functions, the console displays a list of your functions.
On the AWS Lambda page, click Create function.
Click 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.
Under Create function, select Author from scratch.
Under Basic information, 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 select Create function.
On the Lambda > Functions > my-smart-home-skill page, under Function overview, click + 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.
For Skill ID verification, select Enable (recommended).
To add the trigger to invoke your skill, click 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 source code, select the Code tab.
For Python, your skill code resides in the file, lambda_function.py.
For Node.js, your skill code resides in the file, index.js or index.mjs. For the tutorial, rename index.mjs to index.js.
To display the function code, click lambda_function.py or index.js.
Replace the existing code with the following smart home skill code that controls the virtual light bulb.
The code supports the AcceptGrant, Discovery, TurnOn, and TurnOff directives.
// -*- 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));handleDiscovery(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));handlePowerControl(request,context);}}elseif(request.directive.header.namespace==='Alexa.Authorization'&&request.directive.header.name==='AcceptGrant'){handleAuthorization(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}));context.succeed({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}));context.succeed({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));context.succeed(response);}};
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 save the file, under File, select Save.
To deploy the function, select Deploy.
In the green box at the top of the Lambda > Functions > my-smart-home-skill page, you see a message that says that the function updated successfully.
Substep 2.4: Test the Lambda function
After you save 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, select the Test tab.
Under Test event, select Create new event.
For Event name, enter DiscoveryTest.
For Template, from the drop-down menu, select Alexa Smart Home Discovery Request.
In the Event JSON code editor, replace the existing code with the following test code.