Andrés Giraldo RaigozaLuis B Caicedo
Created December 18, 2019 © GPL3+

The perfect crime fighting sidekick for Batman

A voice-commanded smart vehicle to help the Caped Crusader fight crime in a safer and more entertaining way.

IntermediateFull instructions provided558
The perfect crime fighting sidekick for Batman

Things used in this project

Hardware components

Echo Dot
Amazon Alexa Echo Dot
3rd Generation
×1
Mindstorms EV3 Programming Brick / Kit
LEGO Mindstorms EV3 Programming Brick / Kit
×1
LEGO Ultrasonic sensor EV3
×1
LEGO EV3 Infrared Sensor
×1
LEGO EV3 Large motor
×2
LEGO EV3 Medium Motor
×2
Lego Technique Bricks and parts
×1
Post-it notes
×1
Sharpie Markers
×1

Software apps and online services

Visual Studio 2015
Microsoft Visual Studio 2015
Mecabricks
LEGO Digital Designer

Story

Read more

Schematics

Tumbler/Batmobile chassis design

This is a LEGO Digital Designer file (.lxf format). You can open it using LEGO DIGITAL DESIGNER or you can also import it on https://mecabricks.com/en/workshop

Code

The Smart Batmobile project

Python
You have the skill-nodejs folder including the Alexa Skill Code, and you also have the tumbler.py Python file with the corresponding events that enable the activation of motors and sensors.
No preview (download only).

The Smart Batmobile - Main Index File Node.js code

JavaScript
The index.js file code that you can use to update your Alexa Skill
/*
 * Copyright 2019 Amazon.com, Inc. or its affiliates.  All Rights Reserved.
 *
 * You may not use this file except in compliance with the terms and conditions 
 * set forth in the accompanying LICENSE.TXT file.
 *
 * THESE MATERIALS ARE PROVIDED ON AN "AS IS" BASIS. AMAZON SPECIFICALLY DISCLAIMS, WITH 
 * RESPECT TO THESE MATERIALS, ALL WARRANTIES, EXPRESS, IMPLIED, OR STATUTORY, INCLUDING 
 * THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, AND NON-INFRINGEMENT.
*/

// This skill sample demonstrates how to send directives and receive events from an Echo connected gadget.
// This skill uses the Alexa Skills Kit SDK (v2). Please visit https://alexa.design/cookbook for additional
// examples on implementing slots, dialog management, session persistence, api calls, and more.

const Alexa = require('ask-sdk-core'); 
const Util = require('./util');
const Common = require('./common');

// The audio tag to include background music
const BG_MUSIC = '<audio src="soundbank://soundlibrary/ui/gameshow/amzn_ui_sfx_gameshow_waiting_loop_30s_01"/>';

// 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';


var VILLAINSINFO = {
  "joker" : ['He has a Love/Hate Relationship with Harley Quinn', 'Hes Actually Obsessed with you, sir.', "He's Immune to Fear Toxin and Other Drugs.", "The Joker's Origins are Unclear"],
  "poison" : ["Poison Ivy is not always a monster to people. In fact, when she is near true innocents, she will do her best to protect them. During a year of turmoil, she cared for some children, a creditable act of charity. ", 'Just as the actual plant poison ivy can produce unwanted rashes on your skin, Poison Ivy can produce an unwanted condition on your life, namely death. A toxin that can kill a person is located right on her lips.'],
  "freeze" : ["While most people tend to focus on Mr. Freeze's freezing weapons, his real threat is his mind. He specializes in cryogenics, skills which he used to put his wife Nora into suspended animation for years. After his accident, he was able to save his own life by designing and building the cold suit he wears. When he turned to crime, he created his cold gun and the other weapons that can generate ice and freeze anything instantly.", "Freeze was ever exposed to normal temperatures, he would die. Since he has to stay in freezing temperatures to survive, he can only leave his lair in a special containment suit that keeps his body cold."],
  "riddler" : ["Shoots bubbles which can make their opponents blow up like baloons and float in the air for a few seconds.", "As a child, he was abused by his parents. After getting high scores on some important tests in school, Edward's father accused him of cheating and beat him as a result. The physical abuse from his father seemed to have left him with a compulsion to tell the truth, appearing in riddles. The abuse also left behind Ed with the inherit need to prove his superiority by outwitting everyone he can around him. "],
  "clayface" : ["He is a clay-like monster capable of shape-shifting and superhuman abilities. "],
  "harley" : ["Harley Quinn is an Olympic-level athlete with excellent gymnastics skills rivaling that of Catwoman and Nightwing. ", "She can jump very high and perform dangerous acrobatic feats with little effort. She is also a very impressive fighter, utilizing her acrobatic skills in her fighting style. "],
  "catwoman" : ["Catwoman has an good affinity for cats. This allows her to calm injured cats, train them, befriend them, and form strong bonds with them. These animals will even sometimes come to her defense when she is attacked.", 'Selina Kyle is an amnesiac stewardess who turned to a life of crime after suffering a blow to the head during a plane crash.'],
  "twoface" : ['He carries a double-headed coin around with him that has one of the heads scarred, and he flips the coin before he makes any decisions involving a question of law and illegal activities.', "He's completely obsessed with his number 2 duality, and fate. As such, all of the crimes that he commits revolve around the number 2."],
  "scarecrow" : ["He does not commit his crimes for wealth, but rather as a form of 'research' to further study the effects of fear on humans (later, he does it to satisfy his own psychopathic desires), making the innocent citizens of Gotham his unwilling guinea pigs.", "The Scarecrow is an overly-obsessive and deranged ex-professor of psychology who uses a variety of drugs and psychological tactics to use the fears and phobias of his adversaries. "],
  "penguin" : ["The Penguin, one of your oldest foes, is an eccentric criminal mastermind, known as much for his love of ornithology and trick umbrellas as for his already shady business dealings.", "The Penguin, known as Oswald Cobblepott, had a troubled life growing up. Born into the Cobblepott family, one of Gothams most influential families, Oswald found himself bullied greatly for years by his fellow students, who teased him for his short stature, weight, and beak-shaped nose."],
  "madhatter" : ["Jervis Tetch was obsessed with the story, Alice in Wonderland, and was a former researcher. He soon turned to a life and crime, becoming a murderer, thief and even a rapist. ", "He managed to create mind-controlling devices which he used to control others as well as gaining henchmen and was obsessed with kidnapping 'Alices'. "],
  "ventriloquist" : ["A meek, quiet man, Arnold Wesker plans and executes his crimes through a dummy named Scarface, with the dress and persona of a 1920s gangster, complete with pinstripe suit, cigar, and Tommy gun.", "Arnold Wesker is a ventriloquist with multiple personalities. His puppet is a gangster named Scarface. Under the puppet's psychological influence, Wesker is a dangerous and ruthless criminal and crime boss."],
  "bane"  : ['Bane is incredibly strong and his physical appearance demonstrates that, but Bane is much more than a typical muscle-head.', 'He was born and raised in a prison.', 'Bane was born in Pea Duro, a notoriously harsh prison. His father abandoned him and his mother passed away shortly after his birth.', "He sought to become incredibly intelligent, so he read countless books. He wanted to be strong, so he tirelessly honed his body at the prisons gym. He mastered meditation and even invented his own form of it."]
};

var ALLTYPESOFINFO = {
  "awareresponse" : ["Don't worry, Master Wayne. My bat eyes are minding our surroundings on the road.", "Of course. You were never good at minding your surroundings. But don't worry, that's what I am here for, Master Wayne.", "Of course. Whenever you are on the wheel, you do make a bloody mess. But don't worry, I am here to take care of it, sir."],
  "new" : ['witty response 1', 'witty response 2', 'witty response 3', "If you ever decide to turn yourself in, I suppose they'll lock me up as well. As your accomplice..."],
  "joke" : ['I emailed Netflix and asked if they had Batman Forever. They said, no, just until the end of June. hahahah', 'My friend said I am starting to annoy her because I relate everything to Batman. What a joker...', "Why doesn't Batman like Mr Freeze? Because he gives him the cold shoulder!", 'What do you  call it when Batman skips church? Christian Bale.', "Why can't Bruce Wayne get a date? Because he has bat breath!"],
  "jokeopeningline" : ['Alright, I am going to make you laugh hard this time, sir. ', "Let's see if this one makes you laugh. ", 'Did you bring an extra batsuit sir? Because you are going to piss your pants when you hear this. '],
  "openingline" : ["Hello sir, Let's go on a crime fighting adventure together. You can say things like move forward, stay alert, take a left, I want to know more about an enemy. What is it going to be, sir?", 'Hello, Caped Crusader, I am more than ready to be your sidekick for the night. I can help you to control this car in a better and safer manner. You can say things like move forward, move faster, stay aware of your surroundings, or tell me more about an enemy. What is it going to be, sir?'],
  "idleresponse" : ['If you struggle to know what you can do while driving, just say you need help and I will come right away, Master Wayne.','You seem quite, sir. I can also make your night better by telling you jokes, or giving advice. What do you want me to do, sir?', 'Anything else I can help you with, sir?', 'What else can I do for you, Master Wayne?', 'Anything I can do for you, Master Wayne?', "I can also make your night better by telling you jokes, or giving advice."],
  "switchedvoiceresponse" : ['Ahem ahem, how do I sound now? Liking it already Master Wayne?', 'As you wish sir. Do I sound sexier now? '],
  "backcrashingresponse" : ['What in bloody hell do you think you are doing, Master Wayne? We do not want to crash that amazing car of yours...', 'I am not crashing myself, sir. Are you nuts?'],
  "frontproximityresponse" : ['It seems like you are somewhere else, sir. I am coming to a complete stop, Master Wayne. You can then decide what to do with our problem ahead.', 'I am stopping this car for our own safety, Master Wayne. What do you want to do with this obstacle?', 'I am not killing myself sir, so I am going to stop this car right now. How do you want to deal with this obstacle?', 'It is time to stop your driving madness sir. What are you doing with this?', 'Since you are so distracted, I am coming to a complete stop, Master Wayne. What do you want me to do with this?'],
  "advice"  : ["Endure, Master Wayne. Take it. They'll hate you for it, but that's the point of Batman, he can be the outcast. He can make the choice that no one else can make, the right choice.", 'Know your limits, Master Wayne.', "This is a friendly FYI, sir: Some men aren't looking for anything logical, like money. They can't be bought, bullied, reasoned, or negotiated with. Some men just want to watch the world burn."]
};

//The default voice tone selected for the Skill. It can be changed when the SwitchVoiceIntent is triggered
let voiceSelected = "Brian";

const LaunchRequestHandler = {
    canHandle(handlerInput) {
        return Alexa.getRequestType(handlerInput.requestEnvelope) === 'LaunchRequest';
    },
    handle: async function(handlerInput) {

        const request = handlerInput.requestEnvelope;
        const { apiEndpoint, apiAccessToken } = request.context.System;
        const apiResponse = await Util.getConnectedEndpoints(apiEndpoint, apiAccessToken);
        if ((apiResponse.endpoints || []).length === 0) {
            return handlerInput.responseBuilder
            
            .speak(switchVoice("It seems your LEGO EV3 Brick is turned off, sir. Please check to make sure your EV3 Brick is connected, and try again.", voiceSelected))
            .getResponse();
        }
    
        Util.putSessionAttribute(handlerInput, 'voice', voiceSelected)

        // Store the gadget endpointId to be used in this skill session
        const endpointId = apiResponse.endpoints[0].endpointId || [];
        Util.putSessionAttribute(handlerInput, 'endpointId', endpointId);

        // Set skill duration to 5 minutes (ten 30-seconds interval)
        Util.putSessionAttribute(handlerInput, 'duration', 10);

        // Set the token to track the event handler
        const token = handlerInput.requestEnvelope.request.requestId;
        Util.putSessionAttribute(handlerInput, 'token', token);

        let speechOutput = getRandomResponse(ALLTYPESOFINFO["openingline"]);
        return handlerInput.responseBuilder
            .speak(switchVoice(speechOutput, voiceSelected))
            .reprompt(switchVoice(getRandomResponse(ALLTYPESOFINFO["idleresponse"]), voiceSelected))
            .addDirective(Util.buildStartEventHandler(token,60000, {}))
            .getResponse();
    }
};

// Add the speed value to the session attribute.
// This allows other intent handler to use the specified speed value
// without asking the user for input.
const SetSpeedIntentHandler = {
    canHandle(handlerInput) {
        return Alexa.getRequestType(handlerInput.requestEnvelope) === 'IntentRequest'
            && Alexa.getIntentName(handlerInput.requestEnvelope) === 'SetSpeedIntent';
    },
    handle: function (handlerInput) {

         // Get data from session attribute
        const attributesManager = handlerInput.attributesManager;
        voiceSelected = attributesManager.getSessionAttributes().voice || "Brian";
        let direction = attributesManager.getSessionAttributes().direction || "forward";
        let duration = attributesManager.getSessionAttributes().duration || "60";
        
        // Bound speed to (1-100)
        let speed = Alexa.getSlotValue(handlerInput.requestEnvelope, 'Speed');
        let acceleration = Alexa.getSlotValue(handlerInput.requestEnvelope, 'Acceleration');
        
        speed = Math.max(1, Math.min(100, parseInt(speed)));
        
        if(acceleration){
            
            const speedSession = parseInt(attributesManager.getSessionAttributes().speed || "0");
            
             if (acceleration==="faster" || acceleration==="too slow" || acceleration==="speed up") {
                speed = speedSession + 10;
            }
        
            if (acceleration==="slower" || acceleration==="too fast" || acceleration==="slow down") {
                speed = speedSession - 10;
            }
        }
        
        Util.putSessionAttribute(handlerInput, 'speed', speed);
        
        const endpointId = attributesManager.getSessionAttributes().endpointId || [];

        // Construct the directive with the payload containing the speed and move parameters
        let directive = Util.build(endpointId, NAMESPACE, NAME_CONTROL,
            {
                type: 'move',
                direction: direction,
                duration: duration,
                speed: speed
            });

        let speechOutput = `Certainly, sir. I just set the speed to ${speed} percent.`;
        return handlerInput.responseBuilder
            .speak(switchVoice(speechOutput, voiceSelected))
            .withShouldEndSession(false)
            .addDirective(directive)
            .getResponse();
    }
};

// Construct and send a custom directive to the connected gadget with
// data from the MoveIntent.
const MoveIntentHandler = {
    canHandle(handlerInput) {
        return Alexa.getRequestType(handlerInput.requestEnvelope) === 'IntentRequest'
            && Alexa.getIntentName(handlerInput.requestEnvelope) === 'MoveIntent';
    },
    handle: function (handlerInput) {
        const request = handlerInput.requestEnvelope;
        const direction = Alexa.getSlotValue(request, 'Direction') || "forward";

        // Duration is optional, use default if not available
        const duration = Alexa.getSlotValue(request, 'Duration') || "60";

        // Get data from session attribute
        const attributesManager = handlerInput.attributesManager;
        const speed = attributesManager.getSessionAttributes().speed || "10";
        voiceSelected = attributesManager.getSessionAttributes().voice || "Brian";
        
        // Speed from intent is optional, use the one from the session if not provided during the intent

        let speedFromIntent = Alexa.getSlotValue(handlerInput.requestEnvelope, 'Speed') || speed;
        
        Util.putSessionAttribute(handlerInput, 'speed', speedFromIntent);
        Util.putSessionAttribute(handlerInput, 'direction', direction);
        Util.putSessionAttribute(handlerInput, 'duration', duration);
        
        const endpointId = attributesManager.getSessionAttributes().endpointId || [];

        // Construct the directive with the payload containing the move parameters
        let directive = Util.build(endpointId, NAMESPACE, NAME_CONTROL,
            {
                type: 'move',
                direction: direction,
                duration: duration,
                speed: speedFromIntent
            });

        const speechOutput = (direction === "brake")
            ?  "Applying brake"
            : `Certainly, sir. I am moving this thing ${direction} for ${duration} seconds at ${speedFromIntent} percent speed`;

        return handlerInput.responseBuilder
            .speak(switchVoice(speechOutput, voiceSelected))
            .withShouldEndSession(false)
            .addDirective(directive)
            .getResponse();
    }
};

// Construct and send a custom directive to the connected gadget with
// data from the SteerIntent.
const SteerIntentHandler = {
    canHandle(handlerInput) {
        return Alexa.getRequestType(handlerInput.requestEnvelope) === 'IntentRequest'
            && Alexa.getIntentName(handlerInput.requestEnvelope) === 'SteerIntent';
    },
    handle: function (handlerInput) {
        const request = handlerInput.requestEnvelope;
        const direction = Alexa.getSlotValue(request, 'Steer_Direction');

        // Get data from session attribute
        const attributesManager = handlerInput.attributesManager;
        const previous_steering_direction = attributesManager.getSessionAttributes().previous_steering_direction || "center";
        const endpointId = attributesManager.getSessionAttributes().endpointId || [];
        voiceSelected = attributesManager.getSessionAttributes().voice || "Brian";
        
        
        Util.putSessionAttribute(handlerInput, 'previous_steering_direction', direction);

        // Construct the directive with the payload containing the steering parameters
        let directive = Util.build(endpointId, NAMESPACE, NAME_CONTROL,
            {
                type: 'steer',
                steering_direction: direction,
                previous_steering_direction: previous_steering_direction
            });

        const speechOutput = (direction === "brake")
            ?  "Applying brake"
            : `Certainly, sir. Turning ${direction}`;

        return handlerInput.responseBuilder
            .speak(switchVoice(speechOutput, voiceSelected))
            .withShouldEndSession(false)
            .addDirective(directive)
            .getResponse();
    }
};

// Construct and send a custom directive to the connected gadget with data from
// the SetCommandIntent.
const SetCommandIntentHandler = {
    canHandle(handlerInput) {
        return Alexa.getRequestType(handlerInput.requestEnvelope) === 'IntentRequest'
            && Alexa.getIntentName(handlerInput.requestEnvelope) === 'SetCommandIntent';
    },
    handle: function (handlerInput) {

         // Get data from session attribute
        const attributesManager = handlerInput.attributesManager;
        voiceSelected = attributesManager.getSessionAttributes().voice || "Brian";
        let endpointId = attributesManager.getSessionAttributes().endpointId || [];
        let speed = attributesManager.getSessionAttributes().speed || "10";
        
        let command = Alexa.getSlotValue(handlerInput.requestEnvelope, 'Command');
        if (!command) {
            return handlerInput.responseBuilder
                .speak(switchVoice("Can you say that again, sir?", voiceSelected))
                .reprompt(switchVoice(getRandomResponse(ALLTYPESOFINFO["idleresponse"]), voiceSelected))
                .withShouldEndSession(false)
                .getResponse();
        }

        // Construct the directive with the payload containing the command parameters
        let directive = Util.build(endpointId, NAMESPACE, NAME_CONTROL,
            {
                type: 'command',
                command: command,
                speed: speed
            });

        //let speechOutput = `command ${command} activated`;
        let speechOutput = `Certainly, sir. I am taking care of this right away...`;
        if (command === 'eyes open' || command === 'eye open' || command === 'aware' || command === 'mind your surroundings' || command === 'stay alert') {
            speechOutput = getRandomResponse(ALLTYPESOFINFO["awareresponse"], voiceSelected);
        }
        return handlerInput.responseBuilder
            .speak(switchVoice(speechOutput, voiceSelected))
            .addDirective(directive)
            .getResponse();
    }
};

const EventsReceivedRequestHandler = {
    // Checks for a valid token and endpoint.
    canHandle(handlerInput) {
        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) {

        console.log("== Received Custom Event ==");
        let customEvent = handlerInput.requestEnvelope.request.events[0];
        let payload = customEvent.payload;
        let name = customEvent.header.name;

        let speechOutput;
        
        // Get data from session attribute
        const attributesManager = handlerInput.attributesManager;
        voiceSelected = attributesManager.getSessionAttributes().voice || "Brian";
        
        
        if (name === 'Back_Proximity') {
            let distance = parseInt(payload.distance);
            if (distance < 50) {
                let speechOutput = getRandomResponse(ALLTYPESOFINFO["backcrashingresponse"]);
                return handlerInput.responseBuilder
                    .speak(switchVoice(speechOutput, voiceSelected), "REPLACE_ALL")
                    .reprompt(switchVoice(getRandomResponse(ALLTYPESOFINFO["idleresponse"]), voiceSelected))
                    .withShouldEndSession(false)
                    .getResponse();
            }
        }
        
        else if (name === 'Front_Proximity') {
            let distance = parseInt(payload.distance);
            let speechOutput = "";
            if (distance < 150) {
                speechOutput = "I can see an obstacle ahead, sir. What do you want me to do?";
                
            }
            
            if (distance < 100) {
                speechOutput = "We are getting closer to the obstacle! I'm slowing down for our safety, sir";
                
            }
            
            if (distance < 70) {
    
                speechOutput = getRandomResponse(ALLTYPESOFINFO["frontproximityresponse"]);
                
            }
            
            return handlerInput.responseBuilder
                    .speak(switchVoice(speechOutput, voiceSelected), "REPLACE_ALL")
                    .reprompt(switchVoice(getRandomResponse(ALLTYPESOFINFO["idleresponse"]), voiceSelected))
                    .withShouldEndSession(false)
                    .getResponse();
        }
        
        else if (name === 'Rampless_Jump_Proximity') {
            let distance = parseInt(payload.distance);
            let speechOutput = "";
            if (distance < 150) {
                speechOutput = "I am afraid road conditions don't allow me to perform a rampless jump, Master Wayne. I am not crashing myself, sir. Are you nuts?";
                
            }
            
            if (distance > 150) {
                speechOutput = "We are good to go, let's boost this thing into a rampless jump, Master Wayne!";
                
            }
            
            
            return handlerInput.responseBuilder
                    .speak(switchVoice(speechOutput, voiceSelected), "REPLACE_ALL")
                    .withShouldEndSession(false)
                    .getResponse();
        }
        
        else if (name === 'Sentry') {
            
            if ('fire' in payload) {
                speechOutput = "It seems the road is all clear now, Master Wayne.";
            }
            
            if ('dodge' in payload) {
                speechOutput = "Phew, that was a close one. Good call, sir ...";
            }
            
            
        } 
        
        else if (name === 'Speech') {
                speechOutput = payload.speechOut;
        }
        
        else if (name === 'Speed_Changed') {
                speechOutput = payload.speed_changed;
        }
        
        else if (name === 'Driverless') {
                speechOutput = payload.driverlessSpeechOut;
        }
            
        else {
                speechOutput = "Event not recognized. Awaiting new command.";
        }
            
        return handlerInput.responseBuilder
            .speak(switchVoice(speechOutput, voiceSelected), "REPLACE_ALL")
            .reprompt(switchVoice(getRandomResponse(ALLTYPESOFINFO["idleresponse"]), voiceSelected))
            .withShouldEndSession(false)
            .getResponse();
        }
};
const ExpiredRequestHandler = {
    canHandle(handlerInput) {
        return Alexa.getRequestType(handlerInput.requestEnvelope) === 'CustomInterfaceController.Expired'
    },
    handle(handlerInput) {
        console.log("== Custom Event Expiration Input ==");

        // Get data from session attribute
        const attributesManager = handlerInput.attributesManager;
        voiceSelected = attributesManager.getSessionAttributes().voice || "Brian";
        
        // 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 = `Remember I will be shutting myself down in ${duration} minutes.`;
            return handlerInput.responseBuilder
                .addDirective(Util.buildStartEventHandler(token, 60000, {}))
                .speak(switchVoice(speechOutput, voiceSelected))
                .getResponse();
        }
        else {
            // End skill session
            return handlerInput.responseBuilder
                .speak(switchVoice("I am taking a break now, Master Wayne. Goodbye.", voiceSelected))
                .withShouldEndSession(true)
                .getResponse();
        }
    }
};

// A handler to fulfill information about Batman's villains
// by the villain's name. The villain's name comes from a slot value
const GetVillainsInfoIntentHandler = {
    canHandle(handlerInput) {
        return Alexa.getRequestType(handlerInput.requestEnvelope) === 'IntentRequest'
            && Alexa.getIntentName(handlerInput.requestEnvelope) === 'GetVillainInfoIntent';
    },
    handle: function (handlerInput) {
        
        // Get data from session attribute
        
        
        const attributesManager = handlerInput.attributesManager;
        voiceSelected = attributesManager.getSessionAttributes().voice || "Brian";
        
        let villainFromPreviousRequest = attributesManager.getSessionAttributes().villainType
        let villainRequested = Alexa.getSlotValue(handlerInput.requestEnvelope, 'Villain') || villainFromPreviousRequest;

        if (!villainRequested) {
            return handlerInput.responseBuilder
                .speak(switchVoice("Can you say that again, sir?"))
                .reprompt(switchVoice(getRandomResponse(ALLTYPESOFINFO["idleresponse"]), voiceSelected)).getResponse();
        }
        
        let arrayOfVillainsInfo = VILLAINSINFO[villainRequested];
        
        Util.putSessionAttribute(handlerInput, 'villainType', villainRequested);

        return handlerInput.responseBuilder
            .speak(switchVoice(getRandomResponse(arrayOfVillainsInfo), voiceSelected))
            .reprompt(switchVoice(getRandomResponse(ALLTYPESOFINFO["idleresponse"]), voiceSelected))
            .withShouldEndSession(false)
            .getResponse();
    }
};

// A handler to fulfill jokes, advices and other responses in a randomly way
// The type of info fulfilled comes from a slot value
const GetInfoIntentHandler = {
    canHandle(handlerInput) {
        return Alexa.getRequestType(handlerInput.requestEnvelope) === 'IntentRequest'
            && Alexa.getIntentName(handlerInput.requestEnvelope) === 'GetInfoIntent';
    },
    handle: function (handlerInput) {

        // Get data from session attribute
        const attributesManager = handlerInput.attributesManager;
        voiceSelected = attributesManager.getSessionAttributes().voice || "Brian";
        
        let infoPreviousRequest = attributesManager.getSessionAttributes().infoType
        let typeOfInfoRequested = Alexa.getSlotValue(handlerInput.requestEnvelope, 'Info') || infoPreviousRequest;
        let arrayOfInfo=[];
        let speechOutput;
        
        
        if (!typeOfInfoRequested) {
            return handlerInput.responseBuilder
                .speak(switchVoice("Can you say that again, sir?"))
                .reprompt(switchVoice(getRandomResponse(ALLTYPESOFINFO["idleresponse"]), voiceSelected)).getResponse();
        }else {
            
            arrayOfInfo = ALLTYPESOFINFO[typeOfInfoRequested];
            
        }
        
        if(typeOfInfoRequested==="do"){
            
            speechOutput = getRandomResponse(ALLTYPESOFINFO["advice"])
            
        }
        
        Util.putSessionAttribute(handlerInput, 'infoType', typeOfInfoRequested);
        
        if(typeOfInfoRequested==="joke"){
            speechOutput = getRandomResponse(ALLTYPESOFINFO["jokeopeningline"])       
        }else{
            speechOutput= "";
        }

        return handlerInput.responseBuilder
        
            .speak(switchVoice(speechOutput + getRandomResponse(arrayOfInfo), voiceSelected))
            .reprompt(switchVoice(getRandomResponse(ALLTYPESOFINFO["idleresponse"]), voiceSelected))
            .withShouldEndSession(false)
            .getResponse();
    }
};

// Set the tone which Echo will use to reply to Batman.
// This allows other intent handler to use the specified speed value
// without asking the user for input.
let SetEchoVoiceIntentHandler = {
    canHandle(handlerInput) {
        return Alexa.getRequestType(handlerInput.requestEnvelope) === 'IntentRequest'
            && Alexa.getIntentName(handlerInput.requestEnvelope) === 'SwitchVoiceIntent';
    },
    handle: function (handlerInput) {

        // Bound speed to (1-100)
        
        let request = handlerInput.requestEnvelope;
        let voiceType = Alexa.getSlotValue(request, 'Voice');
        
        // Get data from session attribute
        const attributesManager = handlerInput.attributesManager;
        voiceSelected = attributesManager.getSessionAttributes().voice;
        
       if (voiceType === "man") {
            voiceSelected = "Brian";
        }
        
        if (voiceType === "woman") {
            voiceSelected = "woman";
        }
        
        
        Util.putSessionAttribute(handlerInput, 'voice', voiceSelected);
        
        let confirmation = getRandomResponse(ALLTYPESOFINFO["switchedvoiceresponse"]);
        let speechOutput = switchVoice(confirmation, voiceSelected);
        
        return handlerInput.responseBuilder
            .speak(speechOutput)
            .reprompt(switchVoice(getRandomResponse(ALLTYPESOFINFO["idleresponse"]), voiceSelected))
            .withShouldEndSession(false)
            .getResponse();
    }
};

function switchVoice(text,voice_name) {
  if (text && voice_name === "Brian"){
    return "<voice name='" + voice_name + "'>" + text + "</voice>"
  }
  
  if (voice_name==="woman"){
    return  text
  }
}

function getRandomResponse(array){
    
     // Get a random response from a list
        var infoIndex = Math.floor(Math.random() * array.length);
        var randomResponse = array[infoIndex];
        return randomResponse;
}

// Construct and send a custom directive to the connected gadget with data from
// the SetCommandIntent request.
const HelpIntentHandler = {
    canHandle(handlerInput) {
        return Alexa.getRequestType(handlerInput.requestEnvelope) === 'IntentRequest'
            && Alexa.getIntentName(handlerInput.requestEnvelope) === 'AMAZON.HelpIntent';
    },
    handle: function (handlerInput) {

        // Get data from session attribute
        const attributesManager = handlerInput.attributesManager;
        voiceSelected = attributesManager.getSessionAttributes().voice || "Brian";


        return handlerInput.responseBuilder
            .speak(switchVoice("You can say things like move forward, keep your eyes open, and tell me more about one of my enemies. I can also keep you entertained by telling you jokes, providing advice and dancing if you will, sir. What do you want to do?", voiceSelected))
            .reprompt(switchVoice("You can say things like move forward, keep your eyes open, tell me more about one of your enemies, among other things, sir. What do you want to do?", voiceSelected))
            .withShouldEndSession(false)
            .getResponse();
    }
};

// Construct and send a custom directive to the connected gadget with data from
// the SetCommandIntent request.
const StopIntentHandler = {
    canHandle(handlerInput) {
        return Alexa.getRequestType(handlerInput.requestEnvelope) === 'IntentRequest'
            && Alexa.getIntentName(handlerInput.requestEnvelope) === 'AMAZON.StopIntent';
    },
    handle: function (handlerInput) {

        // Get data from session attribute
        const attributesManager = handlerInput.attributesManager;
        voiceSelected = attributesManager.getSessionAttributes().voice || "Brian";


        return handlerInput.responseBuilder
            .speak(switchVoice("Alright, I will leave you to it, Master Wayne, just call my name whenever you need it. Remember to have some fun every once in a while, sir.", voiceSelected))
            .withShouldEndSession(true)
            .getResponse();
    }
};

// The SkillBuilder acts as the entry point for your skill, routing all request and response
// payloads to the handlers above. Make sure any new handlers or interceptors you've
// defined are included below. The order matters - they're processed top to bottom.
exports.handler = Alexa.SkillBuilders.custom()
    .addRequestHandlers(
        LaunchRequestHandler,
        SetSpeedIntentHandler,
        SetCommandIntentHandler,
        MoveIntentHandler,
        SteerIntentHandler,
        GetInfoIntentHandler,
        GetVillainsInfoIntentHandler,
        SetEchoVoiceIntentHandler,
        HelpIntentHandler,
        StopIntentHandler,
        EventsReceivedRequestHandler,
        ExpiredRequestHandler,
        Common.HelpIntentHandler,
        Common.CancelAndStopIntentHandler,
        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();

The Smart Batmobile - Main Python File

Python
The Python logic that enables EV3 Brick management. It connects with the Alexa Skill index.js file.
#!/usr/bin/env python3
# Copyright 2019 Amazon.com, Inc. or its affiliates.  All Rights Reserved.
# 
# You may not use this file except in compliance with the terms and conditions 
# set forth in the accompanying LICENSE.TXT file.
#
# THESE MATERIALS ARE PROVIDED ON AN "AS IS" BASIS. AMAZON SPECIFICALLY DISCLAIMS, WITH 
# RESPECT TO THESE MATERIALS, ALL WARRANTIES, EXPRESS, IMPLIED, OR STATUTORY, INCLUDING 
# THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, AND NON-INFRINGEMENT.

import os
import sys
import time
import logging
import json
import random
import threading
from enum import Enum

from agt import AlexaGadget

from ev3dev2.led import Leds
from ev3dev2.sound import Sound
from ev3dev2.motor import OUTPUT_A, OUTPUT_B, OUTPUT_C, OUTPUT_D, MoveTank, SpeedPercent, MediumMotor, LargeMotor
from ev3dev2.sensor.lego import InfraredSensor
from ev3dev2.sensor.lego import UltrasonicSensor

# 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 Direction(Enum):
    """
    The list of movement commands and their variations.
    These variations correspond to the skill slot values.
    """
    FORWARD = ['forward', 'forwards', 'go forward']
    BACKWARD = ['back', 'backward', 'backwards', 'go backward']
    STOP = ['stop', 'brake', 'halt']


class Steering(Enum):
    """
    The list of steering commands and their variations.
    These variations correspond to the skill slot values.
    """
    LEFT = ['left', 'go left']
    RIGHT = ['right', 'go right']
    CENTER = ['center', 'front', 'medium']


class Command(Enum):
    """
    The list of preset commands and their invocation variation.
    These variations correspond to the skill slot values.
    """
    MOVE_CIRCLE = ['circle', 'move around']
    MOVE_SQUARE = ['square']
    SENTRY = ['sentry', 'sentry mode', 'eyes open', 'eye open', 'aware', 'stay alert', 'mind your surroundings']
    PATROL = ['patrol', 'patrol mode']
    FIRE_ONE = ['cannon', '1 shot', 'one shot', 'intimidate', 'fire']
    FIRE_ALL = ['all shots', 'all shot', 'all', 'bring it down', 'get rid of it', 'blast it', 'blast']
    DODGE = ['dodge', 'avoid it', 'pass it']
    RAMPLESS_JUMP = ['rampless jump', 'jump', 'boost']
    DRIVERLESS = ['i want to rest', 'call it a night', 'tired to drive', 'take me home', 'driverless', 'driverless mode']


class EventName(Enum):
    """
    The list of custom event name sent from this gadget
    """
    SENTRY = "Sentry"
    BACK_PROXIMITY = "Back_Proximity"
    FRONT_PROXIMITY = "Front_Proximity"
    RAMPLESS_JUMP_PROXIMITY = "Rampless_Jump_Proximity"
    SPEECH = "Speech"
    SPEED_CHANGED = "Speed_Changed"
    DRIVERLESS = "Driverless"

class MindstormsGadget(AlexaGadget):
    """
    A Mindstorms gadget that can perform bi-directional interaction with an Alexa skill.
    """

    def __init__(self):
        """
        Performs Alexa Gadget initialization routines and ev3dev resource allocation.
        """
        super().__init__()

        # Robot state
        self.sentry_mode = False
        self.patrol_mode = False
        self.rampless_mode = False
        self.rampless_mode = False

        # Gadget states
        self.bpm = 0
        self.trigger_bpm = "off"

        # Connect two large motors on output ports B and C, and two medium motors on ports A and D.
        self.drive = MoveTank(OUTPUT_B, OUTPUT_C)
        self.weapon = MediumMotor(OUTPUT_A)
        self.steer = MediumMotor(OUTPUT_D)
        self.left_motor = LargeMotor(OUTPUT_B)
        self.right_motor = LargeMotor(OUTPUT_C)
        self.arms = MediumMotor(OUTPUT_D)
        self.sound = Sound()
        self.leds = Leds()

        # Connect one ultrasonic and Infrared sensor on any numeric port.

        self.ir = InfraredSensor()
        self.us = UltrasonicSensor()

        # Start threads
        threading.Thread(target=self._patrol_thread, daemon=True).start()
        threading.Thread(target=self._front_proximity_thread, daemon=True).start()
        threading.Thread(target=self._rampless_jump_proximity_thread, daemon=True).start()
        threading.Thread(target=self._back_proximity_thread, daemon=True).start()
        #threading.Thread(target=self._driverless_mode_thread, daemon=True).start()

    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", "GREEN")
        self.leds.set_color("RIGHT", "GREEN")
        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_alexa_gadget_statelistener_stateupdate(self, directive):
        """
        Listens for the wakeword state change and react by turning on the LED.
        :param directive: contains a payload with the updated state information from Alexa
        """
        color_list = ['BLACK', 'AMBER', 'YELLOW', 'GREEN']
        for state in directive.payload.states:
            if state.name == 'wakeword':

                if state.value == 'active':
                    print("Wake word active", file=sys.stderr)
                    self.sound.play_song((('A3', 'e'), ('C5', 'e')))
                    for i in range(0, 4, 1):
                        self.leds.set_color("LEFT", color_list[i], (i * 0.25))
                        self.leds.set_color("RIGHT", color_list[i], (i * 0.25))
                        time.sleep(0.25)

                elif state.value == 'cleared':
                    print("Wake word cleared", file=sys.stderr)
                    self.sound.play_song((('C5', 'e'), ('A3', 'e')))
                    for i in range(3, -1, -1):
                        self.leds.set_color("LEFT", color_list[i], (i * 0.25))
                        self.leds.set_color("RIGHT", color_list[i], (i * 0.25))
                        time.sleep(0.25)


    def on_custom_mindstorms_gadget_control(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)
            control_type = payload["type"]
            if control_type == "move":

                # Expected params: [direction, duration, speed]
                self._move(payload["direction"], int(payload["duration"]), int(payload["speed"]))

            if control_type == "steer":

                # Expected params: [steering_direction, previous_steering_direction]
                self._steer(payload["steering_direction"], payload["previous_steering_direction"])

            if control_type == "command":
                # Expected params: [command]
                self._activate(payload["command"])

        except KeyError:
            print("Missing expected parameters: {}".format(directive), file=sys.stderr)

    def _move(self, direction, duration: int, speed: int, is_blocking=False):
        """
        Handles vertical move and stop the car commands from the directive.
        :param direction: the move direction
        :param duration: the duration in seconds
        :param speed: the speed percentage as an integer
        :param is_blocking: if set, motor run until duration expired before accepting another command
        """
        print("Move command: ({}, {}, {}, {})".format(direction, speed, duration, is_blocking), file=sys.stderr)
        if direction in Direction.FORWARD.value:
            self.drive.on_for_seconds(SpeedPercent(speed), SpeedPercent(speed), duration, block=is_blocking)

        if direction in Direction.BACKWARD.value:
            self.drive.on_for_seconds(SpeedPercent(-speed), SpeedPercent(-speed), duration, block=is_blocking)

        if direction in Direction.STOP.value:
            self.drive.off()
            self.patrol_mode = False


    def _steer(self, steering_direction, previous_steering_direction, is_blocking=False):
        """
        Handles steering drive commands from the directive.
        :param steering_direction: where to move direction
        :param previous_steering_direction: it remembers the previous steering direction so it can return to center when needed
        :param is_blocking: if set, motor run until duration expired before accepting another command
        """
        print("Steering command: ({}, {}, {})".format(steering_direction, previous_steering_direction, is_blocking), file=sys.stderr)

        previous_move = previous_steering_direction

        if steering_direction in Steering.RIGHT.value:
            if previous_move in Steering.RIGHT.value: self.steer.on_for_seconds(SpeedPercent(0), 0) 
            
            else: self.steer.on_for_seconds(SpeedPercent(30), 0.5)

        if steering_direction in Steering.LEFT.value:
            if previous_move in Steering.LEFT.value:
                self.steer.on_for_seconds(SpeedPercent(0), 0)
            
            else: self.steer.on_for_seconds(SpeedPercent(-30), 0.5)

        if steering_direction in Steering.CENTER.value:
            
            if previous_move in Steering.RIGHT.value:
                self.steer.on_for_seconds(SpeedPercent(-13), 0.5)

            if previous_move in Steering.LEFT.value:
                self.steer.on_for_seconds(SpeedPercent(13), 0.5)

            if previous_move in Steering.CENTER.value:
                self.steer.on_for_seconds(SpeedPercent(0), 0)

    def on_alexa_gadget_musicdata_tempo(self, directive):
        """
        Provides the music tempo of the song currently playing on the Echo device.
        :param directive: the music data directive containing the beat per minute value
        """
        tempo_data = directive.payload.tempoData
        for tempo in tempo_data:

            print("tempo value: {}".format(tempo.value), file=sys.stderr)
            if tempo.value > 0:
                # dance pose
                self.right_motor.run_timed(speed_sp=750, time_sp=2500)
                self.left_motor.run_timed(speed_sp=-750, time_sp=2500)
                self.leds.set_color("LEFT", "GREEN")
                self.leds.set_color("RIGHT", "GREEN")
                time.sleep(3)
                # starts the dance loop
                self.trigger_bpm = "on"
                threading.Thread(target=self._dance_loop, args=(tempo.value,)).start()

            elif tempo.value == 0:
                # stops the dance loop
                self.trigger_bpm = "off"
                self.leds.set_color("LEFT", "BLACK")
                self.leds.set_color("RIGHT", "BLACK")
    
    def _dance_loop(self, bpm):
        """
        Perform motor movement in sync with the beat per minute value from tempo data.
        :param bpm: beat per minute from AGT
        """
        color_list = ["GREEN", "RED", "AMBER", "YELLOW"]
        led_color = random.choice(color_list)
        motor_speed = 400
        motor_speed_half = 300
        milli_per_beat = min(1000, (round(60000 / bpm)) * 0.65)
        print("Adjusted milli_per_beat: {}".format(milli_per_beat), file=sys.stderr)
        while self.trigger_bpm == "on":

            # Alternate led color and motor direction
            led_color = "BLACK" if led_color != "BLACK" else random.choice(color_list)
            motor_speed = -motor_speed
            motor_speed_half = -motor_speed_half

            self.leds.set_color("LEFT", led_color)
            self.leds.set_color("RIGHT", led_color)
            #self.steer.on_for_rotations(SpeedPercent(motor_speed), time_sp=150)
            #self.steer.on_for_rotations(SpeedPercent(-motor_speed), time_sp=150)
            self.right_motor.run_timed(speed_sp=motor_speed, time_sp=150)
            self.left_motor.run_timed(speed_sp=motor_speed, time_sp=150)
            time.sleep(milli_per_beat / 1200)
            self.right_motor.run_timed(speed_sp=-motor_speed, time_sp=150)
            self.left_motor.run_timed(speed_sp=-motor_speed, time_sp=150)
            #self.arms.run_timed(speed_sp=motor_speed_half, time_sp=300)
            #self.arms.run_timed(speed_sp=-motor_speed_half, time_sp=300)
            time.sleep(milli_per_beat / 1200)

        print("Exiting BPM process.", file=sys.stderr)      

    def _activate(self, command, speed=50):
        """
        Handles preset commands.
        :param command: the preset command
        :param speed: the speed if applicable
        """
        print("Activate command: ({}, {})".format(command, speed), file=sys.stderr)
        if command in Command.MOVE_CIRCLE.value:
            self.drive.on_for_seconds(SpeedPercent(int(speed)), SpeedPercent(5), 12)

        if command in Command.MOVE_SQUARE.value:
            for i in range(4):
                self._move("right", 2, speed, is_blocking=True)

        if command in Command.PATROL.value:
            # Set patrol mode to resume patrol thread processing
            self.patrol_mode = True

        if command in Command.SENTRY.value:
            self.sentry_mode = True
            #self._send_event(EventName.SPEECH, {'speechOut': "Sentry mode activated"})

            # Perform Shuffle posture
            self.drive.on_for_seconds(SpeedPercent(20), SpeedPercent(20), 0.2)
            time.sleep(0.3)
            self.drive.on_for_seconds(SpeedPercent(-20), SpeedPercent(-20), 0.2)

            self.leds.set_color("LEFT", "YELLOW", 1)
            self.leds.set_color("RIGHT", "YELLOW", 1)

        if command in Command.FIRE_ONE.value:
            print("Fire one", file=sys.stderr)
            self.weapon.on_for_rotations(SpeedPercent(100), 3)
            self._send_event(EventName.SENTRY, {'fire': 1})
            self.sentry_mode = False
            print("Sent sentry event - 1 shot, alarm reset", file=sys.stderr)
            self.leds.set_color("LEFT", "GREEN", 1)
            self.leds.set_color("RIGHT", "GREEN", 1)

        if command in Command.FIRE_ALL.value:
            print("Fire all", file=sys.stderr)
            self.weapon.on_for_rotations(SpeedPercent(100), 10)
            self._send_event(EventName.SENTRY, {'fire': 3})
            self.sentry_mode = False
            print("sent sentry event - 3 shots, alarm reset", file=sys.stderr)
            self.leds.set_color("LEFT", "GREEN", 1)
            self.leds.set_color("RIGHT", "GREEN", 1) 

        if command in Command.DODGE.value:
            print("Dodging", file=sys.stderr)
            self.steer.on_for_rotations(SpeedPercent(-30), 0.5)
            self.drive.on_for_seconds(SpeedPercent(50), SpeedPercent(50), 5)
            self._send_event(EventName.SENTRY, {'dodge': "We just dodged that obstacle sir ..."})
            self.sentry_mode = False
            print("sent sentry event - dodged, alarm reset", file=sys.stderr)
            self.leds.set_color("LEFT", "GREEN", 1)
            self.leds.set_color("RIGHT", "GREEN", 1)

        if command in Command.RAMPLESS_JUMP.value:
            print("Rampless Jump Feasibility", file=sys.stderr)
            self._send_event(EventName.SPEECH, {'speechOut': "Let me see if we can do a rampless jump, sir."})
            time.sleep(2)
            self.rampless_mode = True
            
            # Perform Shuffle posture

            self.leds.set_color("LEFT", "YELLOW", 1)
            self.leds.set_color("RIGHT", "YELLOW", 1)

        if command in Command.DRIVERLESS.value:
            print("Driverless Mode Activated", file=sys.stderr)
            self._send_event(EventName.DRIVERLESS, {'driverlessSpeechOut': "Rest assured, Master Wayne. I'll handle this. Rest well, sir."})
            self.sentry_mode = True

            # Perform Driverless posture

            self.leds.set_color("LEFT", "YELLOW", 1)
            self.leds.set_color("RIGHT", "YELLOW", 1)
            self.rampless_mode = False
            self.patrol_mode = False

            self.drive.on_for_seconds(SpeedPercent(10), SpeedPercent(10), 2)
            self.drive.on_for_seconds(SpeedPercent(20), SpeedPercent(20), 2)
            self.drive.on_for_seconds(SpeedPercent(30), SpeedPercent(30), 2)
            self.drive.on_for_seconds(SpeedPercent(40), SpeedPercent(40), 2)
            self.drive.on_for_seconds(SpeedPercent(50), SpeedPercent(50), 60)

        
    def _send_event(self, name: EventName, payload):
        """
        Sends a custom event to trigger a sentry or forward/backward proximity action.
        :param name: the name of the custom event
        :param payload: the sentry JSON payload
        """
        self.send_custom_event('Custom.Mindstorms.Gadget', name.value, payload)

    
    def _front_proximity_thread(self):
        """
        Monitors three front distance ranges between the car and an obstacle when sentry mode is activated.
        If the minimum distance is breached for each range, send a custom event to trigger action on
        the Alexa skill.
        """
        count = 0
        while True:
            while self.sentry_mode:
                distance = self.us.distance_centimeters_continuous
                time.sleep(0.1)
                print("Front Proximity: {}".format(distance), file=sys.stderr)

                if distance < 150:
                    count = count + 1 if distance < 150 else 0
                    if count > 3:
                        print("Front Proximity breached <150. Sending event to skill", file=sys.stderr)
                        self._send_event(EventName.FRONT_PROXIMITY, {'distance': distance})
                        self.drive.on_for_seconds(SpeedPercent(int(distance/5)), SpeedPercent(int(distance/5)), 3)
                        self.patrol_mode = False
                        self.leds.set_color("LEFT", "RED", 1)
                        self.leds.set_color("RIGHT", "RED", 1)
                        count = 0
                    time.sleep(0.2)

                if distance < 100:
                    count = count + 1 if distance < 100 else 0
                    if count > 3:

                        print("Front Proximity breached < 100. Sending event to skill", file=sys.stderr)
                        self._send_event(EventName.FRONT_PROXIMITY, {'distance': distance})
                        time.sleep(1)
                        self.drive.on_for_seconds(SpeedPercent(int(distance/4)), SpeedPercent(int(distance/4)), 3)
                
                        self.patrol_mode = False
                        self.leds.set_color("LEFT", "RED", 1)
                        self.leds.set_color("RIGHT", "RED", 1)
                        count = 0
                    time.sleep(0.2)

                if distance < 70:
                    count = count + 1 if distance < 70 else 0
                    if count > 3:
                        print("Front Proximity breached < 70. Sending event to skill", file=sys.stderr)
                        self._send_event(EventName.FRONT_PROXIMITY, {'distance': distance})
                        time.sleep(1)
                        self.drive.on_for_seconds(SpeedPercent(int(distance/3)), SpeedPercent(int(distance/3)), 1)
                        self.drive.off()
                        self.sentry_mode = False
                    
                        self.patrol_mode = False
                        self.leds.set_color("LEFT", "RED", 1)
                        self.leds.set_color("RIGHT", "RED", 1)
                        count = 0
                    time.sleep(0.2)

            time.sleep(1)


    def _back_proximity_thread(self):
        """
        Monitors the distance between the car and an obstacle on the rear when sentry mode is activated.
        If the minimum distance is breached, send a custom event to trigger action on
        the Alexa skill.
        """
        count = 0
        while True:
            while self.sentry_mode:
                distance = self.ir.proximity
                print("Back Proximity: {}".format(distance), file=sys.stderr)
                count = count + 1 if distance < 50 else 0
                if count > 3:
                    print("Back Proximity breached. Sending event to skill", file=sys.stderr)
                    self._send_event(EventName.BACK_PROXIMITY, {'distance': distance})
                    time.sleep(0.2)
                    self.drive.on_for_seconds(SpeedPercent(10), SpeedPercent(10), 2, block=False)
                    self.drive.on_for_seconds(SpeedPercent(20), SpeedPercent(20), 1, block=False)
                    self.drive.off()
                    self.patrol_mode = False
                    self.leds.set_color("LEFT", "RED", 1)
                    self.leds.set_color("RIGHT", "RED", 1)
                    
                    self.sentry_mode = False

                time.sleep(0.2)
            time.sleep(1)

    def _rampless_jump_proximity_thread(self):
        """
        Monitors the distance between the car and an obstacle in front when rampless jump mode is activated.
        If one of two minimum distances are breached, send a custom event to trigger action on
        the Alexa skill.
        """
        count = 0
        while True:
            while self.rampless_mode:
                distance = self.us.distance_centimeters_continuous
                time.sleep(0.1)
                print("Rampless Jump Proximity: {}".format(distance), file=sys.stderr)

                if distance < 150:
                    count = count + 1 if distance < 150 else 0
                    if count > 3:
                        print("Rampless Jump Proximity < 150. Sending event to skill", file=sys.stderr)
                        self._send_event(EventName.RAMPLESS_JUMP_PROXIMITY, {'distance': distance})
                        time.sleep(1)
                        self.patrol_mode = False
                        self.leds.set_color("LEFT", "RED", 1)
                        self.leds.set_color("RIGHT", "RED", 1)
                        self.rampless_mode = False
                        count = 0
                    time.sleep(0.2)

                if distance > 150:
                    count = count + 1 if distance > 150 else 0
                    if count > 3:
                        print("Rampless Jump Proximity > 150. Sending event to skill", file=sys.stderr)
                        self._send_event(EventName.RAMPLESS_JUMP_PROXIMITY, {'distance': distance})
                        time.sleep(1)
                        self.drive.on_for_seconds(SpeedPercent(60), SpeedPercent(60), 1)
                        self.drive.on_for_seconds(SpeedPercent(80), SpeedPercent(80), 1)
                        self.drive.on_for_seconds(SpeedPercent(100), SpeedPercent(100), 3)
                        self.drive.on_for_seconds(SpeedPercent(80), SpeedPercent(80), 1)
                        self.drive.on_for_seconds(SpeedPercent(60), SpeedPercent(60), 1)
                        self.drive.on_for_seconds(SpeedPercent(40), SpeedPercent(40), 1)
                        self.drive.on_for_seconds(SpeedPercent(20), SpeedPercent(20), 1)
                        self.patrol_mode = False
                        self.leds.set_color("LEFT", "RED", 1)
                        self.leds.set_color("RIGHT", "RED", 1)
                        self.rampless_mode = False
                        count = 0
                    time.sleep(0.2)

            time.sleep(1)

    def _patrol_thread(self):
        """
        Performs random movement when patrol mode is activated.
        """
        while True:
            while self.patrol_mode:
                print("Patrol mode activated randomly picks a path", file=sys.stderr)
                direction = random.choice(list(Direction))
                duration = random.randint(1, 5)
                speed = random.randint(1, 4) * 25

                while direction == Direction.STOP:
                    direction = random.choice(list(Direction))

                # direction: all except stop, duration: 1-5s, speed: 25, 50, 75, 100
                self._move(direction.value[0], duration, speed)
                time.sleep(duration)
            time.sleep(1)


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

Andrés Giraldo Raigoza

Andrés Giraldo Raigoza

1 project • 1 follower
Luis B Caicedo

Luis B Caicedo

0 projects • 1 follower
https://www.linkedin.com/in/labdesigns/

Comments