Alexa integration with HomeAssistant Guide

Setting up amazon alexa to work with home assistant.

What you need

  • Identify you home router’s public ip.
  • the entity_id of things you can already turn on and off in homeassistant ie. light switches

Note: You don’t need an ssl cert for this approach. Its “probably” not a big deal but you must realize that all your http traffic is out in the open and not encrypted. The likelihood of someone snooping on your traffic is low, but it is a possibility. Were just here to make your stuff work. You can clean it up later. If you have cameras inside your house, take the time to setup an ssl cert and do this over https.

The overall gist of how this works

You talk -> Your Alexa Skill interprets your words and calls the corresponding aws lamda function -> lamda makes a restful service call to your home router ip/port which fowards the request to your internal HomeAssistant ip -> home assistant turns an entity on or off.

Why not just use the home assistant cloud module?

  • If i wanted to pay a monthly fee i wouldn’t have made home automation my hobby.
  • I really hate monthly fees

What do you give up by not using cloud?

  • easy https
  • Instead of saying “Alexa, turn off my lights” you will have to say, “Alexa, ask [whatever you want] to turn off my lights”

Lets do this.

  1. setup an api password and cors in your http config. The ip you need for cors is the internal ip of your router. Request will come from that addr because they are fowarded from your router from the routers external ip/port you configure in the next step.
http:
  api_password: YOUR_PASSWORD
  cors_allowed_origins:
    - http://192.168.1.1
  1. In your router administration setup a port forwarding rule to forward an external port (ie 9615) to your home assistant server’s internal address 198.0.1.xxx port 8123

  2. Create a skill using amazon alexa developer.

    • A skill defines the keywords necessary for alexa to interpret your spoken words into an action.
    • Once she knows what to do she will call the necessary function to complete the action.
    • The function to complete the action will be an aws lamda function.

    Lets create the skill

    1. go to https://developer.amazon.com/edw/home.html#/
    2. click on “Alexa”
    3. Get Started with Alexa Skills Kit
    4. Name your skill. This doesn’t really matter what you call it. I called it home-assistant-skills-template.
    5. Create you invocation name. You will have to say this when commanding alexa to do something for home assistant, thus i recommend an invocation name of home assistant.
    6. Leave everything else on that page as a default and hit next.
    7. Now we must define our intent schema.
    	{
    	  "intents": [
    	    {
    	      "slots": [
    	        {
    	          "name": "Scene",
    	          "type": "LIST_OF_SCENES"
    	        }
    	      ],
    	      "intent": "SceneTurnOnIntent"
    	    },
    	    {
    	      "slots": [
    	        {
    	          "name": "Scene",
    	          "type": "LIST_OF_SCENES"
    	        }
    	      ],
    	      "intent": "SceneTurnOffIntent"
    	    },
    	    {
    	      "intent": "AMAZON.HelpIntent"
    	    }
    	  ]
    	}
    
    • Lets explain this. We want alexa to do two things. Turn things on and turn them off. The schema must contain an intent for each. Each intent will reference a list of scenes(or entities) that we would like to control. We will create that list in the next step. The same list will be used for both intents.
      First i want to disect what you will eventually say so you understand each piece.
      Alexa ask [home assistant] to [turn off the {master bedroom lights}]
      Invocation name. Utterance. Scene
    1. Create a custom slot type called LIST_OF_SCENES. Add some entries you want to control: master_bedroom_lights, …
    2. Now lets move to aws lamda to create an endpoint for the skill to call.
  3. Create an aws lamda function for your alexa skill that will call your home assistant endpoint.

    • Create a function in aws lamda
    • You will be given two options Author from scratch or create from blueprint. Create from blueprint
    • Find the blueprint called alexa-skills-kit-color-expert and select it. (My example uses the node.js not the python vs)
    • Select the blueprint and press configure
    • Name it whatever you want. HATest.
    • Now select Create Role from Template under “Role:”
    • Name it HaRole or whatever you want and under Policy templates i chose Simple Microservice Permissions.
      ** I believe these roles can provide security separations if you need them for access to keys(KMS). I didn’t touch them besides adding this required field.
    • Scroll down and hit “Create Function”
    • Now you should have an overview of your Alexa Skills kit wired to your Lamda outputting logs to Cloudwatch
    • Now lets overwrite the template nodejs code with the below code
    • You will need to overwite some values
      ** hostname: this will be your external router ip
      ** port: this will be the external router ip’s port you previously configured to be fowarded to internal address
      ** header value for x-ha-access: needs to be your configured api password you made in step 1.
      ** Lastly the getEntity function is to map what comes in as the scene/entity to modify to its actual entity_id in home assistant. If your alexa skill scene is called master_bedroom_lights it will come into the lamda function as master bedroom lights without the underscores. That needs to map to the ha entity called switch.master_bedroom_lights_switch
    'use strict';
    var http = require('http');
    
    /**
     * This sample demonstrates a simple skill built with the Amazon Alexa Skills Kit.
     * The Intent Schema, Custom Slots, and Sample Utterances for this skill, as well as
     * testing instructions are located at http://amzn.to/1LzFrj6
     *
     * For additional samples, visit the Alexa Skills Kit Getting Started guide at
     * http://amzn.to/1LGWsLG
     */
    
    
    // --------------- Helpers that build all of the responses -----------------------
    
    function buildSpeechletResponse(title, output, repromptText, shouldEndSession) {
        return {
            outputSpeech: {
                type: 'PlainText',
                text: output,
            },
            card: {
                type: 'Simple',
                title: `SessionSpeechlet - ${title}`,
                content: `SessionSpeechlet - ${output}`,
            },
            reprompt: {
                outputSpeech: {
                    type: 'PlainText',
                    text: repromptText,
                },
            },
            shouldEndSession,
        };
    }
    
    function buildResponse(sessionAttributes, speechletResponse) {
        return {
            version: '1.0',
            sessionAttributes,
            response: speechletResponse,
        };
    }
    
    
    // --------------- Functions that control the skill's behavior -----------------------
    
    function getWelcomeResponse(callback) {
        // If we wanted to initialize the session to have some attributes we could add those here.
        const sessionAttributes = {};
        const cardTitle = 'Welcome';
        const speechOutput = 'Welcome to Home Assistant skills. ' +
            'Please tell me the scene you would like to turn off or on, such as, turn off the master bedroom lights';
        // If the user either does not reply to the welcome message or says something that is not
        // understood, they will be prompted again with this text.
        const repromptText = 'Please tell me a scene to turn on or off, ';
        const shouldEndSession = false;
    
        callback(sessionAttributes,
            buildSpeechletResponse(cardTitle, speechOutput, repromptText, shouldEndSession));
    }
    
    
    function handleSessionEndRequest(callback) {
        const cardTitle = 'Session Ended';
        const speechOutput = 'Thank you for trying my Home Assistant Skills Kit sample. Have a nice day!';
        // Setting this to true ends the session and exits the skill.
        const shouldEndSession = true;
    
        callback({}, buildSpeechletResponse(cardTitle, speechOutput, null, shouldEndSession));
    }
    
    function createSceneAttributes(sceneToControl) {
        return {
            sceneToControl,
        };
    }
    
    /**
     * Sets the scene in the session
     */
    function setSceneStateInSession(state, intent, session, callback) {
        const cardTitle = intent.name;
        const selectedSceneSlot = intent.slots.Scene;
        let repromptText = null;
        let sessionAttributes = {};
        const shouldEndSession = true;
        let speechOutput = '';
    
        if (selectedSceneSlot) {
            const selectedScene = selectedSceneSlot.value;
            speechOutput = `Requesting Home Assistant to turn ${selectedScene}, ${state}.`;
        }
        callScene(state, selectedSceneSlot.value, function(){
            callback(sessionAttributes,
            buildSpeechletResponse(cardTitle, speechOutput, repromptText, shouldEndSession));
        });
        //callback(sessionAttributes,
        //     buildSpeechletResponse(cardTitle, speechOutput, repromptText, shouldEndSession));
    }
    
    
    // --------------- Events -----------------------
    
    /**
     * Called when the session starts.
     */
    function onSessionStarted(sessionStartedRequest, session) {
        console.log(`onSessionStarted requestId=${sessionStartedRequest.requestId}, sessionId=${session.sessionId}`);
    }
    
    /**
     * Called when the user launches the skill without specifying what they want.
     */
    function onLaunch(launchRequest, session, callback) {
        console.log(`onLaunch requestId=${launchRequest.requestId}, sessionId=${session.sessionId}`);
    
        // Dispatch to your skill's launch.
        getWelcomeResponse(callback);
    }
    
    /**
     * Called when the user specifies an intent for this skill.
     */
    function onIntent(intentRequest, session, callback) {
        console.log(`onIntent requestId=${intentRequest.requestId}, sessionId=${session.sessionId}`);
    
        const intent = intentRequest.intent;
        const intentName = intentRequest.intent.name;
    
        // Dispatch to your skill's intent handlers
        if (intentName === 'SceneTurnOnIntent') {
            setSceneStateInSession('on', intent, session, callback);
        } else if (intentName === 'SceneTurnOffIntent') {
            setSceneStateInSession('off', intent, session, callback);
        } else if (intentName === 'AMAZON.HelpIntent') {
            getWelcomeResponse(callback);
        } else if (intentName === 'AMAZON.StopIntent' || intentName === 'AMAZON.CancelIntent') {
            handleSessionEndRequest(callback);
        } else {
            throw new Error('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 cleanup logic here
    }
    
    
    // --------------- Main handler -----------------------
    
    // Route the incoming request based on type (LaunchRequest, IntentRequest,
    // etc.) The JSON body of the request is provided in the event parameter.
    exports.handler = (event, context, callback) => {
        try {
            console.log(`event.session.application.applicationId=${event.session.application.applicationId}`);
    
            /**
             * Uncomment this if statement and populate with your skill's application ID to
             * prevent someone else from configuring a skill that sends requests to this function.
             */
            /*
            if (event.session.application.applicationId !== 'amzn1.echo-sdk-ams.app.[unique-value-here]') {
                 callback('Invalid Application ID');
            }
            */
    
            if (event.session.new) {
                onSessionStarted({ requestId: event.request.requestId }, event.session);
            }
    
            if (event.request.type === 'LaunchRequest') {
                onLaunch(event.request,
                    event.session,
                    (sessionAttributes, speechletResponse) => {
                        callback(null, buildResponse(sessionAttributes, speechletResponse));
                    });
            } else if (event.request.type === 'IntentRequest') {
                onIntent(event.request, event.session,
                    (sessionAttributes, speechletResponse) => {
                        callback(null, buildResponse(sessionAttributes, speechletResponse));
                    });
            } else if (event.request.type === 'SessionEndedRequest') {
                onSessionEnded(event.request, event.session);
                callback();
            }
        } catch (err) {
            callback(err);
        }
    };
    
    function callScene(state, entity, callback){
        var data = {"entity_id": getEntity(entity)};
        var postData = JSON.stringify(data);
        
        
        var options = {
          hostname: 'xx.x.xxx.xxx',
          port: 9615,
          path: '/api/services/switch/turn_' + state,
          method: 'POST',
          headers: {
            'Content-Type': 'application/json',
            'x-ha-access': 'your_http_api_password',
            'Content-Length': Buffer.byteLength(postData)
          }
        };
        
        
        const req = http.request(options, (res) => {
            console.log(`STATUS: ${res.statusCode}`);
            console.log(`HEADERS: ${JSON.stringify(res.headers)}`);
            res.setEncoding('utf8');
            res.on('data', (chunk) => {
              console.log(`BODY: ${chunk}`);
            });
            res.on('end', () => {
              console.log('No more data in response.');
              callback();
            });
        });
        
        req.on('error', (e) => {
          console.error(`problem with request: ${e.message}`);
        });
        
        // write data to request body
        req.write(postData);
        req.end();
        
    }
    
    
    
    // This should be your LIST_OF_SCENES entries mapped to the entity_id listed in HomeAssistant
    // For example if i have a scene defined as master_bedroom_lights that will come in to lamda as "master bedroom lights"
    // This maps the scene master bedroom lights to the home assistant entity id we will call to turn on or off.
    function getEntity(entity){
        var entities = {
            "master bedroom lights" : "switch.master_bedroom_lights_switch",
            "garage lights": "switch.outside_garage_lights_switch"
        }
        
        return entities[entity];
        
    }
    
  4. Cool now we need to paste our lamda address that looks like arn:aws:lambda:us-east-1:11122233334444:function:myTest into our alexa skill as the endpoint.

  5. Test your alexa skill by typing and test that your lights turn on and off.

    • When you test within the skill you will just type “turn on the master bedroom lights” because your testing directly against your skill
    • When you speak to alexa you will have to say Alexa ask [invokation name] to turn on the master bedroom lights.
  6. Impress your significant other with your powers.

12 Likes

Thank you! I feel the same way about the monthly fees!

Do you believe there is any way to integrate this without the “[invokation name]”?

1 Like

Yes but i from what I understand so far you have to publish the skill. The problem would be that my code as of now hard codes values against my config. If I could set config for a skill within the Alexa app for a partrticular skill this would be easy. Not quite sure how to get the router is/port config Into the skill yet without writing code in home assistant.

1 Like

You can use the Emulated Hue Component if you don’t want to use the invocation name.

3 Likes

Or there’s Haskaa which has a number of forks that are working on getting it updated to lambda smart home v3 code (which is now the only one available seeing as v2 is obsolete and removed as a choice)

1 Like

It’s been posted a few times on here that you can easily get around the v2 deprecation.

What is the plan with Hue Emulation? It is easy as falling off a log to set up and works without needing the invocation name. Perhaps it’s not verbally perfect because I have to “turn on” a macro, but it has fit my use case so well, I’m left wondering what I would gain to make it worth jumping through the hoops of more complicated setups with HA Cloud or AWS.

In all the discussion of HA Cloud vs Hasska vs whatever, I haven’t seen Hue Emulation mentioned. Is it being depreciated out of Home Assistant?

1 Like

I currently use the emulated hue component because it was easy as pie to set up. Now that I have that set up I’ve been working on making my own intents and custom skill so that way i can say things like unlock the front door instead of turn off the front door, plus I can also get my alexa to read out my sensor values. The only real downside that I can think of for this method is that you have to use the extra invocation name. As far as the emulated hue component getting depreciated goes it’s been saying that for quite some time now. I would suggest using it while you set up your hasska or custom intents

Wait, what? Emulated Hue might be disappearing? I’ve loved using it!! Guess it might be time to look into the Cloud component then :flushed:

Ok so I went back over the emulated hue component docs and it appears to just be one of the configuration options that was removed. They added the new google assistant component so it pretty much just makes it pointless for google home users to use the emulated hue component now.

Phew!! Was getting worried there for a sec :wink:

2 Likes

It has been useless for Google users for ages due to changes Google made, nothing to do with HA.

1 Like

try changing the invocation name for each skill you want.

one of my invocation names is “me the status” so I can say , Alexa, tell me the status of the front door

1 Like

In home assistant my lights (dimmable) are listed as lights and not switches. What would I have to change in the template nodes code to make it work? My programming skills are very limited. Your assistance will be highly appreciated.

just wanted to add that you can now accomplish some of this with emulated hue + routines in the alexa app. let’s say you have script called “unlock the front door.” in the alexa app you can create a routine that maps “unlock the front door” to “turn on unlock the front door to 50% brightness” (they’re all dimmable lights in emulated hue, but the brightness is irrelevant). so you don’t need custom intents to be able to say “alexa, unlock the front door,” and you don’t need an invocation name.

How do I go about resolving the “undefined” response in the _SessionSpeechlet - Requesting Home Assistant to turn undefined on ? The light does switch on and off.

Is the HA Cloud available to run on your own, i.e. personal cloud instance?

Also, I believe with openHAB Alexa skill, they got around the invocation word. I haven’t yet take a close look at how they did it differently than this other that they use HomeKit bindings:

Items are exposed to Alexa through the use of tags which follow the HomeKit binding tagging syntax

Yes you can do it DIY, use the latest v3 api compatible version of Haaska which in essence is just creating your own ‘test’ Smart Home Skill with all the hard work already done by the devs. You then link your ‘test’ skill in the Alexa app and away you go. As it’s a Smart Home Skill there are no invocation words.

Set up in HA is identical to the HA Cloud version other than you don’t put cloud: before alexa: in your config yaml. You shouldn’t need to do anything to the skill side once set up and it will have exactly the same features as the HA Cloud version.

I did it the other night in an hour whilst watching telly and it works perfectly.

3 Likes

How did you set up the config.json? I am trying to set it up using let’s encrypt and duckdns, but every time I run the test in AWS I get a 404 error?

Exactly as per the instructions, url set to https://my_domain.duckdns.org:8123/api

Was a while ago now but I have a feeling I had the same issue until I specifically added something for it to find so you have to set up Home Assistant config something like…

alexa:
  smart_home:
    filter:
      include_domains:
        - light