Thanks to the power of the Internet of Things (IoT), we can now interface with technology from anywhere on the planet, using our voices. What better way to put the technology to work than to create a remote camera trigger out of an Amazon Echo? In this tutorial, I explain how to make a remote trigger that can trigger a camera from an AWS region, an internationally accessible cloud space on the Internet.
To create a remote camera trigger, I used my Raspberry Pi A+ with an Echo Dot. This walk-through assumes that you have access to an integrated development environment (IDE) of some kind and that you are familiar with NodeJS and basic command line . Much of this article will guide you through the credential requirements needed to perform the magic. Another large chunk will cover the node code we will deploy. Most GUI and AWS console related items are touched upon, but greater depth into how to operate these interfaces is better left to you and your search engine, though I do provide a few links.
Terminology
- Object – A file (image, script, buffer, etc.)
- Utterance – A sentence or phrase to cue a response
Hardware:
- Echo Dot
- Raspberry Pi equipped with WiFi and a camera
SaaS accounts
- Amazon Web Services ( IOT, IAM, S3, and Lambda )
- Amazon Developer
Stack
- Node
- CLI
Set up
You’ll need a node application for the Pi and one for the AWS Lambda function that your Echo Dot will cue. Let’s establish the directories and objects we’ll need to build this.
I’m tying my camera application to my root user on the Raspberry. My camera application will reside in the /var/www/ directory of my Raspberry Pi. The structure of the build looks like this:
In the above:
- credentials will hold our access keys for AWS services.
- photos will store the photos taken.
- app.js is the application script itself.
- aws-config.json will be needed to connect with AWS.
- package.json will hold your project information and define dependences.
My Alexa App directory, which will be zipped and assigned to a Lambda, is as follows:
In the above:
- alexa.zip is the result of zipping all other objects in the directory together, this is what we upload to AWS Lambda.
- index.js will be our alexa script itself.
- package.json will define dependencies and project methods.
Once the objects and directories have been established, initialize both node packages inside their respective directories.
npm init;
A package.json will be generated in the same directory. This keeps track of packages for our scripts. Node is designed to use external dependencies with a minimal core, so each piece of hardware has its own application, and each application has its own dependency tree. We can add modules on the fly with:
npm install <package-name> --save
Or as a bulk change with the package.json objects:
// Lambda Operation "dependencies": { "alexa-sdk": "1.0.11", "aws-sdk": "2.92.0", "moment": "2.18.1" } ... // Raspberry Pi Application "dependencies": { "alexa-sdk": "1.0.11", "aws-iot-device-sdk": "2.0.1", "aws-sdk": "2.92.0", "lodash": "4.17.4", "raspicam": "0.2.14" } ...
Followed by
npm install
in our command terminal.
NPM Module Details
Each node package has it’s own documentation that you can access online:
Alexa SDK https://github.com/alexa/alexa-skills-kit-sdk-for-nodejs
AWS SDK https://github.com/aws/aws-sdk-js
AWS IOT Device SDK https://github.com/aws/aws-iot-device-sdk-js
Raspicam https://github.com/troyth/node-raspicam
For our Echo Dot
Log in at http://developer.amazon.com and navigate to the ‘Alexa’ section/tab. We will define the Alexa skill associated with our Lambda script.
I established my application name as ‘Take a Picture,’ with an invocation name of the same.
There’s a nice new feature for creating Alexa’s skill model that generates a script that tells the Alexa device what it should expect so far as method invocation on Lambda. You can map this functionality to any Alexa product you control with your account. The scripts are universal.
You are normally provided three ‘built-in’ intents: Cancel, Stop, and Help. We need a fourth one that establishes the actual action taking place.
Add your base event by clicking Add+ on the Intents header. That will bring up a form. Create a new intent. I called mine ‘TakePhoto’.
Utterances are recognized by Alexa as commands. Utterances are recognized by the Alexa device thanks to this utterance tree in the model. This is what the model script looks like after you fill out the form:
Once you confirm that your script looks good, click the Build Model button.
After the model has been built, your Alexa device will associate utterances with your intent. It will then send a payload to the endpoint we’ll establish.
Taking Action In Amazon Web Services
Create a photo bucket
To establish an actual action to be performed after our utterance, we will use Amazon Web Services (AWS).
Log into AWS and confirm that you have access to IoT, Lambda, IAM, and S3 services. For the sake of brevity, I won’t go over how to use the AWS GUI. Instead, I’m going to tell you what you need to do in a manner that enables you to contextually search for the answer yourself.
- Establish a bucket for your photos to be distributed to.
- Establish an IAM user that has read and write access to your bucket.
The IAM user will be used by the AWS SDK in our node app. We’ll need the Secret Key and ID. You can download these once you create a new user in IAM. We’ll wrap these in a JSON later, so take note of them.
Register your IoT device (camera)
Next, establish an IoT device in the AWS IoT Registry. After registering the device, you’ll be able to generate client certificates. These certificates will allow you to connect to the AWS IoT service. Place these files in your RaspiCam’s Apps credentials directory.
Ensure you have the following IoT creds:
- Private key (pem.key)
- Public key (pem.key)
- IoT Device certificate (pem.cert)
- Root certificate (pem)
With your IDE
We established a directory (somewhere) for our Alexa script. In there I created a script called index.js. This script will take our Alexa app’s payload and do something with it.
*To really understand how to do this, you should take a quick dive into the Alexa SDK
https://github.com/alexa/alexa-skills-kit-sdk-for-nodejs
Referencing Keys
A few credentials are needed before you can successfully execute any script. You need to ‘securely’ reference the certifications and keys needed to access certain aspects of the AWS cloud. If you remember back to when I told you to create a user for the bucket? You need that now. Find the key pair associated with the user. Using the values from that document, create a JSON to hold the S3 user account key pairs:
{ "accessKeyId":<string>, "secretAccessKey":<string>, "region":<AWS Region:string> }
Save this JSON nearby.
Writing our script
Ensure you have included the AWS SDK and declare the path to your JSON with the following method in your index.js:
AWS.config.loadFromPath(‘./<filename>.json’);
This keeps the key out of the source JavaScript, making it more secure.
Once we begin writing our script, we’re going to ensure that our code has a handler function declaration for ‘Take a Photo,’ one of the utterances we declared will trigger the skill. I set mine to respond with a random compliment. Once the picture is taken it uploads it to my S3 bucket.
A Lambda operation is tied to only one handler method. The Alexa SDK gives us the ability to create multiple responses from our handler by referencing other methods associated with each utterance. These referenced methods will be declared in an object ‘handler’ which will then be passed to the Alexa handler instance.
We defined an ‘Unhandled’ method because we only want one intent at the moment. We want that to execute the moment our Alexa recognizes we said an utterance associated with our skill. So we tell Alexa to emit the ‘GetPhoto’ event, which will execute an IoT publication.
const handlers = { ... 'Unhandled': function () { this.emit('GetPhoto'); }, ... }; exports.handler = function(event, context) { const alexa = Alexa.handler(event, context); alexa.APP_ID = APP_ID; alexa.resources = languageStrings; alexa.registerHandlers(handlers); alexa.execute(); };
Above you may notice that we’re also pointing the Alexa skill to a language dictionary. This is crucial for production. Alexa apps should be able to scale to many languages. Above, the ‘t’ method will return an entry from said dictionary when provided a key string.
In order for our ‘GetPhoto’ method to operate in conjunction with AWS IoT, it will need to provide some information from us.
... let IotParamsUpdate = { thingName: <Your-things-name-in-AWS-IOT>, payload: JSON.stringify("timestamp":moment().format()) }; …
We provided a very basic payload and a string to represent ‘when’ the photo was taken. This will give us a unique key that we can use to identify our photo in S3. The above is then wrapped in our previously mentioned ‘GetPhoto’ method:
GetPhoto(event,context) { // Use this.t()to get corresponding language data const Compliments = this.t('OTHER'); const ComplimentIndex = Math.floor( Math.random() * Compliments.length ); const randomCompliment = Compliments[ComplimentIndex]; // By default we assume it failed var speechOutput = "Sorry I could not connect to your camera"; let payloadObj = { "timestamp":moment().format() }; //Prepare the parameters of the update call, discussed above. let IotParamsUpdate = { thingName: <Your-things-name-in-AWS-IOT>, payload: JSON.stringify("timestamp":moment().format()) }; // Publish to topic iotData.publish({ topic:'action_take_photo', payload:JSON.stringify(payloadObj), qos:0 },(err,data)=>{ console.log('After Publish: ',err,data); if(!err){ speechOutput = randomCompliment; } // Tell Alexa to respond with speech and define what... // shows up through the alexa app on your phone or whatever. this.emit( ':tellWithCard', speechOutput, this.t('SKILL_NAME'), randomCompliment+this.t('GET_PHOTO_MESSAGE') ); }); }
A few things are happening above:
- We grab a random message from a bound dictionary.
- We set a default message in case of failure.
- We define our IoT payload.
- We declare ‘what’ IoT topic we will publish to.
- We publish our IoT payload to said topic.
- We emit a response for the Alexa device, providing feedback.
Once again it is important to realize that publishing to the IoT topic is what triggers the camera to take a photo. The device is publishing to a topic shared by all AWS IoT devices. The ‘origin’ of the message is not shared. In other words, the device publishes to a topic. It doesn’t ‘own’ the topic. Any device can push to any topic, anytime.
Because of it’s compact payload, MQTT messages can be sent out from microcontrollers connected to a network, wired or wireless. This makes it pretty powerful. Your script could do more than just take a picture. It could set off a chain of events affecting myriad things.
Zip it up
Once your script is complete, in the scripts directory, select your node_modules folder and script file and archive them into a zip. It’s important not to nest any of the files in a directory. Zip the items up.
Upload this to a new Lambda method. Lambda will look for a index.js file within your archive and the produced ARN endpoint will trigger that script.
Finishing the Alexa Skill
Your Lambda console should provide you with an ARN for this Lambda method. Copy the ARN of your completed Lambda method and log back into http://developer.amazon.com and continue to save your Alexa skill. We left off at Configuration, the endpoint tab. Paste your ARN into the North America box option for pointing to Lambda.
After testing it, save it.
Creating a Camera Instance
On the Raspberry Pi I utilize a RaspiCam. There’s a node module for this device called ‘raspicam.’ Its library has methods and events that can trigger callbacks for events like ‘start’,’stop,’ and ‘read’.
npm install raspicam --save
The package is easy to use. Below we make a camera and have it take a picture.
var Raspicam = require(‘raspicam’); var camera = new RaspiCam({ mode: "photo", output: <WhereToSave:PATH>, encoding: 'jpg', timeout: 0 }); camera.start();
If you run this code with the camera module attached to the Raspberry, you’ll see the camera start up and take a picture.
Triggering the Camera with AWS IoT
For the sake of familiarizing ourselves with the IoT, we’ll use the AWSSDKIotDevice node module.
We need to declare our device certificates and keys as references to external files. We won’t need a JSON for this, as we’ll use Objects (cert and key files) rather than raw values (wrapped in a JSON object).
var cameraDevice = IotDevice.device({ keyPath:path/to/<private.pem.key>, certPath:path/to/<certificate.pem.crt>, caPath:path/to/<root.pem>, clientId:<string>, host:<IOT Endpoint:string>, regions:<AWS Region:string> });
The event listeners are triggered by our IoT device object on AWS IoT. Lambda triggers the IoT device on AWS via the Alexa script we developed for our Echo.
cameraDevice.on('connect',function(){}); cameraDevice.on('message',function(topic,payload){});
The above two listeners will listen for the event from the Alexa-Lambda app and trigger the camera’s methods. fter subscribing to that topic on initialization, we also published to a topic called ‘init_started’. The purpose of this is to figuratively wake up the IoT service.
cameraDevice.on('connect',function(){ cameraDevice.subscribe('action_take_photo'); cameraDevice.publish('init_started',JSON.stringify({ init:true })); });
Topics are the individual ‘streams’ of data we listen to. We are sending out a simple piece of data, a binary on/off. If the topic gets a message, this method will trigger our camera.
cameraDevice.on('message',function(topic,payload){ console.log('Topic :',topic, 'Payload: ',payload.toString()); if(topic === 'action_take_photo'){ camera.start(); } });
Along with taking our picture and saving our picture locally we also want the picture to be on the cloud, so we can access it immediately on S3. So we will ‘upload’ it.
function upload(){ s3Params = { Key:_filename, Body:fs.createReadStream(path.join(__dirname,'/photos/'+_filename)), Bucket:_bucket, ACL:'public-read-write' } s3.upload(s3Params,(err,data)=>{ if(err){ console.log('Upload FAILED',err); }else{ console.log('Upload WORKED'); } }); }
Above we send the image at the designated path to S3 for storage. Below, we call this upload method after the camera has executed it’s ‘Read’ event.
camera.on('read', function(){ ... upload(); });
An S3 operation often needs parameters to perform. Most are self explanatory or easy to find. However, some are more robust:
... Body:fs.createReadStream( path.join(__dirname,'/photos/'+_filename) ); …
The body parameter benefits from using the PATH module here. We need to ensure the script knows where it is in relation to everything else so that it can reference our saved photo.
If all went well, after enabling your script on the Raspberry, when you tell your Echo device to ‘Take a Photo’ it should execute your scripts on Lambda, which will reach out to IoT and the Raspberry. You should now be able to tell your Echo to execute ‘Take a photo’ and you should see it take a photo, and then send that photo to your S3 bucket.