Hackster is hosting Hackster Holidays, Ep. 4: Livestream & Giveaway Drawing. Start streaming on Wednesday!Stream Hackster Holidays, Ep. 4 on Wednesday!
Andy Merhaut
Published © GPL3+

Flight Simulator

Take to the skies and pilot a LEGO airliner with your voice!

AdvancedFull instructions provided2 hours1,098

Things used in this project

Story

Read more

Code

package.json

JSON
File listing dependencies for AWS Lambda function
{
    "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": "Andy Merhaut",
    "license": "ISC",
    "dependencies": {
      "ask-sdk-core": "^2.6.0",
      "ask-sdk-model": "^1.18.0",
      "aws-sdk": "^2.326.0",
      "request": "^2.81.0"
    }
  }
  

util.js

JavaScript
Support file for Lambda function
/*
 * 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();
    }));
};

flightsim.py

Python
Flight Simulator application
##################################################################################
#                       LEGO MINDSTORMS VOICE CHALLENGE                          #
#                              FLIGHT SIMULATOR                                  #
#                                Andy Merhaut                                    #
##################################################################################
import time
import logging
import json
import random
import threading
import asyncio
from enum import Enum
from agt import AlexaGadget
from ev3dev2.led import Leds
from ev3dev2.sound import Sound
from ev3dev2.motor import OUTPUT_A, OUTPUT_B, OUTPUT_C, OUTPUT_D, SpeedRPM,SpeedPercent, Motor, MoveSteering, MoveTank, MotorSet
from ev3dev2.sensor.lego import GyroSensor, UltrasonicSensor

#########################################
#               SENSORS                 #
#########################################
yawSensor = GyroSensor('in1')
yawSensor.mode='GYRO-ANG'
collisionSensor = UltrasonicSensor('in4')

#########################################
#               LOGGING                 #
#########################################
logging.basicConfig(level=logging.INFO)


class MindstormsGadget(AlexaGadget):
    """
    A Mindstorms gadget that performs movement based on voice commands.
    Two types of commands are supported, directional movement and preset.
    """

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

        # Gadget state
        self.patrol_mode = False

        # EV3DEV initialization
        self.leds = Leds()
        self.sound = Sound()
        self.rollMotor = Motor(OUTPUT_A)
        self.pitchMotor = Motor(OUTPUT_B)
        self.drive = MoveSteering(OUTPUT_C, OUTPUT_D)
        self.taxi = MoveTank(OUTPUT_C, OUTPUT_D)
        self.drive1 = Motor(OUTPUT_C)
        self.drive2 = Motor(OUTPUT_D)

    def on_connected(self, device_addr):
        """
        Gadget connected to the paired Echo device.
        :param device_addr: the address of the device we connected to
        """

        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")
        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
        """
        try:
            payload = json.loads(directive.payload.decode("utf-8"))
            print("Control payload: {}".format(payload))
            control_type = payload["type"]

            if control_type == "airspeed":
                self._airspeed(payload["speed"])

            if control_type == "landing":
                self._landing(payload["runway"])

            if control_type == "takeoff":
                self._takeoff(payload["runway"])

            if control_type == "turn":
                self._turn(int(payload["heading"]))  

            if control_type == "autopilot":
                self._autopilot()  

            if control_type == "reset":
                self._reset()    

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

    def _airspeed(self, speed):
        self.taxi.on(SpeedPercent(-1 * speed),SpeedPercent(-1 * speed)) 

    def _turn(self, direction):
        """
        Turns based on the specified direction.
        Calibrated for hard smooth surface.
        :param direction: the turn direction
        """
        yaw = yawSensor.value()
        print("Current yaw angle: {} degrees; heading: {}".format(yaw, direction))

        self.pitchMotor.on_for_rotations(SpeedPercent(50), 1, brake=True, block=False) 

        steering = 0
        if yaw <= 0: 
            steering = 100 #turn right
            self.rollMotor.polarity = "inversed"
        elif yaw < direction:
            steering = 100 #turn right
            self.rollMotor.polarity = "inversed"
        elif yaw > direction:
            steering = -100 #turn left
            self.rollMotor.polarity = "normal"

        #START ROLL
        self.rollMotor.on_for_rotations(SpeedPercent(90), 30, brake=False, block=False)

        #TURN
        self.drive.on(steering, SpeedPercent(5))
        while (abs(yawSensor.value() - direction) >= 5):
            if abs(yawSensor.value() - direction) <= 5:
                self.taxi.reset()
                break  
        
        if self.rollMotor.polarity in ["normal"]:
            self.rollMotor.polarity = "inversed"
        else:
            self.rollMotor.polarity = "normal"  

        #END ROLL
        self.rollMotor.on_for_rotations(SpeedPercent(90), 30, brake=False, block=False)

        #CRUISE
        self.taxi.on(SpeedPercent(-10),SpeedPercent(-10)) 

    def _takeoff(self, runway):

        #If takeoff runway is "R", taxi then turn right
        direction = 100 if runway in ["right"] else -100

        #TAXI RUNWAY
        self.pitchMotor.on_for_seconds(SpeedPercent(50), 1, brake=True, block=True)
        self.taxi.on_for_seconds(SpeedPercent(-10), SpeedPercent(-10), 32, brake=False, block=True)
        self.drive.on_for_seconds(direction, SpeedPercent(10), 4.6, brake=False, block=True)
        
        #START FLASHING LEDS
        self._flash()
        
        #FULL THRUST
        accelerate = 0
        while (accelerate > -50):
            self.drive1.on(SpeedPercent(accelerate), brake=False, block=False)
            self.drive2.on(SpeedPercent(accelerate), brake=False, block=False)        
            time.sleep(0.0222) #accelerate in 2 seconds
            accelerate = accelerate - 1
        
        #NOSE UP
        accelerate = 0
        while (accelerate > -90): 
            self.pitchMotor.on(SpeedPercent(accelerate * 0.333), brake=True, block=True)    
            time.sleep(0.0222) #accelerate to 90 in 2 seconds
            accelerate = accelerate - 1

        #FULL SPEED
        accelerate = -50
        while (accelerate < -20):
            self.taxi.on(SpeedPercent(accelerate),SpeedPercent(accelerate))  
            time.sleep(0.0142857) #decel from -90 to -20 in 1 seconds
            accelerate = accelerate + 1

        #LEVEL
        self.pitchMotor.on_for_rotations(SpeedPercent(50), 1, brake=True, block=False) 
        
        #CRUISE
        self.taxi.on(SpeedPercent(-10),SpeedPercent(-10)) 
    
    def _landing(self, runway):

        #LEVEL
        self.pitchMotor.on_for_rotations(SpeedPercent(50), 1, brake=True, block=False) 

        #START FLASHING LEDS (1s)
        self._flash()

        #Full speed
        accelerate = -10
        while (accelerate > -80):
            self.taxi.on(SpeedPercent(accelerate),SpeedPercent(accelerate))  
            time.sleep(0.0142857) #accel in 1 seconds
            accelerate = accelerate - 1

        #NOSE UP
        accelerate = 0
        while (accelerate > -90): 
            self.pitchMotor.on(SpeedPercent(accelerate * 0.6), brake=True, block=True)    
            time.sleep(0.0333) #accelerate to -90 in 2 seconds
            accelerate = accelerate - 1

        #LEVEL
        self.pitchMotor.on_for_rotations(SpeedPercent(50), 1, brake=True, block=False) 

        #BRAKE
        accelerate = -80
        while (accelerate < -10):
            self.taxi.on(SpeedPercent(accelerate),SpeedPercent(accelerate))  
            time.sleep(0.042857) #decel in 3 seconds
            accelerate = accelerate + 1

        #TAXI RUNWAY
        #If takeoff runway is "R", taxi then turn right
        direction = 100 if runway in ["right"] else -100
        self.taxi.on_for_seconds(SpeedPercent(-10), SpeedPercent(-10), 10, brake=False, block=True)
        self.drive.on_for_seconds(direction, SpeedPercent(10), 4.6, brake=False, block=True)
        collisionSensor.mode = "US-DIST-CM" # Use sensor

    def _autopilot(self):


        collisionSensor.mode = "US-DIST-CM" # Use sensor
        
        #LEVEL
        self.pitchMotor.on_for_rotations(SpeedPercent(50), 1, brake=True, block=False) 

        accelerate = 0
        while (accelerate > -20):
            self.drive1.on(SpeedPercent(accelerate), brake=False, block=False)
            self.drive2.on(SpeedPercent(accelerate), brake=False, block=False)        
            time.sleep(0.1) #accelerate to 20 in 2 seconds
            accelerate = accelerate - 1

        # -100 left +100 right
        timeValue = 0
        while (timeValue < 400): #2 minutes
            distance = collisionSensor.distance_centimeters
            print("Distance: {}cm".format(distance))

            if distance < 60:
                accelerate = 0
                steering = distance - 100

                while accelerate > steering: #turning circle
                    self.drive.on(accelerate, SpeedPercent(10))
                    accelerate = accelerate - 1
                    time.sleep(1/(abs(steering) + 1))

            else:
                self.taxi.on(SpeedPercent(-20),SpeedPercent(-20))
            time.sleep(0.3)
            timeValue = timeValue + 1
    
    def _reset(self):
        self.pitchMotor.reset()
        self.taxi.reset()
        self.leds.set_color("LEFT", "GREEN")
        self.leds.set_color("RIGHT", "GREEN")
        collisionSensor.mode = "US-DIST-CM"
      
    def _flash(self):
        collisionSensor.mode = "US-LISTEN" # Flash ultrasonic sensor
        counter = 0
        while (counter < 10):
            self.leds.set_color("LEFT", "BLACK")
            self.leds.set_color("RIGHT", "RED")
            time.sleep(0.2)
            self.leds.set_color("LEFT", "RED")
            self.leds.set_color("RIGHT", "BLACK")
            time.sleep(0.1)
            counter = counter + 1

if __name__ == '__main__':

    # Startup sequence
    gadget = MindstormsGadget()
    gadget.leds.set_color("LEFT", "GREEN")
    gadget.leds.set_color("RIGHT", "GREEN")

    # Gadget main entry point
    gadget.main()

    # Shutdown sequence
    gadget.leds.set_color("LEFT", "BLACK")
    gadget.leds.set_color("RIGHT", "BLACK")

model.json

JSON
Alexa skill model
{
    "interactionModel": {
        "languageModel": {
            "invocationName": "mindstorms",
            "intents": [
                {
                    "name": "AMAZON.CancelIntent",
                    "samples": []
                },
                {
                    "name": "AMAZON.HelpIntent",
                    "samples": []
                },
                {
                    "name": "AMAZON.StopIntent",
                    "samples": []
                },
                {
                    "name": "AMAZON.NavigateHomeIntent",
                    "samples": []
                },
                {
                    "name": "SetSpeedIntent",
                    "slots": [
                        {
                            "name": "Speed",
                            "type": "AMAZON.NUMBER",
                            "samples": [
                                "LEGO One Tower new speed {Speed} knots"
                            ]
                        }
                    ],
                    "samples": [
                        "LEGO One Tower reduce speed to {Speed} if unable advise",
                        "LEGO One Tower Maintain {Speed} knots",
                        "LEGO One this is tower reduce speed to {Speed}",
                        "LEGO One Tower increase speed to {Speed} knots"
                    ]
                },
                {
                    "name": "SetHeadingIntent",
                    "slots": [
                        {
                            "name": "headingA",
                            "type": "HeadingValue",
                            "samples": [
                                "turn to {headingA} {headingB} {headingC}",
                                "new heading {headingA} {headingB} {headingC}",
                                "heading {headingA} {headingB} {headingC}"
                            ]
                        },
                        {
                            "name": "headingB",
                            "type": "HeadingValue",
                            "samples": [
                                "heading {headingA} {headingB} {headingC}",
                                "new heading {headingA} {headingB} {headingC}",
                                "turn to {headingA} {headingB} {headingC}"
                            ]
                        },
                        {
                            "name": "headingC",
                            "type": "HeadingValue",
                            "samples": [
                                "heading {headingA} {headingB} {headingC}",
                                "new heading {headingA} {headingB} {headingC}",
                                "turn to {headingA} {headingB} {headingC}"
                            ]
                        }
                    ],
                    "samples": [
                        "lego one this is tower new heading {headingA} {headingB} {headingC}",
                        "lego one tower new heading {headingA} {headingB} {headingC}",
                        "LEGO One tower heading {headingA} {headingB} {headingC}",
                        "LEGO One turn to {headingA} {headingB} {headingC}"
                    ]
                },
                {
                    "name": "TakeoffIntent",
                    "slots": [
                        {
                            "name": "runway",
                            "type": "RunwayDirection",
                            "samples": [
                                "LEGO One Tower clear for takeoff {runwayA} {runwayB} {runway}",
                                "LEGO One Tower clear for takeoff runway {runwayA} {runwayB} {runway}",
                                "LEGO One Tower depart {runwayA} {runwayB} {runway}"
                            ]
                        },
                        {
                            "name": "runwayA",
                            "type": "HeadingValue"
                        },
                        {
                            "name": "runwayB",
                            "type": "HeadingValue"
                        }
                    ],
                    "samples": [
                        "LEGO One Tower go for takeoff runway {runwayA} {runwayB} {runway}",
                        "LEGO One Tower clear for takeoff runway {runwayA} {runwayB} {runway}",
                        "LEGO One this is Ground you are cleared for takeoff {runwayA} {runwayB} {runway}"
                    ]
                },
                {
                    "name": "AutoPilotIntent",
                    "slots": [],
                    "samples": [
                        "Engage autopilot",
                        "Turn on autopilot"
                    ]
                },
                {
                    "name": "ResetIntent",
                    "slots": [],
                    "samples": [
                        "Reset"
                    ]
                },
                {
                    "name": "LandingIntent",
                    "slots": [
                        {
                            "name": "runwayA",
                            "type": "HeadingValue"
                        },
                        {
                            "name": "runwayB",
                            "type": "HeadingValue"
                        },
                        {
                            "name": "runway",
                            "type": "RunwayDirection"
                        }
                    ],
                    "samples": [
                        "Runway {runwayA} {runwayB} {runway} cleared to land",
                        "LEGO One Tower {runwayA} {runwayB} {runway} cleared to land",
                        "LEGO One Tower cleared to land runway {runwayA} {runwayB} {runway}"
                    ]
                }
            ],
            "types": [
                {
                    "name": "DirectionType",
                    "values": [
                        {
                            "name": {
                                "value": "brake"
                            }
                        },
                        {
                            "name": {
                                "value": "go backward"
                            }
                        },
                        {
                            "name": {
                                "value": "go forward"
                            }
                        },
                        {
                            "name": {
                                "value": "go right"
                            }
                        },
                        {
                            "name": {
                                "value": "go left"
                            }
                        },
                        {
                            "name": {
                                "value": "right"
                            }
                        },
                        {
                            "name": {
                                "value": "left"
                            }
                        },
                        {
                            "name": {
                                "value": "backwards"
                            }
                        },
                        {
                            "name": {
                                "value": "backward"
                            }
                        },
                        {
                            "name": {
                                "value": "forwards"
                            }
                        },
                        {
                            "name": {
                                "value": "forward"
                            }
                        }
                    ]
                },
                {
                    "name": "CommandType",
                    "values": [
                        {
                            "name": {
                                "value": "circle"
                            }
                        },
                        {
                            "name": {
                                "value": "square"
                            }
                        },
                        {
                            "name": {
                                "value": "patrol"
                            }
                        },
                        {
                            "name": {
                                "value": "cannon"
                            }
                        },
                        {
                            "name": {
                                "value": "all shot"
                            }
                        },
                        {
                            "name": {
                                "value": "one shot"
                            }
                        }
                    ]
                },
                {
                    "name": "HeadingValue",
                    "values": [
                        {
                            "name": {
                                "value": "9"
                            }
                        },
                        {
                            "name": {
                                "value": "8"
                            }
                        },
                        {
                            "name": {
                                "value": "7"
                            }
                        },
                        {
                            "name": {
                                "value": "6"
                            }
                        },
                        {
                            "name": {
                                "value": "5"
                            }
                        },
                        {
                            "name": {
                                "value": "4"
                            }
                        },
                        {
                            "name": {
                                "value": "3"
                            }
                        },
                        {
                            "name": {
                                "value": "2"
                            }
                        },
                        {
                            "name": {
                                "value": "1"
                            }
                        },
                        {
                            "name": {
                                "value": "0"
                            }
                        }
                    ]
                },
                {
                    "name": "UpDownValue",
                    "values": [
                        {
                            "id": "1",
                            "name": {
                                "value": "Maintain",
                                "synonyms": [
                                    "remain",
                                    "stay"
                                ]
                            }
                        },
                        {
                            "id": "0",
                            "name": {
                                "value": "Decrease",
                                "synonyms": [
                                    "reduce"
                                ]
                            }
                        },
                        {
                            "id": "2",
                            "name": {
                                "value": "Increase"
                            }
                        }
                    ]
                },
                {
                    "name": "RunwayDirection",
                    "values": [
                        {
                            "name": {
                                "value": "Center"
                            }
                        },
                        {
                            "name": {
                                "value": "Right"
                            }
                        },
                        {
                            "name": {
                                "value": "Left"
                            }
                        }
                    ]
                }
            ]
        },
        "dialog": {
            "intents": [
                {
                    "name": "SetHeadingIntent",
                    "confirmationRequired": true,
                    "prompts": {
                        "confirmation": "Confirm.Intent.743889462359"
                    },
                    "slots": [
                        {
                            "name": "headingA",
                            "type": "HeadingValue",
                            "confirmationRequired": false,
                            "elicitationRequired": true,
                            "prompts": {
                                "elicitation": "Elicit.Slot.732301228136.1546499721780"
                            }
                        },
                        {
                            "name": "headingB",
                            "type": "HeadingValue",
                            "confirmationRequired": false,
                            "elicitationRequired": true,
                            "prompts": {
                                "elicitation": "Elicit.Slot.1440849301641.1304054795354"
                            }
                        },
                        {
                            "name": "headingC",
                            "type": "HeadingValue",
                            "confirmationRequired": false,
                            "elicitationRequired": true,
                            "prompts": {
                                "elicitation": "Elicit.Slot.1440849301641.1181669313456"
                            }
                        }
                    ]
                },
                {
                    "name": "TakeoffIntent",
                    "confirmationRequired": true,
                    "prompts": {
                        "confirmation": "Confirm.Intent.1563286004246"
                    },
                    "slots": [
                        {
                            "name": "runway",
                            "type": "RunwayDirection",
                            "confirmationRequired": false,
                            "elicitationRequired": true,
                            "prompts": {
                                "elicitation": "Elicit.Slot.30722260886.1156290942273"
                            }
                        },
                        {
                            "name": "runwayA",
                            "type": "HeadingValue",
                            "confirmationRequired": false,
                            "elicitationRequired": false,
                            "prompts": {}
                        },
                        {
                            "name": "runwayB",
                            "type": "HeadingValue",
                            "confirmationRequired": false,
                            "elicitationRequired": false,
                            "prompts": {}
                        }
                    ]
                },
                {
                    "name": "LandingIntent",
                    "confirmationRequired": true,
                    "prompts": {
                        "confirmation": "Confirm.Intent.445899441332"
                    },
                    "slots": [
                        {
                            "name": "runwayA",
                            "type": "HeadingValue",
                            "confirmationRequired": false,
                            "elicitationRequired": false,
                            "prompts": {}
                        },
                        {
                            "name": "runwayB",
                            "type": "HeadingValue",
                            "confirmationRequired": false,
                            "elicitationRequired": false,
                            "prompts": {}
                        },
                        {
                            "name": "runway",
                            "type": "RunwayDirection",
                            "confirmationRequired": false,
                            "elicitationRequired": false,
                            "prompts": {}
                        }
                    ]
                },
                {
                    "name": "SetSpeedIntent",
                    "confirmationRequired": false,
                    "prompts": {},
                    "slots": [
                        {
                            "name": "Speed",
                            "type": "AMAZON.NUMBER",
                            "confirmationRequired": false,
                            "elicitationRequired": true,
                            "prompts": {
                                "elicitation": "Elicit.Slot.137219819843.204633308907"
                            },
                            "validations": [
                                {
                                    "type": "isLessThan",
                                    "prompt": "Slot.Validation.137219819843.204633308907.221381148916",
                                    "value": "550"
                                },
                                {
                                    "type": "isGreaterThan",
                                    "prompt": "Slot.Validation.137219819843.204633308907.221557497054",
                                    "value": "160"
                                }
                            ]
                        }
                    ]
                }
            ],
            "delegationStrategy": "ALWAYS"
        },
        "prompts": [
            {
                "id": "Elicit.Slot.732301228136.1546499721780",
                "variations": [
                    {
                        "type": "PlainText",
                        "value": "Tower LEGO One repeat new heading?"
                    }
                ]
            },
            {
                "id": "Elicit.Slot.1440849301641.1181669313456",
                "variations": [
                    {
                        "type": "PlainText",
                        "value": "Tower LEGO One repeat new heading?"
                    }
                ]
            },
            {
                "id": "Elicit.Slot.1440849301641.1304054795354",
                "variations": [
                    {
                        "type": "PlainText",
                        "value": "Tower LEGO One repeat new heading?"
                    }
                ]
            },
            {
                "id": "Confirm.Slot.1440849301641.1536874964390",
                "variations": [
                    {
                        "type": "PlainText",
                        "value": "Tower LEGO One confirm {headingA} {headingB} {headingC}"
                    }
                ]
            },
            {
                "id": "Confirm.Intent.743889462359",
                "variations": [
                    {
                        "type": "PlainText",
                        "value": "Tower LEGO One. Confirm {headingA} {headingB} {headingC}"
                    }
                ]
            },
            {
                "id": "Confirm.Intent.1539523755367",
                "variations": [
                    {
                        "type": "PlainText",
                        "value": "Tower. LEGO One. {upDown} altitude {altitude} feet."
                    }
                ]
            },
            {
                "id": "Elicit.Slot.105486094802.1372282560033",
                "variations": [
                    {
                        "type": "PlainText",
                        "value": "Tower LEGO One repeat new altitude?"
                    }
                ]
            },
            {
                "id": "Elicit.Slot.105486094802.429049922393",
                "variations": [
                    {
                        "type": "PlainText",
                        "value": "Tower LEGO One repeat new altitude?"
                    }
                ]
            },
            {
                "id": "Confirm.Intent.1563286004246",
                "variations": [
                    {
                        "type": "PlainText",
                        "value": "Tower LEGO One confirm {runwayA} {runwayB} {runway} . "
                    }
                ]
            },
            {
                "id": "Elicit.Slot.30722260886.1156290942273",
                "variations": [
                    {
                        "type": "PlainText",
                        "value": "Tower LEGO repeat runway for departure"
                    }
                ]
            },
            {
                "id": "Confirm.Intent.445899441332",
                "variations": [
                    {
                        "type": "PlainText",
                        "value": "Tower LEGO One heavy confirm {runwayA} {runwayB} {runway} . "
                    }
                ]
            },
            {
                "id": "Slot.Validation.137219819843.204633308907.221381148916",
                "variations": [
                    {
                        "type": "PlainText",
                        "value": "Tower LEGO One we can't go that fast please advise"
                    }
                ]
            },
            {
                "id": "Slot.Validation.137219819843.204633308907.221557497054",
                "variations": [
                    {
                        "type": "PlainText",
                        "value": "Tower LEGO One that's gonna be below our stall - unable to maintain that airspeed"
                    }
                ]
            },
            {
                "id": "Elicit.Slot.137219819843.204633308907",
                "variations": [
                    {
                        "type": "PlainText",
                        "value": "Tower LEGO One repeat new airspeed"
                    }
                ]
            }
        ]
    }
}

common.js

JavaScript
Support file for Lambda
/*
 * 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
    };

index.js

JavaScript
Flight Simulator Lambda code
const Alexa = require('ask-sdk-core');
const Util = require('./util');
const Common = require('./common');
const NAMESPACE = 'Custom.Mindstorms.Gadget';
const NAME_CONTROL = 'control';

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

        let request = handlerInput.requestEnvelope;
        let { apiEndpoint, apiAccessToken } = request.context.System;
        let apiResponse = await Util.getConnectedEndpoints(apiEndpoint, apiAccessToken);
        if ((apiResponse.endpoints || []).length === 0) {
            return handlerInput.responseBuilder
            .speak(`I couldn't find an EV3 Brick connected to this Echo device. Please check to make sure your EV3 Brick is connected, and try again.`)
            .getResponse();
        }

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

        return handlerInput.responseBuilder
            .speak("Welcome, you can start issuing move commands")
            .reprompt("Awaiting commands")
            .getResponse();
    }
};

const SetAutoPilotIntentHandler = {
    canHandle(handlerInput) {
        return Alexa.getRequestType(handlerInput.requestEnvelope) === 'IntentRequest'
            && Alexa.getIntentName(handlerInput.requestEnvelope) === 'AutoPilotIntent';
    },
    handle: function (handlerInput) {

        const attributesManager = handlerInput.attributesManager;
        const endpointId = attributesManager.getSessionAttributes().endpointId || [];
        
        const directive = Util.build(endpointId, NAMESPACE, NAME_CONTROL,
            {
                type: 'autopilot'
            });
        
        return handlerInput.responseBuilder
            .speak(`LEGO ONE autopilot confirmed. Autonomous Two will do its best to avoid obstacles, but accidents happen.`)
            .addDirective(directive)
            .reprompt("awaiting command")
            .getResponse();
    }
};

const ResetIntentHandler = {
    canHandle(handlerInput) {
        return Alexa.getRequestType(handlerInput.requestEnvelope) === 'IntentRequest'
            && Alexa.getIntentName(handlerInput.requestEnvelope) === 'ResetIntent';
    },
    handle: function (handlerInput) {

        const attributesManager = handlerInput.attributesManager;
        const endpointId = attributesManager.getSessionAttributes().endpointId || [];
        
        const directive = Util.build(endpointId, NAMESPACE, NAME_CONTROL,
            {
                type: 'reset'
            });
        
        return handlerInput.responseBuilder
            .speak(`Resetting motors`)
            .addDirective(directive)
            .reprompt("awaiting command")
            .getResponse();
    }
};

const SetSpeedIntentHandler = {
    canHandle(handlerInput) {
        return Alexa.getRequestType(handlerInput.requestEnvelope) === 'IntentRequest'
            && Alexa.getIntentName(handlerInput.requestEnvelope) === 'SetSpeedIntent';
    },
    handle: function (handlerInput) {
        
        const attributesManager = handlerInput.attributesManager;
        const endpointId = attributesManager.getSessionAttributes().endpointId || [];

        let speedValue = Alexa.getSlotValue(handlerInput.requestEnvelope, 'Speed');
        let speed = parseInt(speedValue) / 10; //convert knots to percentage
        
        Util.putSessionAttribute(handlerInput, 'speed', speed);
        
        const directive = Util.build(endpointId, NAMESPACE, NAME_CONTROL,
            {
                type: 'airspeed',
                speed: speed
            });

        return handlerInput.responseBuilder
            .speak(`Tower LEGO One new airspeed ${speedValue} knots.`)
            .addDirective(directive)
            .reprompt("ready when you are")
            .getResponse();
    }
};

const LandingIntentHandler = {
    canHandle(handlerInput) {
        return Alexa.getRequestType(handlerInput.requestEnvelope) === 'IntentRequest'
            && Alexa.getIntentName(handlerInput.requestEnvelope) === 'LandingIntent';
    },
    handle: function (handlerInput) {

        const attributesManager = handlerInput.attributesManager;
        const endpointId = attributesManager.getSessionAttributes().endpointId || [];
        
        let runway = Alexa.getSlotValue(handlerInput.requestEnvelope, 'runway');
        Util.putSessionAttribute(handlerInput, 'runway', runway);
        
        const directive = Util.build(endpointId, NAMESPACE, NAME_CONTROL,
            {
                type: 'landing',
                runway: runway
            });
        
        return handlerInput.responseBuilder
            .speak(`Five hundred. Four hundred. Three hundred. Approaching minimums. Two hundred. Minimums. Continue. One hundred. Fifty, forty, thirty, twenty. Retard. Retard. Retard. Spoilers. Reverse green. Manual brakes.`)
            .addDirective(directive)
            .reprompt("awaiting command")
            .getResponse();
    }
};

const SetHeadingIntentHandler = {
    canHandle(handlerInput) {
        return Alexa.getRequestType(handlerInput.requestEnvelope) === 'IntentRequest'
            && Alexa.getIntentName(handlerInput.requestEnvelope) === 'SetHeadingIntent';
    },
    handle: function (handlerInput) {

        const attributesManager = handlerInput.attributesManager;
        const endpointId = attributesManager.getSessionAttributes().endpointId || [];
        
        let headingA = Alexa.getSlotValue(handlerInput.requestEnvelope, 'headingA');
        let headingB = Alexa.getSlotValue(handlerInput.requestEnvelope, 'headingB');
        let headingC = Alexa.getSlotValue(handlerInput.requestEnvelope, 'headingC');
        
        let heading = `${headingA}${headingB}${headingC}    `;
        
        Util.putSessionAttribute(handlerInput, 'heading', heading);
        
        const directive = Util.build(endpointId, NAMESPACE, NAME_CONTROL,
            {
                type: 'turn',
                heading: heading,
                speed: "1"
            });
        
        return handlerInput.responseBuilder
            .speak(`Tower LEGO One heading  ${headingA} ${headingB} ${headingC}.`)
            .addDirective(directive)
            .reprompt("awaiting command")
            .getResponse();
    }
};

const TakeoffIntentHandler = {
    canHandle(handlerInput) {
        return Alexa.getRequestType(handlerInput.requestEnvelope) === 'IntentRequest'
            && Alexa.getIntentName(handlerInput.requestEnvelope) === 'TakeoffIntent';
    },
    handle: function (handlerInput) {

        const attributesManager = handlerInput.attributesManager;
        const endpointId = attributesManager.getSessionAttributes().endpointId || [];
        
        let runway = Alexa.getSlotValue(handlerInput.requestEnvelope, 'runway');
        Util.putSessionAttribute(handlerInput, 'runway', runway);
        
        const directive = Util.build(endpointId, NAMESPACE, NAME_CONTROL,
            {
                type: 'takeoff',
                runway: runway
            });
        
        return handlerInput.responseBuilder
            .speak(`On behalf of the flight crew we would like to thank you for your business and we hope you enjoy your simulation. Flight attendants please prepare the cabin for departure.`)
            .addDirective(directive)
            .reprompt("awaiting command")
            .getResponse();
    }
};

exports.handler = Alexa.SkillBuilders.custom()
    .addRequestHandlers(
        LaunchRequestHandler,
        SetHeadingIntentHandler,
        TakeoffIntentHandler,
        SetAutoPilotIntentHandler,
        ResetIntentHandler,
        LandingIntentHandler,
        SetSpeedIntentHandler,
        Common.HelpIntentHandler,
        Common.CancelAndStopIntentHandler,
        Common.SessionEndedRequestHandler,
        Common.IntentReflectorHandler, 
    )
    .addRequestInterceptors(Common.RequestInterceptor)
    .addErrorHandlers(
        Common.ErrorHandler,
    )
    .lambda();

Credits

Andy Merhaut

Andy Merhaut

1 project • 3 followers

Comments