Wilson L
Created December 25, 2019 © GPL3+

Math Racer

Correctly answer Alexa's math quiz to win the race! But careful, you'll need to feed answers quickly to keep the time from expiring!

IntermediateFull instructions provided118

Things used in this project

Hardware components

Mindstorms EV3 Programming Brick / Kit
LEGO Mindstorms EV3 Programming Brick / Kit
×1
LEGO EV3 Medium Servo Motor
×1
LEGO various other technic components
×1
Flexi-Cables for EV3
×1

Software apps and online services

Alexa Skills Kit
Amazon Alexa Alexa Skills Kit
Microsoft Visual Studio Code

Story

Read more

Schematics

Building instructions and parts list

PDF building instructions and parts list.
*cables are not shown as Studio 2.0 doesn't currently support them and I could not get LDCAD or LDD to work on my computer to add them

Code

Lambda forMathRacer

Python
Lambda backend for the MathRacer skill
# MathRace for Lego MINDSTORMS Voice Challenge: Powered by Alexa
# Tweaked by Wilson L
# Python template ported to Python by Franklin Lobb
# Original version supplied by Amazon

import logging.handlers
import requests
import uuid
from random import choice, randint
import os
import boto3
import math

from ask_sdk_core.skill_builder import SkillBuilder
from ask_sdk_core.utils import is_request_type, is_intent_name, get_slot_value, get_slot
from ask_sdk_core.handler_input import HandlerInput
from ask_sdk_core.serialize import DefaultSerializer

from ask_sdk_model import IntentRequest
from ask_sdk_model.ui import PlayBehavior
from ask_sdk_model import Response
from ask_sdk_s3.adapter import S3Adapter

from ask_sdk_model.interfaces.custom_interface_controller import (
    StartEventHandlerDirective, EventFilter, Expiration, FilterMatchAction,
    StopEventHandlerDirective,
    SendDirectiveDirective,
    Header,
    Endpoint,
    EventsReceivedRequest,
    ExpiredRequest
)


logger = logging.getLogger()
logger.setLevel(logging.INFO)
serializer = DefaultSerializer()
skill_builder = SkillBuilder()


# The namespace of the custom directive to be sent by this skill
NAMESPACE = "Custom.Mindstorms.Gadget"

# The name of the custom directive to be sent this skill
NAME_CONTROL = "control"

# The audio tag to include background music
BG_MUSIC = '<audio src="soundbank://soundlibrary/ui/gameshow/amzn_ui_sfx_gameshow_waiting_loop_30s_01"></audio>'

INCORRECT_BUZZ = '<audio src="soundbank://soundlibrary/gameshow/gameshow_04"></audio>'
CORRECT_BUZZ = '<audio src="soundbank://soundlibrary/ui/gameshow/amzn_ui_sfx_gameshow_positive_response_01"></audio>'

@skill_builder.request_handler(can_handle_func=is_request_type("LaunchRequest"))
def launch_request_handler(handler_input: HandlerInput):
    logger.info("== Launch Intent ==")

    response_builder = handler_input.response_builder

    system = handler_input.request_envelope.context.system
    api_access_token = system.api_access_token
    api_endpoint = system.api_endpoint

    # Get connected gadget endpoint ID.
    endpoints = get_connected_endpoints(api_endpoint, api_access_token)
    logger.debug("Checking endpoint..")
    if not endpoints:
        logger.debug("No connected gadget endpoints available.")
        return (response_builder
                .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.")
                .set_should_end_session(True)
                .response)

    endpoint_id = endpoints[0].get("endpointId", "")

    # Store endpoint ID for using it to send custom directives later.
    logger.debug("Received endpoints. Storing Endpoint Id: %s", endpoint_id)
    session_attr = handler_input.attributes_manager.session_attributes
    session_attr["endpoint_id"] = endpoint_id

    # Set skill duration to 5 minutes (ten 30-seconds interval)
    session_attr["duration"] = 10

    # Set the token to track the event handler
    token = handler_input.request_envelope.request.request_id
    session_attr["token"] = token

    speak_output = "Welcome, to math racer. Player, are you ready to start?"
    reprompt_output = "Are you ready to start?"

    return (response_builder
            .speak(speak_output)
            .ask(reprompt_output)
            .add_directive(build_start_event_handler_directive(token, 60000, NAMESPACE, NAME_CONTROL, 'SEND', {}))
            .response)


@skill_builder.request_handler(can_handle_func=is_intent_name("ReadyToStartIntent"))
def ready_to_start_intent_handler(handler_input: HandlerInput):
    # Confirms start of race. This sends a directive to EV3 to start race mode.
    # Alexa will also start asking the quizzing portion of the game.
    logger.info("ReadyToStartIntent received.")
    
    
    # Get data from session attribute
    session_attr = handler_input.attributes_manager.session_attributes
    endpoint_id = session_attr.get("endpoint_id", [])

    # Set global total of correct answers in current round
    session_attr['total_correct'] = 0

    # Construct the directive telling EV3 to do warm up lap and arrive at starting grid
    payload = {
        "type": "StartRace"
    }
    directive = build_send_directive(NAMESPACE, NAME_CONTROL, endpoint_id, payload)
    reprompt_output = ""
    
    answer_generate = rand_math(0)
    num_a = answer_generate[0]
    num_b = answer_generate[1]
    operator = answer_generate[2]
    answer = answer_generate[3]
    session_attr['answer_value'] = answer
    reprompt_output = "What is {} {} {}? ".format(num_a,operator,num_b)
    speak_output = "Racers, start! "
    
    
    return (handler_input.response_builder
            .speak(speak_output+reprompt_output)
            .ask(reprompt_output)
            .add_directive(directive)
            .response)


@skill_builder.request_handler(can_handle_func=is_intent_name("PassIntent"))
def pass_intent_handler(handler_input: HandlerInput):
    # generates new question if player passes on a previously asked question
    logger.info("PassIntent received.")

    # Get data from session attribute
    session_attr = handler_input.attributes_manager.session_attributes
    
    #endpoint_id = session_attr.get("endpoint_id", [])

    #directive = build_send_directive(NAMESPACE, NAME_CONTROL, endpoint_id, payload)
    reprompt_output = ""
    
    answer_generate = rand_math(session_attr.get('total_correct'))
    num_a = answer_generate[0]
    num_b = answer_generate[1]
    operator = answer_generate[2]
    
    session_attr['answer_value'] = answer_generate[3]
    speak_output = reprompt_output = "What is {} {} {}? ".format(num_a,operator,num_b)

    return (handler_input.response_builder
            .speak(speak_output)
            .ask(reprompt_output)
            .response)


@skill_builder.request_handler(can_handle_func=is_intent_name("AnswerIntent"))
def answer_intent_handler(handler_input: HandlerInput):
    

    session_attr = handler_input.attributes_manager.session_attributes
    endpoint_id = session_attr.get("endpoint_id", [])
    
    actual_answer = session_attr.get('answer_value')
    answer_given = int(handler_input.request_envelope.request.intent.slots[
        "number"].value)
    
    refill_text = ""
    buzz_sound = ""
    extend=False
    if answer_given == actual_answer:
        buzz_sound = CORRECT_BUZZ
        result='correct'
        session_attr['total_correct']=session_attr.get('total_correct')+1
        tot_correct = session_attr.get('total_correct')
        if tot_correct>0 and tot_correct%4==0:
            #if total correct in a game is a multiple of 4, time is extended
            refill_text = "Time extended. "
            extend=True
        else:
            extend=False
            
    elif answer_given != actual_answer:
        buzz_sound = INCORRECT_BUZZ
        result='incorrect'
    
    answer_generate = rand_math(session_attr.get('total_correct'))
    num_a = answer_generate[0]
    num_b = answer_generate[1]
    operator = answer_generate[2]
    
    session_attr['answer_value'] = answer_generate[3]
    reprompt_output = " What is {} {} {}? ".format(num_a,operator,num_b)

    # Construct the directive to modulate speed based on correct/incorrect answer
    payload = {
        "type": "mathResult",
        "result":result,
        "extend":extend
    }
    directive = build_send_directive(NAMESPACE, NAME_CONTROL, endpoint_id, payload)

    
    return (handler_input.response_builder
            .speak(buzz_sound + refill_text + reprompt_output)
            .ask(reprompt_output)
            .add_directive(directive)
            .response)




@skill_builder.request_handler(can_handle_func=lambda handler_input:
    is_request_type("CustomInterfaceController.EventsReceived") and
    has_valid_token(handler_input) and 
    has_valid_endpoint(handler_input)
)
def events_received_request_handler(handler_input: HandlerInput):
    logger.info("== Received Custom Event ==")

    custom_event = handler_input.request_envelope.request.events[0]
    payload = custom_event.payload
    name = custom_event.header.name
    
    session_attr = handler_input.attributes_manager.session_attributes

    speak_output = ""
    reprompt_output = ""
    if name == 'Speech':
        speak_output = payload.get("speechOut", "")
    elif name == 'GameOver':
        tot_correct = session_attr['total_correct']
        speak_output = "Time expired. You answered {} questions correct. ".format(tot_correct)
    elif name == 'GameWon':
        tot_correct = session_attr['total_correct']
        speak_output = "Congratulations! You win! You answered {} questions correct. Until next time!".format(tot_correct)
        return (handler_input.response_builder
                .speak(speak_output)
                .set_should_end_session(True)
                .response)
    elif name == 'ReadyAtStart':
        speak_output = "Player has arrived at the start line."
        reprompt_output = "Are you ready to go?"
    else:
        speak_output = "Event not recognized. Awaiting new command."

    return (handler_input.response_builder
        .speak(speak_output)
        .ask(reprompt_output)
        .set_should_end_session(False)
        .response)

def has_valid_token(handler_input):
    if not is_request_type('CustomInterfaceController.EventsReceived')(handler_input):
        return False

    session_attr = handler_input.attributes_manager.session_attributes
    request = handler_input.request_envelope.request

    # Validate event token
    if session_attr.get("token", None) != request.token:
        logger.info("Event token doesn't match. Ignoring this event")
        return False

    return True

def has_valid_endpoint(handler_input):
    if not is_request_type('CustomInterfaceController.EventsReceived')(handler_input):
        return False

    session_attr = handler_input.attributes_manager.session_attributes
    request = handler_input.request_envelope.request
    custom_event = request.events[0]

    # Validate endpoint
    request_endpoint = custom_event.endpoint.endpoint_id
    if request_endpoint != session_attr.get("endpoint_id", None):
        logger.info("Event endpoint id doesn't match. Ignoring this event")
        return False

    return True

@skill_builder.request_handler(can_handle_func=is_request_type("CustomInterfaceController.Expired"))
def custom_interface_expiration_handler(handler_input):
    logger.info("== Custom Event Expiration Input ==")

    session_attr = handler_input.attributes_manager.session_attributes
    
    # Set the token to track the event handler
    token = handler_input.request_envelope.request.request_id
    session_attr["token"] = token

    duration = session_attr.get("duration", 0)
    if duration > 0:
        session_attr["duration"] = duration - 1 
        # extends skill session
        #speak_output = "{} minutes remaining.".format(duration)
        timeout = 60000
        directive = build_start_event_handler_directive(token, timeout, NAMESPACE, NAME_CONTROL, 'SEND', {})
        return (handler_input.response_builder
            .add_directive(directive)
            #.speak(speak_output + BG_MUSIC)
            .response
        )
    else:
        # End skill session
        return (handler_input.response_builder
            .speak("Skill duration expired. Goodbye.")
            .set_should_end_session(True)
            .response)

@skill_builder.request_handler(can_handle_func=lambda handler_input:
                               is_intent_name("AMAZON.CancelIntent")(handler_input) or
                               is_intent_name("AMAZON.StopIntent")(handler_input))
def stop_and_cancel_intent_handler(handler_input):
    logger.info("Received a Stop or a Cancel Intent..")
    session_attr = handler_input.attributes_manager.session_attributes
    response_builder = handler_input.response_builder

    # When the user stops the skill, stop the EventHandler
    if 'token' in session_attr.keys():
        logger.debug("Active session detected, sending stop EventHandlerDirective.")
        directive = build_stop_event_handler_directive(session_attr["token"])
        response_builder.add_directive(directive)

    return (response_builder
            .speak("Goodbye!")
            .set_should_end_session(True)
            .response)

@skill_builder.request_handler(can_handle_func=is_request_type("SessionEndedRequest"))
def session_ended_request_handler(handler_input):
    logger.info("Session ended with reason: " +
                handler_input.request_envelope.request.reason.to_str())
    return handler_input.response_builder.response

@skill_builder.exception_handler(can_handle_func=lambda i, e: True)
def error_handler(handler_input, exception):
    logger.info("==Error==")
    logger.error(exception, exc_info=True)
    print(exception)
    return (handler_input.response_builder
            .speak("I'm sorry, something went wrong!").response)

@skill_builder.global_request_interceptor()
def log_request(handler_input):
    # Log the request for debugging purposes.
    logger.info("==Request==\r" +
                str(serializer.serialize(handler_input.request_envelope)))

@skill_builder.global_response_interceptor()
def log_response(handler_input, response):
    # Log the response for debugging purposes.
    logger.info("==Response==\r" + str(serializer.serialize(response)))
    logger.info("==Session Attributes==\r" +
                str(serializer.serialize(handler_input.attributes_manager.session_attributes)))


def rand_math(tot_correct):
    operations = ("+","-","x") 
    op = choice(operations)
    
    #every time a checkpoint is achieved, the difficulty increases by randomizing to larger numbers
    max_rand_val=5+(math.floor(tot_correct/4)*5)
    max_rand_val=min(max_rand_val,40)
    
    num_1 = randint(0,max_rand_val)
    num_2 = randint(0,max_rand_val)
    #multiplication difficulty however is limited to a max random value of 12
    if op == "x":
        num_1=num_1%13
        num_2=num_2%13

    holder=0
    if num_1<num_2:
        holder = num_1
        num_1=num_2
        num_2=holder

    if op == "+":
        answer = num_1+num_2
        op_word = "plus"
    elif op == "-":
        answer=num_1-num_2
        op_word = "minus"
    elif op == "x":
        answer = num_1*num_2
        op_word = "times"
    return (num_1,num_2,op_word,answer)



def get_connected_endpoints(api_endpoint, api_access_token):
    headers = {
        'Content-Type': 'application/json',
        'Authorization': 'Bearer {}'.format(api_access_token)
    }

    api_url = api_endpoint + "/v1/endpoints"
    endpoints_response = requests.get(api_url, headers=headers)

    if endpoints_response.status_code == requests.codes.get("ok", ""):
        return endpoints_response.json()["endpoints"]

def build_send_directive(namespace, name, endpoint_id, payload):
    return SendDirectiveDirective(
        header=Header(
            name=name,
            namespace=namespace
        ),
        endpoint=Endpoint(
            endpoint_id=endpoint_id
        ),
        payload=payload
    )



def build_start_event_handler_directive(token, duration_ms, namespace,
                                        name, filter_match_action, expiration_payload):
    return StartEventHandlerDirective(
        token=token,
        # event_filter=EventFilter(
        #     filter_expression={
        #         'and': [
        #             {'==': [{'var': 'header.namespace'}, namespace]},
        #             {'==': [{'var': 'header.name'}, name]}
        #         ]
        #     },
        #     filter_match_action=filter_match_action
        # ),
        expiration=Expiration(
            duration_in_milliseconds=duration_ms,
            expiration_payload=expiration_payload))

def build_stop_event_handler_directive(token):
    return StopEventHandlerDirective(token=token)

lambda_handler = skill_builder.lambda_handler()

EV3 code

Python
ev3dev2 code running on the EV3 brick for MathRacer
# 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.

import time
import logging
import json
import random
import threading
from enum import Enum
from time import sleep

from agt import AlexaGadget

from ev3dev2.led import Leds
from ev3dev2.sound import Sound
from ev3dev2.button import Button
from ev3dev2.motor import OUTPUT_A, OUTPUT_B, OUTPUT_C, OUTPUT_D, MediumMotor, LargeMotor
from ev3dev2.sensor.lego import InfraredSensor, TouchSensor
from ev3dev2.sensor import INPUT_3,INPUT_4


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

class EventName(Enum):
    """
    The list of custom event name sent from this gadget
    """

    READYATSTART = "ReadyAtStart"
    GAMEOVER = "GameOver"
    GAMEWON = "GameWon"

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.cruise_mode = False
        self.race_mode = False
        self.warm_up = False
        self.left_hug = False
        self.cp_refill = False

        # Connect two large motors on output ports B and C
        self.motors = [LargeMotor(address) for address in (OUTPUT_B, OUTPUT_C)]
        self.wall_motor = MediumMotor(OUTPUT_A)
        self.checkpoint_motor = MediumMotor(OUTPUT_D)
        self.sound = Sound()
        self.leds = Leds()
        self.touch = TouchSensor(INPUT_3)
        self.btn = Button()
        self.ir = InfraredSensor(INPUT_4)
        self.ir.mode = 'IR-PROX'


        #--- Input ---
        self.race_power = 55
        self.add_power = 0
        self.target = 8
        self.kp = float(26)
        self.kd = float(5)
        self.minRef=0
        self.maxRef=self.target*2
        #----


        # Start threads
        threading.Thread(target=self._checkpoint_thread,daemon=True).start()
        threading.Thread(target=self._racer_thread, daemon=True).start()
        threading.Thread(target=self._seek_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")
        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 =="StartRace":
                print ('start received')
                self.all_stop=False
                self.race_mode=True
                self.warm_up = True
            if control_type =="mathResult":
                print("result: {}".format(payload["result"]))
                self.power_modulate(payload["result"])
                self.cp_refill=(payload["extend"])

        except KeyError:
            print("Missing expected parameters: {}".format(directive))
    
    def steer(self,course,power):
        course = max(min(course,60),-60)
        if course>=0:
            if course>100:
                power_right=0
                power_left=power
            else:
                power_left=power
                power_right=power-((course*power)/100)
        else:
            if  course<-100:
                power_left=0
                power_right=power
            else:
                power_right=power
                power_left=power+((course*power)/100)
        return (int(power_left),int(power_right))

    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 power_modulate(self,result):
        if result=="correct":
            self.add_power+=40
            sleep(0.5)
            while self.add_power>0:
                self.add_power-=2
                sleep(0.2)
            self.add_power=0
        elif result=="incorrect":
            self.add_power-=15
            sleep(0.5)
            while self.add_power<0:
                self.add_power+=2.5
                sleep(0.2)

    def _checkpoint_thread(self):
        """
        Checkpoint motor counting down
        """
        while True:
            while self.race_mode:
                while self.checkpoint_motor.position>-1080:
                    self.checkpoint_motor.on(speed=-2)
                    if self.cp_refill and self.checkpoint_motor.position<0:
                        max_degrees = max(self.checkpoint_motor.degrees,-200)
                        self.checkpoint_motor.on_for_degrees(speed=-40,degrees=max_degrees,brake=True,block=True)
                        self.cp_refill=False
                    if self.checkpoint_motor.position<-900:
                        #past 270 degrees, emit audio warning for reaching time limit
                        self.sound.beep()
                        sleep(1)

                self._send_event(EventName.GAMEOVER, {'gameover':True})
                self.race_mode=False
                for m in self.motors:
                    m.stop()
                self.checkpoint_motor.on_to_position(speed=-30,position=0,brake=True,block=True)
                self.checkpoint_motor.off()


    def _seek_thread(self):
        while True:
            if self.touch.is_pressed:
                self._send_event(EventName.GAMEWON, {'gamewon':True})
                self.race_mode = False
                self.left_hug = False
                for m in self.motors:
                    m.stop()
                self.all_stop = True


    def _racer_thread(self):
        while True:
            while self.race_mode:
                lastError=error=0
                error = self.target - max(min(self.ir.proximity,self.maxRef),self.minRef)
                derivative = error - lastError
                lastError = error                
                course = 0
                course = self.kp*error+self.kd*derivative
                self.course = course
                total_power = self.race_power+self.add_power

                warm_up_power = -self.race_power
                
                if self.warm_up:
                    while warm_up_power<0:
                        lastError=error=0
                        error = self.target - max(min(self.ir.proximity,self.maxRef),self.minRef)
                        derivative = error - lastError
                        lastError = error                
                        course = 0
                        course = self.kp*error+self.kd*derivative
                        warm_up_total_power = self.race_power+warm_up_power
                        for m,p in zip(self.motors,self.steer(course,warm_up_total_power)):
                            m.run_direct(duty_cycle_sp=p)
                        warm_up_power+=1.5
                        sleep(0.01)
                    
                    self.warm_up = False
                    self.left_hug = True

                elif self.left_hug:
                    for m,p in zip(self.motors,self.steer(course,total_power)):
                        m.run_direct(duty_cycle_sp=p)

            for m in self.motors:
                m.stop()

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

JSON model

JSON
Alexa Skills JSON model
{
    "interactionModel": {
        "languageModel": {
            "invocationName": "mind storm racer",
            "intents": [
                {
                    "name": "AMAZON.CancelIntent",
                    "samples": []
                },
                {
                    "name": "AMAZON.HelpIntent",
                    "samples": []
                },
                {
                    "name": "AMAZON.StopIntent",
                    "samples": []
                },
                {
                    "name": "HelloWorldIntent",
                    "slots": [],
                    "samples": [
                        "hello",
                        "how are you",
                        "say hi world",
                        "say hi",
                        "hi",
                        "say hello world",
                        "say hello"
                    ]
                },
                {
                    "name": "AMAZON.NavigateHomeIntent",
                    "samples": []
                },
                {
                    "name": "ReadyToStartIntent",
                    "slots": [],
                    "samples": [
                        "ready",
                        "ready to start",
                        "ready to go",
                        "we're ready"
                    ]
                },
                {
                    "name": "AnswerIntent",
                    "slots": [
                        {
                            "name": "number",
                            "type": "AMAZON.NUMBER"
                        }
                    ],
                    "samples": [
                        "is it {number}",
                        "the answer is {number}",
                        "{number}"
                    ]
                },
                {
                    "name": "AMAZON.YesIntent",
                    "samples": []
                },
                {
                    "name": "AMAZON.NoIntent",
                    "samples": []
                },
                {
                    "name": "PassIntent",
                    "slots": [],
                    "samples": [
                        "pass"
                    ]
                }
            ],
            "types": []
        }
    }
}

EV3 ini file

INI
ini file that needs to go with the math racer source code
[GadgetSettings]
amazonId = XXXXX
alexaGadgetSecret = XXXXX

[GadgetCapabilities]
Custom.Mindstorms.Gadget = 1.0

Credits

Wilson L

Wilson L

1 project • 1 follower

Comments