2: Technology and Implementation - GharbiRaouf/hhz_hackathon_alexa_coffee GitHub Wiki

There are over 25.000 existing Alexa skills yet on Amazon. You can ask for coffee of the day or make your home smarter by activating the corresponding skill. To create a more personalized experience we started to build an own Alexa skill.

AWS Lambda

AWS Lambda runs the code without provisioning or managing servers. Only the consumed time has to be paid and there is no charge when the code is not running. The code runs for virtually any type of application or backend service - all with zero administration. Therefore the code has to be uploaded and Lambda takes care of everything required to run and scale the code with high availability. One can set up the code to automatically trigger from other AWS services or call it directly from any web or mobile app.

Alexa Skill

Alexa is Amazon’s voice service and the brain behind the devices like the Amazon Echo, Echo Dot, and Echo Show. Alexa provides capabilities, or skills, that enable customers to create a more personalized experience. There are now more than 25,000 skills from companies like Starbucks, Uber, and Capital One as well as other innovative designers and developers.

Implementation via Alexa Skill Kit

The first step in building a new skill is to decide what the skill will do. The functionality is wanted to implement determines how the skill integrates with the Alexa service and what is needed to build. The Alexa Skill Kit supports building different types of skills. To implement a new custom Alexa skill we needed to implement our built-in intent, add the intent to the intent schema and then add handling for the intent to the code.

  • Intent Schema:
  "intents": [
    {
      "intent": "anleitung"
    },
    {
      "intent": "best"
    },
    {
      "intent": "wasser"
    },
    {
      "intent": "alt"
    },
    {
      "intent": "AMAZON.PauseIntent"
    },
    {
      "intent": "AMAZON.ResumeIntent"
    }
  ]
}

Utterances map the phrases the user can speak to the intents which have been defined. They are written as lines in a plain text file.

  • Sample Utterances
anleitung wie mache ich kaffee
anleitung für kaffee
anleitung kaffee kochen
anleitung anleitung zum kaffee kochen
anleitung anleitung kaffee
anleitung anleitung
wasser gibt es noch kaffee
wasser noch kaffee
wasser ob es noch kaffee gibt
wasser gib mir kaffee
wasser kaffee übrig
wasser wie viel kaffee
wasser kaffee da
wasser kaffe noch da
wasser kaffee vorhanden
wasser gibts kaffee
wasser hats kaffee
alt ist der kaffee frisch
alt wie alt ist der kaffee
alt wann wurde der kaffee gekocht
alt ist der kaffee neu
alt noch frisch
alt wann kaffee
alt wann gekocht
alt noch geniessbar
best wer ist der kaffee chef

Implementation via AWS Lambda

We used the Lambda console to create a new Lambda function and do basic testing. By using a blueprint to create a new function with code for a basic function we implemented a skill in Node.js. After entering Name and Description for the function, we selected the Role for the function. This defines the AWS resources the function can access.

We manually tested a Lambda function in the Lambda console by sending sample JSON events formatted in the same way as requests sent by Alexa. Sample events are provided for testing within the console. We used these events as a starting point and modified them to represent requests that the Alexa service would send to our own function. After the function runs, the Execution result section shows the response returned by the function, in JSON format. One should see a response appropriate for the request pasted into the Sample event box.

Configuring the Alexa Skills Kit trigger granted Alexa the necessary invocation permissions for our function. Also we implemented a request handler within the code. Most of the coding tasks for the cloud-based service for a custom skill are related to:

  • Handling the different types of requests sent by the Alexa service.

  • Optionally sending intermediate content to the user before returning a full response, such as a message indicating that Alexa is working on the user’s request.

  • Returning appropriate responses to these requests.

  • Sample Utterances

AWS Lambda Code


'use strict';

exports.handler = function (event, context) {
    try {
        console.log("event.session.application.applicationId=" + event.session.application.applicationId);

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

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

        if (event.request.type === "LaunchRequest") {
            onLaunch(event.request,
                event.session,
                function callback(sessionAttributes, speechletResponse) {
                    context.succeed(buildResponse(sessionAttributes, speechletResponse));
                });
        } else if (event.request.type === "IntentRequest") {
            onIntent(event.request,
                event.session,
                function callback(sessionAttributes, speechletResponse) {
                    context.succeed(buildResponse(sessionAttributes, speechletResponse));
                });
        } else if (event.request.type === "SessionEndedRequest") {
            onSessionEnded(event.request, event.session);
            context.succeed();
        }
    } catch (e) {
        context.fail("Exception: " + e);
    }
};

/**
 * Called when the session starts.
 */
function onSessionStarted(sessionStartedRequest, session) {
    console.log("onSessionStarted requestId=" + sessionStartedRequest.requestId
        + ", sessionId=" + session.sessionId);

    // add any session init logic here
}

/**
 * Called when the user invokes the skill without specifying what they want.
 */
function onLaunch(launchRequest, session, callback) {
    console.log("onLaunch requestId=" + launchRequest.requestId
        + ", sessionId=" + session.sessionId);

    var cardTitle = "Welcome"
    var speechOutput = 'willkommen im Herman Hollerith Zentrum. Du kannst nach kaffeemaschine fragen: sage einfach wie mache ich kaffee, gibt es noch kaffee oder wie alt ist der kaffee';
    callback(session.attributes,
        buildSpeechletResponse(cardTitle, speechOutput, "", false));
}

/**
 * Called when the user specifies an intent for this skill.
 */
function onIntent(intentRequest, session, callback) {
    console.log("onIntent requestId=" + intentRequest.requestId
        + ", sessionId=" + session.sessionId);

    var intent = intentRequest.intent,
        intentName = intentRequest.intent.name;

    // dispatch custom intents to handlers here
    if (intentName == 'anleitung') {
        handleTestRequest(intent, session, callback);
    } else if (intentName == 'ThermosIntent') {
        getLiquidHeightFromThermos(intent, session, callback);
    } else if (intentName == 'wasser')  {
                handleTestRequest1(intent, session, callback);
    }
    else if (intentName == 'alt')  {
                handleTestRequest2(intent, session, callback);
    }
    {
        throw "Invalid intent";
    }
}

/**
 * Called when the user ends the session.
 * Is not called when the skill returns shouldEndSession=true.
 */
function onSessionEnded(sessionEndedRequest, session) {
    console.log("onSessionEnded requestId=" + sessionEndedRequest.requestId
        + ", sessionId=" + session.sessionId);

    // Add any cleanup logic here
}

function handleTestRequest(intent, session, callback) {
    var speechOutput = 'Kaffee am HHZ zu kochen ist ganz einfach. Fülle zwei Liter Wasser von oben in die Kaffeemaschine. Gib 1-2 Messlöffel Kaffeepulver in den Filter und betätige den Kippschalter. '+
    'Wenn der Kaffee durchgelaufen ist, musst du nur noch den Pumpspender an die Kaffeekanne anbringen. Das war’s. Das schaffst du schon. ' ;
    callback(session.attributes,
        buildSpeechletResponseWithoutCard(speechOutput, "", 'true'));
}

// Getting DATA
const https = require("https");
function getLiquidHeightFromThermos(intent, session, callback) {
      
    https.get('Your IP (localtunnel IP)', res => {
      res.setEncoding("utf8");
      let body = "";
      res.on("data", data => {
        body += data;
      });
      res.on("end", () => {
        body = JSON.parse(body);
        callback(session.attributes,
            buildSpeechletResponseWithoutCard(body, "", 'true'));
      });
    });
}

// FN Number of Cups

const roundTo = require('round-to');
const https = require("https");
function getNumCups (intent, session, callback) {
    var weight = 3000;     
    const empty  = 1700;
    const  cup = 200 ;
    roundTo(data, 0);
    var data;
    data = (weight - empty) / cup;
    var speechOutput = 'data';
    callback(session.attributes,
    buildSpeechletResponseWithoutCard(speechOutput ,"", 'true')
    )
} 


function handleTestRequest1(intent, session, callback) {
    var speechOutput = 'es sind noch +"data"' ;
    callback(session.attributes,
        buildSpeechletResponseWithoutCard(speechOutput, "", 'true'));
}

function handleTestRequest2(intent, session, callback) {
    var speechOutput = 'der kaffee wurde heute morgen  gekocht' ;
    callback(session.attributes,
        buildSpeechletResponseWithoutCard(speechOutput, "", 'true'));
}


// ------- Helper functions to build responses -------

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

function buildSpeechletResponseWithoutCard(output, repromptText, shouldEndSession) {
    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
    };
}