Hackster is hosting Hackster Holidays, Ep. 4: Livestream & Giveaway Drawing. Start streaming on Wednesday!Stream Hackster Holidays, Ep. 4 on Wednesday!
cgo22
Created December 29, 2019 © GPL3+

Battle LEGO Robots with Alexa as Your Gamemaster

You can use Alexa as the gamemaster of your next LEGO robot battle where she can assist with setup, keep score, and act as your referee.

AdvancedFull instructions provided16 hours131

Things used in this project

Hardware components

Mindstorms EV3 Programming Brick / Kit
LEGO Mindstorms EV3 Programming Brick / Kit
You need one MINDSTORMS Kit (Home or Education Version) per robot
×1
Echo Dot
Amazon Alexa Echo Dot
You will need one per robot.
×2
Micro SD Card
You will need one per robot.
×2
LEGO Education EV3 Core Set
The Education Version can be used, but it also requires the EV3 Expansion Set, Infrared Beacon, and Infrared sensor.
×1
LEGO Education EV3 Expansion Set
×1
LEGO Infrared Beacon
(only if using the LEGO Education Version of MINDSTORMS)
×1
LEGO Infrared Sensor
(only if using the LEGO Education Version of MINDSTORMS)
×1

Software apps and online services

ev3dev software
Visual Studio Code
Alexa Skills Kit
Amazon Alexa Alexa Skills Kit
Alexa Gadgets Toolkit
Amazon Alexa Alexa Gadgets Toolkit
balenaEtcher

Story

Read more

Schematics

IR Target build

Code

LEGO Robot Battle With Amazon Alexa

Python
This is the Python 3 code for the EV3 brick
#!/usr/bin/env python3

# The above is called a 'shebang' and is needed to let Brickman know to use python 3

# Some code adapted from LEGO Mindstorms Voice Challenge: https:// hackster.io/contests/alexa-lego-voice-challenge 


# Importing required code libraries
import os
import sys
import logging
import json

from enum import Enum
from agt import AlexaGadget
from time import sleep
from threading import Thread
from random import choice

from ev3dev2.led import Leds
from ev3dev2.sound import Sound
from ev3dev2.motor import OUTPUT_A, OUTPUT_B, OUTPUT_C, LargeMotor, MoveSteering, MoveTank, MediumMotor
from ev3dev2.button import Button
from ev3dev2.sensor.lego import TouchSensor, 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 EventName(Enum):
    """
    The list of custom event name sent from this gadget
    """
    LIVESLEFT = "LivesLeft"
    SPEECH = "Speech"


class MindstormsGadget(AlexaGadget):
    """
    A Mindstorms gadget for LEGO Robot Battles that uses Alexa as the Gamemaster.
    """   

    # Code to allow LEGO IR Remote to control motors

    def top_left_channel_action(self, state):
        if state:
            self.leftmotor.on(100)
        else:
            self.leftmotor.off()

    def top_right_channel_action(self, state):
        if state:
            self.rightmotor.on(100)
        else:
            self.rightmotor.off()

    def bottom_left_channel_action(self, state):
        if state:
            self.leftmotor.on(-100)
        else:
            self.leftmotor.off()

    def bottom_right_channel_action(self, state):
        if state:
            self.rightmotor.on(-100)
        else:
            self.rightmotor.off()

    def beacon_channel_action(self, state):
        if state:
            self.Hammermotor.on_for_rotations(speed=100, rotations=0.5)
            sleep(.2)
            self.Hammermotor.on_for_rotations(speed=-20, rotations=0.5)


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

        # Initiating basic parameters for robot battle
        self.NumberOfLives = int(3)
        self.gameon = False
        self.ir_channel = choice([1, 2, 3, 4])

        # Ev3dev initialization
        self.leds = Leds()
        self.sound = Sound()
        self.ts = TouchSensor()
        self.ir1 = InfraredSensor()
        self.ir2 = InfraredSensor()
        self.ir3 = InfraredSensor()
        self.ir4 = InfraredSensor()
        self.my_sound = Sound()
        self.Hammermotor = MediumMotor(OUTPUT_A)
        self.leftmotor = LargeMotor(OUTPUT_B)
        self.rightmotor = LargeMotor(OUTPUT_C)

        # IR Remote initialization
        self.ir1.on_channel1_top_right = self.top_right_channel_action
        self.ir1.on_channel1_top_left = self.top_left_channel_action
        self.ir1.on_channel1_bottom_left = self.bottom_left_channel_action
        self.ir1.on_channel1_bottom_right = self.bottom_right_channel_action
        self.ir1.on_channel1_beacon = self.beacon_channel_action

        self.ir2.on_channel2_top_right = self.top_right_channel_action
        self.ir2.on_channel2_top_left = self.top_left_channel_action
        self.ir2.on_channel2_bottom_left = self.bottom_left_channel_action
        self.ir2.on_channel2_bottom_right = self.bottom_right_channel_action
        self.ir2.on_channel2_beacon = self.beacon_channel_action

        self.ir3.on_channel3_top_right = self.top_right_channel_action
        self.ir3.on_channel3_top_left = self.top_left_channel_action
        self.ir3.on_channel3_bottom_left = self.bottom_left_channel_action
        self.ir3.on_channel3_bottom_right = self.bottom_right_channel_action
        self.ir3.on_channel3_beacon = self.beacon_channel_action

        self.ir4.on_channel4_top_right = self.top_right_channel_action
        self.ir4.on_channel4_top_left = self.top_left_channel_action
        self.ir4.on_channel4_bottom_left = self.bottom_left_channel_action
        self.ir4.on_channel4_bottom_right = self.bottom_right_channel_action
        self.ir4.on_channel4_beacon = self.beacon_channel_action


        # Start threads
        Thread(target=self._touches_thread, daemon=True).start()
        Thread(target=self._activegame_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_custom_mindstorms_gadget_control(self, directive):
        """
        Handles the Custom.Mindstorms.Gadget control directive.
        :param directive: the custom directive with the matching namespace and name
        """
        # Code to get directive info from Alexa when starting a new game
        try:
            payload = json.loads(directive.payload.decode("utf-8"))
            print("Control payload: {}".format(payload), file=sys.stderr)
            control_type = payload["type"]
            if control_type == "StartGame":
                 # Expected params: [numberlives, IrChannel]
                sleep(1)
                self.gameon = False
                self.NumberOfLives = int(payload ["numberlives"])
                if int(payload ["IrChannel"]) > 0:
                    self.ir_channel = int(payload ["IrChannel"])
                sleep(.5)
                self.gameon = True

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


    # General code for sending custom event to Alexa
    def _send_event(self, name: EventName, payload):
        """
        Sends a custom event to Alexa.
        :param name: the name of the custom event
        :param payload: the sentry JSON payload
        """
        self.send_custom_event('Custom.Mindstorms.Gadget', name.value, payload) 

    # Thread to send custom event to Alexa when touch sensor indicates a hit
    # and turn off motors when a robot has no lives left
    def _touches_thread(self):
        """
        Counts the number of touches.
        """
        while True:
            while self.gameon:
                while self.NumberOfLives >= 1:
                    self.leds.set_color("LEFT", "YELLOW")
                    self.leds.set_color("RIGHT", "YELLOW") 
                    self.ts.wait_for_bump()
                    self.NumberOfLives = self.NumberOfLives - 1 
                    self._send_event(EventName.LIVESLEFT, {'livesleft': self.NumberOfLives})
                    logger.info("{} health".format(self.NumberOfLives))
                    self.leds.set_color("LEFT", "RED") 
                    self.leds.set_color("RIGHT", "RED")

                    sleep(0.25)
                    #self.sound.play_tone(1000, 1)
                self.gameon = False
                self.leds.set_color("LEFT", "RED") 
                self.leds.set_color("RIGHT", "RED")
                self.leftmotor.off()
                self.rightmotor.off()
                #self.sound.speak('game over',espeak_opts='-a 200 -s 130',volume=100)
            sleep(.5)


    # Thread to allow IR to control motors when there is an active battle w2ith lives left
    def _activegame_thread(self):
        while True:
            while self.gameon:
                if self.ir_channel == 1:
                    self.ir1.process()
                elif self.ir_channel == 2:
                    self.ir2.process()
                elif self.ir_channel == 3:
                    self.ir3.process()
                elif self.ir_channel == 4:
                    self.ir4.process()
                sleep(0.005)
            sleep(.5)



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
My Alexa Skill JSON file with Intents, Sample Utterances, and Invocation
{
  "interactionModel": {
      "languageModel": {
          "invocationName": "lego robot battles",
          "intents": [
              {
                  "name": "AMAZON.CancelIntent",
                  "samples": []
              },
              {
                  "name": "AMAZON.HelpIntent",
                  "samples": []
              },
              {
                  "name": "AMAZON.StopIntent",
                  "samples": []
              },
              {
                  "name": "AMAZON.NavigateHomeIntent",
                  "samples": []
              },
              {
                  "name": "SetHealthIntent",
                  "slots": [
                      {
                          "name": "NumberLives",
                          "type": "AMAZON.NUMBER"
                      }
                  ],
                  "samples": [
                      "Set initial health to {NumberLives}",
                      "Set initial lifes to {NumberLives}",
                      "Set initial life to {NumberLives}",
                      "Set {NumberLives} life",
                      "Set life to {NumberLives}",
                      "Set {NumberLives} lives",
                      "Set {NumberLives} health",
                      "Set lifes to {NumberLives}",
                      "Set health to {NumberLives}"
                  ]
              },
              {
                  "name": "HealthStatusIntent",
                  "slots": [],
                  "samples": [
                      "health",
                      "Health status update",
                      "How many lives do I have left",
                      "How many lives do I have",
                      "How many lives are left",
                      "Whats my health status",
                      "What is my health status"
                  ]
              },
              {
                  "name": "StartGameIntent",
                  "slots": [],
                  "samples": [
                      "Lets get ready to rumble",
                      "Begin Battle",
                      "Begin a new battle",
                      "New Battle",
                      "Start battle",
                      "Start game",
                      "Start a new game",
                      "New game",
                      "game on",
                      "Start a game",
                      "Start the game",
                      "Begin a game",
                      "Begin the game"
                  ]
              },
              {
                  "name": "SetIrChannelIntent",
                  "slots": [
                      {
                          "name": "IrChannel",
                          "type": "AMAZON.NUMBER"
                      }
                  ],
                  "samples": [
                      "I R channel {IrChannel}",
                      "Set channel to {IrChannel}",
                      "Set I R channel to {IrChannel}"
                  ]
              }
          ],
          "types": []
      }
  }
}

index.js

JavaScript
My main code for the Alexa LEGO Robot Battles Skill
/* JS code for LEGO Robot Battle with Alexa as the Gamemaster

/* Adapted from sample code provided by LEGO Mindstorms Voice Challenge: https:// hackster.io/contests/alexa-lego-voice-challenge 
 * Copyright 2019 Amazon.com, Inc. or its affiliates.  All Rights Reserved.
*/


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

// The audio tags for hit and kill music
const hit_alarm_MUSIC3 = '<audio src="soundbank://soundlibrary/scifi/amzn_sfx_scifi_alarm_03"></audio>';
const hit_alarm_MUSIC2 = '<audio src="soundbank://soundlibrary/scifi/amzn_sfx_scifi_alarm_02"></audio>';
const hit_alarm_MUSIC4 = '<audio src="soundbank://soundlibrary/scifi/amzn_sfx_scifi_alarm_04"></audio>';
const hit_alarm_MUSIC1 = '<audio src="soundbank://soundlibrary/scifi/amzn_sfx_scifi_alarm_01"></audio>';

const kill_MUSIC = '<audio src="soundbank://soundlibrary/hospital/heartbeats_ekg/heartbeats_ekg_02"></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', 10);

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

        let speechOutput = "Welcome to LEGO Robot Battles";
        return handlerInput.responseBuilder
            .speak(speechOutput)
            .addDirective(Util.buildStartEventHandler(token,60000, {}))
            .getResponse();
    }
};


// Code for SetHealth Intent to receive the number of lives (health level) from the User and make it a session attribute.
const SetHealthIntentHandler = {
    canHandle(handlerInput) {
        return Alexa.getRequestType(handlerInput.requestEnvelope) === 'IntentRequest'
            && Alexa.getIntentName(handlerInput.requestEnvelope) === 'SetHealthIntent';
    },
    handle: function (handlerInput) {

        let numberlives = Alexa.getSlotValue(handlerInput.requestEnvelope, 'NumberLives');
        numberlives = Math.max(1, Math.min(100, parseInt(numberlives))) || "3";
        Util.putSessionAttribute(handlerInput, 'numberlives', numberlives);
        const speechOutput = `Health set to ${numberlives} .`;
        return handlerInput.responseBuilder
//           .withShouldEndSession(false)
            .speak(speechOutput)
            .getResponse()
;
    }
};

// Code for SetIrChannel Intent to receive the IR Channel from the User and make it a session attribute.
const SetIrChannelIntentHandler = {
    canHandle(handlerInput) {
        return Alexa.getRequestType(handlerInput.requestEnvelope) === 'IntentRequest'
            && Alexa.getIntentName(handlerInput.requestEnvelope) === 'SetIrChannelIntent';
    },
    handle: function (handlerInput) {

        let IrChannel = Alexa.getSlotValue(handlerInput.requestEnvelope, 'IrChannel');
        IrChannel = parseInt(IrChannel) || "1";
        Util.putSessionAttribute(handlerInput, 'IrChannel', IrChannel);
        const speechOutput = `I R Channel set to  ${IrChannel} .`;
        return handlerInput.responseBuilder
//            .withShouldEndSession(false)
            .speak(speechOutput)
            .getResponse()
;
    }
};


// Code for HealthStatus Intent to announce the current health (number of lives left) when requested.
const HealthStatusIntentHandler = {
    canHandle(handlerInput) {
        return Alexa.getRequestType(handlerInput.requestEnvelope) === 'IntentRequest'
            && Alexa.getIntentName(handlerInput.requestEnvelope) === 'HealthStatusIntent';
    },
    handle: function (handlerInput) {

        const livesleft = handlerInput.attributesManager.getSessionAttributes().livesleft;
        if (livesleft > 1) { 
            const speechOutput = `you have  ${livesleft} lives left.`;
            return handlerInput.responseBuilder
                .withShouldEndSession(false)
                .speak(speechOutput)
                .getResponse();
        }
        else if (livesleft === 1) {
            const speechOutput = `you have  ${livesleft} life left.`;
            return handlerInput.responseBuilder
                .withShouldEndSession(false)
                .speak(speechOutput)
                .getResponse();
        }
    }
};
        
// Code for StartGame Intent to construct and send a custom directive to the connected Gadget
// with the IR channel and starting health for a new battle
const StartGameIntentHandler = {
    canHandle(handlerInput) {
        return Alexa.getRequestType(handlerInput.requestEnvelope) === 'IntentRequest'
            && Alexa.getIntentName(handlerInput.requestEnvelope) === 'StartGameIntent';
    },
    handle: function (handlerInput) {
        
        const numberlives = handlerInput.attributesManager.getSessionAttributes().numberlives || "3";
        const IrChannel = handlerInput.attributesManager.getSessionAttributes().IrChannel || "0";
        const endpointId = handlerInput.attributesManager.getSessionAttributes().endpointId || [];
        
        
        Util.putSessionAttribute(handlerInput, 'livesleft', numberlives);
        
        let directive = Util.build(endpointId, NAMESPACE, NAME_CONTROL,
            {
                type: 'StartGame',
                numberlives: numberlives,
                IrChannel: IrChannel
            });
        const speechOutput = `Get ready to rumble.`;
        return handlerInput.responseBuilder
            .speak(speechOutput)
            .addDirective(directive)
            .getResponse();
    }
};

//  Code for receiving an event from EV3 brick when the touch sensor is hit during a battle
//  Alexa will play a noise and announce current health after each touch
//  Alexa will play an end gamne noise if you are out of lives and announce Game Over

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;
        const IrChannel = handlerInput.attributesManager.getSessionAttributes().IrChannel || "0"; 
        let speechOutput;
        if (name === 'LivesLeft') {
            let livesleft = parseInt(payload.livesleft);
            Util.putSessionAttribute(handlerInput, 'livesleft', livesleft);
            if (livesleft === 0) {
                const speechOutput = `${livesleft} health remaining, game over.`;
                return handlerInput.responseBuilder
                    .speak(kill_MUSIC + speechOutput, "REPLACE_ALL")
                    .getResponse();
            }
            
            else if (livesleft > 0) {
                const speechOutput = `${livesleft} health remaining.`;
                if (IrChannel < 2) {
                    return handlerInput.responseBuilder
                        .speak(hit_alarm_MUSIC1 + speechOutput, "REPLACE_ALL")
                        .getResponse();
                }
                if (IrChannel === 2) {
                    return handlerInput.responseBuilder
                        .speak(hit_alarm_MUSIC2 + speechOutput, "REPLACE_ALL")
                        .getResponse();
                }
                if (IrChannel === 3) {
                    return handlerInput.responseBuilder
                        .speak(hit_alarm_MUSIC3 + speechOutput, "REPLACE_ALL")
                        .getResponse();
                }
                if (IrChannel === 4) {
                    return handlerInput.responseBuilder
                        .speak(hit_alarm_MUSIC4 + speechOutput, "REPLACE_ALL")
                        .getResponse();
                }
            } 
            
        } else if (name === 'Speech') {
            speechOutput = payload.speechOut;

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

// The SkillBuilder acts as the entry point for the skill, routing all request and response
// payloads to the handlers above. 

exports.handler = Alexa.SkillBuilders.custom()
    .addRequestHandlers(
        SetHealthIntentHandler,
        HealthStatusIntentHandler,
        StartGameIntentHandler,
        SetIrChannelIntentHandler,
        EventsReceivedRequestHandler,
        ExpiredRequestHandler,
        LaunchRequestHandler,
        Common.HelpIntentHandler,
        Common.CancelAndStopIntentHandler,
        Common.SessionEndedRequestHandler,
        Common.IntentReflectorHandler, 
    )
    .addRequestInterceptors(Common.RequestInterceptor)
    .addErrorHandlers(
        Common.ErrorHandler,
    )
    .lambda();

common.js

JavaScript
Additional Alexa Skill Code provided by Amazon for Voice Challenge
/*
 * 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.
*/

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

util.js

JavaScript
Additional Alexa Skill Code provided by Amazon for Voice Challenge
/*
 * 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.
*/

'use strict';

const Https = require('https');

/**
 * Build a custom directive payload to the gadget with the specified endpointId
 * @param {string} endpointId - the gadget endpoint Id
 * @param {string} namespace - the namespace of the skill
 * @param {string} name - the name of the skill within the scope of this namespace
 * @param {object} payload - the payload data
 * @see {@link https://developer.amazon.com/docs/alexa-gadgets-toolkit/send-gadget-custom-directive-from-skill.html#respond}
 */
exports.build = function (endpointId, namespace, name, payload) {
    // Construct the custom directive that needs to be sent
    // Gadget should declare the capabilities in the discovery response to
    // receive the directives under the following namespace.
    return {
        type: 'CustomInterfaceController.SendDirective',
        header: {
            name: name,
            namespace: namespace
        },
        endpoint: {
            endpointId: endpointId
        },
        payload
    };
};

/**
 * A convenience routine to add the a key-value pair to the session attribute.
 * @param handlerInput - the context from Alexa Service
 * @param key - the key to be added
 * @param value - the value be added
 */
exports.putSessionAttribute = function(handlerInput, key, value) {
    const attributesManager = handlerInput.attributesManager;
    let sessionAttributes = attributesManager.getSessionAttributes();
    sessionAttributes[key] = value;
    attributesManager.setSessionAttributes(sessionAttributes);
};

/**
 * To get a list of all the gadgets that meet these conditions,
 * Call the Endpoint Enumeration API with the apiEndpoint and apiAccessToken to
 * retrieve the list of all connected gadgets.
 *
 * @param {string} apiEndpoint - the Endpoint API url
 * @param {string} apiAccessToken  - the token from the session object in the Alexa request
 * @see {@link https://developer.amazon.com/docs/alexa-gadgets-toolkit/send-gadget-custom-directive-from-skill.html#call-endpoint-enumeration-api}
 */
exports.getConnectedEndpoints = function(apiEndpoint, apiAccessToken) {

    // The preceding https:// need to be stripped off before making the call
    apiEndpoint = (apiEndpoint || '').replace('https://', '');
    return new Promise(((resolve, reject) => {

        const options = {
            host: apiEndpoint,
            path: '/v1/endpoints',
            method: 'GET',
            headers: {
                'Content-Type': 'application/json',
                'Authorization': 'Bearer ' + apiAccessToken
            }
        };

        const request = Https.request(options, (response) => {
            response.setEncoding('utf8');
            let returnData = '';
            response.on('data', (chunk) => {
                returnData += chunk;
            });

            response.on('end', () => {
                resolve(JSON.parse(returnData));
            });

            response.on('error', (error) => {
                reject(error);
            });
        });
        request.end();
    }));
};

package.json

JSON
Additional Alexa Skill Code provided by Amazon for Voice Challenge
{
  "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"
  }
}

Credits

cgo22

cgo22

1 project • 2 followers

Comments