Sotirios Zormpas
Published © GPL3+

EchoGarage

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

IntermediateFull instructions provided4 hours6,405
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

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
1 project • 4 followers

Comments