Back to Parent

// This #include statement was automatically added by the Particle IDE.
#include "DebugPrint.h"

//    GarageMonitorApp - Monitoring of multiple garage doors Open/Close status to Text/SMS message
  //  Copyright (C) 2015, Jay Moskowitz

// Revisison History
// Rev V4 - Insert Daylight Savings Time code as the Photon library
//          does not account for it. Adjust the GMT offset accordingly.
// Rev V3 - Correct mask in Timer wrap around logic - 10/21/2015
//          - 0xC00 0000 should have been 0xC000 0000
//              - caused LED timer to stop operating when mills()
//                  reached 0x8000 0000
//          - Update set_next_timeout() to insure when close to wrap around
//              time that the new timer is not set to 0 as this value is
//              sometimes used by the logic to indicate if a timer is
//              active or not.
//              - This caused the sync of the date/time from the cloud to
//                  stop and left the Date of alarms from being updated.
//          - Update step 8 in loop() which maintains the current time of day
//              - At clock wrap time, the function went into a loop and
//                  because of the _xC00 0000 error above, resulted in the
//                  clock updates scheduled for a very very long time later
//                  when it came out of the loop. This error and the last one
//                  resulted in the Date/Time on text messages being locked
//                  to a specific Date/Time and not being updated.
//          - All these errors were related to clock wrap around issues.
//
// Rev V2 - Included GNU license and released on Hacketer.io - 9/9/2015
// Rev V1 - Initial release on production Photon and in Operation since 6/1/2015

// This version of the code utilized two functions to deal with clock wrap around.
// These are set_next_timeout() and check_for_timeout(). Using these functions,
// one only needs to maintain a single unsigned long variable to represent the
// timeout time. The code makes sure that near clock wrap around time, it sets the
// next timeout so it occurs after the clock wraps back to 0 to insure that a timeout
// is not accidentally missed which could hang up a function. And alternative method
// of dealing with the clock wrap around entails with having the timer as a struct
// instead of a single unsigned long. The struct would contain the starting time when
// the timeout was requested and the interval of time you wish to wait. Then the
// check_for_timeout function becomes:
//          if (start_time - millis()) < interval) return TRUE; // Timed out
//          else return FALSE;          // Didn't time out
// This requires carrying two values instead of the one being used. It works
// propertly because of unsigned arithmetic which results in this function waiting
// the proper timeout time.
//
//  Monitoring of 3 garage doors via reed switches

//  LED Blink Patterns
//    2 sec on / 2 sec off      -  Idle loop
//    1 sec on / 1 sec off      -  Door is open - not in alarm state
//    4 times per second        -  Door is open - in alarm state - Tweet has been sent
//

// Adapted from an Arduino app and ported to the Photon

#define debug     1    // 1 for debug mode - progress diagnostics - with or without faster (debug) timers
#define debugTimers 0  // use debug timers in debug mode (else use normal timers)
#define debug_step 0   // ! to track each step of processing


#define DoorLft   0
//CHELSIE  We only will use DoorLft for this project since there is only one garage door
#define DoorMid   1
#define DoorRgt   2
#define OPEN      LOW
#define CLOSED    HIGH
#define debounce  (unsigned long)50L    // 50ms debounce timeout
#define LED       D7                    // Show status on blue Photon LED

#define CR '\r'
#define LF '\n'

#define AD_RANGE 4096       // 0 - 3.3v --> 0 to 4095
#define debugReminder 5L    // Debug time in minutes
#define normalReminder 15L  // Normal interval between reminders
#define debugClkUpdate 5L   // update time of day clock from cloud every 5 minutes
#define normalClkUpdate 60L // Once per hour

// Analog read of door status on channels
// Lft =  A0
// Mid =  A1
// Rgt =  A2

#define uint8 byte
#define strcat_P strcat


//---------------------------------------------------------------------------

// Flash stored messages

const char f_left[] PROGMEM = "Lft-";
const char f_open[] PROGMEM = "OPEN ";
const char f_closed[] PROGMEM = "CLOSED ";
const char f_mid[] PROGMEM = "Mid-";
const char f_rgt[] PROGMEM = "Rgt-";
const char f_at[] PROGMEM = " on ";
const char f_sending[] PROGMEM = "\nSending text: ";
const char f_cr[] PROGMEM = "\n";
const char f_startup_msg[] PROGMEM = "\n************ Begin garage door switch test ************";
const char f_gpl1[] PROGMEM = "\nGarageMonitorApp - Version 3, Copyright(c) 2015, Jay Moskowitz";
const char f_gpl2[] PROGMEM = "\nThis program comes with ABSOLUTELY NO WARRANTY";
const char f_gpl3[] PROGMEM = "\nFor details refer to http://www.gnu.org/licenses/ ";
const char f_sl[] PROGMEM = "\nStartup loop()";
const char f_dosc[] PROGMEM = "\nDoor opened - start clock ";
const char f_dcbasc[] PROGMEM = "\nDoor closed before alert - stop clock ";
const char f_mtasdtd[] PROGMEM = "\nMove to alert state due to door ";
const char f_odot[] PROGMEM = "\n--> One or more doors open text";
const char f_adct[] PROGMEM = "\n--> All doors closed text";
const char f_debug_clock[] PROGMEM = "----------> Clock is at ";
const char f_getTime[] PROGMEM = "\n-->Get Date/Time";
const char f_reminder[] PROGMEM = "\nSend Reminder Text";
const char f_step[] PROGMEM = "\nstep: ";

char *dayNames[]={"xxx","Sun","Mon","Tue","Wed","Thu","Fri","Sat"};
char *monthNames[]={"xxx","Jan","Feb","Mar","apr","May","Jun","Jul","Aug","Sept","Oct","Nov","Dec"};

unsigned long proc_time, ledTimer, resubmit_tweet_timer, reqTimeUpdate;
boolean startup, ledState;
unsigned long open_clock[3], timeout, debounceTime[3], clk_time;
boolean Door[3], last[3];
boolean alert_state, time_to_alert, clock_running[3];
unsigned long secondM, minuteM, hourM, dayM;
byte resubmit_tweet_count, resubmit_getTime_count;
byte nhr, nmin, nsec;
byte fptr, find_state, clk_state;
byte cur_hour, cur_minute, cur_second;
boolean tweet_in_progress, tweet_lost;
boolean getTimeInProgress, getTimeFormatError, clk_set, clk_error;
long mainCount = 0;

char dayNumber = 0;
char monthNumber = 0;
char weekdayNumber = 0;

const char fmt1[] PROGMEM = "%d-%d:%d:%d.%ld";
const char fmt2[] PROGMEM = "%s %s %d, %02d:%02d:%02d";
const char fmt3[] PROGMEM = " mainCount=%lx, millis()=%lx";

#define TWEET_LEN 141
  char tweet[TWEET_LEN];

void formatDoorStatusAndConditionallySend(byte tweet_required){
  unsigned long currtime, tweet_stamp;
  unsigned int seconds, minutes, hours, days;
  unsigned long fraction;

  tweet_stamp = proc_time;

  memcpy_P(tweet, f_left, strlen_P(f_left)+1);                   // "Lft: "
  if (Door[DoorLft] == OPEN) strcat_P (&tweet[strlen(tweet)],f_open);  // "OPEN "
  else strcat_P (tweet,f_closed);                                // "CLOSED "

  strcat_P (tweet,f_mid);                                        // "Mid: "
  if (Door[DoorMid] == OPEN) strcat_P (tweet,f_open);                  // "OPEN "
  else strcat_P (tweet,f_closed);                                // "CLOSED "

  strcat_P (tweet,f_rgt);                                        // "Rgt: "
  if (Door[DoorRgt] == OPEN) strcat_P (tweet,f_open);                  // "OPEN "
  else strcat_P (tweet,f_closed);                                // "CLOSED "

  strcat_P (tweet,f_at+1);                                         // "at " (skip leading space in ' at ')

  currtime = tweet_stamp;
  days = currtime / dayM;
  currtime -= (days * dayM);
  hours = currtime / hourM;
  currtime -= (hours * hourM);
  minutes = currtime / minuteM;
  currtime -= (minutes * minuteM);
  seconds = currtime / secondM;
  currtime -= (seconds * secondM);
  fraction = currtime;

  char fmt[40];
  if (!clk_set) strcpy_P(fmt,fmt1);    // get format string from ROM
  else          strcpy_P(fmt,fmt2);
  if (!clk_set) sprintf(&tweet[strlen(tweet)],fmt,days,hours,minutes,seconds,fraction);
  else sprintf(&tweet[strlen(tweet)],fmt,dayNames[weekdayNumber],monthNames[monthNumber],dayNumber,cur_hour,cur_minute,cur_second);

  if (debug) {
    if (tweet_required) DebugPrintFO(f_sending);     // Serial.print ("\nSending text: ");
    else DebugPrintFO(f_cr);                         // Serial.print ("\n");
    Serial.print (tweet);
  }
}

void send_tweet(bool startup){
   formatDoorStatusAndConditionallySend(TRUE);

   if (startup){
       sprintf(&tweet[strlen(tweet)],"<br>App reset");
       if(debug) Serial.println("\r\nApp reset - restarted\r\n");
   }
   else if(debug) Serial.println((char *)"");


   // Send TextMsg event
   Particle.publish("textMsg",tweet,0x7fff,PUBLIC);          // Publish text message - IFTT will send SMS
}

// Read reed switches and debounce input
void readSwitch(){
  int i;
  boolean swd;

  for (i=DoorLft; i<=DoorRgt; i++){
    swd = digitalRead(D0+i);   // Set HIGH (circuit closed) or LOW (circuit open)

    if (swd != last[i]){
      last[i] = swd;              // save current switch value (high or low)
      set_next_timeout(&debounceTime[i], proc_time, debounce);  // time before reading considered stable
    }

    if (check_for_timeout(debounceTime[i],proc_time)){  // timer has timed out
      Door[i] = swd;             // switch has been debounced. Same setting for some period of time
    }
  }
}

// Parse/Analyze data returned from remote time server
void getTime(){

  unsigned long read_time = Time.now();

  if(Time.year()<=1970) return; // This means the RTC has not yet started

  bool daylightSavings = IsDST(Time.day(), Time.month(), Time.weekday());
  Time.zone(daylightSavings? -4 : -5);

  cur_hour = Time.hour(read_time);
  cur_minute = Time.minute(read_time);
  cur_second = Time.second(read_time);
  dayNumber = Time.day();       // 1 - 31
  monthNumber = Time.month();   // 1 -12
  weekdayNumber = Time.weekday();     // 1 = Sunday

  if (debug) {
      DebugPrintFO(f_getTime);
      Serial.println((char *)"");
  }
  clk_set = TRUE;            // once clock is set at least once, the system will maintain the time
  // Set clk_time to count the number of seconds from now and adjust the current time accordingly
  // The set_next_timeout() function will consider the clock as getting ready to wrap around
  // during the last one minute before wrap around time every 49.7102696 days
  // (49 days, 17 hours, 2 minutes, 47.295 seconds)
  set_next_timeout(&clk_time, millis(),1000L);       // Update local time in 1 second
}

// check to see if timeout has occurred
bool check_for_timeout(unsigned long timeout_time, unsigned long cur_time){
  if ( ( (timeout_time & 0xC0000000) == 0) &&    // timeout time a very low value
         (cur_time & 0x80000000) )              // current time is a very large value
    return FALSE;                               // No timeout yet

  if (timeout_time <= cur_time) return TRUE;     // Timeout has occurred
  else return FALSE;                            // No timeout yet
}


void set_next_timeout(unsigned long *timer, unsigned long baseclock,  unsigned long increment){
  *timer = baseclock + increment;      // timeout for next event
  if (!*timer) *timer = 1;          // Add 1ms to timeout to avoid timeout at 0

  // if the new timeout is within 1 minute of wrapping around, add another minute to insure it
  // wraps around as it is easy to miss the timeout check near the clock wrap around point
  if (*timer > ((unsigned long)0xffffffff - (unsigned long)minuteM)) *timer += minuteM;

  return;
}

void show_step(byte step){
  if (debug_step){
    DebugPrintFO(f_step);
    Serial.println(step);
  }
}

// One time setups
void setup(){
  int i;

  Serial.begin(9600);

  pinMode(LED, OUTPUT);
  pinMode(D0,INPUT_PULLDOWN);   // Normally in LOW state if nothing connected
  pinMode(D1,INPUT_PULLDOWN);
  pinMode(D2,INPUT_PULLDOWN);

  for (i=DoorLft; i<=DoorRgt; i++) {
    Door[i] = CLOSED;            // initial door status before first reading of switched = closed
    last[i] = CLOSED;            // For switch debounce logic
    open_clock[i] = 0;
    clock_running[i] = FALSE;
  }


  if (debug) {
    // print noticeable startup message
    delay(5000);
    DebugPrintF(f_gpl1);	// Display GNU General Public License information
    DebugPrintF(f_gpl2);
    DebugPrintF(f_gpl3);
    for (i=0; i<3; i++) DebugPrintF(f_startup_msg);  // Serial.print("\nBegin garage door switch test\n");
  }

  startup = TRUE;

  alert_state = FALSE;
  time_to_alert = FALSE;

  secondM = 1000L;
  minuteM = 60L * secondM;
  hourM = 60L * minuteM;
  dayM = 24L * hourM;
  if (debug & debugTimers) {
    timeout = 30 * secondM;
  }
  else timeout = 5 * minuteM; // how long to wait before sending out an alarm

  reqTimeUpdate = millis() + (30 * secondM);  // when to update clock - 30 secsafter startup

  Time.zone(-4);      // Set to standard Eastern USA time - will adjust for DST later

  ledState = HIGH;
  clk_set = FALSE;
/*
  // Set up to use either of two WiFi networks - Commented out until fix to Particle firmare
  // as WiFi.setCredentials does not set proper credentials if the network is not accessible
  // when the function is called. Ends up getting the Photon hung in not being able to reach
  // the network. It tries to connect to the Particle Cloud before the app starts and therefore
  // the app never runs.

  Serial.print("WiFi credentials ");
  if (WiFi.clearCredentials()) Serial.println("cleared");
  else Serial.println("not cleared - error!");

  WiFi.setCredentials("SSID_of_network_A","Password-A",WPA2);	// None, WEP or WPA2
  WiFi.setCredentials("SSID_of_network_B","Password-B",WPA2);

  Serial.print("WiFi credentials ");
  if (WiFi.hasCredentials()) Serial.println("now set");
  else Serial.println("not set - error!");

  Serial.println("Set up to connect to either: Network_A or Network_B");
*/

  Serial.print("\nConnected to: ");
  Serial.print(WiFi.SSID());
  Serial.print(", Power: ");
  Serial.print(WiFi.RSSI());
  Serial.print(", IP: ");
  Serial.print(WiFi.localIP());
  Serial.print(", Gateway: ");
  Serial.println(WiFi.gatewayIP());
}

// Processing loop
void loop(){
  int i, j;
  static boolean anyDoorOpen = FALSE;
  static unsigned long print_time, last_clk_time;
  static unsigned long reminderTimer;

  show_step(0);

  proc_time = millis();

  readSwitch();            // Read and debounce all switches and save current debounced switch status when ready

  if (startup) {
    // One time startup code
    digitalWrite(LED, ledState);    // Turn On LED on board to show we are running
    ledTimer = proc_time + 1000;          // 1 second blink timer

    getTime();                      // Start time of day clock
    if (debug) {
        DebugPrintF (f_sl);        // Serial.print("\nStartup loop()\n");
        Serial.println((char *)"");
    }
    for (i = 0; i < 50; i++){
      delay(10);
      proc_time = millis();
      readSwitch();                       // Read door switches for first half second
    }
    send_tweet(startup);
    startup = FALSE;
  }

  if(!clk_set) getTime();       // If Real Time Clock did not start, try again

  show_step(1);

  for (i = DoorLft; i <= DoorRgt; i++) {
    // Start 60 minute clock (timeout period) for each door that has just opened
    //Chelsie modified this because theres only one garage door
    if ( (alert_state == FALSE) && (clock_running[i] == FALSE) && (Door[i] == OPEN) ){
      set_next_timeout(&open_clock[i], proc_time, timeout);  // Start 60 minute clock
      clock_running[i] = TRUE;
      if (debug) {
        DebugPrintFO(f_dosc);        // Serial.print ("\nDoor opened - start clock ");
        Serial.print (i);
        DebugPrintFO(f_at);          // " at "
        Serial.println (proc_time);
      }
    }

    show_step(2);

    // Reset clock associated with any door that closes within the timeout period
    if ( (alert_state == FALSE) && (clock_running[i]) && (Door[i] == CLOSED) ){
      open_clock[i] = 0;        // Stop clock
      clock_running[i] = FALSE;
      if (debug) {
        DebugPrintFO(f_dcbasc);   // Serial.print("\nDoor closed before alert - stop clock ");
        Serial.println(i);
      }
    }
    show_step(3);

    // Check if it's time to alert
    // Have not already sent an alert, a door is open, check if open beyond timeout period
    if ( (alert_state == FALSE) && clock_running[i] && check_for_timeout(open_clock[i],proc_time) ) {
        time_to_alert = TRUE;      // at least one door open too long
        if (debug){
          DebugPrintFO(f_mtasdtd);  // Serial.print("\nMove to alert state due to door ");
          Serial.print(i);
          DebugPrintFO(f_at);      // Serial.print(" at ");
          Serial.println(millis());
        }
    }
  }

  show_step(4);

  // Send Tweet if time to alert then move into alert state
  if ( (alert_state == FALSE) && time_to_alert) {
    if (debug) {
        DebugPrintF(f_odot);  // Serial.print("\n--> One door open tweet\n");
        Serial.println((char *)"");
    }
    alert_state = TRUE;              // indicate we've alerted to at least one door open
    time_to_alert = FALSE;
    send_tweet(FALSE);
    // Once in alert state, we do not start any more door open timers until all doors are closed
    // and a new alert is sent to indicate this

    // Set ReminderTimer to send status Tweets every hour if at least one door remains open
    // Debug - Reminder every 5 minutes, Normal - Reminder every hour
    if (debug & debugTimers) set_next_timeout(&reminderTimer, proc_time, (unsigned long)(debugReminder * minuteM));
    else set_next_timeout(&reminderTimer, proc_time, (unsigned long)(normalReminder * minuteM)); // Remind every 30 minutes

  }

  show_step(5);

  // Turn off alert state and send and all clear alert when all doors closed
  if ( (alert_state == TRUE) &&
       ( (Door[DoorLft] == CLOSED) && (Door[DoorMid] == CLOSED) && (Door[DoorRgt] == CLOSED) ) )
    {
      if (debug) DebugPrintF(f_adct);  // Serial.print("\n--> All doors closed tweet\n");

      alert_state = FALSE;

      send_tweet(FALSE);                // Send all clear tweet
      for (j = 0; j < 3; j++) {
        open_clock[j] = 0;        // clear door open timers
        clock_running[j] = FALSE;
      }
      reminderTimer = 0;  // No more reminders necessary
    }

  show_step(6);

  if (reminderTimer && check_for_timeout(reminderTimer,proc_time)){
    // Reminder has timed out. Time to send status again
    // First set another reminder period
    if (debug & debugTimers) {
      set_next_timeout(&reminderTimer, proc_time, (unsigned long)(debugReminder * minuteM));
      DebugPrintF(f_reminder);
      Serial.println((char *)"");
    }
    else set_next_timeout(&reminderTimer, proc_time, (unsigned long)(normalReminder * minuteM));
    send_tweet(FALSE);
  }

  show_step(7);

  // Update the time of day once each hour
  if (reqTimeUpdate && check_for_timeout(reqTimeUpdate,proc_time)){ // time to update clock from Internet
    getTime();
    if (debug & debugTimers)
         set_next_timeout(&reqTimeUpdate, proc_time, (unsigned long)(debugClkUpdate * minuteM));  // request time again in 5 minute
    else set_next_timeout(&reqTimeUpdate, proc_time, (unsigned long)(normalClkUpdate * minuteM));  // update clock once per hour
  }

  show_step(8);

  // Maintain current time of day once it is read off the Internet
  // proc_time could be incorrect because the application hung up for several minutes
  // resetting the WiFi hardware. But the following loop will account for all
  // the time that has passed and correctly update the time of day clock.
  unsigned long last_timeout;
  if (clk_set && check_for_timeout(clk_time,proc_time)){  // 1 or more seconds have passed since last clock update
    while (clk_time <= proc_time){
      last_clk_time = clk_time;
      cur_second += 1;
      if (cur_second >= 60) {cur_second = 0; cur_minute += 1;}
      if (cur_minute >= 60) {cur_minute = 0; cur_hour += 1;}
      if (cur_hour >= 24) {cur_minute = 0; cur_hour = 0;}
      set_next_timeout(&clk_time, clk_time, (unsigned long)1000L);          // add the 1 second just accounted for - loop until clock is adjusted
      // Check if last clk_time was a large value but the new clk_time is small
      if ((last_clk_time & 0x80000000) && (clk_time && 0xC0000000 == 0)){
         // clock is about to wrap around. Stop updating the time of
         // day until the wrap around which will happen within 1 minute
         // The clock will not be update again until the millis() wrap around
         // It will then incorrectly update the HH:MM:SS because it will have
         // missed about 1 minute of updates. But the next call to get_time()
         // to read the clock from the cloud, will correct the time of day.
         // The clock will be off by 1 minute for 1 hour or less (until the
         // time of day update).
         break;
      }
    }
  }
  show_step(9);

  // ---- LED Light Patterns will reflect the current state of processing

  // Blink Blue LED in various patterns to indicate status
  // 4 sec on / 4 sec off - idle mode
  // 1 sec on / 1 sec off - door is open
  // 4x/ sec - door is open and alert tweet sent - waiting for it to close to send another alert
  if (check_for_timeout(ledTimer,proc_time)){
    ledState = (ledState == HIGH)? LOW : HIGH;    // change state of the Yellow LED
    digitalWrite(LED,ledState);
    // Determine when next change of state is to occur
    if ((Door[DoorLft] == OPEN) || (Door[DoorMid] == OPEN) || (Door[DoorRgt] == OPEN)){
      // At least one door is open
      if (alert_state) set_next_timeout(&ledTimer, millis(), (unsigned long)125L);      // Blink 4x a second in alert state waiting for door to close
      else set_next_timeout(&ledTimer, millis(), (unsigned long)1000L);  // Blink once per second while door open for short time period
    }
    else {
      anyDoorOpen = FALSE;           // All doors were just closed
      set_next_timeout(&ledTimer, millis(), (unsigned long)4000L);    // Blink slowly to show program is running
    }
  }
  show_step(10);

  if ((anyDoorOpen == FALSE) && ((Door[DoorLft] == OPEN) || (Door[DoorMid] == OPEN) || (Door[DoorRgt] == OPEN))) {
    anyDoorOpen = TRUE;
    ledTimer = 0;
    ledState = LOW;          // immediately display open door state
  }
  if ((anyDoorOpen == TRUE) && ((Door[DoorLft] == CLOSED) && (Door[DoorMid] == CLOSED) && (Door[DoorRgt] == CLOSED))) {
    anyDoorOpen = FALSE;
    ledTimer = 0;
    ledState = LOW;          // immediately display open door state
  }
  mainCount++;

  show_step(11);

  if (debug){
    // insure proc_time is up to date. Could be wrong if we got hung up in the TCP
    // stack trying to reset the WiFi hardware. That could hang for minutes.
    proc_time = millis();
    if (check_for_timeout(print_time,proc_time)){
      set_next_timeout(&print_time, proc_time, (unsigned long)10000L);
      DebugPrintFO(f_debug_clock);   // Clock is at ##:##:##
      if (clk_set){
        if(cur_hour <= 9) Serial.print("0");
        Serial.print(int(cur_hour));
        Serial.print(":");
        if(cur_minute <= 9) Serial.print("0");
        Serial.print(int(cur_minute));
        Serial.print(":");
        if(cur_second <=9) Serial.print("0");
        Serial.print(int(cur_second));

        char bfr[90], fmt[68];

        strcpy_P(fmt,fmt3);
        sprintf(bfr,fmt,mainCount,millis());
        Serial.println(bfr);
      }
      else Serial.println((char *)"");
    }
  }

  show_step(12);
  // Wait 10ms before next loop
  delay(10);

  show_step(13);
}

// Check for Daylight Savings Time
bool IsDST(int dayOfMonth, int month, int dayOfWeek)
{
  if (month < 3 || month > 11)
  {
    return false;
  }
  if (month > 3 && month < 11)
  {
    return true;
  }
  int previousSunday = dayOfMonth - (dayOfWeek - 1);
  if (month == 3)
  {
    return previousSunday >= 8;
  }
  return previousSunday <= 0;
}
Click to Expand

Content Rating

Is this a good/useful/informative piece of content to include in the project? Have your say!

0