Getting Started with Amazon AWS Development
The first thing you need to do is get yourself set up with an Amazon developers account and an AWS account. You should be able to use the same account for both, but make sure you familiarize yourself with both of these areas:
Amazon Developer
Amazon Web Services (AWS)
Once you've signed up and have browsed around the sites for a bit, you're ready to set up your first Amazon Echo / Alexa skill.
Creating an Alexa (Amazon Echo) "Skill"
The first thing you'll need to build is the Amazon Echo's "skill," which defines the way you interact with the device and how the interactions with it should be processed.
- Log into the Amazon Developer portal, and click on the "Apps and Services" tab at the top, followed by the "Alexa" sub-tab.
- You'll be presented with two options - the Alexa Skills Kit, and the Alexa Voice Service. You'll want to use the skills kit, which lets you add new skills to your Alexa. The voice service is for use in embedding voice recognition in other devices.
- You'll now be presented with a list of any skills you've already created, and a button to "Add a New Skill." Click that button.
- Fill in the basic information about your new skill. The name can be anything you want. The invocation name needs to be the phonetically written out name you would like to verbalize when you address your Alexa. I would like to be able to say things like "Tell EV3 to move forward." But you can see in my example below, I couldn't just make my invocation name "EV3." Instead, I had to write "e. v. three."
- For now we are going to leave the Endpoint blank. You have two choices of endpoints - a web service URL, or an Amazon Lambda expression. Originally I had intended on using a web service, but I changed my mind fairly quickly. With Lambda expressions, all you need to do to wire it up is copy and paste the ARN, and make sure your expression has the right permissions. To use a web service, you'd have to write all the session and security handlers, plus you'd have to host the service. It just wasn't worth the effort.
- The Interaction Model is where things start getting interesting. Here you will define the grammar for interacting with your Alexa skill. The important concepts to understand here are Intents, Slots, and Utterances.
- Intents: An intent is action you'd like to perform or the interaction you'd like to have with the Alexa. You can think of these a lot like a method or function. Slots: Slots are simply arguments to the intent / method / function. So if you had an intent called "AddNumbers", you might have slots for each of the numbers to add up.
- Utterances: These are the actual phrases someone may vocalize to interact with your skill. Again, using the AddNumbers example, you may have an utterance like: "AddNumbers add {x} and {y} together."
- For the EV3 application, I really only have two intents - "Move" and "Stop." When moving, the slots / variables are just an Action and a Value. The Action uses a custom slot type called `LIST_OF_ACTIONS`. This lets me pre-define the allowable values to go into that slot (like "Forward", "Left", and "Turn"). The value has a data type of any number. This way if I want to go forward a certain distance, I can capture the desired distance in the Value slot. See the github code for the exact intent schema, custom slot definition, and sample utterances that I used.
Creating Your Lambda Function
Now that you have your skill stubbed out, it's time to put in some backing code. Again, you could use a REST web service to handle requests from the Alexa, but there weren't any good skeleton services available out there when I wrote this initially. I didn't want to mess around with the AWS security model, among other things, so I opted for creating a Lambda function.
Lambda functions are simply hosted compute resources. You put your code into a Lambda function, and Amazon figures out the rest for you. Rather than hosting virtualized infrastructure, Lambda lets the developer just supply the logic to be hosted.
To create your lambda function, log in to your AWS account and go in to the Lambda function area. When you click to add a new function, you have the option of creating a function based on a pre-defined "blueprint." I'd recommend using one of the Alexa blueprints.
index.js code here
var snsArn = "arn:aws:sns:us-east-1:825429109316:FromAlexa";
var AWS = require('aws-sdk');
AWS.config.region = 'us-east-1';
exports.handler = function (event, context) {
var contextMessage;
var sessionMessage;
try {
console.log("event.session.application.applicationId=" + event.session.application.applicationId);
if (event.session.new) {
onSessionStarted({requestId: event.request.requestId}, event.session);
}
if (event.request.type === "LaunchRequest") {
onLaunch(event.request,
event.session,
function callback(sessionAttributes, speechletResponse) {
contextMessage = buildResponse(sessionAttributes, speechletResponse);
sessionMessage = sessionAttributes;
});
} else if (event.request.type === "IntentRequest") {
onIntent(event.request,
event.session,
function callback(sessionAttributes, speechletResponse) {
contextMessage = buildResponse(sessionAttributes, speechletResponse);
sessionMessage = sessionAttributes;
});
} else if (event.request.type === "SessionEndedRequest") {
onSessionEnded(event.request, event.session);
}
} catch (e) {
context.fail("Exception: " + e);
}
var eventText = JSON.stringify(sessionMessage, null, 2);
console.log("Received event:", eventText);
var sns = new AWS.SNS();
var params = {
Message: eventText,
Subject: "EV3 Alexa Command",
TopicArn: snsArn
};
console.log('context message: ');
console.log(contextMessage);
sns.publish(params, function (err, data) {
console.log('published data: ');
console.log(data);
context.succeed(contextMessage);
});
var timeout = new Date().getTime();
while ((new Date().getTime()) - timeout > 500) {}
};
/**
* Called when the session starts.
*/
function onSessionStarted(sessionStartedRequest, session) {
console.log("onSessionStarted requestId=" + sessionStartedRequest.requestId +
", sessionId=" + session.sessionId);
}
/**
* Called when the user launches the skill without specifying what they want.
*/
function onLaunch(launchRequest, session, callback) {
console.log("onLaunch requestId=" + launchRequest.requestId +
", sessionId=" + session.sessionId);
getWelcomeResponse(callback);
}
/**
* Called when the user specifies an intent for this skill.
*/
function onIntent(intentRequest, session, callback) {
console.log("onIntent requestId=" + intentRequest.requestId +
", sessionId=" + session.sessionId);
var intent = intentRequest.intent,
intentName = intentRequest.intent.name;
if ("MoveIntent" === intentName) {
setMoveSession(intent, session, callback);
} else if ("StopIntent" === intentName) {
endMoveSession(intent, session, callback);
} else if ("AMAZON.HelpIntent" === intentName) {
getWelcomeResponse(callback);
} else {
throw "Invalid intent";
}
}
/**
* Called when the user ends the session.
* Is not called when the skill returns shouldEndSession=true.
*/
function onSessionEnded(sessionEndedRequest, session) {
console.log("onSessionEnded requestId=" + sessionEndedRequest.requestId +
", sessionId=" + session.sessionId);
}
// --------------- Functions that control the skill's behavior -----------------------
function getWelcomeResponse(callback) {
var sessionAttributes = {};
var cardTitle = "Welcome";
var speechOutput = "Welcome to the Alexa E. V. Three Command Tool. " +
"Please tell me how you want me to control your E. V. Three, by saying things" +
"like, forward 10, backward 5, left, right, turn -45, go, stop, or reverse.";
var repromptText = "Please tell me how you want me to control your E. V. Three, by saying things" +
"like, forward 10, backward 5, left, right, turn -45, go, stop, or reverse.";
var shouldEndSession = false;
callback(sessionAttributes,
buildSpeechletResponse(cardTitle, speechOutput, repromptText, shouldEndSession));
}
/**
* Sets the color in the session and prepares the speech to reply to the user.
*/
function setMoveSession(intent, session, callback) {
var cardTitle = "Move";
var actionSlot = intent.slots.Action;
var valueSlot = intent.slots.Value;
var repromptText = "";
var sessionAttributes = {};
var shouldEndSession = false;
var speechOutput = "";
if (actionSlot) {
var action = actionSlot.value;
var value = null;
if (valueSlot) { value = valueSlot.value; }
sessionAttributes = createActionAttributes(action, value);
speechOutput = action;
repromptText = "";
} else {
speechOutput = "I'm not sure what action you want me to have your E. V. Three perform.";
repromptText = "I'm not sure what action you want me to have your E. V. Three perform." +
"Please tell me how you want me to control your E.V.Three, by saying things" +
"like, forward 10, backward 5, left, right, turn -45, go, stop, or reverse.";
}
callback(sessionAttributes,
buildSpeechletResponse(cardTitle, speechOutput, repromptText, shouldEndSession));
}
function createActionAttributes(action, value) {
return {
action: action,
value: value
};
}
function endMoveSession(intent, session, callback) {
var repromptText = null;
var sessionAttributes = {};
var shouldEndSession = true;
var speechOutput = "Done controlling E. V. Three.";
// Setting repromptText to null signifies that we do not want to reprompt the user.
// If the user does not respond or says something that is not understood, the session
// will end.
callback(sessionAttributes,
buildSpeechletResponse(intent.name, speechOutput, repromptText, shouldEndSession));
}
// --------------- Helpers that build all of the responses -----------------------
function buildSpeechletResponse(title, output, repromptText, shouldEndSession) {
return {
outputSpeech: {
type: "PlainText",
text: output
},
card: {
type: "Simple",
title: "EV3 - " + title,
content: output
},
reprompt: {
outputSpeech: {
type: "PlainText",
text: repromptText
}
},
shouldEndSession: shouldEndSession
};
}
function buildResponse(sessionAttributes, speechletResponse) {
return {
version: "1.0",
sessionAttributes: sessionAttributes,
response: speechletResponse
};
}
Connecting your EV3 via bluetooth
Connecting to an EV3 via bluetooth was actually harder than I'd expected. Fundamentally the process is very easy - but the stars need to be aligned just right for it to all work perfectly.
The bluetooth protocol used is the common bluetooth-as-a-serial connection paradigm. Once you've got the EV3 properly paired with your machine (the hard part), then the EV3 is exposed to your applications as a typical serial port. In the Windows world, this means you'll communicate over COM1/2/3, or whatever. In the Linux and OSX worlds, you'll find the EV3 exposed as a /dev/tty.YourDevice device. Setting this up for the console app is a breeze. Just edit the App.config file and set the Ev3Port to be whatever serial port your machine assigned:
<add key="Ev3Port" value="COM1" /> <!-- or for Linux/OSX, "/dev/tty.YourPortName" -->
EV3 Gadget Implementation
The Gadget implementation is simple. The logic is to parse in command that is received by the bluetooh and using an if statement to enable the correct movement
import time
import logging
import ev3bluetooth
from random import randint
from common.robot import Robot
# default sleep timeout in sec
DEFAULT_SLEEP_TIMEOUT_IN_SEC = 0.1
# default speed
DEFAULT_SPEED = 200
# default threshold distance
DEFAULT_THRESHOLD_DISTANCE = 150
# config logging
logging.basicConfig(format='%(levelname)s:%(message)s', level=logging.DEBUG)
def main():
logging.debug('Run robot, run!')
robot = Robot()
ev3bluetooth = BluetoothConnect()
robot.set_speed(DEFAULT_SPEED)
robot.forward()
try:
while True:
message = ev3bluetooth.message()
if message == "left"
robot.turn(45)
time.sleep(2)
if message == "right"
robot.forward(-45)
time.sleep(2)
if message == "forward"
robot.forward(-45)
time.sleep(5)
if message == "backward"
robot.forward(-45)
time.sleep(5)
# doing a cleanup action just before program ends
# handle ctr+c and system exit
except (KeyboardInterrupt, SystemExit) as e:
logging.exception("message")
# handle exceptions
except Exception as e:
logging.exception("message")
finally:
teardown(robot)
logging.shutdown()
def teardown(robot):
robot.brake()
if __name__ == "__main__":
main()
My
Video
Don't
mind this Image (down).
Comments