Things used in this project

Hardware components:
Amazon tap
Amazon Alexa Amazon Tap
×1
Pi 3 02
Raspberry Pi 3 Model B
×1
Relay (generic)
×2
Huawei 3G Dongle
×1
11026 02
Jumper wires (generic)
×1
09590 01
LED (generic)
×1
Software apps and online services:
Dp image kit 02
Amazon Alexa Alexa Skills Kit
Screen%20shot%202015 07 20%20at%206.10.26%20pm
Amazon Web Services AWS Lambda
Ha 2up iot
Amazon Web Services AWS IoT
Raspberry Pi Raspbian Jessie

Schematics

VUI Diagram
VUI diagram for the project
Alexaboat%20vui%20diagram
Connections
Relays I used had 1 more pin on them. So I'm not sure if I drew this right. But it shows the basic idea.
Connections.fzz

Code

Intent Schema.jsonJSON
{
   "intents":[
      {
         "intent":"Open",
         "slots":[
            {
               "name":"Task",
               "type":"Input"
            }
         ]
      },
      {
         "intent":"Close",
         "slots":[
            {
               "name":"Task",
               "type":"Input"
            }
         ]
      },
      {
         "intent":"Anchor",
         "slots":[
            {
               "name":"Direction",
               "type":"LITERAL"
            },
            {
               "name":"Distance",
               "type":"NUMBER"
            }
         ]
      }
   ]
}
Sample Utterances.jsonJSON
Open {Task}
Close {Task}
Anchor {up | Direction} {twenty five | Distance} meters
Anchor {down | Direction} {one | Distance} meters
package.jsonJSON
for use with Lambda
{
  "name": "Bearcat",
  "version": "0.0.1",
  "description": "MQTT from lambda",
  "main": "index.js",
  "dependencies": {
    "aws-iot-device-sdk": "*"
  }
}
index.jsJavaScript
for use with Lambda
/**
 * Control your boat with your voice, using Amazon Alexa, Lambda, IOT, MQTT.
 */

var awsIot = require('aws-iot-device-sdk');
var config = require("./config");

var deviceName = "AlexaBoat";

var mqtt_config = {
    "keyPath": "./certs/YourKey-private.pem.key",
    "certPath": "./certs/YourKey-certificate.pem.crt",
    "caPath": "./certs/rootCA.key",
    "host": config.host,
    "port": 8883,
    "clientId": "RaspberryPi-" + deviceName, 
    "region":"us-west-1",
    "debug":true
};

var ctx = null;
var client = null;

// Route the incoming request based on type (LaunchRequest, IntentRequest, etc.) The JSON body of the request is provided in the event parameter.
exports.handler = function (event, context) {
    try {
        ctx = context;

        if (event.session.application.applicationId !== config.app_id) {
             ctx.fail("Invalid Application ID");
         }

        client = awsIot.device(mqtt_config);

        client.on("connect",function(){
            console.log("Connected to AWS IoT");
        });


        if (event.session.new) {
            onSessionStarted({requestId: event.request.requestId}, event.session);
        }

        if (event.request.type === "LaunchRequest") {
            onLaunch(event.request, event.session);
        }  else if (event.request.type === "IntentRequest") {
            onIntent(event.request, event.session);
        } else if (event.request.type === "SessionEndedRequest") {
            onSessionEnded(event.request, event.session);
            ctx.succeed();
        }
    } catch (e) {
        console.log("EXCEPTION in handler:  " + e);
        ctx.fail("Exception: " + e);
    }
};

/**
 * Called when the session starts.
 */
function onSessionStarted(sessionStartedRequest, session) {
}


/**
 * Called when the user launches the skill without specifying what they want.
 */
function onLaunch(launchRequest, session, callback) {

    // Dispatch to your skill's launch.
    getWelcomeResponse(callback);
}

/**
 * Called when the user specifies an intent for this skill.
 */
function onIntent(intentRequest, session ) {
    try{
    var intent = intentRequest.intent,
    intentName = intentRequest.intent.name;

    console.log("REQUEST to string =" + JSON.stringify(intentRequest));

    console.log("Ufuk =" + intentName);

    var callback = null;
    // Dispatch to your skill's intent handlers
    if ("Open" === intentName) {
        doCommandIntent(intent, session);
    }
    else if ("Close" === intentName) {
        doCommandIntent(intent, session);
    }
    else if ("Anchor" === intentName) {
        anchorCommandIntent(intent, session);
    }
    else if("AMAZON.HelpIntent"=== intentName) {
        helpCommandIntent(session);
    }
    else if("AMAZON.StopIntent"=== intentName) {
        ctx.succeed(buildResponse(null, buildSpeechletResponse(null, "Goodbye", "", true)));
    }
    else if("AMAZON.CancelIntent"=== intentName) {
        ctx.succeed(buildResponse(null, buildSpeechletResponse(null, "Goodbye", "", true)));
    }
    else {
        ctx.succeed(buildResponse(null, buildSpeechletResponse(null, "I can't help you with that. Invalid Request", "", true)));
    }
    }
    catch(ex)
    {
        ctx.succeed(buildResponse(null, buildSpeechletResponse(null, "There is an error with the request. Please try again.", "", false)));
    }
}

/**
 * Called when the user ends the session.
 * Is not called when the skill returns shouldEndSession=true.
 */
function onSessionEnded(sessionEndedRequest, session) {
    // Add cleanup logic here
}

// --------------- Functions that control the skill's behavior -----------------------

function getWelcomeResponse() {
    // If we wanted to initialize the session to have some attributes we could add those here.
    var sessionAttributes = {};
    var cardTitle = "Welcome";
    var speechOutput = "Welcome to the Boat Hand. You can say Open Lights, or, you can say Anchor Up for Twenty Meters... What can I help you with?";

    var repromptText = "Your Boat is ready for command.";
    var shouldEndSession = false;

    ctx.succeed(buildResponse(sessionAttributes, buildSpeechletResponse(cardTitle, speechOutput, repromptText, shouldEndSession)));
}


function doCommandIntent(intent, session, callback) {
    var repromptText = null;
    var sessionAttributes = {};
    var shouldEndSession = true;
    var speechOutput = "";

    repromptText = "Tell me what is the command for the boat.";
    
    var task = intent.slots.Task.value;
    
    var punctuationless = task.replace(/[.,\/#!$%\^&\*;:{}=\-_`~()]/g,"");
    var task = punctuationless.replace(/\s{2,}/g," ");
    
    var validTasks = [ "LIGHTS" ];
    
    if (validTasks.indexOf(task.toString().toUpperCase()) < 0)
    {
        speechOutput = "I couldn't understand the command "+task+". Please try again.";
        ctx.succeed(buildResponse(sessionAttributes, buildSpeechletResponse(null, speechOutput, repromptText, false)));
    }
    else
    {
        var cardTitle = "Executing Open command " + task ;
        speechOutput = "Executing command " + task;
        mqttPublish(intent, sessionAttributes, cardTitle, speechOutput, repromptText, shouldEndSession);
    }
}

function anchorCommandIntent(intent, session, callback) {
    var repromptText = null;
    var sessionAttributes = {};
    var shouldEndSession = true;
    var speechOutput = "";

    repromptText = "Tell me what is the command for the boat.";

    var direction = intent.slots.Direction.value;
    var distance = intent.slots.Distance.value;
    
    
    var validDirections = [ "UP","DOWN" ];
    
    console.log("ufuk " + new RegExp(validDirections.join("|")).test(direction.toUpperCase()));
    
    if(distance === "?" || direction === "?")
    {
        throw "error";
    }
    else if(new RegExp(validDirections.join("|")).test(direction.toUpperCase()))
    {
        var cardTitle = "Executing command Anchor" ;
        speechOutput = "Executing Anchor " + direction + " for " + distance + " meters";
        mqttPublish(intent, sessionAttributes, cardTitle, speechOutput, repromptText, shouldEndSession);
    }
    else{
        speechOutput = "I couldn't understand the command "+ direction+". Please try again.";
        ctx.succeed(buildResponse(sessionAttributes, buildSpeechletResponse(null, speechOutput, repromptText, false)));
    }
}

function helpCommandIntent(intent, session, callback) {
    var repromptText = null;
    var sessionAttributes = {};
    var shouldEndSession = false;
    var speechOutput = "";

    var cardTitle = "Help" ;
    var speechOutput = "You can say Open Lights, or, you can say Anchor Up for Twenty Meters... What can I help you with?";
    var repromptText = "What can I help you with?";
        
    ctx.succeed(buildResponse(sessionAttributes, buildSpeechletResponse(cardTitle, speechOutput, repromptText, shouldEndSession)));
}

function mqttPublish(intent, sessionAttributes, cardTitle, speechOutput, repromptText, shouldEndSession)
{
    var strIntent = JSON.stringify(intent);
    console.log("mqttPublish:  INTENT text = " + strIntent);
    
    client.publish(config.topic, strIntent, function() {
        client.end();
    });
    
    client.on("close", (function () {
        console.log("MQTT CLIENT CLOSE - thinks it's done, successfully. ");
        ctx.succeed(buildResponse(sessionAttributes, buildSpeechletResponse(cardTitle, speechOutput, repromptText, shouldEndSession)));
    }));

    client.on("error", (function (err, granted) {
        console.log("MQTT CLIENT ERROR!!  " + err);
    }));
}


// --------------- Helpers that build all of the responses -----------------------

function buildSpeechletResponse(title, output, repromptText, shouldEndSession) {
    if(title != null)
        return {
            outputSpeech: {
                type: "PlainText",
                text: output
            },
            card: {
                type: "Simple",
                title: title,
                content: output
            },
            reprompt: {
                outputSpeech: {
                    type: "PlainText",
                    text: repromptText
                }
            },
            shouldEndSession: shouldEndSession
        };
    else
        return {
            outputSpeech: {
                type: "PlainText",
                text: output
            },
            reprompt: {
                outputSpeech: {
                    type: "PlainText",
                    text: repromptText
                }
            },
            shouldEndSession: shouldEndSession
        }
}

function buildResponse(sessionAttributes, speechletResponse) {
    return {
        version: "1.0",
        sessionAttributes: sessionAttributes,
        response: speechletResponse
    }
}
config.jsJavaScript
for use with Lambda
var config = {};

config.host = "Host Domain Here";
config.topic = "Topic Here";
config.app_id = "APP ID Here"

module.exports = config;
alexaTest.pyPython
For testing Relays
import RPi.GPIO as GPIO            # import RPi.GPIO module
from time import sleep             # lets us have a delay
GPIO.setmode(GPIO.BCM)             # choose BCM or BOARD
GPIO.setup(10, GPIO.OUT)           # set GPIO number as an output 

try:
    while True:
        GPIO.output(10, 1)         # set GPIO to 1/GPIO.HIGH/True
        print "HIGH!"
        sleep(4)                   # wait 4 seconds
        GPIO.output(10, 0)         # set GPIO to 0/GPIO.LOW/False
        print "LOW!"
        sleep(4)                   # wait 4 seconds

except KeyboardInterrupt:          # trap a CTRL+C keyboard interrupt
    GPIO.cleanup()                 # resets all GPIO ports used by this program
alexaBoat.pyPython
For Controlling Relays
#!/usr/bin/python

"""python boatstation for boat to be controlled by Amazon Alexa"""

import paho.mqtt.client as mqtt
import json, time, sys, ssl
import logging, logging.handlers
import RPi.GPIO as GPIO  

water = 24
lights = 17
navigation = 21
anchorDown = 11
anchorUp = 10

GPIO.setmode(GPIO.BCM)  

GPIO.setup(lights, GPIO.OUT)
GPIO.setup(water, GPIO.OUT)
GPIO.setup(navigation, GPIO.OUT)
GPIO.setup(anchorUp, GPIO.OUT)
GPIO.setup(anchorDown, GPIO.OUT)
GPIO.output(lights, GPIO.HIGH)
GPIO.output(water, GPIO.HIGH)
GPIO.output(navigation, GPIO.HIGH)
GPIO.output(anchorUp, GPIO.HIGH)
GPIO.output(anchorDown, GPIO.HIGH)

cert_path = "/home/pi/Desktop/pi_pi/certs/"
host = "Your Host"
topic = "Your Topic"
root_cert = cert_path + "rootCA.key"
cert_file = cert_path + "YourCert-certificate.pem.crt"
key_file = cert_path + "YourCert-private.pem.key"

globalmessage = ""  # to send status back to MQTT
isConnected = False
logger = logging.getLogger('alexaboat')

def do_command(data,cmd):  #   {"name":"CommandIntent","slots":{"Task":{"name":"Task","value":"launch"}}}
    task = str(data["slots"]["Task"]["value"])
    logger.info("TASK = " + task)
    global globalmessage

    if task.upper() == "LIGHTS":
        globalmessage = "executing command lights"
        print globalmessage
        toggle(lights,cmd)
    elif task.upper() == "WATER":
        globalmessage = "executing command water"
        print globalmessage
        toggle(water,cmd)
    elif task.upper() == "NAVIGATION":
        globalmessage = "executing command navigaiton"
        print globalmessage
        toggle(navigation,cmd)

def anchor_command(data):  #   {"name":"CommandIntent","slots":{"Task":{"name":"Task","value":"launch"}}}
    try:
        direction = str(data["slots"]["Direction"]["value"])
        distance = float(data["slots"]["Distance"]["value"])

        logger.info("Anchor = " + direction + " distance "+ str(distance))
        global globalmessage

        globalmessage = "executing command anchor" + direction+" " + str(distance) +" "+ str(distance/0.48)
        print globalmessage

        if direction.upper() == "UP":
   		anchor = anchorUp
	elif direction.upper() == "DOWN":
		anchor = anchorDown

        GPIO.output(anchor, GPIO.LOW)
        time.sleep(distance/0.48)
        GPIO.output(anchor, GPIO.HIGH)
        
    except (ValueError):
        logger.info("Anchor Error")
    
def toggle(gpio,cmd):
    logger.info("GPIO: " + str(gpio) + "CMD: " + str(cmd))
    GPIO.output(gpio, cmd)
        
# The callback for when the client receives a CONNACK response from the server.
def on_connect(client, userdata, flags, rc):
    isConnected = True
    logger.info("connected with result code " + str(rc))
    # Subscribing in on_connect() means that if we lose the connection and      
    # reconnect then subscriptions will be renewed.    
    client.subscribe(topic)

# The callback for when a PUBLISH message is received from the server.
def on_message(client, userdata, msg):
    logger.info("msg received, payload = " + str(msg.payload))
    data = json.loads(str(msg.payload))
    # Figure out INTENT -- was it Open, Close, Anchor, etc
    if "name" in data:
        if data["name"] == "Open":
            do_command(data,GPIO.LOW)
        if data["name"] == "Close":
            do_command(data,GPIO.HIGH)
        elif data["name"] == "Anchor":
            anchor_command(data)
        
def on_log(client, userdata, level, buf):
    logger.debug(buf)

def logger_init():
    logger.setLevel(logging.DEBUG)
    log_file_size = 1024 * 1024 * 1  # 1 MB
    formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(process)d - %(name)s : %(message)s')
    fh = logging.handlers.RotatingFileHandler('/home/pi/Desktop/pi_pi/logs/alexaboat.log', maxBytes=log_file_size, backupCount=5)
    fh.setFormatter(formatter)
    sh = logging.StreamHandler(sys.stdout)
    sh.setFormatter(formatter)
    logger.addHandler(fh)
    logger.addHandler(sh)
    logger.info('******************************************')
    logger.info('Starting up...')

logger_init()

client = mqtt.Client(client_id="alexaBoat.py")
client.on_connect = on_connect
client.on_message = on_message
client.on_log = on_log
client.tls_set(root_cert,
               certfile = cert_file,
               keyfile = key_file,
               cert_reqs=ssl.CERT_REQUIRED,
               tls_version=ssl.PROTOCOL_TLSv1_2,
               ciphers=None)

logger.info('connecting to mqtt broker')
client.connect(host, 8883, 60)

run = True

try:
    while run:
        client.loop()
        time.sleep(1)

        try:
            mypayload = '''{
                "StatusMessage": "%s"
            }''' % (globalmessage)

            if globalmessage != "":
                client.publish(topic, mypayload)
                globalmessage = ""

        except (TypeError):
            pass
    
except KeyboardInterrupt:
    print "Bye Bye!"
    GPIO.cleanup()    

Credits

12519107 788723347925954 1136277278 n
Ufuk ARSLAN

Entrepreneur; Tech-Lover; Co-Founder at Mobilist; Proud member of CanavarIT

Contact

Replications

Did you replicate this project? Share it!

I made one

Love this project? Think it could be improved? Tell us what you think!

Give feedback

Comments

Similar projects you might like

LoRaWAN for Raspberry Pi with Worldwide Frequency Support
Intermediate
  • 3,878
  • 18

Full instructions

LoRaWAN LMIC 1.6 for Raspberry Pi with Dragino LoRA/GPS HAT or standalone RFM95W LoRa Module.

Human-Following Robot with Kinect
Intermediate
  • 2,571
  • 24

Full instructions

Instead of using single camera and complicated image recognition algorithms we can take advantage of Kinect libraries.

Wireless Programming and Debugging with STM32 and RPi
Intermediate
  • 87
  • 2

Full instructions

Are you tired of connecting programmer every time you want to change some functionality of your robot? Why not to try wireless programming?

Thunderboard Sense and RPi
Intermediate
  • 128
  • 2

Collecting rich sensor data over BLE and uploading it to the cloud for analysis.

Automated Indoor Gardener
Intermediate
  • 4,009
  • 40

Full instructions

Never worry about dead plants again. This automated gardener never forgets to water your plants and provide artificial sunlight.

Computerception
Intermediate
  • 27,480
  • 20

Full instructions

Why not put a Raspberry Pi into a 2007 netbook?

Sign up / LoginProjectsPlatformsTopicsContestsLiveAppsBetaFree StoreBlog