Mazzmn
Published

Mindstorms Math Alexa Enabled Prize Truck

Alexa helps you practice your math skills with a LEGO MINDSTORMS Alexa gadget. Reach your correct answer goal to get a surprise prize!

IntermediateFull instructions provided10 hours722

Things used in this project

Hardware components

Mindstorms EV3 Programming Brick / Kit
LEGO Mindstorms EV3 Programming Brick / Kit
The essential LEGO MINDSTORMS parts include the EV3 Intelligent Brick, 2 Large motors, and the cables to connect them. (Plus lots of LEGO bricks and Technic pieces, see the attached schematic for a list of parts used and assembly instructions.)
×1
Echo Show (2nd Gen)
Amazon Alexa Echo Show (2nd Gen)
This Skill will work with Echo Show, Echo Dot, Alexa Auto, or even Alexa Fire HD Tablet. Not tested yet, but it should work with other Echo devices as well.
×1
TP-Link Wireless Adapter
×1
Tenavolts AA Rechargeable Batteries
×1

Software apps and online services

Alexa Skills Kit
Amazon Alexa Alexa Skills Kit
Alexa Gadgets Toolkit
Amazon Alexa Alexa Gadgets Toolkit
AWS Lambda
Amazon Web Services AWS Lambda
AWS Cloudwatch is used to view the logs created by this Alexa AWS Hosted skill
VS Code
Microsoft VS Code

Story

Read more

Custom parts and enclosures

MINDSTORMS Math Prize Truck Design File (Alternate Wheel Design)

The same design but 2 more axles, four 30.4mm x20mm wheels and four 43.2 x 22 ZR Tires, just for a slightly cooler look.

MINDSTORMS Math Prize Truck Design File

This file can be imported into the BrickLink Studio desktop application where you can view the 3D model of the Prize Truck. You can see how the truck was built and tweak the colors or design for yourself.

Schematics

MINDSTORMS Math Prize Truck Build Instructions and Bill Of Materials

These instructions were created using BrickLink Studio, they are meant to be similar to LEGO build instructions. The list of parts required is included

Code

index.js

JavaScript
This is the primary portion of the Alexa Skill "Mindstorms Math".
/*
 * 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 sample demonstrates sending directives to an Echo connected gadget from an Alexa skill
// using 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.
// Modified for Mindstorms Voice Challenge

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

// 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 defaultNumberOfQuestions = 10;
const defaultAnswerGoal = 9;
const defaultLow = 1;
const defaultHigh = 12;
const defaultFocusRow = 0;

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

        let request = handlerInput.requestEnvelope;
        let { apiEndpoint, apiAccessToken } = request.context.System;
        const apiResponse = await Util.getConnectedEndpoints(apiEndpoint, apiAccessToken) || [];
        let ev3Message = "";
        // if ((apiResponse.endpoints || []).length === 0) {
        //     return handlerInput.responseBuilder
        //     .speak(`Sorry, but 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();
        // }
        if ((apiResponse.endpoints || []).length === 0) { // allow user to continue even without connected EV3
             ev3Message = "Note, I did not find a connected Mindstorms EV3";
        }
    
        // Store the gadget endpointId to be used in this skill session
        let endpointId;
        if ((apiResponse.endpoints || []).length === 0) {  //doing this because we did not connect to mindstorms
             endpointId = 0;
        } else {
            endpointId = apiResponse.endpoints[0].endpointId;
        }

        Util.putSessionAttribute(handlerInput, 'endpointId', endpointId);
        Util.putSessionAttribute(handlerInput, 'questions', defaultNumberOfQuestions);
        Util.putSessionAttribute(handlerInput, 'goal', defaultAnswerGoal);
        Util.putSessionAttribute(handlerInput, 'low', defaultLow);
        Util.putSessionAttribute(handlerInput, 'high', defaultHigh);
        Util.putSessionAttribute(handlerInput, 'focusRow', defaultFocusRow);
        Util.putSessionAttribute(handlerInput, 'lastQuestion', "");
        Util.putSessionAttribute(handlerInput, 'textOutput', "");
        Util.putSessionAttribute(handlerInput, 'awardPrize', "");
        resetQuestions(handlerInput);
        Util.putSessionAttribute(handlerInput, 'lastActivity', 'LaunchRequest');

        var speechOutput = `Welcome to Mindstorms Math, We'll ask you 10 multiplication questions, Ready to Play? Or you can Modify the number and type of questions. Say Play to Play or Modify to Modify. ${ev3Message}`;
        var newResponse = "";
        if (!supportsAPL(handlerInput)) {
            var speechOutputSpoken = speechOutput;
              return handlerInput.responseBuilder.speak(speechOutputSpoken).withSimpleCard('Mindstorms Math', speechOutput)
              .reprompt("Please say Modify or Play")
              .getResponse();
        } else {

            var payload = require('./datasource.json');
            payload.bodyTemplate1Data.textContent.wordText.text = "Welcome to Mindstorms Math";
            payload.bodyTemplate1Data.textContent.definitionText.text = "Say 'Play' to begin, 'Modify' to change default number of questions and other features. or 'Help' or Quit'";
            payload.bodyTemplate1Data.textContent.spellingText.text = newResponse.spelling;
              
            return handlerInput.responseBuilder.speak(speechOutput).withSimpleCard('Mindstorms Math', speechOutput)
                .addDirective({type: 'Alexa.Presentation.APL.RenderDocument',document: require('./latest.json'), datasources: payload,})
                .speak(speechOutput)
                .reprompt("Please say Modify or Play")
  	            .getResponse();
        }
    }
};

function resetQuestions(handlerInput) {
    Util.putSessionAttribute(handlerInput, 'questionNum', 0);
    Util.putSessionAttribute(handlerInput, 'numRight', 0);
    Util.putSessionAttribute(handlerInput, 'numWrong', 0);
}

function supportsAPL(handlerInput) { 
  const supportedInterfaces = 
  handlerInput.requestEnvelope.context.System.device.supportedInterfaces;
  const aplInterface = supportedInterfaces['Alexa.Presentation.APL'];
  return aplInterface !== null && aplInterface !== undefined; 
}

// Play Handler
// Start asking the set number of questions in the specified range
const PlayIntentHandler = {
    canHandle(handlerInput) {
        return Alexa.getRequestType(handlerInput.requestEnvelope) === 'IntentRequest'
            && Alexa.getIntentName(handlerInput.requestEnvelope) === 'PlayIntent';
    },
    handle: function (handlerInput) {
        Util.putSessionAttribute(handlerInput, 'lastActivity', 'PlayIntentHandler');
        const attributesManager = handlerInput.attributesManager;
        let questionNum = attributesManager.getSessionAttributes().questionNum;
        questionNum = questionNum +1;

        var nextQuestion = getNextQuestion(handlerInput);
        return handlerInput.responseBuilder
            .speak(`${nextQuestion}`)
            .reprompt(`${nextQuestion}`)
            .getResponse();
    }
};

function getNextQuestion(handlerInput) {
    var question = '';
    var textOutput = '';
    var speechOutput = "";
    var payload = require('./datasource.json');
    let first = "";
    let second = "";
    const attributesManager = handlerInput.attributesManager;
    let questionNum = attributesManager.getSessionAttributes().questionNum;
    const endpointId = attributesManager.getSessionAttributes().endpointId || 0;
    questionNum = questionNum +1;

    if (questionNum <= attributesManager.getSessionAttributes().questions) {

        var low = attributesManager.getSessionAttributes().low;
        var high = attributesManager.getSessionAttributes().high;
        var focus = attributesManager.getSessionAttributes().focusRow;
        if (focus === 0) {
            first = randomInteger(1*low, 1*high);
        } else {
            first = focus;
        }
        second = randomInteger(1*low, 1*high);
        var latestAnswer = attributesManager.getSessionAttributes().latestAnswer;
        if (latestAnswer === second) {  // lessen the chance of same question twice
            second = randomInteger(1*low, 1*high)
        }
        Util.putSessionAttribute(handlerInput, 'latestAnswer', first * second);
        Util.putSessionAttribute(handlerInput, 'questionNum', questionNum);
        question = `Question ${questionNum}, \n What is ${first} times ${second}?`;
        textOutput = `${first} x ${second} = ?`;
    } else {
        textOutput = `You got ${attributesManager.getSessionAttributes().numRight} right and ${attributesManager.getSessionAttributes().numWrong} wrong`;
        if (attributesManager.getSessionAttributes().numRight < (attributesManager.getSessionAttributes().goal) ) {
            question =  `That was the last question, you got ${attributesManager.getSessionAttributes().numRight} right and 
                ${attributesManager.getSessionAttributes().numWrong} wrong. Let's play some more! Please say play to play again or quit to quit`;
                resetQuestions(handlerInput);
        } else {
            var exclaim = randomAwesome();
            question =  `That was the last question. ${exclaim} You made your goal of ${attributesManager.getSessionAttributes().goal}, you got ${attributesManager.getSessionAttributes().numRight} right and 
                ${attributesManager.getSessionAttributes().numWrong} wrong. <amazon:emotion name="excited" intensity="medium">Let's play some more</amazon:emotion>. Please say play to play again or quit to quit`;
                resetQuestions(handlerInput);
                Util.putSessionAttribute(handlerInput, 'awardPrize', "yes");
        }
    }
    Util.putSessionAttribute(handlerInput, 'lastQuestion', question);
    Util.putSessionAttribute(handlerInput, 'textOutput', textOutput);
    displayQuestion(handlerInput);
    return question
 }
 
 function awardPrizeDirective(handlerInput) {
     var directive;
     var endpointId = handlerInput.attributesManager.getSessionAttributes().endpointId;
     if (handlerInput.attributesManager.getSessionAttributes().awardPrize === "yes") {
        directive = Util.build(endpointId, NAMESPACE, NAME_CONTROL,
            {
                type: 'prize',
                direction: 'backwards',
                duration: 4,
                speed: 25
            });
     } else {
        directive = Util.build(endpointId, NAMESPACE, NAME_CONTROL,
            {
                type: 'prizeNONE',
                direction: 'backwards',
                duration: 4,
                speed: 25
            });
         
     }
     Util.putSessionAttribute(handlerInput, 'awardPrize', "");
     return directive;
 }
 
 function displayQuestion(handlerInput) {
    var speechOutput = "";
    var payload = require('./datasource.json');
    let first = "";
    let second = "";
    const attributesManager = handlerInput.attributesManager;
    let questionNum = attributesManager.getSessionAttributes().questionNum;
    var question = attributesManager.getSessionAttributes().lastQuestion;
    var textOutput = attributesManager.getSessionAttributes().textOutput;
    const endpointId = attributesManager.getSessionAttributes().endpointId || 0;
     if (!supportsAPL(handlerInput)) {
        speechOutput = attributesManager.getSessionAttributes().lastQuestion;
        if (endpointId !== 0) {
            handlerInput.responseBuilder.speak(speechOutput).withSimpleCard('Mindstorms Math', textOutput)
                .reprompt(`${question}`)
                .getResponse();
        } else {
            handlerInput.responseBuilder.speak(speechOutput).withSimpleCard('Mindstorms Math', textOutput)
                .reprompt(`${question}`)
                .getResponse();
        }
    } else {
        speechOutput = question;
        payload.bodyTemplate1Data.textContent.wordText.text = textOutput;
        payload.bodyTemplate1Data.textContent.definitionText.text = "";
        payload.bodyTemplate1Data.textContent.spellingText.text = question;

        if (endpointId !== 0) {
            handlerInput.responseBuilder.withSimpleCard('Mindstorms Math', textOutput)
            .addDirective({type: 'Alexa.Presentation.APL.RenderDocument', document: require('./latest.json'), datasources: payload,})
  		    .getResponse();
        } else {
            handlerInput.responseBuilder.withSimpleCard('Mindstorms Math', textOutput)
            .addDirective({
  		        type: 'Alexa.Presentation.APL.RenderDocument', document: require('./latest.json'),
  		        datasources: payload,})
  		    .getResponse();
        }
    }
 }
 
// AnswerHandler - when a number response occurs check the answer, record the results and ask the next question
const AnswerIntentHandler = {
    canHandle(handlerInput) {
        return Alexa.getRequestType(handlerInput.requestEnvelope) === 'IntentRequest'
            && Alexa.getIntentName(handlerInput.requestEnvelope) === 'AnswerIntent';
    },
    handle: async function (handlerInput) {
        const attributesManager = handlerInput.attributesManager;
        const endpointId = attributesManager.getSessionAttributes().endpointId || 0;

        let direction = "forward";
        let time = "2";
        let directive = "";
        let numWrong = attributesManager.getSessionAttributes().numWrong;
        let numRight = attributesManager.getSessionAttributes().numRight;
        let response = "";
        var userAnswer = "42";
        var motorspeed = "20";
        var correctAnswer = "";
        var nextQuestion = "";
        userAnswer = '' + Alexa.getSlotValue(handlerInput.requestEnvelope, 'UserAnswer');
         
        if (userAnswer === "?" || userAnswer === undefined) {
            var lastQuestion = "Sorry, I didn't get that, " + attributesManager.getSessionAttributes().lastQuestion;
            return handlerInput.responseBuilder
            .speak(`${lastQuestion}`)
            .reprompt(`Please say the answer again, or quit to quit.`)
            .getResponse();
        } else if (attributesManager.getSessionAttributes().lastActivity.indexOf('ModifyIntentHandler') !== -1) {
            //ask next question
            if (attributesManager.getSessionAttributes().lastActivity.indexOf('goal') !== -1) {
                Util.putSessionAttribute(handlerInput, 'goal', userAnswer);
                nextQuestion = `Correct Answer goal set to  ${attributesManager.getSessionAttributes().goal},\n`;
                nextQuestion = nextQuestion + getNextQuestion(handlerInput);
                Util.putSessionAttribute(handlerInput, 'lastActivity', 'AnswerIntentHandler');
            } else if (attributesManager.getSessionAttributes().lastActivity.indexOf('focusRow') !== -1) {
                Util.putSessionAttribute(handlerInput, 'focusRow', userAnswer);
                nextQuestion = `Focus Row value set to ${attributesManager.getSessionAttributes().focusRow},\n How many questions must be correct before a prize is awarded?`;
                Util.putSessionAttribute(handlerInput, 'lastActivity', 'ModifyIntentHandler.goal');
            } else if (attributesManager.getSessionAttributes().lastActivity.indexOf('higher') !== -1) {
                Util.putSessionAttribute(handlerInput, 'high', userAnswer);
                nextQuestion = `Highest value set to ${attributesManager.getSessionAttributes().high}. Name one particular Math Table Row you want to practice or say zero for all rows.`;
                Util.putSessionAttribute(handlerInput, 'lastActivity', 'ModifyIntentHandler.focusRow');
            } else if (attributesManager.getSessionAttributes().lastActivity.indexOf('lower') !== -1) {
                Util.putSessionAttribute(handlerInput, 'low', userAnswer);
                nextQuestion = `Lowest value set to ${attributesManager.getSessionAttributes().low}. What would you like the highest possible table Column?`;
                Util.putSessionAttribute(handlerInput, 'lastActivity', 'ModifyIntentHandler.higher');
            } else {
                Util.putSessionAttribute(handlerInput, 'questions', userAnswer);
                nextQuestion = `Number of questions set to  ${attributesManager.getSessionAttributes().questions}. What would you like for lowest possible table column?`;
                Util.putSessionAttribute(handlerInput, 'lastActivity', 'ModifyIntentHandler.lower');
            } 
            Util.putSessionAttribute(handlerInput, 'lastQuestion', nextQuestion);
            return handlerInput.responseBuilder
                .speak(`${nextQuestion}`)
                .reprompt(`${nextQuestion}`)
                .getResponse();
        } else {
            Util.putSessionAttribute(handlerInput, 'lastActivity', 'AnswerIntentHandler');
            correctAnswer = '' + attributesManager.getSessionAttributes().latestAnswer;
            displayQuestion(handlerInput);
            if (userAnswer === correctAnswer.toString()) {
                response = randomCorrect();
                Util.putSessionAttribute(handlerInput, 'numRight', numRight + 1);
                direction = "forwards";
            } else {
                response = randomWrong();
                response = response + ` ${userAnswer} is wrong, The right answer is ${correctAnswer}.`;
                Util.putSessionAttribute(handlerInput, 'numWrong', numWrong + 1);
                direction = "backwards";
            }
            directive = Util.build(endpointId, NAMESPACE, NAME_CONTROL,
                {
                    type: 'shake',
                    direction: direction,
                    duration: time,
                    speed: motorspeed
                });
            nextQuestion = getNextQuestion(handlerInput);
            if (endpointId !== 0) { 
                var directive2 = awardPrizeDirective(handlerInput);
                return handlerInput.responseBuilder
                    .speak(`${response} ${nextQuestion}`)
                    .addDirective(directive)
                    .addDirective(directive2)
                    .reprompt(`${nextQuestion}`)
                    .getResponse();
            } else  { // not connected to gadget so don't add any move directives
                return handlerInput.responseBuilder
                    .speak(`${response} ${nextQuestion}`)
                    .reprompt(`${nextQuestion}`)
                .   getResponse();
            }
        }
    }
};

// ModifyIntentHandler - User wants to change the defaults: number of questions, low number, high number, forcusRow, awardGoal.
const ModifyIntentHandler = {
    canHandle(handlerInput) {
        return Alexa.getRequestType(handlerInput.requestEnvelope) === 'IntentRequest'
        && Alexa.getIntentName(handlerInput.requestEnvelope) === 'ModifyIntent';
    },
    handle: function (handlerInput) {
        const attributesManager = handlerInput.attributesManager;
        var last = attributesManager.getSessionAttributes().lastActivity;
        let response = "";

        if (last !== `LaunchRequest`) {
               nextQuestion = `I'm sorry, could you repeat that?`;
        } else {
               Util.putSessionAttribute(handlerInput, 'lastActivity', 'ModifyIntentHandler');
               var nextQuestion = `You can modify the number of questions to be asked and the lowest and highest TimesTable Column to be used in a question. 
               You can also choose to focus on one particular row of the table and you can set a correct answer goal. How many questions would you like to be asked?`;
        }
        if (!supportsAPL(handlerInput)) {
            return handlerInput.responseBuilder
                .speak(`${nextQuestion}`)
                .reprompt(`${nextQuestion}`)
                .getResponse();
        } else {
            var speechOutput = nextQuestion;
            var newResponse = "";

            var payload = require('./datasource.json');
            payload.bodyTemplate1Data.textContent.wordText.text = "Modify Defaults";
            payload.bodyTemplate1Data.textContent.definitionText.text = `You can modify the Number of questions asked, the lowest and highest TimesTable Columns,
              you can also choose to focus on one particular row of the table, and you can set a Correct Answer Goal.`;
              payload.bodyTemplate1Data.textContent.spellingText.text = newResponse.spelling;
            return handlerInput.responseBuilder.speak(speechOutput)
                .withSimpleCard('Mindstorms Math', speechOutput)
                .addDirective({
  		            type: 'Alexa.Presentation.APL.RenderDocument', document: require('./latest.json'),
 		            datasources: payload,})
                .speak(speechOutput)
                .reprompt("How many questions would you like to be asked?")
  		        .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,
        AnswerIntentHandler,
        PlayIntentHandler,
        ModifyIntentHandler,
        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();
    
 function randomInteger(lowest, highest) {
      return Math.floor(Math.random() * (highest - lowest + 1) + lowest)
 }
  
 function randomCorrect() {
  let random = randomInteger(1,4);
  let responseString ;
  switch(random) {
    case 1:
      responseString = "Correct <audio src='soundbank://soundlibrary/vehicles/cars/cars_08'></audio>";
      break;
    case 2:
      responseString = "Yes!<audio src='soundbank://soundlibrary/ui/gameshow/amzn_ui_sfx_gameshow_positive_response_01'></audio>";
      break;
    case 3:
      responseString = "That's right!:<audio src='soundbank://soundlibrary/ui/gameshow/amzn_ui_sfx_gameshow_positive_response_01'></audio>";
      break;
    case 4:
      responseString = "You betcha!:<audio src='soundbank://soundlibrary/ui/gameshow/amzn_ui_sfx_gameshow_positive_response_01'></audio>";
      break;
  }
  return responseString
 }
 
 function randomWrong() {
  let random = randomInteger(1,4);
  let responseString ;
  switch(random) {
    case 1:
      responseString = "Oops,";
      break;
    case 2:
      responseString = "That answer";
      break;
    case 3:
      responseString = "Sorry,";
      break;
    case 4:
      responseString = "Nope.";
      break;
  }
  return responseString
 }
 
 function randomAwesome() {
  let random = randomInteger(1,4);
  let responseString ;
  switch(random) {
    case 1:
      responseString = "Awesome!";
      break;
    case 2:
      responseString = "Way to go!";
      break;
    case 3:
      responseString = `<amazon:emotion name="excited" intensity="high">Oh Yeah!</amazon:emotion>`;
      break;
    case 4:
      responseString = `<amazon:emotion name="excited" intensity="high">Alright!</amazon:emotion>`;
      break;
  }
  return responseString
 }
 

datasource.json

JSON
This file is how I specify the text that appears on screens, such as the Alexa Show, that support the APL (Alexa Presentation Language). For this skill I fill in the Large text section and in some cases smaller text. The logoUrl is the icon for the Alexa skill that would appear in the Amazon Skill store and also appears in the upper right of the Show screen when the skill is running.
 {
        "bodyTemplate1Data": {
            "type": "object",
            "objectId": "bt1Sample",
            "backgroundImage": {
                "contentDescription": null,
                "smallSourceUrl": null,
                "largeSourceUrl": null,
                "sources": [
                    {
                        "url": "https://d2o906d8ln7ui1.cloudfront.net/images/BT1_Background.png",
                        "size": "small",
                        "widthPixels": 0,
                        "heightPixels": 0
                    },
                    {
                        "url": "https://d2o906d8ln7ui1.cloudfront.net/images/BT1_Background.png",
                        "size": "large",
                        "widthPixels": 0,
                        "heightPixels": 0
                    }
                ]
            },
            "title": "Mindstorms Math",
            "textContent": {
                "wordText": {
                    "type": "PlainText",
                    "text": "large text",
                    "size": "8vw",
                    "scrollViewHeight": "54vh"
                },"definitionText": {
                    "type": "PlainText",
                    "text": "details"
                },"spellingText": {
                    "type": "PlainText",
                    "text": "spoken words"
                }
            },
            "logoUrl": "https://s3.amazonaws.com/CAPS-SSE/echo_developer/e031/087db02d25d04e42ad504cb15119ef94/APP_ICON_LARGE?versionId=CY5.r19RpcxrSSs48Qz1P0d42_PlSSWr&X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Date=20191210T021428Z&X-Amz-SignedHeaders=host&X-Amz-Expires=86400&X-Amz-Credential=AKIAJFEYRBGIHK2BBYKA%2F20191210%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-Signature=3a335d67b78b2dc9410e6d8691d758f32866b530c243fbeb7a74dbeac23ff7fd"
        }
}

latest.json

JSON
This file defines what the screen looks like on devices like the Alexa Show, that support the APL (Alexa Presentation Language). Notice there are definitions that apply to the different size screens including the circular screen Echo Spot.
{
    "type": "APL",
    "version": "1.1.0",
    "theme": "dark",
    "import": [
        {
            "name": "alexa-layouts",
            "version": "1.0.0"
        }
    ],
    "resources": [],
    "styles": {},
    "layouts": {},
    "mainTemplate": {
        "parameters": [
            "payload"
        ],
        "items": [
            {
                "when": "${viewport.shape == 'round'}",
                "type": "Container",
                "height": "100vh",
                "width": "100vw",
                "alignItems": "center",
                "justifyContent": "center",
                "direction": "column",
                "items": [
                    {
                        "type": "AlexaHeader",
                        "headerTitle": "${payload.bodyTemplate1Data.title}",
                        "headerAttributionImage": "${payload.bodyTemplate1Data.logoUrl}",
                        "height": "28vh",
                        "width": "99vw"
                    },
                    {
                        "type": "AlexaHeader",
                        "height": "1vh",
                        "width": "80vw"
                    },
                    {
                        "type": "Frame",
                        "height": "83vh",
                        "width": "89vw",
                        "backgroundColor": "black",
                        "item": [
                            {
                                "type": "Container",
                                "height": "100vh",
                                "width": "99vw",
                                "paddingTop": "0vw",
                                "paddingLeft": "7vw",
                                "paddingRight": "4vw",
                                "paddingBottom": "1vw",
                                "alignItems": "start",
                                "justifyContent": "center",
                                "direction": "column",
                                "items": [
                                    {
                                        "type": "Text",
                                        "text": " ${payload.bodyTemplate1Data.textContent.wordText.text}",
                                        "color": " #FFFFFF",
                                        "fontSize": "${payload.bodyTemplate1Data.textContent.wordText.size}",
                                        "fontWeight": "900"
                                    },
                                    {
                                        "type": "ScrollView",
                                        "width": "76vw",
                                        "height": "62vh",
                                        "item": [
                                            {
                                                "type": "Text",
                                                "text": "${payload.bodyTemplate1Data.textContent.definitionText.text}",
                                                "color": "#FFFFFF"
                                            }
                                        ]
                                    }
                                ]
                            }
                        ]
                    }
                ]
            },
            {
                "when": "${viewport.shape != 'round'}",
                "type": "Container",
                "height": "100vh",
                "width": "100vw",
                "alignItems": "center",
                "justifyContent": "center",
                "direction": "column",
                "items": [
                    {
                        "type": "AlexaHeader",
                        "height": "8vh",
                        "width": "80vw"
                    },
                    {
                        "type": "AlexaHeader",
                        "headerTitle": "${payload.bodyTemplate1Data.title}",
                        "headerAttributionImage": "${payload.bodyTemplate1Data.logoUrl}",
                        "height": "28vh",
                        "width": "99vw"
                    },
      
                    {
                        "type": "Frame",
                        "height": "83vh",
                        "width": "99vw",
                        "backgroundColor": "cornflowerblue",
                        "item": [
                            {
                                "type": "Container",
                                "height": "100vh",
                                "width": "100vw",
                                "paddingTop": "0vw",
                                "paddingLeft": "1vw",
                                "paddingRight": "1vw",
                                "paddingBottom": "1vw",
                                "alignItems": "center",
                                "justifyContent": "center",
                                "direction": "column",
                                "items": [
                                    {
                                        "type": "Text",
                                        "text": " ${payload.bodyTemplate1Data.textContent.wordText.text}",
                                        "color": " #FFFFFF",
                                        "fontSize": "${payload.bodyTemplate1Data.textContent.wordText.size}",
                                        "fontWeight": "900"
                                    },
                                    {
                                        "type": "ScrollView",
                                        "width": "96vw",
                                        "height": "${payload.bodyTemplate1Data.textContent.wordText.scrollViewHeight}",
                                        "item": [
                                            {
                                                "type": "Text",
                                                "text": "${payload.bodyTemplate1Data.textContent.definitionText.text}",
                                                "color": "#FFFFFF"
                                            }
                                        ]
                                    }
                                ]
                            }
                        ]
                    }
                ]
            }
        ]
    }
}

package.json

JSON
Part of the Mindstorms Math Alexa skill that contains dependencies for the skill.
{
  "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"
  }
}

mindstorms-math.py

Python
This script gets downloaded and run on the MINDSTORMS EV3 Intelligent Brick. It connects to the Alexa Echo device and handles directives that Move the Prize Truck and Deliver prizes.
# 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.
# Modifications by MAZZMN for Mindstorms Voice Challenge

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_B, OUTPUT_C, MoveTank, SpeedPercent, LargeMotor
### Output B is for the prize ejector large motor
### Output C is for the motor that moves the truck

# Set the logging level to INFO to see messages from AlexaGadget
logging.basicConfig(level=logging.INFO)


class Direction(Enum):
    """
    The list of directional commands and their variations.
    These variations correspond to the skill slot values.
    """
    FORWARD = ['forward', 'forwards', 'go forward','forwards', 'go forwards']
    BACKWARD = ['back', 'backward', 'go backward','backwards', 'go backwards']
    LEFT = ['left', 'go left']
    RIGHT = ['right', 'go right']
    STOP = ['stop', 'brake', 'halt']


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 = ['guard', 'guard mode', 'sentry','century', 'sentry mode']
    PATROL = ['patrol', 'patrol mode']
    FIRE_ONE = ['cannon', '1 shot', 'one shot']
    FIRE_ALL = ['all shots', 'all shot']


class EventName(Enum):
    """
    The list of custom event name sent from this gadget
    """
    SENTRY = "Sentry"
    PROXIMITY = "Proximity"
    SPEECH = "Speech"


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.prize = MoveTank(OUTPUT_B, OUTPUT_B)
        self.drive = MoveTank(OUTPUT_C, OUTPUT_C)
        self.sound = Sound()
        self.leds = Leds()
###        self.ts = TouchSensor()

        # Start threads
###        threading.Thread(target=self._proximity_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")
        self.sound.speak('Alexa connected', espeak_opts='-a 200 -s 130', volume=100, play_type=0)
        print("{} 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")
        self.sound.speak('Goodbye from Minstorms Math', espeak_opts='-a 200 -s 130', volume=100, play_type=0)
        print("{} 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
        """
        print("custom control")
        try:
            payload = json.loads(directive.payload.decode("utf-8"))
            print("Control payload: {}".format(payload))
            control_type = payload["type"]
            print("control type: " + control_type)

            if control_type == "shake":
               # self.sound.speak('Moving', espeak_opts='-a 200 -s 130', volume=100, play_type=0)

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

            if control_type == "prize":
               # self.sound.speak('Moving', espeak_opts='-a 200 -s 130', volume=100, play_type=0)

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

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

        except KeyError:
            print("Missing expected parameters: {}".format(directive))

    def _move(self, direction, duration: int, speed: int, is_blocking=True):
        """
        Handles move commands from the directive.
        Right and left movement can under or over turn depending on the surface type.
        :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 xx command: ({}, {}, {}, {})".format(direction, speed, duration, is_blocking))
        if direction in Direction.FORWARD.value:
            self.drive.on_for_seconds(SpeedPercent(speed), SpeedPercent(speed), duration, block=True)

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

        if direction in (Direction.RIGHT.value + Direction.LEFT.value):
            self._turn(direction, speed)
            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 _movePrize(self, direction, duration: int, speed: int, is_blocking=True):
        """
        Handles movePrize command from the Alexa 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 will run for duration before accepting another command
        """
        print("prize xx command: ({}, {}, {}, {})".format(direction, speed, duration, is_blocking))
        #speed = 100
        if direction in Direction.FORWARD.value:
            self.prize.on_for_seconds(SpeedPercent(speed), SpeedPercent(speed), duration, block=True)

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

        if direction in (Direction.RIGHT.value + Direction.LEFT.value):
            self._turn(direction, speed)
            self.prize.on_for_seconds(SpeedPercent(speed), SpeedPercent(speed), duration, block=is_blocking)

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


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

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


    def _send_event(self, name: EventName, payload):
        """
        Sends a custom event to trigger a sentry 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 _proximity_thread(self):
    #     """
    #     This thread is active to listen for a touch sensor, if pressed we move the motor a little. This may be useful
    #     to get the initial starting position just right.
    #     """
    #     while True:
    #         if self.ts.is_pressed:
    #             print("self.ts.is_pressed........moving forward two seconds.")
    #             self.drive.on_for_seconds(SpeedPercent(60), SpeedPercent(60), 2, block=False)
    #             print("Moved")
    #             time.sleep(0.2)
    #         time.sleep(1)


if __name__ == '__main__':
    # Startup sequence
    gadget = MindstormsGadget()
    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")

mindstorms-math.ini

INI
Required on your Mindstorms EV3, this file contains your unique AmazonId and alexaGadgetSecret . You'll need to edit (and protect the secret) as described in the Mission tutorials.
# 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.

[GadgetSettings]
amazonId = xxxxxxxxxx
alexaGadgetSecret = yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy

[GadgetCapabilities]
Custom.Mindstorms.Gadget = 1.0

model.json

JSON
The schema definition for the interaction model of the mindstorms math Alexa skill
{
    "interactionModel": {
        "languageModel": {
            "invocationName": "mindstorms math",
            "intents": [
                {
                    "name": "AMAZON.CancelIntent",
                    "samples": []
                },
                {
                    "name": "AMAZON.HelpIntent",
                    "samples": []
                },
                {
                    "name": "AMAZON.StopIntent",
                    "samples": []
                },
                {
                    "name": "AMAZON.NavigateHomeIntent",
                    "samples": []
                },
                {
                    "name": "AMAZON.FallbackIntent",
                    "samples": []
                },
                {
                    "name": "AnswerIntent",
                    "slots": [
                        {
                            "name": "UserAnswer",
                            "type": "AMAZON.NUMBER"
                        }
                    ],
                    "samples": [
                        "{UserAnswer}"
                    ]
                },
                {
                    "name": "PlayIntent",
                    "slots": [],
                    "samples": [
                        "play"
                    ]
                },
                {
                    "name": "ModifyIntent",
                    "slots": [],
                    "samples": [
                        "modify"
                    ]
                }
            ],
            "types": []
        }
    }
}

common.js

JavaScript
This is part of the Alexa Skill, it contains utility methods that handle common tasks such as the Cancel and Session Ended intents.
/*
 * 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) {
        let speakOutput = '';
        const attributesManager = handlerInput.attributesManager;
        console.log("In the HelpIntent " + attributesManager.getSessionAttributes().lastActivity);
        if (attributesManager.getSessionAttributes().lastActivity.indexOf('AnswerIntentHandler') !== -1) {
            speakOutput = 'Mindstorms Math is a fun way to practice your math skills. By default Alexa will ask you ten random multiplication questions from 1 times 1 to 12 times 12. ' +
        'You can say "play" to begin or you can say "modify" to change the number of questions or to change the minimum or maximum number used in a question. Please answer the last question';
        } else {
            speakOutput = 'Welcome, Mindstorms Math is a fun way to practice your math skills. By default Alexa will ask you ten random multiplication questions from 1 times 1 to 12 times 12. ' +
        'You can say "play" to begin or you can say "modify" to change the number of questions or to change the minimum or maximum number used in a question. If you are ready to play, say "play".';
        }

        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 from Mindstorms Math!';
        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 = `Sorry I don't understand. Please try again, or say Help.`;

        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
Part of the Alexa skill that defines the data sent on a directive to the EV3 Intelligent Brick
/*
 * 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 handlerInput from Alexa Service
 * @param key - the key to be added
 * @param value - the value be added
 */
exports.putSessionAttribute = function(handlerInput, key, value) {
    const attributesManager = handlerInput.attributesManager;
    let sessionAttributes = attributesManager.getSessionAttributes();
    sessionAttributes[key] = value;
    attributesManager.setSessionAttributes(sessionAttributes);
};

/**
 * 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();
    }));
};

Credits

Mazzmn

Mazzmn

3 projects • 6 followers

Comments