/* * 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):"}; 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 logReboot(); // 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); chirpAlarm(1); // Chirp the alarm to show system ready. // hardwareTest(100); // IO Pin testing routing (use to check your inputs with hi/lo +(5-12V) sources) // Also checks relays // if there are incoming bytes available // from the server, read them and print them: String httpresponse = ""; String username = ""; } void loop() // Main branch, runs over and over again { if(reader1Count >= 26) { // When tag presented to reader1 (No keypad on this reader) logTagPresent(reader1,1); // write log entry to serial port 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(); } else { // kf you didn't get a connection to the server: Serial.println("connection failed"); } wiegand26.initReaderOne(); // Reset for next tag scan httpresponse = ""; username = ""; } while (client.available()) { httpresponse += (char)client.read(); } if(httpresponse.length()>0) { Serial.println("Response: "); int c = httpresponse.indexOf('AUTH:'); Serial.println(httpresponse.substring(c+1)); username = httpresponse.substring(c+1); httpresponse = ""; Serial.print("User: "); Serial.println(username); Serial.println("End Response"); } // if the server's disconnected, stop the client: if (!client.connected()) { client.stop(); } } // End of loop() void runCommand(long command) { // Run any commands entered at the pin pad. switch(command) { case 0x1: { // If command = 1, deactivate alarm alarmState(0); // Set global alarm level variable armAlarm(0); chirpAlarm(1); break; } case 0x2: { // If command =2, activate alarm with delay. doorUnlock(1); // Set global alarm level variable door1Locked=false; doorClosed=false; // 200 chirps = ~30 seconds delay if((pollAlarm(3) == 0) && (pollAlarm(2) == 0)) { // Do not arm the alarm if doors are open for(byte i=0; i<30; i++) { if((pollAlarm(3) !=0) && doorClosed==false) { // Set door to be unlocked until alarm timeout or user exits lockall(); doorClosed=true; } digitalWrite(ALARMSTROBEPIN, HIGH); delay(500); digitalWrite(ALARMSTROBEPIN, LOW); delay(500); } chirpAlarm(2); armAlarm(1); lockall(); // Lock all doors on exit } else { // Beep the alarm once and exit if attempt made to arm alarm with doors open digitalWrite(ALARMSTROBEPIN, HIGH); delay(500); digitalWrite(ALARMSTROBEPIN, LOW); delay(500); lockall(); // Lock all doors anyway } break; } case 0x3: { doorLock(1); // Set door 2 to stay unlocked, and door 1 to be locked doorUnlock(2); door1Locked=true; door2Locked=false; chirpAlarm(3); break; } case 0x4: // Set doors to remain open { armAlarm(4); doorUnlock(1); doorUnlock(2); door1Locked=false; door2Locked=false; chirpAlarm(4); break; } case 0x5: // Relock all doors { lockall(); chirpAlarm(5); break; } case 0x911: { chirpAlarm(9); // Emergency armAlarm(1); alarmState(1); break; } case 0x20: { // If command = 20, do nothing break; } default: { break; } } } /* Alarm System Functions - Modify these as needed for your application. Sensor zones may be polled with digital or analog pins. Unique reader2 resistors can be used to check more zones from the analog pins. */ void alarmState(byte alarmLevel) { //Changes the alarm status based on this flow logalarmState(alarmLevel); switch (alarmLevel) { case 0: { // If alarmLevel == 0 turn off alarm. digitalWrite(ALARMSIRENPIN, LOW); digitalWrite(ALARMSTROBEPIN, LOW); alarmActivated = alarmLevel; //Set global alarm level variable break; } case 1: { digitalWrite(ALARMSIRENPIN, HIGH); // If alarmLevel == 1 turn on strobe lights and siren // digitalWrite(ALARMSTROBEPIN, HIGH); // Optionally activate yoru strobe/chome alarmSirenTimer=millis(); alarmActivated = alarmLevel; //Set global alarm level variable logalarmTriggered(); break; } case 2: { digitalWrite(ALARMSTROBEPIN, HIGH); alarmActivated = alarmLevel; break; } case 3: { alarmActivated = alarmLevel; break; } /* case 4: { vaporize_intruders(STUN); break; } case 5: { vaporize_intruders(MAIM); } etc. etc. etc. break; */ default: { // Exceptional cases kill alarm outputs digitalWrite(ALARMSIRENPIN, LOW); // Turn off siren and strobe // digitalWrite(ALARMSTROBEPIN, LOW); break; } } if(alarmActivated != EEPROM.read(EEPROM_ALARM)){ // Update eeprom value EEPROM.write(EEPROM_ALARM,alarmActivated); } } //End of alarmState() void chirpAlarm(byte chirps){ // Chirp the siren pin or strobe to indicate events. for(byte i=0; iSENSORTHRESHOLD){ return 1; } else return 0; } void trainAlarm(){ // Train the system about the default states of the alarm pins. armAlarm(0); // Disarm alarm first alarmState(0); int temp[5]={0}; int avg; for(int i=0; i NUMUSERS)) { // Do not write to invalid EEPROM addresses. Serial.print("Invalid user modify attempted."); } else { EEPROM_buffer[0] = byte(tagNumber & 0xFFF); // Fill the buffer with the values to write to bytes 0..4 EEPROM_buffer[1] = byte(tagNumber >> 8); EEPROM_buffer[2] = byte(tagNumber >> 16); EEPROM_buffer[3] = byte(tagNumber >> 24); EEPROM_buffer[4] = byte(userMask); for(int i=0; i<5; i++){ EEPROM.write((offset+i), (EEPROM_buffer[i])); // Store the resulting value in 5 bytes of EEPROM. } Serial.print("User "); Serial.print(userNum,DEC); Serial.println(" successfully modified"); } } void deleteUser(int userNum) // Deletes a user from the local database. { // Users number 0..NUMUSERS int offset = (EEPROM_FIRSTUSER+(userNum*5)); // Find the offset to write this user to logDate(); if((userNum <0) || (userNum > NUMUSERS)) { // Do not write to invalid EEPROM addresses. Serial.print("Invalid user delete attempted."); } else { for(int i=0; i<5; i++){ EEPROM.write((offset+i), 0xFF); // Store the resulting value in 5 bytes of EEPROM. // Starting at offset. } Serial.print("User deleted at position "); Serial.println(userNum); } } int checkUser(unsigned long tagNumber) // Check if a particular tag exists in the local database. Returns userMask if found. { // Users number 0..NUMUSERS // Find the first offset to check unsigned long EEPROM_buffer=0; // Buffer for recreating tagNumber from the 4 stored bytes. int found=-1; logDate(); for(int i=EEPROM_FIRSTUSER; i<=(EEPROM_LASTUSER-5); i=i+5){ EEPROM_buffer=0; EEPROM_buffer=(EEPROM.read(i+3)); EEPROM_buffer= EEPROM_buffer<<8; EEPROM_buffer=(EEPROM_buffer ^ EEPROM.read(i+2)); EEPROM_buffer= EEPROM_buffer<<8; EEPROM_buffer=(EEPROM_buffer ^ EEPROM.read(i+1)); EEPROM_buffer= EEPROM_buffer<<8; EEPROM_buffer=(EEPROM_buffer ^ EEPROM.read(i)); if((EEPROM_buffer == tagNumber) && (tagNumber !=0xFFFFFFFF) && (tagNumber !=0x0)) { // Return a not found on blank (0xFFFFFFFF) entries Serial.print("User "); Serial.print(((i-EEPROM_FIRSTUSER)/5),DEC); Serial.println(" authenticated."); found = EEPROM.read(i+4); return found; } } Serial.println("User not found"); delay(1000); // Delay to prevent brute-force attacks on reader return found; } void dumpUser(byte usernum) // Return information ona particular entry in the local DB { // Users number 0..NUMUSERS unsigned long EEPROM_buffer=0; // Buffer for recreating tagNumber from the 4 stored bytes. if((0<=usernum) && (usernum <=199)){ int i=usernum*5+EEPROM_FIRSTUSER; EEPROM_buffer=0; EEPROM_buffer=(EEPROM.read(i+3)); EEPROM_buffer= EEPROM_buffer<<8; EEPROM_buffer=(EEPROM_buffer ^ EEPROM.read(i+2)); EEPROM_buffer= EEPROM_buffer<<8; EEPROM_buffer=(EEPROM_buffer ^ EEPROM.read(i+1)); EEPROM_buffer= EEPROM_buffer<<8; EEPROM_buffer=(EEPROM_buffer ^ EEPROM.read(i)); Serial.print(((i-EEPROM_FIRSTUSER)/5),DEC); Serial.print("\t"); Serial.print(EEPROM.read(i+4),DEC); Serial.print("\t"); if(DEBUG==2){ Serial.println(EEPROM_buffer,HEX); } else { if(EEPROM_buffer != 0xFFFFFFFF) { Serial.println("********"); } } } else Serial.println("Bad user number!"); } /* Displays a serial terminal menu system for * user management and other tasks */ void readCommand() { byte stringSize=(sizeof(inString)/sizeof(char)); char cmdString[4][9]; // Size of commands (4=number of items to parse, 10 = max length of each) byte j=0; // Counters byte k=0; char cmd=0; char ch; if (Serial.available()) { // Check if user entered a command this round ch = Serial.read(); if( ch == '\r' || inCount >=stringSize-1) { // Check if this is the terminating carriage return inString[inCount] = 0; inCount=0; } else{ (inString[inCount++] = ch); } //Serial.print(ch); // Turns echo on or off if(inCount==0) { for(byte i=0; i=5) && (millis()-consolefailTimer<300000)) // Do not allow priv mode if more than 5 failed logins in 5 minute { PROGMEMprintln(privsAttemptsMessage); break; } if (strtoul(cmdString[1],NULL,16) == PRIVPASSWORD) { consoleFail=0; PROGMEMprintln(privsenabledMessage); privmodeEnabled=true; } else { PROGMEMprintln(privsdisabledMessage); privmodeEnabled=false; if(consoleFail==0) { // Set the timeout for failed logins consolefailTimer=millis(); } consoleFail++; // Increment the login failure counter } break; } //privmodeEnabled=true; //Debugging statement case 'a': { // List whole user database if(privmodeEnabled==true) { logDate(); Serial.println("User dump started."); Serial.print("UserNum:"); Serial.print(" "); Serial.print("Usermask:"); Serial.print(" "); Serial.println("TagNum:"); for(int i=0; i<(NUMUSERS); i++){ dumpUser(i); //Serial.println(); // commented out due to dumpUser now always printing a line -WB 7-7-2011 } } else{logprivFail();} break; } case 's': { // List user if(privmodeEnabled==true) { Serial.print("UserNum:"); Serial.print(" "); Serial.print("Usermask:"); Serial.print(" "); Serial.println("TagNum:"); dumpUser(atoi(cmdString[1])); //Serial.println(); // commented out due to dumpUser always printing a line -WB 7-7-2011 } else{logprivFail();} break; } case 'd': { // Display current time logDate(); Serial.println(); break; } case '1': { // Deactivate alarm if(privmodeEnabled==true) { armAlarm(0); alarmState(0); chirpAlarm(1); } else{logprivFail();} break; } case '2': { // Activate alarm with delay. chirpAlarm(20); // 200 chirps = ~30 seconds delay armAlarm(1); break; } case 'u': { if(privmodeEnabled==true) { alarmState(0); // Set to door chime only/open doors armAlarm(4); doorUnlock(1); doorUnlock(2); door1Locked=false; door2Locked=false; chirpAlarm(3); } else{logprivFail();} break; } case 'l': { // Lock all doors lockall(); chirpAlarm(1); break; } case '3': { // Train alarm sensors if(privmodeEnabled==true) { trainAlarm(); } else{logprivFail();} break; } case '9': { // Show site status PROGMEMprint(statusMessage1); Serial.println(alarmArmed,DEC); PROGMEMprint(statusMessage2); Serial.println(alarmActivated,DEC); PROGMEMprint(statusMessage3); Serial.println(pollAlarm(3),DEC); PROGMEMprint(statusMessage4); Serial.println(pollAlarm(2),DEC); PROGMEMprint(statusMessage5); Serial.println(door1Locked); PROGMEMprint(statusMessage6); Serial.println(door2Locked); break; } case 'o': { if(privmodeEnabled==true) { if(atoi(cmdString[1]) == 1){ alarmState(0); // Set to door chime only/open doors armAlarm(4); doorUnlock(1); // Open the door specified door1locktimer=millis(); break; } if(atoi(cmdString[1]) == 2){ alarmState(0); // Set to door chime only/open doors armAlarm(4); doorUnlock(2); door2locktimer=millis(); break; } Serial.print("Invalid door number!"); } else{logprivFail();} break; } case 'r': { // Remove a user if(privmodeEnabled==true) { dumpUser(atoi(cmdString[1])); deleteUser(atoi(cmdString[1])); } else{logprivFail();} break; } case 'm': { // Add/change a user if(privmodeEnabled==true) { dumpUser(atoi(cmdString[1])); addUser(atoi(cmdString[1]), atoi(cmdString[2]), strtoul(cmdString[3],NULL,16)); dumpUser(atoi(cmdString[1])); } else{logprivFail();} break; } case '?': { // Display help menu PROGMEMprintln(consolehelpMessage1); PROGMEMprintln(consolehelpMessage2); PROGMEMprintln(consolehelpMessage3); PROGMEMprintln(consolehelpMessage4); PROGMEMprintln(consolehelpMessage5); PROGMEMprintln(consolehelpMessage6); break; } default: PROGMEMprintln(consoledefaultMessage); break; } } // End of 'if' statement for Serial.available } // End of 'if' for string finished } // End of function /* 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();}