Skip to main content

Garage door opener using Arduino + SmartThings


I wired up and controlled my garage (two main doors controlling and one side door sensor only) with the following customizable source code running on an Arduino and the SmartThings platform (via a Z Wave shield. (The .groovy file you load on the web, and the .ino file you compile for the Arduino)

These are the materials I used:

$35 Smarthings Arduino Z Wave shield
$25 Arduino Uno
$10 Power supply for Arduino
$10 Wire
$20 Magnetic switches
$8   Relay

It was pretty easy, I developed all the source and did the installation in a single day (tweaked it a little the rest of the weekend) and it has been running stable for about 2 years now.

The garage doors are toggles that I wired into my garage controller button wires, and they only show if the garage doors are ajar. (It does not indicate whether they are closing or opening... that was troublesome to detect with just a magnetic switch and the way garage doors open and close.)

No express or implied warranty are attached to this guide... your milage may vary, etc.

Here is how it looks on my SmartThings (Android) app:



ArduinoGarage.groovy

/**
 *  Arduino Garage
 *
 *  Author: Marius Piedallu van Wyk
 *  Date: 2016-08-17
 */

metadata {
 // Automatically generated. Make future change here.
 definition (name: "Arduino Garage", namespace: "Lailoken", author: "Marius Piedallu van Wyk") {
  capability "Switch"
  // capability "Sensor"
        capability "Contact Sensor"

        attribute "contact",   "string" // A virtual contact to indicate if anything is open

        attribute "leftDoor",  "string" // left door open
        attribute "rightDoor", "string" // right door open
        attribute "backDoor",  "string" // side walk-in door open

  command "pushLeft"   // toggle left
  command "pushRight"  // toggle right
    }

    simulator {
    }

 // Preferences

 // tile definitions
 tiles {
        standardTile("contact", "device.contact", width: 1, height: 1, canChangeIcon: true, canChangeBackground: true) {
   state "closed",  label: "Closed",  icon: "st.doors.garage.garage-closed",  backgroundColor: "#79b821"
            state "open",    label: "Open",    icon: "st.doors.garage.garage-open",    backgroundColor: "#ffa81e"
   }
  standardTile("leftDoor", "device.leftDoor", width: 1, height: 1, canChangeIcon: true, canChangeBackground: true) {
   state "closed",  label: "Closed",  icon: "st.doors.garage.garage-closed",  backgroundColor: "#79b821", action: "pushLeft", nextState: "open"
            state "open",    label: "Open",    icon: "st.doors.garage.garage-open",    backgroundColor: "#ffa81e", action: "pushLeft", nextState: "toggle"
            state "toggle",  label: "Toggle",  icon: "st.doors.garage.garage-opening", backgroundColor: "#89C2E8", action: "pushLeft", nextState: "toggle"
   }
        standardTile("rightDoor", "device.rightDoor", width: 1, height: 1, canChangeIcon: true, canChangeBackground: true) {
   state "closed",  label: "Closed",  icon: "st.doors.garage.garage-closed",  backgroundColor: "#79b821", action: "pushRight", nextState: "open"
            state "open",    label: "Open",    icon: "st.doors.garage.garage-open",    backgroundColor: "#ffa81e", action: "pushRight", nextState: "toggle"
            state "toggle",  label: "Toggle",  icon: "st.doors.garage.garage-opening", backgroundColor: "#89C2E8", action: "pushRight", nextState: "toggle"
   }
        standardTile("backDoor", "device.backDoor", width: 1, height: 1, canChangeIcon: true, canChangeBackground: true) {
   state "closed",  label: "Closed",  icon: "st.contact.contact.closed",      backgroundColor: "#79b821"
            state "open",    label: "Open",    icon: "st.contact.contact.open",        backgroundColor: "#ffa81e"
   }

  main(["contact"])

  details(["leftDoor", "rightDoor", "backDoor"])
 }

 preferences {
    }

}

def parse(String description) {
 def msg = zigbee.parse(description)?.text
 log.debug "Parse got '${msg}'"

    def parts = msg.split(" ")
    def name  = parts.length>0?parts[0].trim():null
    def value = parts.length>1?parts[1].trim():null

 name = value != "ping" ? name : null

 def result
 if(name == "anyDoor") {
     // Use anyDoor as the virtual contact sensor for whole space:
  result = createEvent(name: "contact", value: value)
    } else {
  result = createEvent(name: name, value: value)
    }

 log.debug result

 return result
}

def pushLeft() {
 log.debug "Left Button pressed"
    zigbee.smartShield(text: "pushLeft").format()
}

def pushRight() {
 log.debug "Right Button pressed"
    zigbee.smartShield(text: "pushRight").format()
}



arduino-garage.ino

/*****************************************************************************
 *  Arduino Garage
 *
 *  Author: Marius Piedallu van Wyk
 *  Date: 2014-07-27
 */

//*****************************************************************************
#include 
#include 

#define DO_BACK_DOOR

//*****************************************************************************
// Pin Definitions
//*****************************************************************************
#define PIN_LED              13
#define PIN_THING_RX          3
#define PIN_THING_TX          2
#define PIN_BACK_DOOR_CONTACT 4     // input
#define PIN_RIGHT             6     // out
#define PIN_RIGHT_CONTACT     7     // input
#define PIN_LEFT_CONTACT      8     // input
#define PIN_LEFT              9     // out

#define OPEN                  0     // HIGH
#define CLOSED                1     // LOW
#define UNKNOWN               2     // --- reset / force update

#define PUSH_DELAY            1200  // milliseconds to keep the button "pushed"

//*****************************************************************************
// Global Variables
//*****************************************************************************
SmartThingsCallout_t messageCallout;    // call out function forward decalaration
SmartThings smartthing(PIN_THING_RX, PIN_THING_TX, messageCallout);  // constructor

int leftClosed  = UNKNOWN;
int rightClosed = UNKNOWN;
int backClosed  = UNKNOWN;

int anyOpen     = UNKNOWN;

bool isDebugEnabled=false;    // enable or disable debug in this example
int stateLED;           // state to track last set value of LED
int stateNetwork;       // state of the network

//*****************************************************************************
// Local Functions
//*****************************************************************************

//*****************************************************************************
void pushLeft()
{
  smartthing.shieldSetLED(0, 0, 2); // blue
  digitalWrite(PIN_LEFT,LOW);
  delay(PUSH_DELAY);
  digitalWrite(PIN_LEFT,HIGH);
  smartthing.shieldSetLED(0, 0, 0); // off
}

//*****************************************************************************
void pushRight()
{
  smartthing.shieldSetLED(0, 2, 0); // green
  digitalWrite(PIN_RIGHT,LOW);
  delay(PUSH_DELAY);
  digitalWrite(PIN_RIGHT,HIGH);
  smartthing.shieldSetLED(0, 0, 0); // off
}

//*****************************************************************************
int isClosed(int pin)
{
  // LOW  -> closed
  // HIGH -> open
  return (digitalRead(pin) == LOW)?CLOSED:OPEN;
}

//*****************************************************************************
void updateDoorState()
{
  int newState;
  String msg;

  newState = isClosed(PIN_LEFT_CONTACT);
  if (leftClosed != newState)
  {
    leftClosed = newState;
    if(leftClosed == CLOSED) msg = "leftDoor closed";
    else                     msg = "leftDoor open";

    smartthing.send(msg);
    if(isDebugEnabled) Serial.println(msg);
    return; // only one message per loop
  }

  newState = isClosed(PIN_RIGHT_CONTACT);
  if (rightClosed != newState)
  {
    rightClosed = newState;
    if(rightClosed == CLOSED) msg = "rightDoor closed";
    else                      msg = "rightDoor open";

    smartthing.send(msg);
    if(isDebugEnabled) Serial.println(msg);
    return; // only one message per loop
  }


#ifdef DO_BACK_DOOR
  newState = isClosed(PIN_BACK_DOOR_CONTACT);
  if (backClosed != newState)
  {
    backClosed = newState;
    if(backClosed == CLOSED) msg = "backDoor closed";
    else                     msg = "backDoor open";

    smartthing.send(msg);
    if(isDebugEnabled) Serial.println(msg);
    return; // only one message per loop
  }
#endif

  newState = CLOSED;

#ifdef DO_BACK_DOOR
  if(rightClosed == OPEN || leftClosed == OPEN || backClosed == OPEN) newState = OPEN;
#else
  if(rightClosed == OPEN || leftClosed == OPEN) newState = OPEN;
#endif

  if(anyOpen != newState)
  {
    anyOpen = newState;
    if(anyOpen == CLOSED) msg = "anyDoor closed";
    else                  msg = "anyDoor open";

    smartthing.send(msg);
    if(isDebugEnabled) Serial.println(msg);
    return; // only one message per loop
  }

}

//*****************************************************************************
void setNetworkStateLED()
{
  SmartThingsNetworkState_t tempState = smartthing.shieldGetLastNetworkState();
  if (tempState != stateNetwork)
  {
    switch (tempState)
    {
      case STATE_NO_NETWORK:
        if (isDebugEnabled) Serial.println("NO_NETWORK");
        smartthing.shieldSetLED(2, 2, 0); // red
        break;
      case STATE_JOINING:
        if (isDebugEnabled) Serial.println("JOINING");
        smartthing.shieldSetLED(2, 2, 0); // yellow
        break;
      case STATE_JOINED:
        if (isDebugEnabled) Serial.println("JOINED");
        smartthing.shieldSetLED(0, 0, 0); // off

        // force report of current door state
        leftClosed  = UNKNOWN;
        rightClosed = UNKNOWN;
        backClosed  = UNKNOWN;
        anyOpen     = UNKNOWN;

        break;
      case STATE_JOINED_NOPARENT:
        if (isDebugEnabled) Serial.println("JOINED_NOPARENT");
        smartthing.shieldSetLED(2, 0, 2); // purple
        break;
      case STATE_LEAVING:
        if (isDebugEnabled) Serial.println("LEAVING");
        smartthing.shieldSetLED(2, 2, 0); // yellow
        break;
      default:
      case STATE_UNKNOWN:
        if (isDebugEnabled) Serial.println("UNKNOWN");
        smartthing.shieldSetLED(2, 0, 0); // red
        break;
    }
    stateNetwork = tempState;
  }
}

//*****************************************************************************
void setup()
{
  // setup default state of global variables
  isDebugEnabled = true;
  stateLED = 0;                 // matches state of hardware pin set below
  stateNetwork = STATE_JOINED;  // set to joined to keep state off if off

  // setup hardware pins
  pinMode(PIN_LED, OUTPUT);     // define PIN_LED as an output
  pinMode(PIN_RIGHT, OUTPUT);
  pinMode(PIN_LEFT, OUTPUT);
  digitalWrite(PIN_RIGHT, HIGH);
  digitalWrite(PIN_LEFT, HIGH);
  digitalWrite(PIN_LED, LOW);   // set value to LOW (off) to match stateLED=0

  pinMode(PIN_LEFT_CONTACT, INPUT_PULLUP);
  pinMode(PIN_RIGHT_CONTACT, INPUT_PULLUP);
  pinMode(PIN_BACK_DOOR_CONTACT, INPUT_PULLUP);

  if(isDebugEnabled)
  { // setup debug serial port
    Serial.begin(9600);         // setup serial with a baud rate of 9600
    Serial.println("setup..");  // print out 'setup..' on start
  }

}

//*****************************************************************************
void loop()
{
  // run smartthing logic
  smartthing.run();

  // Check network connections (and send initial states on Join)
  setNetworkStateLED();

  if(stateNetwork == STATE_JOINED)
  {
    // Check the open/closed state of the doors
    updateDoorState();
  }
}

//*****************************************************************************
void messageCallout(String message)
{
  smartthing.shieldSetLED(2, 2, 2); // white

  // if debug is enabled print out the received message
  if(isDebugEnabled)
  {
    Serial.print("Received message: '");
    Serial.print(message);
    Serial.println("'");
  }

  if(message.equals("pushLeft"))
  {
    pushLeft();
  }
  else if(message.equals("pushRight"))
  {
    pushRight();
  }

  smartthing.shieldSetLED(0, 0, 0); // off
}


Comments

Popular posts from this blog

Firefox: Open diverted links in new background tab instead of new window

This has frustrated me for a long time... something I really liked about Opera. Go to 'about:config' url and set: browser.tabs.loadInBackground to true (should be already) browser.tabs.showSingleWindowModePrefs to true browser.tabs.loadDivertedInBac kground to true Then in advanced preferences is a new option: 'Force links that open a new window to:' can be used for immesdiate effect.

My Custom Mechanical 60% Keyboard Build

My Custom Mechanical 60% Keyboard Build All the parts   1x GH60 (Satan) board - $35 61x Cherry Clear switches  - $35 2x Cherry Blue switches - $3 10x Cherry Red switches - $10 1x PCB Stabilizer set (6.25 space) - $6 1x Royal Oak Glam 60% case (Black Walnut) - $78 1x Aluminium 60% plate - $19 1x SA profile Choclatier keycaps - $102 ( base, modifiers, and novelties ) 1x Braided Nylon USB cable - $15 Solder, Tools, Risers, Labor, etc. Total: ± $300 (Other single switch types added as needed) Testing the board I had my helper do the work here. We needed to test the board before applying any solder. Assembling Add the stabilizers first Add the plate (using a few switches as spacers) Then add the rest Solder solder solder... And test again: The casing The casing I received did to have built-in risers: So I added my own: Then Assemble (using temporary spare GMK Carbon Cherry-profile k...

Why torrent low-ratio banning is killing the network and is generally a bad idea.

It seems a growing trend for people to get banned for low share ratios. Now I can understand how this would appeal to some people`s sense of (in)justice as there are people out there who do not understand that not sharing while you download will ultimately make you download yours slower as you supply bits to other people who would otherwise been supplied by the seeder, making it possible for you to download from open slots at the seeder. These are typlically low-intellect `kiddies` who don`t do math and then download hacked clients because they are just stupid. I agree that these people are evil. But putting a share-ratio of 0.9 and higher as mandatory is just insane. (IMHO 0.1 is the absolute maximum) Take the following example (using mathematical induction and taking values to extremes) if I limited my download to 1k/s and my upload to 25k/s (I have a 256/256 connection) my share ratio inexorably tends to go to sub-1.0. In fact, I find that only the T1 people seems to...