Uttering Alexa voice commands to turn your ESP8266 connected device on or off is cool. There is plenty of information readily available explaining how to make that happen.
But what a custom command?
Like having Alexa tell you something unique. Using data gathered by reading your specific IoT device sensors?
Such as ESP8266 measured values that change over time.
For example, temperature or other weather sensors.
Having the ability to interact verbally with your IoT device in this manner opens up a wide range of possibilities. Yet, as I discovered, information on this topic was more challenging to find with a google search.
So here is how I did it.
And so can you.
The Problem
What is needed is a method of converting your verbal request into a command that can be sent to your ESP8266 (or other IoT device) to get the requested information, and to return the information to Alexa in a format that she can read back to you.
The solution presented here uses the Amazon Web Services (AWS) to do all the voice interfacing with Alexa, Things Speak as a repository for the ESP8266 sensor data, and a VPS CRON script to move the sensor data from the ESP8266 to Things Speak.
You might wonder why a VPS and Thing Speak are used. Why not communicate directly with the ESP8266 from the AWS Lambda Function?
Truth is, you can cut out these intermediate steps. But for security, I have added this layer to hide my IoT credentials within my heavily fortified Virtual Private Server (VPS). But do not be concerned, as you will see, this does not make the system overly complicated.
System Diagram
Test Scenario
We start with a voice command: “Alexa, ask ESP8266-12 for the indoor temperature”
Alexa responds with: “The indoor temperature is 73.2 degrees”
So what just happened?
Since we are dealing with the Amazon Echo device, Amazon lingo is needed (bold).
The voice command was received by the Amazon Echo device and recognized as an Alexa skill.
Invocation of the skill triggered the Lambda function which, in-turn, sent a request to ThingSpeak for the current ESP8266 sensor readings,
The readings returned were passed back to the Alexa skill which recites the values of the temperature sensors.
Implementation
We need three components to make this system function as intended.
Data Source (ESP8266 Sensor Data)
- From ThingSpeak Channel or
- Directly from ESP8266
Amazon Skill to convert your voice to a command to retrieve the requested information
Amazon Lambda function to receive the Skill request, query for the requested information and return a reply to the Amazon Skill
Let’s do it…
You will need to setup two accounts with Amazon, If you do not already have these accounts, create them now. No worries, they are FREE:
Create Amazon Developer Account (For Alexa Skill)
Start by pointing your browser to:
https://developer.amazon.com/
Select “Sign In” located on the page’s top-right header:
Next, select the “Create your Amazon Developer Account” button
Follow the instructions to create your free Amazon Developer Account
Create free AWS account (For Lambda Function)
Start by pointing your browser to:
https://portal.aws.amazon.com/billing/signup#/start
Follow the instructions to create your free AWS (Amazon Web Services) Account
Creating the Alexa Skill
Sign in to https://developer.amazon.com/ and go to the Developer Console.
Select the Alexa tab.
Select “Get Started” Alexa Skills Kit.
Select “Add a New Skill”
Note: The rest of the skill creation can be customized for your needs. It is suggested that you follow along with this guide for your first skill, and customize after getting this example to work. For this example, my publicly shared ThingSpeak Channel is used for the dynamic data source
For the first tab (Skill information), enter:
Name: ESP8266
Invocation Name: e s p eighty two sixty six dash twelve
Then click “Save” at the bottom.
For the Interaction Model tab, enter:
Intent Schema (copy/paste):
- {
- "intents": [
- {
- "slots": [
- {
- "name": "Sensor",
- "type": "ESP_SENSORS"
- }
- ],
- "intent": "GetEspInfoIntent"
- },
- {
- "intent": "AMAZON.HelpIntent"
- },
- {
- "intent": "AMAZON.StopIntent"
- },
- {
- "intent": "AMAZON.CancelIntent"
- }
- ]
- }
Custom Slot Types:
Enter Type: ESP_SENSORS
Enter Values:
every
indoor
outdoor
attic
Then click “Add” below the “Enter Values” box..
Now click “Save” at the bottom.
Sample Utterances: GetEspInfoIntent for {Sensor} temperature
Click “Save” at the bottom.
Then click “Next” at the bottom.
Creating the Lambda Function
Sign in to https://portal.aws.amazon.com
Make sure you are on the URL https://console.aws.amazon.com
From the Console, select Services > Compute > Lambda
Click “Create Function”
Click “Author from scratch”.
Enter the following Basic Information:
Name: ESP8266V2
Role: Create new role from templates
RoleName: myEspRole
Policy Templates: CloudFormation stack read-only permissions
Then click “Create Function”
Select Runtime: “Node.js 4.3”
Then paste the following code in the index.js field:
- /**
- Copyright 2014-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.
- */
- /*
- This file has been modified under the terms of the above license October 2017
- */
- /**
- * This code is dependant on data retreived from a ThingsSpeak channel
- * It is a Lambda function for handling Alexa Skill requests.
- *
- * Examples:
- * One-shot model:
- * User: "Alexa, ask ESP8266 for temperature readings"
- * Alexa: "Indoor temperature is 75.2 degrees and outdoor temperature is 86.2 degrees"
- */
- /**
- * App ID for the skill
- */
- var APP_ID = undefined; //OPTIONAL: replace with "amzn1.echo-sdk-ams.app.[your-unique-value-here]";
- //var APP_ID = "amzn1.ask.skill.018df58b-9e42-4e0f-927c-fd06ba2edb5b"; //OPTIONAL: replace with "amzn1.echo-sdk-ams.app.[your-unique-value-here]";
- /**
- * The AlexaSkill prototype and helper functions
- */
- var AlexaSkill = require('./AlexaSkill');
- var https = require('https');
- //ThingSpeak Data Access
- var channel_id = 33251; //ThingSpeak Channel ID
- var speak_key = '9VGQ4X79MQJC90RD'; //ThingSpeak Channel Read Key
- var p_at,p_ou,p_in; //Sensor Readings
- // Create URL to retrieve latest temperature reading from my ThingsSpeak channel (JSON format)
- var url = 'https://api.thingspeak.com/channels/' + channel_id + '/feed/last.json?api_key=' + speak_key;
- /**
- * ESP8266 is a child of AlexaSkill.
- * To read more about inheritance in JavaScript, see the link below.
- *
- * @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Introduction_to_Object-Oriented_JavaScript#Inheritance
- */
- var Esp8266 = function () {
- AlexaSkill.call(this, APP_ID);
- };
- // Extend AlexaSkill
- Esp8266.prototype = Object.create(AlexaSkill.prototype);
- Esp8266.prototype.constructor = Esp8266;
- Esp8266.prototype.eventHandlers.onSessionStarted = function (sessionStartedRequest, session) {
- //console.log("onSessionStarted requestId: " + sessionStartedRequest.requestId + ", sessionId: " + session.sessionId);
- // any initialization logic goes here
- };
- Esp8266.prototype.eventHandlers.onLaunch = function (launchRequest, session, response) {
- //console.log("onLaunch requestId: " + launchRequest.requestId + ", sessionId: " + session.sessionId);
- handleEsp8266Request(response);
- };
- /**
- * Overridden to show that a subclass can override this function to teardown session state.
- */
- Esp8266.prototype.eventHandlers.onSessionEnded = function (sessionEndedRequest, session) {
- //console.log("onSessionEnded requestId: " + sessionEndedRequest.requestId + ", sessionId: " + session.sessionId);
- // any cleanup logic goes here
- };
- Esp8266.prototype.intentHandlers = {
- "GetEspInfoIntent": function (intent, session, response) {
- var speechOutput;
- var cardTitle;
- var sensorSlot = intent.slots.Sensor,
- sensorName = "every";
- if (sensorSlot && sensorSlot.value){
- sensorName = sensorSlot.value.toLowerCase();
- }
- switch(sensorName) {
- default:
- case "every":
- speechOutput = "Indoor temperature is " + p_in + " degrees" +
- " and the Outdoor temperature is " + p_ou + " degrees and " +
- "the Attic temperature is " + p_at + " degrees";
- cardTitle = "Temperature Readings";
- break;
- case "indoor":
- speechOutput = "Indoor temperature is " + p_in + " degrees";
- cardTitle = "Indoor Temperature Reading";
- break;
- case "outdoor":
- speechOutput = "Outdoor temperature is " + p_ou + " degrees";
- cardTitle = "Outdoor Temperature Reading";
- break;
- case "attic":
- speechOutput = "Attic temperature is " + p_at + " degrees";
- cardTitle = "Attic Temperature Reading";
- break;
- }
- response.tellWithCard(speechOutput, cardTitle, speechOutput);
- },
- "AMAZON.HelpIntent": function (intent, session, response) {
- response.ask("You can say ask ESP8266 for indoor temperature, or, you can say exit... What can I help you with?", "What can I help you with?");
- },
- "AMAZON.StopIntent": function (intent, session, response) {
- var speechOutput = "Goodbye";
- response.tell(speechOutput);
- },
- "AMAZON.CancelIntent": function (intent, session, response) {
- var speechOutput = "Goodbye";
- response.tell(speechOutput);
- }
- };
- /* -------------------------------------------------------------
- * Get all sensors temperatures and return speech to the user.
- * -------------------------------------------------------------
- */
- function handleEsp8266Request(response) {
- // Create speech output
- var speechOutput = "Greetings, I did not understand what you want. How may I serve you?";
- var cardTitle = "Initial Request";
- response.tellWithCard(speechOutput, cardTitle, speechOutput);
- }
- // Create the handler that responds to the Alexa Request.
- exports.handler = function (event, context) {
- https.get(url, function(res) {
- // Get latest sensor data from home IoT SoC
- res.on('data', function(d) {
- p_in = JSON.parse(d).field1;
- p_ou = JSON.parse(d).field2;
- p_at = JSON.parse(d).field3;
- // Create an instance of the SpaceGeek skill.
- var Esp8266_skill = new Esp8266();
- Esp8266_skill.execute(event, context);
- });
- res.on('end', function() {
- });
- res.on('error', function(e) {
- context.fail("Got error: " + e.message);
- });
- });
- };
Now save this function (Click “Save and test”)
Exit out of the “Test” screen and click on the “Triggers” tab
Then click “Add Trigger” and click in the dotted box:
Select “Alexa Skills Kit”:
And click “Submit”.
Now Lets link the Lambda Function to the Alexa skill:
Return to the Alexa Skill developed earlier:
Click Alexa Skills Kit > Get Started
Click on the Name of the Skill created earlier
Click “Configuration”
Click the Endpoint “AWS Lambda ARN” radio button
Paste the ARN from the paste buffer in the “Default” box
Click bottom “Save” button
That’s it!
Now let’s test it…
The next tab on the Alexa Skills page is called “Test”. Select it and scroll down to the “Service Simulator”
Using the “Text” table, simply enter one of the following utterances, click “Ask ESP8266” and verify you get an appropriate “Service Response”:
every temperature
indoor temperature
outdoor temperature
attic temperature
For example, the utterance “attic temperature” should produce the following Service Response in JSON format:
- {
- "version": "1.0",
- "response": {
- "outputSpeech": {
- "text": "Attic temperature is 49.2 degrees",
- "type": "PlainText"
- },
- "card": {
- "content": "Attic temperature is 49.2 degrees",
- "title": "Attic Temperature Reading"
- },
- "speechletResponse": {
- "outputSpeech": {
- "text": "Attic temperature is 49.2 degrees"
- },
- "card": {
- "content": "Attic temperature is 49.2 degrees",
- "title": "Attic Temperature Reading"
- },
- "shouldEndSession": true
- }
- },
- "sessionAttributes": {}
- }
The actual temperature readings will vary, depending on the current temperature in my attic.
You should now be able to invoke this skill using your Amazon Echo using voice commands.
You can verify that this new skill is enabled on your Alexa echo at https://alexa.amazon.com
Select “Skills” and then “Your Skills”. This new skill should be listed and enabled.
The rest of the system has been covered in previous blog posts:
Things Speak Channel:
- Write values
- Read Values
Refer to my ThingSpeak Channel
Virtual Private Server (VPS):
- Read from ESP8266
- Write to Things Speak
Uses a Linux CRON script scheduled to run at the start of every hour.
See this post: PHP Script to move ESP8266 data
ESP8266 Sensor data source:
- Read Sensors Continuously
- Send Sensor Values on request as JSON string
ESP8266 Temperature Sensor Project
In Conclusion
As you can see, you can make the information collected by your IoT device, such as an ESP8266 accessible via audio commands/responses using the Amazon Echo device. The commands and responses can be customized to the exact words you wish to use. This opens up infinite possibilities.
Hope you find this information useful and fun to explore.
I'm following all the steps what you have mentioned. but when ever i run the lambda function I got the following error. Kindly help for this issue
Response:
{
"errorMessage": "Cannot set property 'onSessionStarted' of undefined",
"errorType": "TypeError",
"stackTrace": [
"Module._compile (module.js:409:26)",
"Object.Module._extensions..js (module.js:416:10)",
"Module.load (module.js:343:32)",
"Function.Module._load (module.js:300:12)",
"Module.require (module.js:353:17)",
"require (internal/module.js:12:17)"
]
}
Request ID:
"b218c5f0-7907-11e8-99bf-05ecde589166"
Function Logs:
START RequestId: b218c5f0-7907-11e8-99bf-05ecde589166 Version: $LATEST
module initialization error: TypeError
at Object.<anonymous> (/var/task/index.js:49:50)
at Module._compile (module.js:409:26)
at Object.Module._extensions..js (module.js:416:10)
at Module.load (module.js:343:32)
at Function.Module._load (module.js:300:12)
at Module.require (module.js:353:17)
at require (internal/module.js:12:17)
END RequestId: b218c5f0-7907-11e8-99bf-05ecde589166
REPORT RequestId: b218c5f0-7907-11e8-99bf-05ecde589166 Duration: 152.76 ms Billed Duration: 200 ms Memory Size: 128 MB Max Memory Used: 31 MB
module initialization error
TypeError
I just tested the Lambda function and found it to be working correctly. The error you have listed suggest there is a problem with the APP_ID. Please note that I have left this value undefined for this demo project:
var APP_ID = undefined;
Here is the response received from the test. You should see something similar:
Response:
{
"version": "1.0",
"response": {
"outputSpeech": {
"type": "PlainText",
"text": "Indoor temperature is 79.2 degrees"
},
"shouldEndSession": true,
"card": {
"type": "Simple",
"title": "Indoor Temperature Reading",
"content": "Indoor temperature is 79.2 degrees"
}
},
"sessionAttributes": {}
}
Request ID:
"83ecf5a5-c972-4984-ba92-ff3b07c9a0d2"
Function Logs:
START RequestId: 83ecf5a5-c972-4984-ba92-ff3b07c9a0d2 Version: $LATEST
2018-06-27T22:12:17.283Z 83ecf5a5-c972-4984-ba92-ff3b07c9a0d2 session applicationId: amzn1.ask.skill.5d4b0043-b8fb-4949-9480-af86709ec1db
2018-06-27T22:12:17.308Z 83ecf5a5-c972-4984-ba92-ff3b07c9a0d2 dispatch intent = GetEspInfoIntent
END RequestId: 83ecf5a5-c972-4984-ba92-ff3b07c9a0d2
REPORT RequestId: 83ecf5a5-c972-4984-ba92-ff3b07c9a0d2 Duration: 272.61 ms Billed Duration: 300 ms Memory Size: 128 MB Max Memory Used: 22 MB
This is my test configuration (requests indoor temperature):
{
"session": {
"new": true,
"sessionId": "SessionId.25ebb9d8-b285-491c-9952-7bbb3cb0045d",
"application": {
"applicationId": "amzn1.ask.skill.5d4b0043-b8fb-4949-9480-af86709ec1db"
},
"attributes": {},
"user": {
"userId": "amzn1.ask.account.AG3NZN2RSJPN2UPG7TJTMK7LP4DD3LKSRDGIIEUQTVT5PXWSPT4OSOPZGCMOUJPTX4RBSA6SUXD6W7UAEXHVRUEMCEKBNVGUVCLEWDN26DZVDPEBSAY2IGFWES6A2KSKF4PXEFN6GELGCEEIPHVAVADUMVFPCPBQXSJ3SAHEJUAQJNJLD3IBHMD7MHDNRDTMYCGI2IMRMFTXKGQ"
}
},
"request": {
"type": "IntentRequest",
"requestId": "EdwRequestId.b31fffe1-59c0-450a-88f0-349590dfb8c2",
"intent": {
"name": "GetEspInfoIntent",
"slots": {
"Sensor": {
"name": "Sensor",
"value": "indoor"
}
}
},
"locale": "en-US",
"timestamp": "2017-11-18T15:47:14Z"
},
"context": {
"AudioPlayer": {
"playerActivity": "IDLE"
},
"System": {
"application": {
"applicationId": "amzn1.ask.skill.5d4b0043-b8fb-4949-9480-af86709ec1db"
},
"user": {
"userId": "amzn1.ask.account.AG3NZN2RSJPN2UPG7TJTMK7LP4DD3LKSRDGIIEUQTVT5PXWSPT4OSOPZGCMOUJPTX4RBSA6SUXD6W7UAEXHVRUEMCEKBNVGUVCLEWDN26DZVDPEBSAY2IGFWES6A2KSKF4PXEFN6GELGCEEIPHVAVADUMVFPCPBQXSJ3SAHEJUAQJNJLD3IBHMD7MHDNRDTMYCGI2IMRMFTXKGQ"
},
"device": {
"supportedInterfaces": {}
}
}
},
"version": "1.0"
}
I am getting following error. looks like There is no module called AlexaSkill.
Response:
{
"errorMessage": "Cannot find module './AlexaSkill'",
"errorType": "Error",
"stackTrace": [
"Function.Module._load (module.js:276:25)",
"Module.require (module.js:353:17)",
"require (internal/module.js:12:17)",
"Object.<anonymous> (/var/task/index.js:28:18)",
"Module._compile (module.js:409:26)",
"Object.Module._extensions..js (module.js:416:10)",
"Module.load (module.js:343:32)",
"Function.Module._load (module.js:300:12)",
"Module.require (module.js:353:17)"
]
}
Request ID:
"6747ff5a-802f-11e8-abb1-97c597a1a034"
Function Logs:
START RequestId: 6747ff5a-802f-11e8-abb1-97c597a1a034 Version: $LATEST
Unable to import module 'index': Error
at Function.Module._resolveFilename (module.js:325:15)
at Function.Module._load (module.js:276:25)
at Module.require (module.js:353:17)
at require (internal/module.js:12:17)
at Object.<anonymous> (/var/task/index.js:28:18)
at Module._compile (module.js:409:26)
at Object.Module._extensions..js (module.js:416:10)
at Module.load (module.js:343:32)
at Function.Module._load (module.js:300:12)
at Module.require (module.js:353:17)
END RequestId: 6747ff5a-802f-11e8-abb1-97c597a1a034
REPORT RequestId: 6747ff5a-802f-11e8-abb1-97c597a1a034 Duration: 79.74 ms Billed Duration: 100 ms Memory Size: 128 MB Max Memory Used: 19 MB
if the error you have noted is from the Lambda function, please refer to the comment reply from 2018/06/27 at 3:19 pm
otherwise, more information is needed to understand and hopefully resolve the issue you are facing.
What were you running when the error occurred? At what step in your Alexa Skill development did it occur?
Hi sir,
have difficulty completing the skill.
Where should we put the hutterance …
Sample Utterances: GetEspInfoIntent for {Sensor} temperature
If you can kindly tell me step by step.
I see WordPress butchered my post around the utterance section. I will be unavailable to review and update the guidance until later December. If you need guidance now, your best resources are the AWS websites noted in the article.
Hi,
Nodejs4.3 is obsolete, but finally , I succeeded.
I found an AlexaSkill.js and made some adjustements and now, it's working.
I