|
#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; |
Would it be possible to get your help? I have been using code that works great on the esp8266 but i want to move over to the esp32 poe for the same reason as you.
I am getting the correct code but no way to compare it. Is there anyway to get the code pressed from the custon componet and pass it along to a text sensor?
text_sensor:
id: reader2
lambda: |-
auto wiegand = new WiegandReader(15, 14);
App.register_component(wiegand);
return {wiegand};
text_sensors:
id: reader
name: ${keypad_name} Code
on_value: