ECE 4180 Alexa Controlled Mini Basketball

Introduction

The Amazon Echo is a great tool for accessible IoT devices, as it can serve as a Voice User Interface(VUI) for a number of creative applications! In this project an Alexa skill was developed to communicate to a server. An internet-connected mbed also communicates with that server, and upon indication from the server, starts a game of tabletop basketball!

This project was made by Cameron Allotey and Taylor Dingler

Electronics & Technologies

mbed

The mbed is responsible for running the actual game upon receiving data from server. It also sends the final score to the server after the game. Using the RTOS library, the mbed uses I/O devices to create effects for the game. The roles of specific I/O devices are detailed below. The mbed code to control the I/O devices and communicate using Ethernet is featured in the Appendix. Also note that this project uses mbed OS 5, which has a slightly different implementation of RTOS and has several built-in network libraries.

Amazon Echo Dot

An Alexa skill was developed to start the game on the mbed and report the score to the user. The skill communicates with an AWS Lambda function to send HTTP requests to a backend server when the user requests to start a game. Follow the guide here to create a custom Alexa skill. https://developer.amazon.com/en-US/alexa/alexa-skills-kit/get-deeper/custom-skills. The Lambda function showing the necessary intents is featured in the Appendix.

Backend Server

A locally hosted NodeJs server is made publicly reachable using ngrok, a service that enables a server running locally to be accessed outside of the local network.

ngrok wrote:

How it works You download and run a program on your machine and provide it the port of a network service, usually a web server.

It connects to the ngrok cloud service which accepts traffic on a public address and relays that traffic through to the ngrok process running on your machine and then on to the local address you specified. Postman was used to develop the endpoints prior to implementation.

Read more at https://ngrok.com/

The server uses simple GET and POST requests to manage the status of the mbed system using only Alexa voice commands.

HC-SR04 sonar distance sensor

The distance sensor was used to track the player's scoring, using a change in detected distance as an indicator of a made shot.

Ethernet

Ethernet was used to create a network bridge allowing for the mbed to access the internet. This is imperative in the process of communicating with the backend server.

Other I/O Devices

The project also utlized an RGB LED, speaker, amplifier, and LCD screen to enhance the presentation and game experience

Connections

The wiring for the key project components is shown below

mbedRJ45 Ethernet PortuLCDHC-SR04 Sonar SensorExternal 5V
p9trig
p10echo
RU-p8
RU+p7
TU-p2
TU+p1
p30RES
p28TX
p27RX
+5VVCCx

Images & Videos

Project Progress in Images:

Demo Video:

Presentation Video:

Possible Improvements

  • Inclusion of multiple game modes
  • Make high score carry over between sessions
  • Use Alexa speaker to play music during game, this would also allow for a single session to be used for an unlimited number of games. This addition requires significant knowledge of Alexa Skills development.
  • Improve quality of physical device and order supplemental parts for a more professional final product
  • Wifi (either Huzzah or Pi) rather than Ethernet

Appendix

mbed Code

Import program4180miniBball

ece 4180 final project

Server Code

index.js

const WebSocket = require('ws');
var express = require('express');

var app = express();
var port = process.env.PORT || 8000
app.use(express.static('public'));
var bodyParser = require('body-parser')
//app.use(bodyParser.urlencoded({ extended: true }));

app.listen(port);

var game = 0;
app.route('/')
  .get(function (req, res) {
    res.send(String(game));
    game = 0;
  })
  .post(function (req, res) {
    game = 1;
    res.end();
  })
var score = 0;
app.route('/game')
  .get(function (req, res) {
    res.send(String(score));
  })
  .post(function (req, res) {
    score = req.param('score')
    console.log(score)
    res.end();
  })

Alexa Skill Lambda Function Code

lambda_function.py

# -*- coding: utf-8 -*-

# This sample demonstrates handling intents from an Alexa skill using the Alexa Skills Kit SDK for Python.
# Please visit https://alexa.design/cookbook for additional examples on implementing slots, dialog management,
# session persistence, api calls, and more.
# This sample is built using the handler classes approach in skill builder.
import logging
import ask_sdk_core.utils as ask_utils
# importing the requests library 
import requests


from ask_sdk_core.skill_builder import SkillBuilder
from ask_sdk_core.dispatch_components import AbstractRequestHandler
from ask_sdk_core.dispatch_components import AbstractExceptionHandler
from ask_sdk_core.handler_input import HandlerInput

from ask_sdk_model import Response

logger = logging.getLogger(__name__)
logger.setLevel(logging.INFO)
# api-endpoint 
URL = "http://bf360b94.ngrok.io/"

class LaunchRequestHandler(AbstractRequestHandler):
    """Handler for Skill Launch."""
    def can_handle(self, handler_input):
        # type: (HandlerInput) -> bool

        return ask_utils.is_request_type("LaunchRequest")(handler_input)

    def handle(self, handler_input):
        # type: (HandlerInput) -> Response
        speak_output = "Welcome to E C E 41 80 Tabletop Basketball. To begin, say start game. To get your previous score, say score."
        return (
            handler_input.response_builder
                .speak(speak_output)
                .ask(speak_output)
                .response
        )

class StartGameIntentHandler(AbstractRequestHandler):
    """Handler for Start Game Intent."""
    def can_handle(self, handler_input):
        # type: (HandlerInput) -> bool
        return ask_utils.is_intent_name("StartGameIntent")(handler_input)

    def handle(self, handler_input):
        # type: (HandlerInput) -> Response
        speak_output = "When the lights turn on, score as many points as possible!"
          
        # sending get request and saving the response as response object 
        r = requests.post(url = URL) 
          
        return (
            handler_input.response_builder
                .speak(speak_output)
                # .ask("either start another game or say exit to quit")
                .response
        )


class GetScoreIntentHandler(AbstractRequestHandler):
    """Handler for Start Game Intent."""
    def can_handle(self, handler_input):
        # type: (HandlerInput) -> bool
        return ask_utils.is_intent_name("GetScoreIntent")(handler_input)

    def handle(self, handler_input):
        # sending get request and saving the response as response object 
        r = requests.get(url = URL + 'game/') 
        x = int(r.content)
        txt = "You Scored {} Points Last Round!"
        speak_output = txt.format(x)
        return (
            handler_input.response_builder
                .speak(speak_output)
                # .ask("either start another game or say exit to quit")
                .response
        )


class HelpIntentHandler(AbstractRequestHandler):
    """Handler for Help Intent."""
    def can_handle(self, handler_input):
        # type: (HandlerInput) -> bool
        return ask_utils.is_intent_name("AMAZON.HelpIntent")(handler_input)

    def handle(self, handler_input):
        # type: (HandlerInput) -> Response
        speak_output = "You can say play game to start a game."

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


class CancelOrStopIntentHandler(AbstractRequestHandler):
    """Single handler for Cancel and Stop Intent."""
    def can_handle(self, handler_input):
        # type: (HandlerInput) -> bool
        return (ask_utils.is_intent_name("AMAZON.CancelIntent")(handler_input) or
                ask_utils.is_intent_name("AMAZON.StopIntent")(handler_input))

    def handle(self, handler_input):
        # type: (HandlerInput) -> Response
        speak_output = "Goodbye!"

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


class SessionEndedRequestHandler(AbstractRequestHandler):
    """Handler for Session End."""
    def can_handle(self, handler_input):
        # type: (HandlerInput) -> bool
        return ask_utils.is_request_type("SessionEndedRequest")(handler_input)

    def handle(self, handler_input):
        # type: (HandlerInput) -> Response
        speech_output = "Thanks for playing!"
        # Any cleanup logic goes here.

        return handler_input.response_builder.response


class IntentReflectorHandler(AbstractRequestHandler):
    """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.
    """
    def can_handle(self, handler_input):
        # type: (HandlerInput) -> bool
        return ask_utils.is_request_type("IntentRequest")(handler_input)

    def handle(self, handler_input):
        # type: (HandlerInput) -> Response
        intent_name = ask_utils.get_intent_name(handler_input)
        speak_output = "You just triggered " + intent_name + "."

        return (
            handler_input.response_builder
                .speak(speak_output)
                # .ask("add a reprompt if you want to keep the session open for the user to respond")
                .response
        )


class CatchAllExceptionHandler(AbstractExceptionHandler):
    """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.
    """
    def can_handle(self, handler_input, exception):
        # type: (HandlerInput, Exception) -> bool
        return True

    def handle(self, handler_input, exception):
        # type: (HandlerInput, Exception) -> Response
        logger.error(exception, exc_info=True)

        speak_output = "Sorry, I had trouble doing what you asked. Please try again."

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

# The SkillBuilder object 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.


sb = SkillBuilder()

sb.add_request_handler(LaunchRequestHandler())
sb.add_request_handler(StartGameIntentHandler())
sb.add_request_handler(GetScoreIntentHandler())
sb.add_request_handler(HelpIntentHandler())
sb.add_request_handler(CancelOrStopIntentHandler())
sb.add_request_handler(SessionEndedRequestHandler())
sb.add_request_handler(IntentReflectorHandler()) # make sure IntentReflectorHandler is last so it doesn't override your custom intent handlers

sb.add_exception_handler(CatchAllExceptionHandler())

lambda_handler = sb.lambda_handler()


Please log in to post comments.