anthony hall
Published

Garage Door Open with IFTTT and Alexa

Garage door opener is connected via IFTTT and using a custom Alexa skill.

BeginnerFull instructions provided3 hours4,564
Garage Door Open with IFTTT and Alexa

Things used in this project

Hardware components

Photon
Particle Photon
×1
General Purpose Transistor NPN
General Purpose Transistor NPN
×1
Resistor 1k ohm
Resistor 1k ohm
×2
Garage door Remote
I think it's best to use an old remote to solder onto the button so it can be placed close to the garage door itself and only a short run of wire for the Reed sensor. Though you could connect to the button on your wall.
×1
reed sensor
×1

Story

Read more

Schematics

eLS6PCMSxaiAcdCmwrcL.jpg

When testing I found it helpful to put an led inline with the reed sensor and garage door button so you can see when they are being triggered. For simplicity I left them out of this diagram.

Code

Particle code

C/C++
Paste into particle IDE.
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

JavaScript
Amazon Lambda function. To use this code you'll need to download the zip file below that also contains the Particle-api-js so we can use the GET and POST functions. This is pasted here just for quick viewing. I've left in lots of log statements that can be helpful for understanding how it works.

This 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
    };
}

Alexa Intent Schema

JSON
Used in building the Alexa Skill
{
  "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"
    }
  ]
}

Sample Utterances

Tex
Alexa sample utterances that a user would speak
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

Plain text
These are the custom slot types and values
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

Alexa Zip file

JavaScript
this is the zip that you upload into your lambda function
No preview (download only).

Credits

anthony hall

anthony hall

1 project • 1 follower

Comments