├── .gitignore ├── library.properties ├── keywords.txt ├── LICENSE ├── examples ├── polling │ └── polling.ino ├── interrupts │ └── interrupts.ino └── pin_change_interrupts │ └── pin_change_interrupts.ino ├── src ├── Wiegand.h └── Wiegand.cpp └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled Object files 2 | *.slo 3 | *.lo 4 | *.o 5 | *.obj 6 | 7 | # Precompiled Headers 8 | *.gch 9 | *.pch 10 | 11 | # Compiled Dynamic libraries 12 | *.so 13 | *.dylib 14 | *.dll 15 | 16 | # Fortran module files 17 | *.mod 18 | 19 | # Compiled Static libraries 20 | *.lai 21 | *.la 22 | *.a 23 | *.lib 24 | 25 | # Executables 26 | *.exe 27 | *.out 28 | *.app 29 | -------------------------------------------------------------------------------- /library.properties: -------------------------------------------------------------------------------- 1 | name=Yet Another Arduino Wiegand Library 2 | version=2.0.0 3 | author=Paulo Costa 4 | maintainer=Paulo Costa 5 | sentence=An Arduino Library to receive data from Wiegand card readers. 6 | paragraph=This library supports messages of different sizes (Usually 26 or 34 bits) and automatic size detection. 7 | category=Communication 8 | url=https://github.com/paulo-raca/YetAnotherArduinoWiegand 9 | architectures=* 10 | -------------------------------------------------------------------------------- /keywords.txt: -------------------------------------------------------------------------------- 1 | ####################################### 2 | # Syntax Coloring Map for Sodaq_PcInt 3 | ####################################### 4 | 5 | ####################################### 6 | # Datatypes (KEYWORD1) 7 | ####################################### 8 | 9 | Wiegand KEYWORD1 10 | DataError KEYWORD1 11 | 12 | ####################################### 13 | # Methods and Functions (KEYWORD2) 14 | ####################################### 15 | 16 | begin KEYWORD2 17 | end KEYWORD2 18 | reset KEYWORD2 19 | flush KEYWORD2 20 | flushNow KEYWORD2 21 | onReceive KEYWORD2 22 | onReceiveError KEYWORD2 23 | onStateChange KEYWORD2 24 | setPinState KEYWORD2 25 | setPin0State KEYWORD2 26 | setPin1State KEYWORD2 27 | receivedBit KEYWORD2 28 | 29 | ####################################### 30 | # Constants (LITERAL1) 31 | ####################################### 32 | 33 | Communication LITERAL1 34 | SizeTooBig LITERAL1 35 | SizeUnexpected LITERAL1 36 | DecodeFailed LITERAL1 37 | VerificationFailed LITERAL1 38 | 39 | LENGTH_ANY LITERAL1 40 | TIMEOUT LITERAL1 41 | MAX_BITS LITERAL1 42 | MAX_BYTES LITERAL1 43 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | This is free and unencumbered software released into the public domain. 2 | 3 | Anyone is free to copy, modify, publish, use, compile, sell, or 4 | distribute this software, either in source code form or as a compiled 5 | binary, for any purpose, commercial or non-commercial, and by any 6 | means. 7 | 8 | In jurisdictions that recognize copyright laws, the author or authors 9 | of this software dedicate any and all copyright interest in the 10 | software to the public domain. We make this dedication for the benefit 11 | of the public at large and to the detriment of our heirs and 12 | successors. We intend this dedication to be an overt act of 13 | relinquishment in perpetuity of all present and future rights to this 14 | software under copyright law. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 20 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 21 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | For more information, please refer to 25 | -------------------------------------------------------------------------------- /examples/polling/polling.ino: -------------------------------------------------------------------------------- 1 | /* 2 | * Example on how to use the Wiegand reader library with interruptions. 3 | */ 4 | 5 | #include 6 | 7 | // These are the pins connected to the Wiegand D0 and D1 signals. 8 | #define PIN_D0 2 9 | #define PIN_D1 3 10 | 11 | // The object that handles the wiegand protocol 12 | Wiegand wiegand; 13 | 14 | // Initialize Wiegand reader 15 | void setup() { 16 | Serial.begin(9600); 17 | 18 | //Install listeners and initialize Wiegand reader 19 | wiegand.onReceive(receivedData, "Card readed: "); 20 | wiegand.onReceiveError(receivedDataError, "Card read error: "); 21 | wiegand.onStateChange(stateChanged, "State changed: "); 22 | wiegand.begin(Wiegand::LENGTH_ANY, true); 23 | 24 | //initialize pins as INPUT 25 | pinMode(PIN_D0, INPUT); 26 | pinMode(PIN_D1, INPUT); 27 | } 28 | 29 | // Continuously checks for pending messages and polls updates from the wiegand inputs 30 | void loop() { 31 | // Checks for pending messages 32 | wiegand.flush(); 33 | 34 | // Check for changes on the the wiegand input pins 35 | wiegand.setPin0State(digitalRead(PIN_D0)); 36 | wiegand.setPin1State(digitalRead(PIN_D1)); 37 | } 38 | 39 | // Notifies when a reader has been connected or disconnected. 40 | // Instead of a message, the seconds parameter can be anything you want -- Whatever you specify on `wiegand.onStateChange()` 41 | void stateChanged(bool plugged, const char* message) { 42 | Serial.print(message); 43 | Serial.println(plugged ? "CONNECTED" : "DISCONNECTED"); 44 | } 45 | 46 | // Notifies when a card was read. 47 | // Instead of a message, the seconds parameter can be anything you want -- Whatever you specify on `wiegand.onReceive()` 48 | void receivedData(uint8_t* data, uint8_t bits, const char* message) { 49 | Serial.print(message); 50 | Serial.print(bits); 51 | Serial.print("bits / "); 52 | //Print value in HEX 53 | uint8_t bytes = (bits+7)/8; 54 | for (int i=0; i> 4, 16); 56 | Serial.print(data[i] & 0xF, 16); 57 | } 58 | Serial.println(); 59 | } 60 | 61 | // Notifies when an invalid transmission is detected 62 | void receivedDataError(Wiegand::DataError error, uint8_t* rawData, uint8_t rawBits, const char* message) { 63 | Serial.print(message); 64 | Serial.print(Wiegand::DataErrorStr(error)); 65 | Serial.print(" - Raw data: "); 66 | Serial.print(rawBits); 67 | Serial.print("bits / "); 68 | 69 | //Print value in HEX 70 | uint8_t bytes = (rawBits+7)/8; 71 | for (int i=0; i> 4, 16); 73 | Serial.print(rawData[i] & 0xF, 16); 74 | } 75 | Serial.println(); 76 | } 77 | -------------------------------------------------------------------------------- /examples/interrupts/interrupts.ino: -------------------------------------------------------------------------------- 1 | /* 2 | * Example on how to use the Wiegand reader library with interruptions. 3 | */ 4 | 5 | #include 6 | 7 | // These are the pins connected to the Wiegand D0 and D1 signals. 8 | // Ensure your board supports external Interruptions on these pins 9 | #define PIN_D0 2 10 | #define PIN_D1 3 11 | 12 | // The object that handles the wiegand protocol 13 | Wiegand wiegand; 14 | 15 | // Initialize Wiegand reader 16 | void setup() { 17 | Serial.begin(9600); 18 | 19 | //Install listeners and initialize Wiegand reader 20 | wiegand.onReceive(receivedData, "Card readed: "); 21 | wiegand.onReceiveError(receivedDataError, "Card read error: "); 22 | wiegand.onStateChange(stateChanged, "State changed: "); 23 | wiegand.begin(Wiegand::LENGTH_ANY, true); 24 | 25 | //initialize pins as INPUT and attaches interruptions 26 | pinMode(PIN_D0, INPUT); 27 | pinMode(PIN_D1, INPUT); 28 | attachInterrupt(digitalPinToInterrupt(PIN_D0), pinStateChanged, CHANGE); 29 | attachInterrupt(digitalPinToInterrupt(PIN_D1), pinStateChanged, CHANGE); 30 | 31 | //Sends the initial pin state to the Wiegand library 32 | pinStateChanged(); 33 | } 34 | 35 | // Every few milliseconds, check for pending messages on the wiegand reader 36 | // This executes with interruptions disabled, since the Wiegand library is not thread-safe 37 | void loop() { 38 | noInterrupts(); 39 | wiegand.flush(); 40 | interrupts(); 41 | //Sleep a little -- this doesn't have to run very often. 42 | delay(100); 43 | } 44 | 45 | // When any of the pins have changed, update the state of the wiegand library 46 | void pinStateChanged() { 47 | wiegand.setPin0State(digitalRead(PIN_D0)); 48 | wiegand.setPin1State(digitalRead(PIN_D1)); 49 | } 50 | 51 | // Notifies when a reader has been connected or disconnected. 52 | // Instead of a message, the seconds parameter can be anything you want -- Whatever you specify on `wiegand.onStateChange()` 53 | void stateChanged(bool plugged, const char* message) { 54 | Serial.print(message); 55 | Serial.println(plugged ? "CONNECTED" : "DISCONNECTED"); 56 | } 57 | 58 | // Notifies when a card was read. 59 | // Instead of a message, the seconds parameter can be anything you want -- Whatever you specify on `wiegand.onReceive()` 60 | void receivedData(uint8_t* data, uint8_t bits, const char* message) { 61 | Serial.print(message); 62 | Serial.print(bits); 63 | Serial.print("bits / "); 64 | //Print value in HEX 65 | uint8_t bytes = (bits+7)/8; 66 | for (int i=0; i> 4, 16); 68 | Serial.print(data[i] & 0xF, 16); 69 | } 70 | Serial.println(); 71 | } 72 | 73 | // Notifies when an invalid transmission is detected 74 | void receivedDataError(Wiegand::DataError error, uint8_t* rawData, uint8_t rawBits, const char* message) { 75 | Serial.print(message); 76 | Serial.print(Wiegand::DataErrorStr(error)); 77 | Serial.print(" - Raw data: "); 78 | Serial.print(rawBits); 79 | Serial.print("bits / "); 80 | 81 | //Print value in HEX 82 | uint8_t bytes = (rawBits+7)/8; 83 | for (int i=0; i> 4, 16); 85 | Serial.print(rawData[i] & 0xF, 16); 86 | } 87 | Serial.println(); 88 | } 89 | -------------------------------------------------------------------------------- /examples/pin_change_interrupts/pin_change_interrupts.ino: -------------------------------------------------------------------------------- 1 | /* 2 | * Example on how to use the Wiegand reader library with YetAnotherPcInt library 3 | * https://github.com/paulo-raca/YetAnotherArduinoPcIntLibrary 4 | */ 5 | 6 | #include 7 | #include 8 | 9 | // These are the pins connected to the Wiegand D0 and D1 signals. 10 | // Ensure your board supports external Pin Change Interrupts on these pins 11 | #define PIN_D0 8 12 | #define PIN_D1 9 13 | 14 | // The object that handles the wiegand protocol 15 | Wiegand wiegand; 16 | 17 | // Initialize Wiegand reader 18 | void setup() { 19 | Serial.begin(9600); 20 | 21 | //Install listeners and initialize Wiegand reader 22 | wiegand.onReceive(receivedData, "Card readed: "); 23 | wiegand.onReceiveError(receivedDataError, "Card read error: "); 24 | wiegand.onStateChange(stateChanged, "State changed: "); 25 | wiegand.begin(Wiegand::LENGTH_ANY, true); 26 | 27 | //initialize pins as INPUT and attaches interruptions 28 | pinMode(PIN_D0, INPUT); 29 | pinMode(PIN_D1, INPUT); 30 | PcInt::attachInterrupt(PIN_D0, wiegandD0Changed, &wiegand, CHANGE, true); 31 | PcInt::attachInterrupt(PIN_D1, wiegandD1Changed, &wiegand, CHANGE, true); 32 | } 33 | 34 | // Every few milliseconds, check for pending messages on the wiegand reader 35 | // This executes with interruptions disabled, since the Wiegand library is not thread-safe 36 | void loop() { 37 | noInterrupts(); 38 | wiegand.flush(); 39 | interrupts(); 40 | //Sleep a little -- this doesn't have to run very often. 41 | delay(100); 42 | } 43 | 44 | // When any of the pins have changed, update the state of the wiegand library 45 | void wiegandD0Changed(Wiegand *wiegand, bool value) { 46 | wiegand->setPin0State(value); 47 | } 48 | void wiegandD1Changed(Wiegand *wiegand, bool value) { 49 | wiegand->setPin1State(value); 50 | } 51 | 52 | // Notifies when a reader has been connected or disconnected. 53 | // Instead of a message, the seconds parameter can be anything you want -- Whatever you specify on `wiegand.onStateChange()` 54 | void stateChanged(bool plugged, const char* message) { 55 | Serial.print(message); 56 | Serial.println(plugged ? "CONNECTED" : "DISCONNECTED"); 57 | } 58 | 59 | // Notifies when a card was read. 60 | // Instead of a message, the seconds parameter can be anything you want -- Whatever you specify on `wiegand.onReceive()` 61 | void receivedData(uint8_t* data, uint8_t bits, const char* message) { 62 | Serial.print(message); 63 | Serial.print(bits); 64 | Serial.print("bits / "); 65 | //Print value in HEX 66 | uint8_t bytes = (bits+7)/8; 67 | for (int i=0; i> 4, 16); 69 | Serial.print(data[i] & 0xF, 16); 70 | } 71 | Serial.println(); 72 | } 73 | 74 | // Notifies when an invalid transmission is detected 75 | void receivedDataError(Wiegand::DataError error, uint8_t* rawData, uint8_t rawBits, const char* message) { 76 | Serial.print(message); 77 | Serial.print(Wiegand::DataErrorStr(error)); 78 | Serial.print(" - Raw data: "); 79 | Serial.print(rawBits); 80 | Serial.print("bits / "); 81 | 82 | //Print value in HEX 83 | uint8_t bytes = (rawBits+7)/8; 84 | for (int i=0; i> 4, 16); 86 | Serial.print(rawData[i] & 0xF, 16); 87 | } 88 | Serial.println(); 89 | } 90 | -------------------------------------------------------------------------------- /src/Wiegand.h: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | class Wiegand { 4 | public: 5 | /** 6 | * Accepts messages of any size. 7 | * 8 | * Unfortunately, it also means waiting `TIMEOUT` after the last bit 9 | * and calling `flush` to make sure that the message is finished. 10 | */ 11 | static const uint8_t LENGTH_ANY = 0xFF; 12 | 13 | /** 14 | * 2ms seems to be the accepted interval between bits, but since it is very poorly 15 | * standardized, so it's better to be on the safe side 16 | */ 17 | static const uint8_t TIMEOUT = 25; 18 | 19 | /** 20 | * 34-bit is the maximum I've seen used for Wiegand 21 | */ 22 | static const uint8_t MAX_BITS = 64; 23 | 24 | /** 25 | * 34-bit is the maximum I've seen used for Wiegand 26 | */ 27 | static const uint8_t MAX_BYTES = ((MAX_BITS + 7)/8); 28 | 29 | /** 30 | * Possible communication errors sent to your `data_error_callback` 31 | */ 32 | enum DataError { Communication, SizeTooBig, SizeUnexpected, DecodeFailed, VerificationFailed}; 33 | 34 | /** 35 | * Gets the message associated with a `DataError` 36 | */ 37 | static inline const char* DataErrorStr(DataError error) { 38 | switch (error) { 39 | case Communication: 40 | return "Communication Error"; 41 | case SizeTooBig: 42 | return "Message size too big"; 43 | case SizeUnexpected: 44 | return "Message size unexpected"; 45 | case DecodeFailed: 46 | return "Unsupported message format"; 47 | case VerificationFailed: 48 | return "Message verification failed"; 49 | default: 50 | return "Unknown"; 51 | } 52 | } 53 | 54 | typedef void (*data_callback)(uint8_t* data, uint8_t bits, void* param); 55 | typedef void (*data_error_callback)(DataError error, uint8_t* rawdata, uint8_t bits, void* param); 56 | typedef void (*state_callback)(bool plugged, void* param); 57 | 58 | private: 59 | uint8_t expected_bits; 60 | bool decode_messages; 61 | uint8_t bits; 62 | uint8_t state; 63 | unsigned long timestamp; 64 | uint8_t data[MAX_BYTES]; 65 | Wiegand::data_callback func_data; 66 | Wiegand::data_error_callback func_data_error; 67 | Wiegand::state_callback func_state; 68 | void* func_data_param; 69 | void* func_data_error_param; 70 | void* func_state_param; 71 | 72 | /** 73 | * Adds a new bit to the payload 74 | */ 75 | void addBitInternal(bool value); 76 | 77 | /** 78 | * Verifies if the current buffer is valid and sends it to the data / error callbacks. 79 | * If the buffer is invalid, it is discarded 80 | */ 81 | void flushData(); 82 | 83 | public: 84 | /** 85 | * Sets the device as "initialized" and resets it to wait a new message. 86 | * 87 | * If `expected_bits` is specified (usually 4, 8, 26 or 34), the data callback will 88 | * be notified immedialy aftern the last bit is received. 89 | * 90 | * Otherwise (`expected_bits=LENGTH_ANY`), you will need to call `flush()` 91 | * inside your main loop to receive notifications, and the end of the message will be 92 | * triggered after a few ms without communication. 93 | * 94 | * 95 | * if `decode_messages` is set, parity bits will be checked and removed 96 | * during preprocessing, otherwise the raw message will be sent to the callback. 97 | */ 98 | void begin(uint8_t expected_bits=LENGTH_ANY, bool decode_messages=true); 99 | 100 | /** 101 | * Sets the device as "not-initialized" 102 | */ 103 | void end(); 104 | 105 | /** 106 | * Resets the state so that it awaits a new message. 107 | * 108 | * If the data pins aren't high, it sets the `ERROR_TRANSMISSION` flag 109 | * to signal it is probably in the middle of a truncated message or something. 110 | */ 111 | void reset(); 112 | 113 | /** 114 | * Returns if this device is initialized (with `begin()`) and a reader has been connected. 115 | * 116 | * A reader is considered connected when both D0 and D1 are high, 117 | * and it is considered disconnected when both are low. 118 | */ 119 | operator bool(); 120 | 121 | /** 122 | * Clean up state after `WIEGAND_TIMEOUT` milliseconds without events 123 | * 124 | * This means sending out any pending message and calling `reset()` 125 | */ 126 | void flush(); 127 | 128 | /** 129 | * Immediately cleans up state, sending out pending messages and calling `reset()` 130 | */ 131 | void flushNow(); 132 | 133 | 134 | /** 135 | * Attaches a Data Receive Callback. 136 | * 137 | * This will be called whenever a message has been received without errors. 138 | */ 139 | template void onReceive(void (*func)(uint8_t* data, uint8_t bits, T* param), T* param=nullptr) { 140 | func_data = (data_callback)func; 141 | func_data_param = (void*)param; 142 | } 143 | 144 | 145 | /** 146 | * Attaches a Data Transmission Error Callback. 147 | * 148 | * This will be called whenever a message has been received without errors. 149 | */ 150 | template void onReceiveError(void (*func)(DataError error, uint8_t* data, uint8_t bits, T* param), T* param=nullptr) { 151 | func_data_error = (data_error_callback)func; 152 | func_data_error_param = (void*)param; 153 | } 154 | 155 | /** 156 | * Attaches a State Change Callback. This is called whenever a device is attached or dettached. 157 | * 158 | * If you have a dettachable device, add pull down resistors to both data lines, otherwise random 159 | * noise will produce lots of bogus State Change notifications (and maybe a few Data Received notifications) 160 | */ 161 | template void onStateChange(void (*func)(bool plugged, T* param), T* param=nullptr) { 162 | func_state = (state_callback)func; 163 | func_state_param = (void*)param; 164 | } 165 | 166 | /** 167 | * Updates the state of a pin. 168 | * 169 | * It will trigger adding bits to the payload, device connection/disconnection, 170 | * dispatching the payload to the callback, etc 171 | */ 172 | void setPinState(uint8_t pin, bool pin_state); 173 | 174 | /** 175 | * Notifies the library that the pin Data0 has changed to `pin_state` 176 | */ 177 | inline void setPin0State(bool state) { 178 | setPinState(0, state); 179 | } 180 | 181 | /** 182 | * Notifies the library that the pin Data1 has changed to `pin_state` 183 | */ 184 | inline void setPin1State(bool state) { 185 | setPinState(1, state); 186 | } 187 | 188 | /** 189 | * Receives a data bit. 190 | * 191 | * This is meant for testing only 192 | */ 193 | inline void receivedBit(bool bitValue) { 194 | setPinState(0, true); 195 | setPinState(1, true); 196 | setPinState(bitValue, false); 197 | setPinState(bitValue, true); 198 | } 199 | }; 200 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Yet Another Arduino Wiegand Library 2 | 3 | A library to received data from Wiegand RFID Card readers. 4 | 5 | 6 | ## Features 7 | 8 | _Support multiple data formats_! 9 | - It can detect the message size format automatically! 10 | - 4, 8, 26 and 34 bits are tested and work fine 11 | - Should work with other formats (Let me know) 12 | 13 | _It is event-driven_! 14 | - You don't pool all the time checking if there is a card -- a callback will tell you when there is one. 15 | - The extra `void*` parameter on the callbacks are useful if you are using multiple Wiegand instances in your code. 16 | 17 | _It is hardware-agnostic_! 18 | - It's up to you to detect input changes. You may use [External Interruptions](examples/interrupts/interrupts.ino), [Polling](examples/polling/polling.ino), or something else. 19 | 20 | 21 | # How to use it 22 | 23 | ```c++ 24 | /* 25 | * Example on how to use the Wiegand reader library with interruptions. 26 | */ 27 | 28 | #include 29 | 30 | // These are the pins connected to the Wiegand D0 and D1 signals. 31 | // Ensure your board supports external Interruptions on these pins 32 | #define PIN_D0 2 33 | #define PIN_D1 3 34 | 35 | // The object that handles the wiegand protocol 36 | Wiegand wiegand; 37 | 38 | // Initialize Wiegand reader 39 | void setup() { 40 | Serial.begin(9600); 41 | 42 | //Install listeners and initialize Wiegand reader 43 | wiegand.onReceive(receivedData, "Card readed: "); 44 | wiegand.onReceiveError(receivedDataError, "Card read error: "); 45 | wiegand.onStateChange(stateChanged, "State changed: "); 46 | wiegand.begin(Wiegand::LENGTH_ANY, true); 47 | 48 | //initialize pins as INPUT and attaches interruptions 49 | pinMode(PIN_D0, INPUT); 50 | pinMode(PIN_D1, INPUT); 51 | attachInterrupt(digitalPinToInterrupt(PIN_D0), pinStateChanged, CHANGE); 52 | attachInterrupt(digitalPinToInterrupt(PIN_D1), pinStateChanged, CHANGE); 53 | 54 | //Sends the initial pin state to the Wiegand library 55 | pinStateChanged(); 56 | } 57 | 58 | // Every few milliseconds, check for pending messages on the wiegand reader 59 | // This executes with interruptions disabled, since the Wiegand library is not thread-safe 60 | void loop() { 61 | noInterrupts(); 62 | wiegand.flush(); 63 | interrupts(); 64 | //Sleep a little -- this doesn't have to run very often. 65 | delay(100); 66 | } 67 | 68 | // When any of the pins have changed, update the state of the wiegand library 69 | void pinStateChanged() { 70 | wiegand.setPin0State(digitalRead(PIN_D0)); 71 | wiegand.setPin1State(digitalRead(PIN_D1)); 72 | } 73 | 74 | // Notifies when a reader has been connected or disconnected. 75 | // Instead of a message, the seconds parameter can be anything you want -- Whatever you specify on `wiegand.onStateChange()` 76 | void stateChanged(bool plugged, const char* message) { 77 | Serial.print(message); 78 | Serial.println(plugged ? "CONNECTED" : "DISCONNECTED"); 79 | } 80 | 81 | // Notifies when a card was read. 82 | // Instead of a message, the seconds parameter can be anything you want -- Whatever you specify on `wiegand.onReceive()` 83 | void receivedData(uint8_t* data, uint8_t bits, const char* message) { 84 | Serial.print(message); 85 | Serial.print(bits); 86 | Serial.print("bits / "); 87 | //Print value in HEX 88 | uint8_t bytes = (bits+7)/8; 89 | for (int i=0; i> 4, 16); 91 | Serial.print(data[i] & 0xF, 16); 92 | } 93 | Serial.println(); 94 | } 95 | 96 | // Notifies when an invalid transmission is detected 97 | void receivedDataError(Wiegand::DataError error, uint8_t* rawData, uint8_t rawBits, const char* message) { 98 | Serial.print(message); 99 | Serial.print(Wiegand::DataErrorStr(error)); 100 | Serial.print(" - Raw data: "); 101 | Serial.print(rawBits); 102 | Serial.print("bits / "); 103 | 104 | //Print value in HEX 105 | uint8_t bytes = (rawBits+7)/8; 106 | for (int i=0; i> 4, 16); 108 | Serial.print(rawData[i] & 0xF, 16); 109 | } 110 | Serial.println(); 111 | } 112 | ``` 113 | 114 | 115 | # Wiegand Protocol 116 | 117 | The Wiegand protocol is very and easy to implement, but is poorly standarlized. 118 | 119 | 120 | ## Data transmission 121 | 122 | The data is sent over 2 wires, `Data 0` and `Data 1`. Both wires are set to `HIGH` most of the time. 123 | 124 | When a message is being transmitted, bits are sent one at a time: 125 | - If the bit value is `0`, `Data 0` is set to `LOW` for a few microseconds (between 20µs and 100µs). 126 | - If the bit value is `1`, `Data 1` is set to `LOW` for a few microseconds (between 20µs and 100µs). 127 | 128 | After a small delay (between 200µs and 20 ms), the next bit is sent – until the message is complete. 129 | 130 | Having both pins to `LOW` is an invalid state, and usually means that the card reader is disconnected. 131 | 132 | 133 | ## Message format 134 | 135 | This is where things get hairy: Many vendors have defined their own message formats, with different sizes, fields, parity checks and layouts. 136 | 137 | These are the most common formats seen in the wild: 138 | 139 | 140 | ### 26 and 34 bits formats 141 | 142 | These are the most common formats, tipically used on RFID card readers. 143 | 144 | - The first and the last bits of the message are used for parity checks. 145 | - The first half of the bits must have EVEN parity. 146 | - The second half of the bits must have ODD parity. 147 | 148 | 149 | ### 4 and 8 bit format 150 | 151 | These formats are sometimes used on keypads. 152 | 153 | On the 4-bit format, the digit is encoded in 4 bits, no extra stuff is added. 154 | 155 | On the 8-bit format, the digit is encoded in the lower 4 bits, and the higher 4 bits if the "NOT" of the digit. 156 | 157 | 158 | # This Library 159 | 160 | ## Hardware integration 161 | 162 | Since the hardware changes a lot, this library doesn't assume anything on how to read the data pins. 163 | 164 | When the state of a pin has changed, it's up to you to call `Wiegand.setPinState(pin, state)`. 165 | 166 | There are examples on how to use it with [Interruptions](examples/interrupts/interrupts.ino) and [Polling](examples/polling/polling.ino) on Arduinos. 167 | 168 | (You probably want to use interruptions) 169 | 170 | 171 | ## Receiving Data 172 | 173 | Use `Wiegand.onReceive()` to listen to messages. 174 | 175 | The listener receives the databuffer and number of bits. 176 | 177 | If the Wiegand is initialized with `decode_messages=true` (the default), any parity/check bits are removed from the payload. (E.g., On a 26-bits message, the decoded payload has only 24-bits) 178 | 179 | 180 | ## Handling error Data 181 | 182 | Use `Wiegand.onReceiveError()` to listen to errors. 183 | 184 | The callback receive the raw (non-decoded) message. 185 | 186 | The error can be any of these: 187 | - `Communication`: The message was received right away after the library started listening, so the first bits are likely lost. 188 | - `SizeTooBig`: The payload is bigger than the internal buffer. The payload will be truncated to `Wiegand::MAX_BITS` 189 | - `SizeUnexpected`: The library was configured to expect a specific number of bits, but we received a truncated message. 190 | - `DecodeFailed`: The library was initialized with `decode_messages=true` (the default), but doesn't support the message format it received. 191 | - `VerificationFailed`: The library was initialized with `decode_messages=true`, received a message with one of the known formats, but the message failed the parity checks. 192 | 193 | 194 | ## Padding 195 | 196 | Wiegand messages have weird amounts of bits, but callbacks receive byte arrays. 197 | 198 | Because of this, if the payload on with wiegand data is not multiple of 8 bits, it will be padded with zeros on the most significant bits of the first byte. 199 | 200 | This is _probably_ what you want, so that a 4-bit message with `0xf` is encoded as `[0x0f]` instead of `[0xf0]` 201 | 202 | 203 | ## Automatic message size detection 204 | 205 | If the message size is specified on `Wiegand.begin(size)`, your listener will be called as soon as the last bit is received, inside the call to `Wiegand.setPinState()`. Easy! 206 | 207 | On the other hand, if you are using automatic message size detections (`Wiegand.begin(Wiegand::LENGTH_ANY)`), the library considers a message finished after a few milliseconds without events. 208 | 209 | In this case, you must call `Wiegand.flush()` after suficient time (`WIEGAND_TIMEOUT` milliseconds) has elapsed. The easiest way is to call it from your main loop. 210 | 211 | __This library is not thread safe__. If you are using interruptions to detect changes in pin state, call `Wiegand.flush()` with interruptions disabled. 212 | 213 | 214 | ## Device detection 215 | 216 | This library supports detection of the card reader. 217 | 218 | Use `Wiegand.onStateChange()` to listen to plug/unplug events. 219 | 220 | To use this feature, you'll need to add a pull-down resistor on both data pins. This will set the input on a invalid state (LOW-LOW) when the reader is unplugged. 221 | 222 | -------------------------------------------------------------------------------- /src/Wiegand.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #define PIN_0 0x01 5 | #define PIN_1 0x02 6 | #define DEVICE_CONNECTED 0x04 7 | #define DEVICE_INITIALIZED 0x08 8 | 9 | #define ERROR_TRANSMISSION 0x10 10 | #define ERROR_TOO_BIG 0x20 11 | 12 | #define MASK_PINS (PIN_0 | PIN_1) 13 | #define MASK_STATE 0x0F 14 | #define MASK_ERRORS 0xF0 15 | 16 | /** 17 | * Sets the value of the `i`-th data bit 18 | */ 19 | inline void writeBit(uint8_t* data, uint8_t i, bool value) { 20 | if (value) { 21 | data[i>>3] |= (0x80 >> (i&7)); 22 | } else { 23 | data[i>>3] &= ~(0x80 >> (i&7)); 24 | } 25 | } 26 | 27 | /** 28 | * Reads the value of the `i`-th data bit 29 | */ 30 | inline bool readBit(uint8_t* data, uint8_t i) { 31 | return bool(data[i>>3] & (0x80 >> (i&7))); 32 | } 33 | 34 | /** 35 | * Sign a subrange of data, shrinks and aligns the buffer to the right, inline 36 | * 37 | * returns the number of bits in the subrange 38 | */ 39 | inline uint8_t align_data(uint8_t* data, uint8_t start, uint8_t end) { 40 | uint8_t aligned_data[Wiegand::MAX_BYTES]; 41 | uint8_t aligned_bits = end - start; 42 | uint8_t aligned_bytes = (aligned_bits + 7)/8; 43 | uint8_t aligned_offset = 8*aligned_bytes - aligned_bits; 44 | 45 | aligned_data[0] = 0; 46 | for (int bit=0; bitexpected_bits = expected_bits; 72 | this->decode_messages = decode_messages; 73 | 74 | //Set state as "INVALID", so that data can only be received after a few millis in "ready" state 75 | bits=0; 76 | timestamp = millis(); 77 | state = (state & MASK_STATE) | DEVICE_INITIALIZED | ERROR_TRANSMISSION; 78 | } 79 | 80 | /** 81 | * Sets the device as "not-initialized" 82 | */ 83 | void Wiegand::end() { 84 | expected_bits = 0; 85 | 86 | bits=0; 87 | timestamp = millis(); 88 | state &= MASK_STATE & ~DEVICE_INITIALIZED; 89 | } 90 | 91 | 92 | /** 93 | * Resets the state so that it awaits a new message. 94 | * 95 | * If the data pins aren't high, it sets the `ERROR_TRANSMISSION` flag 96 | * to signal it is probably in the middle of a truncated message or something. 97 | */ 98 | void Wiegand::reset() { 99 | bits=0; 100 | state &= MASK_STATE; 101 | //A transmission must start with D0=1, D1=1 102 | if ((state & MASK_PINS) != MASK_PINS) { 103 | state |= ERROR_TRANSMISSION; 104 | } 105 | } 106 | 107 | 108 | /** 109 | * Returns if this device is initialized (with `begin()`) and a reader has been connected. 110 | * 111 | * A reader is considered connected when both D0 and D1 are high, 112 | * and it is considered disconnected when both are low. 113 | */ 114 | Wiegand::operator bool() { 115 | return (state & (DEVICE_CONNECTED|DEVICE_INITIALIZED)) == (DEVICE_CONNECTED|DEVICE_INITIALIZED); 116 | } 117 | 118 | 119 | /** 120 | * Verifies if the current buffer is valid and sends it to the data / error callbacks. 121 | * If the buffer is invalid, it is discarded 122 | */ 123 | void Wiegand::flushData() { 124 | //Ignore empty messages 125 | if ((bits == 0) || (expected_bits == 0)) { 126 | return; 127 | } 128 | 129 | //Check for pending errors 130 | if (state & MASK_ERRORS) { 131 | if (func_data_error) { 132 | bits = align_data(data, 0, bits); 133 | if (state & ERROR_TOO_BIG) { 134 | func_data_error(DataError::SizeTooBig, data, bits, func_data_error_param); 135 | } else { 136 | func_data_error(DataError::Communication, data, bits, func_data_error_param); 137 | } 138 | } 139 | return; 140 | } 141 | 142 | //Validate the message size 143 | if ((expected_bits != bits) && (expected_bits != Wiegand::LENGTH_ANY)) { 144 | if (func_data_error) { 145 | bits = align_data(data, 0, bits); 146 | func_data_error(DataError::SizeUnexpected, data, bits, func_data_error_param); 147 | } 148 | return; 149 | } 150 | 151 | //Decode the message 152 | if (!decode_messages) { 153 | if (func_data) { 154 | bits = align_data(data, 0, bits); 155 | func_data(data, bits, func_data_param); 156 | } 157 | } else { 158 | //4-bit keycode: No check necessary 159 | if ((bits == 4)) { 160 | if (func_data) { 161 | bits = align_data(data, 0, bits); 162 | func_data(data, bits, func_data_param); 163 | } 164 | 165 | //8-bit keybode: UpperNibble = ~lowerNibble 166 | } else if ((bits == 8)) { 167 | uint8_t value = data[0] & 0xF; 168 | if (data[0] == (value | ((0xF & ~value)<<4))) { 169 | if (func_data) { 170 | func_data(&value, 4, func_data_param); 171 | } 172 | } else { 173 | if (func_data_error) { 174 | bits = align_data(data, 0, bits); 175 | func_data_error(DataError::VerificationFailed, data, bits, func_data_error_param); 176 | } 177 | } 178 | 179 | //26 or 34-bits: First and last bits are used for parity 180 | } else if ((bits == 26) || (bits == 34)) { 181 | //FIXME: The parity check doesn't seem to work for a 34-bit reader I have, 182 | //but I suspect that the reader is non-complaint 183 | 184 | boolean left_parity = false; 185 | boolean right_parity = false; 186 | for (int i=0; i<(bits+1)/2; i++) { 187 | left_parity = (left_parity != readBit(data, i)); 188 | } 189 | for (int i=bits/2; i TIMEOUT) { 224 | // Might have a pending data package 225 | flushData(); 226 | reset(); 227 | } 228 | } 229 | 230 | /** 231 | * Immediately cleans up state, sending out pending messages and calling `reset()` 232 | */ 233 | void Wiegand::flushNow() { 234 | flushData(); 235 | reset(); 236 | } 237 | 238 | /** 239 | * Adds a new bit to the payload 240 | */ 241 | void Wiegand::addBitInternal(bool value) { 242 | //Skip if we have too much data 243 | if (bits >= MAX_BITS) { 244 | state |= ERROR_TOO_BIG; 245 | } else { 246 | writeBit(data, bits++, value); 247 | } 248 | 249 | // If we know the number of bits, there is no need to wait for the timeout to send the data 250 | if (expected_bits > 0 && (bits == expected_bits)) { 251 | flushData(); 252 | reset(); 253 | } 254 | } 255 | 256 | 257 | /** 258 | * Updates the state of a pin. 259 | * 260 | * It will trigger adding bits to the payload, device connection/disconnection, 261 | * dispatching the payload to the callback, etc 262 | */ 263 | void Wiegand::setPinState(uint8_t pin, bool pin_state) { 264 | uint8_t pin_mask = pin ? PIN_1 : PIN_0; 265 | 266 | flush(); 267 | 268 | //No change? Abort! 269 | if (bool(state & pin_mask) == pin_state) { 270 | return; 271 | } 272 | 273 | timestamp = millis(); 274 | if (pin_state) { 275 | state |= pin_mask; 276 | } else { 277 | state &= ~pin_mask; 278 | } 279 | 280 | //Both pins on: bit received 281 | if ((state & MASK_PINS) == MASK_PINS) { 282 | //If the device wasn't ready before -- Enable it, and marks state as INVALID until is settles. 283 | if (state & DEVICE_CONNECTED) { 284 | addBitInternal(pin); 285 | } else { 286 | //Device connection was detected right now! 287 | //Set the device as connected, but unstable 288 | state = (state & MASK_STATE) | DEVICE_CONNECTED | ERROR_TRANSMISSION; 289 | if (func_state) { 290 | func_state(true, func_state_param); 291 | } 292 | } 293 | 294 | //Both pins off - Device is unplugged 295 | } else if ((state & MASK_PINS) == 0) { 296 | if (state & DEVICE_CONNECTED) { 297 | //Flush truncated message, if any, and resets state 298 | state |= ERROR_TRANSMISSION; 299 | flushNow(); 300 | 301 | //Set the state as disconnected 302 | state = (state & MASK_STATE & ~DEVICE_CONNECTED); 303 | if (func_state) { 304 | func_state(false, func_state_param); 305 | } 306 | } 307 | } 308 | } 309 | --------------------------------------------------------------------------------