RFID Card reader - Wiegand


  • Hero Member

    An universal RFID reader using the Wiegand protocol.
    0_1478440526897_upload-fed74ddb-acd8-40b9-8431-c4e2ffd93fdd

    a few features:

    • Can add/ delete/ list rfid codes (with hardcoded master rfid tag)
    • 4 digit 7 segment display
    • Store rfid codes in EEPROM (AVR) (be aware - limited room available)
    • Reports status log to MySensors controller
    • Stores Rfid tags in local memory and presents them as switches to the controller
      ā—‹ Switch on - enable / switch off - disable
    • Communicates via RS485 for security (not necessarily)

    A nice commercial waterproof sealed wiegand rfid reader with buzzer and led can be found for a few bucks. The Wiegand protocol is a well established standard for card reader communication and can be interfaced easily with an Arduino. The interface of this type uses 5V logic but the reader needs 9-12V to function.
    0_1478440971635_upload-1c0ad37e-9999-4453-b720-94bd31802d55 The back side shows the wiring: Wiegand wires are D0 and D1. I also use the LED and BEEP to get some feedback to the user. (the WG26/WG34 can be used to switch between 26 and 34 bit protocol, the "door bell" wires are not even there šŸ˜• )

    A few pictures of hardware. All pretty straightforward connections.
    0_1478441720110_upload-afbf23c8-c964-42b4-864a-7be340b94e7a

    There is a 7-segment display which has a simple interface and a lot of functionality.
    0_1478441768013_upload-e1bd62ba-bb00-42c7-b1b9-2844818a0d7a
    Connected to a RS485 test setup.

    0_1478441948792_upload-15b743bf-7835-4995-a286-ec0a0568f9de 0_1478441975194_upload-c63fd66a-0f41-4a8c-a838-31180d047a92 0_1478442059886_upload-86ede3d5-0303-40bf-b9c4-2cba19295c26 0_1478442105543_upload-cdbcead9-07aa-4359-b246-bc1836a29098 0_1478442141295_upload-a817f39a-64ce-4114-b517-16486ea6ed4d
    And the display showing the presented rfid tag 1 & 2, inclusion mode, deletion mode and error.

    The whole project was meant to play with a FiniteStateMachine machine library/ class, which worked out well (for me šŸ˜ ). This class lets you define "states" for which you create entry, update and exit functions. Worth a try... You can find the sketch and libraries on git. All libraries used are put in the sketch folder.

    more reading in the main sketch

    /*
     PROJECT: MySensors / Wiegand card reader with display
     PROGRAMMER: AWI
     DATE: 20160920 last update: 20161020
     FILE: AWI_Cardreader.ino
     LICENSE: Public domain
    
     Hardware: ATMega328p board w/ RS485
    	and MySensors 2.0 
    		
    Special:
    	
    Summary:
    	Wiegand reader - universal card reader
    	4 7-segment display
    	RS485 interface with MySensors (wired for security, cardID's are sent to controller)
    	
    	1. A local database with Card id's is kept in the node. Card id 0 is the "master master" card. With the master card you can:
    	- Open the door lock (just present it)
    	- Include other cards (present it twice and present the new card)
    	- Delete cards (present it three times, present the card to be deleted and confirm with master card)
    	- Browse the cards (present it four times), the display shows the card indexes with their status.
    
    	2. Any registered card opens the lock for a short time. A non registered card shows "Err"
    	
    	3. A new (included) card is presented to the controller as a switch with "On" status and a node-id which is the same as the card index. The CardID is added as text
    	
    	4. A card deletion will set the corresponding switch to off (to be implemented = switching a card to off in the controller will delete the card)
    	
    	5. The card "browse" function will (re)"present" all the activated cards (again) to the controller
    	
    	
    Remarks:
    	Fixed node-id
    	State machine based on FiniteStateMachine library
    	
    Change log:
    20160920 - created
    20161020 - updated to include MySensors V_TEXT status log. Log should be kept by controller
    20161023 - clean & comment code
    */
    #define MY_NODE_ID 10
    #define NODE_TXT "Cardreader 10"					// Text to add to sensor name
    //#define MY_PARENT_NODE_ID 3						// fixed parent to controller when 0 (else comment out = AUTO)
    #define MY_DEBUG 									// Enable MySensors debug to serial
    #define MY_PARENT_NODE_ID 0							// define if fixed parent
    #define MY_PARENT_NODE_IS_STATIC
    #undef MY_REGISTRATION_FEATURE						// sketch moves on if no registration
    #define MY_TRANSPORT_DONT_CARE_MODE					// transport connection to Gateway not essential/ needs to work without
    
    // Enable RS485 transport layer
    #define MY_RS485
    #define MY_RS485_DE_PIN 4							// Enables DE-pin management 
    #define MY_RS485_BAUD_RATE 19200					// Set RS485 baud rate (max determined by AltsoftSerial)
    // or use radio:
    //#define MY_RADIO_NRF24							// Enable and select radio type attached
    //#define MY_BAUD_RATE 9600
    
    #include <SPI.h>
    #include <MySensors.h>
    
    #include "CardDB.h"									// AWI: local lib to store cards
    #include "Wiegand.h"								// Wiegand protocol lib https://github.com/monkeyboard/Wiegand-Protocol-Library-for-Arduino
    #include "FiniteStateMachine.h"						// FiniteStateMachine https://github.com/gusgonnet/particle-fsm/tree/master/firmware
    #include "LedFlash.h"								// AWI: non blocking class for flexible LED/ buzzer 
    #include "SevenSegmentTM1637.h"						// 4 digit 7 segment display https://github.com/bremme/arduino-tm1637
    
    // helpers
    #define LOCAL_DEBUG
    #ifdef LOCAL_DEBUG
    	#define Sprint(...) (Serial.print( __VA_ARGS__))				// macro's as substitute for print,println 
    	#define Sprintln(...) (Serial.println( __VA_ARGS__))
    #else
    	#define Sprint(...)
    	#define Sprintln(...)
    #endif
    //** door lock 
    const byte DOORLOCK = 5 ;
    //** LedFlash lib used for the Buzzer and Led on the cardreader */
    const byte LED_PIN = 6;												// status led
    const byte BEEP_PIN = 7 ; 											// beep
    //** LED Display connections (library serial protocol)
    const byte PIN_CLK = A4;   											// define CLK pin (any digital pin)
    const byte PIN_DIO = A5;   											// define DIO pin (any digital pin)
    //** MySensors children
    const byte CARD_CHILD = 0 ; 										// MySensors master card child (rest of cards are dynamic)
    const byte CARD_ID_CHILD = 1 ; 										// MySensors card id/ log sensor 
    
    const unsigned long MASTERCARD = xxxxxx ;							// Hardcoded MASTERCARD, insert your master card number
    
    // Instantiate library objects
    SevenSegmentTM1637    display(PIN_CLK, PIN_DIO);					// LED display
    LedFlash statusLed(LED_PIN,true, 50, 400);							// status led (active on, flash on 50ms/ period 400ms )
    LedFlash statusBeep(BEEP_PIN,true, 2, 400);							// buzzer (active on, flash on 2ms/ period 400ms )
    CardDB cardDB ; 													// EEPROM database routines
    WIEGAND wg;															// instantiate Wiegand
    
    // state machine definitions (&routines need to be defined)
    FState idleState( &idleEnter, &idleUpdate, NULL );  				// Idle state (doe not need exit routine)
    FState delayState( &delayEnter, &delayUpdate, &delayExit);	  		// delay after invalid card
    FState unlockState( &unlockEnter, &unlockUpdate, &unlockExit);  	// Unlocks after valid card
    FState includeState( &includeEnter, &includeUpdate, &includeExit);  // waiting for inclusion of new card
    FState deleteState( &deleteEnter, &deleteUpdate, &deleteExit);  	// deletion of card
    FState confirmState( &confirmEnter, &confirmUpdate, &confirmExit);  // confirmation of deletion
    FState browseState( &browseEnter, &browseUpdate, &browseExit);  	// browse cards
    FiniteStateMachine stateMachine(idleState) ; 						//initialize state machine, start in state: noop
    
    const unsigned long idleTime = 2000UL ;								// delay to return to idle
    unsigned long browseTimer = millis() ;								// timer for browsing
    const unsigned long browseTime = 800UL ;							// detay for browsing
    
    unsigned long heartbeat = 60000UL ;									// heartbeat every hour
    unsigned long lastHeartbeat = millis() ; 
    
    unsigned long lastUpdate = millis(); 								// timer value
    
    
    unsigned long lastCardID = 0 ;										// holds last card value for inclusion / deletion
    int curCard = 0 ;													// Used as a browse pointer and temp store for deletion/ inclusion
    bool newCard = false ;												// global to indicate new card is available
    
    // MySensor messages
    MyMessage cardStatusMsg(0,V_STATUS);								// Each card id has its own "Switch", which is presented at inclusion
    MyMessage cardIdMsg(0,V_TEXT);										// Each card id has its own identifier, sent a text to controller 
    
    
    void setup() {
    	pinMode(DOORLOCK, OUTPUT) ;										// doorlock connection
    	display.begin();												// initializes the display
    	display.setBacklight(10);										// set the brightness to x %
    	display.print("INIT");											// display INIT on the display
    	wait(1000) ;
    	wg.begin();														// activate wiegand
    	lastUpdate = millis() ;
    	cardDB.initDB();												// ONLY in the first run to clear the EEPROM store (comment later)
    	cardDB.writeCardIdx(0, MASTERCARD);								// MASTER card (HARD CODED)
    	cardDB.setCardTypeIdx(0, CardDB::masterCard) ;					// write to 0 index in database
    	Sprint("EEPROM: ");
    	Sprintln(EEPROM_LOCAL_CONFIG_ADDRESS, HEX) ;
    }
    
    void presentation(){
    	sendSketchInfo("AWI " NODE_TXT, "1.2");							// Sketch version to gateway and Controller
    	presentCard(CARD_CHILD) ;										// present the master card (index == 0)
    	present(CARD_ID_CHILD, S_INFO, "SwitchID " NODE_TXT);			// present the log child
    }
    
    void loop() {
        unsigned long now = millis();									// timer value for "once in a while" events
        if (now-lastUpdate > 5000UL) {									// change after 5 seconds
    		//cardDB.printDB() ;										// only for debug
    		lastUpdate = now;
    	}
    	newCard = wg.available() ;
    	if(newCard){
    		Sprint("Wiegand HEX = ");
    		Sprint(wg.getCode(),HEX);
    		Sprint(", DECIMAL = ");
    		Sprint(wg.getCode());
    		Sprint(", Type W ");
    		Sprintln(wg.getWiegandType());
    		//sendLog(wg.getCode(), "presented");
    	}
    	stateMachine.update();											// check and update non blocking
    	statusLed.update() ;
    	statusBeep.update() ;
    	}
    
    	
    /* 	State machine
    **	Each state can have functions for enter/ update / exit (defined earlier)
    */
    
    //** ILDE tate **//
    void idleEnter() {Sprintln(" idle enter") ;
    	statusLed.off() ;
    	statusBeep.off() ;
    	display.clear();													// emptdisplay
    }
    void idleUpdate(){
    	if (newCard){
    		curCard = cardDB.readCard(wg.getCode()) ;						
    		if(curCard != cardDB.maxCards){									// card found
    			if(cardDB.readCardTypeIdx(curCard) == CardDB::masterCard || cardDB.readCardTypeIdx(curCard) == CardDB::idCard) { // only open if id or master card
    				stateMachine.transitionTo(unlockState);
    			} else if (cardDB.readCardTypeIdx(curCard) == CardDB::delCard){// card found but deleted
    				display.print("Errd");
    				sendLog(wg.getCode(), "Deleted Card");
    				stateMachine.transitionTo(delayState);
    			}
     		} else {														// card not found
    			display.print("Err");
    			sendLog(wg.getCode(), "Unknown Card");
    			stateMachine.transitionTo(delayState);
    		}
    	}
    }
    
    //** DELAY state **//
    void delayEnter() {	Sprintln(" delay enter") ;
    }
    void delayUpdate() {
    	if (stateMachine.timeInCurrentState() > idleTime){
    		Sprintln(" to idle") ; stateMachine.transitionTo(idleState);
    	}
    }
    void delayExit(){Sprintln(" delay exit") ;}
    
    //** UNLOCK state **//
    void unlockEnter() {Sprintln(" unlock enter") ;
    	display.print(curCard);												// display Card index (curCard) on the display
    	Sprintln("Door unlocked");
    	sendLog(wg.getCode(), "Unlocked");
    	lockDoor(false) ; 													// Unlock the door
    	send(cardStatusMsg.setSensor(curCard).set(1));						// send update for sensor (card) to show its usage.
    }
    void unlockUpdate() {
    	if (stateMachine.timeInCurrentState() > idleTime){
    		Sprintln(" to idle") ;
    		stateMachine.transitionTo(idleState);
    		}
    	if (newCard){														// new tag
    		if(cardDB.readCardType(wg.getCode()) == CardDB::masterCard){	// master card, prepare for inclusion
    			Sprintln(" to include") ;
    			stateMachine.transitionTo(includeState);
    		}
    	}
    }
    void unlockExit(){Sprintln(" unlock exit") ;
    	lockDoor(true) ; 													// Lock the door
    	Sprintln("Door locked");
    }
    
    //** INCLUDE state
    void includeEnter() {
    	Sprintln(" include enter") ;
    	display.print("Incl");
    	statusLed.flash() ;
    	statusBeep.flash() ;
    };
    void includeUpdate(){
    	if (stateMachine.timeInCurrentState() > idleTime){
    		Sprintln(" to idle") ;
    		stateMachine.transitionTo(idleState);
    	}
    	if (newCard){														// new tag
    		curCard = cardDB.readCard(wg.getCode()) ;
    		if (curCard != cardDB.maxCards){								// known card
    			if(cardDB.readCardTypeIdx(curCard) == CardDB::masterCard){	//  master card, goto delete state
    				Sprintln(" to delete") ;
    				stateMachine.transitionTo(deleteState);
    			} else {													// known other card, so only change card type
    				cardDB.setCardTypeIdx(curCard, CardDB::idCard) ;					
    				display.clear(); display.print(curCard);
    				sendLog(wg.getCode(), "re-included");
    				presentCard(curCard); 									// present the new card as a switch and switch on
    				Sprintln(" to delay") ; stateMachine.transitionTo(delayState); // delay only to extend display time
    			}
    		} else {														// unknown card, so add in empty spot
    			curCard = cardDB.writeCard(wg.getCode()) ;
    			if(curCard == cardDB.maxCards){								//  if maxCards, database full
    				display.clear(); display.print("Full") ;
    				sendLog(wg.getCode(), "DB full");
    				Sprintln(" to delay") ; stateMachine.transitionTo(delayState); // delay only to extend display time
    			} else {													// include 
    				display.clear(); display.print(curCard);
    				sendLog(wg.getCode(), "included");
    				presentCard(curCard); 									// present the new card as a switch and switch on
    				Sprintln(" to delay") ; stateMachine.transitionTo(delayState); // delay only to extend display time
    			}
    		}
    	}
    }
    void includeExit(){
    	Sprintln(" include exit") ;
    	statusLed.off() ;
    	statusBeep.off() ;
    	}
    
    //** DELETE state
    void deleteEnter() {
    	Sprintln(" delete enter") ;
    	display.print("Del");
    	statusLed.flash() ;
    	statusBeep.flash() ;
    };
    void deleteUpdate(){
    	if (stateMachine.timeInCurrentState() > idleTime){
    		Sprintln(" to idle") ; stateMachine.transitionTo(idleState);
    	}
    	if (newCard) {
    		if(cardDB.readCardType(wg.getCode()) == CardDB::masterCard){	//  master card, goto Browse
    			Sprintln(" to browse") ;
    			stateMachine.transitionTo(browseState);
    		}else {	
    			lastCardID = wg.getCode() ;									// store code for deletion
    			Sprintln(" to Confirm") ; stateMachine.transitionTo(confirmState);
    		}
    	}
    }
    void deleteExit() {	Sprintln(" delete exit") ;}
    
    //** CONFIRM state
    void confirmEnter() { Sprintln(" confirm enter") ;
    	display.print("Conf");
    	statusLed.flash() ;
    	statusBeep.flash() ;
    }
    void confirmUpdate(){
    	if (stateMachine.timeInCurrentState() > idleTime){
    		Sprintln(" to idle") ;
    		stateMachine.transitionTo(idleState);
    	}
    	if (newCard) {
    		if(cardDB.readCardType(wg.getCode()) == CardDB::masterCard){	//  master card means confirmed
    			Sprint("Delete Card: "); Sprintln(lastCardID) ;
    			cardDB.deleteCard(lastCardID);								// delete card (lib takes care of presence)
    			sendLog(wg.getCode(), "deleted");
    			send(cardStatusMsg.setSensor(lastCardID).set(0));			// switch controller status to "off"
    			Sprintln(" to idle") ; stateMachine.transitionTo(idleState);
    		}
    	}
    }
    void confirmExit() {Sprintln(" confirm exit") ;}
    
    //** BROWSE state
    void browseEnter() {Sprintln(" browse enter") ;
    	display.print("Brws");
    	curCard = 0 ;
    	browseTimer = millis() ;
    	statusLed.off() ;
    	statusBeep.off() ;
    
    }
    void browseUpdate(){
    	unsigned long now = millis() ;
    	if (newCard) {
    		if(cardDB.readCardType(wg.getCode()) == CardDB::masterCard){	//  master card, return to IDLE
    			Sprintln(" to idle") ; stateMachine.transitionTo(idleState);
    		}
    	}
    	if (now >= browseTimer + browseTime){
    		Sprint(" browse id: ") ; Sprintln(curCard);
    		browseTimer = now ;
    		display.clear();
    		display.print(curCard);
    		display.setCursor(0,2) ;
    		if(cardDB.readCardTypeIdx(curCard)== CardDB::masterCard){ 
    			display.print("ma");
    			presentCard(curCard); 									// present the cards again when browsing (to sync controller)
    		} else if(cardDB.readCardTypeIdx(curCard)== CardDB::idCard){ 
    			display.print("id");
    			presentCard(curCard);
    		} else if(cardDB.readCardTypeIdx(curCard)== CardDB::delCard){ 
    			display.print("dl");
    			presentCard(curCard);
    		} else if(cardDB.readCardTypeIdx(curCard)== CardDB::noCard){ display.print("no");}
    		if (++curCard > cardDB.maxCards){								// idle if end of database
    			Sprintln(" to idle") ; stateMachine.transitionTo(idleState);
    		}
    	}
    }
    void browseExit(){Sprintln(" browse exit") ;}
    //** end of stae machine **//
    
    // lock / unlock the door
    void lockDoor(bool doorState){
    	digitalWrite(DOORLOCK, doorState?HIGH:LOW) ;						// Adapt for active low of any other door unlock
    }
    
    // sends a log message to the controller containing a message and cardID
    void sendLog(unsigned long cardID, const char logMessage[]){
    	char tmpBuf[26] ;													// temporary store for message
    	sprintf(tmpBuf, "Card %8lu %-10s", cardID, logMessage);				// sends unsigned long and message (be aware of message length)
    	Sprintln(tmpBuf) ;
    	send(cardIdMsg.setSensor(CARD_ID_CHILD).set(tmpBuf));
    }
    
    // present a (new) card to the controller by presenting it and switch it to state (master, id = On, deleted = Off)
    void presentCard(byte cardIdx){
    	char tmpBuf[26] ;													// temporary store for message
    	sprintf(tmpBuf, "CardId %8lu", cardDB.readCardIdIdx(cardIdx));		// convert the cardID to text
    	Sprintln(tmpBuf) ;
    	present(cardIdx, S_BINARY, tmpBuf) ;								// present the (new) card (idx == child) to controller
    	wait(50) ;															// give it some time to settle
    	send(cardStatusMsg.setSensor(cardIdx).set(cardDB.readCardTypeIdx(cardIdx)==CardDB::delCard?0:1)); // switch according to type
    }
    
    // Handle incoming messages, remote card i.e. disable/ enable
    void receive(const MyMessage &message) {  								// Expect few types of messages from controller
    	if (message.type == V_STATUS){										// Switch "off" messages are handled as deletions
    		if (message.sensor < cardDB.maxCards && message.sensor > 0){	// take care of non existing sensors and master
    			cardDB.setCardTypeIdx( message.sensor, message.getBool()?CardDB::idCard:CardDB::delCard) ;	// set type according to payload
    		}
    	}
    }
    


  • @AWI
    Dear AWI! Good work!
    I used ready solutions of control of access in the practice.
    Now I study Arduino and want to try to repeat your experiment.
    I have a question to you: tell please what contains in the CardDB.h file?
    As for the reader, for activation of the beeper and a led you use transistor assemblies in addition? Connecting +5 volts on them with Arduino, as on the scheme, for example:
    0_1480168494166_5-12V.png


  • Hero Member

    @Threestars Nice to hear you are interested. The CardDB.h /.cpp contain the routines for accessing the "database" of RFID codes. I "abstracted" is to use different kinds of database media (EEPROM/ SD card / ..)

    There is not need to level shift the output. The RFID reader works with 5v.



  • @AWI
    Dear AWI,
    I can ask you to print out the sketch CardDB.h /.cpp? I'm afraid it isn't rather skilled to write independently it. Thank You.


  • Hero Member

    @Threestars I thought I had posted all the libraries on git but forgot to provide the link...
    All the classes except the SevenSegmentTM1637 are placed in the sketch folder..



  • @AWI
    Dear AWI, thank you very much, I will try!šŸ˜ƒ



  • So I love this sketch, except I'm running into an issue. My controller isn't recognizing new included cards. Only the master card. How do I rectify this? I'd like an easy way to controll when a particular card has access so I'm using the switch it creates to trigger scenes



  • Do I need to use mastercard to enable browse mode while scanning for new devices?


  • Mod

    @ross-shirer you could try pinging @AWI but it has a been a while since he was last online here



  • @ross-shirer Thanks for bring this topic alive as I had not seen this before and it was just what i was looking for the basis of another project.

    I have thrown together a quick test node and it is working all fine with openHAB.

    The instructions for use are included within the program Summary block.

    
    	1. A local database with Card id's is kept in the node. Card id 0 is the "master master" card. With the master card you can:
    	- Open the door lock (just present it)
    	- Include other cards (present it twice and present the new card)
    	- Delete cards (present it three times, present the card to be deleted and confirm with master card)
    	- Browse the cards (present it four times), the display shows the card indexes with their status.
    
    

    So the MasterCard is needed to enable the other cards. Swipe the master card twice and then swipe the new card.

    Remember to comment the eprom clear command after first run otherwise the non MasterCard data will be lost on reboot. As i did not do that at first, so caused a few minutes delay.

    	//cardDB.initDB();		// ONLY in the first run to clear the EEPROM store (comment later)
    

    Working fine for me with nine cards, tenth card unlocks ok but report status is incorrect.
    More than ten are not accepted by the program due to EPROM size limits. (time to add serial memory)


 

432
Online

7.9k
Users

8.8k
Topics

94.3k
Posts