|
#include "esphome.h" |
|
#include <sstream> |
|
|
|
/** |
|
* Wiegand Reader Custom Device |
|
* |
|
* Copied from https://github.com/monkeyboard/Wiegand-Protocol-Library-for-Arduino |
|
* Implemented by Greg Doerr (https://github.com/gdoerr) |
|
* |
|
* In my example, hooked to an Olimex ESP32-POE device connected to a Retekess H1EM-W |
|
* Wiegand keypad. This device calls a service on Home Assistant when a code is available |
|
* |
|
* Samples key presses every 200ms and stores the key in a string until: |
|
* [1] - the user presses a '*' which sends the code immediately |
|
* [2] - the user presses nothing for 4,500ms (inter-digit timer) which then clears the code |
|
* [3] - the user presses a '#' which cancels the current code |
|
*/ |
|
class WiegandReader : public PollingComponent, public CustomAPIDevice { |
|
|
|
public: |
|
WiegandReader(int pinD0, int pinD1) |
|
: PollingComponent(200), |
|
pinD0(pinD0), pinD1(pinD1) { |
|
} |
|
|
|
/** |
|
* Initial setup |
|
*/ |
|
void setup() override { |
|
_lastWiegand = 0; |
|
_cardTempHigh = 0; |
|
_cardTemp = 0; |
|
_code = 0; |
|
_wiegandType = 0; |
|
_bitCount = 0; |
|
|
|
// Configure the input pins |
|
pinMode(pinD0, INPUT); |
|
pinMode(pinD1, INPUT); |
|
|
|
// Attach the interrupts |
|
attachInterrupt(digitalPinToInterrupt(pinD0), ReadD0, FALLING); // Hardware interrupt - high to low pulse |
|
attachInterrupt(digitalPinToInterrupt(pinD1), ReadD1, FALLING); // Hardware interrupt - high to low pulse |
|
} |
|
|
|
void update() override { |
|
// See if we have a valid code |
|
noInterrupts(); |
|
bool rc = DoWiegandConversion(); |
|
interrupts(); |
|
|
|
if(rc) { |
|
// ESP_LOGD("wiegand_device", "Data received : %s", (_code + 0x30).c_str()); |
|
if(_code < 10) { |
|
// We have a digit, make it ASCII for convenience |
|
keyCodes += (_code + 0x30); |
|
} else if(_code == 10) { // pressed '*' |
|
// send the accumulated code and reset for the next string |
|
sendTagToHA(keyCodes); |
|
keyCodes = ""; |
|
} else if(_code == 11) { // pressed '#' |
|
// clear the code and send the asterisk |
|
ESP_LOGD("wiegand_device", "User pressed #, clearing codes."); |
|
keyCodes = ""; |
|
} else { |
|
std::string hexString = decToHex(_code); |
|
sendTagToHA(hexString); |
|
keyCodes = ""; |
|
} |
|
// Capture the last time we received a code |
|
lastCode = millis(); |
|
} else { |
|
if(keyCodes.length() > 0) { |
|
// We have a keyCode, clear it if the timer expired |
|
if(millis() - lastCode > 4500) { // 4.5~5 seconds seems to be how long before keypad does "invalid entry" beeps (this may differ for your keypad) |
|
ESP_LOGD("wiegand_device", "Keypad timeout, clearing codes"); |
|
keyCodes = ""; |
|
} |
|
} |
|
} |
|
} |
|
|
|
private: |
|
static volatile unsigned long _cardTempHigh; |
|
static volatile unsigned long _cardTemp; |
|
static volatile unsigned long _lastWiegand; |
|
static volatile int _bitCount; |
|
static int _wiegandType; |
|
static unsigned long _code; |
|
|
|
unsigned long lastCode = 0; |
|
std::string keyCodes = ""; |
|
|
|
int pinD0; |
|
int pinD1; |
|
|
|
/** |
|
* Scan the code into Home Assistant |
|
* @param keyCode |
|
*/ |
|
void sendTagToHA(std::string keyCode) { |
|
ESP_LOGD("wiegand_device", "Scanner code : %s", keyCode.c_str()); |
|
|
|
api::HomeAssistantServiceCallAction<> *ha_service; |
|
ha_service = new api::HomeAssistantServiceCallAction<>(&*api_apiserver, true); |
|
ha_service->set_service("esphome.tag_scanned"); |
|
ha_service->add_data("tag_id", keyCode.c_str()); |
|
ha_service->play(); |
|
} |
|
|
|
/** |
|
* Convert decimal to hex string |
|
* @param keyCode |
|
*/ |
|
std::string decToHex(unsigned long decimal) { |
|
std::stringstream ss; |
|
ss<< std::hex << decimal; // int decimal_value |
|
std::string res ( ss.str() ); |
|
return res; |
|
} |
|
|
|
/** |
|
* D0 Interrupt Handler |
|
*/ |
|
static void ReadD0() { |
|
_bitCount++; // Increment bit count for Interrupt connected to D0 |
|
if(_bitCount > 31) { // If bit count more than 31, process high bits |
|
_cardTempHigh |= ((0x80000000 & _cardTemp)>>31); // shift value to high bits |
|
_cardTempHigh <<= 1; |
|
_cardTemp <<=1; |
|
} else |
|
_cardTemp <<= 1; // D0 represent binary 0, so just left shift card data |
|
|
|
_lastWiegand = millis(); // Keep track of last wiegand bit received |
|
} |
|
|
|
/** |
|
* D1 Interrupt Handler |
|
*/ |
|
static void ReadD1() { |
|
_bitCount ++; // Increment bit count for Interrupt connected to D1 |
|
|
|
if(_bitCount > 31) { // If bit count more than 31, process high bits |
|
_cardTempHigh |= ((0x80000000 & _cardTemp)>>31); // shift value to high bits |
|
_cardTempHigh <<= 1; |
|
_cardTemp |= 1; |
|
_cardTemp <<=1; |
|
} else { |
|
_cardTemp |= 1; // D1 represent binary 1, so OR card data with 1 then |
|
_cardTemp <<= 1; // left shift card data |
|
} |
|
_lastWiegand = millis(); // Keep track of last wiegand bit received |
|
} |
|
|
|
/** |
|
* Extract the Card ID from the received bit stream |
|
* @param codehigh |
|
* @param codelow |
|
* @param bitlength |
|
* @return |
|
*/ |
|
unsigned long getCardId(volatile unsigned long *codehigh, volatile unsigned long *codelow, char bitlength) { |
|
if (bitlength==26) // EM tag |
|
return (*codelow & 0x1FFFFFE) >>1; |
|
|
|
if (bitlength==34) // Mifare |
|
{ |
|
*codehigh = *codehigh & 0x03; // only need the 2 LSB of the codehigh |
|
*codehigh <<= 30; // shift 2 LSB to MSB |
|
*codelow >>=1; |
|
return *codehigh | *codelow; |
|
} |
|
return *codelow; // EM tag or Mifare without parity bits |
|
} |
|
|
|
/** |
|
* Convert the received bitstream |
|
* @return |
|
*/ |
|
bool DoWiegandConversion () { |
|
unsigned long cardID; |
|
unsigned long sysTick = millis(); |
|
|
|
if ((sysTick - _lastWiegand) > 25) // if no more signal coming through after 25ms |
|
{ |
|
if ((_bitCount==24) || (_bitCount==26) || (_bitCount==32) || (_bitCount==34) || (_bitCount==8) || (_bitCount==4)) { // bitCount for keypress=4 or 8, Wiegand 26=24 or 26, Wiegand 34=32 or 34 |
|
_cardTemp >>= 1; // shift right 1 bit to get back the real value - interrupt done 1 left shift in advance |
|
if (_bitCount>32) // bit count more than 32 bits, shift high bits right to make adjustment |
|
_cardTempHigh >>= 1; |
|
|
|
if (_bitCount==8) { // keypress wiegand with integrity |
|
// 8-bit Wiegand keyboard data, high nibble is the "NOT" of low nibble |
|
// eg if key 1 pressed, data=E1 in binary 11100001 , high nibble=1110 , low nibble = 0001 |
|
char highNibble = (_cardTemp & 0xf0) >>4; |
|
char lowNibble = (_cardTemp & 0x0f); |
|
_wiegandType=_bitCount; |
|
_bitCount=0; |
|
_cardTemp=0; |
|
_cardTempHigh=0; |
|
|
|
if (lowNibble == (~highNibble & 0x0f)) { // check if low nibble matches the "NOT" of high nibble. |
|
_code = (int)lowNibble; |
|
return true; |
|
} else { |
|
_lastWiegand=sysTick; |
|
_bitCount=0; |
|
_cardTemp=0; |
|
_cardTempHigh=0; |
|
return false; |
|
} |
|
|
|
// TODO: Handle validation failure case! |
|
} else if (4 == _bitCount) { |
|
// 4-bit Wiegand codes have no data integrity check so we just |
|
// read the LOW nibble. |
|
_code = (int)(_cardTemp & 0x0000000F); |
|
|
|
_wiegandType = _bitCount; |
|
_bitCount = 0; |
|
_cardTemp = 0; |
|
_cardTempHigh = 0; |
|
|
|
return true; |
|
} else { // wiegand 26 or wiegand 34 |
|
cardID = getCardId (&_cardTempHigh, &_cardTemp, _bitCount); |
|
_wiegandType=_bitCount; |
|
_bitCount=0; |
|
_cardTemp=0; |
|
_cardTempHigh=0; |
|
_code=cardID; |
|
return true; |
|
} |
|
} else { |
|
// well time over 25 ms and bitCount !=8 , !=26, !=34 , must be noise or nothing then. |
|
_lastWiegand=sysTick; |
|
_bitCount=0; |
|
_cardTemp=0; |
|
_cardTempHigh=0; |
|
return false; |
|
} |
|
} else |
|
return false; |
|
} |
|
}; |
|
|
|
volatile unsigned long WiegandReader::_cardTempHigh = 0; |
|
volatile unsigned long WiegandReader::_cardTemp = 0; |
|
volatile unsigned long WiegandReader::_lastWiegand = 0; |
|
volatile int WiegandReader::_bitCount = 0; |
|
unsigned long WiegandReader::_code = 0; |
|
int WiegandReader::_wiegandType = 0; |
I did not try the native integration. The company I built this for is no longer in business. Not sure if the native implementation fixed all the issues I encountered with other people's code.
Sorry didn't see this until now. Any entered codes should appear inside Home Assistant as a scanned tag. Go to where the tags are listed in config and you should see the codes coming through. Another way if you just want to debug this would be to look at the log of the esphome device to see what it is sending over. You could also build a really simple Node-RED or HA automation to trigger when a tag is scanned.