Skip to content

Instantly share code, notes, and snippets.

@skylord123
Last active November 14, 2024 16:25
Show Gist options
  • Save skylord123/22501a923b44e3873dd3cd3b526b9999 to your computer and use it in GitHub Desktop.
Save skylord123/22501a923b44e3873dd3cd3b526b9999 to your computer and use it in GitHub Desktop.
Wiegand ESP32-POE config

Wiegand keypad interfaces with ESPHOME device EDIT: I just noticed the logic for sending the codes to HA is inside the wiegand_device.h file. There is still some left over YAML from when I was manually sending them to HA that can be removed.

I found the code and modified it from here (you can see my comments in that thread): esphome/feature-requests#211

Place wiegand_device.h in ESPHome/custom_components/wiegand_device

Put the yaml inside your device config in ESPHOME.

I am using an ESP32-POE because I don't want WiFi to be a problem with people getting into the building. I'm in a giant warehouse with spotty coverage (giant steel walls will do that)

Instead of using the normal submit button on my keypad I use the star button. The submit button does not send anything to the ESPHOME device for my keypad (and it also beeps with an invalid code since the keypad has it's own built in memory bank of valid codes that we are not using, also if you try to tell the keypad to activate after hitting the submit button it wont do anything because the keypad rejects anything during the period it beeps with an invalid code). So I find the star button makes this a much better user experience.

I also added the ability to hold the push-to-leave button for 5 seconds then release to toggle the drop lock on/off. Great if you have deliveries or something else.

The keypad I am using is the Retekess T-AC03 (took a while to find this because the unit I have is white-labeled). This is the same keypad: https://amzn.to/3z6LtZs

Extra Info

So just as an FYI the keypad combo I bought (came with a power supply/control module, drop lock, push to leave button, and the Wiegand keypad) had issues communicating over Wiegand.

I ended up narrowing it down to the power supply. I put my oscilliscope on it and it clearly showed some interference on the 5v power cable. This was causing my keypad to spaz out (the ESPHOME log showed that the device was sending tons of data continously and while this was hapenning the keypad would not respond to button presses). This sort of sucked because the power supply actually controller the power that gets sent to the drop lock and handled opening the door when the push to leave button was pressed. I had to re-create all of this myself using a relay and logic on the ESPHOME device. I used my own power supply and this fixed all of the issues with the keypad.

So if you have problems, try another power supply with the keypad. If you are buying this for the first time avoid the access controller/power supply module and instead buy the other parts separately.

esphome:
name: west_warehouse_access_control
platform: ESP32
board: esp32-poe
includes:
- custom_components/wiegand_device/wiegand_device.h
ethernet:
use_address: west_warehouse_access_control
type: LAN8720
mdc_pin: GPIO23
mdio_pin: GPIO18
clk_mode: GPIO17_OUT
phy_addr: 0
power_pin: GPIO12
# Enable logging
logger:
level: DEBUG # this can be removed if you are no longer debugging
# Enable Home Assistant API
api:
password: "PUT_YOUR_API_PASSWORD_HERE"
ota:
password: "PUT_YOUR_OTA_PASSWORD_HERE"
script:
# after 4 seconds from last button press we clear any entered code.
- id: clear_codes
mode: restart
then:
- delay: 4s
- lambda: |-
id(code_input) = "";
id(code_submit_times) = 0;
- id: buzz_door
mode: restart
then:
- switch.turn_on: drop_lock
- switch.turn_on: keypad_push
- delay: 3000ms
- switch.turn_off: drop_lock
- switch.turn_off: keypad_push
globals:
- id: code_input
type: std::string
restore_value: no
initial_value: ''
- id: code_submit_times
type: int
restore_value: no
initial_value: '0'
custom_component:
# we are using pin 13 & 14 for our reader, modify to match yours.
- lambda: |-
auto wiegand = new WiegandReader(13, 14);
return {wiegand};
switch:
- platform: restart
name: "West Warehouse Access Controller Reboot"
- platform: gpio
name: "West Warehouse Door Drop Lock"
id: drop_lock
pin:
number: GPIO4
# the keypad_push is used to tell the keypad to go green as if there was a successful entry.
# we use our own relay for the drop-lock so this is just used to tell the user it was successful.
- platform: gpio
name: "West Warehouse Door Keypad Push"
id: keypad_push
restore_mode: ALWAYS_OFF
pin:
number: GPIO33
inverted: yes
# tells the door to open for a few seconds, used when manually opened in HA or when push to leave button is pressed.
- platform: template
name: "Buzz West Warehouse Door"
icon: "mdi:door"
turn_on_action:
- script.execute: buzz_door
binary_sensor:
# our keypad has a doorbell button
- platform: gpio
name: "West Warehouse Doorbell"
pin:
number: GPIO35
mode: INPUT_PULLUP
inverted: true
- platform: gpio
name: "West Warehouse Leave Button"
pin:
number: GPIO34
mode: INPUT_PULLUP
inverted: true
on_click:
# if button is held for 5-30 seconds then released we turn the drop lock off
- min_length: 5000ms
max_length: 30000ms
then:
- switch.toggle: drop_lock
- max_length: 4999ms
then:
- script.execute: buzz_door
#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;
@towerclimber87
Copy link

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:

  • platform: custom
    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:

@mcarbonneaux
Copy link

very interesting ! not only the code b ut also your real life experience !

when you coded that esphome don't have weigand natively implemented.
you've tryed the esphome native implementation (had been added in Jun 3, 2023) ?

@skylord123
Copy link
Author

very interesting ! not only the code b ut also your real life experience !

when you coded that esphome don't have weigand natively implemented. you've tryed the esphome native implementation (had been added in Jun 3, 2023) ?

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.

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:

  • platform: custom
    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:

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment