Announcement

Collapse
No announcement yet.

Amazon Echo - Proxy to HS3 Rest API

Collapse
X
 
  • Filter
  • Time
  • Show
Clear All
new posts

  • Amazon Echo - Proxy to HS3 Rest API

    NEWER BETTER VERSION HERE: http://board.homeseer.com/showthread.php?t=176481


    This is a simple proxy that translates commands given to the Amazon Echo to calls to the HS3 REST API. It allows you to run events or control devices by saying things like:

    Alexa, tell home seer to turn off second floor hallway light
    Alexa, tell home seer to run event goodnight

    Security note: This works by making a http request to your homeseer machine that originates on Amazon's servers. Your HS3 system must be accessible to the web via http. So for you technical people who lock down outside access to just https this won't work until I can figure out how to get a Lambda function to do https to a server with a self signed cert. For you non-technical people, if you can log in to your home seer web interface from outside of home without having to type "https" (the "s" being the important part) at the start, you are good to go.

    If you do not have it as part of your HS3 system already, you will need to install the HS3 REST API script. It can be found through this forum post this thread. Just follow the links there to download the script and drop in it your homeseer install.

    First we must set up a function on Amazon's Lambda service that will serve as the translator between the Echo and your home seer system.
    Go to https://console.aws.amazon.com/lambda/. You will be prompted to sign in. Use your Amazon login. It must be the same one that your echo is registered under. If you have never used any Amazon Web Services before there is registration form you will need to fill out.

    Once signed in you should be on a page that had the heading "Lambda > Functions". From here:
    1. Click the big blue "Create a New Lambda Function" button.
    2. On the next screen click the box marked "alexa-skills-kit-color-expert"
    3. For the step 2 screen, it should already say "alexa skills kit" so just click next.
    4. Now you are on the "configure function" screen. Give you function a name. Any name. For example "HS3Proxy".
    5. In the "Code" box, replace everything in there with the code below under Lambda Function.
    6. Then in the code box replace the placeholders for username, password, and URL with what you use to log in to your homeseer system over the web (i.e. the external URL, not internal ip).
    7. Click the drop down box marked "Role" and select "lambda_basic_execution".
    8. Click next.
    9. On the review screen select "enable" and then click the "create function" button.

    You now have a Lambda function! On the right hand side of the screen you should see something like "ARN - arn:aws:lambda:us-east-1:303234957883:function:HS3Proxy". This is your function's ID that we'll need in a moment. Select and copy it.

    Now we need to set up an Alexa Skill to use this function. To get to where we do that first log into the Amazon Developer Console https://developer.amazon.com/home.html. You'll be prompted to log in again. As above use the Amazon login associated with your Echo and you might need to fill in some extra registration info.

    Once into the console do the following.
    1. In the bar at the top click "Apps and Services".
    2. In the bar that appears below that, click "Alexa"
    3. Click "Add a new skill" and you'll get to a form with four fields
    4. Name - this can be anything as it really doesn't matter, for example "HS3Proxy".
    5. Invocation name - this is the on that matters. It is what you will be saying to give commands. The grammar is "Tell {name} to..." or "Ask {name} to...". So if you call it "home seer" to and say "tell home seer to run goodnight" or "tell home seer to dim second floor hallway light to 30 percent". You can have the phrase be "the house" for "tell the house to..." or if you want a sci-fi feel, put in "HAL" or "Computer". It's up to you and you can change it at any time.
    6. Version. Just enter anything doesn't matter.
    7. Endpoint. Click the radio for "Lambda ARN" and paste in the ID we copied just above (for example "arn:aws:lambda:us-east-1:303234957883:function:HS3Proxy")

    On the next screen there are two fields, Intent Schema and Sample Utterances. Just cut and paste the section of the same name below and click next. Click next again and there's a final page that asks for some info about your skill. None of this matters as you are not going to be publishing this. So just put something in the required fields and click save.

    You are done, however I do recommend doing a little customization of the "sample utterances" section which I will go into below.

    You now can say things like:
    tell home seer to turn on second floor hallway light
    tell home seer to turn second floor hallway light off
    tell home seer to dim first floor den light to 30
    tell home seer to dim first floor den light to 10 percent
    tell home seer to run my event

    For device names you can say just the name of the device, or room and device, or the whole "floor room device" name. If you have multiple devices with the same names and you just say the name it will run that command for all of them.

    On success Alexa just responds "done". In the echo app you will see a card with the command.

    Things you need to paste in:
    Lambda Function
    Code:
    /**
     * HS3 control via HS3 REST api
     */
    
    //To make this work for you, all you have to do is replace the hostname, username, password and port values below to match your homeseer system.
    var username="your username";
    var password="your password";
    var hostname="the domain name of your HomeSeer server";
    var port=80; //replace this with the port number your HS system listenes to
    //that's it, you don't need to change anything else 
    
    //however, if you like here are some optional settings you can customize
    //this controls whether the Echo waits for another command after a successful one, change to true to have it not prompt again after success
    var endSessionOnSuccess=false; 
    
    //what is spoken when the echo is waiting for a command but doesn't get one after a short wait
    var repromptMessage = "Is there anything else I can do for you?";
    
    //response to ending the session by saying "thank you" when prompted for a command
    var endSessionMessage = "You're Welcome";
    var endSessionCardTitle = "Home Seer Finished";
    
    //this is what the Echo will respond with on success
    //you can set multiple options and it will randomly pick one, 
    // for example ["Done","Finished","It is my pleasure to serve you, master","Well what do you know, it worked!"]; 
    var successMessages=["Done"]; 
    
    //what the Echo will say when you just launch the app with no command
    var launchPrompt = "Please say a command."; 
    var launchCardTitle = "HomeSeer Lambda Proxy Running."; 
    
    //messages for when it doesn't understand what device you said
    var deviceNotFoundMessage = "Can't find that device, please say again."; 
    
    //if you are receiving a message about all failed but things actually work, add "All_Failed" to this array
    var successValues = ["True","All_Success","Some_Failed"]; 
    
    /* Here you can set alternate names for your devices. Here's an example. 
    var deviceNameAliases = {
    	"Alias" : "Real Name",
    	"TV Room Lights" : "Den Lights",
    	"Garage Sensor" : "Garage Motion Sensor" 
    }
    For non-programmers, notice the comma at the end of every line but the last. If you see a red 'x' next to this when you change this that's probably the problem.
    Use lower case. 
    */
    var deviceNameAliases = {
    };
    
    // Here you can set alternate event names, see the description of alternate device names above
    var eventNameAliases = {
    };
    //end of customizable variables
    
    //lots of constants
    var httpOrHttps = "http"; //currently I can't get this to work with a self-signed https certificate, but one day!
    var restPath="/HomeSeer_REST_API.aspx";
    var deviceStatusIntentName="DeviceStatusIntent";
    var launchRequestName = "LaunchRequest";
    var intentRequestName = "IntentRequest";
    var sessionEndedRequestName = "SessionEndedRequest";
    var runEventIntentName = "RunEventIntent";
    var deviceChangeIntentName = "DeviceChangeIntent";
    var finishedIntentName = "FinishedIntent";
    var unrecognizedIntentErrorMsg = "Unrecognized intent";
    var speechType = "PlainText";
    var cardType = "Simple";
    var askVersion = "1.0";
    var launchCardTitle = "HomeSeer Proxy Running.";
    var failDeviceNotFoundValues = ["Indeterminate"];
    var failEventNotFoundValues = ["False"];
    
    // 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 {
            if (event.session.new) {
                onSessionStarted({requestId: event.request.requestId}, event.session);
            }
    
            if (event.request.type === launchRequestName) {
                onLaunch(event.request,
                         event.session,
                         function callback(sessionAttributes, speechletResponse) {
                            context.succeed(buildResponse(sessionAttributes, speechletResponse));
                         });
            }  else if (event.request.type === intentRequestName) {
                onIntent(event.request,
                         event.session,
                         function callback(sessionAttributes, speechletResponse) {
                             context.succeed(buildResponse(sessionAttributes, speechletResponse));
                         });
            } else if (event.request.type === sessionEndedRequestName) {
                onSessionEnded(event.request, event.session);
                context.succeed();
            }
        } catch (e) {
            context.fail("Exception: " + e);
        }
    };
    
    function onSessionStarted(sessionStartedRequest, session) {
        // this is from the sample, I'm not using any session variables but I left it in for future use
    }
    
    function onSessionEnded(sessionEndedRequest, session) {
        // this is from the sample, I'm not using any session variables but I left it in for future use
    }
    
    function onLaunch(launchRequest, session, callback) {
        getWelcomeResponse(callback);
    }
    
    function onIntent(intentRequest, session, callback) {
        var intent = intentRequest.intent,
            intentName = intentRequest.intent.name;
    
    	if (runEventIntentName == intentName ) {
    		runEvent(intent, session, callback);
    	} else if (deviceChangeIntentName == intentName ) {
        	setDevice(intent, session, callback);
    	} else if (deviceStatusIntentName == intentName ) {
        	getDeviceStatus(intent, session, callback);
    	} else if (	finishedIntentName == intentName ) {
        	getFinishedResponse(intent, session, callback);
        } else {
            throw unrecognizedIntentErrorMsg;
        }
    }
    
    function buildSpeechletResponse(title, output, repromptText, shouldEndSession) {
        return {
            outputSpeech: {
                type: speechType,
                text: output
            },
            card: {
                type: cardType,
                title: title,
                content: output
            },
            reprompt: {
                outputSpeech: {
                    type: speechType,
                    text: repromptText
                }
            },
            shouldEndSession: shouldEndSession
        };
    }
    
    function buildResponse(sessionAttributes, speechletResponse) {
        return {
            version: askVersion,
            sessionAttributes: sessionAttributes,
            response: speechletResponse
        };
    }
    
    function getWelcomeResponse(callback) {
        var sessionAttributes = {};
        var cardTitle = launchCardTitle;
        var speechOutput = launchPrompt;
        var repromptText = "";
        var shouldEndSession = false;
    
        callback(sessionAttributes, buildSpeechletResponse(cardTitle, speechOutput, repromptText, shouldEndSession));
    }
    
    function getFinishedResponse(intent, session, callback) {
        var sessionAttributes = {};
        var cardTitle = endSessionCardTitle;
        var speechOutput = endSessionMessage;
        var repromptText = "";
        var shouldEndSession = true;
    
        callback(sessionAttributes, buildSpeechletResponse(cardTitle, speechOutput, repromptText, shouldEndSession));
    }
    
    
    function setDevice(intent, session, callback) {
    	var device = getDeviceName(intent.slots.Device.value);
    
        //get what we are setting this to, on, off or a dim value
    	var setting = "";
    	if (intent.slots.Dim && intent.slots.Dim.value ) {
    		setting = "Dim "+intent.slots.Dim.value+"%";
    	} else if (intent.slots.OnOff && intent.slots.OnOff.value ) {
    		setting = intent.slots.OnOff.value;		    
    	}
    	
    	var description = ("Setting "+device+" to "+setting);
    	
    	//form the HS3 REST URL from the device name and setting
    	var path = restPath+"?function=setdevicebyname&param1="+device+"&param2="+setting;
    
    	//call helper function that makes the actual request
    	makeRequest(intent,path,description,callback);
    }
    
    function getDeviceStatus(intent, session, callback) {
    	var device = getDeviceName(intent.slots.Device.value);
    
    	var description = ("Getting status of  "+device+".");
    	
    	//form the HS3 REST URL from the device name and setting
    	var path = restPath+"?function=getdevicestatusvaluebyname&param1="+device+"&param2=label";
    
    	//call helper function that makes the actual request
    	makeRequest(intent,path,description,callback);
    }
    
    function runEvent(intent, session, callback) {
    	var eventName = getEventName(intent.slots.EventName.value);
    	var description = "Run Event "+eventName;
    	var path = restPath+"?function=execevent&param1="+eventName;
    	makeRequest(intent, path,description,callback);
    }
    
    //this function makes the actual HS3 api call, parses and forms the response.
    function makeRequest( intent, path, description, callback) {
    	var options = {
    		host: hostname,
    		port: port,
    		path: path,
    		headers: {"Authorization": "Basic " + new Buffer(username + ":" + password).toString("base64")}         
    	};
    	console.log(description);
    	console.log("Requesting: "+options.path);
    
        //initialize values that will be passed into the response
        var sessionAttributes = {};
        var repromptText = repromptMessage;
        var shouldEndSession = endSessionOnSuccess;
        var speechOutput = "";
    	var body = "";
    	var cardTitle = description;		
    	
    	var connection=require(httpOrHttps);
    
    	request = connection.get(options, function(res){
    		res.on("data", function(data) {
    			body += data;
    		});
    		res.on("end", function() {
    			console.log("response body"+body);
    			//parse the response from homeseer and form the Echo Response accordingly.
    			
    			//the rest API return "true" on a successful run event and "All_success" for a successful device change
    			if( successValues.indexOf(body)>=0 ) {
    				speechOutput = getSuccessMessage();
    			//a failed device change that fails because it can't find the device that was named returns "Indeterminate"
    			} else if (failDeviceNotFoundValues.indexOf(body)>=0) {
    			    speechOutput = deviceNotFoundMessage;
    			    shouldEndSession = false;
    			//when the event can't be found the return value is  false
    			} else if (failEventNotFoundValues.indexOf(body)>=0) {
    				speechOutput = description + " not found.";
    			    shouldEndSession = false;
    			} else if (deviceStatusIntentName==intent.name && body !== "") {
    				//intentionally returning what was spoken rather than the real name in the case of an alias
    				//seems less confusing to the user that way, thus better for WAF
    		        speechOutput = intent.slots.Device.value + " is currently "+body; 
    			} else if (deviceStatusIntentName==intent.name && body === "") {
    		        speechOutput = deviceNotFoundMessage;
    			} else {
    				speechOutput = "There was a problem. "+description+ " returned " + body;
    			}
    			callback(sessionAttributes, buildSpeechletResponse(cardTitle, speechOutput, repromptText, shouldEndSession));
    		});
    		res.on("error", function(e) {
    			console.log("Got error: " + e.message);
    			speechOutput = "There was a problem, "+e.message;
    			callback(sessionAttributes, buildSpeechletResponse(cardTitle, speechOutput, repromptText, shouldEndSession));
    		});
    	});		
    	request.end();
    }
    
    //if there are multiple success values pick one at random
    function getSuccessMessage() {
    	if (successMessages.length>1) {
    		return successMessages[Math.floor(Math.random()*successMessages.length)];
    	} else {
    		return successMessages[0];
    	}
    }
    
    //resolves the real device name from what is passed in.
    function getDeviceName(givenName) {
    	if (givenName in deviceNameAliases) {
    		return deviceNameAliases[givenName];
    	} else {
    		return givenName;
    	}
    }
    
    //resolves the real event name from what is passed in.
    function getEventName(givenName) {
    	if (givenName in eventNameAliases) {
    		return eventNameAliases[givenName];
    	} else {
    		return givenName;
    	}
    }
    Intent Schema
    Code:
    {
      "intents": [
      {
          "intent": "RunEventIntent",
          "slots": [
            {
              "name": "EventName",
              "type": "LITERAL"
            }
          ]
        },
        {
          "intent": "DeviceStatusIntent",
          "slots": [
            {
              "name": "Device",
              "type": "LITERAL"
            }
          ]
        }, 
        {
          "intent": "DeviceChangeIntent",
          "slots": [
            {
              "name": "Device",
              "type": "LITERAL"
            },
            {
              "name": "OnOff",
              "type": "LITERAL"
            },
            {
              "name": "Dim",
              "type": "NUMBER"
            }
          ]
        }, 
        {
          "intent": "FinishedIntent",
          "slots": [
          ]
        }
     ]
    }
    Sample Utterances
    Code:
    FinishedIntent No
    FinishedIntent No Thank You
    FinishedIntent Thank You
    
    DeviceStatusIntent get the status of {office lights|Device}
    DeviceStatusIntent get the status of {den temperature|Device}
    DeviceStatusIntent what is the status of {garage door sensor|Device}
    
    RunEventIntent Run Event {some event name|EventName}
    RunEventIntent Run {some event name|EventName}
    RunEventIntent {some event name|EventName}
    
    DeviceChangeIntent turn {on|OnOff} {First Floor Kitchen lights|Device}
    DeviceChangeIntent turn {off|OnOff} {First Floor Kitchen lights|Device}
    DeviceChangeIntent turn {First Floor Kitchen lights|Device} {off|OnOff} 
    DeviceChangeIntent turn {First Floor Kitchen lights|Device} {on|OnOff} 
    DeviceChangeIntent Dim {First Floor Kitchen lights|Device} to {fifty|Dim}
    DeviceChangeIntent Dim {First Floor Kitchen lights|Device} to {fifty|Dim} percent
    Note on sample utterances: The sample utterances serve as generic examples that the Echo will try to match up whatever you say to. They also serve as specific examples of exact phrases that the Echo will listen for. So while you don't need to add anything, it helps.

    You can start with just the basics listed in the example and speak some typical commands to the Echo and see how it responds. For any event or device names it seems to have trouble with, add utterances for them. If you have an event name that overlaps with the device grammar, such as "turn on all lights", definitely enter that as a specific utterance otherwise it will parse it out as a device named "all lights".

    You can also add your own grammar. In my example to run an event I have a few variations. I can say "tell home seer to run {event name}" or "tell home seer to run event {event name}" or just "tell home seer to {event name}". You can add your own if you want to say something other than "run".

    So, if for example you have an event name "Movie Time", and you want to be able to say either "run Movie Time" or "make it movie Time". You can add the following:
    RunEventIntent Run {Movie Time|EventName}
    RunEventIntent Make it {Movie Time|EventName}

    Or say you have an event "Set Alarm" and you don't need an extra verb in your phrase you can make it just "Set Alarm".
    RunEventIntent {Set Alarm|EventName}

    Edits:
    8/15/15(again) Added mappings for both event and device names. Meaning you can set aliases for your devices and events. You can use this if you want to have a different spoken name for your device or event, or if you want to have multiple names for it.

    8/15/15 Made the function easily customizable. Now by changing a few more variable at the top you can control the various messages that the echo responds with on. For fun I also added the ability to set multiple success messages one of which will be picked at random.
    A fix was made to account for devices with unsettable children, like a light switch that has an energy useage child. Those should no longer be responding with an error about "some_failed".
    For anyone with the issue where the command works, but it says "all_failed" you can fix that as well with a small edit.

    7/15/15 Now you can ask your Echo for the status of devices! Update your lambda function and intent schema from the updated code, and some the "DeviceStatusIntent" sample utterances based on the examples. There is one catch that can be fixed with a find a replace in the HS3 REST API. Please see this post in the thread about the rest API.

    7/14/15 Updated Lambda function to improve the error responses and remove a lot of debug logging. The Echo will re-prompt if it can't find the event or device it heard. Remember that you don't need to say the "tell home seer to" on re-prompts.

    I realized that the rest API set device by name function will take not only the full "floor room name" function but any combination and if what you say matches multiple devices, it will set them all. So this is good if you, for example, name all the overhead lights "lights" in each room you can control them all by just saying "lights" or specific ones by just room and light "garage lights", "office lights" etc. This might also be bad if it causes devices you didn't intend to activate like maybe a garage door. So take care naming your devices.

    To answer a question from the thread:
    Yes, you can just leave this running indefinitely in developer mode and never publish it.
    Last edited by Thrag; September 1st, 2015, 11:27 AM. Reason: More, better, customizable

  • #2
    Originally posted by Thrag View Post
    This is a simple proxy that translates commands given to the Amazon Echo to calls to the HS3 REST API. It allows you to run events or control devices by saying things like:

    Alexa, tell home seer to turn off second floor hallway light
    Alexa, tell home seer to run event goodnight

    Security note: This works by making a http request to your homeseer machine that originates on Amazon's servers. Your HS3 system must be accessible to the web via http. So for you technical people who lock down outside access to just https this won't work until I can figure out how to get a Lambda function to do https to a server with a self signed cert. For you non-technical people, if you can log in to your home seer web interface from outside of home without having to type "https" (the "s" being the important part) at the start, you are good to go.

    If you do not have it as part of your HS3 system already, you will need to install the HS3 REST API script. It can be found through this forum post this thread. Just follow the links there to download the script and drop in it your homeseer install.

    First we must set up a function on Amazon's Lambda service that will serve as the translator between the Echo and your home seer system.
    Go to https://console.aws.amazon.com/lambda/. You will be prompted to sign in. Use your Amazon login. It must be the same one that your echo is registered under. If you have never used any Amazon Web Services before there is registration form you will need to fill out.

    Once signed in you should be on a page that had the heading "Lambda > Functions". From here:
    1. Click the big blue "Create a New Lambda Function" button.
    2. On the next screen click the box marked "alexa-skills-kit-color-expert"
    3. For the step 2 screen, it should already say "alexa skills kit" so just click next.
    4. Now you are on the "configure function" screen. Give you function a name. Any name. For example "HS3Proxy".
    5. In the "Code" box, replace everything in there with the code below under Lambda Function.
    6. Then in the code box replace the placeholders for username, password, and URL with what you use to log in to your homeseer system over the web (i.e. the external URL, not internal ip).
    7. Click the drop down box marked "Role" and select "lambda_basic_execution".
    8. Click next.
    9. On the review screen select "enable" and then click the "create function" button.

    You now have a Lambda function! On the right hand side of the screen you should see something like "ARN - arn:aws:lambda:us-east-1:303234957883:function:HS3Proxy". This is your function's ID that we'll need in a moment. Select and copy it.

    Now we need to set up an Alexa Skill to use this function. To get to where we do that first log into the Amazon Developer Console https://developer.amazon.com/home.html. You'll be prompted to log in again. As above use the Amazon login associated with your Echo and you might need to fill in some extra registration info.

    Once into the console do the following.
    1. In the bar at the top click "Apps and Services".
    2. In the bar that appears below that, click "Alexa"
    3. Click "Add a new skill" and you'll get to a form with four fields
    4. Name - this can be anything as it really doesn't matter, for example "HS3Proxy".
    5. Invocation name - this is the on that matters. It is what you will be saying to give commands. The grammar is "Tell {name} to..." or "Ask {name} to...". So if you call it "home seer" to and say "tell home seer to run goodnight" or "tell home seer to dim second floor hallway light to 30 percent". You can have the phrase be "the house" for "tell the house to..." or if you want a sci-fi feel, put in "HAL" or "Computer". It's up to you and you can change it at any time.
    6. Version. Just enter anything doesn't matter.
    7. Endpoint. Click the radio for "Lambda ARN" and paste in the ID we copied just above (for example "arn:aws:lambda:us-east-1:303234957883:function:HS3Proxy")

    On the next screen there are two fields, Intent Schema and Sample Utterances. Just cut and paste the section of the same name below and click next. Click next again and there's a final page that asks for some info about your skill. None of this matters as you are not going to be publishing this. So just put something in the required fields and click save.

    You are done, however I do recommend doing a little customization of the "sample utterances" section which I will go into below.

    You now can say things like:
    tell home seer to turn on second floor hallway light
    tell home seer to turn second floor hallway light off
    tell home seer to dim first floor den light to 30
    tell home seer to dim first floor den light to 10 percent
    tell home seer to run my event

    For device names it wants the full "floor room device" name. Events are just the name of the event.

    On success Alexa just responds "done". In the echo app you will see a card with the command.

    Things you need to paste in:
    Lambda Function
    Code:
    //To make this work for you, all you have to do is replace the hostname, username, password and port values below to match your homeseer system.
    var username="your username";
    var password="your password";
    var hostname="the domain name of your HomeSeer server";
    var port=80; //replace this with the port number your HS system listenes to
    //That's it. You're done. 
    
    var restPath="/HomeSeer_REST_API.aspx";
    
    // 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 {
            console.log("event.session.application=" + event.session.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) {
    						console.log("reached final callback")
                            context.succeed(buildResponse(sessionAttributes, speechletResponse));
                         });
            }  else if (event.request.type === "IntentRequest") {
                onIntent(event.request,
                         event.session,
                         function callback(sessionAttributes, speechletResponse) {
    						console.log("reached final callback")
                             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);
        // this is from the sample, I'm not using any session variables but I left it in for future use
    }
    
    /**
     * Called when the user launches the app without specifying what they want.
     */
    function onLaunch(launchRequest, session, callback) {
        console.log("onLaunch requestId=" + launchRequest.requestId
                    + ", sessionId=" + session.sessionId);
    
        getWelcomeResponse(callback);
    }
    
    /** 
     * Called when the user specifies an intent for this application.
     */
    function onIntent(intentRequest, session, callback) {
        console.log("onIntent requestId=" + intentRequest.requestId
                    + ", sessionId=" + session.sessionId);
    
        var intent = intentRequest.intent,
            intentName = intentRequest.intent.name;
    
    	if ("RunEventIntent" == intentName ) {
    		runEvent(intent, session, callback);
    	} else if ("DeviceChangeIntent" == intentName ) {
        	setDevice(intent, session, callback);
        } else {
            throw "Unrecognized intent";
        }
    }
    
    /**
     * Called when the user ends the session.
     * Is not called when the app returns shouldEndSession=true.
     */
    function onSessionEnded(sessionEndedRequest, session) {
        console.log("onSessionEnded requestId=" + sessionEndedRequest.requestId
                    + ", sessionId=" + session.sessionId);
        // this is from the sample, I'm not using any session variables but I left it in for future use
    }
    
    /**
     * 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: shouldEndSession
        };
    }
    
    function buildResponse(sessionAttributes, speechletResponse) {
        return {
            version: "1.0",
            sessionAttributes: sessionAttributes,
            response: speechletResponse
        };
    }
    
    /** 
     * Functions that control the app's behavior.
     */
    function getWelcomeResponse(callback) {
        // 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 Home Seer";
        // 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.
        var repromptText = "";
        var shouldEndSession = false;
    
        callback(sessionAttributes, buildSpeechletResponse(cardTitle, speechOutput, repromptText, shouldEndSession));
    }
    
    //function to set device value
    function setDevice(intent, session, callback) {
    	//parse the request
        //get Device Name	
    	var device = intent.slots.Device.value;
    	console.log("Device: "+device);
    
        //get what we are setting this to, on, off or a dim value
    	var setting = "";
    	if (intent.slots.Dim && intent.slots.Dim.value ) {
    		setting = "Dim "+intent.slots.Dim.value+"%";
    	} else if (intent.slots.OnOff && intent.slots.OnOff.value ) {
    		setting = intent.slots.OnOff.value;		    
    	}
    	var description = ("Setting "+device+" to "+setting);
    	var path = restPath+"?function=setdevicebyname&param1="+device+"&param2="+setting;
    
    	var cardTitle = "Home Control: Setting "+device+" to "+setting;
    
    	//call helper function that makes the actual request
    	makeRequest(path,description,callback);
    }
    
    // Function to run events
    function runEvent(intent, session, callback) {
    	//parse out event name
    	var eventName = intent.slots.EventName.value;
    	var cardTitle = "Home Control: Run Event "+eventName;
    	var description = "Run Event "+eventName;
    
    	//form path to rest api
    	var path = restPath+"?function=execevent&param1="+eventName;
    	//call helper function that makes the actual request
    	makeRequest(path,description,callback);
    }
    
    //this function makes the actual HS3 api call
    function makeRequest( path, description, callback) {
    	var options = {
    		host: hostname,
    		port: port,
    		path: path,
    		headers: {"Authorization": "Basic " + new Buffer(username + ":" + password).toString("base64")}         
    	};
    	console.log(description);
    	console.log("Requesting: "+options.path);
    
        var repromptText = null;
        var sessionAttributes = {};
        var shouldEndSession = true;
        var speechOutput = "";
    	var body = "";
    	var cardTitle = "Home Seer: "+description;		
    	
    	var connection=require("http");
    	//var connection=require("https"); //I can't seem to make https work
    
    	request = connection.get(options, function(res){
    		res.on("data", function(data) {
    			body += data;
    		});
    		res.on("end", function() {
    			console.log(body);
    			if( "True" == body || "All_Success"==body) {
    				speechOutput = "Done";
    			} else {
    				speechOutput = "There was a problem. "+description+ " returned " + body;
    			}
    			console.log("making callback");
    			callback(sessionAttributes, buildSpeechletResponse(cardTitle, speechOutput, repromptText, shouldEndSession));
    		});
    		res.on("error", function(e) {
    			console.log("Got error: " + e.message);
    			speechOutput = "There was a problem, "+e.message;
    			callback(sessionAttributes, buildSpeechletResponse(cardTitle, speechOutput, repromptText, shouldEndSession));
    		});
    	});		
    	request.end();
    }
    Intent Schema
    Code:
    {
      "intents": [
      {
          "intent": "RunEventIntent",
          "slots": [
            {
              "name": "EventName",
              "type": "LITERAL"
            }
          ]
        },
        {
          "intent": "DeviceChangeIntent",
          "slots": [
            {
              "name": "Device",
              "type": "LITERAL"
            },
            {
              "name": "OnOff",
              "type": "LITERAL"
            },
            {
              "name": "Dim",
              "type": "NUMBER"
            }
          ]
        }
     ]
    }
    Sample Utterances
    Code:
    RunEventIntent Run Event {some event name|EventName}
    RunEventIntent Run {some event name|EventName}
    RunEventIntent {some event name|EventName}
    
    DeviceChangeIntent turn {on|OnOff} {First Floor Kitchen lights|Device}
    DeviceChangeIntent turn {off|OnOff} {First Floor Kitchen lights|Device}
    DeviceChangeIntent turn {First Floor Kitchen lights|Device} {off|OnOff} 
    DeviceChangeIntent turn {First Floor Kitchen lights|Device} {on|OnOff} 
    DeviceChangeIntent Dim {First Floor Kitchen lights|Device} to {fifty|Dim}
    DeviceChangeIntent Dim {First Floor Kitchen lights|Device} to {fifty|Dim} percent
    Note on sample utterances: The sample utterances serve as generic examples that the Echo will try to match up whatever you say to. They also serve as specific examples of exact phrases that the Echo will listen for. So while you don't need to add anything, it helps.

    You can start with just the basics listed in the example and speak some typical commands to the Echo and see how it responds. For any event or device names it seems to have trouble with, add utterances for them. If you have an event name that overlaps with the device grammar, such as "turn on all lights", definitely enter that as a specific utterance otherwise it will parse it out as a device named "all lights".

    You can also add your own grammar. In my example to run an event I have a few variations. I can say "tell home seer to run {event name}" or "tell home seer to run event {event name}" or just "tell home seer to {event name}". You can add your own if you want to say something other than "run".

    So, if for example you have an event name "Movie Time", and you want to be able to say either "run Movie Time" or "make it movie Time". You can add the following:
    RunEventIntent Run {Movie Time|EventName}
    RunEventIntent Make it {Movie Time|EventName}

    Or say you have an event "Set Alarm" and you don't need an extra verb in your phrase you can make it just "Set Alarm".
    RunEventIntent {Set Alarm|EventName}
    thank you! I am definitely going to have to try this now as well !
    cheeryfool

    Comment


    • #3
      This works, nice instructions!

      I did find I had to change my region to us-east (us-west was not an available region for Alexa Skills yet).

      Now I have to work on the Utterances. First time I asked Alexa "Ask Hal to turn on zwave garage light", the garage door (not even zwave) opened, yikes!

      So am I correct in presuming it's ok to leave long term as development mode? Obviously I don't want to have Amazon publish this.
      Attached Files
      Mike

      Comment

      Working...
      X