Project tutorial
Edison: Nightlight Lantern Powered by Alexa

Edison: Nightlight Lantern Powered by Alexa © GPL3+

A nightlight lantern powered by Alexa Home Skill.

  • 1,167 views
  • 0 comments
  • 4 respects

Components and supplies

Apps and online services

About this project

Introduction

There is a project call Anna: Alexa Powered Interactive Light. But that does not use the Smart Home Skill. This project will use the Smart Home Skill to connect to a NeoPixel Ring thru Raspberry Pi and Arduino Uno.

Step 1. Create a Smart Home Skill and Connected Lambda.

Follow the steps here to create the Smart Home Skill and Lambda. Setup LWA and take note of the client id and client secret which will be used in the Skill Configuration.

Here I create a Skill (v3) called Edison Nightlight Lantern and a Lambda with the same name.

Step 2. Create an IoT Thing.

Follow step 2 of this to create the IoT Thing. I named the thing edison_nightlight_lantern. Remember to download the public key, private key and certificate.

Step 3. Connect Lambda to the Thing.

Follow step 3 of this to connect the Lambda to the IoT Thing. For now, you can use the Lambda function from the steps of the guide to check that you have setup the skill and lambda correctly. In Step 6 below, you will use the actual lambda function (code section) of this project.

Step 4. Setup Raspberry Pi

Follow this link to setup the Raspberry Pi. Follow this resource to get the newer version of Node.js.

Step 5. Connect the Raspberry Pi to AWS IoT using JavaScript SDK

Follow this guide and this to setup the connection between Raspberry Pi and AWS IoT using the JavaScript SDK. You are good to continue if you can run some of the examples of the SDK.

I follow the custom configuration file approach to run those example applications. See the command line in Step 7. I named the configuration file as config.json. It is a JSON file with the following properties:

{
   "host":           "a22j5sm6o3yzc5.iot.us-east-1.amazonaws.com",
   "port":           8883,
   "clientId":       "MyRaspberryPi",
   "thingName":      "MyRaspberryPi",
   "caCert":         "root-CA.crt",
   "clientCert":     "4bbdc778b9-certificate.pem.crt",
   "privateKey":     "4bbdc778b9-private.pem.key"
}

Step 6. Copy the Lambda function from the CODE Section

Update the Lambda function by copying the Python code from the CODE section. The Python code implements the PowerController and ColorController interfaces.

These are the capabilities in Python code:

                         "capabilities": [
                           {
                             "type": "AlexaInterface",
                             "interface": "Alexa",
                             "version": "3"
                           },
                           {
                             "interface": "Alexa.PowerController",
                             "version": "3",
                             "type": "AlexaInterface",
                             "properties": {
                               "supported": [
                                 {
                                   "name": "powerState"
                                 }
                               ],
                               "proactivelyReported": True,
                               "retrievable": True
                             }
                           },
                           {
                             "type": "AlexaInterface",
                             "interface": "Alexa.ColorController",
                             "version": "3",
                             "properties": {
                               "supported": [
                                 {
                                   "name": "color"
                                 }
                               ],
                               "proactivelyReported": True,
                               "retrievable": True
                             }
                           }
                       ] 

This is the Power Controller code:

   if request_namespace == "Alexa.PowerController":
       if request_name == "TurnOn":
           # light_state("on")
           value = { 'hue': 0, 'saturation': 0, 'brightness': 1 }
           set_color(value)
           value = "ON"   # this is for response
       else:
           # light_state("off")
           value = { 'hue': 0, 'saturation': 0, 'brightness': 0 }
           set_color(value)
           value = "OFF"
       response = {
           "context": {
               "properties": [
                   {
                       "namespace": "Alexa.PowerController",
                       "name": "powerState",
                       "value": value,
                       "timeOfSample": get_utc_timestamp(),
                       "uncertaintyInMilliseconds": 500
                   }
               ]
           },
           "event": {
               "header": {
                   "namespace": "Alexa",
                   "name": "Response",
                   "payloadVersion": "3",
                   "messageId": get_uuid(),
                   "correlationToken": request["directive"]["header"]["correlationToken"]
               },
               "endpoint": {
                   "scope": {
                       "type": "BearerToken",
                       "token": request["directive"]["endpoint"]["scope"]["token"]
                   },
                   "endpointId": request["directive"]["endpoint"]["endpointId"]
               },
               "payload": {}
           }
       }

This is the Color Controller Code:

   elif request_namespace == "Alexa.ColorController":
       value = request["directive"]["payload"]["color"]
       set_color(value)
       response = {
           "context": {
               "properties": [ {
                   "namespace": "Alexa.ColorController",
                   "name": "color",
                   "value": value,
                   "timeOfSample": get_utc_timestamp(),
                   "uncertaintyInMilliseconds": 1000
               } ]
           },
           "event": {
               "header": {
                   "namespace": "Alexa",
                   "name": "Response",
                   "payloadVersion": "3",
                   "messageId": get_uuid(),
                   "correlationToken": request["directive"]["header"]["correlationToken"]
               },
               "endpoint": {
                   "scope": {
                     "type": "BearerToken",
                     "token": request["directive"]["endpoint"]["scope"]["token"]
                   },
                   "endpointId": request["directive"]["endpoint"]["endpointId"]
               },
               "payload": {}
           }
       }

Step 7. Setup Arduino Uno and Neo Pixel with Johnny-Five and node-pixel

Follow this to install Johnny-Five.

Follow this to install the node-pixel module. Take note that you need to install a custom firmata to your Arduino.

Install the hsv-rgb node module:

npm install hsv-rgb

This node module is used inside the handleDelta() to convert the HSV value to RGB value, like:

        let color = stateObject.state.color;
        let h = isUndefined(color.hue) ? 0 : color.hue;
        let s = isUndefined(color.saturation) ? 1 : color.saturation;
        let v = isUndefined(color.brightness) ? 1 : color.brightness;
        let value = rgb(h, s*100, v*100);
        console.log(JSON.stringify([h, s, v]));
        console.log(JSON.stringify(value));
        strip.color(value);
        strip.show(); 

Setup the Arduino and Neo Pixel as follows:

  • Arduino Pin 6 -> NeoPixel Din
  • Arduino 5V -> NeoPixeo 5V
  • Arduino GND -> NeoPixel GND

Copy the JavaScript (edison_nightlight_lantern.js) from the CODE section to the examples directory and run the following command:

node examples/edison_nightlight_lantern.js -F config.json

Pictures and Video

Edison demo video.

Code

edison_nightlight_lantern.pyPython
import logging
import time
import json
import uuid
import datetime

import boto3

client = boto3.client('iot-data')

def get_utc_timestamp(seconds=None):
    return time.strftime("%Y-%m-%dT%H:%M:%S.00Z", time.gmtime(seconds))

def set_color(color):
    response = client.update_thing_shadow(
        thingName = 'your_thing_name', 
        payload = json.dumps({
            'state': {
                'desired': {
                    'color': color
                }
            }
        }
        )
    )

def get_uuid():
    return str(uuid.uuid4())
    
# Setup logger
logger = logging.getLogger()
logger.setLevel(logging.INFO)

def lambda_handler(event, context):

    logger.info(">>> received v3 directive <<<")

    logger.info(json.dumps(event))
    
    if event['directive']['header']['name'] == "Discover":
        logger.info(">>> discover <<<")
        response = handle_discovery(event)
    else:
        logger.info(">>> control <<<")
        response = handle_control(event, context)    

    logger.info("response" + json.dumps(response))  
    return response
 

def handle_discovery(event):

        response = {
            "event":
                {
                    "header":
                    {
                        "correlationToken": "12345692749237492",
                        "namespace": "Alexa.Discovery",
                        "name": "Discover.Response",
                        "payloadVersion": "3",
                        "messageId": event['directive']['header']['messageId']
                    },
            "payload":
                {
                "endpoints":[
                        {
                          "endpointId": "your_custom_endpoint",
                          "manufacturerName": "your manufacturer name",
                          "friendlyName": "your friendly name",
                          "description": "your description",
                          "displayCategories": ["LIGHT"],
                          "cookie": {
                            "key1": "arbitrary key/value pairs for skill to reference this endpoint.",
                          },
                          "capabilities": [
                            {
                              "type": "AlexaInterface",
                              "interface": "Alexa",
                              "version": "3"
                            },
                            {
                              "interface": "Alexa.PowerController",
                              "version": "3",
                              "type": "AlexaInterface",
                              "properties": {
                                "supported": [
                                  {
                                    "name": "powerState"
                                  }
                                ],
                                "proactivelyReported": True,
                                "retrievable": True
                              }
                            },
                            {
                              "type": "AlexaInterface",
                              "interface": "Alexa.ColorController",
                              "version": "3",
                              "properties": {
                                "supported": [
                                  {
                                    "name": "color"
                                  }
                                ],
                                "proactivelyReported": True,
                                "retrievable": True
                              }
                            }
                        ]
                    }
                ]
            }
          }
        }
        logger.info("Response: " +json.dumps(response))
        return response
 

def handle_control(request, context):
    request_namespace = request["directive"]["header"]["namespace"]
    request_name = request["directive"]["header"]["name"]

    if request_namespace == "Alexa.PowerController":
        if request_name == "TurnOn":
            # light_state("on")
            value = { 'hue': 0, 'saturation': 0, 'brightness': 1 }
            set_color(value)
            value = "ON"   # this is for response
        else:
            # light_state("off")
            value = { 'hue': 0, 'saturation': 0, 'brightness': 0 }
            set_color(value)
            value = "OFF"

        response = {
            "context": {
                "properties": [
                    {
                        "namespace": "Alexa.PowerController",
                        "name": "powerState",
                        "value": value,
                        "timeOfSample": get_utc_timestamp(),
                        "uncertaintyInMilliseconds": 500
                    }
                ]
            },
            "event": {
                "header": {
                    "namespace": "Alexa",
                    "name": "Response",
                    "payloadVersion": "3",
                    "messageId": get_uuid(),
                    "correlationToken": request["directive"]["header"]["correlationToken"]
                },
                "endpoint": {
                    "scope": {
                        "type": "BearerToken",
                        "token": request["directive"]["endpoint"]["scope"]["token"]
                    },
                    "endpointId": request["directive"]["endpoint"]["endpointId"]
                },
                "payload": {}
            }
        }
    elif request_namespace == "Alexa.ColorController":
        value = request["directive"]["payload"]["color"]
        set_color(value)

        response = {
            "context": {
                "properties": [ {
                    "namespace": "Alexa.ColorController",
                    "name": "color",
                    "value": value,
                    "timeOfSample": get_utc_timestamp(),
                    "uncertaintyInMilliseconds": 1000
                } ]
            },
            "event": {
                "header": {
                    "namespace": "Alexa",
                    "name": "Response",
                    "payloadVersion": "3",
                    "messageId": get_uuid(),
                    "correlationToken": request["directive"]["header"]["correlationToken"]
                },
                "endpoint": {
                    "scope": {
                      "type": "BearerToken",
                      "token": request["directive"]["endpoint"]["scope"]["token"]
                    },
                    "endpointId": request["directive"]["endpoint"]["endpointId"]
                },
                "payload": {}
            }
        }

    return response
    
edison_nightlight_lantern.jsJavaScript
/*
 * Copyright 2010-2015 Amazon.com, Inc. or its affiliates. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (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/apache2.0
 *
 * or in the "license" file accompanying this file. This file 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.
 */

//node.js deps

//npm deps

//app deps
const thingShadow = require('..').thingShadow;
const cmdLineProcess = require('./lib/cmdline');
const isUndefined = require('../common/lib/is-undefined');
const rgb = require('../../hsv-rgb');
const pixel = require('../../node-pixel');
const five = require('../../johnny-five');

//begin module

//
// Simulate the interaction of a mobile device and a remote thing via the
// AWS IoT service.  The remote thing will be a dimmable color lamp, where
// the individual RGB channels can be set to an intensity between 0 and 255.  
// One process will simulate each side, with testMode being used to distinguish 
// between the mobile app (1) and the remote thing (2).  The remote thing
// will update its state periodically using an 'update thing shadow' operation,
// and the mobile device will listen to delta events to receive the updated
// state information.
//

function processNightlight(args) {
   //
   // Instantiate the thing shadow class.
   //
   const thingShadows = thingShadow({
      keyPath: args.privateKey,
      certPath: args.clientCert,
      caPath: args.caCert,
      clientId: args.clientId,
      region: args.region,
      baseReconnectTimeMs: args.baseReconnectTimeMs,
      keepalive: args.keepAlive,
      protocol: args.Protocol,
      port: args.Port,
      host: args.Host,
      debug: args.Debug
   });

   //
   // Operation timeout in milliseconds
   //
   const operationTimeout = 10000;

   const thingName = 'your_thing_name';

   var currentTimeout = null;

   var board = new five.Board( { repl: false } );
   var strip = null;

   //
   // For convenience, use a stack to keep track of the current client 
   // token; in this example app, this should never reach a depth of more 
   // than a single element, but if your application uses multiple thing
   // shadows simultaneously, you'll need some data structure to correlate 
   // client tokens with their respective thing shadows.
   //
   var stack = [];

   function genericOperation(operation, state) {
      var clientToken = thingShadows[operation](thingName, state);

      if (clientToken === null) {
         //
         // The thing shadow operation can't be performed because another one
         // is pending; if no other operation is pending, reschedule it after an 
         // interval which is greater than the thing shadow operation timeout.
         //
         if (currentTimeout !== null) {
            console.log('operation in progress, scheduling retry...');
            currentTimeout = setTimeout(
               function() {
                  genericOperation(operation, state);
               },
               operationTimeout * 2);
         }
      } else {
         //
         // Save the client token so that we know when the operation completes.
         //
         stack.push(clientToken);
      }
   }

   function offState() {
      var values = {
         "hue": 0,
         "saturation": 0,
         "brightness": 0
      };

      return {
         state: {
            reported: values
         }
      };
   }

   function onState() {
      var values = {
         "hue": 0,
         "saturation": 0,
         "brightness": 1
      };

      return {
         state: {
            reported: values
         }
      };
   }

   function turnOff() {
      strip.off();
   }

   function turnOn() {
      strip.color([0, 0, 255]);
      strip.show();
   }

   function deviceConnect() {
      thingShadows.register(thingName, {
            ignoreDeltas: false
         },
         function(err, failedTopics) {
            if (isUndefined(err) && isUndefined(failedTopics)) {
               console.log('Device thing registered.');
               genericOperation('update', offState());
            }
         });
   }

   function handleStatus(thingName, stat, clientToken, stateObject) {
      var expectedClientToken = stack.pop();

      if (expectedClientToken === clientToken) {
         console.log('got \'' + stat + '\' status on: ' + thingName);
      } else {
         console.log('(status) client token mismtach on: ' + thingName);
      }

   }

   function handleDelta(thingName, stateObject) {
      console.log('delta on: ' + thingName + JSON.stringify(stateObject));

      // do delta
      console.log(JSON.stringify(stateObject.state.color));
      if (!isUndefined(stateObject.state.color)) {
         let color = stateObject.state.color;
         let h = isUndefined(color.hue) ? 0 : color.hue;
         let s = isUndefined(color.saturation) ? 1 : color.saturation;
         let v = isUndefined(color.brightness) ? 1 : color.brightness;

         let value = rgb(h, s*100, v*100);

         console.log(JSON.stringify([h, s, v]));
         console.log(JSON.stringify(value));

         strip.color(value);
         strip.show();
      }

      genericOperation('update', {
         state: {
            reported: stateObject.state
         }
      });
   }

   function handleTimeout(thingName, clientToken) {
      var expectedClientToken = stack.pop();

      if (expectedClientToken === clientToken) {
         console.log('timeout on: ' + thingName);
      } else {
         console.log('(timeout) client token mismtach on: ' + thingName);
      }

   }

   board.on("ready", function() {
      strip = new pixel.Strip({
         board: this,
         controller: "FIRMATA",
         strips: [ {pin: 6, length: 12} ],
         gamma: 2.8
      });   // end strip


      strip.off();

      deviceConnect();

   // setup shadows api events

   thingShadows.on('connect', function() {
      console.log('connected to AWS IoT');
   });

   thingShadows.on('close', function() {
      console.log('close');
      thingShadows.unregister(thingName);
   });

   thingShadows.on('reconnect', function() {
      console.log('reconnect');
   });

   thingShadows.on('offline', function() {
      //
      // If any timeout is currently pending, cancel it.
      //
      if (currentTimeout !== null) {
         clearTimeout(currentTimeout);
         currentTimeout = null;
      }
      //
      // If any operation is currently underway, cancel it.
      //
      while (stack.length) {
         stack.pop();
      }
      console.log('offline');
   });

   thingShadows.on('error', function(error) {
      console.log('error', error);
   });

   thingShadows.on('message', function(topic, payload) {
      console.log('message', topic, payload.toString());
   });

   thingShadows.on('status', function(thingName, stat, clientToken, stateObject) {
      handleStatus(thingName, stat, clientToken, stateObject);
   });

   thingShadows.on('delta', function(thingName, stateObject) {
      handleDelta(thingName, stateObject);
   });

   thingShadows.on('timeout', function(thingName, clientToken) {
      handleTimeout(thingName, clientToken);
   });

   });   // end board

}

module.exports = cmdLineProcess;

if (require.main === module) {
   cmdLineProcess('connect to the AWS IoT service and monitor thing shadow APIs',
      process.argv.slice(2), processNightlight);
}

Comments

Similar projects you might like

Anna: Alexa Powered Interactive Light

Project tutorial by vincent wong

  • 1,981 views
  • 0 comments
  • 7 respects

Animated Smart Light with Alexa and Arduino

Project tutorial by Bruno Portaluri

  • 7,510 views
  • 13 comments
  • 31 respects

Alexa BBQ/Kitchen Thermometer with IoT Arduino and e-Paper

Project tutorial by Roger Theriault

  • 3,403 views
  • 0 comments
  • 10 respects

Smart Pool: Alexa Controlled Pool Manager

Project tutorial by Benjamin Winiarski

  • 3,277 views
  • 3 comments
  • 10 respects

Enable Alexa Control to your Ceiling Fan

Project tutorial by Jithin Thulase

  • 4,070 views
  • 5 comments
  • 11 respects

Alexa Powered Arduino Kitchen Assistant

Project tutorial by TheParticleGuy

  • 3,594 views
  • 1 comment
  • 16 respects
Add projectSign up / Login