// 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!
You must login before you can post a comment. .