Here is my attempt at creating this event log functionality for MyDoorOpener:
I wanted to store the Open/Close events in a MySQL database on my WAMP WinXP Server (http://www.yetihacks.com/2013/03/arduin ... d-php.html), using this table:
- Code: Select all
CREATE TABLE mydooropenerevents (
eventid int(32) NOT NULL AUTO_INCREMENT,
eventuser varchar(25) NOT NULL,
eventstate varchar(25) NOT NULL,
eventdevice varchar(25) NOT NULL,
eventtime timestamp NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (eventid)
)
ENGINE=InnoDB
AUTO_INCREMENT=2416
;
and a PHP script to add the event (called from my sketch):
- Code: Select all
<?php
//
// FileName="addMyDoorOpenerEvent.php"
//Include the connectMyDoorOpener.php file this file contains the information
// needed to connect to you database.
//
//http://localhost/addMyDoorOpenerEvent.php?user=Sallee&state=Close&device=Garage Door
//
//MySQL Connection
$hostname = "localhost";
$database = "YourDatabase";
$username = "root";
$password = "YourPassword";
// time zone set to ensure the correct time stamp is added to the database with each rfid store event.
date_default_timezone_set('America/New_York');
//get the card id comming in from the arduino
$user = $_GET['user'];
$state = $_GET['state'];
$device = $_GET['device'];
// setup the conection to the database server,
//if the attempt fails exit and send the message back to the arduino
$connect = mysql_connect($hostname, $username, $password) or die( '<'."can't connect to DB" .'>'. mysql_error());
// setup the conection to the database
//if the attempt fails exit and send the mesage back to the arduino
$bool = mysql_select_db($database, $connect);
if ($bool === False){ echo "< DB Error >"; }
//setup the Insert
$query = "INSERT INTO mydooropenerevents (eventuser, eventstate, eventdevice) VALUES ('" .$user. "','" .$state. "','" .$device. "')";
$result = mysql_query($query) or die( '<'.'< Event not created>'.'>' );
//echo '<'." new event added".'>';
echo("<Event successfully created!>");
mysql_close($connect);
?>
I also wanted to query this MySQL table from a web page, again using a PHP script:
- Code: Select all
<?php
//
// FileName="MyDoorOpenerEvents.php"
//
//http://localhost/MyDoorOpenerEvents.php/?user=All&state=Open&device=Garage%20Door&date=2014-02-14
//
$hostname = "localhost";
$database = "mydooropener";
$username = "root";
$password = "****************";
if (isset($_GET["pageType"]))
{
$pageType = $_GET["pageType"];
}
else
{
$pageType = "null";
}
$user = $_POST["user"];
$state = $_POST["state"];
$device = $_POST["device"];
$date = $_POST["date"];
$num_params = sizeof($_GET);
if ($num_params == 0)
{
// print "usage: MtDoorOpener.php?year=<i>YYYY</i> (".$user."/".$state."/".$device."/".$date.")\n ";
print ".";
}
// Show the events in an HTML <table>
function displayEvents($result, $connection, $query)
{
// print "<h1>".$query."</h1>\n";
// Start a table, with column headers
print "\n<br><br><table>\n<tr>\n" .
"\n\t<th>User</th>" .
"\n\t<th>State</th>" .
"\n\t<th>Device</th>" .
"\n\t<th>Date/Time</th>" .
"\n</tr>";
// Until there are no rows in the result set, fetch a row into
// the $row array and ...
while ($row = mysql_fetch_row($result))
{
// ... start a TABLE row ...
print "\n<tr>";
// ... and print out each of the attributes in that row as a
// separate TD (Table Data).
foreach($row as $data)
print "\n\t<td> {$data} </td>";
// Finish the row
print "\n</tr>";
}
// Then, finish the table
print "\n</table>\n";
// print "\ndocument.formFrame.year.focus()\n";
}
if ($pageType == "null")
{
print "<html>\n";
print " <head>\n";
print " <style>\n";
print " FRAME\n";
print " {\n";
print " border-left: 1px solid black;\n";
print " }\n";
print " </style>\n";
print " </head>\n";
print " <frameset cols='285,*' border='0'>\n";
print " <frame name='formFrame' src='MyDoorOpener.php?pageType=form'/>\n";
print " <frame name='resultFrame' src='MyDoorOpener.php?pageType=result'/>\n";
print " </frameset>\n";
print "</html>\n";
}
else if ($pageType == "form")
{
print "<html>\n";
print " <head>\n";
print " <style>\n";
print " BODY\n";
print " {\n";
print " font-size: 10pt;\n";
print " font-family: Arial;\n";
print " text-align: center;\n";
print " background-color: #FFFFEF;\n";
print " }\n";
print " H1\n";
print " {\n";
print " font-size: 16pt;\n";
print " font-color: blue;\n";
print " }\n";
print " H2\n";
print " {\n";
print " font-size: 12pt;\n";
print " font-color: blue;\n";
print " }\n";
print " DIV.InputForm\n";
print " {\n";
print " background-color: #EFEFEF;\n";
print " padding: 10px;\n";
print " border: 1px solid black;\n";
print " }\n";
print " TABLE TR TD\n";
print " {\n";
print " font-size: 10pt;\n";
print " }\n";
print " DIV.instructions\n";
print " {\n";
print " text-align: left;\n";
print " }\n";
print " </style>\n";
print " </head>\n";
print " <body onLoad=\"document.getElementById('year').focus();\">\n";
print " <h1>MyDoorOpener Events</h1>\n";
print " <br/><br/>\n";
print " <form action='MyDoorOpener.php' method='post' target='resultFrame'>\n";
print " <input type='hidden' name='pageType' value='result'>\n";
print " <div class='inputForm'>\n";
print " <table>\n";
print " <tr><td>User:</td><td><input type='text' name='user' id='user'></td></tr>\n";
print " <tr><td>State:</td><td><input type='text' name='state' id='state'></td></tr>\n";
print " <tr><td>Device:</td><td><input type='text' name='device'></td></tr>\n";
print " <tr><td>Date:</td><td><input type='text' name='date'></td></tr>\n";
print " <tr><td colspan='2'><center><input type='submit' value='Submit' name='submit'></center></td></tr>\n";
print " </table>\n";
print " </div>\n";
print " </form>\n";
print " <br/>\n";
print " <div class='instructions'>\n";
print " <h2>\n";
print " Instructions\n";
print " </h2>\n";
print " Enter the User, State, Device or Date for your query. \n";
print " <p/>\n";
print " Date query format should be YYYY-MM-DD. \n";
print " <p/>\n";
print " To see all Events, just enter All in User and click the Submit button. \n";
print " <p/>\n";
print " All results will appear in the frame to the right.\n";
print " </div>\n";
print " </body>\n";
print "</html>\n";
}
// Connect to the MySQL server
$connection = mysql_connect($hostname, $username, $password) or die('Could not connect: ' . mysql_error());
mysql_select_db($database, $connection) or die('Could not select the database: ' . $database);
//$query = "SELECT `eventuser`, `eventstate`, `eventdevice`, `eventtime` FROM mydooropenerevents";
$query = "SELECT `eventuser`, `eventstate`, `eventdevice`, Date_Format(eventtime, '%W, %M %d, %Y %h:%i:%s %p') FROM mydooropenerevents";
$where = " where ";
// User
//if (strlen($user) > 0) && ($user != "ALL") && ($user != "All")
if (strlen($user) > 0)
{
if ($user != "all" && $user != "All" && $user != "ALL")
{
if (strlen($where) > 7)
$where = $where . "and `eventuser` like '".$user."%' ";
else
$where = $where . "`eventuser` like '".$user."%' ";
}
}
// State
if (strlen($state) > 0)
{
if (strlen($where) > 7)
$where = $where . "and `eventstate` like '".$state."%' ";
else
$where = $where . "`eventstate` like '".$state."%' ";
}
// Device
if (strlen($device) > 0)
{
if (strlen($where)> 7 )
$where = $where . "and `eventdevice` like '".$device."%' ";
else
$where = $where . "`eventdevice` like '".$device."%' ";
}
// Date
if (strlen($date) > 0)
{
if (strlen($where)> 7 )
$where = $where . "and `eventtime` like '".$date."%' ";
else
$where = $where . "`eventtime` like '".$date."%' ";
}
if (strlen($where) == 7 )
$query = $query . " where eventuser like '%' ";
else
$query = $query . $where;
$result = mysql_query ($query, $connection) or die('Query failed: ' . mysql_error());
// Display the results
if ( (strlen($user)>0) or (strlen($state)>0) or(strlen($device)>0) or(strlen($date)>0) )
displayEvents($result, $connection, $query);
// Free resultset
mysql_free_result($result);
// Closing connection
mysql_close($connection);
?>
Here is a screen shot of the web page:
I had an issue with Comcast SMTP with Authentication, decided, since I was using WAMP for event logging, I would use WAMP for Comcast SMTP/SMS with Authentication
(http://stackoverflow.com/questions/14822656/setting-smtp-on-php-ini-with-authentication), below is the PHP script (PEAR and a few packages are required), again, called from my sketch:
- Code: Select all
<?php
//
// FileName="MyDoorOpenerEmail.php"
//
// Must install PEAR and these PEAR packages: NET_SMTP, NET_Socket and Auth_SASL
//http://stackoverflow.com/questions/14822656/setting-smtp-on-php-ini-with-authentication
//http://email.about.com/od/emailprogrammingtips/qt/PHP_Email_SMTP_Authentication.htm
//
//http://localhost/MyDoorOpenerEmail.php?to=5083453375@tmomail.net&subject=Hi!&body=Hi, \n\nHow are you today?
//
// Comcast Email
$from = "noreply@mydooropener.com";
$host = "smtp.comcast.net";
$port = "587";
$localhost = "relai.mydooropener.com";
$timeout = 20;
$username = "YourEmail@comcast.net";
$password = "YourEmailPassword";
require_once "mail.php";
$to = $_GET['to'];
$subject = $_GET['subject'];
$body = $_GET['body'];
$body = $body . "\n" . date("F, j, Y, g:i:s a");
$headers = array ('From' => $from,
'To' => $to,
'Subject' => $subject);
$smtp = Mail::factory('smtp',
array ('host' => $host,
'port' => $port,
'localhost' => $localhost,
'auth' => true,
'username' => $username,
'password' => $password));
$mail = $smtp->send($to, $headers, $body);
if (PEAR::isError($mail)) {
echo("<" . $mail->getMessage() . ">");
} else {
echo("<Message successfully sent!>");
}
?>
Here is the sketch:
- Code: Select all
//----------------------------------------------------------------------------------------------------
// Copyright (C) 2009 - 2012, MyDoorOpener.com
//----------------------------------------------------------------------------------------------------
//
// Release Notes:
//
// v1.0 [02/23/2010] - Initial release
//
// v1.1 [03/02/2010] - Added support for up to three garage doors.
//
// v1.2 [08/11/2010] - Deprecated HTTP POST in favor of HTTP GET parameters.
// - Added additional serial debugging.
//
// v2.0 [05/11/2012] - Updated to support unlimited number of devices (used to be max 3).
// - Updated to support Arduino v1.0 IDE.
//
// v2.2 [12/15/2012] - Updated to support SMTP email notifications
// - Updated to support SMS notifications (via carrier provided SMTPtoSMS gateway)
// - Updated to support iOS push notifications (via Prowl) Removed EWE...
// - Added logic to support multiple status reading strategies (3v5v, NC, NO, etc)
// - Relocated most string literals to flash memory
// - Certified compatibility with ArduinoEthernet (all-in-one) and DFRobot RelayShield hardware
// - Certified compatibility with Arduino v1.0.3 IDE
//
//----------------------------------------------------------------------------------------------------
//
//----------------------------------------------------------------------------------------------------
// Hardware...
//----------------------------------------------------------------------------------------------------
//
//SainSmart UNO R3 + Ethernet Shield W5100
//http://www.sainsmart.com/arduino/arduino-kits/uno-r3-smartkits/sainsmart-uno-r3-improved-version-ethernet-shield-w5100-for-arduino.html
//
//SainSmart Sensor Shield V5 - Not required Dupont jumpers instead of soldering.
//http://www.sainsmart.com/arduino/arduino-shields/sainsmart-sensor-shield-v5-4-arduino-apc220-bluetooth-analog-module-servo-motor.html
//
//SainSmart 4-Channel 5V Relay Module
//http://www.sainsmart.com/4-channel-5v-relay-module-for-pic-arm-avr-dsp-arduino-msp430-ttl-logic.html
//
// Picture...
//http://ewenfs.dyndns.org/MyDoorOpener.JPG
//----------------------------------------------------------------------------------------------------
// Uncomment to turn ON serial debugging
//#define MYDOOROPENER_SERIAL_DEBUGGING 1
//#define WEBDUINO_SERIAL_DEBUGGING 1
//#define NOTIFICATIONS_SERIAL_DEBUGGING 1
//#define SMTP_SERIAL_DEBUGGING 1
//#define PUSH_SERIAL_DEBUGGING 1
//#define LOGGING_SERIAL_DEBUGGING 1
//*******************************************************************
// Global configuration (adjust to reflect your setup)
//*******************************************************************
// if defined, will fire a notification when any door/device stays open more than the specified number of minutes
//#define NOTIFICATIONS_WATCHDOG_MINUTES 5
// if defined, will fire a notification every time and as soon as any door/device gets opened
//#define NOTIFICATIONS_OPEN
// if defined, will fire notifications using iOS push notifications (uncomment to turn ON)
//#define PUSH_NOTIFICATIONS
// if defined, will fire notifications using SMS notifications (uncomment to turn ON - remember to adjust SMTP settings too)
//#define SMS_NOTIFICATIONS
// if defined, will use SMTP with Authentication ( http://stackoverflow.com/questions/14822656/setting-smtp-on-php-ini-with-authentication) to SMS Email with Authentication
#define SMS_AUTHENTICATION_NOTIFICATIONS
// if defined, will fire notifications using SMTP notifications
//#define SMTP_NOTIFICATIONS
// if defined, will use SMTP with Authentication (http://stackoverflow.com/questions/14822656/setting-smtp-on-php-ini-with-authentication) to SMTP Email with Authentication
//#define SMTP_AUTHENTICATION_NOTIFICATIONS
// if defined, will use WAMP/MAMP ( http://www.yetihacks.com/2013/arduino-mysql-and-php.html ) to write Open/Close Events to MySQL, implied NOTIFICATIONS_OPEN.
#define EVENT_LOGGING // Log State changes (Open to Close and Close to Open).
//*******************************************************************
#include <Arduino.h>
#include <SPI.h>
#include <Time.h>
#include <Ethernet.h>
#include <WebServer.h>
#include <aes256.h>
//*******************************************************************
// Global configuration (adjust to reflect your setup)
//*******************************************************************
// EthernetShield IP address (DHCP reserved, never allocated to anyone else). This is the internal
// network IP address you want your Arduino assigned. This is not the address you will be accessing
// your Arduino from the iPhone application or internet ... You need to configure NAT forwarding
// on your home router to do that. See http://en.wikipedia.org/wiki/Port_forwarding for more details.
static const uint8_t ip[4] = {10, 0, 0, 101};
// password required for operating door [maximum length = 16] (status fetching doesn't require password). This
// must match the password you set in the iPhone application (be careful as case is sensitive).
#define PASSWORD "****************"
//*******************************************************************
// Device/Door configuration (relais and sensors)
//*******************************************************************
// *** IMPORTANT NOTE ***
//
// THE ARDUINO ETHERNET SHIELD RESERVES DIGITAL PINS 10, 11, 12, 13 AS WELL AS
// ANALOG PINS 0, 1, THEREFORE YOU SHOULD NOT USE ANY OF THOSE PINS FOR YOUR DEVICE(S) OR DOOR(S)
// open/close trigger relay should be connected to these digital output pins (digitalWrite).
// Adjust to match the number of devices you have hooked up (examples provided below in comment) ...
static const uint8_t relayPins[] = {9}; // single device at pin #9
//static uint8_t relayPins[] = { 2, 3, 4, 5 }; // select if using DFRobot RelayShield
//static uint8_t relayPins[] = { 2, 3 }; // two devices at pins #2 and #3
//static uint8_t relayPins[] = { 2, 3, 4, ... }; // even more devices at pins #2, #3, #4, etc ...
// status contact should be connected to these analog input pins (anologRead).
// Adjust to match the number of devices you have hooked up (examples provided below in comment) ...
static const uint8_t statusPins[sizeof(relayPins)] = {3}; // single device at pin #3
//static uint8_t statusPins[] = { 2, 3, 4, 5 }; // select if using DFRobot RelayShield
//static uint8_t statusPins[] = { 2, 3 }; // two devices at pins #2 and #3
//static uint8_t statusPins[] = { 2, 3, 4, ... }; // even more devices at pins #2, #3, #4, etc ...
// added door labels for SMS and email reporting
static char door_label[sizeof(relayPins)][21] = {"Garage Door"}; // substitute with your door labels (make each 20 characters or less)
#define APP_NOTIFICATION_SUBJECT "Your House Notification: "
#define ARDUINO_NAME "Controller #1"
// status reading strategy (uncomment only one ... the one that reflects how you want status to be interpreted)
//#define STATUS_STRATEGY_3VCLOSED_5VOPENED // initial approach - uses analogRead combined with STATUS_OPEN_TRESHOLD (opened == +5v, closed == +3v)
//#define STATUS_STRATEGY_5VCLOSED_3VOPENED // alternate approach - uses analogRead combined with STATUS_OPEN_TRESHOLD (opened == +3v, closed == +5v)
//#define STATUS_STRATEGY_NORMALLY_CLOSED // classic door sensor - uses digitalRead to interpret door/device status (opened == high-impedance, closed == GND)
#define STATUS_STRATEGY_NORMALLY_OPENED // alternate approach - uses digitalRead to interpret door/device status (opened == GND, closed == high-impedance)
// Analog boundary value (0-1023) used to distinguish between device/door status == opened and closed. Only applicable
// when STATUS_STRATEGY_3VCLOSED_5VOPENED or STATUS_STRATEGY_5VCLOSED_3VOPENED is being used.
//#define STATUS_OPEN_TRESHOLD 1000
//*******************************************************************
// Notifications
//*******************************************************************
// if defined, will fire a notification when any door/device stays open more than the specified number of minutes
//#define NOTIFICATIONS_WATCHDOG_MINUTES 5
// if defined, will fire a notification every time and as soon as any door/device gets opened
//#define NOTIFICATIONS_OPEN
//*******************************************************************
// iOS push notifications (via Prowl)
//*******************************************************************
// if defined, will fire notifications using iOS push notifications (uncomment to turn ON)
//#define PUSH_NOTIFICATIONS
#if defined(PUSH_NOTIFICATIONS)
// after installing Prowl (iTunes store) on your iPhone, set to match the Prowl API key that was
// assigned for your iPhone http://www.prowlapp.com/api_settings.php
static const char prowlApiKey[] = "paste-your-prowl-provided-api-key-here";
// set to Prowl HTTP server name (typically api.prowlapp.com)
static const char prowlServerName[] = "api.prowlapp.com";
// set to Prowl HTTP port number (typically port 80)
static const int prowlServerPort = 80;
// set to Prowl API base url (typically /publicapi/add)
static const char prowlApiBaseUrl[] = "/publicapi/add";
#endif
//*******************************************************************
// SMS notifications (via carrier provided SMS gateway, relies on SMTP)
//*******************************************************************
// if defined, will fire notifications using SMS notifications (uncomment to turn ON - remember to adjust SMTP settings too)
//#define SMS_NOTIFICATIONS
#if defined(SMS_NOTIFICATIONS) || defined(SMS_AUTHENTICATION_NOTIFICATIONS)
// using http://en.wikipedia.org/wiki/List_of_SMS_gateways set to match your mobile carrier and phone number
static const char smtpToForSms[] = "1234567890@tmomail.net";
#endif
//*******************************************************************
// SMTP email notifications (also used for SMS notifications)
//*******************************************************************
// if defined, will fire notifications using SMTP notifications (uncomment to turn ON)
//#define SMTP_NOTIFICATIONS
#if defined(SMTP_NOTIFICATIONS) || defined(SMTP_AUTHENTICATION_NOTIFICATIONS)
// set to the email address you want notifications sent to
static const char smtpToForEmail[] = "YourEmail@comcast.net";
#endif
#if defined(SMTP_NOTIFICATIONS) || defined(SMS_NOTIFICATIONS)
// set to your ISP's SMTP server name
static const char smtpServerName[] = "smtp-server.your-isp.com";
// set to your ISP's SMTP server port (typically port 25)
static const int smtpServerPort = 25;
#endif
#if defined(SMTP_AUTHENTICATION_NOTIFICATIONS) || defined(SMS_AUTHENTICATION_NOTIFICATIONS)
// set to AuthenticationHTTP server IP - WAMP/MAMP Server
static const IPAddress AuthenticationServerIP(10, 0, 0, 100);
// set to Authentication HTTP port number (typically port 80)
static const int AuthenticationServerPort = 80;
#endif
//*******************************************************************
// Event Logging
//*******************************************************************
#define EVENT_LOGGING
#if defined(EVENT_LOGGING)
// set to eventLogging HTTP server IP - WAMP/MAMP Server
static const IPAddress eventLogServerIP(10, 0, 0, 100);
// set to eventLogging HTTP port number (typically port 80)
static const int eventLogServerPort = 80;
//Lookup Username using Password (see MyDoorOpener.h)
typedef struct passwordUserLookup {
char *pulPassword;
char *pulUserName;
}
passwordUserLookup;
static const passwordUserLookup passwordUserLookupKeywords[] = {
{pulPassword: "Password1", pulUserName: "User1"},
{pulPassword: "Password2", pulUserName: "User2"},
{pulPassword: "Password3", pulUserName: "User3"}
};
//Relay State
typedef struct relayState {
uint8_t pulIndex; // index of the pulUserName - out of range is User Manual
boolean currentState; // 0 or 1 (Open or Close)
}
relayState;
relayState relayStates[sizeof(relayPins)] = {
{pulIndex: 0, currentState: 0}
};
#endif
//*******************************************************************
// Misc configuration constants (should not require any changes)
//*******************************************************************
// EthernetShield MAC address.
static uint8_t mac[6] = {0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED};
// Arduino HTTP server listening port number.
WebServer webserver("", 8002);
// number of milliseconds relay pin will be held high when triggered.
#define RELAY_DELAY 1000
// misc size constants
#define HTTP_PARAM_NAME_SIZE 16
#define HTTP_PARAM_VALUE_SIZE 64
#define PASSWORD_HEX_SIZE 32
#define PASSWORD_SIZE 16
#define AES256_CRYPTO_KEY_SIZE 32
#define CHALLENGE_TOKEN_SIZE 16
//----------------------------------------------------------------------------------------------------
#if defined(EVENT_LOGGING) || defined(PUSH_NOTIFICATIONS)
//----------------------------------------------------------------------------------------------------
void getUrlEncoded(const char* msg, char* encodedMsg)
{
const char hex[] = "0123456789abcdef";
while (*msg != '\0') {
if (('a' <= *msg && *msg <= 'z') || ('A' <= *msg && *msg <= 'Z') || ('0' <= *msg && *msg <= '9')) {
*encodedMsg = *msg;
encodedMsg++;
}
else
{
*encodedMsg = '%';
encodedMsg++;
*encodedMsg = hex[*msg >> 4];
encodedMsg++;
*encodedMsg = hex[*msg & 15];
encodedMsg++;
}
msg++;
}
}
#endif
//----------------------------------------------------------------------------------------------------
#if defined(PUSH_NOTIFICATIONS)
//----------------------------------------------------------------------------------------------------
EthernetClient pushClient;
void notifyViaPush(const char* subject, const char* body)
{
#if defined(PUSH_SERIAL_DEBUGGING)
Serial.print(F("*** Push - server name: '"));
Serial.print(prowlServerName);
Serial.print(F("' - "));
Serial.print(F("apiKey: '"));
Serial.print(prowlApiKey);
Serial.print(F("' - "));
Serial.print(F("subject: '"));
Serial.print(subject);
Serial.print(F("' - "));
Serial.print(F("body: '"));
Serial.print(body);
Serial.println(F("' ***"));
#endif
if (pushClient.connect(prowlServerName, prowlServerPort))
{
#if defined(PUSH_SERIAL_DEBUGGING)
Serial.println(F("*** Push - connection established ***"));
#endif
pushClient.print(F("GET "));
pushClient.print(prowlApiBaseUrl);
pushClient.print(F("?apikey="));
pushClient.print(prowlApiKey);
pushClient.print(F("&application=MyDoorOpener"));
pushClient.print(F("&url=mydooropener%3A%2F%2Fstatus"));
char encodedBuffer[100] = "";
pushClient.print(F("&event="));
memset(encodedBuffer, 0, 100);
getUrlEncoded(subject, encodedBuffer);
pushClient.print(encodedBuffer);
pushClient.print(F("&description="));
memset(encodedBuffer, 0, 100);
getUrlEncoded(body, encodedBuffer);
pushClient.print(encodedBuffer);
pushClient.println(F(" HTTP/1.1"));
pushClient.print(F("Host: "));
pushClient.println(prowlServerName);
pushClient.print(F("Accept: *"));
pushClient.print(F("/"));
pushClient.println(F("*"));
pushClient.println(F("Connection: close"));
pushClient.println();
pushClient.stop();
#if defined(PUSH_SERIAL_DEBUGGING)
Serial.println(F("*** Push - connection stopped ***"));
#endif
}
#if defined(PUSH_SERIAL_DEBUGGING)
Serial.println(F("*** Push - completed ***"));
#endif
}
#endif
//----------------------------------------------------------------------------------------------------
#if defined(SMTP_NOTIFICATIONS) || defined(SMS_NOTIFICATIONS)
//----------------------------------------------------------------------------------------------------
EthernetClient smtpClient;
void notifyViaEmail(const String& to, const String& subject, const String& body)
{
#if defined(SMTP_SERIAL_DEBUGGING)
Serial.print(F("*** SMTP - server name: '"));
Serial.print(smtpServerName);
Serial.print(F("' - "));
Serial.print(F("to: '"));
Serial.print(to);
Serial.print(F("' - "));
Serial.print(F("subject: '"));
Serial.print(subject);
Serial.print(F("' - "));
Serial.print(F("body: '"));
Serial.print(body);
Serial.println(F("' ***"));
#endif
if (smtpClient.connect(smtpServerName, smtpServerPort))
{
#if defined(SMTP_SERIAL_DEBUGGING)
Serial.println(F("*** SMTP - connection established ***"));
#endif
smtpClient.println(F("HELO relai.mydooropener.com"));
smtpClient.print(F("MAIL FROM:"));
smtpClient.println(F("noreply@mydooropener.com"));
smtpClient.print(F("RCPT TO:"));
smtpClient.println(to);
smtpClient.println(F("DATA"));
smtpClient.print(F("SUBJECT: "));
smtpClient.println(subject);
smtpClient.println();
smtpClient.print(body);
smtpClient.print(F(" "));
smtpClient.println(F("mydooropener://status"));
smtpClient.println(F("."));
smtpClient.println(F("."));
smtpClient.println(F("QUIT"));
smtpClient.stop();
#if defined(SMTP_SERIAL_DEBUGGING)
Serial.println(F("*** SMTP - connection stopped ***"));
#endif
}
#if defined(SMTP_SERIAL_DEBUGGING)
Serial.println(F("*** SMTP - completed ***"));
#endif
}
#endif
//----------------------------------------------------------------------------------------------------
#if defined(SMTP_NOTIFICATIONS) || defined(SMTP_AUTHENTICATION_NOTIFICATIONS)
//----------------------------------------------------------------------------------------------------
void notifyViaEmail(const char* subject, const char* body)
{
#if defined(SMTP_AUTHENTICATION_NOTIFICATIONS)
notifyViaEmailAuthentication(smtpToForEmail, subject, body);
#else
notifyViaEmail(smtpToForEmail, subject, body);
#endif
}
#endif
//----------------------------------------------------------------------------------------------------
#if defined(SMS_NOTIFICATIONS) || defined(SMS_AUTHENTICATION_NOTIFICATIONS)
//----------------------------------------------------------------------------------------------------
void notifyViaSms(const char* subject, const char* body)
{
#if defined(SMS_AUTHENTICATION_NOTIFICATIONS)
notifyViaEmailAuthentication(smtpToForSms, subject, body);
#else
notifyViaEmail(smtpToForSms, subject, body);
#endif
}
#endif
//----------------------------------------------------------------------------------------------------
#if defined(SMTP_AUTHENTICATION_NOTIFICATIONS) || defined(SMS_AUTHENTICATION_NOTIFICATIONS)
//----------------------------------------------------------------------------------------------------
EthernetClient smtpAuthenticationClient;
void notifyViaEmailAuthentication(const char *to, const char *subject, const char *body)
{
#if defined(SMTP_SERIAL_DEBUGGING)
Serial.print(F("*** SMTP - server IP: '"));
Serial.print(AuthenticationServerIP);
Serial.print(F("' - "));
Serial.print(F("to: '"));
Serial.print(to);
Serial.print(F("' - "));
Serial.print(F("subject: '"));
Serial.print(subject);
Serial.print(F("' - "));
Serial.print(F("body: '"));
Serial.print(body);
Serial.print(F("' - "));
Serial.println(F("' ***"));
#endif
if (smtpAuthenticationClient.connect(AuthenticationServerIP, AuthenticationServerPort))
{
#if defined(SMTP_SERIAL_DEBUGGING)
Serial.println(F("*** SMTP - connection established ***"));
#endif
char encodedBuffer[100] = "";
smtpAuthenticationClient.print(F("GET /MyDoorOpenerEmail.php?"));
smtpAuthenticationClient.print(F("to="));
smtpAuthenticationClient.print(to);
smtpAuthenticationClient.print(F("&subject="));
memset(encodedBuffer, 0, 100);
getUrlEncoded(subject, encodedBuffer);
smtpAuthenticationClient.print(encodedBuffer);
smtpAuthenticationClient.print(F("&body="));
memset(encodedBuffer, 0, 100);
getUrlEncoded(body, encodedBuffer);
smtpAuthenticationClient.print(encodedBuffer);
smtpAuthenticationClient.println(F(" HTTP/1.1"));
smtpAuthenticationClient.println(F("Host: localhost"));
smtpAuthenticationClient.println(F("Connection: close"));
smtpAuthenticationClient.println(F(" "));
#if defined(SMTP_SERIAL_DEBUGGING)
do {
char c = smtpAuthenticationClient.read();
Serial.print(c);
} while (smtpAuthenticationClient.connected());
#endif
smtpAuthenticationClient.stop();
#if defined(SMTP_SERIAL_DEBUGGING)
Serial.println(F("*** SMTP - connection stopped ***"));
#endif
}
#if defined(SMTP_SERIAL_DEBUGGING)
Serial.println(F("*** SMTP - completed ***"));
#endif
}
#endif
//----------------------------------------------------------------------------------------------------
#if defined(EVENT_LOGGING)
//----------------------------------------------------------------------------------------------------
EthernetClient logClient;
void logEventHandler(int index)
{
#if defined(LOGGING_SERIAL_DEBUGGING)
Serial.print(F("*** Logging - server IP: '"));
Serial.print(eventLogServerIP);
Serial.print(F("' - "));
Serial.print(F("user: '"));
if (relayStates[index].pulIndex == sizeof(passwordUserLookupKeywords) / sizeof(passwordUserLookup))
Serial.print("Manual");
else
Serial.print(passwordUserLookupKeywords[relayStates[index].pulIndex].pulUserName);
Serial.print(F("' - "));
Serial.print(F("state: '"));
Serial.print(relayStates[index].currentState ? F("Open") : F("Close"));
Serial.print(F("' - "));
Serial.print(F("device: '"));
Serial.print(door_label[index]);
Serial.print(F("' - "));
Serial.println(F("' ***"));
#endif
if (logClient.connect(eventLogServerIP, eventLogServerPort))
{
#if defined(LOGGING_SERIAL_DEBUGGING)
Serial.println(F("*** Logging - connection established ***"));
#endif
char encodedBuffer[100] = "";
logClient.print(F("GET /addMyDoorOpenerEvent.php?"));
logClient.print(F("user="));
memset(encodedBuffer, 0, 100);
if (relayStates[index].pulIndex == sizeof(passwordUserLookupKeywords) / sizeof(passwordUserLookup))
strcpy(encodedBuffer, "Manual");
else
getUrlEncoded(passwordUserLookupKeywords[relayStates[index].pulIndex].pulUserName, encodedBuffer);
logClient.print(encodedBuffer);
logClient.print(F("&state="));
logClient.print(relayStates[index].currentState ? F("Open") : F("Close"));
logClient.print(F("&device="));
memset(encodedBuffer, 0, 100);
getUrlEncoded(door_label[index], encodedBuffer);
logClient.print(encodedBuffer);
logClient.println(F(" HTTP/1.1"));
logClient.println(F("Host: localhost"));
logClient.println(F("Connection: close"));
logClient.println(F(" "));
#if defined(SMTP_SERIAL_DEBUGGING)
do {
char c = logClient.read();
Serial.print(c);
} while (logClient.connected());
#endif
logClient.stop();
#if defined(LOGGING_SERIAL_DEBUGGING)
Serial.println(F("*** Logging - connection stopped ***"));
#endif
}
#if defined(LOGGING_SERIAL_DEBUGGING)
Serial.println(F("*** Logging - completed ***"));
#endif
}
#endif
//----------------------------------------------------------------------------------------------------
#if defined(EVENT_LOGGING)
//----------------------------------------------------------------------------------------------------
void initializeRelayState(int index)
{
relayStates[index].pulIndex = sizeof(passwordUserLookupKeywords) / sizeof(passwordUserLookup); // initialize to 'Manual'
relayStates[index].currentState = isOpen(statusPins[index]); // get the current state, 0 or 1 (Open or Close)
}
#endif
//----------------------------------------------------------------------------------------------------
void configureStatusPin(int pinNumber)
{
#if defined(STATUS_STRATEGY_3VCLOSED_5VOPENED) || defined(STATUS_STRATEGY_5VCLOSED_3VOPENED)
pinMode(pinNumber, INPUT);
#elif defined(STATUS_STRATEGY_NORMALLY_CLOSED) || defined(STATUS_STRATEGY_NORMALLY_OPENED)
pinMode(pinNumber + 14, INPUT_PULLUP); // addressing analog pins as digital pins (+14)
#endif
}
//----------------------------------------------------------------------------------------------------
boolean isOpen(int pinNumber)
{
#if defined(STATUS_STRATEGY_3VCLOSED_5VOPENED) || defined(STATUS_STRATEGY_5VCLOSED_3VOPENED)
int status = analogRead(pinNumber);
#elif defined(STATUS_STRATEGY_NORMALLY_CLOSED) || defined(STATUS_STRATEGY_NORMALLY_OPENED)
int status = digitalRead(pinNumber + 14); // addressing analog pins as digital pins (+14)
#endif
#if defined(MYDOOROPENER_SERIAL_DEBUGGING)
Serial.print(F("*** isOpen - status value for pin: '"));
Serial.print(pinNumber);
Serial.print(F("' is '"));
Serial.print(status);
#endif
boolean retVal = false;
#if defined(STATUS_STRATEGY_3VCLOSED_5VOPENED)
retVal = status >= STATUS_OPEN_TRESHOLD;
#elif defined(STATUS_STRATEGY_5VCLOSED_3VOPENED)
retVal = status <= STATUS_OPEN_TRESHOLD;
#elif defined(STATUS_STRATEGY_NORMALLY_CLOSED)
retVal = status == LOW;
#elif defined(STATUS_STRATEGY_NORMALLY_OPENED)
retVal = status == HIGH;
#endif
#if defined(MYDOOROPENER_SERIAL_DEBUGGING)
Serial.print(F("' returing: '"));
Serial.print(retVal ? F("Opened") : F("Closed"));
Serial.println(F("' ***"));
#endif
return retVal;
}
//----------------------------------------------------------------------------------------------------
void output(WebServer &server, char* data, bool newLine)
{
#if defined(MYDOOROPENER_SERIAL_DEBUGGING)
if (newLine)
Serial.println(data);
else
Serial.print(data);
#endif
if (newLine)
server.println(data);
else
server.print(data);
}
//----------------------------------------------------------------------------------------------------
void output(WebServer &server, int number, bool newLine)
{
char str[10] = "";
itoa(number, str, 10);
output(server, str, newLine);
}
//----------------------------------------------------------------------------------------------------
void webRequestHandler(WebServer &server, WebServer::ConnectionType type, char *url, bool isUrlComplete)
{
#if defined(MYDOOROPENER_SERIAL_DEBUGGING)
Serial.println(F("*** WebServerRequestHandler begin ***"));
#endif
// holder for submitted password (as hex)
char submitPassword[PASSWORD_HEX_SIZE + 1];
memset(&submitPassword, 0, sizeof(submitPassword));
// door on which open/close action is to be carried out. If unspecified, assume door #1
char relayPinAsString[10];
memset(&relayPinAsString, 0, sizeof(relayPinAsString));
int relayPin = -1;
// holder for current challenge token value. The following must not be
// initialized (memset) because it is static and must persist across HTTP calls
static char currentChallengeToken[CHALLENGE_TOKEN_SIZE + 1] = "";
// handle HTTP GET params (if provided)
char name[HTTP_PARAM_NAME_SIZE + 1];
char value[HTTP_PARAM_VALUE_SIZE + 1];
// process all HTTP GET parameters
if (type == WebServer::GET)
{
#if defined(MYDOOROPENER_SERIAL_DEBUGGING)
Serial.println(F("*** GET Request ***"));
#endif
while (url && strlen(url))
{
// process each HTTP GET parameter, one at a time
memset(&name, 0, sizeof(name));
memset(&value, 0, sizeof(value));
server.nextURLparam(&url, name, HTTP_PARAM_NAME_SIZE, value, HTTP_PARAM_VALUE_SIZE);
#if defined(MYDOOROPENER_SERIAL_DEBUGGING)
Serial.print(F("*** HTTP GET PARAM - name: '"));
Serial.print(name);
Serial.print(F("' - "));
Serial.print(F("value: '"));
Serial.print(value);
Serial.println(F("' ***"));
#endif
// keep hold of submitted encrypted hex password value
if (strcmp(name, "password") == 0)
strcpy(submitPassword, value);
// keep hold of relay pin which should be triggered
else if (strcmp(name, "relayPin") == 0)
{
strcpy(relayPinAsString, value);
relayPin = atoi(relayPinAsString);
}
}
}
// the presence of an HTTP GET password param results in a request
// to trigger the relay (used to be triggered by an HTTP request of type POST)
if (strlen(submitPassword) > 0)
{
#if defined(MYDOOROPENER_SERIAL_DEBUGGING)
Serial.print(F("*** submitPassword: '"));
Serial.print(submitPassword);
Serial.println(F("' ***"));
#endif
// decrypt password using latest challenge token as cypher key
uint8_t cryptoKey[AES256_CRYPTO_KEY_SIZE + 1];
memset(&cryptoKey, 0, sizeof(cryptoKey));
for (int i = 0; i < strlen(currentChallengeToken); ++i)
cryptoKey[i] = currentChallengeToken[i];
uint8_t password[PASSWORD_SIZE + 1];
memset(&password, 0, sizeof(password));
// convert password from hex string to ascii decimal
int i = 0;
int j = 0;
while (true)
{
if (!submitPassword[j])
break;
char hexValue[3] = {
submitPassword[j], submitPassword[j + 1], '\0'
};
password[i] = (int) strtol(hexValue, NULL, 16);
i += 1;
j += 2;
}
// proceed with AES256 password decryption
aes256_context ctx;
aes256_init(&ctx, cryptoKey);
aes256_decrypt_ecb(&ctx, password);
aes256_done(&ctx);
char passwordAsChar[PASSWORD_SIZE + 1];
memset(&passwordAsChar, 0, sizeof(passwordAsChar));
for (int i = 0; i < sizeof(password); ++i)
passwordAsChar[i] = password[i];
#if defined(MYDOOROPENER_SERIAL_DEBUGGING)
Serial.print(F("*** passwordAsChar: '"));
Serial.print(passwordAsChar);
Serial.println(F("' ***"));
#endif
#if defined(EVENT_LOGGING)
// Lookup the Username based on Password, save the Username index in the relayStates structure.
int pulIndex;
for (pulIndex = 0; pulIndex < sizeof(passwordUserLookupKeywords) / sizeof(passwordUserLookup); pulIndex++) {
if (strstr(passwordAsChar, passwordUserLookupKeywords[pulIndex].pulPassword) != NULL) {
for (int iPin = 0; iPin < sizeof(relayPins); iPin++) {
if (relayPins[iPin] == relayPin);
relayStates[iPin].pulIndex = pulIndex;
break;
}
}
}
#endif
// if password matches, trigger relay
#if defined(EVENT_LOGGING)
if (strcmp(passwordAsChar, passwordUserLookupKeywords[pulIndex].pulPassword) == 0)
#else
if (strcmp(passwordAsChar, PASSWORD) == 0)
#endif
{
#if defined(MYDOOROPENER_SERIAL_DEBUGGING)
Serial.println(F("*** password matched ***"));
#endif
// trigger relay pin and hold it HIGH for the appropriate number of milliseconds
if (relayPin != -1)
{
#if defined(MYDOOROPENER_SERIAL_DEBUGGING)
Serial.println(F("*** relay triggered ***"));
#endif
//Working with the SainSmart 5v Relay Board - http://sainsmart.wordpress.com/tag/electronics/
//digitalWrite(relayPin, HIGH);
//delay(RELAY_DELAY);
//digitalWrite(relayPin, LOW);
digitalWrite(relayPin, LOW);
delay(RELAY_DELAY);
digitalWrite(relayPin, HIGH);
}
}
}
// write HTTP headers
server.httpSuccess("text/xml; charset=utf-8");
#if defined(MYDOOROPENER_SERIAL_DEBUGGING)
Serial.println(F("*** XML output begin ***"));
#endif
// write opening XML element to output stream
output(server, "<?xml version=\"1.0\"?>", true);
output(server, "<myDoorOpener>", true);
// write current door status
for (int i = 0; i < sizeof(statusPins); ++i)
{
output(server, "<status statusPin=\"", false);
output(server, statusPins[i], false);
output(server, "\">", false);
// write current open/close state to output stream
output(server, (char*)(isOpen(statusPins[i]) ? "Opened" : "Closed"), false);
output(server, "</status>", true);
}
// re-generate new challenge token
sprintf(currentChallengeToken, "Cyber%i%i%i", hour(), minute(), second());
// write challenge token to output stream
output(server, "<challengeToken>", false);
output(server, currentChallengeToken, false);
output(server, "</challengeToken>", true);
// write closing XML element to output stream
output(server, "</myDoorOpener>", true);
#if defined(MYDOOROPENER_SERIAL_DEBUGGING)
Serial.println(F("*** XML output end ***"));
Serial.println(F("*** WebServerRequestHandler end ***"));
#endif
}
//----------------------------------------------------------------------------------------------------
#if defined(NOTIFICATIONS_WATCHDOG_MINUTES)
//----------------------------------------------------------------------------------------------------
void watchDogNotificationsHandler()
{
static time_t initialOpen = NULL;
time_t latestOpen = NULL;
static boolean notificationSent = false;
boolean openDetected = false;
for (int i = 0; i < sizeof(statusPins); ++i)
{
if (isOpen(statusPins[i]))
{
if (!initialOpen)
initialOpen = now();
latestOpen = now();
if ((latestOpen - initialOpen) > NOTIFICATIONS_WATCHDOG_MINUTES * 60)
{
#if defined(NOTIFICATIONS_SERIAL_DEBUGGING)
Serial.print(F("*** watchdog notification handler - detected opened device/door @ pin #"));
Serial.print(statusPins[i]);
Serial.println(F(" ***"));
#endif
if (!notificationSent)
{
#if defined(NOTIFICATIONS_SERIAL_DEBUGGING)
Serial.println(F("*** watchdog notification handler - sending notification ***"));
#endif
char subject[] = "MyDoorOpener Notification";
char body[100] = "";
sprintf(body, "A door or device has been opened for more than %i minute(s).", NOTIFICATIONS_WATCHDOG_MINUTES);
#if defined(SMS_NOTIFICATIONS) || defined(SMS_AUTHENTICATION_NOTIFICATIONS)
notifyViaSms(subject, body);
#endif
#if defined(SMTP_NOTIFICATIONS) || defined(SMTP_AUTHENTICATION_NOTIFICATIONS)
notifyViaEmail(subject, body);
#endif
notificationSent = true;
}
else
{
#if defined(NOTIFICATIONS_SERIAL_DEBUGGING)
Serial.println(F("*** watchdog notification handler - NOT sending notification ***"));
#endif
}
}
openDetected = true;
break;
}
}
// if all door/devices are closed, reset notificationSent semaphore and timer so that further notifications
// can be sent. This is done to avoid floOding recipient with notifications for a same event occurrence.
if (!openDetected)
{
notificationSent = false;
initialOpen = NULL;
delay(500);
}
}
#endif
//----------------------------------------------------------------------------------------------------
#if defined(NOTIFICATIONS_OPEN) || defined(EVENT_LOGGING)
//----------------------------------------------------------------------------------------------------
void openNotificationsHandler()
{
static boolean notificationSent = false;
boolean openDetected = false;
for (int i = 0; i < sizeof(statusPins); ++i)
{
if (isOpen(statusPins[i]))
{
#if defined(NOTIFICATIONS_SERIAL_DEBUGGING)
Serial.print(F("*** open notification handler - detected an opened device/door @ pin #"));
Serial.print(statusPins[i]);
Serial.println(F(" ***"));
#endif
if (!notificationSent)
{
#if defined(NOTIFICATIONS_SERIAL_DEBUGGING)
Serial.println(F("*** open notification handler - sending notification ***"));
#endif
//char subject[] = "MyDoorOpener Notification:";
char subject[] = APP_NOTIFICATION_SUBJECT;
//char body[] = "A door or device has just been opened.";
char body[100] = "";
#if defined(EVENT_LOGGING)
if (relayStates[i].pulIndex == sizeof(passwordUserLookupKeywords) / sizeof(passwordUserLookup))
sprintf(body, "Opening %s Report User %s", door_label[i], "Manual");
else
sprintf(body, "Opening %s Report User %s", door_label[i], passwordUserLookupKeywords[relayStates[i].pulIndex].pulUserName);
#else
sprintf(body, "Opening %s Report User %s", door_label[i], "Manual");
#endif
#if defined(SMS_NOTIFICATIONS) || defined(SMS_AUTHENTICATION_NOTIFICATIONS)
notifyViaSms(subject, body);
#endif
#if defined(SMTP_NOTIFICATIONS) || defined(SMTP_AUTHENTICATION_NOTIFICATIONS)
notifyViaEmail(subject, body);
#endif
notificationSent = true;
}
else
{
#if defined(NOTIFICATIONS_SERIAL_DEBUGGING)
Serial.println(F("*** open notification handler - NOT sending notification ***"));
#endif
}
openDetected = true;
break;
}
}
#if defined(EVENT_LOGGING)
for (int i = 0; i < sizeof(statusPins); ++i)
{
// If a 'State' changed, log the event.
if (relayStates[i].currentState != isOpen(statusPins[i])) {
relayStates[i].currentState = isOpen(statusPins[i]);
logEventHandler(i);
if (!isOpen(statusPins[i])) //If Close, set UserName = Manual
relayStates[i].pulIndex = sizeof(passwordUserLookupKeywords) / sizeof(passwordUserLookup);
}
}
#endif
// if all door/devices are closed, reset notificationSent semaphore so that further notifications
// can be sent. This is done to avoid flooding recipient with notifications for a same event occurrence.
if (!openDetected)
{
notificationSent = false;
delay(500);
}
}
#endif
//----------------------------------------------------------------------------------------------------
void setup()
{
#if defined(MYDOOROPENER_SERIAL_DEBUGGING) || defined(WEBDUINO_SERIAL_DEBUGGING) || defined(SMTP_SERIAL_DEBUGGING) || defined(NOTIFICATIONS_SERIAL_DEBUGGING)
Serial.begin(9600);
#endif
#if defined(MYDOOROPENER_SERIAL_DEBUGGING)
Serial.println(F("*** MyDoorOpener setup begin ***"));
#endif
for (int i = 0; i < sizeof(statusPins); ++i)
configureStatusPin(statusPins[i]);
for (int i = 0; i < sizeof(relayPins); ++i)
{
pinMode(relayPins[i], OUTPUT);
//Working with the SainSmart 5v Relay Board - http://sainsmart.wordpress.com/tag/electronics/
//digitalWrite(relayPins[i], LOW);
digitalWrite(relayPins[i], HIGH);
}
#if defined(EVENT_LOGGING)
for (int i = 0; i < sizeof(relayPins); ++i)
initializeRelayState(i);
#endif
// set arbitrary time - used for always-changing challenge token generation
setTime(0, 0, 0, 1, 1, 2010);
// start web server
Ethernet.begin(mac, ip);
webserver.setDefaultCommand(&webRequestHandler);
webserver.addCommand("", &webRequestHandler);
webserver.begin();
#if defined(MYDOOROPENER_SERIAL_DEBUGGING)
Serial.println(F("*** MyDoorOpener setup completed ***"));
#endif
}
//----------------------------------------------------------------------------------------------------
void loop()
{
char buffer[200];
int len = sizeof(buffer);
webserver.processConnection(buffer, &len);
#if defined(NOTIFICATIONS_WATCHDOG_MINUTES)
watchDogNotificationsHandler();
#endif
#if defined(NOTIFICATIONS_OPEN) || defined(EVENT_LOGGING)
openNotificationsHandler();
#endif
}
Have Fun…