PlanetJARK
Published © GPL3+

Just Another Garage Door Opener

Use of a Particle Argon to open/close a garage door with a Particle Xenon used as a mesh limit switch & smartphone control via Blynk app.

IntermediateShowcase (no instructions)16 hours2,465
Just Another Garage Door Opener

Things used in this project

Hardware components

Argon
Particle Argon
AUD$44.95 I was lucky and get them for $15 each on the pre-order https://core-electronics.com.au/particle-argon-kit.html
×1
Xenon
Particle Xenon
AUD$26.95 I was lucky and get them for $9 each on the pre-order https://core-electronics.com.au/particle-xenon-kit.html
×1
Duinotech 5Vdc Relay XC4419
AUD$5.49 each
×1
Jumper Wire Leads Socker to Socket 150mm long - 6 required
AUD$5.95 for 40 pieces
×1
Magnetic Reed Switch LA5070
AUD$5.95 each
×1
Resistor 10k ohm
Resistor 10k ohm
AUD$14.04 Grab a variety pack to keep you going on all your projects. https://core-electronics.com.au/resistor-kit-1-4w-500-total.html
×1
Slide Switch with Male/Female JST connectors
AUD$3.95 each
×1
18650 Rechargeable Li-Ion Battery 2600mAh 3.7V SB2308
AUD$15.95 each
×1
Battery Holder for 18650 sized battery
AUD$2.95 each
×1

Software apps and online services

Particle Build Web IDE
Particle Build Web IDE
Blynk
Blynk
Fusion 360
Autodesk Fusion 360
Ultimaker Cura Slicing Software

Hand tools and fabrication machines

Snapmaker 3 in 1 3D Printer

Story

Read more

Custom parts and enclosures

Thingiverse Link

All the individual 3D Printer .stl files for each component or the complete .f3d file for each case can be sourced from Thingiverse.

Schematics

Particle Argon Garage Door Controller

This is the Fritzing .fzz file for the Argon

Particle Xenon Wireless Garage Door Limit Switch

This is the Fritzing .fzz file for the Xenon

Particle Argon Garage Door Controller Layout

Particle Xenon Wireless Garage Door Limit Switch Layout

Particle Argon Garage Door Controller Schematic

Particle Xenon Wireless Garage Door Limit Switch Schematic

Code

garagedoorlimit.ino

C/C++
This is the C++ code used on the Particle Xenon
// Version 1 PlanetJARK 20191020
// IO mapping
// SDA : spare
// SCL : spare
// D2 : spare
// D3 : spare
// D4 : spare
// D5 : spare
// D6 : spare 
// D7 : Onboard LED
// D8 : garage_reed_OPEN: high means contact open (garage door opened), low means contact closed (garage door closed)
// A0 : spare
// A1 : spare
// A2 : spare
// A3 : spare
// A4 : spare
// A5 : spare

// Declare Variables

int garage_reed_OPEN = D8;
int garage_LED = D7;
int garage_CLOSED;
int garage_OPEN;
int last_OPEN_state;
float Batt_voltage ;
int Batt_charging, hasUsbPower ;
unsigned long PublishInterval = 10000;
unsigned long lastPublishTime = 0;
unsigned long SleepWaitTime = 20000;
unsigned long lastSleepWaitTime = 0;
char msg[128];

// The code in setup() runs once when the device is powered on or reset. Used for setting up states, modes, etc
void setup() {
    
  pinMode(garage_reed_OPEN, INPUT);
  pinMode(garage_LED, OUTPUT);
  pinMode(PWR, INPUT); // PWR: 0=no USB power, 1=USB powered
  pinMode(CHG, INPUT); // CHG: 0=charging, 1=not charging

}
/* loop(), runs all the time. Over and over again. */

void loop() {
    
  Batt_voltage = analogRead(BATT) * 0.0011224;
  hasUsbPower = digitalRead(PWR); 
  Batt_charging = (hasUsbPower && !digitalRead(CHG));  

  garage_OPEN = digitalRead(garage_reed_OPEN);
  if (garage_OPEN == HIGH) {  // 1 = garage open
    digitalWrite(garage_LED, HIGH); // Led On = garage open
    garage_CLOSED = LOW ;
    lastSleepWaitTime = millis();
  }    
  else { 
    digitalWrite(garage_LED, LOW); // Led Off = garaged closed
    garage_CLOSED = HIGH ;
  }
  delay(50);
  
  //Send the data.
  if (((millis() - lastPublishTime) > PublishInterval) || (garage_OPEN != last_OPEN_state)) {  // process on change of state
      snprintf(msg, sizeof(msg), "{\"GarageClosed\":\"%0i\",\"GarageOpen\":\"%0i\",\"BattVoltage\":\"%0f\", \"BattCharging\":\"%d\"}", garage_CLOSED, garage_OPEN, Batt_voltage, Batt_charging );
      Mesh.publish("XenonGDL", msg);
      last_OPEN_state = garage_OPEN;
      lastPublishTime = millis();
  }
  // put the Xenon to sleep 
  if (((millis() - lastSleepWaitTime) > SleepWaitTime) && (garage_CLOSED == HIGH)) {  
    System.sleep(D8, CHANGE, 28800);  // wake on rising D8 signal or every 8 hours.
    waitUntil(Mesh.ready) ;
    lastSleepWaitTime = millis();
  }
}

garagedoormaster.ino

C/C++
This is the C++ code used on the Particle Argon
// Version 1 PlanetJARK 20191020
// IO mapping
// D0 : spare
// D1 : spare
// D2 : spare
// D3 : spare
// D4 : spare
// D5 : spare
// D6 : spare
// D7 : onboard LED
// D8 : garage_RELAY: This output is connected to the garage remote open/close contact switching 24Vdc

// A0 : spare
// A1 : spare
// A2 : spare
// A3 : spare
// A4 : spare
// A5 : spare

// Virtual I/O  used below is used within the Blynk App. 
// V0 : Garage Door GPS
// V1 : Garage Door is Closed LED
// V2 : Garage Door is Open LED
// V3 : Garage Door Button
// V4 : Garage Limit Battery Voltage
// V5 : spare

#include <blynk.h>
#include <JsonParserGeneratorRK.h>
#include <math.h>

#define BLYNK_GREEN     "#23C48E"
#define BLYNK_BLUE      "#04C0F8"
#define BLYNK_ORANGE    "#ED9D00"
#define BLYNK_RED       "#D3435C"
#define BLYNK_DARK_BLUE "#5F7CD8"

int garage_RELAY = D8;
int garage_LED = D7;
int garage_CLOSED;
int garage_OPEN;
int garagedoor_BUTTON;
int last_OPEN_state;
bool garage_OPEN_alarm = false;
bool garage_OPEN_geo_alarm = false;
bool battery_LOW_alarm = false;
unsigned long BlynkPublishInterval = 10000;
unsigned long lastBlynkPublishTime = 0;
unsigned long GD_Open_TimeoutInterval = 900000;
unsigned long GD_Open_Time = 0;

float latitude, longitude ;
float Batt_voltage ; 
int Batt_charging ; // 0 = charging, 1 = not charging
double geofencelat = 0 ;  // put your lat here lat, long - centre location of geofence
double geofencelong = 0 ;  // put your long here
double laterror = 0 ;
double longerror = 0 ;
bool insidegeofence = true ;
bool isFirstConnect = true;

char auth[] = "put your auth code here"; // authorisation code from Blynk App.
char msg[128];

void printJson(JsonParser &jp);

// Create a parser to handle 2K of data and 100 tokens
JsonParserStatic<2048, 100> jsonParser;

// this will be called whenever an event arrives we subscribed to
void myHandler(const char *event, const char *data)
{
  strncpy(msg, data, sizeof(msg)-1); // copy the incoming data to the global variable msg (with boundary limit)

  jsonParser.clear();         // make sure the parser buffer is fresh and empty
  jsonParser.addString(data); // copy the received data into the parser buffer for it to work with
  if (jsonParser.parse())     // let the parser do its job and split up the data internally
  {
    // first ask the parser for the value connected with the key "Garage Closed"
    // if this is successful pop that value into the provided variable garage_CLOSED
    // if not, the function will return false which - in turn - triggers the error output
    if (!jsonParser.getOuterValueByKey("GarageClosed", garage_CLOSED))
    {
      //Do Nothing
    }
    // Repeat for other variables
    // if(!someBoolean) is short hand for if(someBoolean == false)
    if (!jsonParser.getOuterValueByKey("GarageOpen", garage_OPEN))
    {
      // Do Nothing
    }
    
    if (!jsonParser.getOuterValueByKey("BattVoltage", Batt_voltage))
    {
      // Do Nothing
    }
    
    if (!jsonParser.getOuterValueByKey("BattCharging", Batt_charging))
    {
      // Do Nothing
    }
    
  }
  
  if (garage_OPEN == 1) {  // 1 = garage open
    digitalWrite(garage_LED, HIGH); // Led On = garage open
  }    
  else { 
    digitalWrite(garage_LED, LOW);// Led Off = garaged closed
  }
}

// The code in setup() runs once when the device is powered on or reset. Used for setting up states, modes, etc
void setup() {
    
    Serial.begin(9600);
    Blynk.begin(auth);
    
    while (Blynk.connect() == false) {
    // Wait until connected
    }
    
    pinMode(garage_RELAY, OUTPUT);
    pinMode(garage_LED, OUTPUT);
    
    Mesh.subscribe("XenonGDL", myHandler);  //Collect data from Xenon on the Garage Door Closed Limit Switch
}

// loop(), runs all the time. Over and over again.

void loop() {
    
  Blynk.run();
  
  if ((millis() - lastBlynkPublishTime > BlynkPublishInterval) || (garage_OPEN != last_OPEN_state)) {  // process on change of state
    
    if ((garage_CLOSED == 0) && (garage_OPEN == 1)) {
        
        Blynk.virtualWrite(V1, 0);
        Blynk.virtualWrite(V2, 255);
        Blynk.setProperty(V3, "offLabel", "CLOSE");
        Blynk.setProperty(V3, "onLabel", "OPEN");
    }
    
    if ((garage_CLOSED == 1) && (garage_OPEN == 0)) {
        
        Blynk.virtualWrite(V1, 255);
        Blynk.virtualWrite(V2, 0);
        Blynk.setProperty(V3, "offLabel", "OPEN");
        Blynk.setProperty(V3, "onLabel", "CLOSE");
    }
    
    if ((garage_OPEN == 1) && (garage_OPEN_alarm == true)) {
      Blynk.setProperty(V2, "color", BLYNK_RED);
      Blynk.setProperty(V2, "label", "GARAGE OPEN > 15MINS");
    } else {
        Blynk.setProperty(V2, "color", BLYNK_ORANGE);
        Blynk.setProperty(V2, "label", "GARAGE OPEN");
    }
  
    if (insidegeofence == false) {
        Blynk.setProperty(V3, "onBackColor", BLYNK_ORANGE);
        Blynk.setProperty(V3, "offBackColor", BLYNK_ORANGE);
    } else {
        Blynk.setProperty(V3, "onBackColor", BLYNK_GREEN);
        Blynk.setProperty(V3, "offBackColor", BLYNK_GREEN);
    }
  
    Blynk.virtualWrite(V4, Batt_voltage);
    //Full charge (V >= 4.2V), Nominal (4.2 > V > 3.5), Low/Needs Recharge (3.5 > V >= 3.4), Critical (3.4 > V)
    if (Batt_voltage >= 4.2) {
        Blynk.setProperty(V4, "color", BLYNK_GREEN);
        Blynk.setProperty(V4, "label", "Batt Charged");
        battery_LOW_alarm = false ; 
    } else if (4.2 > Batt_voltage > 3.5) {
        Blynk.setProperty(V4, "color", BLYNK_GREEN);
        Blynk.setProperty(V4, "label", "Battery Voltage");
        battery_LOW_alarm = false ; 
    } else if (3.5 > Batt_voltage >= 3.4) {
        Blynk.setProperty(V4, "color", BLYNK_ORANGE);
        Blynk.setProperty(V4, "label", "Recharge");
        battery_LOW_alarm = true  ;
    } else if (3.4 > Batt_voltage) {
        Blynk.setProperty(V4, "color", BLYNK_RED);
        Blynk.setProperty(V4, "label", "Recharge");
        battery_LOW_alarm = true  ;
    } else {
        Blynk.setProperty(V4, "color", BLYNK_GREEN);
        Blynk.setProperty(V4, "label", "Battery Voltage");
         battery_LOW_alarm = false ; 
    }  
    
    if (Batt_charging == 1) {
        
        Blynk.setProperty(V4, "label", "Charging");
    }
    
    if ((battery_LOW_alarm == true) && (Batt_voltage != 0)){
        battery_LOW_alarm == false ;
        Blynk.notify("Garage Door Xenon requires recharge");   
    }
  
    last_OPEN_state = garage_OPEN;
    lastBlynkPublishTime = millis(); 
  }  // end of Blynk Publish If Loop
  
  if (garage_CLOSED == 1) {
    garage_OPEN_alarm = false ;
    garage_OPEN_geo_alarm = false ;
    GD_Open_Time = millis();
  }
 
  if (((millis() - GD_Open_Time) > GD_Open_TimeoutInterval) && (garage_OPEN_alarm == false)) {
    garage_OPEN_alarm = true ;
    Blynk.notify("Garage Door has been open for longer than 15mins!");          
    Blynk.email("Front Garage Door Open", "Check the Front Garage Door.  It has been left open for more than 15mins");
    Blynk.setProperty(V2, "color", BLYNK_RED);
    Blynk.setProperty(V2, "label", "GARAGE OPEN > 15MINS");
  }
  
  if ((garage_OPEN == 1) && (insidegeofence == false) && (garage_OPEN_geo_alarm == false)) {
    garage_OPEN_geo_alarm = true ;
    Blynk.notify("Garage Door has been left open with house unoccupied");          
    Blynk.email("Front Garage Door Open", "Check the Front Garage Door.  It has been left open with the house unoccupied");
  }
  
  
  // GPS Geofencing code
  laterror = fabs (geofencelat - latitude ) ;
  longerror = fabs( geofencelong - longitude )  ;
 
  // compare lat/long error.  Rough calc based upon 0.0001 equalling approx. 11m at my lat. location. Ie Approx 0.2km2 geofence.
  if ((laterror <= 0.001) && (longerror <= 0.001 )  && (latitude != 0) && (longitude != 0 )) { // Check only if lat/long not zero ie receiving signal
       insidegeofence = true ;
  } else {
      insidegeofence = false ;
  }
  
}

BLYNK_CONNECTED() // runs every time Blynk connection is established
{
  if (isFirstConnect)
    {      // Request server to re-send latest values for all pins
    Blynk.syncAll();
    isFirstConnect = false;
    }
}

// GPS data from phone via Blynk
BLYNK_WRITE(0) {
  latitude = param[0].asFloat(); 
  longitude = param[1].asFloat();
}

BLYNK_WRITE(3) {   
  garagedoor_BUTTON = param.asInt(); // Get button status from Blynk as integer
  
  if ((garagedoor_BUTTON == 1) && (insidegeofence == true )) {  // 1 = garage open
    digitalWrite(garage_RELAY, HIGH); // Led On = garage open
  }    
  else { 
    digitalWrite(garage_RELAY, LOW);// Led Off = garaged closed
  }
}

Credits

PlanetJARK

PlanetJARK

1 project • 2 followers

Comments