Rusty Morrison
Published © CC BY-NC

Garage Monitoring and Door / Light Control

Use of a Particle Photon, sensors, and a 2-relay shield to monitor garage conditions and remotely control my garage door & light.

AdvancedFull instructions provided24 hours1,875
Garage Monitoring and Door / Light Control

Things used in this project

Hardware components

Photon
Particle Photon
×1
ControlEverything.com Relay Shield for Particle Photon I²C 2-Channel SPDT 1-Amp Signal Relay
×1
ControlEverything.com 12 Volt 1.25 Amp Regulated Switcher Supply
×1
HC-SR501 PIR
×1
Photo resistor
Photo resistor
×1
DHT22 Temperature Sensor
DHT22 Temperature Sensor
×1
Mini Breadboard
×1
Resistor 10Kohm
×2
Resistor 22 kohm
×2
Resistor 47 kohm
×2
2.4GHz Antenna
×1
4 11/16 Work Box
×1
4 11/16 Work Box Cover
×1
Single Gang Surface Mount Work Box & Cover
×1
CAT 5 Bulk Cable
×1
Various Mounting Hardware
×1

Software apps and online services

Ubidots
Ubidots
IFTTT Particle Channel
Pushbullet

Hand tools and fabrication machines

IWISS Dupont Professional Pin Compression Ratcheting Modular Insulated Terminal Crimper Pin Crimping Tool

Story

Read more

Schematics

Drawing - Fritzing

Used to show connections.

Schematic

from the Fritzing file

Code

Photon Firmwhere

C/C++
You have to put in your own Ubidots token & variable IDs.
I use a unique prefix for the event handler to avoid someone else in the world publishing the same public event & triggering actions on my system.
// Garage Monitor
// developed by Rusty Morrison <rustymorr@yahoo.com>
/* 
This work is licensed under a Creative Commons Attribution-NonCommercial 4.0 International License.
http://creativecommons.org/licenses/by-nc/4.0/
*/
// Connections
// A0 = 3V3 power source
// A1 = CdS photorestor

// DO: bad IO pin, not used
// D1: bad IO pin, not used
// D2: Garage Door down switch (0 VDC when door fully shut, + 5 VDC otherwise)
// D3: Garage DOor up switch  (0 VDC when door fully open, +5 VDC otherwise)
// D4: DHT22 Data pin
// D5: PIR1 Signal

#include "ProbeCube_DHT/ProbeCube_DHT.h"
#include "HttpClient/HttpClient.h"
#include "NCD2Relay/NCD2Relay.h"
#include "math.h"

// ubidots parameters:

#define TOKEN "put your ubidots token here"
#define GARAGE_DOOR_ID "put ubidots variable id here"
#define LIGHT_VARIABLE_ID "put ubidots variable id here"
#define TEMP_ID "put ubidots variable id here"
#define RH_ID "put ubidots variable id here"
#define PIR_ID "put ubidots variable id here"

#define DHTTYPE DHT22
#define DHT1PIN D4    // what pin we're connected to

HttpClient http;

DHT dht1(DHT1PIN, DHTTYPE);

float LightLevel = 0;
char resultstr[64];

int power = A0; // This is the other end of your photoresistor. The other side is plugged into the "photoresistor" pin (above).
int photoresistor = A1; // This is where your photoresistor is plugged in. The other side goes to the "power" pin (below).

int DownSwitch = D2;
int UpSwitch = D3;
// DHT1PIN = D4, assigned above using #define
int PIR1 = D5;

float LastSavedBrightness = 0;
int LastSavedTime = 0;
int MaximumLightLevelReportInterval = 300;
float LastBrightness = 0;
float AbsoluteDiff = 0;
float PercentDiff = 0;

int Temp_RH_SaveInterval = 300;
int LastTemp_RH_SaveTime = 0;
float f1 = 0;
float h1 = 0;
float Last_f1 = 0;
float Last_h1 = 0;

int MaximumPIR1_ReportInterval = 60; // in seconds
int LastPIR1_StateSaveTime = 0;
int PIR1_State = 0;
int LastPIR1_State = 0;

float GarageDoorState = 0;
int DownSwitchState = 0;
int UpSwitchState = 0;

float LastGarageDoorState = 0;
int LastGarageDoorSaveTime = 0;
int MaximumGarageDoorSaveInterval = 300;

int GarageDoorNotClosedTime = 0;
int GarageDoorLastNotificationTime = 0;
int GarageDoorNotClosedNotificationThreshold = 900; // Duration (seconds) that must be open before notifications start
int GarageDoorNotClosedNotificationInterval = 900; // How often to repeat garage door open notifications
int GarageDoorHourStartNotification = 22; // Hour of day after which notifications will be sent
int GarageDoorHourStopNotification = 4; // Hour of day before which notifications will be sent
int DST_Adjustment = -4; // not DST aware, need to change to -5 when DST ends

String RawCommand;
String strCommand;
String strArguments;

bool NewCommand = false;

// Headers currently need to be set at init, useful for API keys etc.
http_header_t headers[] = {
    //{ "Host", "things.ubidots.com" },     // Declare the host here if your Spark has DNS problems
    { "Content-Type", "application/json" },
    { "X-Auth-Token", TOKEN },
    { NULL, NULL } // NOTE: Always terminate headers with NULL
};

http_request_t request;
http_response_t response;

SYSTEM_MODE(AUTOMATIC);
NCD2Relay relayController;

void setup()
{
    WiFi.selectAntenna(ANT_EXTERNAL);
        
    GarageDoorNotClosedTime = Time.now();
        
    Particle.subscribe("XXYYZZ_HomeCommand", Command_Handler); // use a unique code to prevent "listening" to someone else's published events
        
    RGB.control(true);
    RGB.brightness(4);
    RGB.control(false);
        
    pinMode(photoresistor,INPUT);  // Our photoresistor pin is input (reading the photoresistor)
    pinMode(power,OUTPUT); // The pin powering the photoresistor is output (sending out consistent power)
    pinMode(DHT1PIN, INPUT);
    pinMode(PIR1,INPUT);
    pinMode(DownSwitch, INPUT); 
    pinMode(UpSwitch, INPUT);  
        
    request.hostname = "things.ubidots.com";
    request.port = 80;
        
    digitalWrite(power,HIGH);
        
    dht1.begin();
        
    delay(5000);  // wait 5 sec to allow A0 to stabilize
        
    Particle.publish ("Garage A0 Stabilization Complete", NULL, 60, PRIVATE);
        
    RGB.control(TRUE);
    RGB.brightness(4);
    RGB.control(FALSE);
        
	relayController.setAddress(0,0,0);
}

void loop()
{

    LightLevel = (float(4096)- analogRead(photoresistor))/40; // max value is 4095, so will always be 1 or greater.
        
    AbsoluteDiff = abs(LightLevel-LastSavedBrightness);
        
    PercentDiff =  (float(LightLevel - LastSavedBrightness))/float(LastSavedBrightness)*100; // mulitiply by 100 to get to %
        
    if(((AbsoluteDiff>=2.0 && fabs(PercentDiff)>=12.0) && ((Time.now() - LastSavedTime) >=5)) || ((Time.now() - LastSavedTime) >= MaximumLightLevelReportInterval))
    // this if statement will save basement brightness level no more than every 5 seconds, if the brightness level has changed
    // the required amount, and in case every hour 
    {
        request.path = "/api/v1.6/variables/"LIGHT_VARIABLE_ID"/values?token="TOKEN; //
            
        if((Time.now() - LastSavedTime) < MaximumLightLevelReportInterval) // then the parent if statement was triggered by light level change not time
        // save last brightness before change
        {
            sprintf(resultstr, "{\"value\":%.1f}", LastBrightness);
            request.body = resultstr;
            http.post(request, response, headers);
            
            delay(500);   
        }
            
        // in any case save current LightLevel
            
        sprintf(resultstr, "{\"value\":%.1f}", LightLevel);
        request.body = resultstr;
        http.post(request, response, headers);
            
        delay(500);
            
        LastSavedBrightness = LightLevel;
            
        LastSavedTime = Time.now();
    }
    
    LastBrightness = LightLevel; 


    // TEMP AND RH
    if(Time.now() >= LastTemp_RH_SaveTime + Temp_RH_SaveInterval )
        {
        
        h1 = dht1.getHumidity();
        // Read temperature as Farenheit
        f1 = dht1.getTempFarenheit();
        
       if ( isnan(f1) )
        {
            Particle.publish("Failed to read from garage DHT22 temp sensor!", NULL, 60,PRIVATE);
        }
        else
        {
            if (((Time.now() - LastTemp_RH_SaveTime ) > 900)  || (fabs(Last_f1 - f1) <=3.7 ) )
            {
                request.path = "/api/v1.6/variables/"TEMP_ID"/values?token="TOKEN; //
               
                sprintf(resultstr, "{\"value\":%.1f}", f1);
                request.body = resultstr;
                http.post(request, response, headers);
               
                Last_f1 = f1;
                //delay(500);
            }
        }
       
        if ( isnan(h1) )
       	{
            Particle.publish("Failed to read from garage DHT22 humidity sensor!", NULL, 60, PRIVATE);
     	}
        else
        {
            if(((Time.now() - LastTemp_RH_SaveTime ) > 900)  || (fabs(Last_h1 - h1) <=5.1  ))
            {
                request.path = "/api/v1.6/variables/"RH_ID"/values?token="TOKEN; //
                    
               sprintf(resultstr, "{\"value\":%.1f}", h1);
               request.body = resultstr;
               http.post(request, response, headers);
                   
               Last_h1 = h1;
               //delay(500);
            }   
        }
            
        LastTemp_RH_SaveTime = Time.now();            
        
    }

    // PIR CHECKING
    
    if(millis() > 75000) // wait 75 seconds after starting to use PIRs
        {
            
        if(LastPIR1_StateSaveTime == 0)
        {
            Particle.publish("Enabling Garage PIR...", NULL, 60, PRIVATE);
        }
    
        PIR1_State = PIR_Read(PIR1);
            
        if( (Time.now() - LastPIR1_StateSaveTime) >= MaximumPIR1_ReportInterval)
        {
            Particle.publish("Garage WiFi RSS: ", String(WiFi.RSSI()), 60, PRIVATE); // Put this here for convenience...will report every minute.
            
            Particle.publish("GaragePIR_State", String(PIR1_State), 60, PRIVATE);
            
            request.path = "/api/v1.6/variables/"PIR_ID"/values?token="TOKEN; //
            sprintf(resultstr, "{\"value\":%i}", PIR1_State);
            request.body = resultstr;
            http.post(request, response, headers);
         
            delay(500);
            
            if (GarageDoorState != 25) Particle.publish("Seconds Garage Door has been not closed:", String(Time.now() - GarageDoorNotClosedTime), 
                60, PRIVATE);   
            
            LastPIR1_StateSaveTime = Time.now();
        }
         
        if(PIR1_State != LastPIR1_State ) // then PIR1 changed state
        {
           
            Particle.publish("GaragePIR_State", String(PIR1_State), 60, PRIVATE);
            
            request.path = "/api/v1.6/variables/"PIR_ID"/values?token="TOKEN; 
            
            // save last PIR1 state
            sprintf(resultstr, "{\"value\":%i}", LastPIR1_State);
            request.body = resultstr;
            http.post(request, response, headers);
         
            delay(500);  
            
            //save current PIR State
            sprintf(resultstr, "{\"value\":%i}", PIR1_State);
            request.body = resultstr;
            http.post(request, response, headers);
            
            LastPIR1_StateSaveTime = Time.now();
        }
            
        LastPIR1_State = PIR1_State;
    }
    
    // CHECK / REPORT GARAGE DOOR POSITION
    
    DownSwitchState = digitalRead(DownSwitch);
    UpSwitchState = digitalRead(UpSwitch);
    
    GarageDoorState = ReadGarageDoorState();
    
    if(GarageDoorState != LastGarageDoorState && (LastGarageDoorSaveTime > 0)) // then garage door changed state
    {
        if(LastGarageDoorState == 25) // garage door changed state from shut
        {
            Particle.publish("Garage Door is Not Shut!", NULL, 60, PRIVATE);
            GarageDoorNotClosedTime = Time.now();
        }
        else if (GarageDoorState == 25) // Garage door just shut
        {
            GarageDoorNotClosedTime = 0;
            Particle.publish("Garage Door is Shut!", NULL, 60, PRIVATE);
        }
        
        request.path = "/api/v1.6/variables/"GARAGE_DOOR_ID"/values?token="TOKEN; //
        sprintf(resultstr, "{\"value\":%.0f}", LastGarageDoorState);
        request.body = resultstr;
        http.post(request, response, headers);        
        
        sprintf(resultstr, "{\"value\":%.0f}", GarageDoorState);
        request.body = resultstr;
        http.post(request, response, headers);
        
        LastGarageDoorSaveTime = Time.now();
    }
    
    if (Time.now() >= LastGarageDoorSaveTime + MaximumGarageDoorSaveInterval )
    {
        request.path = "/api/v1.6/variables/"GARAGE_DOOR_ID"/values?token="TOKEN; //
        sprintf(resultstr, "{\"value\":%.0f}", GarageDoorState);
        request.body = resultstr;
        http.post(request, response, headers);
       
        LastGarageDoorSaveTime = Time.now();
    }
    
    LastGarageDoorState = GarageDoorState;


    // Garage Door Not Closed Too Long / Time of Day Notification
    
    if  (GarageDoorState != 25 && // garage door is not closed
        (Time.now() >= (GarageDoorNotClosedNotificationInterval + GarageDoorLastNotificationTime)) &&
        (Time.now() >= (GarageDoorNotClosedNotificationThreshold + GarageDoorNotClosedTime)) &&
        (Time.hour() >= (GarageDoorHourStartNotification - DST_Adjustment - 24 )) &&
        (Time.hour() <= (GarageDoorHourStopNotification - DST_Adjustment)))
            
        {
            Particle.publish("IFTTT Level 2 Notification","Garage Door Open Too Long!", 60, PRIVATE);
            GarageDoorLastNotificationTime = Time.now();
        }

    if (NewCommand == true)
    {
        int ColonLocation = RawCommand.indexOf(":");
        
        if (ColonLocation == -1) // no ":" in RawCommand
        {
            strCommand = RawCommand;
            strArguments = ""; 
        }   
        else // there is a ":" in RawCommand
        {
            strCommand = RawCommand.substring(0,ColonLocation);
            strArguments = RawCommand.substring(ColonLocation +1); 
        }
       
        NewCommand = false;
        
        if(strCommand.equals("ToggleGarageLight"))
        {
            Particle.publish("I have been commanded to toggle garage light!", NULL, 60, PRIVATE);
            
           Particle.publish("IFTTT Level 2 Notification","Garage Light Toggle Command Received", 60, PRIVATE);
            
            relayController.turnOnRelay(1);
            delay(750);
            relayController.turnOffRelay(1);
        }
        else if (strCommand.equals("ToggleGarageDoor"))
        {
           Particle.publish("Toggle Garage Door aye aye!", NULL, 60, PRIVATE); 
           
           Particle.publish("IFTTT Level 2 Notification","Garage Door Toggle Command Received", 60, PRIVATE);           
           
            relayController.turnOnRelay(2);
            delay(750);
            relayController.turnOffRelay(2);
        }
        else if (strCommand.equals("GarageSystemReset"))
        {
            Particle.publish("Garage System Reset Command Received", NULL, 60, PRIVATE); 
            System.reset();
        }
        else
        {
            Particle.publish("I don't know what to do.....", NULL, 60, PRIVATE);
        }
    }

}

// FUNCTIONS
//

 boolean PIR_Read(int PIR_Pin)
{
    if (digitalRead(PIR_Pin) == HIGH)
    {return true;}
    else
    {return false;}
} 


float ReadGarageDoorState()
{

    if (DownSwitchState == 0 && UpSwitchState == 1) // door is down
        {return 25;}
    else if (DownSwitchState == 1 && UpSwitchState == 1) // door is intermediate
        {return 35;}
    else if (DownSwitchState == 1 && UpSwitchState == 0) // door is up
        {return 45;}
    else // this should never happen
        {return -1;} // this should never happen
}


void Command_Handler(const char *event, const char *data)
{
    RawCommand = String(data);
    NewCommand = true;
}

Credits

Rusty Morrison

Rusty Morrison

1 project • 3 followers

Comments