Skip to content

Instantly share code, notes, and snippets.

@jonn26
Last active July 30, 2017 23:54
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jonn26/5a4d0fd9e3332ab6ecb56bbd51ed77f8 to your computer and use it in GitHub Desktop.
Save jonn26/5a4d0fd9e3332ab6ecb56bbd51ed77f8 to your computer and use it in GitHub Desktop.
Amazon Echo / Alexa Smart Home Example Flow

This flow demonstrates using the Alexa Smart Home Skill with a minimal lambda smart-home skill adapter to interface directly with node-red.

Unlike other approaches, this only relies on two components, Amazon and node-red; not a 3rd party service which you have to worry about being secure or going down.

You can then control a light (or anything in node-red, really) directly with phrases like:

"Alexa, turn on test light" "Alexa, dim test light to 10 percent"

Steps to get this up and running:

  1. Configure an Alexa Smart Home skill and use "Login with Amazon" for your account linking. See here & here

  2. Although it will work without it, for security, I HIGHLY recommend using SSL between your lambda skill adapter in step 3 below and node-red. If running node-red on a home server this would normally entail setting up dynamic DNS (desec.io is free and works well) and using LetsEncrypt to set up a certificate for your domain.

  3. Use the following python skill adapter in your Amazon lambda instance, and be sure to insert your node-red url endpoint in url = 'https://my_url/smarthome'. Credit to JonW who posted this skill adapter here which I only slightly modified.

"""
--------------------  REFERENCE MATERIALS  --------------------  
Smart Home Skill API Reference:
https://developer.amazon.com/public/solutions/alexa/alexa-skills-kit/docs/smart-home-skill-api-reference
Python reference for url/http handling
https://docs.python.org/2/howto/urllib2.html
---------------------------------------------------------------  
"""
import json
import urllib
import urllib2
import logging
 
logger = logging.getLogger()
logger.setLevel(logging.INFO)
 
# ============================================================================
#   Default Handler for all function requests
# ============================================================================
def lambda_handler(event, context):
 
    # Set the request URL to host:port.
    url = 'https://my_url/smarthome'
 
    # Encode the request event data.
    data = json.dumps(event)
 
    # Perform the request with the event data.
    req = urllib2.Request(url, data)
    req.add_header('Content-Type' , 'application/json')
    
 
    # Create the response object.
    response = urllib2.urlopen(req)
    
    # Get the complete response page.
    the_page = str(response.read())
    
    # Load raw page data into JSON object.
    parsed_json = json.loads(the_page)
 
    # Log the event request, page response and json object.
    #logger.info('**  Context Request  ** = {}'.format(context)) 
    logger.info('**  Event Request  ** = {}'.format(event)) 
    logger.info('**  Page Response  ** = {}'.format(the_page)) 
    logger.info('**  Parsed JSON  ** = {}'.format(parsed_json)) 
 
    # Return value as a JSON object.
    return parsed_json    
  1. Set up port forwarding on your firewall to accept and forward to the right internal host running node-red on your network.

  2. Paste in the node-red flow below and change e-mail address in the "verify email" node to your e-mail address associated with your linked Amazon account.

  3. In the Alexa mobile app, link your smart home skill with your Amazon account.

  4. In the Alexa mobile app, hit Discover Devices. If everything worked, you should see 2 new devices discovered: Test Light & Test Thermostat.

  5. You're done! Say "Alexa, turn on test light", and you should see a message in node red debug tab.

[{"id":"5a2248b8.76ba18","type":"tab","label":"alexa smart home"},{"id":"d8c878fa.c923b8","type":"http in","z":"5a2248b8.76ba18","name":"Echo request","url":"/smarthome","method":"post","swaggerDoc":"","x":75,"y":227.54074096679688,"wires":[["f2837818.d89118","557c2004.c3a75"]]},{"id":"9a1bb05f.d2e1d","type":"http response","z":"5a2248b8.76ba18","name":"","x":1624.0790710449219,"y":297.19420623779297,"wires":[]},{"id":"67446ad6.6005f4","type":"debug","z":"5a2248b8.76ba18","name":"","active":false,"console":"false","complete":"payload","x":541.7514038085938,"y":61.672271728515625,"wires":[]},{"id":"b7f0a6d5.e25498","type":"switch","z":"5a2248b8.76ba18","name":"check namespace","property":"req.body.header.namespace","propertyType":"msg","rules":[{"t":"eq","v":"Alexa.ConnectedHome.Discovery","vt":"str"},{"t":"eq","v":"Alexa.ConnectedHome.System","vt":"str"},{"t":"eq","v":"Alexa.ConnectedHome.Control","vt":"str"}],"checkall":"true","outputs":3,"x":525.8333435058594,"y":219.111083984375,"wires":[["8f8662f3.bc633"],["390b23f2.5ccdec"],["db82b2df.e7f6b"]]},{"id":"c21e0114.0174","type":"template","z":"5a2248b8.76ba18","name":"Discovery Response","field":"payload","fieldType":"msg","format":"json","syntax":"mustache","template":"{\n \"header\": {\n \"messageId\": \"{{_msgid}}\",\n \"namespace\": \"Alexa.ConnectedHome.Discovery\",\n \"name\": \"DiscoverAppliancesResponse\",\n \"payloadVersion\": \"2\"\n \n},\n \"payload\": {\n \"discoveredAppliances\": [\n {\n \"actions\": [\n \"turnOn\",\n \"turnOff\",\n \"setPercentage\",\n \"incrementPercentage\",\n \"decrementPercentage\"\n ],\n \"additionalApplianceDetails\": {\n \"extraDetail1\": \"optionalDetailForSkillAdapterToReferenceThisDevice\",\n \"extraDetail2\": \"There can be multiple entries\",\n \"extraDetail3\": \"but they should only be used for reference purposes.\",\n \"extraDetail4\": \"This is not a suitable place to maintain current device state\"\n },\n \"applianceId\": \"testlight\",\n \"friendlyName\": \"Test Light\",\n \"friendlyDescription\": \"My test light in the office\",\n \"isReachable\": true,\n \"manufacturerName\": \"jonn26 inc.\",\n \"modelName\": \"TESTDIMABLELIGHT01\",\n \"version\": \"VER01\"\n },\n {\n \"actions\": [\n \"incrementTargetTemperature\",\n \"decrementTargetTemperature\",\n \"setTargetTemperature\"\n ],\n \"additionalApplianceDetails\": {},\n \"applianceId\": \"thermostat\",\n \"friendlyName\": \"Test Thermostat\",\n \"friendlyDescription\": \"Test Thermostat in the livingroom\",\n \"isReachable\": true,\n \"manufacturerName\": \"jonn26 inc.\",\n \"modelName\": \"TESTTHERMOSTAT01\",\n \"version\": \"VER01\"\n }\n ]\n}\n}","x":1016.6665954589844,"y":164.66659545898438,"wires":[["9a1bb05f.d2e1d"]]},{"id":"f2837818.d89118","type":"http request","z":"5a2248b8.76ba18","name":"verify oauth token","method":"GET","ret":"obj","url":"https://api.amazon.com/user/profile?access_token={{req.body.payload.accessToken}}","tls":"","x":200.72222900390625,"y":295.55556297302246,"wires":[["f14aa09c.eba3d"]]},{"id":"390b23f2.5ccdec","type":"switch","z":"5a2248b8.76ba18","name":"HealthCheckRequest","property":"req.body.header.name","propertyType":"msg","rules":[{"t":"eq","v":"HealthCheckRequest","vt":"str"}],"checkall":"true","outputs":1,"x":753.2421569824219,"y":217.95311737060547,"wires":[["4069c1ff.5ce7c"]]},{"id":"4069c1ff.5ce7c","type":"template","z":"5a2248b8.76ba18","name":"HealthCheckResponse","field":"payload","fieldType":"msg","format":"json","syntax":"mustache","template":"{\n \"header\": {\n \"messageId\": \"{{_msgid}}\",\n \"name\": \"HealthCheckResponse\",\n \"namespace\": \"Alexa.ConnectedHome.System\",\n \"payloadVersion\": \"2\"\n },\n \"payload\": {\n \"description\": \"The system is currently healthy\",\n \"isHealthy\": true\n }\n}","x":1024.2421569824219,"y":217.13280487060547,"wires":[["9a1bb05f.d2e1d"]]},{"id":"8f8662f3.bc633","type":"switch","z":"5a2248b8.76ba18","name":"DiscoverAppliancesRequest","property":"req.body.header.name","propertyType":"msg","rules":[{"t":"eq","v":"DiscoverAppliancesRequest","vt":"str"}],"checkall":"true","outputs":1,"x":774.5077819824219,"y":164.52342224121094,"wires":[["c21e0114.0174"]]},{"id":"557c2004.c3a75","type":"debug","z":"5a2248b8.76ba18","name":"","active":false,"console":"false","complete":"req.body","x":299.7300262451172,"y":102.07900810241699,"wires":[]},{"id":"b513dbda.e8f538","type":"change","z":"5a2248b8.76ba18","name":"invalid oauth key return error 401","rules":[{"t":"set","p":"statusCode","pt":"msg","to":"401","tot":"str"}],"action":"","property":"","from":"","to":"","reg":false,"x":603.2853698730469,"y":291.7456588745117,"wires":[["9a1bb05f.d2e1d"]]},{"id":"f14aa09c.eba3d","type":"switch","z":"5a2248b8.76ba18","name":"verify e-mail","property":"payload.email","propertyType":"msg","rules":[{"t":"eq","v":"your_email@gmail.com","vt":"str"},{"t":"else"}],"checkall":"true","outputs":2,"x":331.2421875,"y":219.9453125,"wires":[["b7f0a6d5.e25498","67446ad6.6005f4"],["b513dbda.e8f538","b7924711.cf6e28"]]},{"id":"b7924711.cf6e28","type":"debug","z":"5a2248b8.76ba18","name":"","active":false,"console":"false","complete":"false","x":528.1744689941406,"y":361.41231536865234,"wires":[]},{"id":"db82b2df.e7f6b","type":"switch","z":"5a2248b8.76ba18","name":"applianceid","property":"req.body.payload.appliance.applianceId","propertyType":"msg","rules":[{"t":"eq","v":"testlight","vt":"str"},{"t":"eq","v":"thermostat","vt":"str"}],"checkall":"true","outputs":2,"x":726.25,"y":522.9452972412109,"wires":[["e80d4cca.26651"],["e6ce36ad.83bff8"]]},{"id":"e80d4cca.26651","type":"change","z":"5a2248b8.76ba18","name":"","rules":[{"t":"set","p":"payload","pt":"msg","to":"req.body.header.name","tot":"msg"}],"action":"","property":"","from":"","to":"","reg":false,"x":913.0198669433594,"y":484.92186737060547,"wires":[["912adbaa.b72ea8","c9328294.af43f"]]},{"id":"5358d664.c30898","type":"template","z":"5a2248b8.76ba18","name":"ControlResponse","field":"payload","fieldType":"msg","format":"json","syntax":"mustache","template":"{\n \"header\": {\n \"messageId\": \"{{_msgid}}\",\n \"name\": \"{{payload}}\",\n \"namespace\": \"Alexa.ConnectedHome.Control\",\n \"payloadVersion\": \"2\"\n },\n \"payload\": {}\n}\n","x":1420.2421569824219,"y":485.63280487060547,"wires":[["9a1bb05f.d2e1d"]]},{"id":"912adbaa.b72ea8","type":"change","z":"5a2248b8.76ba18","name":"set confirmation","rules":[{"t":"change","p":"payload","pt":"msg","from":"TurnOnRequest","fromt":"str","to":"TurnOnConfirmation","tot":"str"},{"t":"change","p":"payload","pt":"msg","from":"TurnOffRequest","fromt":"str","to":"TurnOffConfirmation","tot":"str"},{"t":"change","p":"payload","pt":"msg","from":"SetPercentageRequest","fromt":"str","to":"SetPercentageConfirmation","tot":"str"}],"action":"","property":"","from":"","to":"","reg":false,"x":1150.1308288574219,"y":485.3108139038086,"wires":[["5358d664.c30898"]]},{"id":"c9328294.af43f","type":"switch","z":"5a2248b8.76ba18","name":"On / Off / Percentage","property":"payload","propertyType":"msg","rules":[{"t":"eq","v":"TurnOnRequest","vt":"str"},{"t":"eq","v":"TurnOffRequest","vt":"str"},{"t":"eq","v":"SetPercentageRequest","vt":"str"}],"checkall":"true","outputs":3,"x":1172.8333435058594,"y":408.66661834716797,"wires":[["799dd08f.61465"],["e29b098a.0459a8"],["e44bcb63.179558"]]},{"id":"e44bcb63.179558","type":"change","z":"5a2248b8.76ba18","name":"testlight percent","rules":[{"t":"set","p":"topic","pt":"msg","to":"testlight","tot":"str"},{"t":"set","p":"payload","pt":"msg","to":"req.body.payload.percentageState.value","tot":"msg"}],"action":"","property":"","from":"","to":"","reg":false,"x":1411.3889083862305,"y":446.9999885559082,"wires":[["81b9eee3.07f19"]]},{"id":"799dd08f.61465","type":"change","z":"5a2248b8.76ba18","name":"testlight on","rules":[{"t":"set","p":"topic","pt":"msg","to":"testlight","tot":"str"},{"t":"set","p":"payload","pt":"msg","to":"on","tot":"str"}],"action":"","property":"","from":"","to":"","reg":false,"x":1402.6665954589844,"y":370.66661834716797,"wires":[["81b9eee3.07f19"]]},{"id":"e29b098a.0459a8","type":"change","z":"5a2248b8.76ba18","name":"testlight off","rules":[{"t":"set","p":"topic","pt":"msg","to":"testlight","tot":"str"},{"t":"set","p":"payload","pt":"msg","to":"off","tot":"str"}],"action":"","property":"","from":"","to":"","reg":false,"x":1401.1110534667969,"y":408.8888626098633,"wires":[["81b9eee3.07f19"]]},{"id":"45a19fe1.860a7","type":"comment","z":"5a2248b8.76ba18","name":"Edit DiscoveryResponse with your device names","info":"See the following for details on the fields:\n\nhttps://developer.amazon.com/public/solutions/alexa/alexa-skills-kit/docs/smart-home-skill-api-reference","x":1097.4812927246094,"y":128.6296157836914,"wires":[]},{"id":"81b9eee3.07f19","type":"debug","z":"5a2248b8.76ba18","name":"","active":true,"console":"false","complete":"false","x":1644.8149108886719,"y":409.2221908569336,"wires":[]},{"id":"f53f43e.60bc2c","type":"comment","z":"5a2248b8.76ba18","name":"ControlRequest","info":"","x":725.9258728027344,"y":479.4073715209961,"wires":[]},{"id":"9ce4cf8d.98065","type":"comment","z":"5a2248b8.76ba18","name":"Edit your e-mail address here","info":"This flow assumes you are using \"login with amazon\"\n\nedit the verify e-mail node with your amazon linked e-mail address.","x":342.0370635986328,"y":177.4074182510376,"wires":[]},{"id":"80541fe8.2412d","type":"comment","z":"5a2248b8.76ba18","name":"testlight","info":"","x":872.8334045410156,"y":450.99999237060547,"wires":[]},{"id":"e6ce36ad.83bff8","type":"change","z":"5a2248b8.76ba18","name":"","rules":[{"t":"set","p":"payload","pt":"msg","to":"req.body.header.name","tot":"msg"}],"action":"","property":"","from":"","to":"","reg":false,"x":910.6665954589844,"y":563.6667098999023,"wires":[["50689281.9725ac"]]},{"id":"50689281.9725ac","type":"switch","z":"5a2248b8.76ba18","name":"Set / Incr / Decr","property":"payload","propertyType":"msg","rules":[{"t":"eq","v":"SetTargetTemperatureRequest","vt":"str"},{"t":"eq","v":"IncrementTargetTemperatureRequest","vt":"str"},{"t":"eq","v":"DecrementTargetTemperatureRequest","vt":"str"}],"checkall":"true","outputs":3,"x":1145.6665954589844,"y":612.6667098999023,"wires":[[],[],[]]},{"id":"730c62c8.6a736c","type":"comment","z":"5a2248b8.76ba18","name":"thermostat","info":"","x":881.6665954589844,"y":530.6667098999023,"wires":[]},{"id":"5553e653.c6d578","type":"comment","z":"5a2248b8.76ba18","name":"Thermostat Work-In-Progress","info":"","x":1155.8333435058594,"y":554.0000839233398,"wires":[]}]
@Averelll
Copy link

Great stuff, I just set it up myself, exactly what I wanted to do. 2 questions though, the first one is baffling me, somehow I can't get my email back when the verify oauth token call is made, it just returns my userid. I used that for now, but still strange, only the user_id is in the payload.
second one more general, when you do this in principal your node-red console is open to the outside world. Did you set additional security? filtering on ip_address ranges?

Thx,
Ferd

@Averelll
Copy link

Nevermind the first one, I messed up the scope for my skill :)

@jonn26
Copy link
Author

jonn26 commented Apr 1, 2017

Sorry for the late reply, just saw your message. Glad you got it to work. As for your second question, I handle this by running an apache reverse proxy which is configured to only requests made to the /smarthome url endpoint to the outside world.

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