/* * Open Source RFID Access Controller * * 4/3/2011 v1.32 * Last build test with Arduino v00.21 * Arclight - arclight@23.org * Danozano - danozano@gmail.com * * Notice: This is free software and is probably buggy. Use it at * at your own peril. Use of this software may result in your * doors being left open, your stuff going missing, or buggery by * high seas pirates. No warranties are expressed on implied. * You are warned. * * * For latest downloads, including Eagle CAD files for the hardware, check out * http://code.google.com/p/open-access-control/downloads/list * * Latest update moves strings to PROGMEM to free up memory and adds a * console password feature. * * * This program interfaces the Arduino to RFID, PIN pad and all * other input devices using the Wiegand-26 Communications * Protocol. It is recommended that the keypad inputs be * opto-isolated in case a malicious user shorts out the * input device. * Outputs go to a Darlington relay driver array for door hardware/etc control. * Analog inputs are used for alarm sensor monitoring. These should be * isolated as well, since many sensors use +12V. Note that resistors of * different values can be used on each zone to detect shorting of the sensor * or wiring. * * Version 1.00+ of the hardware implements these features and uses the following pin * assignments on a standard Arduino Duemilanova or Uno: * * Relay outpus on digital pins 6,7,8,9 * DS1307 Real Time Clock (I2C):A4 (SDA), A5 (SCL) * Analog pins (for alarm):A0,A1,A2,A3 * Reader 1: pins 2,3 * Reader 2: pins 4,5 * Ethernet: pins 10,11,12,13 (Not connected to the board, reserved for the Ethernet shield) * * Quickstart tips: * Set the console password(PRIVPASSWORD) value to a numeric DEC or HEX value. * Define the static user list by swiping a tag and copying the value received into the #define values shown below * Compile and upload the code, then log in via serial console at 57600,8,N,1 * */ #include // Needed for I2C Connection to the DS1307 date/time chip #include // Needed for saving to non-voilatile memory on the Arduino. #include // Allows data to be stored in FLASH instead of RAM #include // Ethernet stuff, comment out if not used. #include #include #include #include // DS1307 RTC Clock/Date/Time chip library #include // Wiegand 26 reader format libary #include // Pcint.h implementation, allows for >2 software interupts. /* Static user List - Implemented as an array for testing and access override */ #define DEBUG 2 // Set to 2 for display of raw tag numbers in log files, 1 for only denied, 0 for never. #define will 0xabcdef // Name and badge number in HEX. We are not using checksums or site ID, just the whole #define jeremy 0xabcdef // output string from the reader. #define jacob 0xabcdef const long superUserList[] = { will, jeremy, jacob}; // Super user table (cannot be changed by software) #define PRIVPASSWORD 0x1234 // Console "priveleged mode" password #define DOORDELAY 5000 // How long to open door lock once access is granted. (2500 = 2.5s) #define SENSORTHRESHOLD 100 // Analog sensor change that will trigger an alarm (0..255) #define EEPROM_ALARM 0 // EEPROM address to store alarm triggered state between reboots (0..511) #define EEPROM_ALARMARMED 1 // EEPROM address to store alarm armed state between reboots #define EEPROM_ALARMZONES 20 // Starting address to store "normal" analog values for alarm zone sensor reads. #define KEYPADTIMEOUT 5000 // Timeout for pin pad entry. Users on keypads can enter commands after reader swipe. #define EEPROM_FIRSTUSER 24 #define EEPROM_LASTUSER 1024 #define NUMUSERS ((EEPROM_LASTUSER - EEPROM_FIRSTUSER)/5) //Define number of internal users (200 for UNO/Duemillanova) #define DOORPIN1 relayPins[0] // Define the pin for electrified door 1 hardware #define DOORPIN2 relayPins[2] // Define the pin for electrified door 2 hardware #define ALARMSTROBEPIN relayPins[3] // Define the "non alarm: output pin. Can go to a strobe, small chime, etc #define ALARMSIRENPIN relayPins[1] // Define the alarm siren pin. This should be a LOUD siren for alarm purposes. byte reader1Pins[]={2,3}; // Reader 1 connected to pins 4,5 byte reader2Pins[]= {4,5}; // Reader2 connected to pins 6,7 //byte reader3Pins[]= {10,11}; // Reader3 connected to pins X,Y (Not implemented on v1.x and 2.x Access Control Board) const byte analogsensorPins[] = {0,1,2,3}; // Alarm Sensors connected to other analog pins const byte relayPins[]= {6,7,8,9}; // Relay output pins bool door1Locked=true; // Keeps track of whether the doors are supposed to be locked right now bool door2Locked=true; unsigned long door1locktimer=0; // Keep track of when door is supposed to be relocked unsigned long door2locktimer=0; // after access granted. boolean doorChime=false; // Keep track of when door chime last activated boolean doorClosed=false; // Keep track of when door last closed for exit delay unsigned long alarmDelay=0; // Keep track of alarm delay. Used for "delayed activation" or level 2 alarm. unsigned long alarmSirenTimer=0; // Keep track of how long alarm has gone off unsigned long consolefailTimer=0; // Console password timer for failed logins byte consoleFail=0; #define numUsers (sizeof(superUserList)/sizeof(long)) //User access array size (used in later loops/etc) #define NUMDOORS (sizeof(doorPin)/sizeof(byte)) #define numAlarmPins (sizeof(analogsensorPins)/sizeof(byte)) //Other global variables byte second, minute, hour, dayOfWeek, dayOfMonth, month, year; // Global RTC clock variables. Can be set using DS1307.getDate function. byte alarmActivated = EEPROM.read(EEPROM_ALARM); // Read the last alarm state as saved in eeprom. byte alarmArmed = EEPROM.read(EEPROM_ALARMARMED); // Alarm level variable (0..5, 0==OFF) boolean sensor[4]={false}; // Keep track of tripped sensors, do not log again until reset. unsigned long sensorDelay[2]={0}; // Same as above, but sets a timer for 2 of them. Useful for logging // motion detector hits for "occupancy check" functions. // Enable up to 3 door access readers. volatile long reader1 = 0; volatile int reader1Count = 0; volatile long reader2 = 0; volatile int reader2Count = 0; int userMask1=0; int userMask2=0; boolean keypadGranted=0; // Variable that is set for authenticated users to use keypad after login //volatile long reader3 = 0; // Uncomment if using a third reader. //volatile int reader3Count = 0; unsigned long keypadTime = 0; // Timeout counter for reader with key pad unsigned long keypadValue=0; // Serial terminal buffer (needs to be global) char inString[40]={0}; // Size of command buffer (<=128 for Arduino) byte inCount=0; boolean privmodeEnabled = false; // Switch for enabling "priveleged" commands // Enter a MAC address and IP address for your controller below. // The IP address will be dependent on your local network: byte mac[] = { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED }; byte ip[] = { 10,1,1,2 }; byte server[] = { 10,1,1,1 }; // hsl-access // Initialize the Ethernet client library // with the IP address and port of the server // that you want to connect to (port 80 is default for HTTP): Client client(server, 80); /* Create an instance of the various C++ libraries we are using. */ DS1307 ds1307; // RTC Instance WIEGAND26 wiegand26; // Wiegand26 (RFID reader serial protocol) library PCATTACH pcattach; // Software interrupt library /* Set up some strings that will live in flash instead of memory. This saves our precious 2k of * RAM for something else. */ const prog_uchar rebootMessage[] PROGMEM = {"Access Control System rebooted."}; const prog_uchar doorChimeMessage[] PROGMEM = {"Front Door opened."}; const prog_uchar doorslockedMessage[] PROGMEM = {"All Doors relocked"}; const prog_uchar alarmtrainMessage[] PROGMEM = {"Alarm Training performed."}; const prog_uchar privsdeniedMessage[] PROGMEM = {"Access Denied. Priveleged mode is not enabled."}; const prog_uchar privsenabledMessage[] PROGMEM = {"Priveleged mode enabled."}; const prog_uchar privsdisabledMessage[] PROGMEM = {"Priveleged mode disabled."}; const prog_uchar privsAttemptsMessage[] PROGMEM = {"Too many failed attempts. Try again later."}; const prog_uchar consolehelpMessage1[] PROGMEM = {"Valid commands are:"}; const prog_uchar consolehelpMessage2[] PROGMEM = {"(d)ate, (s)show user, (m)odify user "}; const prog_uchar consolehelpMessage3[] PROGMEM = {"(a)ll user dump,(r)emove_user ,(o)open door "}; const prog_uchar consolehelpMessage4[] PROGMEM = {"(u)nlock all doors,(l)lock all doors"}; const prog_uchar consolehelpMessage5[] PROGMEM = {"(1)disarm_alarm, (2)arm_alarm,(3)train_alarm (9)show_status"}; const prog_uchar consolehelpMessage6[] PROGMEM = {"(e)nable - enable or disable priveleged mode"}; const prog_uchar consoledefaultMessage[] PROGMEM = {"Invalid command. Press '?' for help."}; const prog_uchar statusMessage1[] PROGMEM = {"Alarm armed state (1=armed):"}; const prog_uchar statusMessage2[] PROGMEM = {"Alarm siren state (1=activated):"}; const prog_uchar statusMessage3[] PROGMEM = {"Front door open state (0=closed):"}; const prog_uchar statusMessage4[] PROGMEM = {"Roll up door open state (0=closed):"}; const prog_uchar statusMessage5[] PROGMEM = {"Door 1 unlocked state(1=locked):"}; const prog_uchar statusMessage6[] PROGMEM = {"Door 2 unlocked state(1=locked):"}; // strings for storing results from web server String httpresponse = ""; String username = ""; bool authorized = false; bool relay1engaged = false; void setup(){ // Runs once at Arduino boot-up Wire.begin(); // start Wire library as I2C-Bus Master /* Attach pin change interrupt service routines from the Wiegand RFID readers */ pcattach.PCattachInterrupt(reader1Pins[0], callReader1Zero, CHANGE); pcattach.PCattachInterrupt(reader1Pins[1], callReader1One, CHANGE); pcattach.PCattachInterrupt(reader2Pins[1], callReader2One, CHANGE); pcattach.PCattachInterrupt(reader2Pins[0], callReader2Zero, CHANGE); //Clear and initialize readers wiegand26.initReaderOne(); //Set up Reader 1 and clear buffers. wiegand26.initReaderTwo(); //Initialize output relays for(byte i=0; i<4; i++){ pinMode(relayPins[i], OUTPUT); digitalWrite(relayPins[i], LOW); // Sets the relay outputs to LOW (relays off) } ds1307.setDateDs1307(0,49,1,3,7,6,11); /* Sets the date/time (needed once at commissioning) byte second, // 0-59 byte minute, // 0-59 byte hour, // 1-23 byte dayOfWeek, // 1-7 byte dayOfMonth, // 1-28/29/30/31 byte month, // 1-12 byte year); // 0-99 */ Serial.begin(57600); // Set up Serial output at 8,N,1,57600bps // start the Ethernet connection: Ethernet.begin(mac, ip); // start the serial library: //Serial.begin(9600); // give the Ethernet shield a second to initialize: //delay(1000); // hardwareTest(100); // IO Pin testing routing (use to check your inputs with hi/lo +(5-12V) sources) // Also checks relays } void loop() // Main branch, runs over and over again { ////////////////////////// // Reader input/authentication section ////////////////////////// if(reader1Count >= 26) { // When tag presented to reader1 (No keypad on this reader) Serial.println("connecting..."); // if you get a connection, report back via serial: if (client.connect()) { Serial.println("connected"); Serial.print("GET /~access/access?device=laser&id="); Serial.print(reader1, HEX); Serial.println(" HTTP/1.0"); Serial.println(); client.print("GET /~access/access?device=laser&id="); client.print(reader1, HEX); client.println(" HTTP/1.0"); client.println(); // reset values coming from http httpresponse = ""; username = ""; authorized = false; } else { // kf you didn't get a connection to the server: Serial.println("connection failed"); } wiegand26.initReaderOne(); // Reset for next tag scan } while (client.available()) { char thisChar = client.read(); // only fill up httpresponse with data after a ^ sign. if (httpresponse.charAt(0) == '^' || thisChar == '^') { httpresponse += thisChar; } } if(!client.available() && httpresponse.length()>0) { Serial.println("Response: "); Serial.println(httpresponse); int c = httpresponse.indexOf('^'); int d = httpresponse.indexOf('|'); int e = httpresponse.indexOf('$'); Serial.print("IndexOf:"); Serial.println(c); Serial.println(d); Serial.println("SubStr:"); Serial.println(httpresponse.substring(c+1,d)); username = httpresponse.substring(c+1,d); Serial.print("User: "); Serial.println(username); Serial.println("SubStr:"); Serial.println(httpresponse.substring(d+1,e)); if(httpresponse.substring(d+1,e) == "OK") { authorized = true; } Serial.print("Auth: "); Serial.println(authorized); Serial.println("End Response"); httpresponse = ""; } // if the server's disconnected, stop the client: if (!client.connected()) { client.stop(); } ////////////////////////// // Normal operation section ////////////////////////// if(authorized) { } } // End of loop() /* Access System Functions - Modify these as needed for your application. These function control lock/unlock and user lookup. */ int checkSuperuser(long input){ // Check to see if user is in the user list. If yes, return their index value. int found=-1; for(int i=0; i<=numUsers; i++){ if(input == superUserList[i]){ Serial.print("Superuser "); Serial.print(i,DEC); Serial.println(" found."); found=i; return found; } } return found; //If no, return -1 } void doorUnlock(int input) { //Send an unlock signal to the door and flash the Door LED byte dp=1; if(input == 1) { dp=DOORPIN1; } else(dp=DOORPIN2); digitalWrite(dp, HIGH); Serial.print("Door "); Serial.print(input,DEC); Serial.println(" unlocked"); } void doorLock(int input) { //Send an unlock signal to the door and flash the Door LED byte dp=1; if(input == 1) { dp=DOORPIN1; } else(dp=DOORPIN2); digitalWrite(dp, LOW); Serial.print("Door "); Serial.print(input,DEC); Serial.println(" locked"); } /* Wrapper functions for interrupt attachment Could be cleaned up in library? */ void callReader1Zero(){wiegand26.reader1Zero();} void callReader1One(){wiegand26.reader1One();} void callReader2Zero(){wiegand26.reader2Zero();} void callReader2One(){wiegand26.reader2One();} void callReader3Zero(){wiegand26.reader3Zero();} void callReader3One(){wiegand26.reader3One();}