Hackster is hosting Hackster Holidays, Ep. 5: Livestream & Giveaway Drawing. Watch previous episodes or stream live on Friday!Stream Hackster Holidays, Ep. 5 on Friday!
gusgonnet
Published © Apache-2.0

Garage Commander - Garage Opener

Control your garage door from wherever you are.

BeginnerFull instructions provided11,529
Garage Commander - Garage Opener

Things used in this project

Hardware components

Photon
Particle Photon
you can use a Photon or a Core (you do not need both)
×1
Spark Core
Particle Spark Core
you can use a Photon or a Core (you do not need both)
×1
reed switch
×2
Particle relay shield
you can use either a relay shield or a single high trigger relay (you do not need both)
×1
high trigger 3.3Volts relay
you can use either a relay shield or a single high trigger 3.3volts relay (you do not need both)
×1
Adafruit nfc tags
×1

Software apps and online services

Blynk
Blynk
IFTTT DO button
trigger
tasker
autovoice

Story

Read more

Schematics

Wifi Garage Commander Schematic

the relay here represents the single high trigger relay or one of the relays in the particle relay shield

Code

MyActivity.java

Java
java code for my first Android app
package com.example.gusgonnet.myfirstapp;

//TODO: now
// - app has to have a get and a post function
// - app has to refresh status at beginning
// - have a settings page
// - store Access Token and Device ID
// - make the spark return open/close status in one call

//TODO: later
// - store email, password, Device ID
// - get an access token from email and password


import android.app.Activity;
import android.content.Context;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.os.AsyncTask;
import android.os.Bundle;
import android.util.Log;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.content.Intent;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.LinearLayout;
import android.widget.RelativeLayout;
import android.widget.TextView;
import android.widget.Toast;

import java.io.DataOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.io.UnsupportedEncodingException;
import java.net.HttpURLConnection;
import java.net.URL;

public class MyActivity extends Activity {

    public enum AppStatus { REFRESHING, OPENING, CLOSING, OPEN, CLOSED, UNKNOWN };

    RelativeLayout DefaultLayout;
    RelativeLayout WaitingLayout;

    Button actionButton;
    TextView waitingTextView;

    public static AppStatus appStatus = AppStatus.CLOSED;

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        // Inflate the menu items for use in the action bar
        MenuInflater inflater = getMenuInflater();
        inflater.inflate(R.menu.my, menu);  //this was main_activity_actions -> my
        return super.onCreateOptionsMenu(menu);
    }

    public void actionButtonClickHandler(View view) {

        //replace 121345678901234567890 with your particle photon id
        String url = "https://api.spark.io/v1/devices/121345678901234567890/garage_open";

        ConnectivityManager connMgr = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE);
        NetworkInfo networkInfo = connMgr.getActiveNetworkInfo();
        if (networkInfo != null && networkInfo.isConnected()) {
            appStatus = updateAppStatus(appStatus);
            updateLayout(appStatus);
            new sendCommand().execute(url);
        } else {
            Toast toast = Toast.makeText(getApplicationContext(), "Something wrong\r\nno internet connection?", Toast.LENGTH_LONG);
            toast.show();
        }
    }

    // Uses AsyncTask to create a task away from the main UI thread. This task takes a
    // URL string and uses it to create an HttpUrlConnection. Once the connection
    // has been established, the AsyncTask downloads the contents of the webpage as
    // an InputStream. Finally, the InputStream is converted into a string, which is
    // displayed in the UI by the AsyncTask's onPostExecute method.
    private class sendCommand extends AsyncTask<String, Void, String> {

        private static final String DEBUG_TAG = "HomeCommander";

        protected void onPostExecute (String result) {
                //check that the response indicates success
                if (result.toLowerCase().contains("\"return_value\": 0") ) {
                    Toast toast = Toast.makeText(getApplicationContext(), "Success!", Toast.LENGTH_LONG);
                    toast.show();
                    appStatus = updateAppStatus(appStatus);
                } else {
                    Toast toast = Toast.makeText(getApplicationContext(), "Something went wrong! \r\n" + result, Toast.LENGTH_LONG);
                    toast.show();
                    appStatus = rollbackAppStatus(appStatus);
                }
            updateLayout(appStatus);
        }


        @Override
        protected String doInBackground(String... urls) {

            // params comes from the execute() call: params[0] is the url.
            try {
                return sendCommandToSparkCloud(urls[0]);
            } catch (IOException e) {
                return "problems with the url?";
            }
        }


        // establishes an HttpUrlConnection and sends a command to the Spark cloud
        // it returns the response as a string
        private String sendCommandToSparkCloud(String myurl) throws IOException {
            InputStream is = null;

            try {
                URL url = new URL(myurl);
                HttpURLConnection conn = (HttpURLConnection) url.openConnection();
                conn.setReadTimeout(10000 /* milliseconds */);
                conn.setConnectTimeout(15000 /* milliseconds */);
                conn.setRequestMethod("POST");
                //replace 121345678901234567890 here with your particle auth token
                conn.setRequestProperty ("Authorization", "Bearer 121345678901234567890");
                conn.setDoOutput(true);

                // sends the request
                conn.connect();

                //get the response code
                int response = conn.getResponseCode();
                Log.d(DEBUG_TAG, "The response code is: " + response);

                // get the response content
                is = conn.getInputStream();
                // we are interested in only the first 500 chars
                String responseAsString = stream2string(is, 500);
                Log.d(DEBUG_TAG, "The response content is: " + responseAsString);

                return responseAsString;

                // This makes sure that the InputStream is closed after the app is
                // finished using it.
            } finally {
                if (is != null) {
                    is.close();
                }
            }
        }

        // Reads an InputStream and converts it to a String.
        public String stream2string(InputStream stream, int len) throws IOException, UnsupportedEncodingException {
            Reader reader = new InputStreamReader(stream, "UTF-8");
            char[] buffer = new char[len];
            reader.read(buffer);
            return new String(buffer);
        }

    }

        @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_my);

            DefaultLayout=(RelativeLayout)findViewById(R.id.DefaultLayout);
            WaitingLayout=(RelativeLayout)findViewById(R.id.WaitingLayout);

            actionButton = (Button)findViewById(R.id.actionButton);
            waitingTextView = (TextView)findViewById(R.id.waitingTextView);

            updateLayout(appStatus);

    }

    public void updateLayout(AppStatus appStatus) {
        switch (appStatus) {
            case OPENING:
                waitingTextView.setText(getString(R.string.opening_garage));
                DefaultLayout.setVisibility(View.GONE);
                WaitingLayout.setVisibility(View.VISIBLE);
                break;

            case CLOSING:
                waitingTextView.setText(getString(R.string.closing_garage));
                DefaultLayout.setVisibility(View.GONE);
                WaitingLayout.setVisibility(View.VISIBLE);
                break;

            case REFRESHING:
                waitingTextView.setText(getString(R.string.refreshing));
                DefaultLayout.setVisibility(View.GONE);
                WaitingLayout.setVisibility(View.VISIBLE);
                break;

            case OPEN:
                actionButton.setText(getString(R.string.close_garage));
                DefaultLayout.setVisibility(View.VISIBLE);
                WaitingLayout.setVisibility(View.GONE);
                break;

            case CLOSED:
                actionButton.setText(getString(R.string.open_garage));
                DefaultLayout.setVisibility(View.VISIBLE);
                WaitingLayout.setVisibility(View.GONE);
                break;

            case UNKNOWN:
                actionButton.setText(getString(R.string.open_close_garage));
                DefaultLayout.setVisibility(View.VISIBLE);
                WaitingLayout.setVisibility(View.GONE);
                break;
        }

    }

    public AppStatus updateAppStatus(AppStatus appStatus) {
        switch (appStatus) {
            case OPEN:
                return AppStatus.CLOSING;

            case CLOSING:
                return AppStatus.CLOSED;

            case CLOSED:
                return AppStatus.OPENING;

            case OPENING:
                return AppStatus.OPEN;

            case REFRESHING:
            case UNKNOWN:
            //TODO: not sure what to do here, so for now we don't change the appStatus
        }
        return appStatus;
    }

    public AppStatus rollbackAppStatus(AppStatus appStatus) {
        switch (appStatus) {
            case CLOSING:
                return AppStatus.OPEN;

            case OPENING:
                return AppStatus.CLOSED;

            case OPEN:
            case CLOSED:
            case REFRESHING:
            case UNKNOWN:
                //TODO: not sure what to do here, so for now we don't change the appStatus
        }
        return appStatus;
    }

    @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.
        switch (item.getItemId()) {
            case R.id.action_settings:
//                openSettings();
                Toast toast = Toast.makeText(getApplicationContext(), "Settings are coming soon...", Toast.LENGTH_SHORT);
                toast.show();
                return true;
            default:
                return super.onOptionsItemSelected(item);
        }
    }
}

How to read an NFC tag and open your garage

Plain text
step 1:
-------
Install the Trigger and Tasker apps in your phone.

step 2:
-------
In the Tasker app we create a task that will send an HTTP POST request to the Particle Cloud to open or close the garage door.

Please take a look at this thread to see how to enter information in Tasker:
https://community.particle.io/t/solved-using-tasker-for-android-to-post-an-api-request-to-spark-core/4724/17

step 3:
-------
In the Trigger app we will create a task to fire off a task in Tasker. At the end of the task creation, the app will ask us to scan the NFC tag so it can save the task in it.

You can take a look at great full tutorial here:
http://www.androidauthority.com/nfc-trigger-tasker-409686/

particle webhook

JSON
Follow instructions to setup this webhook in this project:
https://www.hackster.io/gusgonnet/water-detection-system-227b08
{
  "eventName": "pushbullet",
  "url": "https://api.pushbullet.com/v2/pushes",
  "requestType": "POST",
  "headers": {
    "Authorization": "Bearer 123456789012345678901234567890",
    "Content-Type": "application/json"
  },
  "json": {
      "type": "note",
      "title": "{{SPARK_EVENT_VALUE}}",
      "body": ""
  },
  "mydevices": true
}

Particle code

C/C++
flash this code in your Particle
// IO mapping
// D0 : relay: garage_BUTTON - this output is connected to the garage wall button or the garage remote
// D1 : relay
// D2 : relay
// D3 : relay
// D4 : garage_CLOSE : high means contact open (garage door opened), low means contact closed (garage door closed)
// D5 : garage_OPEN : high means contact open (garage door opened), low means contact closed (garage door closed)
// D6 :
// D7 :

// A0 :
// A1 : 
// A2 : 
// A3 : 
// A4 :
// A5 :
// A6 :
// A7 :

#include "application.h"

String _version = "0.01";

#define PUSHBULLET_NOTIF "pushbullet"

#define GARAGE_READ_INTERVAL 1000
#define GARAGE_OPEN "open"
#define GARAGE_CLOSED "closed"
#define GARAGE_OPENING "opening"
#define GARAGE_CLOSING "closing"
#define GARAGE_NOTIF "GARAGE"
unsigned long garage_interval = 0;
int garage_BUTTON = D0;
int garage_CLOSE = D4;
int garage_OPEN = D5;
String garage_status_string = "unknown";


void setup() {

 Particle.publish("device starting", "Firmware version: " + _version, 60, PRIVATE);

  pinMode(garage_BUTTON, OUTPUT);
  pinMode(garage_OPEN, INPUT_PULLUP);
  pinMode(garage_CLOSE, INPUT_PULLUP);
  
  bool success = Particle.function("garage_open", garage_open);
  if (not success) {
      Particle.publish("ERROR", "Failed to register function garage_open", 60, PRIVATE);
  }
  
  success = Particle.function("garage_stat", garage_stat);
  if (not success) {
      Particle.publish("ERROR", "Failed to register function garage_stat", 60, PRIVATE);
  }

}

void loop() {
 
  //read garage status every now and then
  if( millis() - garage_interval >= GARAGE_READ_INTERVAL ) {
    garage_read();
    garage_interval = millis();
  }

}

/*******************************************************************************
 * Function Name  : garage_open
 * Description    : garage_BUTTON goes up for one second
 * Return         : 0
 *******************************************************************************/
int garage_open(String args)
{
    digitalWrite(garage_BUTTON, HIGH);
    delay(1000);
    digitalWrite(garage_BUTTON, LOW);

    return 0;
}

/*******************************************************************************
 * Function Name  : garage_read()
 * Description    : reads and publishes the status of the garage, intended for using it with a service like ifttt
 * Return         : 0
 *******************************************************************************/
int garage_read()
{
    int open = digitalRead(garage_OPEN);
    int closed = digitalRead(garage_CLOSE);
    String previous_garage_status_string = garage_status_string;
    
    //input goes low when the reed switch is activated
    if ( not closed ) {
        garage_status_string = GARAGE_CLOSED;
    }

    //input goes low when the reed switch is activated
    if ( not open ) {
        garage_status_string = GARAGE_OPEN;
    }

    //if both inputs are high, it means that the garage is moving
    // so if it was open, we believe it's closing now
    // and if it was closed, we believe it's opening now
    if ( open and closed ) {
        if ( previous_garage_status_string == GARAGE_OPEN ) {
            garage_status_string=GARAGE_CLOSING;
        }
        if ( previous_garage_status_string == GARAGE_CLOSED ) {
            garage_status_string=GARAGE_OPENING;
        }
    }
    
    //if status of the garage changed from last scan, publish the new status
    if ( previous_garage_status_string != garage_status_string ) {
        Particle.publish(PUSHBULLET_NOTIF, "Your garage door is " + garage_status_string, 60, PRIVATE);
    }

    return 0;
}

/*******************************************************************************
 * Function Name  : garage_stat  // function name cannot be longer than 12 chars otherwise it wont be registered!
 * Description    : reads and publishes the status of the garage, intended for using it with a service like ifttt or pushbullet
 * Return         : 0
 *******************************************************************************/
int garage_stat(String args)
{
    int opened = digitalRead(garage_OPEN);
    int closed = digitalRead(garage_CLOSE);

    if ( not closed ) {
        garage_status_string = GARAGE_CLOSED;
    }
    if ( not opened ) {
        garage_status_string = GARAGE_OPEN;
    }
    
   Particle.publish(PUSHBULLET_NOTIF, "Your garage door is " + garage_status_string, 60, PRIVATE);

    return 0;
}

How to use Google Voice to open your garage

Plain text
step 1:
-------
Install the Autovoice and Tasker apps in your phone.


step 2:
-------
In the Tasker app we create a task that will send an HTTP POST request to the Particle Cloud to open or close the garage door.
Note: if you already created a task to open your garage with an NFC tag, we will use the same task.

Here is what I entered in Tasker:
- create a new Task called "OpenGarage"
- add an Action
- select Net
- select HTTP Post
- enter in Server:Port
      https://api.particle.io/v1/devices/<PARTICLE DEVICE ID>/garage_open
- enter in Data/File
      access_token=<YOUR PARTICLE ACCESS TOKEN>
-note that garage_open is the function we want to trigger with this POST request

Please take a look at this thread to see how to enter information in Tasker:
https://community.particle.io/t/solved-using-tasker-for-android-to-post-an-api-request-to-spark-core/4724/17


step 3:
-------
In the Tasker app we will create a Profile to trigger that task when we speak "open garage". 

Here is what I did in Tasker:
- create a new Profile
- select Event
- select Plugin
- select Autovoice -> Recognized
- tap on Configuration
- tap on Speak Filter
- record your voice command
- choose your voice command from the list
- hit the check at the top then hit back in Tasker
- select the task you want this command to trigger, it should be OpenGarage
- you are done!


You can also follow the instructions on the video in the Google Play page:
https://play.google.com/store/apps/details?id=com.joaomgcd.autovoice&hl=en


step 4 (optional):
------------------

Repeat steps 2 and 3 to create a GarageStatus action and CloseGarage actions in Tasker and the respective voice commands to get a full voice interface to your garage.

How to read an NFC tag and open your garage

Plain text
step 1:
-------
Install the Trigger and Tasker apps in your phone.


step 2:
-------
In the Tasker app we create a task that will send an HTTP POST request to the Particle Cloud to open or close the garage door.

Here is what I entered in Tasker:
- create a new Task called "OpenGarage"
- add an Action
- select Net
- select HTTP Post
- enter in Server:Port
      https://api.particle.io/v1/devices/<PARTICLE DEVICE ID>/garage_open
- enter in Data/File
      access_token=<YOUR PARTICLE ACCESS TOKEN>
-note that garage_open is the function we want to trigger with this POST request

Please take a look at this thread to see how to enter information in Tasker:
https://community.particle.io/t/solved-using-tasker-for-android-to-post-an-api-request-to-spark-core/4724/17


step 3:
-------
In the Trigger app we will create a task to fire off a task in Tasker. At the end of the task creation, the app will ask us to scan the NFC tag so it can save the task in it.

Here is what I did in Trigger:
- create new task
- hit next on the "Add one or more triggers" screen
- add an action
- select Tasker -> Tasker Task then next
- hit the magnifier so you can select the task in Tasker
- select OpenGarage and tap on "ADD TO TASK"
- hit Done
- hit NFT in "Add one or more triggers" and Next
- hit done in "Add restrictions" -> Next -> Next -> Done (there is no need for a second, switch task)
- Scan your NFC tag to save the task date in it
- Hit Done on top
- you are finally done


You can take a look at great full tutorial here:
http://www.androidauthority.com/nfc-trigger-tasker-409686/

Credits

gusgonnet

gusgonnet

37 projects • 306 followers
With a decade of Software Engineering experience in IoT, I focus on creating Particle IoT solutions coupled with mobile and web applications

Comments