Building a Custom Alexa Skill - Part 3

Link to Building a Custom Alexa Skill - Part 2

If you are this far, lets go back to our “index.js” file for our lamda function. If you have been following along, a lot of this will be “copy and paste” for you.

First and foremost, we need to define a constant for our lighting URL. Go to the top of your index.js file and add the following variable declaration (note, the double slash is a comment):

//endpoint URLS
const houseLightsURL = 'http://yourdomain:1880/endpoint/lights';

Go back to your HouseLightsIntent, and replace it with this:

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

        //set default as place holders
        var entity = 'default';
        var action = 'default';
        var brightness_pct = 0;
        var speakOutput = 'There was an error talking to Node Red, please try again';
        var jsonData = {};

        //Set up Session to record entity
        const sessionAttributes = handlerInput.attributesManager.getSessionAttributes();

        var requestedEntity = sessionAttributes.requestedEntity;
        var requestedAction = sessionAttributes.requestedAction;
        var requestedBrightness = sessionAttributes.requestedAction;
        var requestedColor = sessionAttributes.color;
        
        //entity name
        if (handlerInput.requestEnvelope.request.intent.slots.entity.value !== '' && handlerInput.requestEnvelope.request.intent.slots.entity.value !== undefined) {
            requestedEntity = handlerInput.requestEnvelope.request.intent.slots.entity.value;
            jsonData["entity"] = requestedEntity;
        }
        //action
        if (handlerInput.requestEnvelope.request.intent.slots.action.value !== '' && handlerInput.requestEnvelope.request.intent.slots.action.value !== undefined) {
            requestedAction = handlerInput.requestEnvelope.request.intent.slots.action.value;
            jsonData["action"] = requestedAction;
        }
        //brightness
        if (handlerInput.requestEnvelope.request.intent.slots.percentage_brightness.value !== '' && handlerInput.requestEnvelope.request.intent.slots.percentage_brightness.value !== undefined) {
            requestedBrightness = handlerInput.requestEnvelope.request.intent.slots.percentage_brightness.value
            jsonData["percentage_brightness"] = requestedBrightness;
        }
        //color
        if (handlerInput.requestEnvelope.request.intent.slots.color.value !== '' && handlerInput.requestEnvelope.request.intent.slots.color.value !== undefined) {
            requestedColor = handlerInput.requestEnvelope.request.intent.slots.color.value
            jsonData["color"] = requestedColor;
        }

        sessionAttributes.requestedEntity = requestedEntity;
        sessionAttributes.requestedAction = requestedAction;
        sessionAttributes.requestedBrightness = requestedBrightness;
        sessionAttributes.color = requestedColor;

        handlerInput.attributesManager.setSessionAttributes(sessionAttributes);

        //call Node Red endpoint
        try {
            const resplights = await fetchURLwithJSON(houseLightsURL, jsonData);
            speakOutput = JSON.stringify(resplights.response) + ' If you are finished, please tell me goodbye';
        }
        catch (error) {
            speakOutput = 'There was an error talking to Node Red, please try again';
        }


        if (requestedEntity === 'default' || requestedAction === 'default') {
            return handlerInput.responseBuilder
                .speak("I'm sorry.  I don't know how to do that.  Please restate what you would like to do and file a bug report with hunky hubby")
                .reprompt("I'm sorry.  I don't know how to do that.  Please restate what you would like to do and file a bug report with hunky hubby")
                .getResponse();
        }
        else {
            return handlerInput.responseBuilder
                .speak(speakOutput)
                .reprompt(speakOutput)
                .getResponse();
        }

    }
};

NOTE: I’m not a Javascript expert (I’m an old C# dood), so I probably took the “long way round” on some of these things, but it helps me so I hope it helps you.

The key part we care about is the ‘async handle(handlerInput)’ method. This is where the bulk of the work will occur on each intent.

Lets break down what is going on in this code
First we declare some variables that will be used later:

        var entity = 'default';
        var action = 'default';
        var brightness_pct = 0;
        var speakOutput = 'There was an error talking to Node Red, please try again';
        var jsonData = {};

We turn on Sessions (this allows us to be more conversational as we can “ask for clarity” and hold on to what was originally said, although I haven’t fully implemented this yet). This is key to when Alexa asks you “did you mean XXXX”, although there is more to it than that programmatically, but this lays the foundation.

This block, grabs the current session, and sets values:

//Set up Session to record entity
        const sessionAttributes = handlerInput.attributesManager.getSessionAttributes();

        var requestedEntity = sessionAttributes.requestedEntity;
        var requestedAction = sessionAttributes.requestedAction;
        var requestedBrightness = sessionAttributes.requestedAction;
        var requestedColor = sessionAttributes.color;

This block does some “sanity checking” and builds our JSON object to send to NodeRed:

//entity name
         if (handlerInput.requestEnvelope.request.intent.slots.entity.value !== '' && handlerInput.requestEnvelope.request.intent.slots.entity.value !== undefined) {
             requestedEntity = handlerInput.requestEnvelope.request.intent.slots.entity.value;
             jsonData["entity"] = requestedEntity;
         }
         //action
         if (handlerInput.requestEnvelope.request.intent.slots.action.value !== '' && handlerInput.requestEnvelope.request.intent.slots.action.value !== undefined) {
             requestedAction = handlerInput.requestEnvelope.request.intent.slots.action.value;
             jsonData["action"] = requestedAction;
         }
         //brightness
         if (handlerInput.requestEnvelope.request.intent.slots.percentage_brightness.value !== '' && handlerInput.requestEnvelope.request.intent.slots.percentage_brightness.value !== undefined) {
             requestedBrightness = handlerInput.requestEnvelope.request.intent.slots.percentage_brightness.value
             jsonData["percentage_brightness"] = requestedBrightness;
         }
         //color
         if (handlerInput.requestEnvelope.request.intent.slots.color.value !== '' && handlerInput.requestEnvelope.request.intent.slots.color.value !== undefined) {
             requestedColor = handlerInput.requestEnvelope.request.intent.slots.color.value
             jsonData["color"] = requestedColor;
         }

handlerInput.requestEnvelope.request.intent.slots.entity.value in the first line above is the VALUE of the SLOT that you defined. So if I asked Alexa to “turn kitchen lights on” and my pattern to match was “turn {entity} lights {action}”, this response object would contain the value of “kitchen”. Inside the if statement, we set the session variable to (in this example) “kitchen”, while also adding a JSON document entry of “entity”:”kitchen”.

Moving on to the next code block:

sessionAttributes.requestedEntity = requestedEntity;
        sessionAttributes.requestedAction = requestedAction;
        sessionAttributes.requestedBrightness = requestedBrightness;
        sessionAttributes.color = requestedColor;

        handlerInput.attributesManager.setSessionAttributes(sessionAttributes);

Sets the session values, and stores them until the session ends (in this case . . . telling your skill goodbye).

//call Node Red endpoint
        try {
            const resplights = await fetchURLwithJSON(houseLightsURL, jsonData);
            speakOutput = JSON.stringify(resplights.response) + ' If you are finished, please tell me goodbye';
        }
        catch (error) {
            speakOutput = 'There was an error talking to Node Red, please try again';
        }

This TRY’s to call our node red endpoint const const resplights = await fetchURLwithJSON(houseLightsURL, jsonData); and sets the text to “speak” to our response.

And the last bit of sanity checking:

if (requestedEntity === 'default' || requestedAction === 'default') {
            return handlerInput.responseBuilder
                .speak("I'm sorry.  I don't know how to do that.  Please restate what you would like to do and file a bug report with hunky hubby")
                .reprompt("I'm sorry.  I don't know how to do that.  Please restate what you would like to do and file a bug report with hunky hubby")
                .getResponse();
        }
        else {
            return handlerInput.responseBuilder
                .speak(speakOutput)
                .reprompt(speakOutput)
                .getResponse();
        }

Note: .reprompt(speakOutput) is used to “reply and wait” (it keeps your session of interactivity “open” so you can provide more info to your skill.

Save, build, Deploy . . . if everything is correct, you shouldn’t have any issues. But What do we do with that Webservice call?

Look for the next part