Project

How to Build a Web-based Garage Door Controller

September 29, 2015 by Travis Fagerness

Use your Android device to open and close your garage door! Much better than that clunky remote you've been using.

Use your Android device to open and close your garage door!

Overview

I was motivated to find a new solution to controlling my garage door because I didn't want to carry an extra remote around, and they just don't work very well. This article demontrates how to use a CC3200 to connect to an existing garage door opener. The CC3200 acts as a TCP server which can send information about the garage door state to any network capable device. The garage door can also be opened and closed by sending a TCP message. An Android application is also created to act as a garage door remote.

Requirements

  • CC3200 Demo board
    • Updated to latest firmware for Energia support, directions here.
  • Energia - an IDE from TI based on processing, similar to the Arduino IDE
    • Used in article: v. 0101E00016
  • Wire to connect to an existing garage door opener
  • Optional: Android device to use as a garage remote control
    • Used in article: Android Lollipop
  • Optional: Android Studio for making an Android app

Hardware Setup

Controlling the garage door motor

My particular garage door opener is a Chamberlain brand. The rear of the garage door motor has 4 wires going into it for the sensors and the hard-wired switch. A DMM is used to find out which wire is connected to the hard-wired switch by probing the voltage while pressing the switch. I assumed the white wires were ground. The blue wire is about 5-6V and didn't react when the garage switch is pressed. I found that the red to white wire potential is normally about 15.8V when the switch is not pressed, and drops to 0V when the switch is pressed. This will make it easy to use an NPN or NMOS transistor to simulate a button press from the CC3200.

Reading the garage door status

Reading the garage door status is a little trickier because there isn't a way to tell from any of the connections on the motor. I thought of a few ways to tell if the door is open or closed.

  1. Use a single limit switch at the top of the door by the motor. If the limit switch is pressed, the door is open. If the switch is closed, the door most likely is closed, but could be stuck half-way open and the CC3200 wouldn't know.
  2. Use two limit switches at the top and bottom of the door. The switch that is pressed determines if the door is open or closed, and if both are open the door is moving or stuck half-way.
  3. Use a distance sensor in-line with the door rail. The distance will become greater as the door is closing. This would give you fine resolution into the exact state of the door.

I'm going to use option 1 to keep things simple. I can mount the switch on the door bracket near the rear so it activates when the door is fully open.

Connection Diagram

  • CC3200 pinout here.
  • The switch is just pulled-up to VCC to create an active-low connection to the GPIO input.
  • A GPIO output is interfaced to the motor red wire through an NPN to isolate the low-voltage CC3200 from the 15V switch voltage.
  • The resistor value is not important and could be whatever you have laying around, ~500-50kohm is reasonable. If you use an NMOS instead you don't need a resistor at all.

 

 

Software

Embedded

The following Energia project code does the following:

  1. Connect to the wifi network that is specified in the configuration variables
  2. Obtains an IP address from the router
  3. Opens a TCP server on the port specified in the settings
  4. Waits for a client to connect
  5. When a client connects, it waits for a password and commands.
  6. If the password is correct and the command matches a known command, an action is performed.
  7. The server responds with the state of the garage: activated, open, or closed depending on the command.

#include 
#include 

#define SERVER_PORT   23
#define LIMIT_SW_PIN  2
#define MOTOR_SW_PIN  8
#define GRN_LED      10
#define RED_LED      29
#define YELLOW_LED    9

//configuration variables
char ssid[] = "ssid";
char password[] = "pass";
char garage_password[] = "mypass";
char command_activate[] = "Activate";
char command_status[] = "Status";


boolean alreadyConnected = false; // whether or not the client was connected previously
WiFiServer server(SERVER_PORT);
void setup() {
  //debug serial port
  Serial.begin(115200);
  
  //interface pins
  pinMode(GRN_LED, OUTPUT);
  pinMode(RED_LED, OUTPUT);
  pinMode(YELLOW_LED, OUTPUT);
  pinMode(MOTOR_SW_PIN, OUTPUT);
  pinMode(LIMIT_SW_PIN, INPUT_PULLUP);
  
  digitalWrite(GRN_LED, LOW);
  digitalWrite(YELLOW_LED, LOW);
  digitalWrite(RED_LED, HIGH);
  // attempt to connect to Wifi network:
  Serial.print("Attempting to connect to Network named: ");
  // print the network name (SSID);
  Serial.println(ssid); 
  // Connect to WPA/WPA2 network. Change this line if using open or WEP network:
  WiFi.begin(ssid, password);
  while ( WiFi.status() != WL_CONNECTED) {
    // print dots while we wait to connect
    Serial.print(".");
    delay(300);
  }
  
  Serial.println("\nYou're connected to the network");
  Serial.println("Waiting for an ip address");
  
  while (WiFi.localIP() == INADDR_NONE) {
    // print dots while we wait for an ip addresss
    Serial.print(".");
    delay(300);
  }

  Serial.println("\nIP Address obtained");

  // you're connected now, so print out the status:
  printWifiStatus();

  // start the server:
  server.begin();
  
  digitalWrite(RED_LED, LOW);
  digitalWrite(YELLOW_LED, HIGH);
}

#define CLIENT_BUFF_SIZE 100
char client_in_buffer[CLIENT_BUFF_SIZE];
uint8_t idx=0;
void loop() {
  // wait for a new client:
   WiFiClient client = server.available();
  

  if (client) {
    digitalWrite(YELLOW_LED, LOW);
    if (!alreadyConnected) {
      // clead out the input buffer:
      client.flush();
      Serial.println("Client connected");
      client.println("Garage connected!");
      alreadyConnected = true;
      digitalWrite(GRN_LED, HIGH);
    }

    if (client.available() > 0) {
      char thisChar = client.read();
      Serial.write(thisChar);
      if(thisChar == '\n'){
          if(strncmp(client_in_buffer,garage_password,strlen(garage_password)) == 0){
          Serial.println("passwords match");
          
          if(strncmp(client_in_buffer+strlen(garage_password)+1,command_activate,strlen(command_activate)) == 0){
            Serial.println("Activate");
            client.println("Garage activated");
            digitalWrite(MOTOR_SW_PIN, HIGH);
            delay(200);
            digitalWrite(MOTOR_SW_PIN, LOW);
          }
          if(strncmp(client_in_buffer+strlen(garage_password)+1,command_status,strlen(command_status)) == 0){
            Serial.println("Status");
            if(digitalRead(LIMIT_SW_PIN) == HIGH) client.println("Garage is open");
            else  client.println("Garage is closed");
          }
          }
          memset(client_in_buffer,0,CLIENT_BUFF_SIZE);
          idx=0;
      }
      else{
        client_in_buffer[idx]=thisChar;
        idx++;
        if(idx>=CLIENT_BUFF_SIZE){
          idx=0;
          memset(client_in_buffer,0,CLIENT_BUFF_SIZE);
        }
      }
    }
  }
  else{
    digitalWrite(YELLOW_LED, HIGH);
    digitalWrite(GRN_LED, LOW);
    alreadyConnected = false;
  }
}

void printWifiStatus() {
  // print the SSID of the network you're attached to:
  Serial.print("SSID: ");
  Serial.println(WiFi.SSID());

  // print your WiFi shield's IP address:
  IPAddress ip = WiFi.localIP();
  Serial.print("IP Address: ");
  Serial.println(ip);

  // print the received signal strength:
  long rssi = WiFi.RSSI();
  Serial.print("signal strength (RSSI):");
  Serial.print(rssi);
  Serial.println(" dBm");
}

GarageControllerEnergia.zip

Android Application

The Android application opens a connection to the server. You'll have to modify the IP address you want to use. If you want to connect from outside your local network, you'll have to forward the port through the router to the IP address. You'll have more security if you only allow connections inside the local network. The application is just a couple buttons and some status indication. The activate button triggers the motor button for 200ms, and the status button reads the limit switch.


package com.example.travis.garagecontroller;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.net.InetAddress;
import java.net.Socket;
import java.net.UnknownHostException;
import android.os.Bundle;
import android.view.View;
import android.support.v7.app.AppCompatActivity;
import android.view.Menu;
import android.view.MenuItem;
import android.os.Handler;
import android.widget.TextView;

public class MainActivity extends AppCompatActivity {

    private Socket socket;
    Handler updateConversationHandler;
    private static final int SERVER_PORT = 23;
    private static final String SERVER_IP = "192.168.1.144";
    private static final String PASSWORD = "mypass";
    private TextView t_garage;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        t_garage = (TextView) findViewById(R.id.t_garage);
        updateConversationHandler = new Handler();
        new Thread(new ClientThread()).start();
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        // Inflate the menu; this adds items to the action bar if it is present.
        getMenuInflater().inflate(R.menu.menu_main, menu);
        return true;
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        // Handle action bar item clicks here. The action bar will
        // automatically handle clicks on the Home/Up button, so long
        // as you specify a parent activity in AndroidManifest.xml.
        int id = item.getItemId();

        //noinspection SimplifiableIfStatement
        if (id == R.id.action_settings) {
            return true;
        }
        return super.onOptionsItemSelected(item);
    }

    public void onClick_activate(View view) {
        try {
            String packet = PASSWORD + ",Activate";
            PrintWriter out = new PrintWriter(new BufferedWriter(
                    new OutputStreamWriter(socket.getOutputStream())),
                    true);
            out.println(packet);

            //get data back from server
            BufferedReader input = new BufferedReader(new InputStreamReader(socket.getInputStream()));
        }
        catch (UnknownHostException e) {
            t_garage.setText("Can't find garage");
        }
        catch (IOException e) {
            t_garage.setText("Comm error connect");
        }
        catch (Exception e) {
            t_garage.setText("Can't find garage");
        }
    }

    public void onClick_status(View view) {
        try {
            String packet = PASSWORD + ",Status";
            PrintWriter out = new PrintWriter(new BufferedWriter(
                    new OutputStreamWriter(socket.getOutputStream())),
                    true);
            out.println(packet);
            //get data back from server
            BufferedReader input = new BufferedReader(new InputStreamReader(socket.getInputStream()));
        }
        catch (UnknownHostException e) {
            t_garage.setText("Can't find garage");
        }
        catch (IOException e) {
            t_garage.setText("Comm error connect");
        }
        catch (Exception e) {
            t_garage.setText("Can't find garage");
        }
    }

    class ClientThread implements Runnable {

        @Override
        public void run() {

            try {
                InetAddress serverAddr = InetAddress.getByName(SERVER_IP);
                socket = new Socket(serverAddr, SERVER_PORT);
                SeverResponseThread serverThread = new SeverResponseThread(socket);
                new Thread(serverThread).start();
            } catch (UnknownHostException e1) {
                t_garage.setText("Can't find garage");
            } catch (IOException e1) {
                t_garage.setText("Comm error connect");
            }
        }

    }

    class SeverResponseThread implements Runnable {
        private Socket clientSocket;
        private BufferedReader input;

        public SeverResponseThread(Socket clientSocket) {

            this.clientSocket = clientSocket;

            try {
                this.input = new BufferedReader(new InputStreamReader(this.clientSocket.getInputStream()));
            }
            catch (IOException e) {
                t_garage.setText("Comm error write");
            }
        }

        public void run() {
            while (!Thread.currentThread().isInterrupted()) {
                try {
                    String read = input.readLine();
                    updateConversationHandler.post(new updateTextThread(read));
                }
                catch (IOException e) {
                    t_garage.setText("Comm error read");
                }
            }
        }

    }

    class updateTextThread implements Runnable {
        private String server_response;

        public updateTextThread(String str) {
            this.server_response = str;
        }

        @Override
        public void run() {
            t_garage.setText(server_response);
        }
    }
}


APK

The IP is hard-coded to 192.168.1.144 and the port is 23.

garage_controller.zip

Project

GarageController.zip

Testing the Door

In the video below I am controlling the garage door by pressing the Activate button on my phone.

Conclusion

You can use this project as a guideline to control many things using TCP connections with the CC3200. You don't necessarily have to use an Android device. Any network connected device capable of TCP could be used as a garage door remote.

 

Give this project a try for yourself! Get the BOM.