Hackster is hosting Hackster Holidays, Ep. 4: Livestream & Giveaway Drawing. Start streaming on Wednesday!Stream Hackster Holidays, Ep. 4 on Wednesday!
Jason Hocker
Published © MIT

Robot Action Game

Pay attention to the movements of the robot and then answer simple questions based on what you saw.

AdvancedWork in progress1 hour629

Things used in this project

Hardware components

Mindstorms EV3 Programming Brick / Kit
LEGO Mindstorms EV3 Programming Brick / Kit
×1

Software apps and online services

ev3dev

Story

Read more

Schematics

lego model

Code

index.js

JavaScript
Alexa Skill
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();

package.json

JavaScript
package.json for Alexa kill
{
  "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"
  }
}

util.js

JavaScript
Utility methods for Alexa Skill
'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();
    }));
};

game.py

Python
#!/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")

Credits

Jason Hocker

Jason Hocker

2 projects • 2 followers
Thanks to LEGO.

Comments