From b2178bc3721a307131c3f4e9628e2ef3374ab0b4 Mon Sep 17 00:00:00 2001 From: sle Date: Tue, 16 Mar 2021 18:58:31 +0100 Subject: [PATCH] support now several key pad strokes as one tag switchable by SetOption124 and added a hardware emulation for Wiegand reader ( will be only compiled in a debug mode) --- tasmota/settings.h | 2 +- tasmota/xsns_82_wiegand.ino | 321 ++++++++++++++++++++++++++++++------ 2 files changed, 267 insertions(+), 56 deletions(-) diff --git a/tasmota/settings.h b/tasmota/settings.h index 25b0145da..7dd8dbc29 100644 --- a/tasmota/settings.h +++ b/tasmota/settings.h @@ -149,7 +149,7 @@ typedef union { // Restricted by MISRA-C Rule 18.4 bu uint32_t mqtt_state_retain : 1; // bit 7 (v9.3.0.1) - CMND_STATERETAIN uint32_t mqtt_info_retain : 1; // bit 8 (v9.3.0.1) - CMND_INFORETAIN uint32_t wiegand_hex_output : 1; // bit 9 (v9.3.1.1) - SetOption123 - (Wiegand) switch tag number output to hex format (1) - uint32_t spare10 : 1; // bit 10 + uint32_t wiegand_keypad_to_tag : 1; // bit 10 (v9.3.1.1) - SetOption124 - (Wiegand) send key pad stroke as single char (0) or one tag (ending char #) (1) uint32_t spare11 : 1; // bit 11 uint32_t spare12 : 1; // bit 12 uint32_t spare13 : 1; // bit 13 diff --git a/tasmota/xsns_82_wiegand.ino b/tasmota/xsns_82_wiegand.ino index 4fff9c5d1..70f87b9c8 100644 --- a/tasmota/xsns_82_wiegand.ino +++ b/tasmota/xsns_82_wiegand.ino @@ -41,29 +41,47 @@ * - fix for #11047 Wiegand 26/34 missed some key press if they are press at normal speed * - removed testing code for tests without attached hardware * - added SetOption123 0-Wiegand UID decimal (default) 1-Wiegand UID hexadecimal - * - added SetOption124 0-Keypad every key a single tag (default) 1-all keys up to ending char (#) send as one tag + * - added SetOption124 0-all keys up to ending char (# or *) send as one tag by MQTT (default) 1-Keypad every key a single tag + * - added a new realtime testing option emulating a Wiegang reader output on same GPIOs where normally reader is attached. Details below \*********************************************************************************************/ -#warning **** Wiegand interface enabled **** +#pragma message("**** Wiegand interface enabled ****") #define XSNS_82 82 #define WIEGAND_CODE_GAP_FACTOR 3 // Gap between 2 complete RFID codes send by the device. (WIEGAND_CODE_GAP_FACTOR * bitTime) to detect the end of a code -#define WIEGAND_BIT_TIME_DEFAULT 1250 // period time of one bit (impluse + impulse_gap time) 1250µs measured by oscilloscope on my RFID Reader -#define WIEGAND_RFID_ARRAY_SIZE 5 // storage of rfids found between 2 calls of FUNC_EVERY_100_MSECOND +#define WIEGAND_BIT_TIME_DEFAULT 1250 // period time (µs) of one bit (impluse + impulse_gap time) 1250µs measured by oscilloscope on my RFID Reader +#define WIEGAND_RFID_ARRAY_SIZE 11 // storage of rfids found between 2 calls of FUNC_EVERY_100_MSECOND #define WIEGAND_OPTION_HEX 123 // Index of option to switch output between hex (1) an decimal (0) (default) +#define WIEGAND_OPTION_HEX_POSTFIX "h" // will be added after UID output nothing = "" +#define WIEGAND_OPTION_KEYPAD_TO_TAG 124 //Index of option to switch output of key pad strokes between every single stroke one single char (0) (default) + // or all strokes until detecting ending char (WIEGAND_OPTION_KEYPAD_END_CHAR) as one tag (1) -// using #define will save some space in the final code -// DEV_WIEGAND_TEST_MODE 2 : testing with hardware correctly connected. #define DEV_WIEGAND_TEST_MODE 0 +// using #define will save some space in the final code +// DEV_WIEGAND_TEST_MODE 1 : Use only without Wiegand reader device attache. On a second ESP to simulate reader output! +// DEV_WIEGAND_TEST_MODE 2 : testing with hardware correctly connected. #ifdef DEV_WIEGAND_TEST_MODE #if (DEV_WIEGAND_TEST_MODE==0) #elif (DEV_WIEGAND_TEST_MODE==1) - #warning "(no longer available) Wiegand Interface compiled with 'DEV_WIEGAND_TEST_MODE' 1 (Random RFID)" + #pragma message("\nWiegand Interface code generator (testing purpose only!) compiled with 'DEV_WIEGAND_TEST_MODE' 1 \nUse only on esp WITHOUT Wiegand reader hardware attached! GPIOS will be configured as OUTPUT!" ) + // use on own risk for testing purpose only. + // please don't attach your reader to the ESP when you use this option. The GPIOS will be defined as OUTPUT + // the interrupts will be enabled and normally recognize the generated code, that's the idea behind for testing. + // Commands: + // WieBitTime [time] : get or set the bit impuls length + // WieInterBitTime [time]: get or set the length of the gap between 2 bits + // WieTagGap [tagGap]: get or set the current used gap time between 2 tags send in µs minimal WIEGAND_BIT_TIME_DEFAULT µs default WIEGAND_BIT_TIME_DEFAULT * WIEGAND_CODE_GAP_FACTOR + // WieTagSize [tagsize]: get or set the tagsize (4,8,24,26,32,34) default 26. + // WieTag [tag]: get or set the current used tag. For tagsize 4,8 only one char will be used. + // WieSend [tag[:tagsize];tag[:tagsize];...] : Generate the current Tag with current TagSize to GPIOs if the paramters are used + // tags and tagsize from commandline are used as current values. If tagsize is omitted always last value will be used + // WieSend 4:4;5:8; will send 4 in 4 bit mode and 5 in 8 bit mode with a pause of current TagGab between the chars + // WieSend will send the last used tag with last used tagsize #elif (DEV_WIEGAND_TEST_MODE==2) - #warning "Wiegand Interface compiled with 'DEV_WIEGAND_TEST_MODE' 2 (Hardware connected)" + #pragma message("\nWiegand Interface compiled with 'DEV_WIEGAND_TEST_MODE' 2 (Hardware connected)") #else - #warning "Wiegand Interface compiled with unknown mode" + #pragma message("\nWiegand Interface compiled with unknown mode") #endif #endif @@ -71,7 +89,6 @@ typedef struct rfid_store { uint64_t RFID; uint16_t bitCount; } RFID_store; class Wiegand { public: - Wiegand(void); void Init(void); void ScanForTag(void); @@ -81,21 +98,24 @@ class Wiegand { bool isInit = false; - private: - //uint64_t HexStringToDec(uint64_t); + #if (DEV_WIEGAND_TEST_MODE!=1) + private: + #endif //(DEV_WIEGAND_TEST_MODE==1) uint64_t CheckAndConvertRfid(uint64_t,uint16_t); - char translateEnterEscapeKeyPress(char); uint8_t CalculateParities(uint64_t, int); bool WiegandConversion (uint64_t , uint16_t ); void setOutputFormat(void); // fix output HEX format + void HandleKeyPad(void); //handle one tag for multi key strokes static void handleD0Interrupt(void); static void handleD1Interrupt(void); static void handleDxInterrupt(int in); // fix #11047 uint64_t rfid; - uint8_t tagSize; - char outFormat; + uint32_t tagSize; + const char* outFormat; + uint64_t mqttRFIDKeypadBuffer; + uint64_t webRFIDKeypadBuffer; static volatile uint64_t rfidBuffer; static volatile uint16_t bitCount; @@ -107,7 +127,6 @@ class Wiegand { static volatile bool CodeComplete; static volatile RFID_store rfid_found[]; static volatile int currentFoundRFIDcount; - }; Wiegand* oWiegand = new Wiegand(); @@ -141,7 +160,9 @@ Wiegand::Wiegand() { rfid_found[i].RFID=0; rfid_found[i].bitCount=0; } - outFormat='u'; // standard output format decimal + outFormat="u"; // standard output format decimal + mqttRFIDKeypadBuffer = 0; + webRFIDKeypadBuffer = 0; } void ICACHE_RAM_ATTR Wiegand::handleD1Interrupt() { // Receive a 1 bit. (D0=high & D1=low) @@ -202,6 +223,10 @@ void Wiegand::Init() { #endif // DEV_WIEGAND_TEST_MODE>0 pinMode(Pin(GPIO_WIEGAND_D0), INPUT_PULLUP); pinMode(Pin(GPIO_WIEGAND_D1), INPUT_PULLUP); +#if (DEV_WIEGAND_TEST_MODE==1) // overwrite the setting + pinMode(Pin(GPIO_WIEGAND_D0), OUTPUT); + pinMode(Pin(GPIO_WIEGAND_D1), OUTPUT); +#endif //(DEV_WIEGAND_TEST_MODE==1) attachInterrupt(Pin(GPIO_WIEGAND_D0), handleD0Interrupt, FALLING); attachInterrupt(Pin(GPIO_WIEGAND_D1), handleD1Interrupt, FALLING); isInit = true; // Helps to run only if correctly setup @@ -286,27 +311,8 @@ uint8_t Wiegand::CalculateParities(uint64_t tagWithoutParities, int tag_size=26) return retValue; } -char Wiegand::translateEnterEscapeKeyPress(char oKeyPressed) { - switch(oKeyPressed) { - case 0x0b: // 11 or * key - return 0x0d; // 13 or ASCII ENTER - - case 0x0a: // 10 or # key - return 0x1b; // 27 or ASCII ESCAPE - - default: - return oKeyPressed; - } -} - bool Wiegand::WiegandConversion (uint64_t rfidBuffer, uint16_t bitCount) { bool bRet = false; - // unsigned long nowTick = micros(); - // Add a maximum wait time for new bits - // unsigned long diffTicks = nowTick - lastFoundTime; - // unsigned long inter_code_gap = WIEGAND_CODE_GAP_FACTOR * bitTime; - // if ((diffTicks > inter_code_gap) && (diffTicks >= 1000000 )) { // Max. 4-8 secs between 2 bits comming in. depends on micros() resolution - #if (DEV_WIEGAND_TEST_MODE)>0 AddLog(LOG_LEVEL_INFO, PSTR("WIE: Raw tag %llu, Bit count %u"), rfidBuffer, bitCount); #endif // DEV_WIEGAND_TEST_MODE>0 @@ -318,7 +324,7 @@ bool Wiegand::WiegandConversion (uint64_t rfidBuffer, uint16_t bitCount) { } else if (4 == bitCount) { // 4-bit Wiegand codes for keypads - rfid = (int)translateEnterEscapeKeyPress(rfidBuffer & 0x0000000F); + rfid = (int)(rfidBuffer & 0x0000000F); tagSize = bitCount; bRet = true; } @@ -329,16 +335,15 @@ bool Wiegand::WiegandConversion (uint64_t rfidBuffer, uint16_t bitCount) { char highNibble = (rfidBuffer & 0xf0) >>4; char lowNibble = (rfidBuffer & 0x0f); if (lowNibble == (~highNibble & 0x0f)) { // Check if low nibble matches the "NOT" of high nibble. - rfid = (int)translateEnterEscapeKeyPress(lowNibble); + rfid = (int)(lowNibble); bRet = true; } else { - // lastFoundTime = nowTick; bRet = false; } tagSize = bitCount; } else { // Time reached but unknown bitCount, clear and start again - // lastFoundTime = nowTick; + tagSize = 0; bRet = false; } #if (DEV_WIEGAND_TEST_MODE)>0 @@ -349,8 +354,31 @@ bool Wiegand::WiegandConversion (uint64_t rfidBuffer, uint16_t bitCount) { void Wiegand::setOutputFormat(void) { - if (GetOption(WIEGAND_OPTION_HEX) == 0) { outFormat = 'u'; } - else { outFormat = 'X'; } + if (GetOption(WIEGAND_OPTION_HEX) == 0) { outFormat = "u"; } + else { outFormat = "X" WIEGAND_OPTION_HEX_POSTFIX ; } +} + +void Wiegand::HandleKeyPad(void) { // will be called if a valid key pad input was recognized + if (GetOption(WIEGAND_OPTION_KEYPAD_TO_TAG) == 0) { // handle all key pad inputs as ONE Tag until # is recognized + if ( (tagSize == 4) || (tagSize == 8) ) { + //only handle Keypad strokes if it is requested + if (rfid >= 0x0a) { // # * as end of input detected -> all key values which are larger than 9 + rfid = mqttRFIDKeypadBuffer; // original tagsize of 4 or 8 will be kept. + webRFIDKeypadBuffer = 0; // can be resetted, because now rfid > 0 will be used at web interface + mqttRFIDKeypadBuffer = 0; + } + else { + mqttRFIDKeypadBuffer = (mqttRFIDKeypadBuffer*10)+rfid; //left shift + new key + webRFIDKeypadBuffer = mqttRFIDKeypadBuffer; // visualising the current typed keys + rfid = 0; + tagSize = 0; + } + } + else { //it's not a key pad entry, so another key come in, we will reset the buffer, if it is not finished yet + webRFIDKeypadBuffer = 0; + mqttRFIDKeypadBuffer = 0; + } + } } void Wiegand::ScanForTag() { @@ -364,7 +392,7 @@ void Wiegand::ScanForTag() { // format MQTT output setOutputFormat(); char sFormat[50]; - snprintf( sFormat, 50, PSTR(",\"Wiegand\":{\"UID\":%%0ll%c,\"" D_JSON_SIZE "\":%%%c}}"), outFormat, outFormat); + snprintf( sFormat, 50, PSTR(",\"Wiegand\":{\"UID\":%%0ll%s,\"" D_JSON_SIZE "\":%%%s}}"), outFormat, outFormat); for (int i= 0; i < WIEGAND_RFID_ARRAY_SIZE; i++) { @@ -375,11 +403,14 @@ void Wiegand::ScanForTag() { AddLog(LOG_LEVEL_INFO, PSTR("WIE: Previous tag %llu"), oldTag); #endif // DEV_WIEGAND_TEST_MODE>0 if (validKey) { // Only in case of valid key do action. Issue#10585 - if (oldTag == rfid) { - AddLog(LOG_LEVEL_DEBUG, PSTR("WIE: Old tag")); + HandleKeyPad(); //support one tag for multi key input + if (tagSize>0) { //do output only for rfids which are complete + if (oldTag == rfid) { + AddLog(LOG_LEVEL_DEBUG, PSTR("WIE: Old tag")); + } + ResponseTime_P(sFormat, rfid, tagSize); + MqttPublishTeleSensor(); } - ResponseTime_P(sFormat, rfid,tagSize); - MqttPublishTeleSensor(); } rfid_found[i].RFID=0; rfid_found[i].bitCount=0; @@ -399,17 +430,192 @@ void Wiegand::ScanForTag() { #ifdef USE_WEBSERVER void Wiegand::Show(void) { - setOutputFormat(); - char sFormat [30]; - snprintf( sFormat, 30,PSTR("{s}Wiegand UID{m}%%ll%c {e}"), outFormat); - WSContentSend_PD(sFormat, rfid); - //WSContentSend_PD(PSTR("{s}Wiegand UID{m}%llX {e}"), rfid); -#if (DEV_WIEGAND_TEST_MODE)>0 - AddLog(LOG_LEVEL_INFO, PSTR("WIE: Tag %llu, Bits %u"), rfid, bitCount); -#endif // DEV_WIEGAND_TEST_MODE>0 + setOutputFormat(); + char sFormat [30]; + snprintf( sFormat, 30,PSTR("{s}Wiegand UID{m}%%ll%s {e}"), outFormat); + if (tagSize>0) { WSContentSend_PD(sFormat, rfid); } + else { WSContentSend_PD(sFormat, webRFIDKeypadBuffer); } + + #if (DEV_WIEGAND_TEST_MODE)>0 + AddLog(LOG_LEVEL_INFO, PSTR("WIE: Tag %llu, Bits %u"), rfid, bitCount); + #endif // DEV_WIEGAND_TEST_MODE>0 + } #endif // USE_WEBSERVER +#if (DEV_WIEGAND_TEST_MODE==1) + void CmndTag(void); + void CmndTagSize(void); + void CmndTagGap(void); + void CmndTimeReset(void); + void CmndAllReset(void); + void CmndSend(void); + void CmndBitTime(void); + void CmndInterBitTime(void); + unsigned int setTagSize( char *); + unsigned int setTag ( char * ); + void sendBit(unsigned int b); + void sendTag(uint32_t Tag, uint32_t TagSize); + + uint32_t currTag = 0; + uint32_t currTagSize = 26; //default value 26 Wiegand + uint32_t currBitTime=(WIEGAND_BIT_TIME_DEFAULT/10); //length of the bit impluse in µs + uint32_t currInterBitTime = ((WIEGAND_BIT_TIME_DEFAULT/10)*9); //time to wait before next bit is send in µs + uint32_t currTagGabTime = (WIEGAND_BIT_TIME_DEFAULT * WIEGAND_CODE_GAP_FACTOR) ; //time to wait before next tag is send in µs + + + void CmndTag(void){ + if (XdrvMailbox.data_len > 0) { + currTag= strtoul(XdrvMailbox.data, nullptr, 0); + } + ResponseCmndNumber(currTag); + } + void CmndTagSize(void){ + if (XdrvMailbox.data_len > 0) { + currTagSize = setTagSize(XdrvMailbox.data); + } + ResponseCmndNumber(currTagSize); + } + void CmndTagGap(void){ + if (XdrvMailbox.data_len > 0) { + currTagGabTime = strtoul(XdrvMailbox.data, nullptr, 0); + if (currTagGabTime < (currBitTime+currInterBitTime) ) // doesn't make sense + { currTagGabTime = (currBitTime+currInterBitTime) * WIEGAND_CODE_GAP_FACTOR; } + } + ResponseCmndNumber(currTagGabTime); + } + void CmndBitTime(void){ + if (XdrvMailbox.data_len > 0) { + uint32_t newBitTime = strtoul(XdrvMailbox.data, nullptr, 0); + if ( (newBitTime >= 100) && (newBitTime <= 500000) ) // accept only values between 100µs and 5s + { currBitTime = newBitTime; } + } + ResponseCmndNumber(currBitTime); + } + void CmndInterBitTime(void){ + if (XdrvMailbox.data_len > 0) { + uint32_t newInterBitTime = strtoul(XdrvMailbox.data, nullptr, 0); + if ( (newInterBitTime >= currBitTime) && (newInterBitTime <= (100 * currBitTime)) ) // accept only values between 100µs and 5s + { currInterBitTime = newInterBitTime; } + } + ResponseCmndNumber(currInterBitTime); + } + void CmndTimeReset(void){ + currBitTime=(WIEGAND_BIT_TIME_DEFAULT/10); + currInterBitTime = ((WIEGAND_BIT_TIME_DEFAULT/10)*9); + currTagGabTime = (WIEGAND_BIT_TIME_DEFAULT * WIEGAND_CODE_GAP_FACTOR) ; + ResponseCmndChar_P(PSTR("All timings reset to default!")); + } + void CmndAllReset(void){ + CmndTimeReset(); + currTagSize = 26; + ResponseCmndChar_P(PSTR("All timings and tag size reset to default")); + } + void CmndSend(void){ + if (XdrvMailbox.data_len > 0) { // parameter [tag[:tagsize];tag[:tagsize];...] + char *parameter = strtok(XdrvMailbox.data, ";"); + while (parameter != nullptr) { + char* pTagSize = strchr(parameter,':'); // find optional tagsizes + if (pTagSize != 0) { // 2 parameters found tag:tagsize + *pTagSize = 0; //replace separator ':' by \0 string end + currTag = setTag(parameter); // is now ending before tagsize + pTagSize++; //set the starting char of tagsize correctly + currTagSize = setTagSize(pTagSize); + ResponseCmndChar(pTagSize); + } + else {//only one parameter (tag) found + currTag = setTag(parameter); + } + ResponseCmndChar(parameter); + sendTag(currTag, currTagSize); + ResponseCmndNumber(currTag); + parameter = strtok(nullptr, ";"); + } + } + else { // send last used values again + sendTag(currTag, currTagSize); + ResponseCmndNumber(currTag); + } + } + unsigned int setTag ( char * newTag) { + unsigned int retValue = strtoul(newTag, nullptr, 0); + if ( (currTagSize == 4) || (currTagSize == 8) ) //key pad input simulation requested + { retValue &= 0x0F; } + return retValue; + } + unsigned int setTagSize ( char * newTagSize) { + unsigned int retValue = strtoul(newTagSize, nullptr, 0); + // accept only supported TagSize + if ( retValue <= 4) { retValue = 4;} + else if ( retValue <= 8) { retValue = 8;} + else if ( retValue <= 24) { retValue = 24;} + else if ( retValue <= 26) { retValue = 26;} + else if ( retValue <= 32) { retValue = 32;} + else if ( retValue <= 34) { retValue = 34;} + else { retValue = 26;} //default value + return retValue; + } + void sendBit(unsigned int b) { + int sel = (b == 0) ? Pin(GPIO_WIEGAND_D0) : Pin(GPIO_WIEGAND_D1); + digitalWrite(sel, 0); + delayMicroseconds(currBitTime); // bit impuls time + digitalWrite(sel, 1); + delayMicroseconds(currBitTime+currInterBitTime); // bit + inter bit gap time + } + void sendPlainTag( uint32_t pTag, uint32_t pTagSize){ // send tag without parity + for (int i=1; i<=pTagSize; ++i) + { + sendBit((pTag >> (pTagSize-i)) & 1); + } + } + void sendTag(uint32_t Tag, uint32_t TagSize) { + // TagSize is the requested output tagSize. means b.e. 24 bit == 24 Tag without parity 26 bit = 24 bit with parity bits + // supported tag sizes 4/8 for key pad simulation 24/26 and 32/34 for RFID tags + switch (TagSize){ + case 24: + case 32: + case 4: + sendPlainTag( Tag, TagSize); + break; + case 26: + case 34: + uint8_t parity; + parity = oWiegand->CalculateParities(Tag, TagSize); + sendBit(parity & 0x01); //even parity (starting parity) + sendPlainTag( Tag, TagSize-2); + sendBit(parity & 0x80); //odd parity (ending parity) + break; + case 8: // high nibble is ~ low nibble + Tag = Tag & 0x0F; // low nibble in case of more the one char input it will be cut here + Tag = Tag | ((~Tag) << 4); + sendPlainTag ( Tag, TagSize); + break; + } + //delay to simulate end of tag + delayMicroseconds(currTagGabTime); // inter code gap + return; +} +const char kWiegandCommands[] PROGMEM = "Wie|" // No prefix + "Tag|" + "TagSize|" + "TagGap|" + "BitTime|" + "InterBitTime|" + "TimeReset|" + "AllReset|" + "Send"; + + void (* const WiegandCommand[])(void) PROGMEM = { + &CmndTag, + &CmndTagSize, + &CmndTagGap, + &CmndBitTime, + &CmndInterBitTime, + &CmndTimeReset, + &CmndAllReset, + &CmndSend + }; +#endif //(DEV_WIEGAND_TEST_MODE==1) /*********************************************************************************************\ * Interface \*********************************************************************************************/ @@ -430,6 +636,11 @@ bool Xsns82(byte function) { oWiegand->Show(); break; #endif // USE_WEBSERVER +#if (DEV_WIEGAND_TEST_MODE==1) + case FUNC_COMMAND: + result = DecodeCommand(kWiegandCommands, WiegandCommand); + break; +#endif //(DEV_WIEGAND_TEST_MODE==1) } } return result;