Craig Gibson
Published © GPL3+

Garage MC (Monitor & Controller)

Garage Door IOT Monitor and Controller, using Blynk App on your cell phone.

IntermediateShowcase (no instructions)43

Things used in this project

Hardware components

NodeMCU ESP8266 Breakout Board
NodeMCU ESP8266 Breakout Board
Any ESP8266 board will work, but I went with KeeYees Development Board WiFi WLAN Wireless Module for ESP8266 NodeMCU ESP-12E from Amazon
×1
Hall Effect Sensor - A3144
digital output, non-latching, normally HIGH
×2
Relay - 1-channel
5V 1 Channel Level Trigger Optocoupler Relay Module - Normally Open
×1
LED (generic)
LED (generic)
×2
Buzzer, Piezo
Buzzer, Piezo
×1
Resistor 221 ohm
Resistor 221 ohm
One for each LED
×2
Resistor 100 ohm
Resistor 100 ohm
For Piezo buzzer
×1
2-pin Screw Terminals
Optional
×2
Neodymium Magnet
Although any magnets will work, 2 neodymium magnets allow the Hall Sensors to be 3 cm or more from the magnet and still work.
×2
Breadboard (generic)
Breadboard (generic)
×1
Jumper wires (generic)
Jumper wires (generic)
×1
Perfboard (Prototyping Board)
×1

Software apps and online services

Arduino IDE
Arduino IDE
Fritzing
Blynk
Blynk

Hand tools and fabrication machines

Soldering iron (generic)
Soldering iron (generic)

Story

Read more

Schematics

Fritzing breadboard diagram

Breadboard diagram used for testing

Fritzing Perf/Proto Board Diagram

Perf/Proto Board diagram for the final product.
Note: the NodeMCU goes on the header rails and the top pins were not used/connected

Code

Garage MC code

C/C++
// Debugging Macros (comment out #define DEBUG to remove debugging code)
//#define DEBUG
#ifdef DEBUG
 #define DEBUG_PRINT(x)     Serial.print (x)
 #define DEBUG_PRINTDEC(x)  Serial.print (x, DEC)
 #define DEBUG_PRINTLN(x)   Serial.println (x)
#else
 #define DEBUG_PRINT(x)
 #define DEBUG_PRINTDEC(x)
 #define DEBUG_PRINTLN(x)
#endif

#ifdef DEBUG
#define BLYNK_PRINT Serial      // Blynk printouts to serial port
#endif
#include <ESP8266WiFi.h>        // WiFi Library
#include <BlynkSimpleEsp8266.h> // Blynk ESP8266 Library
#include <TimeLib.h>            // Time Library (required by Blynk RTC)
#include <WidgetRTC.h>          // Blynk RealTimeClock (RTC) widget

/*******************************************************************************
/* Global Variables
/*******************************************************************************/

//----------------------------------------
// Pins
const int WIFI_OFF_PIN = D2;    // If Wifi is not connected, turn on RED led
const int WIFI_ON_PIN = D3;     // If Wifi is connected, turn on GREEN led
const int RELAY1_PIN = D4;      // D4 is HIGH at boot (relay triggers low) - Garage Door Control
const int BUZZER_PIN = D5;      // Passive (piezo) buzzer pin (PWM pin)
const int HALL_CLOSED_PIN = D6; // Hall sensor at garage door CLOSED point
const int HALL_OPEN_PIN = D7;   // Hall sensor at garage door OPEN point

//----------------------------------------
// Hall Sensors
//  -Initialize to door AJAR (sensors are checked in Setup function anyway)
// Global variables in Interrupts must be declared 'volatile'
volatile byte hallClosedState = HIGH; // sensor to check if the door is down (CLOSED)
volatile byte hallOpenState = HIGH;   // sensor to check if the door is up (OPEN)

//----------------------------------------
// Garage Door States
const int GARAGE_CLOSED = 0;  // door CLOSED completely
const int GARAGE_AJAR = 1;    // between closed and open, could be stopped or moving
const int GARAGE_OPEN = 2;    // door OPEN completely
const char *garageStateString[] = {"CLOSED", "AJAR", "OPEN"}; // String for each state coinciding with numbers above
volatile int garageState = GARAGE_AJAR; // default to the door AJAR - V11 Blynk

// Keep track of the time in the current state & if Open/Ajar send a notification to Blynk
// Checked in DoorOpenTooLong() function
// Open Too Long if:   (now()-stateStartTime) > (MAX_OPEN_TIME+openNotifyRepeat+(allowOpenMinutes * 60))
time_t stateStartTime = 0;                  // start time for the current garage door state - V13 Blynk
const unsigned int MAX_OPEN_TIME = 300;     // If the door is open longer, send a notification (300 seconds=5min)
long openNotifyRepeat = 0;                  // time counter to re-send notifications to Blynk (seconds) about door open/ajar
volatile unsigned int allowOpenMinutes = 0; // Override allows the door to be open/ajar (minutes) longer - V12 Blynk controlled

//----------------------------------------
// Blynk WiFi and Variables
// V10 - Activate the garage door (button)
// V11 - Garage Door State
// V12 - Override for number of minutes the garage door is allowed to be open
// V13 - State start time
// V14 - Garage Door Image

WidgetRTC rtc;        // Blynk Real Time Clock widget
BlynkTimer myTimer;   // Blynk Timer - single timer object can schedule up to 16 timers
int idTimerWifiDay;   // ID for the timer to normally check WiFi & Blynk connections during day
int idTimerWifiNight; // ID for the timer if Not conntected to WiFi at night (WiFi is turned off)

// Blynk Widget color definitions
#define BLYNK_GREEN     "#23C48E"
#define BLYNK_YELLOW    "#ED9D00"
#define BLYNK_RED       "#D3435C"
const char *garageStateColor[] = { BLYNK_GREEN, BLYNK_YELLOW, BLYNK_RED };
#define BLYNK_BLUE      "#04C0F8"
#define BLYNK_DARK_BLUE "#5F7CD8"

// Your WiFi credentials
char auth[] = YOUR_BLYNK_TOKEN; // Blynk Project Token
char ssid[] = YOUR_SSID;                    // Wifi Network name
char pass[] = YOUR_SSID_PASS;                // Wifi password
int cntWifiConnect = 0; // Counter while waiting for Wifi to connect

//----------------------------------------
// Melody variables (played by piezo buzzer)
// Play a short song to let anyone in the garage know that the door is about to move
const int numNotes = 8;                                               // number of notes we're storing
const char noteNames[] = { 'c', 'd', 'e', 'f', 'g', 'a', 'b', 'C' };  // the notes used
const int frequencies[] = {262, 294, 330, 349, 392, 440, 494, 523};   // frequencies for each note
int iSong = 0;                                      // Counter for current note to play
const int songLength = 17;                          // Length must equal the total number of notes and spaces
char notes[] = "cdfda ag cdfdg gf";                 // Notes to play, a space represents a rest
int beats[] = {1,1,1,1,1,1,4,4,2,1,1,1,1,1,1,4,4};  // Notes & rests. 1=quarter-note, 2=half-note, etc.
int tempo = 113;                                    // Tempo is how fast to play the song. Lower value is faster.

//----------------------------------------
// Interrupt Declarations
// ESP board needs ICACHE_RAM_ATTR included
void ICACHE_RAM_ATTR InterruptDoorClosed();
void ICACHE_RAM_ATTR InterruptDoorOpen();

/*******************************************************************************
/* SETUP - run once initialization code
/*******************************************************************************/
void setup() {
  #ifdef DEBUG
  Serial.begin(115200);
  delay(500);             // give the Serial port time to initialize
  #endif
  
  // PINS setup first
  // Setup the relay. Ensure it is set HIGH, as it is active low (we don't want the door opening on reset)
  digitalWrite(RELAY1_PIN, HIGH); // Garage Door Relay
  pinMode(RELAY1_PIN, OUTPUT);    // Garage Door Relay
  pinMode(BUZZER_PIN, OUTPUT);    // Buzzer

  // Interrupts set for the two hall sensors
  // Both Interrupts are activated on a CHANGE of state
  pinMode(HALL_CLOSED_PIN,INPUT_PULLUP);
  attachInterrupt(digitalPinToInterrupt(HALL_CLOSED_PIN), InterruptDoorClosed, CHANGE);
  pinMode(HALL_OPEN_PIN,INPUT_PULLUP);
  attachInterrupt(digitalPinToInterrupt(HALL_OPEN_PIN), InterruptDoorOpen, CHANGE);

  // LED pins to show status of Wifi
  pinMode(WIFI_ON_PIN, OUTPUT);
  pinMode(WIFI_OFF_PIN, OUTPUT);
  SetWifiLeds(false);

  // Setup Timers
  myTimer.setInterval(9000L, CheckGarageState);                       // Check garage door state every 9 seconds
  idTimerWifiDay = myTimer.setInterval(120000L, CheckConnection);     // Check WiFi & Blynk connection every 2 min (120,000)
  idTimerWifiNight = myTimer.setInterval(1260000L, CheckConnection);  // Check for WiFi connection at night, every 21 min (1,260,000)
  myTimer.disable(idTimerWifiNight);                                  // Disable the night WiFi check (Default is Day)
  setSyncInterval(60 * 60 * 6);                                       // RTC Sync interval in seconds (60 sec * 60 min * 6 hours)

  InitConnection();  // Connect to WiFi & Blynk (must be after timers setup)

  // Get the Intial state of the garage door based on hall sensors
  hallClosedState = digitalRead(HALL_CLOSED_PIN);
  hallOpenState = digitalRead(HALL_OPEN_PIN);
  DEBUG_PRINTLN((String)"@Setup@ ClosedState: " + hallClosedState + " - OpenState: " + hallOpenState);
  
  // We are initially in an unknown garage state, so GarageStateChanged
  // First check of the states the time is likely not properly synchronized
  myTimer.setTimeout(1000, GarageStateChanged);
}

/*******************************************************************************
/* MAIN LOOP
 *  Run Blynk and the Blynk Timer which runs the code
 */
/*******************************************************************************/
void loop() {
  if (Blynk.connected()) {Blynk.run();} // run Blynk if it's connected
  myTimer.run();                        // Blynk Events Timer (run either way)
}

/*******************************************************************************
/*******************************************************************************
/* Interrupts
/*******************************************************************************/

// Interrupt for the Hall Sensor at the Door CLOSED point
// activated on CHANGE
// Sets a timer to immediately CheckGarageState
void InterruptDoorClosed() {
  hallClosedState = digitalRead(HALL_CLOSED_PIN); // get the sensor state
  myTimer.setTimeout(10, GarageStateChanged);     // set short timer to run function immediately
}

// Interrupt for the Hall Sensor at the Door OPEN point
// activated on CHANGE
// Sets a timer to immediately CheckGarageState
void InterruptDoorOpen() {
  hallOpenState = digitalRead(HALL_OPEN_PIN);   // get the sensor state
  myTimer.setTimeout(10, GarageStateChanged);   // set short timer to run function immediately
}

/*******************************************************************************
/*******************************************************************************
/* Functions
/*******************************************************************************/
/*----------------------------------------------------
 * GarageStateChanged
 * Change in the state of the garage door (ie. door has moved)
 * Interrupts change the garageStateChange
*/
void GarageStateChanged() {
  stateStartTime = now(); // new state start time
  openNotifyRepeat = 0;   // Reset the Door Open/Ajar repeat time for new state

  // figure out the new state of the door
  if ((hallClosedState == LOW) && (hallOpenState == HIGH)) {          // Garage door is CLOSED
    garageState = GARAGE_CLOSED;
  } else if ((hallClosedState == HIGH) && (hallOpenState == LOW)) {   // Garage door is FULLY OPEN
    garageState = GARAGE_OPEN;
  } else if ((hallClosedState == HIGH) && (hallOpenState == HIGH)) {  // Garage door is AJAR or MOVING
    garageState = GARAGE_AJAR;
  } else {  // can't be in any other state, so we have an error
    Blynk.notify("!ERROR! - GarageStateChanged()");
    DEBUG_PRINTLN("!ERROR! - GarageStateChanged()");
  }

  #ifdef DEBUG
  char buff[20];
  strftime(buff, 20, "%Y-%m-%d %H:%M:%S ", localtime(&stateStartTime));
  Serial.println((String)"@GarageStateChanged@ - State Start: " + buff + 
                " - hallClosedState=" + hallClosedState + " hallOpenState=" + hallOpenState + 
                " - garageState=" + garageStateString[garageState]);
  #endif
  
  CheckGarageState();   // Update Garage State
  UpdateBlynkWidgets(); // Update Blynk app with new state
}

/*----------------------------------------------------
 * CheckGarageState
 * Main function to check the state of the garage door
 * Run by a Blynk Timer every 10 seconds
 * also called immediately from a timer after an Interrupt
*/
void CheckGarageState() {
  unsigned int secondsInState = 0; // used to notify time door open
  String strNotify = "Door " + (String)garageStateString[garageState] + " : ";
  
  // do things based on the current garage state
  switch (garageState) {
    case GARAGE_CLOSED:
      secondsInState = now() - stateStartTime;
      DEBUG_PRINTLN(strNotify + (String)secondsInState + " seconds.");
      break;
    case GARAGE_AJAR:
    case GARAGE_OPEN:
      if (secondsInState = DoorOpenTooLong()) {
        strNotify = strNotify + (String)(secondsInState/60) + " min " + (String)(secondsInState%60) + " sec!";
        Blynk.notify(strNotify);
        DEBUG_PRINTLN(strNotify);
      }
      break;
  }
}

//----------------------------------------
// Determine if the garage door has been open for too long
// Return: Number of seconds open if longer than maxOpenTime
//         Otherwise, return zero
unsigned int DoorOpenTooLong() {
  unsigned int secondsOpen = now() - stateStartTime;                                      // How long have we been in this State
  unsigned int maxOpenTime = MAX_OPEN_TIME + openNotifyRepeat + (allowOpenMinutes * 60);  // Get the Maximum Open Time
  DEBUG_PRINTLN((String)"@DoorOpenTooLong@ - MaxOpenTime: " + maxOpenTime + " - Time open: " + secondsOpen);

  if (secondsOpen > maxOpenTime) {
    openNotifyRepeat += MAX_OPEN_TIME; // ensure the Blynk notify is repeated every MAX_OPEN_TIME seconds
  } else {
    secondsOpen = 0;  // return 0 = Door hasn't been open too long
  }

  // Error Case - Time not properly synchronized if seconds open
  //              is greater than 11 hours (60 sec * 60 min * 11)
  // So set the stateStartTime to now()
  // It will still send a notification until next time checked
  if (secondsOpen > 39600) {
    stateStartTime = now();
    openNotifyRepeat = 0;
    secondsOpen = 0;
    DEBUG_PRINTLN("!ERROR! DoorOpenTooLong() - Time not properly Synchronized");
  }

  return secondsOpen;
}

/*******************************************************************************
/*******************************************************************************
/* WiFi & Blynk Connections
/*******************************************************************************/

// Used for the Initial Connection
// Should only be run once from Setup
void InitConnection() {
  Blynk.config(auth); // Only needs to be done once
  CheckConnection();
}

// Called by Timers to Check the WiFi & Blynk Connection
void CheckConnection() {
  DEBUG_PRINTLN("@CheckConnection@");

  // Check the Blynk Connection First
  // If it's connected, WiFi is Connected so don't have to do anything
  if (!Blynk.connected()) {
    if (WiFi.status() != WL_CONNECTED) {
      WiFi.begin(ssid, pass);
      DEBUG_PRINT("Connecting");
    }
    cntWifiConnect = 0;
    myTimer.setTimeout(500, ConnectionTimer); // Run the Connection Timer
  }

  // Ensure that the time is set (otherwise hour is zero which may not be correct)
  // If time hasn't been set just use default day timer from SETUP
  // Time is Set, then pick which timer to use
  if (timeStatus() == timeSet) {
    int theHour = hour();
    if ((theHour == 23) || (theHour < 6)) {   // Home WiFi is turned off at NIGHT
      myTimer.disable(idTimerWifiDay);
      myTimer.enable(idTimerWifiNight);
      DEBUG_PRINTLN((String)"Night - Hour=" + theHour);
    } else {                                  // DAY
      myTimer.enable(idTimerWifiDay);
      myTimer.disable(idTimerWifiNight);
      DEBUG_PRINTLN((String)"Day - Hour=" + theHour);
    }
  }
}

// ConnectionTimer
// None blocking timer which sets up the WiFi & Blynk connection
void ConnectionTimer() {
  if (WiFi.status() != WL_CONNECTED) {  // NOT connected to Wifi
    DEBUG_PRINT(".");
    if (cntWifiConnect < 16) {  // Only try for so long
      cntWifiConnect++;
      myTimer.setTimeout(500, ConnectionTimer);
    } else {
      DEBUG_PRINTLN("@ConnectionTimer@ WiFi NOT connected");
      SetWifiLeds(false); // Turn on the Not connect LED
      WiFi.disconnect();  // Stop trying to connect
    }
  } else {                              // CONNECTED to Wifi
    DEBUG_PRINT("IP address: ");
    DEBUG_PRINTLN(WiFi.localIP());

    if (!Blynk.connected()) {           // Blynk is not connected, so try to connect
      DEBUG_PRINTLN("@ConnectionTimer@ Blynk NOT Connected");
      Blynk.connect();
    }
  }
}

// Turn on the appropriate LED based on WiFi Status
void SetWifiLeds(bool wifiOn) {
  digitalWrite(WIFI_ON_PIN, wifiOn);
  digitalWrite(WIFI_OFF_PIN, !wifiOn);
}

/*******************************************************************************
/*******************************************************************************
/* Activate the Garage Door
/*******************************************************************************/

// The door plays a song prior to activating (opening/closing) the door
// So, someone could Activate the door from Blynk, then someone else use the garage controller
// To prevent two activations, we check that the door state hasn't changed from button press to actual activation
int activateState = 0;

//-------------------------------------------
// send signal to relay (garage door) to open or close
// from the Blynk Widget
void ActivateDoor() {
  DEBUG_PRINTLN("Activate Door pressed");
  activateState = garageState;                        // get the garage state when the button was pressed
  long songLength = PlaySong();                       // Play a song to notify anyone in the garage that the door is about to move
  myTimer.setTimeout(songLength, ActivateDoorRelay);  // Wait for the song to finish, then activate the relay
}

//-------------------------------------------
void ActivateDoorRelay() {
  // only trigger the relay if the state hasn't changed since the button was pressed
  if (garageState == activateState) {
    digitalWrite(RELAY1_PIN, LOW);                    // trigger the relay to open/close the door
    myTimer.setTimeout(300, ActivateDoorRelayReset);  // Timer to wait 300ms
  }
}

//-------------------------------------------
void ActivateDoorRelayReset() {
  digitalWrite(RELAY1_PIN, HIGH);                   // Deactivate the door relay
  Blynk.virtualWrite(V10, 0);                       // Reset the Blynk switch after the door is activated
  //TestButton(); // for testing, sets hall sensor states
}

//-------------------------------------------
// Test Function
// manually set the states of the Hall Sensors for testing purposes
void TestButton() {
  switch (garageState) {
    case 0: // CLOSED so change to AJAR
      hallClosedState = HIGH;
      hallOpenState = HIGH;
      break;
    case 1: // AJAR so change to OPEN
      hallClosedState = HIGH;
      hallOpenState = LOW;
      break;
    case 2: // OPEN so change to CLOSED
      hallClosedState = LOW;
      hallOpenState = HIGH;
      break;
  }
  myTimer.setTimeout(10, GarageStateChanged);   // set short timer to run function immediately
}

/*******************************************************************************
/*******************************************************************************
/* PlaySound
/*******************************************************************************/

//-------------------------------------------
// - Play a sound so that people know that the garage door is about to move
// - None blocking, therefore...
// - Returns: long of the duration of the song in milliseconds
long PlaySong() {
  iSong = 0;                              // reset to first note of song
  myTimer.setTimeout(10, PlaySongTimer);  // start playing the song (none blocking)

  // Calculate the length of the song in milliseconds to return
  long duration = 0;
  for (int i = 0; i < songLength; i++) {        // step through the song arrays
    duration += (beats[i] * tempo) + (tempo/10); // length of note/rest in ms + brief pause between notes
  }
  return duration;
}

//-------------------------------------------
// Only called by timer set in PlaySong
void PlaySongTimer() {
  int duration = 0;
  if (iSong < songLength) {           // step through the song array
    duration = beats[iSong] * tempo;  // length of note/rest in ms
    if (notes[iSong] != ' ') {        // if this is not a rest, play the note
      tone(BUZZER_PIN, Frequency(notes[iSong]), duration);
    }
    iSong++;                          // increment the counter to go to the next note
    // timer to wait for tone/rest to finish + brief pause between notes, then return to function
    myTimer.setTimeout(duration+(tempo/10), PlaySongTimer);
  }
}

//-------------------------------------------
// This function takes a note character (a-g), and returns the
// corresponding frequency in Hz for the tone() function.
int Frequency(char note) {
  // Now we'll search through the letters in the array, and if
  // we find it, we'll return the frequency for that note.
  for (int i = 0; i < numNotes; i++) {    // Step through the notes
    if (noteNames[i] == note) {           // Is this the one?
      return(frequencies[i]);             // Yes! Return the frequency
    }
  }
  return(0);
}

/*******************************************************************************
/*******************************************************************************
/* Blynk Functions
/*******************************************************************************/

// BLYNK_CONNECTED is done when connected to the Blynk server
BLYNK_CONNECTED() {
  rtc.begin();                  // Synchronize RTC time on Blynk connection
  Blynk.virtualWrite(V12, 0);   // Initialize the open override to zero in Blynk app
  SetWifiLeds(true);            // Set LED to show WiFi & Blynk connected
  myTimer.setTimeout(1000, GarageStateChanged);
}

//----------------------------------------
// Blynk Switch Widget pressed
// Activate the garage door using the relay
//
// IFTTT sends a "1" to the Blynk button widget to trigger the door
// it has to be reset to "0", not sure how to do multiple actions through IFTTT
BLYNK_WRITE(V10) {
  if (param.asInt()) {  // only do it on button "rising=1"
    ActivateDoor();     // trigger the door
  }
}

//----------------------------------------
// Blynk slider widget changed
// Sets the number of minutes the door is allowed to be open
// until the app starts sending messages again
BLYNK_WRITE(V12) {
  allowOpenMinutes = param.asInt();
}

//----------------------------------------
// Update information in the Blynk widgets
void UpdateBlynkWidgets() {
  if (!Blynk.connected()) {   // Blynk isn't connected, so don't do anything
    return;
  }

  if (garageState == GARAGE_CLOSED) {
    Blynk.virtualWrite(V12, 0); // garage is closed so reset the open override to zero
  }

  // Update the Garage State Widget with text & color
  Blynk.virtualWrite(V11, garageStateString[garageState]);
  Blynk.setProperty(V11, "color", garageStateColor[garageState]);

  // Update the Garage Image
  Blynk.virtualWrite(V14, garageState+1);

  // Tell Blynk the State Start Time as a String
  char strStateTime[20];
  strftime(strStateTime, 10, "%a %H:%M ", localtime(&stateStartTime));
  Blynk.virtualWrite(V13, strStateTime);
}

Credits

Craig Gibson

Craig Gibson

0 projects • 0 followers

Comments

Add projectSign up / Login