Hi all,
Made a simple (and whimsically named) intruder alarm using a PIR motion sensor and an Arduino ESP32, and using Twilio to send SMS arm/disarm/alert messages. I used the available twilio.hpp library. An account at Twilio will cost you about a penny per SMS.
Code below; the PDF contains all other instructions.
Intrudalizer Manual.pdf (2.6 MB)
I am sure I'll refine the code more but it does work well, just what I intended. Possible future:
-
add hardware watchdog (deliberately left off for now: I want to avoid ANY chance of constant restarts and consequent SMSs)
-
Debounce the switch (seems not needed because after the switch cage, there is an SMS send action, which takes over a second, by which time any bounce would surely be over).
-
Refinement, if needed, of the failed SMS sequence.
But that's all just "perhaps". As it, it works. Enjoy!
/* ------------------------------------------------------------------------------------------
Intruder Alarm with text messaging: "Intrudalizer 2000 Pro"
By: Michael Willems - michael@willems.ca
Needs:
-Arduino Nano ESP32
-WiFi connectivity (2.4 GHz)
-Twilio account
-twilio-esp32-client library (from Arduino IDE)
-PIR sensor
-LEDs, resistor, SPDT switch, pushbutton.
Alarm is activated with switch (flashing yellow); 60 seconds later it's active (solid yellow).
Sends SMS to phone upon bootup, arm, disarm, and alarm.
After an alarm, it stays in alarmed status for an hour, and then it resets so that
new alarms can be generated and new alarm SMSs can be sent.
Version of 11 Aug 2023
Change log:
0.5 added infinite 4-sec retry on all SMS messages.
also added "green LED off" while sending SMS.
0.41 simplified message text
0.4 added a 2x retry to a failed SMS on trigger
0.32 minor changes, tidied up, removed unnecessary pushbotton, removed serialWrite.
0.31 documentation/comments added
0.3 added one hour trigger reset
0.2 added SMS on arm/disarm
0.1 made it work
--------------------------------------------------------------------------------------------*/
//-------------------------------------------------------------------------------------------
// INCLUDES, DECLARATIONS:
//-------------------------------------------------------------------------------------------
#include "WiFi.h"
#include "twilio.hpp"
// define WiFi and SMS credentials:
const char *ssid = "xxxxx";
const char *password = "xxxxx";
Twilio *twilio;
// Values from Twilio (from the dashboard):
static const char *account_sid = "xxxxx";
static const char *auth_token = "xxxxx";
static const char *from_number = "xxxxx";
static const char *to_number = "xxxxx";
static const char *messageStart = "Boyer: Startup------";
static const char *messageAlert = "Boyer: MOTION DETECT";
static const char *messageArmed = "Boyer: Armed";
static const char *messageDisarmed = "Boyer: Disarmed";
String response;
// define misc pins:
int PIRpin = 2;
int Switch = 10;
// on-board LEDs:
int activeled = 13; // flashes 1x/sec to indicate functioning
int blueled = 16; // blue when WiFi is connected
// External LEDs:
int OkLed = 3; // WiFi and SMS OK
int WiFiErrLed = 4; // WiFi Error
int SmsErrLed = 5; // SMS Error
int ArmLed = 6; // Armed
int TrigLed = 7; // Motion seen!
// Counters etc:
unsigned long oneseccounter;
unsigned long quartersecondcounter;
unsigned long fourseccounter;
unsigned long prearmtimer; // delay before armed is active (default: 60000)
unsigned long prearmdelay = 60000;
unsigned long rearmtimer; // "no new alarm" time after alarm (default: 3600000)
unsigned long rearmdelay = 3600000;
unsigned long resendtimer; // TBD: resend an SMS on error
unsigned long resenddelay = 60000;
boolean ArmedStat = false; // Flag: are we armed?
boolean PreArmInhibit = false; // Flag: are we stil in pre-arm time, so no alarms yet please?
boolean TriggeredStat = false; // Flag: is the alarm triggered?
boolean TriggerNotSent = false; // to allow resends if trigger SMS not sent correctly
boolean ArmedNotSent = false; // same for armed
boolean DisarmedNotSent = false; // same for disarmed
boolean StartNotSent = false; // same for start
//-------------------------------------------------------------------------------------------
// THE SETUP (RUNS ONCE):
//-------------------------------------------------------------------------------------------
void setup() {
pinMode(PIRpin, INPUT);
pinMode(Switch, INPUT_PULLUP);
pinMode(activeled, OUTPUT);
pinMode(blueled, OUTPUT);
pinMode(OkLed, OUTPUT); // green
pinMode(WiFiErrLed, OUTPUT); // red
pinMode(SmsErrLed, OUTPUT); // red
pinMode(ArmLed, OUTPUT); // yellow
pinMode(TrigLed, OUTPUT); // blue
// Set inital LED status:
digitalWrite(OkLed, LOW);
digitalWrite(WiFiErrLed, HIGH);
digitalWrite(SmsErrLed, HIGH);
digitalWrite(ArmLed, LOW);
digitalWrite(TrigLed, LOW);
// Now connect to WiFi and wait until connected:
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) {
digitalWrite(activeled, LOW);
analogWrite(blueled, LOW);
delay(250); // and if we use a hardware watchdog, don't forget to reset it here!
}
// We are connected, so indicate this:
analogWrite(blueled, HIGH);
digitalWrite(activeled, HIGH);
digitalWrite(WiFiErrLed, LOW);
delay(250);
// start Twilio instance:
twilio = new Twilio(account_sid, auth_token);
// Now send SMS:
String response;
SendStartMessage();
// set the timers to start:
fourseccounter = millis();
oneseccounter = millis();
quartersecondcounter = millis();
}
//-------------------------------------------------------------------------------------------
// THE LOOP (RUNS CONTINUOUSLY):
//-------------------------------------------------------------------------------------------
void loop() {
// -----------now do the "once every second" things:-------------------------
if ((millis() - oneseccounter) > 1000) {
//Toggle the on-board 'active' LED:
digitalWrite(activeled, !digitalRead(activeled));
// is WiFi connected? If not, turn off the blue and OK LED, and turn on WiFi Error LED:
if (WiFi.status() != WL_CONNECTED) {
analogWrite(blueled, LOW);
analogWrite(WiFiErrLed, HIGH);
digitalWrite(OkLed, LOW);
} else {
// wifi is OK, so turn the on-board blue LED on, and turn WiFi error light off:
analogWrite(blueled, HIGH);
analogWrite(WiFiErrLed, LOW);
digitalWrite(OkLed, HIGH);
}
// Now reset counter so we can start counting another second:
oneseccounter = millis();
}
// End of "once a second" things.
// Every quarter second" - flashing the light while armed+inhibited:-------
if ((millis() - quartersecondcounter) > 250) {
if ((ArmedStat == true) && (PreArmInhibit == true)) {
digitalWrite(ArmLed, !digitalRead(ArmLed));
}
quartersecondcounter = millis();
}
// --------------"every four seconds": the SMS error routines---------------
if ((millis() - fourseccounter) > 4000) {
if (StartNotSent == true) {
SendStartMessage();
}
if (ArmedNotSent == true) {
SendArmedMessage();
}
if (TriggerNotSent == true) {
SendTriggerMessage();
}
if (DisarmedNotSent == true) {
SendDisarmedMessage();
}
fourseccounter = millis();
}
// Now the "always do this" things:----------------------------------------
//
// -------------------
// Arm/Disarm routines:
// -------------------
if (digitalRead(Switch) == LOW) { //switch is turned on (up)
if ((millis() - prearmtimer) > prearmdelay) {
PreArmInhibit = false;
digitalWrite(ArmLed, HIGH); // turn on "Armed" light just in case it's not on yet
}
if (ArmedStat == false) { // i.e. it was just now turned to ARM...
ArmedStat = true; // so next loop it knows it's not only just been armed, so not another SMS
digitalWrite(ArmLed, HIGH); // turn on "Armed" light just in case it's not on yet
// Send "Armed" SMS:
digitalWrite(OkLed, LOW);
SendArmedMessage();
// Start Inhibit timer (minute to get out of the room):
PreArmInhibit = true;
prearmtimer = millis();
}
} else { // Switch is turned down (off)
digitalWrite(ArmLed, LOW); // so turn off Armed LED
if (ArmedStat == true) { // i.e. it was just turned to DISARM...
// Send "Disarmed" SMS:
digitalWrite(OkLed, LOW);
SendDisarmedMessage();
ArmedStat = false; // so next loop it knows it's not only just been disarmed, so not another SMS
}
}
// -----------------------
// Trigger Event Routines:
// -----------------------
if (digitalRead(PIRpin) == HIGH) { // The PIR is giving an trigger indication...
digitalWrite(TrigLed, HIGH); // ..so, turn on the Trigger LED just in case it's not on yet.
if ((ArmedStat == true) && (PreArmInhibit == false)) { // we are armed and we are not inhibited
if (TriggeredStat == false) { // we WERE not triggered, but now we are.
// So, send an SMS: we are fully armed and had a motion alert.
digitalWrite(OkLed, LOW);
SendTriggerMessage();
TriggeredStat = true; // Trigger status ON (so we won't send another SMS, for a start!)
rearmtimer = millis(); // Start the re-arm timer (after which we will turn triggered status off)
}
}
} else { // the PIR is not giving an trigger indication
if ((ArmedStat == false) || (PreArmInhibit == true)) {
digitalWrite(TrigLed, LOW); // ...we are not [yet] armed, so turn off Trigger LED after PIR stops activating
TriggeredStat = false; // also, since we are not armed and not triggered, turn off triggered status
}
}
// If we are past the rearm timer, re-arm the system, ie turn off blue LED & get ready to send SMS if triggered+armed
// It is safe to do this even when we are not armed: all it does is say we are not triggered and turn off blue LED.
// It is better to do this unnecessarily than to risk missing a condition and not re-arming in error.
// This will never happen within an hour of sending a trigger SMS (that's what this condition is for)
if (((millis() - rearmtimer) > rearmdelay) && (digitalRead(PIRpin) == LOW)) {
digitalWrite(TrigLed, LOW); // ...we are not triggered, so turn off Trigger LED after PIR stops activating
TriggeredStat = false; // also, since we are not armed and not triggered, turn off triggered status
}
//--end of loop code here--
//
}
//-------------------------------------------------------------------------------------------
// THE FUNCTIONS (CALLED AS NEEDED):
//-------------------------------------------------------------------------------------------
void SendStartMessage() {
bool success = twilio->send_message(to_number, from_number, messageStart, response);
if (success) { // SMS sent successfully.
digitalWrite(SmsErrLed, LOW);
digitalWrite(OkLed, HIGH);
StartNotSent = false;
} else { // uh oh, SMS failure
digitalWrite(SmsErrLed, HIGH);
digitalWrite(OkLed, LOW);
StartNotSent = true;
}
}
void SendTriggerMessage() {
bool success = twilio->send_message(to_number, from_number, messageAlert, response);
if (success) { // SMS sent successfully.
digitalWrite(SmsErrLed, LOW);
digitalWrite(OkLed, HIGH);
TriggerNotSent = false;
} else { // uh oh, SMS failure
digitalWrite(SmsErrLed, HIGH);
digitalWrite(OkLed, LOW);
TriggerNotSent = true;
}
}
void SendArmedMessage() {
bool success = twilio->send_message(to_number, from_number, messageArmed, response);
if (success) { // SMS sent successfully.
digitalWrite(SmsErrLed, LOW);
digitalWrite(OkLed, HIGH);
ArmedNotSent = false;
} else { // uh oh, SMS failure
digitalWrite(SmsErrLed, HIGH);
digitalWrite(OkLed, LOW);
ArmedNotSent = true;
}
}
void SendDisarmedMessage() {
bool success = twilio->send_message(to_number, from_number, messageDisarmed, response);
if (success) { // SMS sent successfully.
digitalWrite(SmsErrLed, LOW);
digitalWrite(OkLed, HIGH);
DisarmedNotSent = false;
} else { // uh oh, SMS failure
digitalWrite(SmsErrLed, HIGH);
digitalWrite(OkLed, LOW);
DisarmedNotSent = true;
}
}


