Skip to content
  • MySensors
  • OpenHardware.io
  • Categories
  • Recent
  • Tags
  • Popular
Skins
  • Light
  • Brite
  • Cerulean
  • Cosmo
  • Flatly
  • Journal
  • Litera
  • Lumen
  • Lux
  • Materia
  • Minty
  • Morph
  • Pulse
  • Sandstone
  • Simplex
  • Sketchy
  • Spacelab
  • United
  • Yeti
  • Zephyr
  • Dark
  • Cyborg
  • Darkly
  • Quartz
  • Slate
  • Solar
  • Superhero
  • Vapor

  • Default (No Skin)
  • No Skin
Collapse
Brand Logo
  1. Home
  2. My Project
  3. RFID Card reader - Wiegand

RFID Card reader - Wiegand

Scheduled Pinned Locked Moved My Project
10 Posts 5 Posters 8.4k Views 8 Watching
  • Oldest to Newest
  • Newest to Oldest
  • Most Votes
Reply
  • Reply as topic
Log in to reply
This topic has been deleted. Only users with topic management privileges can see it.
  • AWIA Offline
    AWIA Offline
    AWI
    Hero Member
    wrote on last edited by AWI
    #1

    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 :confused: )

    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 :smirk: ). 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
    		}
    	}
    }
    
    ThreestarsT 1 Reply Last reply
    6
    • AWIA AWI

      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 :confused: )

      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 :smirk: ). 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
      		}
      	}
      }
      
      ThreestarsT Offline
      ThreestarsT Offline
      Threestars
      wrote on last edited by
      #2

      @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

      AWIA 1 Reply Last reply
      0
      • ThreestarsT Threestars

        @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

        AWIA Offline
        AWIA Offline
        AWI
        Hero Member
        wrote on last edited by
        #3

        @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.

        ThreestarsT 1 Reply Last reply
        0
        • AWIA AWI

          @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.

          ThreestarsT Offline
          ThreestarsT Offline
          Threestars
          wrote on last edited by
          #4

          @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.

          AWIA 1 Reply Last reply
          0
          • ThreestarsT Threestars

            @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.

            AWIA Offline
            AWIA Offline
            AWI
            Hero Member
            wrote on last edited by
            #5

            @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..

            ThreestarsT 1 Reply Last reply
            0
            • AWIA AWI

              @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..

              ThreestarsT Offline
              ThreestarsT Offline
              Threestars
              wrote on last edited by
              #6

              @AWI
              Dear AWI, thank you very much, I will try!:smiley:

              1 Reply Last reply
              0
              • R Offline
                R Offline
                Ross Shirer
                wrote on last edited by
                #7

                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

                R 1 Reply Last reply
                0
                • R Ross Shirer

                  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

                  R Offline
                  R Offline
                  Ross Shirer
                  wrote on last edited by
                  #8

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

                  YveauxY H 2 Replies Last reply
                  0
                  • R Ross Shirer

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

                    YveauxY Offline
                    YveauxY Offline
                    Yveaux
                    Mod
                    wrote on last edited by
                    #9

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

                    http://yveaux.blogspot.nl

                    1 Reply Last reply
                    0
                    • R Ross Shirer

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

                      H Offline
                      H Offline
                      hard-shovel
                      wrote on last edited by hard-shovel
                      #10

                      @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)

                      1 Reply Last reply
                      0
                      Reply
                      • Reply as topic
                      Log in to reply
                      • Oldest to Newest
                      • Newest to Oldest
                      • Most Votes


                      19

                      Online

                      11.7k

                      Users

                      11.2k

                      Topics

                      113.1k

                      Posts


                      Copyright 2025 TBD   |   Forum Guidelines   |   Privacy Policy   |   Terms of Service
                      • Login

                      • Don't have an account? Register

                      • Login or register to search.
                      • First post
                        Last post
                      0
                      • MySensors
                      • OpenHardware.io
                      • Categories
                      • Recent
                      • Tags
                      • Popular