This project aim is to integrate the Alexa voice controlled function with the EV3 Mindstrom Lego to create a voice controlled robotic cars.
Building the Robot
Building the RobotFor this project I followed the instruction provided by lego EV3 to build the robot
The instruction can be found here : https://le-www-live-s.legocdn.com/sc/media/lessons/mindstorms-ev3/building-instructions/ev3-rem-driving-base-79bebfc16bd491186ea9c9069842155e.pdf
Setting Up Alexa
Setting Up AlexaTo set up the Alexa, I first must register with the online Alexa developer console and make a new project in it. Once the a new project is made I must go over and edit the Json file in the skill as show below :
{
"interactionModel": {
"languageModel": {
"invocationName": "claw",
"intents": [
{
"name": "AMAZON.CancelIntent",
"samples": []
},
{
"name": "AMAZON.HelpIntent",
"samples": []
},
{
"name": "AMAZON.StopIntent",
"samples": []
},
{
"name": "HelloWorldIntent",
"slots": [],
"samples": [
"hello",
"how are you",
"say hi world",
"say hi",
"hi",
"say hello world",
"say hello"
]
},
{
"name": "GoIntent",
"samples": [ "Go" ]
}
{
"name": "BackIntent",
"samples": [ "Backward" ]
},
{
"name": "RightIntent",
"samples": [ "Right" ]
},
{
"name": "LeftIntent",
"samples": [ "Left" ]
},
{
"name": "TurnIntent",
"samples": [ "Turn" ]
},
],
"types": []
}
}
}
Next I modify the index.js with the my own index.js code. This is the "lambda" code that server as the back end server that connect the Alexa command to the EV3 mindstorm to controlled the robot
const Alexa = require('ask-sdk-core');
const persistenceAdapter = require('ask-sdk-s3-persistence-adapter');
const Util = require('./util');
const Common = require('./common');
const DEFAULT_PERSISTENT_ATTRIBUTES = require('./default_attributes.json')
// The namespace of the custom directive to be sent by this skill
const NAMESPACE = 'Custom.Mindstorms.Gadget';
// The name of the custom directive to be sent this skill
const NAME_CONTROL = 'control';
// function to return the endpoint associated with the EV3 robot
const getEndpointID = async function (handlerInput) {
// get the stored endpointId from the attributesManager
const attributesManager = handlerInput.attributesManager;
var endpointId = attributesManager.getSessionAttributes().endpointId || [];
// if there is no stored endpointId, query the connected endpoints and store the new endpointId
if (endpointId.length === 0) {
const request = handlerInput.requestEnvelope;
let { apiEndpoint, apiAccessToken } = request.context.System;
let apiResponse = await Util.getConnectedEndpoints(apiEndpoint, apiAccessToken);
if ((apiResponse.endpoints || []).length !== 0) {
endpointId = apiResponse.endpoints[0].endpointId || [];
Util.putSessionAttribute(handlerInput, 'endpointId', endpointId);
}
}
return endpointId;
}
const LaunchRequestHandler = {
canHandle(handlerInput) {
return Alexa.getRequestType(handlerInput.requestEnvelope) === 'LaunchRequest';
},
handle: async function (handlerInput) {
// check for a connected EV3 brick
const endpointId = getEndpointID(handlerInput);
// speak an error message to the user if there is no EV3 brick connected
if (endpointId.length === 0) {
return handlerInput.responseBuilder
.speak("I couldn't find an EV3 Brick connected to this Echo device.")
.getResponse();
}
return handlerInput.responseBuilder
.speak(`Welcome, you can start issuing commands`)
.reprompt(`Awaiting commands`)
.getResponse();
}
};
const LeftIntentHandler = {
canHandle(handlerInput) {
return Alexa.getRequestType(handlerInput.requestEnvelope) === 'IntentRequest'
&& Alexa.getIntentName(handlerInput.requestEnvelope) === 'Left';
},
handle: async function (handlerInput) {
// get the slot values from the intent request
const request = handlerInput.requestEnvelope;
const location = Alexa.getSlotValue(request, 'Left');
// get the endpoint of the EV3 robot
const endpointId = await getEndpointID(handlerInput);
console.log(`endpointId: ${JSON.stringify(endpointId)}`);
// get the persistent attributes
const attributesManager = handlerInput.attributesManager;
var s3Attributes = await attributesManager.getPersistentAttributes() || {};
if (Object.entries(s3Attributes).length === 0)
s3Attributes = DEFAULT_PERSISTENT_ATTRIBUTES;
console.log(JSON.stringify(s3Attributes));
const directive = Util.build(endpointId, NAMESPACE, NAME_CONTROL,
{
type: 'Left',
state: s3Attributes.robot.state,
location: location_fixed,
});
return handlerInput.responseBuilder
.addDirective(directive)
.getResponse();
}
};
const RightIntentHandler = {
canHandle(handlerInput) {
return Alexa.getRequestType(handlerInput.requestEnvelope) === 'IntentRequest'
&& Alexa.getIntentName(handlerInput.requestEnvelope) === 'Right';
},
handle: async function (handlerInput) {
// get the slot values from the intent request
const request = handlerInput.requestEnvelope;
const location = Alexa.getSlotValue(request, 'Right');
// get the endpoint of the EV3 robot
const endpointId = await getEndpointID(handlerInput);
console.log(`endpointId: ${JSON.stringify(endpointId)}`);
// get the persistent attributes
const attributesManager = handlerInput.attributesManager;
var s3Attributes = await attributesManager.getPersistentAttributes() || {};
if (Object.entries(s3Attributes).length === 0)
s3Attributes = DEFAULT_PERSISTENT_ATTRIBUTES;
console.log(JSON.stringify(s3Attributes));
const directive = Util.build(endpointId, NAMESPACE, NAME_CONTROL,
{
type: 'Right',
state: s3Attributes.robot.state,
location: location_fixed,
});
return handlerInput.responseBuilder
.addDirective(directive)
.getResponse();
}
};
const GoIntentHandler = {
canHandle(handlerInput) {
return Alexa.getRequestType(handlerInput.requestEnvelope) === 'IntentRequest'
&& Alexa.getIntentName(handlerInput.requestEnvelope) === 'Go';
},
handle: async function (handlerInput) {
// get the slot values from the intent request
const request = handlerInput.requestEnvelope;
const location = Alexa.getSlotValue(request, 'Go');
// get the endpoint of the EV3 robot
const endpointId = await getEndpointID(handlerInput);
console.log(`endpointId: ${JSON.stringify(endpointId)}`);
// get the persistent attributes
const attributesManager = handlerInput.attributesManager;
var s3Attributes = await attributesManager.getPersistentAttributes() || {};
if (Object.entries(s3Attributes).length === 0)
s3Attributes = DEFAULT_PERSISTENT_ATTRIBUTES;
console.log(JSON.stringify(s3Attributes));
const directive = Util.build(endpointId, NAMESPACE, NAME_CONTROL,
{
type: 'up',
state: s3Attributes.robot.state,
location: location_fixed,
});
return handlerInput.responseBuilder
.addDirective(directive)
.getResponse();
}
};
const BackIntentHandler = {
canHandle(handlerInput) {
return Alexa.getRequestType(handlerInput.requestEnvelope) === 'IntentRequest'
&& Alexa.getIntentName(handlerInput.requestEnvelope) === 'Back';
},
handle: async function (handlerInput) {
// get the slot values from the intent request
const request = handlerInput.requestEnvelope;
const location = Alexa.getSlotValue(request, 'Back');
// get the endpoint of the EV3 robot
const endpointId = await getEndpointID(handlerInput);
console.log(`endpointId: ${JSON.stringify(endpointId)}`);
// get the persistent attributes
const attributesManager = handlerInput.attributesManager;
var s3Attributes = await attributesManager.getPersistentAttributes() || {};
if (Object.entries(s3Attributes).length === 0)
s3Attributes = DEFAULT_PERSISTENT_ATTRIBUTES;
console.log(JSON.stringify(s3Attributes));
const directive = Util.build(endpointId, NAMESPACE, NAME_CONTROL,
{
type: 'down',
state: s3Attributes.robot.state,
location: location_fixed,
});
return handlerInput.responseBuilder
.addDirective(directive)
.getResponse();
}
};
const TurnIntentHandler = {
canHandle(handlerInput) {
return Alexa.getRequestType(handlerInput.requestEnvelope) === 'IntentRequest'
&& Alexa.getIntentName(handlerInput.requestEnvelope) === 'Turn';
},
handle: async function (handlerInput) {
// get the slot values from the intent request
const request = handlerInput.requestEnvelope;
const location = Alexa.getSlotValue(request, 'Turn');
// get the endpoint of the EV3 robot
const endpointId = await getEndpointID(handlerInput);
console.log(`endpointId: ${JSON.stringify(endpointId)}`);
// get the persistent attributes
const attributesManager = handlerInput.attributesManager;
var s3Attributes = await attributesManager.getPersistentAttributes() || {};
if (Object.entries(s3Attributes).length === 0)
s3Attributes = DEFAULT_PERSISTENT_ATTRIBUTES;
console.log(JSON.stringify(s3Attributes));
const directive = Util.build(endpointId, NAMESPACE, NAME_CONTROL,
{
type: 'open',
state: s3Attributes.robot.state,
location: location_fixed,
});
return handlerInput.responseBuilder
.addDirective(directive)
.getResponse();
}
};
exports.handler = Alexa.SkillBuilders.custom()
.withPersistenceAdapter(
new persistenceAdapter.S3PersistenceAdapter({ bucketName: process.env.S3_PERSISTENCE_BUCKET })
)
.addRequestHandlers(
LaunchRequestHandler,
GoIntentHandler,
BackIntentHandler,
TurnIntentHandler,
LeftIntentHandler,
RightIntentHandler,
Common.SessionEndedRequestHandler,
Common.IntentReflectorHandler, // make sure IntentReflectorHandler is last so it doesn't override your custom intent handlers
)
.addRequestInterceptors(Common.RequestInterceptor)
.addErrorHandlers(
Common.ErrorHandler,
)
.lambda();
EV3 code
EV3 codeFirst I make sure that the EV3 is connect to the local network and that the Bluetooth function is working then I push the following python code onto the ev3. This python code decode the Alexa command and control the motor as well as the peripheral that is connected to the EV3,
import time
import json
from math import *
# import the navigation functions for the robot
from navigation import *
# import the Alexa Gadgets Toolkit so the robot can communicate with an Alexa device
from agt import AlexaGadget
# import the required libraries to interface with the EV3 hardware components
from ev3dev2.motor import LargeMotor, MediumMotor
from ev3dev2.sensor.lego import ColorSensor, InfraredSensor
from ev3dev2.led import Leds
# a basic class to handle PID control behavior
class PID:
kp, kd = 0, 0
e0 = 0
def __init__(self, kp=0, kd=0):
self.kp = kp
self.kd = kd
def calculate(self, e):
v = self.kp * e + (e - self.e0) * self.kd
self.e0 = e
return v
# main class that handles all of the robot behaviors controlled through the Alexa Skill
class Robot(AlexaGadget):
def __init__(self):
super().__init__()
# initialize all of the motors
print('Initializing devices')
self.leds = Leds()
self.motor_hand = LargeMotor(address='outA')
self.motor_claw = MediumMotor(address='outC')
# called when the EV3 brick connects to an Alexa device
def on_connected(self, device_addr):
self.leds.set_color('LEFT', 'GREEN')
self.leds.set_color('RIGHT', 'GREEN')
print("{} connected to Echo device".format(self.friendly_name))
# called when the EV3 brick disconnects from an Alexa device
def on_disconnected(self, device_addr):
self.leds.set_color('LEFT', 'BLACK')
self.leds.set_color('RIGHT', 'BLACK')
print("{} disconnected from Echo device".format(self.friendly_name))
# the function called to receive gadget control directives from the Alexa Skill through the connected Alexa device
def on_custom_mindstorms_gadget_control(self, directive):
# decode the directive payload into a JSON object
payload = json.loads(directive.payload.decode("utf-8"))
print("Control payload: {}".format(payload))
# determine which command to be executed
control_type = payload['type']
if control_type == 'Go':
# get the source and destination states for this command
src_state = State[payload['state']]
self.motors_claw.on(20)
time.sleep(2)
self.motors_claw.off(brake=True)
elif control_type == 'Back':
# get the source and destination states for this command
src_state = State[payload['state']]
self.motors_claw.on(-20)
time.sleep(2)
self.motors_claw.off(brake=True)
elif control_type == 'Left':
# get the source and destination states for this command
src_state = State[payload['state']]
self.motors_hand.on(20)
time.sleep(2)
self.motors_hand.off(brake=True)
elif control_type == 'Right':
# get the source and destination states for this command
src_state = State[payload['state']]
self.motors_hand.on(-20)
time.sleep(2)
self.motors_hand.off(brake=True)
elif control_type == 'Turn':
# get the source and destination states for this command
src_state = State[payload['state']]
while(danceflag == 1):
self.motors_hand.on(20)
time.sleep(2)
self.motors_hand.on(-20)
self.motors_hand.off(brake=True)
# move the robot straight back at a certain speed for a certain number of rotations
def move_back(self, speed=0.2, distance=1.6):
self.motor_left.on_for_rotations(round(speed * 100),
distance,
block=False)
self.motor_right.on_for_rotations(round(speed * 100), distance)
# turn off all motors and lights
def poweroff(self):
self.motor_hand.off(brake=False)
self.motor_claw.off(brake=False)
# called at program startup
def main():
# create a robot instance
robot = Robot()
# run the main function to handle Alexa Gadget code
robot.main()
# poweroff after the execution has been completed (or program exited)
robot.poweroff()
if __name__ == '__main__':
main()
Comments