vincent wongmok mun fong
Published © GPL3+

Edison: Nightlight Lantern Powered by Alexa

A nightlight lantern powered by Alexa Home Skill.

IntermediateFull instructions provided1,869
Edison: Nightlight Lantern Powered by Alexa

Things used in this project

Story

Read more

Code

edison_nightlight_lantern.py

Python
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.js

JavaScript
/*
 * 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);
}

Credits

vincent wong

vincent wong

80 projects • 202 followers
mok mun fong

mok mun fong

2 projects • 2 followers
Thanks to Ben Eagan.

Comments