Sotirios Zormpas
Published © GPL3+

EchoGarage

Universal cloud-based Garage Controller with remote Door Opener and Parking Assistant.

IntermediateFull instructions provided4 hours6,293
EchoGarage

Things used in this project

Hardware components

Wemos D1 Mini
Espressif Wemos D1 Mini
×1
SparkFun Logic Level Converter - Bi-Directional
SparkFun Logic Level Converter - Bi-Directional
×2
Ultrasonic Sensor - HC-SR04 (Generic)
Ultrasonic Sensor - HC-SR04 (Generic)
×2
USB-A to Micro-USB Cable
USB-A to Micro-USB Cable
×1
NeoPixel Ring: WS2812 5050 RGB LED
Adafruit NeoPixel Ring: WS2812 5050 RGB LED
×1
Capacitor 1000 µF
Capacitor 1000 µF
×1
Resistor 475 ohm
Resistor 475 ohm
×1
relay
×1
SparkFun wire
×1
project box
×1
cat 5e cable
×1

Software apps and online services

Arduino IDE
Arduino IDE
Thinger.io Platform
Thinger.io Platform

Hand tools and fabrication machines

Soldering iron (generic)
Soldering iron (generic)

Story

Read more

Custom parts and enclosures

HC-SR04 case

made by Ernani1 at thingiverse.com

Schematics

EchoGarage diagram

Fritzing breadboard connections

Code

EchoGarage Firmware

Arduino
Arduino sketch to upload in ESP8266
#include <FS.h>                          // File system wrapper, this library is part of the esp8266 core for Arduino.
#include <ESP8266WiFi.h>                 // ESP8266 Core WiFi Library, this library is part of the esp8266 core for Arduino.
#include <DNSServer.h>                   // Local DNS Server used for redirecting all requests to the configuration portal, this library is part of the esp8266 core for Arduino.
#include <ESP8266WebServer.h>            // Local WebServer used to serve the configuration portal, this library is part of the esp8266 core for Arduino.
#include <WiFiManager.h>                 // WiFi Connection manager with fallback web configuration portal. https://github.com/tzapu/WiFiManager
#include <ThingerWebConfig.h>            // Client Library to connect your IoT devices to the thinger.io platform. https://github.com/thinger-io/Arduino-Library
#include <NewPing.h>                     // Advanced library for interfacing with HC-SR04 sensor with many features. http://playground.arduino.cc/Code/NewPing)
#include <SimpleTimer.h>                 // Simple library based on millis() to launch timed actions. http://playground.arduino.cc/Code/SimpleTimer
#include <Adafruit_NeoPixel.h>           // Arduino library for controlling single-wire-based LED pixels and strip. https://github.com/adafruit/Adafruit_NeoPixel
#include <EEPROM.h>                      // ESP8266 Core Library that stores value into the EEPROM memory, this library is part of the esp8266 core for Arduino.
 
#define CAR_TRIGGER_PIN  12                  // ESP8266 chip pin tied to trigger pin on car sensor. It corresponds to pin D6 on WeMos D1 mini board.
#define CAR_ECHO_PIN     14                  // ESP8266 chip pin tied to echo pin on car sensor. It corresponds to pin D5 on WeMos D1 mini board.
#define DOOR_TRIGGER_PIN  4                  // ESP8266 chip pin tied to trigger pin on door sensor. It corresponds to pin D2 on WeMos D1 mini board.
#define DOOR_ECHO_PIN     5                  // ESP8266 chip pin tied to echo pin on door sensor. It corresponds to pin D1 on WeMos D1 mini board.
#define PINGSPEED 50                         // Time interval that sensors are pinged at (milliseconds between pings).
#define RELAY 16                             // ESP8266 chip pin tied to D1 pin on relay board. It corresponds to pin D0 on WeMos D1 mini board.
#define NEOPIXEL 13                          // ESP8266 chip pin tied to input data pin on neopixel ring. It corresponds to pin D7 on WeMos D1 mini board.

int doorDistanceTimerId;                       // Holds the specified Timer slot for distance_update() function.
byte maxDoorDistance = 60;                     // Allows setting of a maximum distance in cm where pings beyond that distance are read as no ping or clear (defauld 60).
unsigned int currentDoorDistance = 0;          // Holds current ultrasonic ping distance in cm.
unsigned int previousDoorDistance = 0;         // Holds previous ultrasonic ping distance in cm.
byte doorStatus = 0;                           // Holds door status (4:just open, 1:still open, 2:just closed, 3:still closed).
String doorState = "";                         // Holds current door state (Open or Closed).
unsigned long doorStartTime_ms = 0;            // Holds last time when the door was opened.
unsigned long doorTimeInterval_ms = 0;         // Holds time interval in ms since door was opened.
byte doorAlertInterval = 0;                    // Configuration option in min that used to define door notifications.
unsigned long doorAlertTimer = 0;              // Holds the specified Timer slot for door alert() function.
byte autoCloseInterval = 0;                    // Configuration option in min that used to enable door auto close feature, zero means disabled (default 0).
unsigned long autoCloseTimer = 0;              // Holds the specified Timer slot for energize_relay() function.

int carDistanceTimerId;                        // Holds the specified Timer slot for distance_update() function.
byte maxCarDistance = 50;                      // Allows setting of a maximum distance in cm where pings beyond that distance are read as no ping or clear (defauld 50).
unsigned int currentCarDistance = 0;           // Holds current ultrasonic ping distance in cm.
unsigned int previousCarDistance = 0;          // Holds previous ultrasonic ping distance in cm.
byte carStatus = 0;                            // Holds car status (2:just out, 3:still out, 4:just in, 1:still in).
String carState = "";                          // Holds current car state (Out or In).
unsigned long carStartTime_ms = 0;             // Holds last time when the car was taken out.
unsigned long carTimeInterval_ms = 0;          // Holds time interval in ms since the car was taken out.
byte carAlertInterval = 0;                     // Configuration option in min that used to to enable car notifications, zero means disabled (default 0).
unsigned long carAlertTimer = 0;               // Holds the specified Timer slot for car alert() function.
unsigned long neopixelsStartTime_ms = 0;       // Holds last time in ms when the neopixels shown off.
unsigned long neopixelsTimeInterval_ms = 0;    // Holds time interval in ms since the neopixels show off.
int i = 0;                                     // Holds the positions of leds at neopixel ring.

ThingerWebConfig thing;                                                 // ThingerWebConfig object that creates an access point for initial setup, connects the ESP8266 to WiFi and the cloud server.
NewPing sonarDoor(DOOR_TRIGGER_PIN, DOOR_ECHO_PIN, maxDoorDistance);    // NewPing door object setup of pins and maximum distance.
NewPing sonarCar(CAR_TRIGGER_PIN, CAR_ECHO_PIN, maxCarDistance);        // NewPing car object setup of pins and maximum distance.
Adafruit_NeoPixel strip = Adafruit_NeoPixel(12, NEOPIXEL);              // Declares a NeoPixel object, parameter 1 = number of pixels in strip, Parameter 2 = pin for data.
SimpleTimer timer;                                                      // There must be one global SimpleTimer object.

void setup() {
  
  EEPROM.begin(512);
  maxDoorDistance = EEPROM.read(0);
  maxCarDistance = EEPROM.read(1);
  doorAlertInterval = EEPROM.read(2);
  carAlertInterval = EEPROM.read(3);
  autoCloseInterval = EEPROM.read(4);  
  doorAlertTimer = doorAlertInterval;
  carAlertTimer = carAlertInterval;
  autoCloseTimer = autoCloseInterval;
    
  pinMode(RELAY, OUTPUT);              // set pin D0 (GPIO16) output.
  digitalWrite(RELAY, LOW);           // set pin D0 (GPIO16) low.

// Prepare the data pin for NeoPixel output and initialize all pixels to 'off'.
  strip.begin();
  strip.setBrightness(50);
  strip.show();
  
// initialize door state after reboot
  currentDoorDistance = sonarDoor.ping_cm(maxDoorDistance);                    // Send a ping and get the distance in whole centimeters.
  if (currentDoorDistance != 0){
      doorStartTime_ms = millis();      
      doorState = "Open";      
  } else if (currentDoorDistance == 0){
             doorState = "Closed";
             doorTimeInterval_ms = 0;                           
    }

  delay(50);

// initialize car state after reboot
  currentCarDistance = sonarCar.ping_cm(maxCarDistance);                      // Send a ping and get the distance in whole centimeters.
  if (currentCarDistance != 0){
      show_off_neopixels ();
      neopixelsStartTime_ms = millis();
      carState = "In";
      carTimeInterval_ms = 0;      
  } else if (currentCarDistance == 0){
             carStartTime_ms = millis(); 
             carState = "Out";                         
    }

// Timed actions setup
  doorDistanceTimerId = timer.setInterval(PINGSPEED, check_door_distance);
  carDistanceTimerId = timer.setInterval(PINGSPEED, check_car_distance);
  timer.disable(carDistanceTimerId);
  
// Output resource (states and time intervals)
  thing["door&car_states_timeIntervals"] >> [](pson& out){
    String door_state_timeInterval;
    String car_state_timeInterval;
    if (doorState == "Open" && carState == "Out"){      
       door_state_timeInterval = String("Door is OPEN for ") + (doorTimeInterval_ms / 60000) + String(" min");
       car_state_timeInterval = String("Car is OUT for ") + (carTimeInterval_ms / 60000) + String(" min");
       } else if (doorState == "Closed" && carState == "Out"){
       door_state_timeInterval = String("Door is closed");
       car_state_timeInterval = String("Car is OUT for ") + (carTimeInterval_ms / 60000) + String(" min");
       } else if (doorState == "Open" && carState == "In"){
       door_state_timeInterval = String("Door is OPEN for ") + (doorTimeInterval_ms / 60000) + String(" min");
       car_state_timeInterval = String("Car is IN");
       } else {
       door_state_timeInterval = String("Door is closed");
       car_state_timeInterval = String("Car is IN");
       }
    out["Door"] = door_state_timeInterval.c_str();
    out["Car"] = car_state_timeInterval.c_str();
  };

// Output resource (distances)
  thing["door&car_distances"] >> [](pson& out){
     out["Door"] = currentDoorDistance;
     out["Car"] = currentCarDistance;
  };

// Input resource (open-close the door by pushing the dashboard button)
  thing["relay"] << [](pson& relay){ 
    if(relay.is_empty()){
       relay = false;
       }
       else{
       energize_relay();
       }
  };

// Input resource to remotelly modify the maxDoorDistance variable defined as a global variable.
  thing["maxDoorDistance"] << inputValue(maxDoorDistance,{    
    EEPROM.write(0, maxDoorDistance);
    EEPROM.commit();  
  });
  
// Input resource to remotelly modify the maxCarDistance variable defined as a global variable.
  thing["maxCarDistance"] << inputValue(maxCarDistance,{    
    EEPROM.write(1, maxCarDistance);
    EEPROM.commit();  
  });

// Input resource to remotelly modify the doorAlertInterval variable defined as a global variable.
  thing["doorAlertInterval"] << inputValue(doorAlertInterval,{
    doorAlertTimer = (doorTimeInterval_ms / 60000) + doorAlertInterval;    
    EEPROM.write(2, doorAlertInterval);
    EEPROM.commit();  
  });

// Input resource to remotelly modify the carAlertInterval variable defined as a global variable.
  thing["carAlertInterval"] << inputValue(carAlertInterval,{
    carAlertTimer = (carTimeInterval_ms / 60000) + carAlertInterval;    
    EEPROM.write(3, carAlertInterval);
    EEPROM.commit();  
  });

// Input resource to remotelly modify the autoCloseInterval variable defined as a global variable.
  thing["autoCloseInterval"] << inputValue(autoCloseInterval,{
    autoCloseTimer = (doorTimeInterval_ms / 60000) + autoCloseInterval;    
    EEPROM.write(4, autoCloseInterval);
    EEPROM.commit();  
  });
  
// Output resource (states)
  thing["door&car_states"] >> [](pson& out){
     out["Door"] = doorState;
     out["Car"] = carState;    
  };
}

void loop() { 
  /*   It is important here to do not add delays inside the loop method, 
  as it will prevent the required execution of the thing.handle() method.  */

  timer.run();
  
  thing.handle();
    
}

/********************************    Functions   **********************************/

// defines Status based on current and previous distance measurements.
byte check_Status(unsigned int x, unsigned int y){
  byte Status;  
  if (x != 0 && y != 0){
    Status = 1;
  }
   if (x == 0 && y != 0){
    Status = 2;
  }
   if (x == 0 && y == 0){
    Status = 3;
  }
  if (x != 0 && y == 0){
    Status = 4;
  }  
  return Status;
}

// Calls endpoint to send a door alert.
void door_alert() {
    pson data;
    data["The garage door is OPEN for"] = doorTimeInterval_ms / 60000;
    thing.call_endpoint("doorAlert", data);
}

// Calls endpoint to send a car alert.
void car_alert() {
    pson data;
    data["The car is OUT for"] = carTimeInterval_ms / 60000;
    thing.call_endpoint("carAlert", data);
}

// Energize relay to simulate push button.
void energize_relay() {
  digitalWrite(RELAY, HIGH); delay(300); digitalWrite(RELAY, LOW);
}

// show off neopixels for visual aid.
void show_off_neopixels (){
       if (currentCarDistance > maxCarDistance / 2){
           for (int q = 0; q < 12; q += 2){
                strip.setPixelColor(i + q,0,255,0);
           }
           strip.show();
           for (int q = 0; q < 12; q += 2){
                strip.setPixelColor(i + q,0,0,0);
           }            
       }
       if (currentCarDistance <= (maxCarDistance / 2) && currentCarDistance != 0){           
           for (int q = 0; q < 12; q += 2){
                strip.setPixelColor(i + q,255,0,0);
           }
           strip.show();
           for (int q = 0; q < 12; q += 2){
                strip.setPixelColor(i + q,0,0,0);
           }           
      }
      i++;
      if (i>1){
          i = 0;       
      }
}

// Calculates the door distance in cm, updates door state and enables timers.
void check_door_distance(){
      previousDoorDistance = currentDoorDistance;     
      currentDoorDistance = sonarDoor.ping_cm(maxDoorDistance);            // Send a ping and get the distance in whole centimeters.
      timer.disable(doorDistanceTimerId);
      timer.enable(carDistanceTimerId);
      timer.restartTimer(carDistanceTimerId);      
      doorStatus = check_Status(currentDoorDistance, previousDoorDistance);
      switch (doorStatus) {
        case 1:
              doorTimeInterval_ms = (millis() - doorStartTime_ms);              
              if (autoCloseInterval != 0){                           
                  if (doorTimeInterval_ms >= autoCloseTimer * 60000){
                      energize_relay();
                      autoCloseTimer = (doorTimeInterval_ms / 60000) + autoCloseInterval;                                     
                  }                                  
              }
              if (doorAlertInterval != 0){                           
                  if (doorTimeInterval_ms >= doorAlertTimer * 60000){
                      door_alert();
                      doorAlertTimer = (doorTimeInterval_ms / 60000) + doorAlertInterval;                                     
                  }                                  
              }          
              break;
        case 2:
              doorState = "Closed";
              doorTimeInterval_ms = 0;              
              autoCloseTimer = autoCloseInterval;
              doorAlertTimer = doorAlertInterval; 
              thing.write_bucket("bucket_1", "door&car_states");
              break;
        case 3:
              break;
        case 4:
              doorStartTime_ms = millis(); 
              doorState = "Open";                           
              thing.write_bucket("bucket_1", "door&car_states");
              break;    
      }
}

// Calculates the car distance in cm, updates car state, show off neopixels for visual aid and enables timer.
void check_car_distance(){
      previousCarDistance = currentCarDistance;
      currentCarDistance = sonarCar.ping_cm(maxCarDistance);               // Send a ping and get the distance in whole centimeters.
      timer.disable(carDistanceTimerId);
      timer.enable(doorDistanceTimerId); 
      timer.restartTimer(doorDistanceTimerId);
      carStatus = check_Status(currentCarDistance, previousCarDistance);
      switch (carStatus) {
        case 1:
              neopixelsTimeInterval_ms = millis() - neopixelsStartTime_ms;
              if (neopixelsTimeInterval_ms <= 600000){
                  strip.show();
                  show_off_neopixels();
              } else {
                strip.show();
              }
              break;
        case 2:
              carStartTime_ms = millis(); 
              strip.show();
              carState = "Out";                           
              thing.write_bucket("bucket_1", "door&car_states");
              break;
        case 3:
              carTimeInterval_ms = millis() - carStartTime_ms;              
              if (carAlertInterval != 0){                           
                  if (carTimeInterval_ms >= carAlertTimer * 60000){
                      car_alert();
                      carAlertTimer = (carTimeInterval_ms / 60000) + carAlertInterval;                                     
                  }                                  
              }
              break;
        case 4:
              show_off_neopixels ();
              neopixelsStartTime_ms = millis();               
              carState = "In";                                     
              carTimeInterval_ms = 0;              
              carAlertTimer = carAlertInterval;                
              thing.write_bucket("bucket_1", "door&car_states");
              break;    
      }
}

/***********************************************************************************/

Credits

Sotirios Zormpas

Sotirios Zormpas

1 project • 4 followers

Comments