Tamoghna Das
Created December 11, 2019 © MIT

Alexa controlled Garage Door with Mindstorm Ev3

A Mindstorm EV3 Garage Door, which takes command from Alexa, check the secret code and let one in.

AdvancedProtip6 hours103

Things used in this project

Story

Read more

Custom parts and enclosures

Video Demo - Alexa Controlled garage door

This is a video demonstration of the Alexa controlled EV3 garage door model.

Code

index.js

JavaScript
Skill logic index.js
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"></audio>';

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

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(`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
        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', 4);

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

        let speechOutput = "Welcome, voice interface activated";
        return handlerInput.responseBuilder
            .speak(speechOutput + BG_MUSIC)
            .addDirective(Util.buildStartEventHandler(token,60000, {}))
            .getResponse();
    }
};

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

        // Get data from session attribute
        const attributesManager = handlerInput.attributesManager;
        const endpointId = attributesManager.getSessionAttributes().endpointId || [];

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

        return handlerInput.responseBuilder
            .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) {
        const request = handlerInput.requestEnvelope;
        // Code is optional.
        let code = Alexa.getSlotValue(request, 'Code') || "0";
        
        let command = Alexa.getSlotValue(request, 'Command') || "CheckCode";
        

        const attributesManager = handlerInput.attributesManager;
        let endpointId = attributesManager.getSessionAttributes().endpointId || [];
        
        // Construct the directive with the payload containing the move parameters
        let directive = Util.build(endpointId, NAMESPACE, NAME_CONTROL,
            {
                type: 'command',
                command: command,
                code: code
            });

        let speechOutput = `command ${command} activated`;
        if (command === 'guard' || command === 'guard mode' || command === "CheckCode") {
            speechOutput = '';
        }
        return handlerInput.responseBuilder
            .speak(speechOutput + BG_MUSIC)
            .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;
        if (name === 'Proximity') {
            let distance = parseInt(payload.distance);
            if (distance < 20) {
                let speechOutput = "Hello! What do you want?";
                return handlerInput.responseBuilder
                    .speak(speechOutput, "REPLACE_ALL")
                    .withShouldEndSession(false)
                    .getResponse();
            }
        } else if (name === 'Speech') {
            speechOutput = payload.speechOut;

        } else if (name === 'SpeechWait') {
            speechOutput = payload.speechOut;
            return handlerInput.responseBuilder
                    .speak(speechOutput, "REPLACE_ALL")
                    .withShouldEndSession(false)
                    .getResponse();
        }
        else {
            speechOutput = "Event not recognized. Awaiting new command.";
        }
        return handlerInput.responseBuilder
            .speak(speechOutput + BG_MUSIC, "REPLACE_ALL")
            .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 + BG_MUSIC)
                .getResponse();
        }
        else {
            // End skill session
            return handlerInput.responseBuilder
                .speak("Skill duration expired. Goodbye.")
                .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,
        SetCommandIntentHandler,
        DoorIntentHandler,
        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();

util.js

JavaScript
Skill logic util.js
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"></audio>';

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

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(`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
        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', 2);

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

        let speechOutput = "Welcome, voice interface activated";
        return handlerInput.responseBuilder
            .speak(speechOutput + BG_MUSIC)
            .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) {

        // Bound speed to (1-100)
        let speed = Alexa.getSlotValue(handlerInput.requestEnvelope, 'Speed');
        speed = Math.max(1, Math.min(100, parseInt(speed)));
        Util.putSessionAttribute(handlerInput, 'speed', speed);

        let speechOutput = `speed set to ${speed} percent.`;
        return handlerInput.responseBuilder
            .speak(speechOutput + BG_MUSIC)
            .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');

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

        // Get data from session attribute
        const attributesManager = handlerInput.attributesManager;
        const speed = attributesManager.getSessionAttributes().speed || "50";
        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: speed
            });

        //const speechOutput = (direction === "brake")
        //    ?  "Applying brake"
        //    : `${direction} ${duration} seconds at ${speed} percent speed`;

        //return handlerInput.responseBuilder
        //    .speak(speechOutput + BG_MUSIC)
        //    .addDirective(directive)
        //    .getResponse();
        
        return handlerInput.responseBuilder
            .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) {
        const request = handlerInput.requestEnvelope;
        // Code is optional.
        let code = Alexa.getSlotValue(request, 'Code') || "0";
        
        let command = Alexa.getSlotValue(request, 'Command') || "CheckCode";
        
        
        //if (!command) {
        //    return handlerInput.responseBuilder
        //        .speak("Can you repeat that?")
        //        .withShouldEndSession(false)
        //        .getResponse();
        //}

        const attributesManager = handlerInput.attributesManager;
        let endpointId = attributesManager.getSessionAttributes().endpointId || [];
        let speed = attributesManager.getSessionAttributes().speed || "50";

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

        let speechOutput = `command ${command} activated`;
        if (command === 'sentry' || command === 'sentry mode' || command === "CheckCode") {
            speechOutput = '';
        }
        return handlerInput.responseBuilder
            .speak(speechOutput + BG_MUSIC)
            .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;
        if (name === 'Proximity') {
            let distance = parseInt(payload.distance);
            if (distance < 20) {
                let speechOutput = "Hello! What do you want?";
                return handlerInput.responseBuilder
                    .speak(speechOutput, "REPLACE_ALL")
                    .withShouldEndSession(false)
                    .getResponse();
            }
        } else if (name === 'Sentry') {
            if ('fire' in payload) {
                speechOutput = "Threat eliminated";
            }

        } else if (name === 'Speech') {
            speechOutput = payload.speechOut;

        } else if (name === 'SpeechWait') {
            speechOutput = payload.speechOut;
            return handlerInput.responseBuilder
                    .speak(speechOutput, "REPLACE_ALL")
                    .withShouldEndSession(false)
                    .getResponse();
        }
        else {
            speechOutput = "Event not recognized. Awaiting new command.";
        }
        return handlerInput.responseBuilder
            .speak(speechOutput + BG_MUSIC, "REPLACE_ALL")
            .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 + BG_MUSIC)
                .getResponse();
        }
        else {
            // End skill session
            return handlerInput.responseBuilder
                .speak("Skill duration expired. Goodbye.")
                .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,
        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();

doorkeeper.py

Python
The Ev3 gadget source code file.
#!/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

from ev3dev2.led import Leds
from ev3dev2.sound import Sound
from ev3dev2.motor import OUTPUT_A, MediumMotor, SpeedPercent
from ev3dev2.sensor.lego import InfraredSensor

# 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 open/close commands and their variations.
    These variations correspond to the skill slot values.
    """
    OPEN = ['open', 'open door', 'door open']
    CLOSE = ['close', 'close door', 'door close']


class Command(Enum):
    """
    The list of preset commands and their invocation variation.
    These variations correspond to the skill slot values.
    """
    GUARD = ['guard', 'guard mode','doorkeeper']
    CODECHECK = ['CheckCode']


class EventName(Enum):
    """
    The list of custom event name sent from this gadget
    """
    PROXIMITY = "Proximity"
    SPEECH = "Speech"
    SPEECHWAIT = "SpeechWait"
    
    
class Code(Enum):
    CODE_GARBAGE = ['?']
    
class CodeValue:
    CODE = 4567



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.guard_mode = False
        self.patrol_mode = False
        self.is_door_open = False

        # Connect two large motors on output ports B and C
        self.sound = Sound()
        self.leds = Leds()
        self.ir = InfraredSensor()
        self.door = MediumMotor(OUTPUT_A)

        # Start threads
        threading.Thread(target=self._proximity_thread, daemon=True).start()
        
        # Code check parameters
        self.codecheckCounter = 0
        self.codeCheckLimitReached = False
        self.start_time = 0
        self.elapsed_time = 0

    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_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 == "doorIntent":

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

            if control_type == "command":
                # Expected params: [command]
                if payload["code"] not in Code.CODE_GARBAGE.value:
                    self._activate(payload["command"],int(payload["code"]))
                else:
                    self._send_event(EventName.SPEECHWAIT, {'speechOut': "Can you repeat that?"})

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

    def _door_intent(self, direction, is_blocking=False):
        """
        Handles door intent commands from the directive.
        
        
        """
        print("Door Intent command: ({}, {})".format(direction, is_blocking), file=sys.stderr)
        
        if direction in Direction.OPEN.value:
            self._check_elspsed_time()
            if (not self.codeCheckLimitReached):
                if (not self.is_door_open):
                    self._send_event(EventName.SPEECHWAIT, {'speechOut': "Please say the secret code"})
                else:
                    print("Door already open", file=sys.stderr)
                    self._send_event(EventName.SPEECH, {'speechOut': "Door already open!"})	
             
            
        if direction in Direction.CLOSE.value:
            if (self.is_door_open):
                print("Closing Door", file=sys.stderr)
                self._send_event(EventName.SPEECH, {'speechOut': "Door Closed!"})
                self.door.on_for_seconds(SpeedPercent(-20), 2)
                self.is_door_open = False
                self.guard_mode = True
                
            else:
                print("Door already closed", file=sys.stderr)
                self._send_event(EventName.SPEECH, {'speechOut': "Door already closed"})

    def _activate(self, command, code: int):
        """
        Handles preset commands.
        :param command: the commands e.g. activate guard mode
        :param code: the secret code
        """
        print("Activate command: ({}, {})".format(command, code), file=sys.stderr)
        if command in Command.GUARD.value:
            self.guard_mode = True
            self._send_event(EventName.SPEECH, {'speechOut': "Guard mode activated"})

            # Perform Shuffle posture
            self.leds.set_color("LEFT", "YELLOW", 1)
            self.leds.set_color("RIGHT", "YELLOW", 1)

        if command in Command.CODECHECK.value:
            
            self._check_elspsed_time()
            if (not self.codeCheckLimitReached):
                #self.guard_mode = False
                if code == CodeValue.CODE:
                    print("Code check successful;", file=sys.stderr)
                    self.codeCheckLimitReached = False
                    self.codecheckCounter = 0
                    if (not self.is_door_open):
                        print("Opening Door", file=sys.stderr)
                        self._send_event(EventName.SPEECH, {'speechOut': "Door Opened!"})
                        self.door.on_for_seconds(SpeedPercent(20), 2)
                        self.is_door_open = True
                        #self.guard_mode = False
                    else:
                        print("Door already open", file=sys.stderr)
                        self._send_event(EventName.SPEECH, {'speechOut': "Door already open!"})
                else:
                    #self.guard_mode = False
                    print("Code not right", file=sys.stderr)
                    self.codecheckCounter = self.codecheckCounter + 1
                    #self.guard_mode = True
                    if self.codecheckCounter > 2:
                        self.codeCheckLimitReached = True
                        self.start_time = time.time()
                        self._send_event(EventName.SPEECH, {'speechOut': "Max try reached. Try again after 1 minute"})
                        time.sleep(5)
                        self.guard_mode = True
                    else:
                        self._send_event(EventName.SPEECHWAIT, {'speechOut': "Code Wrong! Please try again"})
                

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

    def _proximity_thread(self):
        """
        Monitors anything in front of the door when guard 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.guard_mode:
                distance = self.ir.proximity
                print("Proximity: {}".format(distance), file=sys.stderr)
                count = count + 1 if distance < 20 else 0
                if count > 3:
                    if (not self.codeCheckLimitReached):
                        print("Somebody too close. Sending event to skill", file=sys.stderr)
                        self.leds.set_color("LEFT", "RED", 1)
                        self.leds.set_color("RIGHT", "RED", 1)

                        self._send_event(EventName.PROXIMITY, {'distance': distance})
                        count = 0
                    self.guard_mode = False

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

    def _check_elspsed_time(self):
        if self.codeCheckLimitReached:
            if time.time() - self.start_time > 60:
                self.codeCheckLimitReached = False
                self.codecheckCounter = 0
        


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")

model.json

JSON
Skill interaction model .json file
{
    "interactionModel": {
        "languageModel": {
            "invocationName": "mindstorms",
            "intents": [
                {
                    "name": "AMAZON.CancelIntent",
                    "samples": []
                },
                {
                    "name": "AMAZON.HelpIntent",
                    "samples": []
                },
                {
                    "name": "AMAZON.StopIntent",
                    "samples": []
                },
                {
                    "name": "AMAZON.NavigateHomeIntent",
                    "samples": []
                },
                {
                    "name": "DoorIntent",
                    "slots": [
                        {
                            "name": "Direction",
                            "type": "DirectionType"
                        }
                    ],
                    "samples": [
                        "{Direction} now",
                        "Please {Direction} the Door"
                    ]
                },
                {
                    "name": "SetCommandIntent",
                    "slots": [
                        {
                            "name": "Command",
                            "type": "CommandType"
                        },
                        {
                            "name": "Code",
                            "type": "AMAZON.NUMBER"
                        }
                    ],
                    "samples": [
                        "activate {Command} mode",
                        "activate {Command}",
                        "{Code}",
                        "Code is {Code}",
                        "Passcode is {Code}"
                    ]
                }
            ],
            "types": [
                {
                    "name": "DirectionType",
                    "values": [
                        {
                            "name": {
                                "value": "open"
                            }
                        },
                        {
                            "name": {
                                "value": "close"
                            }
                        }
                    ]
                },
                {
                    "name": "CommandType",
                    "values": [
                        {
                            "name": {
                                "value": "guard"
                            }
                        }
                    ]
                }
            ]
        }
    }
}

common.js

JavaScript
Skill logic common.js
'use strict'

const Alexa = require('ask-sdk-core');

const HelpIntentHandler = {
    canHandle(handlerInput) {
        return Alexa.getRequestType(handlerInput.requestEnvelope) === 'IntentRequest'
            && Alexa.getIntentName(handlerInput.requestEnvelope) === 'AMAZON.HelpIntent';
    },
    handle(handlerInput) {
        const speakOutput = 'You can say hello to me! How can I help?';

        return handlerInput.responseBuilder
            .speak(speakOutput)
            .reprompt(speakOutput)
            .getResponse();
    }
};
const CancelAndStopIntentHandler = {
    canHandle(handlerInput) {
        return Alexa.getRequestType(handlerInput.requestEnvelope) === 'IntentRequest'
            && (Alexa.getIntentName(handlerInput.requestEnvelope) === 'AMAZON.CancelIntent'
                || Alexa.getIntentName(handlerInput.requestEnvelope) === 'AMAZON.StopIntent');
    },
    handle(handlerInput) {
        const speakOutput = 'Goodbye!';
        return handlerInput.responseBuilder
            .speak(speakOutput)
            .getResponse();
    }
};
const SessionEndedRequestHandler = {
    canHandle(handlerInput) {
        return Alexa.getRequestType(handlerInput.requestEnvelope) === 'SessionEndedRequest';
    },
    handle(handlerInput) {
        // Any cleanup logic goes here.
        return handlerInput.responseBuilder.getResponse();
    }
};

// The intent reflector is used for interaction model testing and debugging.
// It will simply repeat the intent the user said. You can create custom handlers
// for your intents by defining them above, then also adding them to the request
// handler chain below.
const IntentReflectorHandler = {
    canHandle(handlerInput) {
        return Alexa.getRequestType(handlerInput.requestEnvelope) === 'IntentRequest';
    },
    handle(handlerInput) {
        const intentName = Alexa.getIntentName(handlerInput.requestEnvelope);
        const speakOutput = `You just triggered ${intentName}`;

        return handlerInput.responseBuilder
            .speak(speakOutput)
            .reprompt("I don't understand this command, try again")
            .getResponse();
    }
};

// Generic error handling to capture any syntax or routing errors. If you receive an error
// stating the request handler chain is not found, you have not implemented a handler for
// the intent being invoked or included it in the skill builder below.
const ErrorHandler = {
    canHandle() {
        return true;
    },
    handle(handlerInput, error) {
        console.log(`~~~~ Error handled: ${error.stack}`);
        const speakOutput = `Sorry, I had trouble doing what you asked. Please try again.`;

        return handlerInput.responseBuilder
            .speak(speakOutput)
            .reprompt(speakOutput)
            .getResponse();
    }
};

// The request interceptor is used for request handling testing and debugging.
// It will simply log the request in raw json format before any processing is performed.
const RequestInterceptor = {
    process(handlerInput) {
        let { attributesManager, requestEnvelope } = handlerInput;
        let sessionAttributes = attributesManager.getSessionAttributes();

        // Log the request for debug purposes.
        console.log(`=====Request==${JSON.stringify(requestEnvelope)}`);
        console.log(`=========SessionAttributes==${JSON.stringify(sessionAttributes, null, 2)}`);
    }
};

module.exports = {
    HelpIntentHandler,
    CancelAndStopIntentHandler,
    SessionEndedRequestHandler,
    IntentReflectorHandler,
    ErrorHandler,
    RequestInterceptor
};

package.json

JSON
Skill logic package.json
{
  "name": "agt-mindstorms",
  "version": "1.1.0",
  "description": "A sample skill demonstrating how to use AGT with Lego Mindstorms",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "Amazon Alexa",
  "license": "ISC",
  "dependencies": {
    "ask-sdk-core": "^2.6.0",
    "ask-sdk-model": "^1.18.0",
    "aws-sdk": "^2.326.0",
    "request": "^2.81.0",
    "lodash": "^4.17.11"
  }
}

package.json

JSON
Skill logic package.json
{
  "name": "agt-mindstorms",
  "version": "1.1.0",
  "description": "A skill to control EV3 garage door with Lego Mindstorms",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "Amazon Alexa",
  "license": "ISC",
  "dependencies": {
    "ask-sdk-core": "^2.6.0",
    "ask-sdk-model": "^1.18.0",
    "aws-sdk": "^2.326.0",
    "request": "^2.81.0",
    "lodash": "^4.17.11"
  }
}

Credits

Tamoghna Das

Tamoghna Das

1 project • 1 follower

Comments