Garage Door Minder - SMS Alert for Garage Door Left Open

Hi Par-ticklers. Here is some background to my first Photon project. Its a garage door position monitor with SMS alarm if the (remote controlled) garage door gets left open for a pre-defined alarm time (in my case 5 minutes). It has a repeat alarm by SMS if the door does not get closed, with SMS alarms sent at a repeat alarm period (in my case 30 minutes). If the door gets closed after an initial alarm then it sends a "door now closed" SMS message.

I decided on this as my first useful project, since in our home the garage door occasionally gets left open which has obvious security concerns (given the various stuff stored in the garage is then open to the street). I havent yet progressed with control of the garage door, simply monitoring its position and giving an alarm if it appears to have been left open. Maybe a project down the road will be to include the control of the garage door either manually or automatically based on the alarm.

The door position is remotely sensed using a HC-SR04 ultrasonic distance sensor, which is cheaply available on ebay etc. The code as I have posted it uses an Australian SMS API (a free Telstra service, currently available to developers for SMS message sending). Other SMS via API providers such as Twilio could also be used. I have also tried using email notifications of the alarm messages using Mailgun.

1) HC-SR04 Sensor & Connections.
I decided to use this sensor because it allows remote sensing of the garage door position without limit switches in physical contact with the door. In my experience this is usually a more reliable way of position detection. I also happened to have one ready to use from another unfinished project idea.

There are lots of good write ups of this versatile and cheap sensor. A good one is here:
Ultrasonic Distance Sensor - HC-SR04 (5V) - SEN-15569 - SparkFun Electronics. There are also lots of good web pages on interfacing the HC-SR04 with Arduino with sample code etc.

However this device is powered at 5Vdc and outputs a 5Vdc pulse, so the pulse needs voltage shifting to suit the 3.3Vdc Photon inputs. I did this with a simple resistor divider network between the 5Vdc pulse from the HC-SR04 to ground, based on the use of three identical resistors, with the Phton input taken between ground and two of the three resistors in series. This means the 5Vdc is reduced by 2/3 as seen at the Photon input pin (or 3.3V). There are probably better and more fancy ways to solve this problem, but this was simple and worked.

The Fritzing diagram of the circuit:

My breadboard didnt look exactly like this because I have adjusted a few things to account for Fritzing's layout limitations.

2) Photon code and State Machine.
I already had an interest to learn about State Machines (Also called Finite State Machines or FSM) and soon realised that my Garage Door Minder was a good candidate. For some reason I had imagined that the coding for a FSM would be really difficult (it sounds complicated, highly technical and only for expertsl!), but actually it turned into being very easy to understand, code and debug (I am a novice programmer). After this experience I really like the FSM approach and will try to use them again if I can.

Below is the state diagram I established for the garage door position and related alarms. On each pass of the software the state is restricted to one of these states, with defined conditions leading to a transition to another state. You can see the state machine within the switch statement in the code. I have added lots of comments in the code so it should be understandable.

I have placed the Garage Door Minder (GDM) code for the Particle at the end of my post for readability. I don't claim it to be great coding but it does the job.

3) Message related events/webhooks
I debugged the first pass of the code by using Particle.publish events as alarms. This allows the events such as alarm messages to be seen in the Particle Console. Then I trialled using Mailgun to send emails as the alarm messages. This worked well and Mailgun was a very easy product to get working and a nicely setup App (well done Mailgun). Finally I worked out how to send SMS messages via a free developers site here in Australia run by Telstra (our largest telecoms provider). The service allows sending of free SMS messages to an Australian mobile phone number based upon an API interface to registered users. You can read more about it here: https://dev.telstra.com/content/sms-api-0. I understand that this wont be an available solution for all Par-ticklers - but hopefully there is something useful in what I have done so you can solve your own requirements for sending SMS messages (you can for example use the service at Twilio - and lots has already been written on how to use this service for sending SMSs eg https://community.particle.io/t/doorbell-to-sms/).

In regards to webhooks and passing multiple data variable from the Photon to the webhook (which this application needs) then everyone should read the Tutorial by @rickkas7 https://community.particle.io/t/webhook-intermediate-tutorial/).

Also of note is that the use and debugging of webhooks is made much easier by (a) use of the CLI interface, with the webhook JSON statements in a TXT file and (b) the use of the Requestb.in (at https://requestb.in/).

The JSON statement text file I used for the setup of the Particle webhook is here:

{
"event": "SendSMS",
"url": "https://api.telstra.com/v1/sms/messages",
"requestType": "POST",
"headers": {
"Authorization": "{{SMSTokString}}"
},
"json": {
"to": "< cell number >",
"body": "{{body}}"

},

"mydevices": true,
"noDefaults": true
}

4) Physical Installation.
I prototyped my Garage Door Minder on a breadboard which had a sticky surface (like a sticker with peelable paper covering it) for sticking the breadboard down to a surface. I used this to stick the GDM breadboard to a spacer (some wood) attached to the garage door rail down near the floor (so that I could confirm when the door was "not closed"). The HC-SR04 is aimed at the inside face of the garage door in the closed position. I am using a threshold distance of 5 cm (open = distance >5cm). I did try another orientation with the HC-SR04 looking at the side of the garage door (perpendicular to the orientation in the photo), but interference with adjacent wall of the house seemed to upset the HS-SR04 readings making them unreliable.

For a power supply I cut and stripped a USB cable and tinned the +5Vdc and GND cores and taped these into the breadboard power rails. The USB cable is suppplied from a plug in phone charger module.

After various tests (and once having the garage door crash into the HC-SR04) and repositioning it is now working well. Proof is in my phone screen shot of a message generated from a test.

Ideas and Next Steps For Improvements

  1. Send a weekly (or monthly) SMS as a test message with some sort of count of operations of the door as a sort of a health check so I know the GDM is still alive and working. With no alarms given it will be easy for some sort of failure to occur and for this to be forgotten (missed), leading to a failure to detect the door being left open.

  2. Look at either integrating the control of the garage door using a second photon by either automatic or (remote) manual means if the alarm is given.

  3. Having a visible alert (flashing LED) for the alarm condition inside the house as well as the SMS.

  4. Packaging up the unit into a nice enclosure with a USB port for power supply, and a cutout for the HC-SR04 to see out of.

Thanks!
Thanks to all the wonderful people on the Particle forum who give their time and expertise to helping others get their Particles working and project problems solved. This is a great community!
Special thanks to: @ScruffR , @rickkas7 and @Ric who helped me solve various problems along the way.

Photon Code
Please note distances are in metric units (cm).

// Garage Door Manager
// By GreyMonkey
// A program using the Particle Photon to detect when a garage door has been left up too long and then to send an SMS to alert the building owner.
//
// ======================================================================
// Version Tracking
// Version 0.1 12/06/2016 Development stage
// Version 0.2 21/08/2016 Changed timer related variables to unsigned long to allow rollover of millis() to work correctly.
// Version 0.3 25/08/2016 Added webhook to Mailgun for email notification.
// Version 0.4 29/08/2016 Added webhook to Telstra SMS API for SMS notification.
//  Note:  For more information on the Telstra SMS API refer to https://dev.telstra.com/content/sms-getting-started
// Version 0.5 03/09/2016 Passing JSON data strings to SendSMS webhook.
// Version 1.0 11/09/2016 Initial deployment, reset temporary timer values to operational values.
//
// =======================================================================
//
// The garage door position detection is via a HC-SR04 ultrasonic rangefinder.
// triggerPin is the Photon pin connected to the HC-SR04 trigger pin.
// The Photon sets this alternately low, high and then low to trigger the HC-SR04.
// Once triggered the HC-SR04 sends a series of ultrasonic pulses, and times the return period until it hears an echo pulse.
int triggerPin = D1;
// echoPin is the Photon pin connected to the HC-SR04 echo pin.
// The HC-SR04 outputs a HIGH pulse (voltage Vcc = 5V) of the same duration as the time between it sending an ultrasonic pulse and receiving one back.
// Its on this pin that the photon monitors for a high signal and times its duration (so it knows the HC-SR04 to object distance).
// A voltage divider is necessary to reduce the 5V pulse to about a 3.3V pulse.  We do this with three identical resistors, to drop the voltage by 2/3.
int echoPin = A1;
// This is the threhold distance at which we define that the garage door is closed.
// ie if the distance detected from the HC-SR04 is less than this threshold, we define the garage door to be closed.
int threshold_dist = 5;

int tokenlength; //The length of the Telstra authorisation token string.

long distance = 0; // The distance from the HC-SR04 to either the garage door (or the next object if the garage door is open).
long alm_t_init = 300000; // the initial time from defining the garage door to be open until an alarm is raised (in mSec)
long alm_rpt_time = 1800000; // the repeat alarm time from an initial alarm to subsequent repeat alarms (in mSec)

String token; //This is the string variable that carries the Authorisation token received from Telstra to allow the SMS to be sent.
String message;

void setup() {

  Serial.begin (9600);
  pinMode(echoPin,INPUT); // define the echoPin as an INPUT pin
  pinMode(triggerPin,OUTPUT); // define the triggerPin as an OUTPUT pin

  // This is the Particle subscription to the event "hook-response/GetToken".
  // This code will then listen for the webhook response given by the remote server.
  // When the event "hook-response/GetToken" occurs this next statement will initiate function "TokenHandler".
  Particle.subscribe("hook-response/GetToken", TokenHandler, MY_DEVICES);
}
void loop() {

  // The following static declarations mean these variables hold their values as the code in loop is repeatedly executed:
  static int state = 1; // initialise the state variable
  static unsigned long time_opn = 0; // The time stamp in millis() that the garage door was opened.  Only reset when door is determined to be closed again.
  static unsigned long tsince_opn = 0; // The time in millseconds since the garage door was opened.
  static unsigned long lst_alm_time = 0; // The time stamp in millis() that the last time the door opened alarm was issued. Is updated to a new time stamp each time a repeat alarm is issued.
  static unsigned long tsince_alm = 0; // The time in millseconds since the last alarm was issued.

  // The following code is to have the HC-SR04 determine a distance reading
  digitalWrite(triggerPin,LOW);
  delayMicroseconds(2);
  digitalWrite(triggerPin, HIGH);
  delayMicroseconds(10);
  digitalWrite(triggerPin,LOW);
  // distance is the distance from the HC-SR04 to the nearest object in the "sight" of the HC-SR04.
  // The time returned by the pulseIn function is in microseconds.  At 340 m/sec sound travels 1 cm in 29 microseconds.
  // Since the echo has travelled the distance to the object twice, we divide by 58 to get the distance to the object in cm.
  distance = pulseIn(echoPin, HIGH)/58;
  if (distance == 0){ distance = threshold_dist + 1;}// in case distance returned is zero meaning the HC-SR04 "timed out" (no object sighted in its path) we set distance to be greater than the threshold distance.
  // ie the garage door will be deemed to be open.
  //Serial.print("Distance ");
  //Serial.println(distance);

  //These two lines allow the Distance and the State (of the state machine) to be visible in the Particle.Dev IDE (purely for some visibility during debugging etc)
  Particle.variable("Distance", distance);
  Particle.variable("State",state);

  switch (state)
  //This section is the finite state machine into which the garage door alarm software has been designed.
  //There are five possible states for the state machine to be in.  The state machine is initialised (earlier in the program to be in State=1)).
  // State = 1 = Garage door is closed.
  //              Transition to State = 2 if distance exceeds threshold_dist, indicating the garage door has opened.
  // State = 2 = Garage door is detected as open, timer is running, timer value is less than the initial alarm time (alm_t_init).
  //              Transition to State = 1 if distance less than threshold_dist, indicating the garage door has closed.
  //              Transition to State = 3 if distance exceeds threshold_dist, and time since open > initial alaarm time.
  // State = 3 = Garage door was detected as open, and the alarm timer has exceeded the initial alarm time (alm_t_init).  If still open an alarm is issued.
  //              Transition to State = 1 if distance less than threshold_dist, indicating the garage door has closed.  This is checked just before giving the first alarm.  Break (do not give first alarm) if this is the case.
  //              Transition to State = 4 once the initial alarm is given.
  // State = 4 = Garage door was detected as open, and the initial alarm has been given, continues timing to see if the alarm repeat timer is exceeded.  Give repeat alarm every repeat time if the door remains open.
  //               Transition to State = 5 if distance less than threshold_dist, indicating the garage door has closed.
  // State = 5 = Anytime after an initial alarm is given and the door is detected as closed, a message is sent to confirm the door is now closed.
  //               Transition to State = 1 once the door closed message is sent.
  {
    case 1:
    // In this state the garage door is closed.
    //The only transition from this state occurs if distance exceeds the threshold distance, upon which the state will shift on the next loop to S_OPEN_WAIT

      if (distance > threshold_dist) { // detect if the garage door has opened
        state = 2; // transition to state 2 (on next loop)
        Particle.publish("State 1 to State 2: Door Open");// Event visible in Particle Console. For diagnostics only.
        time_opn = millis(); // this sets the time recorded at the opening of the door
      }
      break;

    case 2:
    // In this state the garage door is opened, but has not yet exceeded the alarm time setpoint.
    // This state has two possible transitions: (1) The garage door is detected as being closed again (go back to state=1) or
    // (2) The garage door remains open and the duration since opening exceeds the alarm time setpoint (go to state=3)

      if (distance < threshold_dist) { // the garage door has closed
        state = 1; // So if the garage door has closed again whilst in this state, transition to state 1 (on next loop)
        Particle.publish("State 2 to State 1: Door Closed");// Event visible in Particle Console. For diagnostics only.
        time_opn = 0; //reset time_open
        break; //break and go direct (next loop to state 1)
      }
      tsince_opn = millis() - time_opn;
      if (tsince_opn > alm_t_init){ // detects if the time that garage door has been open exceeds the alarm setpoint
        state = 3; //if it has, then transition to state = 3 (next loop)
        Particle.publish("State 2 to State 3: Door still open, Check first Alarm");// Event visible in Particle Console. For diagnostics only.
        //time_open is left with its opening value and not reset.
      }
      break;

      case 3:
      // In this state the garage door is opened, and has exceeded the alarm time setpoint.
      // This state has two possible transitions: (1) The garage door is detected as being closed again (go back to state=1) or
      // (2) The garage door remains open and the duration since opening exceeds the alarm time setpoint, an alarm is given (go to state=4)

      if (distance < threshold_dist) { // the garage door has closed
        state = 1; // So if the garage door has closed again whilst in this state, transition to state=1 (next loop)
        Particle.publish("State 3 to State 1: Door Closed");// Event visible in Particle Console. For diagnostics only.
        time_opn = 0; //reset time_open
        break; //break and go direct (next loop to state 1)
      }

      state = 4; // if garge door still open, transition to state=4 (next loop)
      Particle.publish("State 3 to State 4: Door still open, Give first Alarm");// Event visible in Particle Console. For diagnostics only.
      message = "The garage door has been left open.  This is the first alarm.  Please close the door.";
      sendSMSFunction(message);
      lst_alm_time = millis();// record the time the alarm was given.
      //time_open is left with its opening value and not reset.
      break;

      case 4:
      // In this state the garage door is opened, and a first alarm has already been given.  In this state repeat alarms will be given at an interval of the alarm_repeat_time.
      // This state has only one possible transition: The garage door is detected as being closed again (go to state=5).

      if (distance < threshold_dist) { // the garage door has closed
        state = 5; // So if the garage door has closed again whilst in this state, transition to state=5 (next loop)
        Particle.publish("State 4 to State 5: Door closed after initial alarm");// Event visible in Particle Console. For diagnostics only.
        time_opn = 0; // reset time_open
        lst_alm_time = 0;// reset last_alarm_time
        break;  //break and go direct (next loop to state 5)
      }
      tsince_alm = millis() - lst_alm_time;
      if (tsince_alm > alm_rpt_time){
      Particle.publish("State 4: Door still open, Alarm repeat time up, Repeat Alarm");// Event visible in Particle Console. For diagnostics only.
      message = "The garage door has been left open.  This is a repeat alarm.  Please close the door.";
      sendSMSFunction(message);
      lst_alm_time = millis();
      }
      break;

      case 5:
      // In this state the garage door has been closed after an initial alarm was given.  In this state we will send an SMS to advise the garage door is closed.
      // This state has only one possible transition: After giving the SMS, go to state=1).

      state = 1; // transition to State=1 (next loop)
      Particle.publish("State 5 to State 1: Door closed after first alarm, Give all clear message");// Event visible in Particle Console. For diagnostics only.
      String message = "The garage door has now been closed.  The emergency is over.  Resume normal operations.";
      sendSMSFunction(message);
      break;
}//end of switch

  delay(20000);// wait time in the main loop

}
// This function will get called when the Telstra OAuth2.0 Token is received (data received with the response to the GetToken webhook
// Function based on "gotWeatherData" from Particle Tutorial Webhooks webpage (https://docs.particle.io/tutorials/topics/webhooks/)
void TokenHandler(const char *name, const char *data) {
  //String TokenString = String(data);
  //Example returned text from Telstra server {"access_token": "Lp4a5ZsZ3JB0CEryxkmcc3Dq07ge", "expires_in": "3599"}
  token = tryExtractString(data, "\"access_token\": \"","\", \"expires_in");
  Particle.publish("Token: " ,token);
}

// Function from Particle Tutorial (at https://docs.particle.io/tutorials/topics/webhooks/#the-firmware-1)
String tryExtractString(String str, const char* start, const char* end)
{
    if (str == NULL) {
        return NULL;
    }

    int idx = str.indexOf(start);
    if (idx < 0) {
        return NULL;
    }

    int endIdx = str.indexOf(end);
    if (endIdx < 0) {
        return NULL;
    }

    return str.substring(idx + strlen(start), endIdx);
}

// This function is called whenever an SMS is required to be sent via the Telstra server.
// The function is passed the message to be sent.  The mobile/cell phone number the message is to be sent to is hard coded in this function.
void sendSMSFunction(const char *data) {
//Publish the body text passed to the function for diagnostic purposes.
  String body = data;
  Particle.publish("body", body);
// An authorisaton token has to be requested from the Telstra server.  This is via a webhook triggered by the following event.
  Particle.publish("GetToken","",60,PRIVATE);
  delay(10000); //Wait for token to be received.
  if (token.length() > 0)  {
    String message = "{\"SMSTokString\": \"Bearer ";
    message.concat(token);
    message.concat("\", \"body\": \"");
    message.concat(body);
    message.concat("\"}");
    Particle.publish("SendSMS",message,60,PRIVATE); //Create new event linked to webhook to send SMS via Telstra server
  }
}
8 Likes

This is an awesome idea! I can see myself adding this as an addition to my home in the future. Thanks for sharing this!

The Fritzing diagram is missing a wire to ground from the left end of the resistor divider network.

Thanks @kb5mu for spotting that. You are right. My error. I used fritzing to sketch up what I built, and somehow dropped that wire.