Dorin SubaAndrei Ciobanu
Created December 3, 2019

Duplo zoo Ev3luater

Teaches kids about animal. Jack in a Box random selector of an duplo animal. Names and tells a story about it.

BeginnerFull instructions provided4 hours154

Things used in this project

Hardware components

Mindstorms EV3 Programming Brick / Kit
LEGO Mindstorms EV3 Programming Brick / Kit
×1
Mini SDHC Card
×1
Amazon Echo
Amazon Alexa Amazon Echo
×1
Lego Pixy2 Camera
×1

Software apps and online services

ev3dev2
VS Code
Microsoft VS Code
Alexa Skills Kit
Amazon Alexa Alexa Skills Kit
Alexa Gadgets Toolkit
Amazon Alexa Alexa Gadgets Toolkit

Story

Read more

Custom parts and enclosures

Components needed

Schematics

Lego Duplo Zoo Ev3luater schematics

Code

Lego Duplo Zoo Ev3luater code

Python
#!/usr/bin/env python3

# 1. Misinea 1
# - Alexa start Zoo Ev3luator
# - Motor C merge inainte cu viteza de 15 pana senzorul de culoare citeste Albastru(pentru calibrare)
# - Aleza raspunde ca "Duplo Zoo Ev3luator is ready. You can learn about animals or take a quiz."

# 2. Misiunea 2
# - Alexa, learn
# - What animal would you like to learn about?
# - EX: Pig
# - motor C merge inapoi cu viteza 15 % pana camera detecteaza Obiect 1 sau senzorul de culoare detecteaza Rosu
# - daca camera detecteaza Obiectul_1 motor_C se opreste, Motor_B merge - 30 % cu 360 grade si apoi 30 % cu 360 . Apoi motor D merge cu viteza 50, 15 rotatii.
# Alexa spune o poveste despre porc apoi motor merge cu viteza - 50 % , 15 rotatii. Si se repeta pasii 2 si 3 de la misiunea 1.
# - daca animalul nu este pe banda, senzorul de culoare deteacteaza Rosu, alexa spune ca animalul nu e pe banda si sa incerci altceva si se repeta pasii 2 si 3 de la misiunea 1

# 3. Misiunea 3 .
# - Quiz
# - motor C merge inapoi cu viteza 15 % pana camera detecteaza cele 3 obiecte de pe banda si le asignezi o variabila(1, 2, 3) pana ajunge la Rosu.
# - apoi motor C merge inapoi cu viteza 15 % pana detecteaza unu random dintre cele 3 .
# - cand ajunge la obiect motor_C se opreste, Motor_B merge - 30 % cu 360 grade si apoi 30 % cu 360 . Apoi motor D merge cu viteza 50, 15 rotatii.
# Alexa intreaba "What animal is This". Daca nimeresti spune "well done" daca nu spune "Wrong, this is a <<Object1>> ". Apoi motor D merge cu viteza - 50 15 rotatii si se repeta pasii 2 si 3 de la misiunea 1.


# Animale:

# 1. Pig
# 2. Lion
# 3. Rabbit
# 4. Cat
# 5. Dog
# 6. Chicken
# 7. Cow
import os
import logging
import json
import threading
import random

from time import sleep
from sys import stderr, stdout
import traceback

from ev3dev2.motor import LargeMotor, MediumMotor, OUTPUT_B, OUTPUT_C, OUTPUT_D
from ev3dev2.sound import Sound
from ev3dev2.led import Leds
from ev3dev2.sensor.lego import ColorSensor

from ev3dev2.port import LegoPort
from ev3dev2.sensor import INPUT_1
from smbus import SMBus

from agt import AlexaGadget

# set logger to display on both EV3 Brick and console
logging.basicConfig(level=logging.INFO, stream=stdout,
                    format='%(message)s')
logging.getLogger().addHandler(logging.StreamHandler(stderr))
logger = logging.getLogger(__name__)

# Make sure the same address is set in Pixy2
ADDRESS = 0x54


class MindstormsGadget(AlexaGadget):
    """
    An Mindstorms gadget that will react to the Alexa wake word.
    """

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

        self.leds = Leds()
        self.sound = Sound()
        self.motor_conveyor = LargeMotor(OUTPUT_C)
        self.motor_pusher = LargeMotor(OUTPUT_B)
        self.motor_lifter = MediumMotor(OUTPUT_D)
        self.color_sensor = ColorSensor()
        self.lifter_up = False
        self.should_reset = False

        # Settings for I2C (SMBus(3) for INPUT_1)
        self.bus = SMBus(3)

    def on_connected(self, device_addr):
        """
        Gadget connected to the paired Echo device.
        :param device_addr: the address of the device we connected to
        """
        self.leds.set_color("LEFT", "GREEN")
        self.leds.set_color("RIGHT", "GREEN")
        logger.info("{} connected to Echo device".format(self.friendly_name))

    def on_disconnected(self, device_addr):
        """
        Gadget disconnected from the paired Echo device.
        :param device_addr: the address of the device we disconnected from
        """
        self.leds.set_color("LEFT", "BLACK")
        self.leds.set_color("RIGHT", "BLACK")
        logger.info("{} disconnected from Echo device".format(
            self.friendly_name))

    def on_custom_mindstorms_gadget_init(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("Init payload: {}".format(payload), file=stderr)
            self.to_start()
            self.send_custom_event('Custom.Mindstorms.Gadget',
                                   'init', {'done': True})
        except KeyError:
            print("Missing expected parameters: {}".format(
                directive), file=stderr)

    def on_custom_mindstorms_gadget_reset(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("Reset payload: {}".format(payload), file=stderr)
            self.to_start()
        except KeyError:
            print("Missing expected parameters: {}".format(
                directive), file=stderr)

    def on_custom_mindstorms_gadget_learn(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("Learn payload: {}".format(payload), file=stderr)
            found = self.find_animal(animal_index=int(payload["animal_id"]))
            if(found):
                self.send_custom_event('Custom.Mindstorms.Gadget',
                                   'learn',
                                   {'done': True, 'found': True})
            else:
                self.send_custom_event('Custom.Mindstorms.Gadget',
                                   'learn',
                                   {'done': True, 'found': False})      
        except KeyError:
            print("Missing expected parameters: {}".format(
                directive), file=stderr)

    def on_custom_mindstorms_gadget_quiz(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("Quiz payload: {}".format(payload), file=stderr)
            self.to_start()
            the_chosen_one = self.quiz_animals()
            self.send_custom_event('Custom.Mindstorms.Gadget',
                                   'quiz', {'animal_id': str(the_chosen_one)})
        except KeyError:
            print("Missing expected parameters: {}".format(
                directive), file=stderr)

    def to_start(self):
        print('Back to start', file=stderr)
        if self.lifter_up:
            self.motor_lifter.on_for_rotations(speed=50, rotations=-15)
            self.lifter_up = False
        # BLUE = 2
        if self.color_sensor.color != 2:
            self.motor_conveyor.on(speed=15)
            while self.color_sensor.color != 2:
                sleep(0.1)
            self.motor_conveyor.stop()
        self.should_reset = False

    def find_animal(self, animal_index):
        if self.should_reset or self.lifter_up:
            self.to_start()
        self.should_reset = True
        # RED = 5
        if self.color_sensor.color != 5 and self.find_animal_on_camera(index=animal_index) == 0:
            print('Finding animal', file=stderr)
            self.motor_conveyor.on(speed=-15)
            while self.color_sensor.color != 5 and self.find_animal_on_camera(index=animal_index) == 0:
                sleep(0.1)
            self.motor_conveyor.stop()
        if self.find_animal_on_camera(index=animal_index) != 0:
            self.motor_pusher.on_for_rotations(speed=-30, rotations=1)
            self.motor_pusher.on_for_rotations(speed=30, rotations=1)
            self.motor_lifter.on_for_rotations(speed=50, rotations=15)
            self.lifter_up = True
            return True
        else:
            return False

    def find_animal_on_camera(self, index):
        data = [174, 193, 32, 2, int(2**(index-1)), 1]

        # Request block
        self.bus.write_i2c_block_data(ADDRESS, 0, data)
        # Read block
        block = self.bus.read_i2c_block_data(ADDRESS, 0, 20)

        # Extract data
        sig = block[7]*256 + block[6]

        print("Signature: {} = {},{}".format(sig,block[6],block[7]), file=stderr)

        if index == sig:
            sleep(0.5)
            return index

        return 0

        # if self.color_sensor.color == 4:
        #     return index
        # else:
        #     return 0


    def scan_animal_on_camera(self):
        data = [174, 193, 32, 2, 127, 1]

        # Request block
        self.bus.write_i2c_block_data(ADDRESS, 0, data)
        # Read block
        block = self.bus.read_i2c_block_data(ADDRESS, 0, 20)

        # Extract data
        sig = block[7]*256 + block[6]

        if sig > 0 and sig <= 7:
            return sig

        return 0
        # return random.choice(list([1, 2, 3]))

    def quiz_animals(self):
        animals = set()
        if self.should_reset or self.lifter_up:
            self.to_start()
        self.should_reset = True
        # RED = 5
        if self.color_sensor.color != 5:
            print('Scanning animals', file=stderr)
            self.motor_conveyor.on(speed=-15)
            while self.color_sensor.color != 5:
                sleep(0.1)
                sig = self.scan_animal_on_camera()
                if sig != 0:
                    animals.add(sig)

            self.motor_conveyor.stop()
        
        the_chosen_one = random.choice(list(animals))
        print('The chosen one is {}'.format(the_chosen_one), file=stderr)
        self.find_animal(the_chosen_one)
        return the_chosen_one




if __name__ == '__main__':

    # Set LEGO port for Pixy2 on input port 1
    in1 = LegoPort(INPUT_1)
    in1.mode = 'other-i2c'
    # Short wait for port to get ready
    sleep(0.5)

    gadget = MindstormsGadget()

    # Set LCD font and turn off blinking LEDs
    os.system('setfont Lat7-Terminus12x6')
    gadget.leds.set_color("LEFT", "BLACK")
    gadget.leds.set_color("RIGHT", "BLACK")

    # Startup sequence
    # gadget.sound.play_song((('C4', 'e'), ('D4', 'e'), ('E5', 'q')))
    gadget.leds.set_color("LEFT", "GREEN")
    gadget.leds.set_color("RIGHT", "GREEN")

    # Gadget main entry point
    gadget.main()

    # Shutdown sequence
    # gadget.sound.play_song((('E5', 'e'), ('C4', 'e')))
    gadget.leds.set_color("LEFT", "BLACK")
    gadget.leds.set_color("RIGHT", "BLACK")

zoo.zip

Python
No preview (download only).

model.json for Alexa

JavaScript
{
    "interactionModel": {
        "languageModel": {
            "invocationName": "duplo zoo",
            "intents": [
                {
                    "name": "AMAZON.CancelIntent",
                    "samples": []
                },
                {
                    "name": "AMAZON.HelpIntent",
                    "samples": []
                },
                {
                    "name": "AMAZON.StopIntent",
                    "samples": []
                },
                {
                    "name": "AMAZON.NavigateHomeIntent",
                    "samples": []
                },
                {
                    "name": "LearnIntent",
                    "slots": [
                        {
                            "name": "DuploAnimal",
                            "type": "DuploAnimal",
                            "samples": [
                                "{DuploAnimal}"
                            ]
                        }
                    ],
                    "samples": [
                        "Learn about animals",
                        "Learn {DuploAnimal}",
                        "Learn about {DuploAnimal}",
                        "Learn"
                    ]
                },
                {
                    "name": "QuizIntent",
                    "slots": [],
                    "samples": [
                        "take quiz",
                        "take test",
                        "let's take a quiz",
                        "let's take a test",
                        "Test",
                        "Quiz"
                    ]
                },
                {
                    "name": "AnswerIntent",
                    "slots": [
                        {
                            "name": "DuploAnimal",
                            "type": "DuploAnimal",
                            "samples": [
                                "Answer is {DuploAnimal}",
                                "This is a {DuploAnimal}",
                                "The animal is {DuploAnimal}",
                                "{DuploAnimal}"
                            ]
                        }
                    ],
                    "samples": [
                        "The animal is a {DuploAnimal}",
                        "This is a {DuploAnimal}",
                        "Answer is {DuploAnimal}",
                        "It's a {DuploAnimal}",
                        "{DuploAnimal}"
                    ]
                }
            ],
            "types": [
                {
                    "name": "DuploAnimal",
                    "values": [
                        {
                            "id": "7",
                            "name": {
                                "value": "Cow",
                                "synonyms": [
                                    "Cows"
                                ]
                            }
                        },
                        {
                            "id": "6",
                            "name": {
                                "value": "Chicken",
                                "synonyms": [
                                    "Chickens"
                                ]
                            }
                        },
                        {
                            "id": "5",
                            "name": {
                                "value": "Dog",
                                "synonyms": [
                                    "Dogs"
                                ]
                            }
                        },
                        {
                            "id": "4",
                            "name": {
                                "value": "Cat",
                                "synonyms": [
                                    "Cats"
                                ]
                            }
                        },
                        {
                            "id": "3",
                            "name": {
                                "value": "Rabbit",
                                "synonyms": [
                                    "Rabbits"
                                ]
                            }
                        },
                        {
                            "id": "2",
                            "name": {
                                "value": "Lion",
                                "synonyms": [
                                    "Lions"
                                ]
                            }
                        },
                        {
                            "id": "1",
                            "name": {
                                "value": "Pig",
                                "synonyms": [
                                    "Pigs"
                                ]
                            }
                        }
                    ]
                }
            ]
        },
        "dialog": {
            "intents": [
                {
                    "name": "LearnIntent",
                    "confirmationRequired": false,
                    "prompts": {},
                    "slots": [
                        {
                            "name": "DuploAnimal",
                            "type": "DuploAnimal",
                            "confirmationRequired": false,
                            "elicitationRequired": true,
                            "prompts": {
                                "elicitation": "Elicit.Slot.559936688270.1216049997006"
                            },
                            "validations": [
                                {
                                    "type": "hasEntityResolutionMatch",
                                    "prompt": "Slot.Validation.559936688270.1216049997006.12505410713"
                                }
                            ]
                        }
                    ]
                },
                {
                    "name": "AnswerIntent",
                    "confirmationRequired": false,
                    "prompts": {},
                    "slots": [
                        {
                            "name": "DuploAnimal",
                            "type": "DuploAnimal",
                            "confirmationRequired": false,
                            "elicitationRequired": true,
                            "prompts": {
                                "elicitation": "Elicit.Slot.182725031909.777190447479"
                            },
                            "validations": [
                                {
                                    "type": "hasEntityResolutionMatch",
                                    "prompt": "Slot.Validation.182725031909.777190447479.238329023491"
                                }
                            ]
                        }
                    ]
                }
            ],
            "delegationStrategy": "ALWAYS"
        },
        "prompts": [
            {
                "id": "Elicit.Slot.559936688270.1216049997006",
                "variations": [
                    {
                        "type": "PlainText",
                        "value": "What animal would you like to learn about?"
                    }
                ]
            },
            {
                "id": "Slot.Validation.559936688270.1216049997006.12505410713",
                "variations": [
                    {
                        "type": "PlainText",
                        "value": "What animal would you like to learn about?"
                    }
                ]
            },
            {
                "id": "Elicit.Slot.182725031909.777190447479",
                "variations": [
                    {
                        "type": "PlainText",
                        "value": "What animal is this?"
                    }
                ]
            },
            {
                "id": "Slot.Validation.182725031909.777190447479.238329023491",
                "variations": [
                    {
                        "type": "PlainText",
                        "value": "What animal is this?"
                    }
                ]
            }
        ]
    }
}

index.js

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

// The namespace of the custom directive to be sent by this skill
const NAMESPACE = 'Custom.Mindstorms.Gadget';
const NAME_INIT = 'init';
const NAME_LEARN = 'learn';
const NAME_QUIZ = 'quiz';
const NAME_RESET = 'reset';

const DUPLO_ANIMALS = {
    1: { name: 'Pig', story: 'Story about pigs.' },
    2: { name: 'Lion', story: 'Story about lions.' },
    3: { name: 'Rabbit', story: 'Story about rabbits.' },
    4: { name: 'Cat', story: 'Story about cats.' },
    5: { name: 'Dog', story: 'Story about dogs.' },
    6: { name: 'Chicken', story: 'Story about chickens.' },
    7: { name: 'Cow', story: 'Story about cows.' },
}

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

        // Set skill duration to 2 minutes (4 30-seconds interval)
        Util.putSessionAttribute(handlerInput, 'duration', 4);
        // Set the token to track the event handler
        const token = handlerInput.requestEnvelope.request.requestId;
        Util.putSessionAttribute(handlerInput, 'token', token);

        let directive = Util.build(endpointId, NAMESPACE, NAME_INIT,
            {
            });

        return handlerInput.responseBuilder
            .addDirective(buildStartEventHandler(token, 30000, {}))
            .addDirective(directive)
            .getResponse();
    }
};

const LearnIntentHandler = {
    canHandle(handlerInput) {
        return Alexa.getRequestType(handlerInput.requestEnvelope) === 'IntentRequest'
            && Alexa.getIntentName(handlerInput.requestEnvelope) === 'LearnIntent';
    },
    handle(handlerInput) {
        const attributesManager = handlerInput.attributesManager;
        let endpointId = attributesManager.getSessionAttributes().endpointId || [];

        // Set skill duration to 2 minutes (4 30-seconds interval)
        Util.putSessionAttribute(handlerInput, 'duration', 4);
        // Set the token to track the event handler
        const token = handlerInput.requestEnvelope.request.requestId;
        Util.putSessionAttribute(handlerInput, 'token', token);

        let animalSlot = Alexa.getSlot(handlerInput.requestEnvelope, 'DuploAnimal');
        let animal = animalSlot.resolutions.resolutionsPerAuthority[0].values[0].value;
        Util.putSessionAttribute(handlerInput, 'animal_id', animal.id);

        console.log(animal);

        let directive = Util.build(endpointId, NAMESPACE, NAME_LEARN,
            {
                animal_id: animal.id,
                animal_name: animal.name
            });

        return handlerInput.responseBuilder
            .addDirective(directive)
            .getResponse();
    }
}

const QuizIntentHandler = {
    canHandle(handlerInput) {
        return Alexa.getRequestType(handlerInput.requestEnvelope) === 'IntentRequest'
            && Alexa.getIntentName(handlerInput.requestEnvelope) === 'QuizIntent';
    },
    handle(handlerInput) {
        const attributesManager = handlerInput.attributesManager;
        let endpointId = attributesManager.getSessionAttributes().endpointId || [];

        // Set skill duration to 2 minutes (4 30-seconds interval)
        Util.putSessionAttribute(handlerInput, 'duration', 4);
        // Set the token to track the event handler
        const token = handlerInput.requestEnvelope.request.requestId;
        Util.putSessionAttribute(handlerInput, 'token', token);

        let directive = Util.build(endpointId, NAMESPACE, NAME_QUIZ,
            {
            });

        return handlerInput.responseBuilder
            .speak(`I will choose an animal and you have to quess it's name.`)
            .addDirective(directive)
            .getResponse();
    }
}

const AnswerIntentHandler = {
    canHandle(handlerInput) {
        return Alexa.getRequestType(handlerInput.requestEnvelope) === 'IntentRequest'
            && Alexa.getIntentName(handlerInput.requestEnvelope) === 'AnswerIntent';
    },
    handle(handlerInput) {
        const attributesManager = handlerInput.attributesManager;

        let animalSlot = Alexa.getSlot(handlerInput.requestEnvelope, 'DuploAnimal');
        let animal = animalSlot.resolutions.resolutionsPerAuthority[0].values[0].value;

        console.log('Answered: ' + animal);

        let animal_id = attributesManager.getSessionAttributes().animal_id || [];

        if(animal.id === animal_id) {
            return handlerInput.responseBuilder
            .speak('Correct! Well done! What should we do next?')
            .reprompt('What should we do next?')
            .withShouldEndSession(false)
            .getResponse();
        } else {
            return handlerInput.responseBuilder
            .speak(`Wrong answer. This is a ${DUPLO_ANIMALS[animal_id].name}. What should we do next?`)
            .reprompt('What should we do next?')
            .withShouldEndSession(false)
            .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)}`);
    }
};

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

const HelpIntentHandler = {
    canHandle(handlerInput) {
        return Alexa.getRequestType(handlerInput.requestEnvelope) === 'IntentRequest'
            && Alexa.getIntentName(handlerInput.requestEnvelope) === 'AMAZON.HelpIntent';
    },
    handle(handlerInput) {
        const speakOutput = 'How can I help?';
        return handlerInput.responseBuilder
            .speak(speakOutput)
            .withShouldEndSession(false)
            .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 attributesManager = handlerInput.attributesManager;
        let endpointId = attributesManager.getSessionAttributes().endpointId || [];

        let directive = Util.build(endpointId, NAMESPACE, NAME_RESET,
            {
            });

        return handlerInput.responseBuilder
            .addDirective(buildStopEventHandlerDirective(handlerInput))
            .speak('Goodbye!')
            .addDirective(directive)
            .withShouldEndSession(true)
            .getResponse();
    }
};
const SessionEndedRequestHandler = {
    canHandle(handlerInput) {
        return Alexa.getRequestType(handlerInput.requestEnvelope) === 'SessionEndedRequest';
    },
    handle(handlerInput) {
        // Any cleanup logic goes here.
        return handlerInput.responseBuilder
            .addDirective(buildStopEventHandlerDirective(handlerInput))
            .getResponse();
    }
};

const EventsReceivedRequestHandler = {
    // Checks for a valid token and endpoint.
    canHandle(handlerInput) {
        let { request } = handlerInput.requestEnvelope;
        console.log('Request type: ' + Alexa.getRequestType(handlerInput.requestEnvelope));
        if (request.type !== 'CustomInterfaceController.EventsReceived') return false;

        const attributesManager = handlerInput.attributesManager;
        let sessionAttributes = attributesManager.getSessionAttributes();
        let customEvent = request.events[0];

        // Validate endpoint
        let requestEndpoint = customEvent.endpoint.endpointId;
        if (requestEndpoint !== sessionAttributes.endpointId) {
            console.log("Event endpoint id doesn't match. Ignoring this event");
            return false;
        }
        return true;
    },
    handle(handlerInput) {

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

        const token = handlerInput.requestEnvelope.request.requestId;
        Util.putSessionAttribute(handlerInput, 'token', token);

        console.log(customEvent)

        if (name === NAME_INIT) {
            return handlerInput.responseBuilder
                .speak("Duplo Zoo Evaluator is ready. You can learn about animals or take a quiz. What should we do?")
                .reprompt('What should we do next?')
                .withShouldEndSession(false)
                .getResponse();
        }

        if (name === NAME_LEARN) {
            if (payload.found === false) {
                return handlerInput.responseBuilder
                    .speak("Animal not found. Please try a different animal. What should we do next?")
                    .reprompt('What should we do next?')
                    .withShouldEndSession(false)
                    .getResponse();
            } else {
                const attributesManager = handlerInput.attributesManager;
                let animal_id = attributesManager.getSessionAttributes().animal_id || [];
                return handlerInput.responseBuilder
                    .speak(DUPLO_ANIMALS[animal_id].story + ' What should we do next?')
                    .reprompt('What should we do next?')
                    .withShouldEndSession(false)
                    .getResponse();
            }
        }

        if (name === NAME_QUIZ) {
            let animal_id = payload.animal_id;
            Util.putSessionAttribute(handlerInput, 'animal_id', animal_id);

            console.log('Chosen animal: ' + animal_id);
            
            return handlerInput.responseBuilder
               .speak('What animal is this?')
               .reprompt('What animal is this?')
               .withShouldEndSession(false)
               .getResponse();
        }


        return handlerInput.responseBuilder
            .speak('unknown event')
            .getResponse();
    }
};
const ExpiredRequestHandler = {
    canHandle(handlerInput) {
        return Alexa.getRequestType(handlerInput.requestEnvelope) === 'CustomInterfaceController.Expired'
    },
    handle(handlerInput) {
        console.log("== Custom Event Expiration Input ==");

        const attributesManager = handlerInput.attributesManager;

        let token = handlerInput.attributesManager.getSessionAttributes().token || '';

        let duration = attributesManager.getSessionAttributes().duration || 0;
        if (duration > 0) {
            Util.putSessionAttribute(handlerInput, 'duration', --duration);
            // Extends skill session
            return handlerInput.responseBuilder
                .addDirective(buildStartEventHandler(token, 30000, {}))
                .getResponse();
        }
        else {
            // End skill session
            return handlerInput.responseBuilder
                .withShouldEndSession(true)
                .getResponse();
        }
    }
};

// The SkillBuilder acts as the entry point for your skill, routing all request and response
// payloads to the handlers above. Make sure any new handlers or interceptors you've
// defined are included below. The order matters - they're processed top to bottom.
exports.handler = Alexa.SkillBuilders.custom()
    .addRequestHandlers(
        LaunchRequestHandler,
        LearnIntentHandler,
        QuizIntentHandler,
        AnswerIntentHandler,
        EventsReceivedRequestHandler,
        ExpiredRequestHandler,
        HelpIntentHandler,
        CancelAndStopIntentHandler,
        SessionEndedRequestHandler,
    )
    .addRequestInterceptors(RequestInterceptor)
    .addErrorHandlers(
        ErrorHandler,
    )
    .lambda();

function buildStartEventHandler(token, timeout = 30000, payload) {
    return {
        type: "CustomInterfaceController.StartEventHandler",
        token: token,
        expiration: {
            durationInMilliseconds: timeout,
            expirationPayload: payload
        }
    };
}

function buildStopEventHandlerDirective(handlerInput) {
    let token = handlerInput.attributesManager.getSessionAttributes().token || '';
    return {
        "type": "CustomInterfaceController.StopEventHandler",
        "token": token
    }
}

Credits

Dorin Suba

Dorin Suba

1 project • 1 follower
Romanian software system test engineer currently living in Germany.
Andrei Ciobanu

Andrei Ciobanu

12 projects • 16 followers

Comments