feature-requests icon indicating copy to clipboard operation
feature-requests copied to clipboard

Wiegand interface

Open golfvert opened this issue 5 years ago • 137 comments

Describe the problem you have/What new integration you would like I am using wiegand access. It would be nice to be able to integrate this kind of equipment to ESPHome

Please describe your use case for this integration and alternatives you've tried: At the moment, I am using a home made script. Having one solution for all would be nice.

Additional context

golfvert avatar May 03 '19 11:05 golfvert

I think the RDM6300 uses wiegand - could you try with the rdm6300 integration?

OttoWinter avatar May 08 '19 13:05 OttoWinter

The RDM6300 seems to use a standard UART protocol. Wiegand is its own beast. It would require some interrupt-driven communication for accuracy, but does seem fairly simple.

There's a good library that can figure out the number of bits in the data automagically. If I had time, I'd port it. Any takers?

https://github.com/paulo-raca/YetAnotherArduinoWiegandLibrary

Zebble avatar May 23 '19 00:05 Zebble

That would be very good. I have a weigand reader outside, knowing the tag read would be perfect for integrating it into Home Assistant. Not knowing the tag I have mapped the relay as an ON/OFF, and I will have to put another rfid reader integrated for automations since I want some automation just for my tag and my girlfriend's, not for other tags.

nerdosity avatar Jun 21 '19 18:06 nerdosity

I have a couple of Siemens AR6182-MX readers (w/keypads) that can be configure to use a Wiegand interface, would be interesting to get this hooked up to EspHome.

Tried the YetAnotherArduinoWiegandLibrary and it works on arduino but not ESP8266. Found https://github.com/esprfid/esp-rfid which uses https://github.com/monkeyboard/Wiegand-Protocol-Library-for-Arduino.git but this didn't boot on my NodeMCU board at all. Might poke more later, but in case the links are of interest ☝️

beikeland avatar Jul 15 '19 07:07 beikeland

This fixes the library used in esp-rfid as linked to above https://github.com/monkeyboard/Wiegand-Protocol-Library-for-Arduino/pull/38

Unfortunately I need other bitcounts than standard depending on card type and optional pin length so I'll probably do a AR6182-MX library instead of a generic Wiegand.

beikeland avatar Jul 17 '19 17:07 beikeland

@beikeland I am using esp-rfid project on my nodeMCU since a couple of weeks, just use version 1.0.2 since latest one has problems with MQTT messages for tags.

nerdosity avatar Jul 18 '19 10:07 nerdosity

My primary motivation for looking into using ESPhome was to not rely on MQTT. I've got the library working and sorted out separating Pin, Card and Card+Pin in an Arduino sketch running on a NodeMCU-devkit. So just have to wrap my head around the abstraction needed to write a new component for the Wiegand bus and a sensor or something on top of that, or find that someone beats me to it while trying to understand the ESPhome code:)

beikeland avatar Jul 18 '19 11:07 beikeland

I understand that, I just replied to the fact that it wasn't booting on your nodeMCU ;)

nerdosity avatar Jul 18 '19 11:07 nerdosity

That was due to the missing ICACHE attribute for the interrupt handler in the underlying library. Fixed it.

On Thu, Jul 18, 2019 at 1:34 PM nerdosity [email protected] wrote:

I understand that, I just replied to the fact that it wasn't booting on your nodeMCU ;)

— You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub https://github.com/esphome/feature-requests/issues/211?email_source=notifications&email_token=AAUTS3RPMROPNEK6IKZNRDLQABINZA5CNFSM4HKTC32KYY3PNVWWK3TUL52HS4DFVREXG43VMVBW63LNMVXHJKTDN5WW2ZLOORPWSZGOD2IGHOQ#issuecomment-512779194, or mute the thread https://github.com/notifications/unsubscribe-auth/AAUTS3URDCHZMNLVY22GOADQABINZANCNFSM4HKTC32A .

beikeland avatar Jul 18 '19 11:07 beikeland

Still trying to work out how to add the components to ESPHome, maybe one day. Sharing a gist with the working arduino sketch for esp8266 and AR6182 reader. Reader uses an electric wiegand interface, but custom protocol. https://gist.github.com/beikeland/145b20b1fc585049ea8951eddfc5732e

beikeland avatar Jul 20 '19 13:07 beikeland

I have a bunch of HID Prox readers, would love a component to use them with ESPHome! Currently using them with https://github.com/TomHarkness/homeID

CountParadox avatar Sep 14 '19 07:09 CountParadox

Any news on that? I've bought a keypad that use this protocol and would like to wiring it to an ESP32 running ESPHOME

pricard32 avatar Jan 10 '20 04:01 pricard32

Also interested in using wiegand keypads connected to an ESP8266/ESP32.

edwardscaleb9 avatar Jan 11 '20 03:01 edwardscaleb9

Can you help me building the plugin ?

pricard32 avatar Jan 11 '20 04:01 pricard32

I’d be happy to give it a go, but I’m not much of a programmer these days. More of a scripter I’d say.

While I’m thinking about it, one feature I’d like to see is the ability to set codes/card numbers in HomeAssistant (or whatever frontend) and have them cached locally on the ESP.

edwardscaleb9 avatar Jan 11 '20 13:01 edwardscaleb9

You must need the card number to be passed to home assistant, then you can do what you want with it.

On Sun, 12 Jan 2020, 12:49 am Caleb Edwards, [email protected] wrote:

I’d be happy to give it a go, but I’m not much of a programmer these days. More of a scripter I’d say.

While I’m thinking about it, one feature I’d like to see is the ability to set codes/card numbers in HomeAssistant (or whatever frontend) and have them cached locally on the ESP.

— You are receiving this because you commented. Reply to this email directly, view it on GitHub https://github.com/esphome/feature-requests/issues/211?email_source=notifications&email_token=ABP5BVHK2LB73ZEJCCAAB2DQ5HE53A5CNFSM4HKTC32KYY3PNVWWK3TUL52HS4DFVREXG43VMVBW63LNMVXHJKTDN5WW2ZLOORPWSZGOEIWCK5A#issuecomment-573318516, or unsubscribe https://github.com/notifications/unsubscribe-auth/ABP5BVCRMOCRY5SU5SE65U3Q5HE53ANCNFSM4HKTC32A .

CountParadox avatar Jan 12 '20 12:01 CountParadox

If you want local cache / standalone control, you might want to look at the esprfid project. It can also talk to Mqtt so can also be manually integrated with stuff like Hass.

https://github.com/esprfid/esp-rfid

You don't need their special board, any esp8266 will do. I used it to handle card reading/users and had Hass control the lock. Worked quite well.

Still would be good to have weigand in esphome, to keep everything on one platform.

Sent from Ninehttp://www.9folders.com/


From: Caleb Edwards [email protected] Sent: Saturday, January 11, 2020 8:49 AM To: esphome/feature-requests Cc: Wade J. Weppler; Comment Subject: Re: [esphome/feature-requests] Wiegand interface (#211)

I’d be happy to give it a go, but I’m not much of a programmer these days. More of a scripter I’d say.

While I’m thinking about it, one feature I’d like to see is the ability to set codes/card numbers in HomeAssistant (or whatever frontend) and have them cached locally on the ESP.

— You are receiving this because you commented. Reply to this email directly, view it on GitHubhttps://github.com/esphome/feature-requests/issues/211?email_source=notifications&email_token=AAGOH54GIWWMK5GXAEOSI53Q5HE53A5CNFSM4HKTC32KYY3PNVWWK3TUL52HS4DFVREXG43VMVBW63LNMVXHJKTDN5WW2ZLOORPWSZGOEIWCK5A#issuecomment-573318516, or unsubscribehttps://github.com/notifications/unsubscribe-auth/AAGOH5Y4SLFAYOZPNN365CDQ5HE53ANCNFSM4HKTC32A.

Zebble avatar Jan 12 '20 18:01 Zebble

I'm not interest of just RFID. I would like the complete wiegand protocol. Either Keypad of RDIF will be pass to HA.

And like @edwardscaleb9 said, it would be great to program the keypas via HA.

I've found online a library for Weigand, but dont have the skills to implement it with ESPHOME.

Someone with those skill would want to help us ?

pricard32 avatar Jan 12 '20 22:01 pricard32

I was able to implement a CustomAPIDevice using code from this Github repo without any issues. The code works as is (I had to drop the IRAM_ATTR on the interrupt handlers in order to get it to link but it's working well for me.

I can post the sample custom device code if anyone is interested.

gdoerr avatar Jan 27 '20 02:01 gdoerr

@gdoerr i’m really interested to see how you did it !!

pricard32 avatar Jan 27 '20 04:01 pricard32

Save the code below as wiegand_device.h and include it as a custom component in your YAML file.

custom_component:
  - lambda: |-
       auto wiegand = new WiegandReader(pin0, pin1, "script.to_call");
       return {wiegand};

pin0 should be the GPIO connected to the D0 signal and pin1 is the GPIO connected to the D1 signal. Replace "script.to_call" with the name of the script or service to call. The code entered will be passed as the parameter code.

#include "esphome.h"

/**
 * 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 2,000ms (inter-digit timer) which then sends the code
 *   [3] - the user presses a '*' which sends the '*' immediately
 */
class WiegandReader : public PollingComponent, public CustomAPIDevice {

public:
    WiegandReader(int pinD0, int pinD1, std::string serviceName)
        : PollingComponent(200),
        pinD0(pinD0), pinD1(pinD1),
        serviceName(serviceName) {
    }

    /**
     * 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) {
            if(_code < 10) {
                // We have a digit, make it ASCII for convenience
                keyCodes += (_code + 0x30);
            } else if(_code == 11) {
                // The user pressed '#', send the accumulated code and reset for the next string
                callHAService(keyCodes);
                keyCodes = "";
            } else if(_code == 10) {
                // The user pressed '*', clear the code and send the asterisk
                callHAService("*");
                keyCodes = "";
            }
            // Capture the last time we received a code
            lastCode = millis();
        } else {
            if(keyCodes.length() > 0) {
                // We have a keyCode, see if the interdigit timer expired
                if(millis() - lastCode > 2000) {
                    // The interdigit timer expired, send the code and reset for the next string
                    callHAService(keyCodes);
                    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;
    std::string serviceName;

    /**
     * Calls a Home Assistant service with the key code
     * @param keyCode
     */
    void callHAService(std::string keyCode) {
        call_homeassistant_service(serviceName.c_str(), {
                {"code", keyCode.c_str()}
        });
    }

    /**
     * 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;

gdoerr avatar Jan 27 '20 05:01 gdoerr

Wow @gdoerr , that's a greate job you did! Thanks ! Since i'm pretty new to this, and i've buy the exact same keypad as you, can you help me with something more. I'll like to push the data into a text_sensor: can you explain me what I have to that to the yaml and .h file?

Thanks again!

pricard32 avatar Jan 27 '20 12:01 pricard32

And i'm using a ESP-32 for now, can I use any GPIO pin ?

pricard32 avatar Jan 27 '20 12:01 pricard32

You'd have to restructure the Wiegand class into a TextSensor. I don't recommend this because you're not interested in state, you really want the event which is why I call a service. You should be able to find the example code on esphome to implement a custom text sensor.

You should be able to use any GPIO pins on the ESP32 but the order is important...swap D0 and D1 if your're getting weird results.

Good Luck!

gdoerr avatar Jan 27 '20 12:01 gdoerr

What would you recommend to write in the script to call to received as a event ?

pricard32 avatar Jan 27 '20 12:01 pricard32

You can use either a yaml script or a python script.

I use a python script to validate the code sequences against a dedicated outlook.com calendar to control access to my garage.

gdoerr avatar Jan 27 '20 13:01 gdoerr

Or maybe only share your how implentation, that's help me reverse engineering and understand or to implement it for my use.

pricard32 avatar Jan 27 '20 13:01 pricard32

I've tried to change the class declaration from CustomApiDevice to TextSensor and add PublishState(keyCode) to CallHaService without success

pricard32 avatar Jan 28 '20 05:01 pricard32

@pricard32 Rather than mucking up this feature request thread with a specific problem, perhaps you can post your question on Stack Overflow which is better suited and then post a link to the problem here.

I'm happy to help but you'll need to provide more information than you tried something without success. When you ask the question, be sure and post exactly what you tried and what errors you're getting. Otherwise it's difficult to help you without writing the software for you...

gdoerr avatar Jan 28 '20 15:01 gdoerr

@gdoerr great work! Solves the immediate problem and really helped me understand how custom components work in esphome. Well done, and thank you.

Zebble avatar Jan 28 '20 16:01 Zebble