/////////////////////////////////////////////////////////////////////////////////////////////////////////
//J.R De Villiers April 2024.
//A home alarm system.
//V1.0 - 4 Zones + Panic (Some zones may need to be sacrificed if using Lora Comms - see below)
//Using Barebones ATMega328 with separate SX1278 Lora Module.
//Features:
//* 4 Zones
//* 24hr Panic Zone
//* Test mode
//* Automatic zone bypass on error when Armed
//* Siren timeout
//* Strobe output
//NOTE: Due to Wokwi limitations any resistance inserted between a zone sense pin and ground will always be read as a HIGH, but in the
//real world assuming the internal pullup resistance of the AtMega Pins are between 20-40k, then permissible Alarm Zone wire resistance
//can range from 8.5Kohm max - 17kohm max and the pin will still be read as a LOW.
//Assuming resistance of 0.2mm rip cable is approx 0.15 - 0.2 ohm/metre on the 8.5kohm side, it would mean an allowed 42500 metres of wire!
//100% Working!
//Built and Tested with a Real Bares AtMega328 chip in June 2025.
/////////////////////////////////////////////////////////////////////////////////////////////////////////
//-----------------------------------------------------
//PROGRAMMING (SETTING OF EEPROM)
//-----------------------------------------------------
//This can be done by connecting the MCU's RX & TX serial lines to a USB to Serial TTL device such as a CP2102 etc.
//Then plugging the USB to Serial TTL device into a PC/Laptop/Cellphone. If using PC/Laptop then either a serial terminal program may be
//used such as Putty etc, or my Arduino Toolbox VB.Net program.
//NOTE: If using a cellphone, then a OTG (On the Go) cable is required (Type C to female USB). This makes the phone go into 'Host' mode
//and the CP2102 etc then becomes the client. Then use my Android software (Made using B4A with USBSerial library) to issue a valid command.
//See 'GetSerialDataCommand' function for valid commands.
//-----------------------------------------------------
//COMMUNICATIONS:
//-----------------------------------------------------
////////////////////
//LONG RANGE
////////////////////
//If remote communications are required for example to send an an alert to another location from where the alarm occurred.
//1. GSM Cellular
//Great for unlimited range and 2G modules are cheap, but modules are expensive if long term support required for 3G/4G required.
//Requires only 2 serial pins (Can use SoftwareSerial RX & TX) and uses AT commands.
//2. Lora (Long Range) Radio. Requires 6 pins, so some zones will need to be sacrificed.
//Works fine for shorter range from a few hundred metres to a few km's depending on antenna used.
//Cannot use a module like RA-07 (Microprocessor with SX1262, which uses 2 serial pins and works with AT commands),
//as the firmware may not allow peer to peer mode only LoraWan mode. So if no WAN mode in your area, pretty much useless.
//Options are:
//* SX1278 Lora Module RA-02 Ai-Thinker 433MHZ (Needs 6 pins to connect to MCU --> MOSI, MISO, SCK, NSS/CS, RESET, DIO0)
//https://www.robotics.org.za/RA-02-433-PCB?search=sx1278 - +- R140 (Priced July 2025)
//* Heltec ESP32 S3 LORA Module V3 (Uses SX1262 incorporated with a MCU)
//This has a ESP32 MCU incorporated with a SX1262 but also fairly expensive at +- R580 (Priced July 2025)
//https://www.robotics.org.za/HTIT-WSL-V3?search=heltec
//Use Duck antenna 5dbi at Palmira, Wavecrest and No 7.
//https://www.robotics.org.za/YN-868MHZ-5DBI (For 868MHZ)
//https://www.robotics.org.za/YNX-433-5DB?search=antenna (For 433MHZ)
//////////////////////
//SHORT RANGE
//////////////////////
//For local short range Remote control to Arm/Disarm/Activate Panic range.
//Option 1:
//Repurpose old 4 channel 433Mhz handheld transmitter from cheap chinese alarm system with 4 buttons (4 channel) which uses EV1527 chip internally
//to encode and send through transmitter.
//Use CY33 receiver to receive signals with Duck 5dbi 433Mhz antenna.
//Option 2: Commercial (Stand alone relay board)
//4 channel board + 1 remote (No programming needed, uses dry relay contacts on receiver board)
//https://www.robotics.org.za/4CHAN-REM?search=remote
//Option 3: Commercial (No relays)
//Suitable 4 channel board + 1 remote (Requires 4 AtMega Pins & some code to decipher receiver)
//https://www.robotics.org.za/ABCD-433MHZ?search=remote (Premium 433MHz Remote + RX - EV1527)
//-----------------------------------------------------
//LIBRARIES USED:
//-----------------------------------------------------
// millisDelay.h (Timer library)
// EEProm.h
// SoftwareSerial.h
// RCSwitch (If using Remote receiver CY33 using EV1527 decoder)
//-----------------------------------------------------
//PHYSICAL PIN LAYOUT (IF using ATMega328 barebones)
//-----------------------------------------------------
// ------------v------------
// Onboard Reset Button o| 1 28 |o Zones All Ready LED / IO19 / A5
// Serial RX o| 2 27 |o Zone 4 Ready LED / IO18 / A4
// Serial TX o| 3 26 |o Zone 3 Ready LED / IO17 / A3
// ***SPARE*** INT 0 / IO2 o| 4 25 |o Zone 2 Ready LED / IO16 / A2
// Remote Receiver CY33 data / INT1 / IO3 o| 5 24 |o Zone 1 Ready LED / IO15 / A1
// ***SPARE*** IO4 o| 6 23 |o Keyswitch / Remote Latching NO / IO14 / A0 <---- This can be reclaimed later when I have a remote.
// VCC 5V o| 7 22 |o GND
// GND o| 8 21 |o AREF
// XTAL 16MHZ o| 9 20 |o VCC 5V
// XTAL 16MHZ o| 10 19 |o Zone 4 Sense
// Armed LED / IO5 o| 11 18 |o Zone 3 Sense
// Panic / IO6 o| 12 17 |o Zone 2 Sense
// Siren / IO7 o| 13 16 |o Zone 1 Sense
// Strobe / IO8 o| 14 15 |o Onboard Test/Arm/Disarm NO Momentary Button / Remote Momentary NO / IO9
// -------------------------
//Note: To prevent interference on Zone Sense lines from possibly giving false alarms, it is advisable to add a 0.1uf cap from the sense pin to ground.
//Also add a 3.3K Resistor in series from the sensor (Mag etc) to the sense pin. This will form a low pass RC filter to cut off anything faster then 482HZ.
//1 (Reset) --> Connect through 10K Resistor to +5V Power Supply
// --> Connect through momentary normally open push button to GND (For resetting chip)
//2 (Hardware RX) --> To USB Serial To TTL TX (For Laptop/PC comms). For visual indicator, connect an LED to +5V through a 1.7k resistor & other side to TX (Goes LOW when data present).
//3 (Hardware TX) --> To USB Serial To TTL RX (For Laptop/PC comms). For visual indicator, connect an LED to +5V through a 1.7k resistor & other side to TX (Goes LOW when data present).
//4 Digital 2 / INT 0 --> ***SPARE***
//5 Digital 3 / INT 1 --> To CY33 433MHZ Receiver Data line
// For Arm/Disarm/Panic Remotes using EV1527 encoded signals (Must use interrupt as we are using RC-Switch library to decode EV1527 signals)
//6 Digital 4 --> ***SPARE***
//7 5V VCC --> Connect to Power supply +5V, use 12V supply through a 5V Regulator.
//8 GND --> Connect to Power Supply GND
//9 XTAL1 --> Connect to one side of 16MHZ Crystal
//10 XTAL2 --> Connect to other side of 16MHZ Crystal
//11 Digital 5 --> Armed LED - HIGH when armed, LOW when disarmed (Position this LED where visible when Armed in windows etc)
// NOTE: If using more then one Armed LED at various windows, then you must use a transistor driver as ATMega Pin can only drive 20-40ma max.
// Connect 330ohm resistor to TIP31C base, emitter to ground, and Armed LED between collector and +12V.
//12 Digital 6 --> Panic Zone (NO)
//13 Digital 7 --> Siren Output (HIGH when Alarm), connect via 1k resistor to 2N3904 base (or similar) to power a Siren Relay (Relay between 12V + Collector)
//14 Digital 8 --> Strobe Light (HIGH when on), connect via 1k resistor to 2N3904 base (or similar) to power a Strobe Relay (Relay between 12V + Collector)
//15 Digital 9 --> Arm/Disarm Momentary Normally Open / Remote Control NO (Connect to GND momentarily to Arm/Disarm)
//16 Digital 10 // SS (SPI Comms) --> Zone 1 Sense Pin - connect to ground through mags / PIR (Normally Closed)
//17 Digital 11 // MOSI (SPI Comms) --> Zone 2 Sense Pin - connect to ground through mags / PIR (Normally Closed)
//18 Digital 12 // MISO (SPI Comms) --> Zone 3 Sense Pin - connect to ground through mags / PIR (Normally Closed)
//19 Digital 13 // SCK (SPI Comms) --> Zone 4 Sense Pin - connect to ground through mags / PIR (Normally Closed)
//20 5V VCC --> Connect to Power Supply +5v, use 12V supply through a 5V Regulator.
//21 AREF --> Not connected
//22 GND --> Connect to Power Supply GND
//23 Digital 14 / A0 --> Connect to one side of Keyswitch to Arm/Disarm, other side to GND
//24 Digital 15 / A1 --> Zone 1 LED Ready (Off / LOW when ready)
//25 Digital 16 / A2 --> Zone 2 LED Ready (Off / LOW when ready)
//26 Digital 17 / A3 --> Zone 3 LED Ready (Off / LOW when ready)
//27 Digital 18 / A4 --> Zone 4 LED Ready (Off / LOW when ready)
//28 Digital 19 / A5 --> Zones All Ready LED (LOW when ready) / Alarm LED / Panic LED / Test Mode LED (HIGH)
//-----------------------------------------------------
//OPERATION:
//-----------------------------------------------------
//------------------------
//ARMING:
//------------------------
//System is Armed by pulling IDE Pin 9 (Physical Pin 15) to ground briefly or by latching IDE Pin 14 (Physical Pin 23) to ground.
//Arming will only occur if all zone/s are ready, indicated by a LED (Physical pins 24-25) for each zone. If a specific LED is on, the zone is
//not ready. If any zone/s is not ready, system will not be able to be armed by design. The All Zones Ready LED as well as the specific zone LED will also be on.
//Once all zone/s are ready, and system is armed, the Armed LED (Physical Pin 11) will go on. This can be set to be steady or flash.
//Should any zone now be open circuit (Zone sense pin not LOW) for longer then the set time ('ZoneIntrusionOpenMilliseconds' variable) to be considered a non false alarm,
//siren output IDE pin 7 (Physical pin 13) will go high and turn on the siren. The strobe output IDE pin 8 (Physical pin 14) will also go high.
//The All Zones Ready LED IDE Pin 19 (Physical pin 28) will also blink fast to show an alarm was triggerred. The relevant LED for the zone/s will also light up to show zone
//has been triggered. The strobe output IDE pin 8 (Physical Pin 14) will also go high and remain high even once Alarm has timed out, to show an Alarm has occurred.
//Siren will sound for xxx seconds, then shut off, but the All Zones Ready will continue to flash fast as a warning (strobe).
//NOTE: After the Siren timeout period, the system will briefly check all the zones to see if they are still closed, if it finds any zone/s which
//are not (signalling they have been damaged by the intruder), then just that zone/s will be automatically bypassed and the other zone/s will continue
//to function. If a zone detector such as a mag switch was opened, then closed again by an intruder, the siren will ring till it times out, then that zone
//will activate the siren again if it is opened again as it is still NC. The same applies to a PIR.
//System can now be reset with momentary contact again if armed with it initially, or the keywsitch if armed with it initially.
//---------------------------
//DISARMING:
//---------------------------
//If system has been armed with the keyswitch, it can only be disarmed again with the keyswitch (not the remote),
//this is by design, so that the keyswitch can not be left in the on position.
//However, if it was armed with the remote, it can be disarmed again with the remote or keyswitch, by turning the keyswitch on, then off again.
//Siren can always be muted with the momentary contact (remote) or keyswitch if Alarm occurred.
//So touching Physical Pin 15 to ground briefly will disarm again (toggle), or with keyswitch breaking contact with
//Physical Pin 23 to ground again will disarm.
//---------------------------
//PANIC
//---------------------------
//When 24 hr (System does not need to be armed) panic is trigerred (By bringing Physical Pin 12 LOW briefly), siren will ring continuously and never time out,
//until switched off with the keyswitch (latching contact), or it can also be switched off with the remote (momentary contact) but only if it is held in for
//xxx seconds, this is set in the 'DisArmActivatePanic' variable (Default = 10 seconds)
//---------------------------
//TEST MODE:
//---------------------------
//Hold momentary contact / remote button in for at least xxx (Default = 5) seconds to engage Test mode. Set in the 'TestModeActivate' variable.
//All Zone Ready led will blink fast to indicate test mode is activated.
//In this mode, any zone can be opened/closed and the siren will just blip briefly for every open/close.
//Also the zone lights will not latch, they will go on/off everytime when the zone is opened or closed.
//For example opening a window or door will make the siren blip briefly, and then it will go silent, even if the window/door is still open.
//-----------------------------------------------------
//DECLARES:
//-----------------------------------------------------
//-----------------------
//LIBRARIES
//-----------------------
//For working with ATMega Internal EEProm.
#include <EEPROM.h>
//Needed for Watch dog Timer (if used)
#include <avr/wdt.h>
//Needed if using GSM Cellular Module with AT Commands so that a SMS can be sent to a remote 'listening' ATMega in the event an Alarm occurs.
//We are using Software Serial as we want to reserve RXD & TXD Pins (Physical pins 2 & 3) to connect a PC/Laptop/Cellphone for debugging/changing EEPROM settings etc.
//#include <SoftwareSerial.h>
#include "millisDelay.h" //Used for special timers so main thread does not get blocked.
//#include <RCSwitch.h> //Used to decode short range remote codes received from the CY33 receiver for Arm/Disarm/Panic
//Used to record an Alarm event to EEPROM.
struct AlarmLog
{
byte zone; // 1 byte
uint32_t timeStamp; // 4 bytes = milliseconds since power on.
};
//Used for the Remote controls using EV1527 encoded signals to Arm/Disarm/Panic the system.
//RCSwitch Remote = RCSwitch();
//TODO:Replace later with valid codes.
//Remote 1
const unsigned long REMOTE1_ARM_CODE = 1111111;
const unsigned long REMOTE1_DISARM_CODE = 1111112;
const unsigned long REMOTE1_PANIC_CODE = 1111112;
//Remote 2
const unsigned long REMOTE2_ARM_CODE = 2222221;
const unsigned long REMOTE2_DISARM_CODE = 2222222;
const unsigned long REMOTE2_PANIC_CODE = 1111112;
const int REMOTE_PIN = 1; //Tells the RCSwitch library to use Interrupt 1 (Physical Pin 5) to decode EV1527 signals received from the CY33 data line.
//Zone sense (Normally Closed) pins.
//Each of these pins, when connected to GND will then be considered Ready (Normally Closed)
//When this is disrupted, the pin will be pulled high with an internal 10k pullup resistor and the zone will be considered
//open and trigger the alarm.
//NOTE:
//If a 3 zone system is required, then ZONE_3_SENSE_PIN must be set to 4
const int ZONE_1_SENSE_PIN = 10; //Physical pin 16, Digital IO 10. Zone 1 sense pin.
const int ZONE_2_SENSE_PIN = 11; //Physical pin 17, Digital IO 11. Zone 2 sense pin.
const int ZONE_3_SENSE_PIN = 12; //Physical pin 18. Digital IO 11. Zone 3 sense pin.
const int ZONE_4_SENSE_PIN = 13; //Physical pin 19. Digital IO 11. Zone 4 sense pin.
const int ZONE_PANIC = 6; //Physical pin 12. Digital IO 6. Panic zone.
const int ARMED_LED_PIN = 5; //Physical pin 11. Digital IO 5. The armed LED pin will be HIGH.
const int SIREN_PIN = 7; //Physical pin 13. Digital IO 7.
const int ARM_DISARM_MOMENTARY_PIN = 9; //Physical pin 15. Digital IO 9. When pulled LOW momentarily toggles between Armed/Disarmed (There is a 10k pullup resistor)
//When latched(When using keyswitch) LOW Arms, when pulled high Disarms.NOTE: Momentary Arm / Disarm will override this one.
const int ARM_DISARM_LATCH_PIN = 14; //Physical pin 23. Digital IO 14. Marked as A0 on Arduino Board.
//Zone LED ready pins (red leds).
//When a LED is on for a zone, that zone is not ready. These LEDS are all on the board.
//We are using some Analog In pins here which we have set as OUTPUTS. We then do a digitalWrite to them to switch them on/off.
const int ZONE_1_OPEN_LED_PIN = 15; //Physical pin 24. Red Led. Digital IO 15. Marked as A1 on Arduino Board.
const int ZONE_2_OPEN_LED_PIN = 16; //Physical pin 25. Red Led. Digital IO 16. Marked as A2 on Arduino Board.
const int ZONE_3_OPEN_LED_PIN = 17; //Physical pin 26. Red Led. Digital IO 17. Marked as A3 on Arduino Board.
const int ZONE_4_OPEN_LED_PIN = 18; //Physical pin 27. Red Led. Digital IO 18. Marked as A4 on Arduino Board.
//Strobe
const int STROBE_PIN = 8; //Physical pin 14. Digital IO 8. Strobe output to be used for an external strobe light and LED.
//(Will remain on when Alarm has timed out to show intrusion occurred)
//There is one LED of this on the Board, and one at the entry point (ie Front door etc.)
//Has 4 Functions:
//Will be on solid if any zone is not ready.
//Will flash fast if Alarm was triggered.
//Will flash fast if Panic triggered.
//Will flash slow if in Test Mode.
//NOTE: Must be a normal non flash LED.
const int ZONES_ALL_READY_LED_PIN = 19; //Physical pin 28. Digital 19. Marked as A5 on Arduino board.
////////////////////////////////
//FINITE STATES
////////////////////////////////
//Finite state mode.
//0 = Currently Disarmed and Zones All Ready
//1 = Currently Disarmed, but one or more Zone/s not ready, so cannot be armed
//2 = Armed (Not triggered)
//3 = Alarm Triggered - Siren timeout not reached
//4 = Alarm Triggered - Siren timeout reached, siren is off. Only Strobes are on.
//5 = Panic Mode Triggered (Siren will not Time Out, momentary button or Remote must be pressed to Disarm the system and kill the siren)
//6 = Test Mode
int CurrMode;
bool IsZone1Open;
bool IsZone2Open;
bool IsZone3Open;
bool IsZone4Open;
//If a zone was triggered while Armed, and if the zone circuit remains open after siren time out (meaning it was damaged in some way),
//we will automatically bypass this zone and keep the other zones active.
bool IsZone1Bypassed;
bool IsZone2Bypassed;
bool IsZone3Bypassed;
bool IsZone4Bypassed;
bool ZonesAllReady; //Zones 1-4 must all be ready for this to be TRUE.
bool IsArmed;
bool IsMomentaryContactPressed;
//Set this to TRUE if you want the ATMega to flash the Armed LED (when armed), or FALSE if the LEDS have their own built in flash.
//If FALSE, then just a steady HIGH will be sent to the output PIN.
bool MustFlashArmedLED;
/////////////////////////////
//TIMERS
/////////////////////////////
//These timers do not block the main thread.
millisDelay ExitDelayTimer; //Timing how long before system will be armed - Exit Delay (not currently implemented)
millisDelay SirenTimeoutTimer; //Timing how long siren will ring before shutting off.
millisDelay ArmedLEDOnTimer; //Timing how long the Armed led has been on (to create flash effect)
millisDelay ArmedLEDOffTimer; //Timing how long the Armed led has been off (to create flash effect)
millisDelay AllZonesReadyLEDOnTimer; //Timer for Alarm / Panic triggerred / Test Mode
millisDelay AllZonesReadyLEDOffTimer; //Timer for Alarm / Panic triggerred / Test Mode
//Exit delay.
//NOT CURRENTLY USED.
long ExitDelayMilliseconds = 30000; //30 Seconds
//If we want to make the Siren temporarily muted by issuing a Serial command from a Laptop, for testing purposes.
//This will not be written to EEPRom, hence is only temporary.
bool SirenMuted;
//Siren Timout.
long SirenTimeoutMilliseconds = 150000; //180000; //2 and a half minutes (60000 milliseconds in a minute)
//Siren Annunciate ring time in milliseconds
long SirenAnnunciateRingTime = 120;
//If the momentary contact (Remote) is held in for this amount of milliseconds, system will enter into Test Mode.
int TestModeActivate = 5000;
//The amount of milliseconds the remote would need to be held in for it to disarm the system if panic was triggered.
//NOIE: Keyswitch may also be used to switch Alarm off.
int DisArmActivatePanic = 10000;
//The amount of milliseconds a zone must stay open for it to be considered an intrusion.
//This is to avoid false alarms (debounce for spikes/interference on lines).
long ZoneIntrusionOpenMilliseconds = 500;
//System Armed - Armed LED flash rates.
//Note: This is not used if the Armed LED/s have their own internal flash circuitry.
int ArmedLEDNormalOnTime = 100; //1000
int ArmedLEDNormalOffTime = 750;
//Alarm trigerred - Zones All Ready LED flash rates.
int AlarmTrigerredLEDFastOnTime = 100;
int AlarmTrigerredLEDFastOffTime = 250;
//Test mode zones all ready LED flash rates.
//To enter test mode, hold momentary button in for minimum xxx seconds.
int TestModeLEDOnTime = 100;
int TestModeLEDOffTime = 1000;
//Which method was last used to Arm the system?
//0 = System has not yet been armed, so cannot be determined.
//1 = Keyswitch was used (latching)
//2 = Remote was used (Momentary)
int LastArmedMode;
//Test Mode variables.
//Used to enter into Test mode. Momentary contact must be held in for xxx seconds or longer.
unsigned long HoldStartTime;
unsigned long HoldDuration;
bool TestModeAlarm;
////////////////////////////////////////////////////////////////////////////////////////
//SETUP
////////////////////////////////////////////////////////////////////////////////////////
void setup() {
//Let system stabilise.
delay(500);
//For Remote CY33 433Mhz receiver board.
//Serial.println("Receiver CY33 Initialising...");
//Remote.enableReceive(REMOTE_PIN); //Using interrupt 1.
//For serial communications, also used for testing in development.
Serial.println("Serial Initialising...");
Serial.begin(9600);
Serial.flush();
//Enable Watch dog Timer for 8 seconds. This is the Max for the Timer.
//If system gets stuck in a loop, WT will automatically reset the MCU.
//wdt_enable(WDTO_8S);
Serial.println("Setting Pins...");
//The pin to read to know if system is armed/disarmed.
//When pulsed momentarily to ground will toggle between Armed/Disarmed.
//This is pulled up to 5V when not armed, or grounded when armed.
pinMode(ARM_DISARM_MOMENTARY_PIN, INPUT_PULLUP);
//When latched to ground. When using keyswitch.
//Momentary pin will override this.
pinMode(ARM_DISARM_LATCH_PIN, INPUT_PULLUP);
pinMode(SIREN_PIN, OUTPUT); //The pin for the siren.
digitalWrite(SIREN_PIN, LOW);
//pinMode(BUZZER_PIN, OUTPUT); //The pin for the buzzer.
//digitalWrite(BUZZER_PIN, LOW);
pinMode(STROBE_PIN, OUTPUT); //The pin for the strobe.
digitalWrite(STROBE_PIN, LOW);
pinMode(ARMED_LED_PIN, OUTPUT);
//The pin for our flashing Armed LED.
digitalWrite(ARMED_LED_PIN, LOW);
pinMode(ZONE_1_OPEN_LED_PIN, OUTPUT); //The pin for our Red LED - Zone 1
pinMode(ZONE_2_OPEN_LED_PIN, OUTPUT); //The pin for our Red LED - Zone 2
pinMode(ZONE_3_OPEN_LED_PIN, OUTPUT); //The pin for our Red LED - Zone 3
pinMode(ZONE_4_OPEN_LED_PIN, OUTPUT); //The pin for our Red LED - Zone 4
//Make sure all our zone ready LEDS are initially off.
digitalWrite(ZONE_1_OPEN_LED_PIN, LOW);
digitalWrite(ZONE_2_OPEN_LED_PIN, LOW);
digitalWrite(ZONE_3_OPEN_LED_PIN, LOW);
digitalWrite(ZONE_4_OPEN_LED_PIN, LOW);
pinMode(ZONES_ALL_READY_LED_PIN, OUTPUT); //The pin for our LED which will light up when any Zone/s is not ready (Main General indicator)
digitalWrite(ZONES_ALL_READY_LED_PIN, LOW);
//Ensure that a Zone sense pin is pulled up to 5V when not grounded through the alarm sensor to stop it from 'floating'.
//If this is HIGH alarm will trigger.
pinMode(ZONE_1_SENSE_PIN, INPUT_PULLUP);
pinMode(ZONE_2_SENSE_PIN, INPUT_PULLUP);
pinMode(ZONE_3_SENSE_PIN, INPUT_PULLUP);
pinMode(ZONE_4_SENSE_PIN, INPUT_PULLUP);
pinMode(ZONE_PANIC, INPUT_PULLUP);
//Finite State Machine mode - System disarmed, all zones ready.
CurrMode = 0;
IsMomentaryContactPressed = false; //This will only be TRUE if momentary contact was pressed.
//Get all settings from EEProm.
GetEEPromSettings();
//These flags will be set to TRUE if an intruder damages a zone/s, so that the other zone/s can still function but just ignore the damaged
//zone/s.
//IsZone1Bypassed = false;
//IsZone2Bypassed = false;
//IsZone3Bypassed = false;
LastArmedMode = 0; //Was the keyswitch or remote last used to arm the system? Cannot yet be determined as system is not armed.
HoldStartTime = 0; //Used to measure how long the momentary contact has been held in for to enter Test mode.
TestModeAlarm = true;
SirenMuted = false;
MustFlashArmedLED = false; //Do not flash the Armed LED (Leave this false if using LEDS with their own built in flash)
Serial.println("Testing onboard LEDS...");
//Self test mimic LEDS and output pins.
digitalWrite(ARMED_LED_PIN, HIGH);
delay(150);
digitalWrite(ARMED_LED_PIN, LOW);
digitalWrite(ZONE_1_OPEN_LED_PIN, HIGH);
delay(150);
digitalWrite(ZONE_1_OPEN_LED_PIN, LOW);
digitalWrite(ZONE_2_OPEN_LED_PIN, HIGH);
delay(150);
digitalWrite(ZONE_2_OPEN_LED_PIN, LOW);
digitalWrite(ZONE_3_OPEN_LED_PIN, HIGH);
delay(150);
digitalWrite(ZONE_3_OPEN_LED_PIN, LOW);
digitalWrite(ZONE_4_OPEN_LED_PIN, HIGH);
delay(150);
digitalWrite(ZONE_4_OPEN_LED_PIN, LOW);
digitalWrite(ZONES_ALL_READY_LED_PIN, HIGH);
delay(150);
digitalWrite(ZONES_ALL_READY_LED_PIN, LOW);
digitalWrite(STROBE_PIN, HIGH);
delay(150);
digitalWrite(STROBE_PIN, LOW);
}
////////////////////////////////////////////////////////////////////////////////////////
//MAIN LOOP
////////////////////////////////////////////////////////////////////////////////////////
void loop()
{
//We must constantly check the zones, whether system is armed or not.
//This is to stop the system from being armed if any zone is not ready and light up the appropriate Zone LED
//and Main Zones Not Ready LED.
CheckZones();
CheckPanicZone();
//Used for a keyswitch to Arm/Disarm. We do not need to debounce the latching contact like we do for the momentary one.
//If system was armed with the remote, it can be disarmed by turning keyswitch on and off.
CheckArmDisarmLatchedContact();
//Used for a momentary contact to Arm/Disarm (this could be a button or remote reciever's normally open relay contact).
//If system has been armed with the keyswitch it can only be disarmed with the keyswitch.
CheckArmDisarmMomentaryContact();
//Checks the CY33 433Mhz receiver board to see if we have received data from a EV1527 encoded remote control.
//CheckRemote();
//-------------------------------------------------------------------------------
//FINITE STATE MACHINE MODES
//-----------------------------------------------------------------------------
switch (CurrMode)
{
//////////////////////////////////////////
//Currently disarmed, and all zones are ready.
case 0:
GetSerialData(); //In this mode we can also receive Serial commands from PC/Laptop.
break;
//////////////////////////////////////////
//1 = Currently Disarmed (But One or more Zone/s not ready, cannot be armed.)
case 1:
GetSerialData(); //In this mode we can also receive Serial commands from PC/Laptop.
break;
//////////////////////////////////////////
//2 = Currently Armed (Alarm/Panic have not been triggered)
case 2:
//If the LED has its own built in flash, then this should be set to FALSE as the LED will flash itself and this will not be needed.
//The AtMega will then just set the LED output to HIGH once in the 'ArmSystem' function.
if (MustFlashArmedLED == true)
{
FlashArmedLEDNormal(); //Flash the Armed led at normal speed to show system armed, but not triggered.
}
break;
//////////////////////////////////////////
//3 = Alarm has been Triggered, but siren timeout not yet reached (siren is currently ringing).
case 3:
//See comment in case 2.
if (MustFlashArmedLED == true)
{
FlashArmedLEDFast();
}
FlashAllZonesReadyLED(); //This functions as an Alarm trigerred warning light at entry point.
CheckIfSirenTimeoutComplete(); //Must siren switch off yet?
break;
//////////////////////////////////////////
//4 = Alarm Triggered - Siren timeout reached, siren is off. Only Strobes are on to show an alarm occurred.
case 4:
//Serial.println("Siren Time ");
//See comment in case 2.
//if (MustFlashArmedLED == true)
//{
//FlashArmedLEDFast(); //The fast flash must continue here to show an alarm occurred even when siren off (strobe).
//}
FlashAllZonesReadyLED(); //This functions as an Alarm trigerred warning light at entry point.
break;
//////////////////////////////////////////
//Panic Mode has been triggered. Siren will not time out in this mode, and will need to be switched off with the remote/momentary contact.
case 5:
//See comment in case 2.
//if (MustFlashArmedLED == true)
//{
//FlashArmedLEDFast();
//}
FlashAllZonesReadyLED(); //This functions as an Panic trigerred warning light at entry point.
break;
//////////////////////////////////////////
//Test Mode.
case 6:
CheckZonesTestMode();
FlashTestModeLED();
GetSerialData(); //In this mode we can also receive Serial commands from PC/Laptop.
break;
}
//Reset the Watchdog Timer to indicate all is OK and we are not stuck in a loop somewhere.
//If this timer is not reset, then it will automatically reset the whole system.
//wdt_reset();
}
////////////////////////////////////////////////////////////////////////////////////////
//Checks to see if any zone/s have been triggered (Are open).
//Uses Led's to show if Zone/s are ready. Each zone has it's own led.
//If LED is on for a specific zone, the zone is not ready and the system will not be able to
//be armed.
void CheckZones()
{
//First just assume all zones are ready.
ZonesAllReady = true;
IsZone1Open = false;
IsZone2Open = false;
IsZone3Open = false;
IsZone4Open = false;
//If a zone has not been manually or automatically bypassed, then we check it which can change the state of its 'IsZoneOpen' flag,
//otherwise the flag will just remain FALSE
if (IsZone1Bypassed == false)
{ CheckZone1(); }
if (IsZone2Bypassed == false)
{ CheckZone2(); }
if (IsZone3Bypassed == false)
{ CheckZone3(); }
if (IsZone4Bypassed == false)
{ CheckZone4(); }
switch (CurrMode)
{
case 0: //0 = If system is currently disarmed and all zones are currently ready....
if (IsZone1Open == true)
{ Serial.println("Zone1 Open..."); }
if (IsZone2Open == true)
{ Serial.println("Zone2 Open..."); }
if (IsZone3Open == true)
{ Serial.println("Zone3 Open..."); }
if (IsZone4Open == true)
{ Serial.println("Zone4 Open..."); }
if (ZonesAllReady == false) //If a zone/s is not ready....
{ CurrMode = 1; } //Then Switch to mode 1 to prevent system from being armed as a zone/s is not ready.
break;
case 1: //1 = If system is currently disarmed, but One or more Zone/s not ready, cannot be armed.
if (ZonesAllReady == true) //If all the zones are closed (not triggered).....
{ CurrMode = 0; } //Switch to Zones all ready, allowing the system to now be armed.
break;
case 2: //Armed
case 4: //Armed, siren time out reached.
if (ZonesAllReady == false)
{
if (IsZone1Open == true)
{ Serial.println("Zone1 Open..."); }
if (IsZone2Open == true)
{ Serial.println("Zone2 Open..."); }
if (IsZone3Open == true)
{ Serial.println("Zone3 Open..."); }
if (IsZone4Open == true)
{ Serial.print("Zone4 Open...\n"); }
TriggerAlarm(); //Activate the alarm.
}
break;
case 5: //Panic
break; //Do nothing......
case 6: //Test mode
break; //Do nothing......
}
//If any zone/s is not ready, then we make our ZONE ALL ready pin HIGH.
//This can be used for an LED at the front door for example to indicate the system is not ready to be armed.
//It is also used to show if any zone has possibly been damaged and is now open as it will still be on after an
//Alarm has triggered and timed out (strobe)
if (CurrMode == 0 || CurrMode == 1) //If disarmed or not ready .....
{
if (ZonesAllReady == false)
//One or more zones are NOT ready.....
{ digitalWrite(ZONES_ALL_READY_LED_PIN, HIGH); }
else
{
//All the Zones are ready.....
//If none of the zones have been manually or automatically bypassed by the system, then we know no zone is damaged.
//We need this check here as the 'ZonesAllReady' flag may be TRUE as a zone/s has possibly been bypassed, but we
//still want our LED to turn on showing possible damage.
if (IsZone1Bypassed == false && IsZone2Bypassed == false && IsZone3Bypassed == false && IsZone4Bypassed == false)
{
digitalWrite(ZONES_ALL_READY_LED_PIN, LOW);
}
else
{
//One or more zone/s are open, so possibly damaged, give an indication of this.
digitalWrite(ZONES_ALL_READY_LED_PIN, HIGH);
}
}
}
}
////////////////////////////////////////////////////////////////////////////////////////
void CheckZone1()
{
//A High state (Open circuit, possible intrusion) has been detected, now we will only register it as an intrusion
//if it persists for as long as, or greater then, the 'ZoneIntrusionOIpenMilliseconds' time, which we will monitor.
//This is too avoid false alarms (debouncing for interference/spikes on the wires), ie. zone may need to be open for 500ms etc for it to
//Alarm instead of Alarming instantly.
//Was an open circuit detected, and we have started monitoring it?
static bool checking = false; //Declare static so variable is only initialised once and not cleared between function calls
//Used to measure the time we have been monitoring.
static unsigned long startTime = 0; //Declare static so variable is only initialised once and not cleared between function calls
//Check the Zone sense pin...
int state = digitalRead(ZONE_1_SENSE_PIN);
//If it is OPEN circuit (possible intrusion)...
if (state == HIGH)
{
//Have we started monitoring the time it has been open yet?
if (!checking)
{
//First time seeing HIGH, start monitoring timer....
checking = true;
startTime = millis();
}
//We have started monitoring, now has it reached the time yet to be considered an Alarm?
else if (millis() - startTime >= ZoneIntrusionOpenMilliseconds)
{
//It's been HIGH for the full monitoring period, so we confirm it as an Alarm.
IsZone1Open = true;
ZonesAllReady = false;
digitalWrite(ZONE_1_OPEN_LED_PIN, HIGH); //Switch on the Zone LED to show it is open.
}
}
else
{
//Make sure monitoring variable is Reset as zone is closed.
checking = false;
IsZone1Open = false;
//Since we want the zone led to function as a strobe to show alarm was triggered even when siren is off, only allow it to be
//switched off if system was not in alarm triggered or timeout modes.
if (CurrMode != 3 && CurrMode != 4)
{
//Then we make sure the Zone LED is off.
digitalWrite(ZONE_1_OPEN_LED_PIN, LOW);
}
}
}
////////////////////////////////////////////////////////////////////////////////////////
void CheckZone2()
{
//A High state (Open circuit, possible intrusion) has been detected, now we will only register it as an intrusion
//if it persists for as long as, or greater then, the 'ZoneIntrusionOIpenMilliseconds' time, which we will monitor.
//This is too avoid false alarms (debouncing for interference/spikes on the wires), ie. zone may need to be open for 500ms etc for it to
//Alarm instead of Alarming instantly.
//Was an open circuit detected, and we have started monitoring it?
static bool checking = false; //Declare static so variable is only initialised once and not cleared between function calls
//Used to measure the time we have been monitoring.
static unsigned long startTime = 0; //Declare static so variable is only initialised once and not cleared between function calls
//Check the Zone sense pin...
int state = digitalRead(ZONE_2_SENSE_PIN);
//If it is OPEN circuit (possible intrusion)...
if (state == HIGH)
{
//Have we started monitoring the time it has been open yet?
if (!checking)
{
//First time seeing HIGH, start monitoring timer....
checking = true;
startTime = millis();
}
//We have started monitoring, now has it reached the time yet to be considered an Alarm?
else if (millis() - startTime >= ZoneIntrusionOpenMilliseconds)
{
//It's been HIGH for the full monitoring period, so we confirm it as an Alarm.
IsZone2Open = true;
ZonesAllReady = false;
digitalWrite(ZONE_2_OPEN_LED_PIN, HIGH); //Switch on the Zone LED to show it is open.
}
}
else
{
//Make sure monitoring variable is Reset as zone is closed.
checking = false;
IsZone2Open = false;
//Since we want the zone led to function as a strobe to show alarm was triggered even when siren is off, only allow it to be
//switched off if system was not in alarm triggered or timeout modes.
if (CurrMode != 3 && CurrMode != 4)
{
//Then we make sure the Zone LED is off.
digitalWrite(ZONE_2_OPEN_LED_PIN, LOW);
}
}
}
////////////////////////////////////////////////////////////////////////////////////////
void CheckZone3()
{
//A High state (Open circuit, possible intrusion) has been detected, now we will only register it as an intrusion
//if it persists for as long as, or greater then, the 'ZoneIntrusionOIpenMilliseconds' time, which we will monitor.
//This is too avoid false alarms (debouncing for interference/spikes on the wires), ie. zone may need to be open for 500ms etc for it to
//Alarm instead of Alarming instantly.
//Was an open circuit detected, and we have started monitoring it?
static bool checking = false; //Declare static so variable is only initialised once and not cleared between function calls
//Used to measure the time we have been monitoring.
static unsigned long startTime = 0; //Declare static so variable is only initialised once and not cleared between function calls
//Check the Zone sense pin...
int state = digitalRead(ZONE_3_SENSE_PIN);
//If it is OPEN circuit (possible intrusion)...
if (state == HIGH)
{
//Have we started monitoring the time it has been open yet?
if (!checking)
{
//First time seeing HIGH, start monitoring timer....
checking = true;
startTime = millis();
}
//We have started monitoring, now has it reached the time yet to be considered an Alarm?
else if (millis() - startTime >= ZoneIntrusionOpenMilliseconds)
{
//It's been HIGH for the full monitoring period, so we confirm it as an Alarm.
IsZone3Open = true;
ZonesAllReady = false;
digitalWrite(ZONE_3_OPEN_LED_PIN, HIGH); //Switch on the Zone LED to show it is open.
}
}
else
{
//Make sure monitoring variable is Reset as zone is closed.
checking = false;
IsZone3Open = false;
//Since we want the zone led to function as a strobe to show alarm was triggered even when siren is off, only allow it to be
//switched off if system was not in alarm triggered or timeout modes.
if (CurrMode != 3 && CurrMode != 4)
{
//Then we make sure the Zone LED is off.
digitalWrite(ZONE_3_OPEN_LED_PIN, LOW);
}
}
}
////////////////////////////////////////////////////////////////////////////////////////
void CheckZone4()
{
//A High state (Open circuit, possible intrusion) has been detected, now we will only register it as an intrusion
//if it persists for as long as, or greater then, the 'ZoneIntrusionOIpenMilliseconds' time, which we will monitor.
//This is too avoid false alarms (debouncing for interference/spikes on the wires), ie. zone may need to be open for 500ms etc for it to
//Alarm instead of Alarming instantly.
//Was an open circuit detected, and we have started monitoring it?
static bool checking = false; //Declare static so variable is only initialised once and not cleared between function calls
//Used to measure the time we have been monitoring.
static unsigned long startTime = 0; //Declare static so variable is only initialised once and not cleared between function calls
//Check the Zone sense pin...
int state = digitalRead(ZONE_4_SENSE_PIN);
//If it is OPEN circuit (possible intrusion)...
if (state == HIGH)
{
//Have we started monitoring the time it has been open yet?
if (!checking)
{
//First time seeing HIGH, start monitoring timer....
checking = true;
startTime = millis();
}
//We have started monitoring, now has it reached the time yet to be considered an Alarm?
else if (millis() - startTime >= ZoneIntrusionOpenMilliseconds)
{
//It's been HIGH for the full monitoring period, so we confirm it as an Alarm.
IsZone4Open = true;
ZonesAllReady = false;
digitalWrite(ZONE_4_OPEN_LED_PIN, HIGH); //Switch on the Zone LED to show it is open.
}
}
else
{
//Make sure monitoring variable is Reset as zone is closed.
checking = false;
IsZone4Open = false;
//Since we want the zone led to function as a strobe to show alarm was triggered even when siren is off, only allow it to be
//switched off if system was not in alarm triggered or timeout modes.
if (CurrMode != 3 && CurrMode != 4)
{
//Then we make sure the Zone LED is off.
digitalWrite(ZONE_4_OPEN_LED_PIN, LOW);
}
}
}
////////////////////////////////////////////////////////////////////////////////////////
void CheckPanicZone()
{
if (digitalRead(ZONE_PANIC) == LOW) //If panic zone is closed (ie panic button pressed).
{
if (CurrMode != 3 && CurrMode != 5) //If the system is not already in Alarm mode and is not in Panic mode.....
{ TriggerPanic(); }
}
}
////////////////////////////////////////////////////////////////////////////////////////
//Checks a latched contact e.g. a keyswitch etc, to ARM/DISARM.
//NOTE:
//If armed with remote, system can be disarmed again with the keyswitch by turning it on then off again.
//However, if armed with keyswitch it can only be turned off again with the keyswitch.
void CheckArmDisarmLatchedContact()
{
//static unsigned long switchOnTime = 0;
static bool switchWasOn = false;
//If keyswitch turned ON....
if (digitalRead(ARM_DISARM_LATCH_PIN) == LOW)
{
//This is needed as we want to be able to disarm with the keyswitch even when originally armed with the remote.
if (!switchWasOn)
{
// This is a new ON event
switchWasOn = true;
//switchOnTime = millis();
}
//Serial.println(CurrMode);
// If system is disarmed, allow normal arming with keyswitch
switch (CurrMode)
{
case 0: // Disarmed and zones ready
LastArmedMode = 1; // Armed via keyswitch
ArmSystem();
break;
case 1: // Disarmed but zones not ready
// SoundBuzzer();
break;
case 5: //Panic triggered
break;
case 6: // Test mode
break;
}
}
else
{
// Switch is OFF.
if (switchWasOn)
{
// It was previously on — check if we need to disarm.
switchWasOn = false;
//Disarm if it was originally armed with the keyswitch (LastArmedMode = 1) or it may be disarmed (LastArmedMode = 0) but we make it Disarm
//regardless as a Panic was triggered and we use the keyswitch to turn it off.
//Armed with keyswitch = 1 Not armed (But panic) = 0
if (LastArmedMode == 1 || LastArmedMode == 0)
{
switch (CurrMode)
{
case 2: // Armed
case 3: // Alarm triggered not timed out
case 4: // Alarm triggered timed out
case 5: //Panic Triggerred
DisarmSystem();
break;
case 6: // Test mode
break;
}
}
// Case 2: Disarm via fallback if armed by remote.
else if (LastArmedMode == 2)
{
switch (CurrMode)
{
case 2:
case 3:
case 4:
case 5: //Panic Triggerred
DisarmSystem();
break;
}
}
}
}
}
////////////////////////////////////////////////////////////////////////////////////////
//Checks a momentary switch (e.g. remote) or contact to toggle the system between Armed/Disarmed.
//NOTE:
//If armed with remote, system can be disarmed again with the keyswitch by turning it on then off again.
//However, if armed with keyswitch it can only be turned off again with the keyswitch.
void CheckArmDisarmMomentaryContact()
{
// Flag to prevent entering test mode during the same button press that cleared panic.
static bool JustClearedPanic = false;
// Tracks whether the momentary button is currently being held.
static bool IsMomentaryContactPressed = false;
// NEW FLAG: Blocks the system from arming immediately after clearing panic,
// until the button is released and pressed again.
static bool BlockMomentaryAfterPanicClear = false;
// Used for button debounce timing.
static unsigned long LastDebounceTime = 0;
// Stores the last known state of the button (HIGH = not pressed, due to pull-up).
static int LastButtonState = HIGH;
// Debounce time in milliseconds – input must remain stable for this long.
const unsigned long DebounceDelay = 50;
// Read the current button state (LOW = pressed due to pull-up resistor).
int currentState = digitalRead(ARM_DISARM_MOMENTARY_PIN);
// If the button state changed (bounced or new press), record the time and update state.
if (currentState != LastButtonState)
{
LastDebounceTime = millis(); // Save current time
LastButtonState = currentState; // Update last known button state
}
// Only act if the state has been stable longer than debounce delay
if ((millis() - LastDebounceTime) > DebounceDelay)
{
// ---------- BUTTON IS BEING HELD DOWN ----------
if (currentState == LOW)
{
// First detection of button press
if (!IsMomentaryContactPressed)
{
IsMomentaryContactPressed = true;
HoldStartTime = millis(); // Save the moment button was first pressed
}
// Calculate how long the button has been held so far
HoldDuration = millis() - HoldStartTime;
// ---------- CLEAR PANIC MODE ----------
// If held long enough to disarm panic mode
if (HoldDuration >= DisArmActivatePanic && CurrMode == 5)
{
HoldDuration = 0; // Reset hold timer
DisarmSystem(); // Disarm system (exit panic)
JustClearedPanic = true; // Prevent test mode entry on same press
BlockMomentaryAfterPanicClear = true; // Prevent re-arming while button is still being held in
}
// ---------- ENTER TEST MODE ----------
// Only allowed if panic wasn’t just cleared and enough time has passed
if (!JustClearedPanic && HoldDuration >= TestModeActivate)
{
// Only enter test mode if not already in panic, armed, or test
if (CurrMode != 6 && CurrMode != 2 && CurrMode != 5)
{
ActivateTestMode(); // Enter test mode
}
}
}
// ---------- BUTTON RELEASED ----------
else
{
// If the button was being held before and released now...
if (IsMomentaryContactPressed && !BlockMomentaryAfterPanicClear)
{
SetMomentaryMode(); // Toggle arm/disarm logic based on current system state
}
// Reset all relevant flags and timers after release
IsMomentaryContactPressed = false;
HoldDuration = 0;
JustClearedPanic = false; // Allow test mode on next valid press
BlockMomentaryAfterPanicClear = false; // Allow arming on next press cycle
}
}
}
////////////////////////////////////////////////////////////////////////////////////////
//This is called by the Momentary contact / Remote being pressed and then released.
//It will basically Arm/Disarm the system depending on its current mode.
void SetMomentaryMode()
{
//Check the current state the system is in....
switch (CurrMode)
{
case 0: //Currently disarmed and zones are ready.
LastArmedMode = 2; //Record that it was the momentary contact that armed the system (not the keyswitch)
ArmSystem(); //Zones are ready so we Arm the system.
break;
case 1: //Currently disarmed, but zones are not ready.
//SoundBuzzer(); //Play a buzzer tone to indicate arming not allowed as zone/s are not ready.
break;
case 2: //Currently Armed, alarm not triggered.
if (LastArmedMode == 2)
{ //Only allow the system to be disarmed with the remote, if the remote was used to arm the system in the first place.
DisarmSystem();
}
break;
case 3: //Currently Armed, alarm has been trigerred, siren is currently ringing.
if (LastArmedMode == 2)
{ //Only allow the system to be disarmed with the remote, if the remote was used to arm the system in the first place.
DisarmSystem();
}
else
{ //Keyswitch was used to arm system, so we cannot disarm it (only keyswitch can), but we just mute the siren.
SwitchSirenOff();
}
break;
case 4: //Currently Armed, alarm has triggered, siren has timed out, only strobes on.
if (LastArmedMode == 2)
{ //Only allow the system to be disarmed with the remote, if the remote was used to arm the system in the first place.
DisarmSystem();
}
break;
case 5: //Panic.
//This is dealt with in the 'CheckArmDisarmMomentaryContact' function to clear the Panic, but only if this button is held in for xxxx seconds.
//Usually this should be set to quite a high value like 10 seconds (default).
break;
case 6: //Test Mode
//Since test mode is activated while the momentary button is being held in, we do not want to switch it off again immediately
//when the button is released, so we check for how long the button has been held in for. Since the first time it is released after
//just going into test mode the value of 'HoldDuration' will be minimum of the 'TestModeActivate' variable value so it will not disarm. But then 'HoldDuration' gets
//reset, so next time button is pressed it will disarm.
if (HoldDuration <= (TestModeActivate - 1000))
{
DisarmSystem();
}
break;
}
}
////////////////////////////////////////////////////////////////////////////////////////
void ActivateTestMode()
{
Serial.println("Test Mode On...");
digitalWrite(ZONES_ALL_READY_LED_PIN, HIGH); //Switch on the Main Zone/s Ready LED.
AnnunciateSirenArm();
AllZonesReadyLEDOnTimer.start(TestModeLEDOnTime); //Start it's first timer which will control its flashing.
CurrMode = 6; //Switch to Test Mode.
//Since we will now be listening on the Serial Port for incoming data, we clear Serial buffer and make sure there is no junk.
while (Serial.available())
{
Serial.read(); // discard junk
}
}
////////////////////////////////////////////////////////////////////////////////////////
void DisarmSystem()
{
//If not in Test Mode currently.
if(CurrMode != 6)
{Serial.println("DisArming...");}
else
{Serial.println("Test Mode Off...");}
//Make sure all timers are switched off.
ExitDelayTimer.stop();
SirenTimeoutTimer.stop();
ArmedLEDOnTimer.stop();
ArmedLEDOffTimer.stop();
AllZonesReadyLEDOnTimer.stop();
AllZonesReadyLEDOffTimer.stop();
SwitchSirenOff(); //Make sure the siren is off.
//Make sure the Armed LED is turned off.
digitalWrite(ARMED_LED_PIN, LOW); //Make sure the Armed LED is turned off by making the voltage LOW
//Make sure strobe output is off.
digitalWrite(STROBE_PIN, LOW); //Switch strobe output off.
Serial.println("Strobe Off");
digitalWrite(ZONES_ALL_READY_LED_PIN, LOW);
//Make sure all zone led's are turned off (they will come on again if a zone is open though)
digitalWrite(ZONE_1_OPEN_LED_PIN, LOW);
digitalWrite(ZONE_2_OPEN_LED_PIN, LOW);
digitalWrite(ZONE_3_OPEN_LED_PIN, LOW);
digitalWrite(ZONE_4_OPEN_LED_PIN, LOW);
CurrMode = 0; //Switch to Disarmed mode.
LastArmedMode = 0;
//EEProm address 0-3 holds whether a Zone is bypassed or not.
//If we see a 0 for a particular zone's bypass, it means that the zone is not bypassed by settings and we can
//safely unbypass any temp bypasses that the system may have made automatically if a zone was damaged while armed
//to keep the other zones running while at the same time not letting the siren go off constantly.
//If it is not 0 it will be a 1 which means the zone has been bypassed at the start of the program and therefore we
//keep it bypassed.
if (EEPROM.read(0) == 0)
{IsZone1Bypassed = false;}
if (EEPROM.read(1) == 0)
{IsZone2Bypassed = false;}
if (EEPROM.read(2) == 0)
{IsZone3Bypassed = false;}
if (EEPROM.read(3) == 0)
{IsZone4Bypassed = false;}
AnnunciateSirenDisarm();
}
////////////////////////////////////////////////////////////////////////////////////////
void ArmSystem()
{
//Serial.println(GetLastAlarmLog());
//ExitDelayTimer.start(ExitDelayMilliseconds);
Serial.println("Arming...");
digitalWrite(ARMED_LED_PIN, HIGH); //Switch on the Armed LED.
digitalWrite(STROBE_PIN, LOW); //Make sure strobe output is LOW.
ArmedLEDOnTimer.start(100); //Start it's first timer which will control its flashing.
CurrMode = 2; //Switch to Armed mode.
AnnunciateSirenArm();
}
////////////////////////////////////////////////////////////////////////////////////////
//Will briefly annunciate the siren each time a zone is open or closed.
void CheckZonesTestMode()
{
// Static variables remember their value between function calls
static bool lastZoneState[4] = {LOW, LOW, LOW, LOW}; // LOW = closed (NC)
bool currentZoneState[4];
// Read current states
currentZoneState[0] = digitalRead(ZONE_1_SENSE_PIN);
currentZoneState[1] = digitalRead(ZONE_2_SENSE_PIN);
currentZoneState[2] = digitalRead(ZONE_3_SENSE_PIN);
currentZoneState[3] = digitalRead(ZONE_4_SENSE_PIN);
//Check each zone for a state change
for (int i = 0; i < 4; i++)
{
if (currentZoneState[i] != lastZoneState[i])
{
AnnunciateSirenArm(); // Zone changed (opened or closed)
lastZoneState[i] = currentZoneState[i]; // Update remembered state
}
}
// }
// else
// {
// Reset last known states if not in test mode to avoid false triggers on reentry
// lastZoneState[0] = digitalRead(ZONE_1_SENSE_PIN);
// lastZoneState[1] = digitalRead(ZONE_2_SENSE_PIN);
// lastZoneState[2] = digitalRead(ZONE_3_SENSE_PIN);
// lastZoneState[3] = digitalRead(ZONE_4_SENSE_PIN);
// }
}
////////////////////////////////////////////////////////////////////////////////////////
void AnnunciateSirenArm()
{
//First make sure we reset siren timer and siren.
SirenTimeoutTimer.stop();
digitalWrite(SIREN_PIN, LOW);
//Beep the siren briefly once.
if(SirenMuted == false)
{
digitalWrite(SIREN_PIN, HIGH);
delay(SirenAnnunciateRingTime);
digitalWrite(SIREN_PIN, LOW);
}
}
////////////////////////////////////////////////////////////////////////////////////////
void AnnunciateSirenDisarm()
{
//First make sure we reset siren timer and siren.
SirenTimeoutTimer.stop();
digitalWrite(SIREN_PIN, LOW);
//Beep the siren briefly twice.
if(SirenMuted == false)
{
digitalWrite(SIREN_PIN, HIGH);
delay(SirenAnnunciateRingTime);
digitalWrite(SIREN_PIN, LOW);
delay(SirenAnnunciateRingTime);
digitalWrite(SIREN_PIN, HIGH);
delay(SirenAnnunciateRingTime);
digitalWrite(SIREN_PIN, LOW);
}
}
////////////////////////////////////////////////////////////////////////////////////////
void SwitchSirenOn()
{
if(SirenMuted == false)
{ digitalWrite(SIREN_PIN, HIGH);
Serial.println("Siren On");
}
else
{
Serial.println("Siren On (Muted)");
}
}
////////////////////////////////////////////////////////////////////////////////////////
void SwitchSirenOff()
{
SirenTimeoutTimer.stop();
digitalWrite(SIREN_PIN, LOW);
Serial.println("Siren Off");
}
////////////////////////////////////////////////////////////////////////////////////////
void TestSiren()
{
//First make sure we reset siren timer and siren.
SirenTimeoutTimer.stop();
digitalWrite(SIREN_PIN, LOW);
//Beep the siren briefly once.
//In test mode we sound the siren for 1 second whether it was muted or not....
digitalWrite(SIREN_PIN, HIGH);
delay(1000);
digitalWrite(SIREN_PIN, LOW);
}
////////////////////////////////////////////////////////////////////////////////////////
void TestStrobe()
{
digitalWrite(STROBE_PIN, HIGH); //Switch strobe output on.
delay(3000);
digitalWrite(STROBE_PIN, LOW);
}
////////////////////////////////////////////////////////////////////////////////////////
void TriggerAlarm()
{
Serial.println("Alarm Activated!");
//This timer is used for the Armed LED if it has been set that the AtMega must flash it and not the LED
//using its own internal flash circuitry.
ArmedLEDOffTimer.stop();
ArmedLEDOnTimer.stop();
ArmedLEDOnTimer.start(100);
//Start the Timer for the LED (All Zones Ready / Panic / Test)
AllZonesReadyLEDOffTimer.stop();
AllZonesReadyLEDOnTimer.stop();
AllZonesReadyLEDOnTimer.start(100);
digitalWrite(STROBE_PIN, HIGH); //Switch strobe output on.
Serial.println("Strobe On");
SwitchSirenOn();
SirenTimeoutTimer.start(SirenTimeoutMilliseconds);
//Write the Alarm event to EEPROM.
if(IsZone1Open == true)
{LogAlarm(1); }
if(IsZone2Open == true)
{LogAlarm(2); }
if(IsZone3Open == true)
{LogAlarm(3); }
if(IsZone4Open == true)
{LogAlarm(4); }
CurrMode = 3; //Switch to Alarm Triggered - Siren timeout not reached mode.
}
////////////////////////////////////////////////////////////////////////////////////////
//When this sub is called the panic zone has been activated, and the siren will ring continuously and
//not time out, until the momentary contact is pressed to reset it.
void TriggerPanic()
{
Serial.println("Panic Activated!");
CurrMode = 5; //Switch to Panic mode.
AllZonesReadyLEDOffTimer.stop();
AllZonesReadyLEDOnTimer.stop();
AllZonesReadyLEDOnTimer.start(100);
SwitchSirenOff();
SwitchSirenOn();
digitalWrite(STROBE_PIN, HIGH); //Switch strobe output on.
Serial.println("Strobe On");
}
////////////////////////////////////////////////////////////////////////////////////////
//This used the All Zones Ready LED.
void FlashTestModeLED()
{
if (AllZonesReadyLEDOnTimer.justFinished()) //Has the timer controlling how long LED is on timed out?
{
digitalWrite(ZONES_ALL_READY_LED_PIN, LOW); //Turn the LED off by making the voltage LOW
AllZonesReadyLEDOffTimer.start(TestModeLEDOffTime); //Start the timer which controls how long the LED will remain off.
}
if (AllZonesReadyLEDOffTimer.justFinished())
{
digitalWrite(ZONES_ALL_READY_LED_PIN, HIGH); //Turn the LED on (HIGH is the voltage level)
AllZonesReadyLEDOnTimer.start(TestModeLEDOnTime); //Start the timer which controls how long the LED will remain on.
}
}
////////////////////////////////////////////////////////////////////////////////////////
//This used the All Zones Ready LED.
void FlashAllZonesReadyLED()
{
if (AllZonesReadyLEDOnTimer.justFinished()) //Has the timer controlling how long LED is on timed out?
{
digitalWrite(ZONES_ALL_READY_LED_PIN, LOW); //Turn the LED off by making the voltage LOW
AllZonesReadyLEDOffTimer.start(AlarmTrigerredLEDFastOffTime); //Start the timer which controls how long the LED will remain off.
// Serial.println("off");
}
if (AllZonesReadyLEDOffTimer.justFinished())
{
digitalWrite(ZONES_ALL_READY_LED_PIN, HIGH); //Turn the LED on (HIGH is the voltage level)
AllZonesReadyLEDOnTimer.start(AlarmTrigerredLEDFastOnTime); //Start the timer which controls how long the LED will remain on.
// Serial.println("on");
}
}
////////////////////////////////////////////////////////////////////////////////////////
//This will only be used if Armed LED/s does not have its own internal flash circuit.
//Just makes the Armed LED/s flash fast to show an Alarm was triggered.
//We use the 'millisDelay' timer type variables here so we do not block the Main thread
//with traditional 'delay' calls. It simply checks to see if the specific background timer has
//completed, then transitions.
////////////////////////////////////////////////////////////////////////////////////////
void FlashArmedLEDFast()
{
if (ArmedLEDOnTimer.justFinished()) //Has the timer controlling how long LED is on timed out?
{
digitalWrite(ARMED_LED_PIN, LOW); //Turn the Armed LED off by making the voltage LOW
ArmedLEDOffTimer.start(AlarmTrigerredLEDFastOffTime); //Start the timer which controls how long the LED will remain off.
}
if (ArmedLEDOffTimer.justFinished())
{
digitalWrite(ARMED_LED_PIN, HIGH); //Turn the Armed LED on (HIGH is the voltage level)
ArmedLEDOnTimer.start(AlarmTrigerredLEDFastOnTime); //Start the timer which controls how long the LED will remain on.
}
}
////////////////////////////////////////////////////////////////////////////////////////
//This will only be used if Armed LED/s does not have its own internal flash circuit.
void FlashArmedLEDNormal()
{
if (ArmedLEDOnTimer.justFinished()) //Has the timer controlling how long LED is on timed out?
{
digitalWrite(ARMED_LED_PIN, LOW); //Turn the Armed LED off by making the voltage LOW
ArmedLEDOffTimer.start(ArmedLEDNormalOffTime); //Start the timer which controls how long the LED will remain off.
}
if (ArmedLEDOffTimer.justFinished())
{
digitalWrite(ARMED_LED_PIN, HIGH); //Turn the Armed LED on (HIGH is the voltage level)
ArmedLEDOnTimer.start(ArmedLEDNormalOnTime); //Start the timer which controls how long the LED will remain on.
}
}
////////////////////////////////////////////////////////////////////////////////////////
//void SoundBuzzer()
//{
//digitalWrite(BUZZER_PIN, HIGH);
//}
////////////////////////////////////////////////////////////////////////////////////////
void CheckIfSirenTimeoutComplete()
{
if (SirenTimeoutTimer.justFinished() == true)
{
Serial.println("Alarm Timed Out");
SwitchSirenOff();
//At this point we quickly do a check on the Zones to see if any of them are staying open circuit, if so we will bypass it, so the other zone/s can remain active,
//in case one of them was damaged by an intruder.
CheckZone1();
if (IsZone1Open == true)
{
IsZone1Bypassed = true; //Zone is possibly damaged and open so bypass it so other zones can continue to function.
}
CheckZone2();
if (IsZone2Open == true)
{
IsZone2Bypassed = true; //Zone is possibly damaged and open so bypass it so other zones can continue to function.
}
CheckZone3();
if (IsZone3Open == true)
{
IsZone3Bypassed = true; //Zone is possibly damaged and open so bypass it so other zones can continue to function.
}
CheckZone4();
if (IsZone4Open == true)
{
IsZone4Bypassed = true; //Zone is possibly damaged and open so bypass it so other zones can continue to function.
}
CurrMode = 4; //Switch to Siren is off, but strobes are still on mode.
}
}
////////////////////////////////////////////////////////////////////////////////////////
void FlashLED(int pin, int flashes, int onTimeMs, int offTimeMs)
{
for (int i = 0; i < flashes; i++)
{
digitalWrite(pin, HIGH);
delay(onTimeMs);
digitalWrite(pin, LOW);
delay(offTimeMs);
}
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////
//SERIAL COMMS & EEPROM ////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////
//Checks for data on the Serial lines from a PC or Laptop.
void GetSerialData()
{
static String input = "";
while (Serial.available() > 0) {
char c = Serial.read();
if (c == '\r') {
// Ignore carriage return; wait for newline
continue;
} else if (c == '\n') {
input.trim();
if (input.length() > 0) {
//Serial.println("Received input: " + input); // Optional debug
GetSerialDataCommand(input);
}
input = ""; // Clear for next command
} else {
input += c;
}
}
}
///////////////////////////////////////////////////////////////////////////////////////
void GetSerialDataCommand(String input)
{
input.trim(); // clean up whitespace
String datastring;
byte databyte; //1 byte = 1 EEPROM Address
int dataint; //2 bytes = 2 EEPROM Address
long datalong; //4 bytes = 4 EEPROM Address
// NOTE: Can only use 'EEPROM.update' for single byte values, otherwise must use 'EEPROM.put'.
/////////////////////////////////////////////////
// ZONE COMMANDS
/////////////////////////////////////////////////
if (input == "z1:bypass:1") {
IsZone1Bypassed = true;
EEPROM.update(0, 1);
Serial.print(input + " --> OK");
} else if (input == "z1:bypass:0") {
IsZone1Bypassed = false;
EEPROM.update(0, 0);
Serial.print(input + " --> OK");
} else if (input == "z2:bypass:1") {
IsZone2Bypassed = true;
EEPROM.update(1, 1);
Serial.print(input + " --> OK");
} else if (input == "z2:bypass:0") {
IsZone2Bypassed = false;
EEPROM.update(1, 0);
Serial.print(input + " --> OK");
} else if (input == "z3:bypass:1") {
IsZone3Bypassed = true;
EEPROM.update(2, 1);
Serial.print(input + " --> OK");
} else if (input == "z3:bypass:0") {
IsZone3Bypassed = false;
EEPROM.update(2, 0);
Serial.print(input + " --> OK");
} else if (input == "z4:bypass:1") {
IsZone4Bypassed = true;
EEPROM.update(3, 1);
Serial.print(input + " --> OK");
} else if (input == "z4:bypass:0") {
IsZone4Bypassed = false;
EEPROM.update(3, 0);
Serial.print(input + " --> OK");
} else if (input == "bypass_all") {
IsZone1Bypassed = true;
IsZone2Bypassed = true;
IsZone3Bypassed = true;
IsZone4Bypassed = true;
EEPROM.update(0, 1);
EEPROM.update(1, 1);
EEPROM.update(2, 1);
EEPROM.update(3, 1);
Serial.print(input + " --> OK");
} else if (input == "unbypass_all") {
IsZone1Bypassed = false;
IsZone2Bypassed = false;
IsZone3Bypassed = false;
IsZone4Bypassed = false;
EEPROM.update(0, 0);
EEPROM.update(1, 0);
EEPROM.update(2, 0);
EEPROM.update(3, 0);
Serial.print(input + " --> OK");
// Zone Query commands
} else if (input == "z1:bypass:?") {
databyte = EEPROM.read(0);
Serial.print(input + " --> " + databyte);
} else if (input == "z2:bypass:?") {
databyte = EEPROM.read(1);
Serial.print(input + " --> " + databyte);
} else if (input == "z3:bypass:?") {
databyte = EEPROM.read(2);
Serial.print(input + " --> " + databyte);
} else if (input == "z4:bypass:?") {
databyte = EEPROM.read(3);
Serial.print(input + " --> " + databyte);
// Last Alarm event log?
} else if (input == "log:?") {
datastring = GetLastAlarmLog();
Serial.print(input + " --> " + datastring);
// Clear Last Alarm event log
} else if (input == "log:clear") {
EEPROM.write(10, 0);
Serial.print(input + " --> OK");
/////////////////////////////////////////////////
// SIREN COMMANDS.
/////////////////////////////////////////////////
// TEMP MUTE/UNMUTE
} else if (input == "siren:mute:1") {
Serial.print(input + " --> OK");
SirenMuted = true;
} else if (input == "siren:mute:0") {
Serial.print(input + " --> OK");
SirenMuted = false;
// TIMEOUT (4 bytes / EEPROM addresses)
// Siren timeout (in milliseconds) - SET
} else if (input.startsWith("siren:timeout:")) {
datalong = atol(input.substring(input.lastIndexOf(':') + 1).c_str());
EEPROM.put(20, datalong);
SirenTimeoutMilliseconds = datalong;
Serial.print(input + " --> OK");
// Siren timeout (in milliseconds) - GET
} else if (input.startsWith("siren:timeout:?")) {
EEPROM.get(20, datalong);
Serial.println(input + " --> " + String(datalong));
// ANNUNCIATE
// Siren Annunciate On/Off - SET
} else if (input == "siren:annunciate:1") {
EEPROM.update(25, 1);
SirenMuted = false;
Serial.print(input + " --> OK");
// Siren Annunciate On/Off - GET
} else if (input == "siren:annunciate:0") {
EEPROM.update(25, 0);
SirenMuted = true;
Serial.print(input + " --> OK");
// ANNUNCIATE RING TIME
// Siren Annunciate Ring Time (In milliseconds) - SET
} else if (input.startsWith("siren:annunciate_ringtime:")) {
datalong = atol(input.substring(input.lastIndexOf(':') + 1).c_str());
EEPROM.put(30, datalong);
SirenAnnunciateRingTime = datalong;
Serial.print(input + " --> OK");
// Siren Annunciate Ring Time (In milliseconds) - GET
} else if (input.startsWith("siren:annunciate_ringtime:?")) {
EEPROM.get(30, datalong);
Serial.println(input + " --> " + String(datalong));
// TEST MODE ACTIVATE
// For how many milliseconds must Arm button be held in to enter test mode? - SET
} else if (input.startsWith("test_mode_activate:")) {
datalong = atol(input.substring(input.lastIndexOf(':') + 1).c_str());
EEPROM.put(35, datalong);
TestModeActivate = datalong;
Serial.print(input + " --> OK");
// For how many milliseconds must Arm button be held in to enter test mode? - GET
} else if (input.startsWith("test_mode_activate:?")) {
EEPROM.get(35, datalong);
Serial.println(input + " --> " + String(datalong));
// SILENCE PANIC HOLD TIME
// How many milliseconds must a remote / momentary be held in to silence a panic? - SET
} else if (input.startsWith("panic_cancel:")) {
datalong = atol(input.substring(input.lastIndexOf(':') + 1).c_str());
EEPROM.put(40, datalong);
DisArmActivatePanic = datalong;
Serial.print(input + " --> OK");
// How many milliseconds must a remote / momentary be held in to silence a panic? - GET
} else if (input.startsWith("panic_cancel:?")) {
EEPROM.get(40, datalong);
Serial.println(input + " --> " + String(datalong));
// ZONE INTRUSION DEBOUNCE TIME
// How many milliseconds must a zone/s be open for, before the alarm will trigger? - SET
} else if (input.startsWith("zone_debounce:")) {
datalong = atol(input.substring(input.lastIndexOf(':') + 1).c_str());
EEPROM.put(45, datalong);
ZoneIntrusionOpenMilliseconds = datalong;
Serial.print(input + " --> OK");
// How many milliseconds must a zone/s be open for, before the alarm will trigger? - GET
} else if (input.startsWith("zone_debounce:?")) {
EEPROM.get(45, datalong);
Serial.println(input + " --> " + String(datalong));
//////////////////////////////////////
// LED COMMANDS
//////////////////////////////////////
//Should the Armed LED/s be made to flash with the code, or not?
//If using LEDS with built in flash, then this should be set to 0.
} else if (input == "led_flash_armed:0") {
MustFlashArmedLED = false;
EEPROM.update(50, 0);
Serial.print(input + " --> OK");
} else if (input == "led_flash_armed:1") {
MustFlashArmedLED = true;
EEPROM.update(50, 1);
Serial.print(input + " --> OK");
} else if (input == "led_flash_armed:?") {
databyte = EEPROM.read(50);
Serial.print(input + " --> " + databyte);
// How many milliseconds must a zone/s be open for, before the alarm will trigger? - SET
} else if (input.startsWith("zone_debounce:")) {
datalong = atol(input.substring(input.lastIndexOf(':') + 1).c_str());
EEPROM.put(45, datalong);
ZoneIntrusionOpenMilliseconds = datalong;
Serial.print(input + " --> OK");
// How many milliseconds must a zone/s be open for, before the alarm will trigger? - GET
} else if (input.startsWith("zone_debounce:?")) {
EEPROM.get(45, datalong);
Serial.println(input + " --> " + String(datalong));
//////////////////////////////////////
// TEST COMMANDS
//////////////////////////////////////
// Test Siren
} else if (input == "siren:test") {
TestSiren();
Serial.print(input + " --> OK");
// Test Strobe
} else if (input == "strobe:test") {
TestStrobe();
Serial.print(input + " --> OK");
} else if (input == "reboot") {
Reboot();
Serial.print(input + " --> OK");
// Comms test message
// Will broadcast out a message for X iterations with a delay of X between each iteration, useful
// for setting up Lora etc.
// e.g: comms:send_test:Hello world:5:1000
} else if (input.startsWith("comms:send_test:")) {
// Find the positions of the colons
int firstColon = input.indexOf(':'); // after "comms"
int secondColon = input.indexOf(':', firstColon + 1); // after "send_test"
int thirdColon = input.indexOf(':', secondColon + 1); // after "Hello world"
int fourthColon = input.indexOf(':', thirdColon + 1); // after "5"
// Extract the parts
String message = input.substring(secondColon + 1, thirdColon); // "Hello world"
long repeat = input.substring(thirdColon + 1, fourthColon).toInt(); // 5
long delayMs = input.substring(fourthColon + 1).toInt(); // 1000
Serial.println(input + " --> OK");
for (int i = 0; i < repeat; i++) {
Serial.println("TEST: " + message);
Serial.println(repeat);
Serial.print(delayMs);
delay(delayMs);
}
// Comms check
} else if (input == "comms_is_working") {
// Fake check — just say OK for now
Serial.print("OK");
// COMMAND NOT VALID / UNDERSTOOD.
// Unknown command - just echo the text received.
} else {
Serial.print(input + " --> Unknown Command?");
}
}
///////////////////////////////////////////////////////////////////////////////////////
void Reboot()
{
wdt_enable(WDTO_15MS); // Set watchdog to trigger after 15ms
while (true); // Wait for watchdog to reset MCU
}
///////////////////////////////////////////////////////////////////////////////////////
void GetEEPromSettings()
{
byte data;
//Zone 1 Bypass?
data = EEPROM.read(0);
if (data == 255)
{ //EEProm has not yet been set, so default it.
EEPROM.update(0, 0); // set to default 'not bypassed'
IsZone1Bypassed = false;
}
else
{
//Get values from EEPRom.
if(data == 0)
{IsZone1Bypassed = false;}
else
{ IsZone1Bypassed = true;}
}
//Zone 2 Bypass?
data = EEPROM.read(1);
if (data == 255)
{ //EEProm has not yet been set, so default it.
EEPROM.update(1, 0); // set to default 'not bypassed'
IsZone2Bypassed = false;
}
else
{
//Get values from EEPRom.
if(data == 0)
{IsZone2Bypassed = false;}
else
{ IsZone2Bypassed = true;}
}
//Zone 3 Bypass?
data = EEPROM.read(2);
if (data == 255)
{ //EEProm has not yet been set, so default it.
EEPROM.update(2, 0); // set to default 'not bypassed'
IsZone3Bypassed = false;
}
else
{
//Get values from EEPRom.
if(data == 0)
{IsZone3Bypassed = false;}
else
{ IsZone3Bypassed = true;}
}
//Zone 4 Bypass?
data = EEPROM.read(3);
if (data == 255)
{ //EEProm has not yet been set, so default it.
EEPROM.update(3, 0); // set to default 'not bypassed'
IsZone4Bypassed = false;
}
else
{
//Get values from EEPRom.
if(data == 0)
{IsZone4Bypassed = false;}
else
{ IsZone4Bypassed = true;}
}
}
/////////////////////////////////////////////////////////////////////////////
//Records if an alarm occurred to EEPROM.
void LogAlarm(byte zone)
{
AlarmLog log;
log.zone = zone;
log.timeStamp = millis();
EEPROM.put(10, log); // Save to EEPROM
// Convert millis to human-readable time
uint32_t seconds = log.timeStamp / 1000;
uint32_t minutes = seconds / 60;
uint32_t hours = minutes / 60;
seconds %= 60;
minutes %= 60;
//Serial.print("Alarm in Zone ");
//Serial.print(log.zone);
//Serial.print(" at ");
//Serial.print(hours);
//Serial.print("h ");
//Serial.print(minutes);
//Serial.print("m ");
//Serial.print(seconds);
//Serial.println("s since power-up.");
}
/////////////////////////////////////////////////////////////////////////////
String GetLastAlarmLog()
{
struct AlarmLog {
byte zone;
uint32_t timestamp;
};
AlarmLog log;
EEPROM.get(10, log); // Read the full struct starting at address 10
// Check if EEPROM has never been written
if (log.timestamp == 0xFFFFFFFF || log.timestamp == 0 || log.zone == 0xFF || log.zone == 0) {
return "*None*";
}
// Convert to hours, minutes, seconds
uint32_t seconds = log.timestamp / 1000;
uint32_t minutes = seconds / 60;
uint32_t hours = minutes / 60;
seconds %= 60;
minutes %= 60;
// Format as "Zone X at hh:mm:ss"
char buffer[30];
sprintf(buffer, "Zone %d at %02lu:%02lu:%02lu", log.zone, hours, minutes, seconds);
return String(buffer);
}
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//FOR REMOTE CONTROL (ARM/DISARM/PANIC) //////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//Uses RCSwitch library to decode recieved 433Mhz EV1527 encoded signals (My cheap chinese remotes use this)
//which were recieved on our CY33 433Mhz receiver board.
void CheckRemote()
{
// Static debounce state (retains values between calls)
static unsigned long lastCode = 0;
static unsigned long lastSeen = 0;
static bool waitingForRelease = false;
static unsigned long disarmHoldStart = 0; //For panic disarm hold timer, panic can only be silenced if DISARM button held in for 10 secs
const unsigned long WaitTime = 300; //ms delay to consider button has been released again
const unsigned long PanicHoldTime = 10000; //10 seconds hold to disarm during panic
//If data has been recieved on the 433Mhz receiver....
//if (Remote.available())
//{
//Get the data.
//unsigned long code = Remote.getReceivedValue();
//Serial.println(String("Remote Code Received: ") + code);
//If the button has just been pressed.....
//if (!waitingForRelease)
//{
//if (code == REMOTE1_ARM_CODE || code == REMOTE2_ARM_CODE) //If it is an ARM code....
//{
//Serial.println("ARM command received");
// switch(CurrMode)
// {
// case 0: LastArmedMode = 2; ArmSystem(); break; //Disarmed, zones ready
// case 1: break; //Disarmed, but zones not ready.
// case 2: break; //Armed already, alarm not triggered
// case 3: break; //Alarm already, alarm trigerred
// case 4: break; //Armed already, timed out
// case 5: break; //Panic
// case 6: break; //Test Mode
// }
// waitingForRelease = true;
// lastCode = code;
// lastSeen = millis();
//}
//else if (code == REMOTE1_PANIC_CODE || code == REMOTE2_PANIC_CODE) //If it is a PANIC code....
//{
//Serial.println("PANIC command received");
//TriggerPanic(); //Trigger Panic
//waitingForRelease = true;
//lastCode = code;
//lastSeen = millis();
//}
//else if (code == REMOTE1_DISARM_CODE || code == REMOTE2_DISARM_CODE) //If it is a DISARM code....
// {
// Serial.println("DISARM command received");
// if (CurrMode == 5) //If we are in Panic mode....
// {
// disarmHoldStart = millis(); //Start measuring hold duration to see how long Disarm button is held in too silence Panic.
// }
// else
// {
// switch(CurrMode)
// {
// case 0: break; //Disarmed, zones ready
// case 1: break; //Disarmed, but zones not ready.
// case 2: //Armed, alarm not triggered
// if (LastArmedMode == 2) DisarmSystem();
// break;
// case 3: //Alarm, alarm trigerred
// //If Armed with the Remote, disarm, otherwise it was armed with keyswitch so just mute the siren.
// if (LastArmedMode == 2) DisarmSystem();
// else SwitchSirenOff();
// break;
// case 4: //Armed already, timed out
// //If Armed with the Remote, disarm, otherwise it was armed with keyswitch se we cannot disarm.
// if (LastArmedMode == 2) DisarmSystem();
// break;
// case 6: break; //Test Mode.
// }
// }
// waitingForRelease = true;
//lastCode = code;
// lastSeen = millis();
// }
// }
// else
// {
//// Button still being held (code has not changed) — refresh the timeout
// if (code == lastCode)
// {
// lastSeen = millis();
// //If we're in panic mode, check how long button has been held in for if DISARM button is being pressed.
// if ((lastCode == REMOTE1_DISARM_CODE || lastCode == REMOTE2_DISARM_CODE) && CurrMode == 5)
// {
// //Button has been held in long enough to switch off panic.
// if (millis() - disarmHoldStart >= PanicHoldTime)
// {
// Serial.println("DISARM after panic (10s hold) confirmed");
// DisarmSystem();
// waitingForRelease = true;
// lastSeen = millis(); // Still debounce
// disarmHoldStart = 0; // Reset timer
// }
// }
// }
//}
// Remote.resetAvailable();
//}
////Check if button has been released? We assume it has been released if we have recieved no codes for x time.
//if (waitingForRelease && (millis() - lastSeen > WaitTime)) {
//waitingForRelease = false;
// lastCode = 0;
// disarmHoldStart = 0; //Reset panic disarm timer on release
//}
}
Represents Siren
On = Siren On
Off = Siren Off
Armed LED (Should be positioned in Window as visible deterrant)
Off = Disarmed
Code can be set to output a steady HIGH here, or flash.
Momentary Arm/Disarm
(e.g. Remote)
Keyswitch Arm/Disarm
Panic Button 24Hr
Zone 1
Simulate
Zone 2
Simulate
Zone 3
Simulate
Power On
Strobe Output
Used to power external
large strobe lights etc.
Will go on if Alarm triggered and
only go off when Alarm turned off.
Position this LED at Entry Point.
Has 4 Functions:
1. Will be on solid if any zone is not ready.
2. Will flash fast if Alarm was triggered.
3. Will flash fast if Panic triggered.
4. Will flash slow if in Test Mode.
Must be a normal non flash LED.
Zone 2
Ready
Zone 1
Ready
Zone 3
Ready
To drive a large 12V Strobe light,
you can use a 330/390 ohm
base resistor from Arduino Pin to
drive a TIP31C or similar / Relay
Zone 4
Simulate
Zone 4
Ready
Remote button must be held in
for 10 secs to switch off again
Strobe