Building a Custom Alexa Skill - Part 2

Link to first post: Creating My Own House Jarvis - part 1

Now that we have built an intent (HouseLightsIntent) and we have predefined slots and values . . . lets get into some coding:

For my HouseLightsIntent, I have the following Utterances:

  • to turn {action} {entity}
  • {entity} to {action}
  • set {entity} lights {action}
  • {entity} to {percentage_brightness} percent
  • switch the {entity} {action}
  • set {entity} to {color}
  • {entity} lights {action}
  • set {entity} to {percentage_brightness}
  • set {entity} to {action}
  • turn {entity} {action}

As you can see in the above “Utterances”, I have the following slot types:

  • Action
  • Entity
  • Percentage_brightness
  • Color

Each one of these is a “slot” as defined in the slot types section on your left hand nav of the Skills console. My values for my Entity Slot are as follows:

  • HVAC
  • H. V. A. C.
  • heater
  • heat
  • airconditioning
  • air conditioning
  • bathroom fan
  • bathroom vanity
  • kitchen cabinets
  • kitchen cabinet
  • homestead
  • house
  • thermostat
  • laundry room motion sensor
  • hallway motion sensor
  • bathroom motion sensor
  • bedroom motion sensor
  • kitchen motion sensor
  • kitchen overhead
  • living room overhead
  • living room lamp
  • living room
  • sunroom
  • sun room
  • hallway
  • hall
  • master bedroom
  • bedroom
  • kitchen

And my values for my “action” slot are as follows:

  • brighten
  • auto
  • cool
  • heat
  • set brightness percent
  • dim
  • off
  • on

I’ve put this here to help provide context. As you can see, with these two lists, I can then “trigger” my “HouseLightsIntent” with phrases such as:

  • Turn kitchen on
  • Turn kitchen lights on
  • Turn h. v. a. c to auto
  • Set kitchen to red

Remember . . . you have to save and build your model for Amazon to understand how to “listen” to you.

Code
Node RED
So this is where the magic happens. Lets set up the “how” to communicate with Node Red first. Hop over to Node Red and do a node search for “Http”. You want this node:

image

Drag to your flow, and provide an endpoint name. Also make sure that it is set to “get”. This is VERY important in order to talk back to your skill. For my “HouseLightsIntent”, I set my endpoint name to it lights. (attach a debug node to this too, set to entire message object) These endpoints will live under http(s)://yourdomain:1800/endpoint/ (in my case http://mydomain:1880/endpoints/lights)

image

Deploy your node. Hop over to your favorite API testing tool (I like Postman), and put in the full URL as we just discussed. Make sure that you are set to send a “GET” request. Also (and this is key), you will need to set up “basic authentication” which uses the USERNAME and PASSWORD as defined in your node-red config.

Hit send and see your debug output!! (if you have gotten it all right, and I haven’t missed anything, you should see a msg object in your sidebar)

Alexa Lamda Code
I’m assuming that you have already used postman to validate that your node red and that it is public and responds.
Hop back over to VS Code. First, at the top of your index.js file, add the following code:

const axios = require('axios');

(just put it right under the const Alexa = require('ask-sdk-core'); line). This is needed for us to talk to our webservice.

At the bottom of your file, but above this line: exports.handler = Alexa.SkillBuilders.custom(), add this block of code (replace the XXXXXXX with the appropriate values from your node-red configuration):

const fetchURLwithJSON = async (url, jsonobj) => {
    try {
        let config = {
            auth:
            {
                username: ‘XXXXXXXX’,
                password: ‘XXXXXXXX'
            },
            params: {
                jsonobj
            }
        };
        const { data } = await axios.get(url, config);
        return data;
    } catch (error) {
        console.error('cannot fetch quotes', error);
    }
};
const fetchURL = async (url) => {
    try {
        const { data } = await axios.get(url, {
            auth: {
                username: ‘XXXXXXXX’,
                password: ‘XXXXXXXX'
            }
        });
        return data;
    } catch (error) {
        console.error('cannot fetch quotes', error);
    }
};

What you just added was two different ways for you to communicate with your node-red http endpoint. One call allows for you to pass JSON to your endpoint (fetchURLwithJSON), and the other allows you to do so without any data (fetchURL). I created fetchURL this as my son wanted a “random dinosaur fact”, so all I care about is a response, I don’t need to pass any data around.

Now . . . find a a code block that you can copy and paste. The hello world intent is a good place to start. Find this code:

const HelloWorldIntentHandler = {
    canHandle(handlerInput) {
        return Alexa.getRequestType(handlerInput.requestEnvelope) === 'IntentRequest'
            && Alexa.getIntentName(handlerInput.requestEnvelope) === 'HelloWorldIntent';
    },
    handle(handlerInput) {
        const speakOutput = 'Hello World!';

        return handlerInput.responseBuilder
            .speak(speakOutput)
            //.reprompt('add a reprompt if you want to keep the session open for the user to respond')
            .getResponse();
    }
};

Copy / Paste it and change it to look like this:

const HouseLightsIntent = {
    canHandle(handlerInput) {
        return Alexa.getRequestType(handlerInput.requestEnvelope) === 'IntentRequest'
            && Alexa.getIntentName(handlerInput.requestEnvelope) === ‘HouseLightsIntent’;
    },
    async handle(handlerInput) {
        const speakOutput = 'Hello World!';

        return handlerInput.responseBuilder
            .speak(speakOutput)
            //.reprompt('add a reprompt if you want to keep the session open for the user to respond')
            .getResponse();
    }
};

Note: we made changes on the first, and fourth lines (replacing ‘HelloWorldIntent’ with ‘HouseLightsIntent’), and on the sixth line, we added the ‘async’ key word in front of the “handles” statement. We had to do this or our webservice calls won’t work.

Then at the VERY bottom of the file, find the ‘exports.handler’ line. Add ‘HouseLightsIntent,’ just under the ‘LaunchRequestHandler’ (don’t forget your comma), and your code should look like this:

exports.handler = Alexa.SkillBuilders.custom()
    .addRequestHandlers(
            LaunchRequestHandler,
            HouseLightsIntent,
        	HelloWorldIntentHandler,
        	HelpIntentHandler,

Note: you will have to do this for EVERY intent you write, as the backend runtime uses reflection to find the methods it needs to execute. Save your code, and save often. I’m assuming that you are still in VS code . . . so do the git command thing in your git terminal. This will push your code to Amazon, and make it your “current version of code to execute”. Switch back over to your AWS Skill console. At the top you will see a link that says code . . .open that bad boy in a new window and you should see your edits appear. If you don’t, it means you didn’t push your code right (go back and try again).

If you do see your code changes . . . click that deploy button. Wait for your deployment to be finished, and BOB’s Your Uncle!!! You have just edited the back end of your alexa skill and deployed it. It won’t do anything for you, but this is the start of your skill’s greatness.

Part 3 to come

2 Likes

Any way to do this without NodeRed?

You can . . . you can point your lambda to any URL given the code I shared. As long as you know the endpoint (data requirements, authentication, response types, etc). I’m just a fan of “local” processing, and to be honest . . . the hardest part FOR ME was getting the AXIOS call working in the lambda functions. That and wrapping my brain around all the possible permutations of how “people talk”.

1 Like

I tried nodered a while back didn’t find configuring the nodes intuitive. And I was able to get along fine without it so I kind of let it go at that point.

Maybe I’ll have to try this all out and see where I can go with it. maybe… :wink:

thanks for posting this in-depth walkthru.

My goal is to “cut the cloud” as much as possible. Node Red and my deconz dongle allows for that. It has a learning curve, but once you get it, it is WAY easier than YAML (for me anyway)

And you are welcome.

1 Like