Hardware components | ||||||
| × | 1 | ||||
Software apps and online services | ||||||
|
This is a project to show interactions from Alexa to the EV3 and also backwards. It also demonstrates what the API can do.
I designed this that Alexa would be the host of a game, and EV3 model would do some things, and then Alexa would ask the question. The EV3 is using the SDK suggested by this contest (ev3dev). I took the ideas from Mission 3 and Mission 4 to have the Alexa device send messages to the robot, and then for the robot to send messages back to Alexa. The robot follows the EV3RSTORM model through pages 104 (like Mission 3 listed this contest). It is set up as an Alexa gadget for the Alexa device to interact with it.
The Launch intent asks if you want to start a game or for help. If you ask for help, the directive is sent to the robot to demonstrate all the things it will do in the game. This unfortunately takes too long. I wish I could have the things fire faster on the robot. It may be the speaking is taking a long time, as I want the robot to use the terms we expect Alexa to listen to later.
The game consists of several rounds. Each round may ask longer questions to try to make the game harder. One intent will create the question, send the directive to the robot to do those actions, and then the robot will signal to Alexa for Alexa to ask the question to the user and wait for the answer. A problem with this is that the EV3 may signal to Alexa that it is done, and it may ask the question while the robot is doing its demo.
The robot is programmed to change its lights, move in different directions, fire its weapon, or play a note. There is a randomizer on the Alexa Skill side, that chooses the kinds of skills that the robot should do, and which random action it will ask a question about that chosen skill. A JSON payload is sent to EV3 with the array of actions to make. EV3 will display the question number in the screen and perform that action. Once it is done with the actions, it will alert the Alexa Skill that it is done so Alexa can then ask the question (and display it on appropriate devices).
Video shows a demo of the game in action:
const Alexa = require('ask-sdk-core');
const Util = require('./util');
const NAMESPACE = 'Custom.Mindstorms.Gadget';
const NAME = 'dance';
const numberOfQuestions = 5;
const LaunchRequestHandler = {
canHandle(handlerInput) {
return handlerInput.requestEnvelope.request.type === `LaunchRequest`;
},
handle: async function (handlerInput) {
let request = handlerInput.requestEnvelope;
let { apiEndpoint, apiAccessToken } = request.context.System;
let apiResponse = await Util.getConnectedEndpoints(apiEndpoint, apiAccessToken);
if ((apiResponse.endpoints || []).length === 0) {
return handlerInput.responseBuilder
.speak(`I couldn't find an EV3 Brick connected to this Echo device. Please check to make sure your EV3 Brick is connected, and try again.`)
.getResponse();
}
// Store the gadget endpointId to be used in this skill session
let endpointId = apiResponse.endpoints[0].endpointId || [];
Util.putSessionAttribute(handlerInput, 'endpointId', endpointId);
return handlerInput.responseBuilder
.speak(`Welcome to my game with my friend EV3. Would you like to start or ask for help?`)
.reprompt(helpMessage)
.getResponse();
}
};
const QuizHandler = {
canHandle(handlerInput) {
console.log("Inside QuizHandler - canHandle");
const request = handlerInput.requestEnvelope.request;
return request.type === "IntentRequest" &&
(request.intent.name === "QuizIntent" || request.intent.name === "AMAZON.StartOverIntent");
},
handle(handlerInput) {
return startQuiz(handlerInput);
}
};
const EventsReceivedRequestHandler = {
// Checks for a valid token and endpoint.
canHandle(handlerInput) {
console.log("in EventsReceivedRequestHandler");
let { request } = handlerInput.requestEnvelope;
console.log('Request type: ' + Alexa.getRequestType(handlerInput.requestEnvelope));
if (request.type !== 'CustomInterfaceController.EventsReceived') {
return false;
}
const attributesManager = handlerInput.attributesManager;
let sessionAttributes = attributesManager.getSessionAttributes();
let customEvent = request.events[0];
// Validate event token
if (sessionAttributes.token !== request.token) {
console.log("Event token doesn't match. Ignoring this event");
return false;
}
// Validate endpoint
let requestEndpoint = customEvent.endpoint.endpointId;
if (requestEndpoint !== sessionAttributes.endpointId) {
console.log("Event endpoint id doesn't match. Ignoring this event");
return false;
}
return true;
},
handle(handlerInput) {
let customEvent = handlerInput.requestEnvelope.request.events[0];
let payload = customEvent.payload;
let name = customEvent.header.name;
if(name == 'help-done') {
return startQuiz(handlerInput);
}
const attributes = handlerInput.attributesManager.getSessionAttributes();
const quizItem = attributes.quizItem;
if (supportsDisplay(handlerInput)) {
display(`Question #${attributes.counter}`, quizItem.Question, handlerInput.responseBuilder);
}
console.log((quizItem.Question));
return handlerInput.responseBuilder
.speak(quizItem.Question)
.withShouldEndSession(false)
.getResponse();
}
};
const ExpiredRequestHandler = {
canHandle(handlerInput) {
return Alexa.getRequestType(handlerInput.requestEnvelope) === 'CustomInterfaceController.Expired'
},
handle(handlerInput) {
console.log("== Custom Event Expiration Input ==");
// Set the token to track the event handler
const token = handlerInput.requestEnvelope.request.requestId;
Util.putSessionAttribute(handlerInput, 'token', token);
const attributesManager = handlerInput.attributesManager;
let duration = attributesManager.getSessionAttributes().duration || 0;
if (duration > 0) {
Util.putSessionAttribute(handlerInput, 'duration', --duration);
// Extends skill session
const speechOutput = `${duration} minutes remaining.`;
return handlerInput.responseBuilder
.addDirective(Util.buildStartEventHandler(token, 60000, {}))
.speak(speechOutput)
.getResponse();
}
else {
// End skill session
return handlerInput.responseBuilder
.speak("")
.withShouldEndSession(true)
.getResponse();
}
}
};
const QuizAnswerHandler = {
canHandle(handlerInput) {
const attributes = handlerInput.attributesManager.getSessionAttributes();
const request = handlerInput.requestEnvelope.request;
return attributes.state === states.QUIZ &&
request.type === 'IntentRequest' &&
request.intent.name === 'AnswerIntent';
},
handle(handlerInput) {
console.log("Inside QuizAnswerHandler - handle");
const attributes = handlerInput.attributesManager.getSessionAttributes();
const response = handlerInput.responseBuilder.withShouldEndSession(false);
if (attributes.currentInputHandlerId) {
responseBuilder.addDirective({
'type': 'CustomInterfaceController.StopEventHandler',
'token': attributes.currentEventHandlerToken
});
}
var speakOutput = ``;
const quizItem = attributes.quizItem;
const isCorrect = compareSlots(handlerInput.requestEnvelope.request.intent.slots, quizItem.Answer);
if (isCorrect) {
speakOutput = getPositiveReaction();
attributes.quizScore += 1;
handlerInput.attributesManager.setSessionAttributes(attributes);
} else {
speakOutput = getNegativeReaction();
}
speakOutput += `The correct answer is ${quizItem.Answer}. `;
if (attributes.counter < numberOfQuestions) {
speakOutput += getCurrentScore(attributes.quizScore, attributes.counter);
const quizItem = prepareQuizItem(handlerInput);
speakOutput += quizItem.Question;
const endpointId = handlerInput.attributesManager.getSessionAttributes().endpointId
let directive = Util.build(endpointId, NAMESPACE, NAME,
{
counter: attributes.counter,
actions: quizItem.Actions
});
const token = handlerInput.requestEnvelope.request.requestId;
if (supportsDisplay(handlerInput)) {
display(`Question #${attributes.counter}`, quizItem.Question, handlerInput.responseBuilder);
}
return response.speak(speakOutput)
.addDirective(Util.buildStartEventHandler(token, 60000, {}))
.addDirective(directive)
.reprompt(quizItem.Question)
.getResponse();
} else {
const finalScoreOutput = getFinalScore(attributes.quizScore, attributes.counter);
if (supportsDisplay(handlerInput)) {
display('Final Score', finalScoreOutput, response);
}
speakOutput += finalScoreOutput + exitSkillMessage;
return response.speak(speakOutput).withSimpleCard("Robot Game finished", finalScoreOutput)
.withShouldEndSession(true)
.getResponse();
}
}
};
const RepeatHandler = {
canHandle(handlerInput) {
const attributes = handlerInput.attributesManager.getSessionAttributes();
const request = handlerInput.requestEnvelope.request;
return attributes.state === states.QUIZ &&
request.type === 'IntentRequest' &&
request.intent.name === 'AMAZON.RepeatHandler';
},
handle(handlerInput) {
console.log("Inside RepeatHandler - handle");
const attributes = handlerInput.attributesManager.getSessionAttributes();
const response = handlerInput.responseBuilder;
const quizItem = attributes.quizItem;
const question = quizItem.Question;
return handlerInput.responseBuilder
.speak(question)
.reprompt(question)
.getResponse();
}
};
const HelpIntentHandler = {
canHandle(handlerInput) {
const request = handlerInput.requestEnvelope.request;
return request.type === 'IntentRequest' &&
request.intent.name === 'AMAZON.HelpIntent';
},
handle(handlerInput) {
const token = handlerInput.requestEnvelope.request.requestId;
const endpointId = handlerInput.attributesManager.getSessionAttributes().endpointId || [];
let directive = Util.build(endpointId, NAMESPACE, 'demo', {});
return handlerInput.responseBuilder
.speak(``)
.withShouldEndSession(false)
.addDirective(Util.buildStartEventHandler(token, 60000, {}))
.addDirective(directive)
.getResponse();
}
};
const ExitHandler = {
canHandle(handlerInput) {
const attributes = handlerInput.attributesManager.getSessionAttributes();
const request = handlerInput.requestEnvelope.request;
return request.type === `IntentRequest` && (
request.intent.name === 'AMAZON.StopIntent' ||
request.intent.name === 'AMAZON.PauseIntent' ||
request.intent.name === 'AMAZON.CancelIntent'
);
},
handle(handlerInput) {
return handlerInput.responseBuilder
.speak(exitSkillMessage)
.getResponse();
}
};
const SessionEndedRequestHandler = {
canHandle(handlerInput) {
return handlerInput.requestEnvelope.request.type === 'SessionEndedRequest';
},
handle(handlerInput) {
return handlerInput.responseBuilder.getResponse();
}
};
const IntentReflectorHandler = {
canHandle(handlerInput) {
return Alexa.getRequestType(handlerInput.requestEnvelope) === 'IntentRequest';
},
handle(handlerInput) {
const intentName = Alexa.getIntentName(handlerInput.requestEnvelope);
const speakOutput = `You just triggered ${intentName}`;
console.log(JSON.stringify(handlerInput.requestEnvelope));
return handlerInput.responseBuilder
.speak(speakOutput)
.reprompt("I don't understand this command, try again")
.getResponse();
}
};
const ErrorHandler = {
canHandle() {
return true;
},
handle(handlerInput, error) {
console.log("Inside ErrorHandler - handle");
console.log(`Error handled: ${JSON.stringify(error)}`);
console.log(`Handler Input: ${JSON.stringify(handlerInput)}`);
return handlerInput.responseBuilder
.speak('error')
.getResponse();
}
};
const skillBuilder = Alexa.SkillBuilders.custom();
const states = {
START: `_START`,
QUIZ: `_QUIZ`,
};
const helpMessage = `My friend EV3 will perform some actions. He may move, turn, or change his colors. Then I will ask a question about what it did. Sounds easy enough? The game will consist of ${numberOfQuestions} questions.`;
const exitSkillMessage = `Thank you for playing my game with EV3! Let's play again soon!`;
function supportsDisplay(handlerInput) {
var hasDisplay =
handlerInput.requestEnvelope.context &&
handlerInput.requestEnvelope.context.System &&
handlerInput.requestEnvelope.context.System.device &&
handlerInput.requestEnvelope.context.System.device.supportedInterfaces &&
handlerInput.requestEnvelope.context.System.device.supportedInterfaces.Display
return hasDisplay;
}
function startQuiz(handlerInput) {
console.log("Inside QuizHandler - handle");
const attributes = handlerInput.attributesManager.getSessionAttributes();
const response = handlerInput.responseBuilder;
attributes.state = states.QUIZ;
attributes.counter = 0;
attributes.quizScore = 0;
handlerInput.attributesManager.setSessionAttributes(attributes);
const quizItem = prepareQuizItem(handlerInput);
var speakOutput = `OK. EV3 will perform some actions and then I will ask you about what you saw. `;
const endpointId = handlerInput.attributesManager.getSessionAttributes().endpointId || [];
Util.putSessionAttribute(handlerInput, 'endpointId', endpointId);
const token = handlerInput.requestEnvelope.request.requestId;
Util.putSessionAttribute(handlerInput, 'token', token);
let directive = Util.build(endpointId, NAMESPACE, NAME,
{
counter: 1,
actions: quizItem.Actions
});
console.log(JSON.stringify(directive));
return response.speak(speakOutput )
.withShouldEndSession(false)
.addDirective(Util.buildStartEventHandler(token, 60000, {}))
.addDirective(directive)
.getResponse();
}
function getCurrentScore(score, counter) {
return `Your current score is ${score} out of ${counter}. `;
}
function getFinalScore(score, counter) {
return `Your final score is ${score} out of ${counter}. `;
}
function getRandom(min, max) {
return Math.floor((Math.random() * ((max - min) + 1)) + min);
}
function prepareQuizItem(handlerInput) {
const attributes = handlerInput.attributesManager.getSessionAttributes();
var counter = attributes.counter;
counter += 1;
const quizItem = buildQuizItem(counter);
attributes.quizItem = quizItem;
attributes.counter = counter;
handlerInput.attributesManager.setSessionAttributes(attributes);
return quizItem;
}
function buildQuizItem(counter) {
var question = null;
var answer = null;
var actions = [];
let action, action2 = null;
const random = getRandom(0, counter - 1);
for (let i = 0; i < counter; i++) {
const randomType = getRandom(0, 6);
switch (randomType) {
default:
case 0:
action = { Type: 'LED', LedGroup: ['LEFT', 'RIGHT'][getRandom(0, 1)], Color: ['BLACK', 'RED', 'GREEN', 'AMBER', 'ORANGE', 'YELLOW'][getRandom(1, 5)] };
if (i == random) {
question = `What is the color for the ${i + 1}th action.`;
answer = action.Color;
}
actions.push(action);
break;
case 1:
action = { Type: 'LED', LedGroup: ['LEFT', 'RIGHT'][getRandom(0, 1)], Color: ['BLACK', 'RED', 'GREEN', 'AMBER', 'ORANGE', 'YELLOW'][getRandom(1, 5)] };
if (i == random) {
question = `Did the ${i + 1}th action light up the left or right? `;
answer = action.LedGroup;
}
actions.push(action);
break;
case 2:
action = { Type: 'MOVE', Direction: ['FORWARD', 'BACKWARD', 'LEFT', 'RIGHT'][getRandom(0, 3)], Duration: getRandom(1, 2), Speed: 50 };
if (i == random) {
question = `For the ${i + 1}th action, did EV3 move Forward, Backward, Left, or right?`;
answer = action.Direction;
}
actions.push(action);
i++;
action2 = reverseAction(action);
if (i == random) {
question = `For the ${i + 1}th action, did EV3 move Forward, Backward, Left, or right?`;
answer = action2.Direction;
}
actions.push(action2);
break;
case 3:
action = { Type: 'MOVE', Direction: ['FORWARD', 'BACKWARD', 'LEFT', 'RIGHT'][getRandom(0, 3)], Duration: getRandom(1, 2), Speed: 50 };
if (i == random) {
question = `For the ${i + 1}th action, how many seconds did EV3 move?`;
answer = action.Duration;
}
actions.push(action);
i++;
action2 = reverseAction(action);
if (i == random) {
question = `For the ${i + 1}th action, how many seconds did EV3 move?`;
answer = action2.Duration;
}
actions.push(action2);
break;
case 4:
action = { Type: 'WEAPON', Rotations: getRandom(1, 3), Speed: getRandom(0, 1) ? 50 : -50 };
if (i == random) {
question = `For the ${i + 1}th action, how many rotations did EV3's weapon move?`;
answer = action.Rotations;
}
actions.push(action);
break;
case 5:
action = { Type: 'WEAPON', Rotations: getRandom(1, 3), Speed: getRandom(0, 1) ? 100 : -100 };
if (i == random) {
question = `For the ${i + 1}th action, did EV3's weapon rotate clockwise or counter-clockwise?`;
answer = action.Speed == 100 ? 'Clockwise' : 'Counter-clockwise';
}
actions.push(action);
break;
case 6:
action = { Type: 'SOUND', Note: ['C', 'D', 'E', 'F', 'G', 'A', 'B'][getRandom(0, 6)] };
if (i == random) {
question = `For the ${i + 1}th action, what was the musical note?`;
answer = action.Note;
}
actions.push(action);
break;
}
console.log(i);
console.log(JSON.stringify(actions));
}
console.log(actions);
return { 'Question': question, 'Answer': answer, 'Actions': actions };
}
function reverseAction(action) {
let action2 = JSON.parse(JSON.stringify(action));
switch(action.Direction) {
case 'FORWARD':
action2.Direction = 'BACKWARD';
break;
case 'BACKWARD':
action2.Direction = 'FORWARD';
break;
case 'LEFT':
action2.Direction = 'RIGHT';
break;
case 'RIGHT':
action2.Direction = 'LEFT';
break;
}
return action2;
}
function compareSlots(slots, value) {
for (const slot in slots) {
if (Object.prototype.hasOwnProperty.call(slots, slot) && slots[slot].value !== undefined) {
if (slots[slot].value.toString().toLowerCase() === value.toString().toLowerCase()) {
return true;
}
}
}
return false;
}
function getPositiveReaction() {
const speechConsCorrect = ['Booya', 'All righty', 'Bam', 'Bazinga', 'Bingo', 'Boom', 'Bravo', 'Cha Ching', 'Cheers', 'Dynomite', 'Hip hip hooray', 'Hurrah', 'Hurray', 'Huzzah', 'Oh dear. Just kidding. Hurray', 'Kaboom', 'Kaching', 'Oh snap', 'Phew', 'Righto', 'Way to go', 'Well done', 'Whee', 'Woo hoo', 'Yay', 'Wowza', 'Yowsa'];
return `<say-as interpret-as='interjection'>${speechConsCorrect[getRandom(0, speechConsCorrect.length - 1)]}! </say-as><break strength='strong'/>`;
}
function getNegativeReaction() {
const speechConsWrong = ['Argh', 'Aw man', 'Blarg', 'Blast', 'Boo', 'Bummer', 'Darn', "D'oh", 'Dun dun dun', 'Eek', 'Honk', 'Le sigh', 'Mamma mia', 'Oh boy', 'Oh dear', 'Oof', 'Ouch', 'Ruh roh', 'Shucks', 'Uh oh', 'Wah wah', 'Whoops a daisy', 'Yikes'];
return `<say-as interpret-as='interjection'>${speechConsWrong[getRandom(0, speechConsWrong.length - 1)]} </say-as><break strength='strong'/>`;
}
function display(title, text, response) {
console.log("display " + title + text + response);
const textContent = new Alexa.RichTextContentHelper().withPrimaryText(text).getTextContent();
response.addRenderTemplateDirective({
type: 'BodyTemplate1',
token: 'Question',
backButton: 'hidden',
title,
textContent
});
}
exports.handler = skillBuilder
.addRequestHandlers(
LaunchRequestHandler,
QuizHandler,
QuizAnswerHandler,
EventsReceivedRequestHandler,
// ExpiredRequestHandler,
RepeatHandler,
HelpIntentHandler,
ExitHandler,
SessionEndedRequestHandler,
IntentReflectorHandler
)
// .addErrorHandlers(ErrorHandler)
.lambda();
{
"name": "game",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
"license": "ISC",
"dependencies": {
"ask-sdk-core": "^2.0.0",
"ask-sdk-model": "^1.0.0"
}
}
'use strict';
const Https = require('https');
/**
* Builds a directive to start the EventHandler.
* @param token - a unique identifier to track the event handler
* @param {number} timeout - the duration to wait before sending back the expiration
* payload to the skill.
* @param payload - the expiration json payload
* @see {@link https://developer.amazon.com/docs/alexa-gadgets-toolkit/receive-custom-event-from-gadget.html#start}
*/
exports.buildStartEventHandler = function (token, timeout = 30000, payload) {
return {
type: "CustomInterfaceController.StartEventHandler",
token: token,
expiration : {
durationInMilliseconds: timeout,
expirationPayload: payload
}
};
};
/**
*
* Builds a directive to stops the active event handler.
* The event handler is identified by the cached token in the session attribute.
* @param {string} handlerInput - the JSON payload from Alexa Service
* @see {@link https://developer.amazon.com/docs/alexa-gadgets-toolkit/receive-custom-event-from-gadget.html#stop}
*/
exports.buildStopEventHandlerDirective = function (handlerInput) {
let token = handlerInput.attributesManager.getSessionAttributes().token || '';
return {
"type": "CustomInterfaceController.StopEventHandler",
"token": token
}
};
/**
* Build a custom directive payload to the gadget with the specified endpointId
* @param {string} endpointId - the gadget endpoint Id
* @param {string} namespace - the namespace of the skill
* @param {string} name - the name of the skill within the scope of this namespace
* @param {object} payload - the payload data
* @see {@link https://developer.amazon.com/docs/alexa-gadgets-toolkit/send-gadget-custom-directive-from-skill.html#respond}
*/
exports.build = function (endpointId, namespace, name, payload) {
// Construct the custom directive that needs to be sent
// Gadget should declare the capabilities in the discovery response to
// receive the directives under the following namespace.
return {
type: 'CustomInterfaceController.SendDirective',
header: {
name: name,
namespace: namespace
},
endpoint: {
endpointId: endpointId
},
payload
};
};
/**
* A convenience routine to add the a key-value pair to the session attribute.
* @param handlerInput - the handlerInput from Alexa Service
* @param key - the key to be added
* @param value - the value be added
*/
exports.putSessionAttribute = function(handlerInput, key, value) {
const attributesManager = handlerInput.attributesManager;
let sessionAttributes = attributesManager.getSessionAttributes();
sessionAttributes[key] = value;
attributesManager.setSessionAttributes(sessionAttributes);
console.log("putSessionAttribute " + key + " "
+ value);
};
/**
* To get a list of all the gadgets that meet these conditions,
* Call the Endpoint Enumeration API with the apiEndpoint and apiAccessToken to
* retrieve the list of all connected gadgets.
*
* @param {string} apiEndpoint - the Endpoint API url
* @param {string} apiAccessToken - the token from the session object in the Alexa request
* @see {@link https://developer.amazon.com/docs/alexa-gadgets-toolkit/send-gadget-custom-directive-from-skill.html#call-endpoint-enumeration-api}
*/
exports.getConnectedEndpoints = function(apiEndpoint, apiAccessToken) {
// The preceding https:// need to be stripped off before making the call
apiEndpoint = (apiEndpoint || '').replace('https://', '');
return new Promise(((resolve, reject) => {
const options = {
host: apiEndpoint,
path: '/v1/endpoints',
method: 'GET',
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer ' + apiAccessToken
}
};
const request = Https.request(options, (response) => {
response.setEncoding('utf8');
let returnData = '';
response.on('data', (chunk) => {
returnData += chunk;
});
response.on('end', () => {
resolve(JSON.parse(returnData));
});
response.on('error', (error) => {
reject(error);
});
});
request.end();
}));
};
#!/usr/bin/env python3
import os
import sys
import time
import logging
import json
import random
import threading
from enum import Enum
from agt import AlexaGadget # pylint: disable=import-error
from ev3dev2.led import Leds # pylint: disable=import-error
from ev3dev2.sound import Sound # pylint: disable=import-error
from ev3dev2.motor import OUTPUT_A, OUTPUT_B, OUTPUT_C, MoveTank, SpeedPercent, MediumMotor # pylint: disable=import-error
from ev3dev2.display import Display # pylint: disable=import-error
# Set the logging level to INFO to see messages from AlexaGadget
logging.basicConfig(level=logging.INFO, stream=sys.stdout, format='%(message)s')
logging.getLogger().addHandler(logging.StreamHandler(sys.stderr))
logger = logging.getLogger(__name__)
class MindstormsGadget(AlexaGadget):
"""
A Mindstorms gadget that performs movement based on voice commands.
Two types of commands are supported, directional movement and preset.
"""
def __init__(self):
"""
Performs Alexa Gadget initialization routines and ev3dev resource allocation.
"""
super().__init__()
# Ev3dev initialization
self.leds = Leds()
self.sound = Sound()
self.drive = MoveTank(OUTPUT_B, OUTPUT_C)
self.weapon = MediumMotor(OUTPUT_A)
self.screen = Display()
# self.draw_face()
# self.sound.speak("Alexa")
def on_connected(self, device_addr):
"""
Gadget connected to the paired Echo device.
:param device_addr: the address of the device we connected to
"""
self.leds.set_color("LEFT", "BLACK")
self.leds.set_color("RIGHT", "BLACK")
logger.info("{} connected to Echo device".format(self.friendly_name))
def on_disconnected(self, device_addr):
"""
Gadget disconnected from the paired Echo device.
:param device_addr: the address of the device we disconnected from
"""
self.leds.set_color("LEFT", "BLACK")
self.leds.set_color("RIGHT", "BLACK")
logger.info("{} disconnected from Echo device".format(self.friendly_name))
def on_custom_mindstorms_gadget_dance(self, directive):
"""
Handles the Custom.Mindstorms.Gadget control directive.
:param directive: the custom directive with the matching namespace and name
"""
try:
payload = json.loads(directive.payload.decode("utf-8"))
print("Control payload: {}".format(payload), file=sys.stderr)
time.sleep(3)
self.draw_counter(payload["counter"])
for action in payload["actions"]:
if action["Type"] == 'LED':
self.leds.set_color(action["LedGroup"], action["Color"])
time.sleep(2)
self.leds.set_color("LEFT", "BLACK")
self.leds.set_color("RIGHT", "BLACK")
time.sleep(2)
if action["Type"] == 'MOVE':
self.move(action["Direction"], int(action["Duration"]), int(action["Speed"]))
if action["Type"] == 'WEAPON':
self.weapon.on_for_rotations(SpeedPercent(int(action["Speed"])), int(action["Rotations"]))
if action["Type"] == 'SOUND':
self.sound.play_note(action["Note"] + "4", 1, 3, play_type=0)
self.send_custom_event('Custom.Mindstorms.Gadget', "done", payload)
self.screen.clear()
self.screen.update()
except KeyError:
print("Missing expected parameters: {}".format(directive), file=sys.stderr)
def on_custom_mindstorms_gadget_demo(self, directive):
"""
Handles the Custom.Mindstorms.Gadget control directive.
:param directive: the custom directive with the matching namespace and name
"""
self.sound.speak("I am EV3. Let me show you what I do!")
self.sound.speak("My buttons can go ")
for i in ['RED', 'GREEN', 'AMBER', 'ORANGE', 'YELLOW']:
self.sound.speak(i)
self.leds.set_color("LEFT", i)
self.leds.set_color("RIGHT", i)
self.sound.speak("I can move")
for i in ['FORWARD', 'BACKWARD', 'LEFT', 'RIGHT']:
self.sound.speak(i)
self.move(i, 1, 50)
self.move('FORWARD', 1, 50)
self.sound.speak("I can move my weapon ")
for i in ['CLOCKWISE', 'COUNTER-CLOCKWISE']:
self.sound.speak(i)
if i == 'CLOCKWISE':
self.weapon.on_for_rotations(SpeedPercent(100), 2)
if i == 'COUNTER-CLOCKWISE':
self.weapon.on_for_rotations(SpeedPercent(-100), 2)
self.sound.speak("I can play the notes")
for i in ['C', 'D', 'E', 'F', 'G', 'A', 'B']:
self.sound.speak(i)
self.sound.play_note(i + "4", 1, 3, play_type=0)
self.send_custom_event('Custom.Mindstorms.Gadget', "help-done", {})
def draw_counter(self, counter):
mystring = "Question " + str(counter)
self.screen.clear()
size = self.screen.draw.textsize(mystring) # returns a tuple
# screen height = 128 pixels, so vertical center is
# at 64 pixels, so place text (height 11 pixels) at
# 5 pixels above the center, at 59
self.screen.draw.text((89-size[0]/2, 59), mystring)
self.screen.update()
def move(self, direction, duration: int, speed: int, is_blocking=True):
print("Move command: ({}, {}, {}, {})".format(direction, speed, duration, is_blocking), file=sys.stderr)
if direction == 'FORWARD':
self.drive.on_for_seconds(SpeedPercent(speed), SpeedPercent(speed), duration, block=is_blocking)
if direction == 'BACKWARD':
self.drive.on_for_seconds(SpeedPercent(-speed), SpeedPercent(-speed), duration, block=is_blocking)
if direction == 'LEFT':
self.drive.on_for_seconds(SpeedPercent(0), SpeedPercent(speed), duration, block=is_blocking)
if direction == 'RIGHT':
self.drive.on_for_seconds(SpeedPercent(speed), SpeedPercent(0), duration, block=is_blocking)
if __name__ == '__main__':
gadget = MindstormsGadget()
# Set LCD font and turn off blinking LEDs
os.system('setfont Lat7-Terminus12x6')
gadget.leds.set_color("LEFT", "BLACK")
gadget.leds.set_color("RIGHT", "BLACK")
# Startup sequence
# gadget.sound.play_song((('C4', 'e'), ('D4', 'e'), ('E5', 'q')))
# gadget.leds.set_color("LEFT", "GREEN")
# gadget.leds.set_color("RIGHT", "GREEN")
# Gadget main entry point
gadget.main()
# Shutdown sequence
# gadget.sound.play_song((('E5', 'e'), ('C4', 'e')))
# gadget.leds.set_color("LEFT", "BLACK")
# gadget.leds.set_color("RIGHT", "BLACK")
Comments