Hardware components | ||||||
![]() |
| × | 1 | |||
![]() |
| × | 1 | |||
![]() |
| × | 2 | |||
| × | 1 | ||||
| × | 1 |
I was looking around for Xmas gift ideas and thought someone must make a ioT garage door opener and after some looking I found some but the price tags seemed outrageous. Another quick search for building my own landed me on this site with this project.
It was a great project but I really wanted to make it without using the relay and with a custom Alexa skill. IFTTT is a great app but it is pretty slow and I wanted to do a custom Alexa skill to bypass IFTTT when using the Amazon Echo.
If you've never created an Alexa skill, it's not very complicated but does have a few steps to go through to get it all set up. Once you understand how it is put together it should go pretty quickly. Here is a great place to get started. The tutorial will walk you through the steps. You'll need a free Amazon account.
AmazonSign into the developer portal with your amazon credentials. This is where you'll set up the structure of your skill.
The other piece of the skill is the code itself. You'll use an Amazon Web Service called AWS Lambda to do this. Sign into the console with your Amazon account info. Here is a tutorial for creating the Lambda function.
The Alexa code I've supplied is only meant for use on your own device. There are many more things you need to do to make it a publishable Alexa skill but this code works fine for home use and testing.
ProgrammingI've included code for both the Particle Photon and the Lambda function that work together. Using my code you'll need to upload the zip file into you Lambda function because it includes the Particle-js-api
which is used in the function. The project liked at the beginning does a good job of walking through setting up a project with the Particle IDE if you've never done that.
bool isOpen();
void toggleRelay();
int toggleDoor(String command);
int openDoor(String command);
int closeDoor(String command);
int doorStatus(String command);
//pin initializations
const int pinReedSensor = A0;
const int hotPin = D0;
const int onPin = D3;
const char* STATUS_MESSAGE_UNAVAILABLE = "Status unavailable.";
const char* STATUS_MESSAGE_CLOSED = "Closed";
const char* STATUS_MESSAGE_OPEN = "Open";
const char* doorPosition = STATUS_MESSAGE_UNAVAILABLE;
void setup() {
pinMode(pinReedSensor, INPUT);
pinMode(hotPin, OUTPUT);
pinMode(onPin, OUTPUT);
digitalWrite(hotPin, HIGH);
digitalWrite(onPin, LOW);
Particle.function("toggleDoor", toggleDoor);
Particle.function("checkStatus", doorStatus);
Particle.variable("doorPostion", doorPosition, STRING);
}
void loop() {
const char* message;
if(digitalRead(pinReedSensor) == 0){
message = STATUS_MESSAGE_CLOSED;
}else if(digitalRead(pinReedSensor) == 1){
message = STATUS_MESSAGE_OPEN;
}else{
message = STATUS_MESSAGE_UNAVAILABLE;
}
//keep set so Alexa can check upon GET call
Particle.variable("doorPosition", message, STRING);
}
bool isOpen(){
return digitalRead(pinReedSensor);
}
//public funcion called by DO BUTTON which causes new event to
//be published so IFTTT can respond
int doorStatus(String command){
String status;
int result = -1;
if(!isOpen()){
result = 0;
status = STATUS_MESSAGE_CLOSED;
}
else if(isOpen()){
result = 1;
status = STATUS_MESSAGE_OPEN;
}
//publish variable IFTTT listens for new event published and sends text message
Particle.publish("thePosition", status);
return result;
}
//reed sensnsor returns 0 for closed door and 1 for open door
int openDoor(String command){
int result = 1;
//door is closed, open it
if(!isOpen()){
toggleRelay();
}else{
result = -1;
}
return result;
}
int closeDoor(String command){
int result = 1;
if(isOpen()){
toggleRelay();
}else{
result = -1;
}
return result;
}
//public function called by Alexa or DO BUTTON
//use two DO BUTTON calls. open or close. if you wanted you could expose toggle relay
//and have DO BUTTON call that.
//alexa checks for return value for successful completion of task.
//this makes it a button with logic rather than just a toggle.
//won't "press" the button if user asks to open door and it's already open.
int toggleDoor(String command){
int result = 0;
if(command == "open"){
result = openDoor("none");
}
else if(command == "close"){
result = closeDoor("none");
}
return result;
}
void toggleRelay(){
digitalWrite(onPin, HIGH);
delay(500);
digitalWrite(onPin, LOW);
}
Alexa code
JavaScriptThis code isn't meant to be published. It is only suitable for personal use and testing. It does not meet all the requirements of a finished Alexa Skill.
'use strict';
var https = require('https');
var particleServer = "api.particle.io";
var particlePath = "/v1/devices/";
const deviceID = 'your particle device ID number';
const token = 'your particle token number';
//var myContext;
const Particle = require('particle-api-js');
var particle = new Particle();
exports.handler = function (event, context) {
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]") {
context.fail("Invalid Application ID");
}/
*/
// myContext = context;
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);
}
//Called when the user launches the skill without specifying what they want.
function onLaunch(launchRequest, session, callback) {
getWelcomeResponse(callback);
}
/**
* Called when the user specifies an intent for this skill.
*/
function onIntent(intentRequest, session, callback) {
var intent = intentRequest.intent,
intentName = intentRequest.intent.name;
// Dispatch to your skill's intent handlers
if("BasicIntent" == intentName){
getWelcomeResponse(callback);
}
else if ("ToggleIntent" === intentName) {
setToggleIntent(intent, session, callback);
}else if ("CheckStatusIntent" === intentName) {
setCheckStatusIntent(intent, session, callback);
}else if ("ReplyIntent" === intentName) {
replyIntent(intent, session, callback);
}
else if ("AMAZON.HelpIntent" === intentName) {
getHelp(callback);
} else if ("AMAZON.StopIntent" === intentName || "AMAZON.CancelIntent" === intentName) {
handleSessionEndRequest(callback);
} else {
throw "Invalid intent";
}
}
function onSessionEnded(sessionEndedRequest, session) {
// Add cleanup logic here
}
// --------------- Functions that control the skill's behavior -----------------------
function getHelp(callback){
var cardTitle = '';
var speechOutput = "Welcome to Door Remote. You can ask me to open or close the door. You can also check the status of the door.";
var repromptText = "Welcome to Door Remote. You can ask me to open or close the door. You can also check the status of the door";
var shouldEndSession = false;
var sessionAttributes = {};
callback(sessionAttributes,
buildSpeechletResponse(cardTitle, speechOutput, repromptText, shouldEndSession));
}
function getWelcomeResponse(callback) {
// If we wanted to initialize the session to have some attributes we could add those here.
var sessionAttributes = {};
var cardTitle = "Welcome to Door Remote";
var speechOutput = "Welcome to Door Remote. You can ask me to open or close the door. You can also check the status of the door";
var repromptText = "I can control your garage door. " +
"Say something like, close the garage door or, is my garage door open?";
var shouldEndSession = false;
callback(sessionAttributes,
buildSpeechletResponse(cardTitle, speechOutput, repromptText, shouldEndSession));
}
function handleSessionEndRequest(callback) {
var cardTitle = "Goodbye";
var speechOutput = "";
var shouldEndSession = true;
callback({}, buildSpeechletResponse(cardTitle, speechOutput, null, shouldEndSession));
}
function replyIntent(intent, session, callback){
var cardTitle = '';
var repromptText = "";
var sessionAttributes = {};
var shouldEndSession = false;
var speechOutput = 'OK';
var theResponse = intent.slots.reply.value;
console.log(theResponse);
if(theResponse == 'yes'){
getParticleDoorStatus(function(result){
console.log('result in function is: ' + result);
//the door is open so send close argument to function
if(result == 'Open'){
console.log('the result is open');
postParticleInfo(function (callback){
console.log('reply open function fired');
},'close');
}
if(result =='Closed'){
postParticleInfo(function (callback){
console.log('reply closed function fired');
},'open');
}
callback(sessionAttributes, buildSpeechletResponse(cardTitle, speechOutput, repromptText, shouldEndSession));
});
}
}
function setToggleIntent(intent, session, callback){
var cardTitle = '';
var repromptText = "";
var sessionAttributes = {};
var speechOutput = '';
var argument = intent.slots.position.value;
var shouldEndSession = true;
postParticleInfo(function (result){
var speechOutput = '';
var verb = '';
//argument is the voice command from user, either open or close
//change the verb for proper respnse wording
if(argument =="close"){
verb = 'closed';
}
else if(argument == "open"){
verb = 'opened';
}
//result is from Particle.function
if(result == 1){
speechOutput = "Ok, I " + verb + " the door.";
}else if(result == -1){
speechOutput = "The door is already " + verb;
}
callback(sessionAttributes, buildSpeechletResponse(cardTitle, speechOutput, repromptText, shouldEndSession));
},argument);
}
function setCheckStatusIntent(intent, session, callback){
var cardTitle = '';
var repromptText = "";
var sessionAttributes = {};
var verb = '';
getParticleDoorStatus(function (result){
console.log('the object is: ' + result);
//set verb for reply sentance
if(result == 'Closed'){
verb = 'open';
}
else if(result == 'Open'){
verb = 'close';
}
var speechOutput = "The Door is " + result + '. Would you like me to ' + verb + ' it for you?';
// repromptText = "I can " + verb + " the door for you. Say " + verb + " the door."
var shouldEndSession = false;
callback(sessionAttributes,
buildSpeechletResponse(cardTitle, speechOutput, repromptText, shouldEndSession));
});
}
// post and get functions need the Particle-api-js libary included in zip file
function postParticleInfo(callback, arg){
//console.log('postParticleInfo Function is: '+ arg);
var fnPr = particle.callFunction({ deviceId: deviceID, name: 'toggleDoor', argument: arg, auth: token });
fnPr.then(
function(data) {
//console.log('Post Function called succesfully:', data);
callback(data.body.return_value);
}, function(err) {
console.log('An error occurred:', err);
});
}
function getParticleDoorStatus(callback) {
particle.getVariable({ deviceId: deviceID, name: 'doorPosition', auth: token }).then(function(data) {
//console.log('Device variable retrieved successfully:', data);
//returns the string value of Particle variable
callback(data.body.result);
}, function(err) {
console.log('An error occurred while getting attrs:', err);
});
}
function buildSpeechletResponse(title, output, repromptText, shouldEndSession) {
return {
outputSpeech: {
type: "PlainText",
text: output
},
card: {
type: "Simple",
title: "Garage Door - " + title,
content: output
},
reprompt: {
outputSpeech: {
type: "PlainText",
text: repromptText
}
},
shouldEndSession: shouldEndSession
};
}
function buildResponse(sessionAttributes, speechletResponse) {
return {
version: "1.0",
sessionAttributes: sessionAttributes,
response: speechletResponse
};
}
{
"intents": [
{
"intent": "ToggleIntent",
"slots": [{
"name": "position",
"type": "DOOR_POSITIONS"
}
]
},
{
"intent": "ReplyIntent",
"slots": [{
"name": "reply",
"type": "ANSWER"
}
]
},
{
"intent": "CheckStatusIntent",
"slots": [{
"name": "position",
"type": "DOOR_POSITIONS"
}]
},
{
"intent": "BasicIntent"
},
{
"intent": "AMAZON.StopIntent"
},
{
"intent": "AMAZON.CancelIntent"
},
{
"intent": "AMAZON.HelpIntent"
}
]
}
ToggleIntent {position} my garage door
ToggleIntent {position} the garage door
ToggleIntent {position} garage door
ToggleIntent {position} garage
ToggleIntent {position} door
ToggleIntent {position} the door
ToggleIntent {position} the garage door
ToggleIntent {position} my garage
ToggleIntent {position} my door
ToggleIntent {position}
CheckStatusIntent is my garage door {position}
CheckStatusIntent is my garage {position}
CheckStatusIntent is my door {position}
CheckStatusIntent is the garage door {position}
CheckStatusIntent is the garage {position}
CheckStatusIntent is the door {position}
CheckStatusIntent if my garage door {position}
CheckStatusIntent if my garage {position}
CheckStatusIntent if my door {position}
CheckStatusIntent if the garage door {position}
CheckStatusIntent if the garage {position}
CheckStatusIntent if the door {position}
ReplyIntent {reply}
custom Slot types:
DOOR_POSTITIONS
values:
open
opened
close
closed
yes
no
ANSWER
values:
yes
no
these are used in the Intent Schema and the sample utterances
Comments