Wiegand protocol RFID readers known to work with ESPHome?

Purchased this inexpensive RFID reader from Amazon. Using a Wemos D1 Mini Pro (authentic Wemos device) and pins D1 and D2 for the Wiegand D0 and D1 signal inputs. Despite using two different code sources, I’m still not getting any output from the reader. :frowning:

Anybody have experience using a Wiegand-protocol RFID reader device in ESPHome that is outdoor-rated? The HID mullion-style device is significantly more expensive, but if it works with ESPHome, that’s worth it.

2 Likes

there is no direct support. but there is solution

I’m looking for a somewhat simpler device interface, not requiring an additional Arduino in the configuration. Thank you for that link, I’ve seen it before.

I’m going to try using a TXS0108E bi-directional level shifter to work the D0 and D1 data bit lines and also the LED and BEEP lines. Essentially, I want something that is ESP8266 only (no Arduino) and only one set of code, all contained within ESPHome.

The TXS0108E level shifter hasn’t arrived yet, but I had a simple level shifter sitting around from a previous effort. It didn’t seem to help in the least. No data received, no scans noted. :frowning:

Well, one thing I’ve definitely determined. That first inexpensive RFID reader? It’s not reliable AT ALL. Only registers a tag being read one out of seven or eight attempts. This inexpensive RFID reader is reliably registering tags at just about 100%, every swipe anywhere near it.

Bad thing is, ESPHome isn’t decoding / registering the Wiegand data. Using the gdoerr code from github feature-request 211. Reader beeps, LED goes green for a second, nothing registers. I’m supplying 12VDC to the reader, which makes me wonder how high the signal levels are on the D0 and D1 data pins. D0 and D1 sit about 4.68VDC. I don’t have a scope but do have a meter, and need to check them. If the signal doesn’t drop below 3V when a tag is scanned, I think that’s the issue. The cheap meter isn’t nearly fast enough to see the size of the momentary voltage drop. It only drops about .01V when a tag is read.

Well, after some extensive additions of ESP_LOGD macros to the code, it quickly became apparent, the code wasn’t working. It was geared more toward accumulating keypad/button presses than scanning multi-digit tags/fobs in a single swipe. The reader was in fact interrupting on the D0 and D1 lines, and a valid decimal value was being received by the code.

With some extremely valuable assistance from the ESPHome Discord channel tonight (thank you @MauriceM and @ssieb for being the brick wall against which I beat my head) I’ve got this reader working, and firing the tag_scanned event in Home Assistant.


sensor:
  platform: custom
  lambda: |-
      auto wiegand = new WiegandReader(D6, D7);
      App.register_component(wiegand);
      return {wiegand};

  sensors:
    name: "Card ID"
    on_value: 
    - then:
      - homeassistant.tag_scanned: !lambda 'return uint32_to_string(x);'
1 Like

hey if you could, can you share your wiring for this project?

I wired the RFID reader as simply as possible, using only ground and power and the two data lines. There are two other lines that could be used with relays to activate the different color LEDs by grounding them, but the relays would have added more size to the unit inside the house.

I use a 12 VDC wall-wart with a DC-to-DC buck convertor to step down to 5VDC for powering the Wemos D1 mini, while the 12VDC powers the RFID reader. Green and white data lines from the RFID reader attach to Wemos D1 mini pins D6 and D7. The call to the Wiegand subroutine passes D6 and D7 as arguments.

Hope this helps!

sensor:
  platform: custom
  lambda: |-
      auto wiegand = new WiegandReader(D6, D7);
      App.register_component(wiegand);
      return {wiegand};
1 Like

bro this is awesome!!! thank you, im looking at using this UHPPOTE Proximity RFID Card Reader that uses the same protocol, and i think it will line up the same way, but we will see.

If you want to use relays to ground the brown and yellow wires to light the leds, that’s a bit more code in ESPHome, but using a combo ESP8266 with two relays board makes it easier. The downside is a larger enclosure to place somewhere close to the reader. I stuck with just the Wemos D1 Mini and power convertor inside a small plastic project box.

hey one more random question. How would you wire the relays with the led lights? Could i just run the brown and yellow wires to digital inputs on the board?

The LED signal lines activate the LEDs when they are connected to GROUND. Therefore, you’d connect a GROUND to the center/common line of the relay, and the LED signal line to the normally open NO line of the relay. Then you’d use any desired output GPIO line as the input to the relay. When you turn on the GPIO line, the relay fires and connects the LED signal line to GROUND, lighting the LED.

Hello, can you leave me the example of the wiegand_device.h file? Thank you

#include "esphome.h"

/**
 * Wiegand Reader Custom Device
 *
 * Adapted from Greg Doerr (https://github.com/gdoerr)
 * Sourced from https://github.com/monkeyboard/Wiegand-Protocol-Library-for-Ardu
ino
 *
 */
class WiegandReader : public PollingComponent, public Sensor {

public:
    const char* TAG = "Wiegand";

    WiegandReader(int pinD0, int pinD1) : PollingComponent(200), pinD0(pinD0), p
inD1(pinD1) {
    }

    /**
     * Initial setup
     */
    void setup() override {
        _lastWiegand = 0;
        _cardTempHigh = 0;
        _cardTemp = 0;
        _code = 0;
        _wiegandType = 0;
        _bitCount = 0;
        _holdBitCount = 0;

        // Configure the input pins
        //
        pinMode(pinD0, INPUT);
        pinMode(pinD1, INPUT);

        // Attach the interrupts
        // Wiegand protocol uses FALLING
        //
        attachInterrupt(digitalPinToInterrupt(pinD0), ReadD0, FALLING);  // Hard
ware interrupt - high to low pulse
        attachInterrupt(digitalPinToInterrupt(pinD1), ReadD1, FALLING);  // Hard
ware interrupt - high to low pulse
    }

    void update() override {
        //
        // See if we have a valid code
        //
        noInterrupts();
        bool rc = DoWiegandConversion();
        interrupts();

        if (rc)
        {
            ESP_LOGV(TAG, "_holdBitCount = %i", _holdBitCount);
            ESP_LOGD(TAG, "resultCode  = %s",  rc ? "true" : "false" );
            ESP_LOGD(TAG, "Tag scanned = %i", _code);

            ESP_LOGV(TAG, "_cardTemp     = %i", _cardTemp);
            ESP_LOGV(TAG, "_cardTempHigh = %i", _cardTempHigh);
            ESP_LOGV(TAG, "_wiegandType  = %i", _wiegandType);
            ESP_LOGV(TAG, "_bitCount     = %i", _bitCount);

            publish_state(_code);

            char buf[16];
            sprintf(buf, "%0lX", _code);

            char hex[16];
            sprintf(hex, "%02lX:%02lX:%02lX:%02lX", (_code >> 24), (_code >> 16
& 0xff), (_code >> 8 & 0xff), (_code & 0xff));

            ESP_LOGD(TAG, "Tag ID = %s  %s", buf, hex);
        }
    }

private:
    static volatile unsigned long _cardTempHigh;
    static volatile unsigned long _cardTemp;
    static volatile unsigned long _lastWiegand;
    static volatile int _bitCount;
    static volatile int _holdBitCount;

    static int _wiegandType;
    static unsigned long _code;

    unsigned long lastCode = 0;
    std::string keyCodes = "";

    int pinD0;
    int pinD1;
    std::string serviceName;


    /**
     * D0 Interrupt Handler
     */
    static void ICACHE_RAM_ATTR ReadD0()
    {
        _bitCount++;                                            // Increment bit
 count for Interrupt connected to D0
        if (_bitCount > 31)                                     // If bit count
more than 31, process high bits
        {                                                       // shift value t
o high bits
            _cardTempHigh |= ((0x80000000 & _cardTemp)>>31);
            _cardTempHigh <<= 1;
            _cardTemp <<=1;
        }
         else
        {
            _cardTemp <<= 1;            // D0 represent binary 0, so just left s
hift card data
        }
        _lastWiegand = millis();        // Save the time the last wiegand bit wa
s received
     }

    /**
     * D1 Interrupt Handler
     */
    static void ICACHE_RAM_ATTR 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 t
o high bits
            _cardTempHigh <<= 1;
            _cardTemp |= 1;
            _cardTemp <<=1;
        }
        else
        {
            _cardTemp  |= 1;            // D1 represent binary 1, so OR card dat
a with 1 then
            _cardTemp <<= 1;            // left shift card data
        }
        _lastWiegand = millis();        // Save the time the last wiegand bit wa
s 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==24)
            return (*codelow & 0x7FFFFFE) >>1;

        if (bitlength==26)                              // EM tag
            return (*codelow & 0x1FFFFFE) >>1;

        if (bitlength==32)
            return (*codelow & 0x7FFFFFE) >>1;

        if (bitlength==34)                              // Mifare
        {
            *codehigh = *codehigh & 0x03;               // only need the 2 LSB o
f the codehigh
            *codehigh <<= 30;                           // shift 2 LSB to MSB
            *codelow >>=1;
            return *codehigh | *codelow;
        }
        return *codelow;                                // EM tag or Mifare with
out parity bits
    }

    /**
     * Convert the received bitstream
     * @return
     */
    bool DoWiegandConversion () {
        unsigned long cardID;
        unsigned long sysTick = millis();

        _holdBitCount = _bitCount;

        if ((sysTick - _lastWiegand) > 25)
                        // if no more signal coming through after 25ms
        {
            if ((_bitCount==24) || (_bitCount==26) ||   //  Wiegand 26 = 24 or 2
6
                (_bitCount==32) || (_bitCount==34) ||   //  Wiegand 34 = 32 or 3
4
                (_bitCount==8)  || (_bitCount==4))      //  keypress = 4 or 8
            {
                _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 3
2 bits, shift high bits right to make adjustment
                    _cardTempHigh >>= 1;

                if (_bitCount==8) {                     // keypress wiegand with
 integrity
                                                        // 8-bit Wiegand keyboar
d 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 ju
st
                    // 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;
volatile int WiegandReader::_holdBitCount = 0;
unsigned long WiegandReader::_code = 0;
int WiegandReader::_wiegandType = 0;

YAML Sensor definition code:

sensor:
  platform: custom
  lambda: |-
      auto wiegand = new WiegandReader(D6, D7);
      App.register_component(wiegand);
      return {wiegand};

  sensors:
    name: "Card ID"
    on_value:
      then:
      - logger.log: "Triggered"
      - homeassistant.tag_scanned: !lambda 'return to_string(x);'
1 Like

Connect the data inputs to the indicated pins but I don’t get a response on each read. Am I supposed to integrate it into a script or should it appear as a sensor?

If your reader recognizes tags correctly, it should register a tag ID with the code, which is then passed to HomeAssistant by the final line of the YAML. That line fires the tag_scanned event, submitting the decimal value returned by the tag scanner.

homeassistant.tag_scanned: !lambda 'return to_string(x);'

If your scanner isn’t working as expected, you may have to introduce some troubleshooting/debugging code into the .h file, and investigate. What reader are you using?

I am using an sf7 rfid, I also did the test with another reader. I don’t get decimals in the readings.

I see the device at Fingerprint & RFID Controller (SF7-W)-Access Control|Time Attendance |Door Lock|Hotel Lock System|Locker Lock and it shows the following:
WHITE NO
GREEN NC

These should be the two Wiegand data pins, connected to pins D6 and D7 on the ESP8266. You must also connect

BLACK GND
BLACK COM

to the GND ground pin on the ESP8266.

Does the device detect the card-swipe, flash a LED, and beep?

this device is standalone and does not interact with anything else!! integration is not possible

Thank you very much !!! It has worked, it was necessary to connect the ground, previously I had the ESP8266 module connected to a different usb source than the one received by the rfid reader, and therefore the ground was different.

2 Likes

Yes, referencing a single, common, ground is one of those hard/fast principles. :wink:
Glad I could help you getting it going!! :slight_smile:

1 Like