Skip to content

Commit

Permalink
add wiegand module
Browse files Browse the repository at this point in the history
  • Loading branch information
ccutrer committed Jul 9, 2020
1 parent 2fa63a1 commit 85ba94d
Show file tree
Hide file tree
Showing 5 changed files with 317 additions and 0 deletions.
1 change: 1 addition & 0 deletions app/include/user_modules.h
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@
//#define LUA_USE_MODULES_U8G2
//#define LUA_USE_MODULES_UCG
//#define LUA_USE_MODULES_WEBSOCKET
//#define LUA_USE_MODULES_WIEGAND
#define LUA_USE_MODULES_WIFI
//#define LUA_USE_MODULES_WIFI_MONITOR
//#define LUA_USE_MODULES_WPS
Expand Down
270 changes: 270 additions & 0 deletions app/modules/wiegand.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,270 @@
// Module for reading keycards via Wiegand protocol of various formats

// Mostly taken from https://github.com/monkeyboard/Wiegand-Protocol-Library-for-Arduino, adapted to send card as NodeMCU event
// ## Contributors
// [Cody Cutrer](https://github.com/ccutrer) adapted to being a NodeMCU module
// [Francesco Uggetti (ugge75)](https://github.com/ugge75) improved this version of library to support multiple readers for ATMEGA2560. Please check out [his version of multiple wiegand readers library here](https://github.com/ugge75/Wiegand-Protocol-Library-for-Arduino-MEGA-2560)
// [Apollon77](https://github.com/Apollon77) improved interrupt safety and removed sysTick from global
// [paulfurley](https://github.com/paulfurley) added 4 bit code
// [PaulStoffregen](https://github.com/PaulStoffregen) added Use digitalPinToInterrupt on newer Arduino software, if present
// [tholum](https://github.com/tholum) Simpler Instructions
// [zanhecht](https://github.com/zanhecht) Recognize 24- and 32-bit
// [luckymallari](https://github.com/luckymallari) Simplified begin(D0, D1) for ESP(8266/32) devices.
// Written by [JP Liew](http://jpliew.com)
// Project home: http://www.monkeyboard.org/tutorials/82-protocol/24-wiegand-converter
// *This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version.*
// *This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.*

#include "module.h"
#include "lauxlib.h"
#include "platform.h"
#include "task/task.h"
#include "user_interface.h"
#include "pm/swtimer.h"

#ifdef LUA_USE_MODULES_WIEGAND
#if !defined(GPIO_INTERRUPT_ENABLE) || !defined(GPIO_INTERRUPT_HOOK_ENABLE)
#error Must have GPIO_INTERRUPT and GPIO_INTERRUPT_HOOK if using WIEGAND module
#endif
#endif

static volatile unsigned long _cardTempHigh;
static volatile unsigned long _cardTemp;
static volatile int _bitCount;
static int _wiegandType;
static unsigned long _code;
static int wiegand_cb_ref;
static ETSTimer wiegand_timer;
static int wiegand_timer_running;
static int wiegand_tasknumber;
static unsigned wiegand_pinD0, wiegand_pinD1;

static uint32_t ICACHE_RAM_ATTR wiegand_readD0(uint32_t ret_gpio_status)
{
_bitCount++; // Increament 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
}

task_post_medium(wiegand_tasknumber, 0);

ret_gpio_status &= ~(1 << pin_num[wiegand_pinD0]);
return ret_gpio_status;
}

static uint32_t ICACHE_RAM_ATTR wiegand_readD1(uint32_t ret_gpio_status)
{
_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
}

task_post_medium(wiegand_tasknumber, 0);

ret_gpio_status &= ~(1 << pin_num[wiegand_pinD1]);
return ret_gpio_status;
}

static unsigned long wiegand_get_card_id (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
}

static inline char translateEnterEscapeKeyPress(char originalKeyPress) {
switch(originalKeyPress) {
case 0x0b: // 11 or * key
return 0x0d; // 13 or ASCII ENTER

case 0x0a: // 10 or # key
return 0x1b; // 27 or ASCII ESCAPE

default:
return originalKeyPress;
}
}

static bool wiegand_store_value()
{
unsigned long cardID;

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)translateEnterEscapeKeyPress(lowNibble);
return true;
}
else {
_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)translateEnterEscapeKeyPress(_cardTemp & 0x0000000F);

_wiegandType = _bitCount;
_bitCount = 0;
_cardTemp = 0;
_cardTempHigh = 0;

return true;
}
else // wiegand 26 or wiegand 34
{
cardID = wiegand_get_card_id (&_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.
_bitCount=0;
_cardTemp=0;
_cardTempHigh=0;
return false;
}
}

static void lwiegand_cb(os_param_t param, uint8_t prio)
{
(void) param;
(void) prio;

if (wiegand_timer_running)
os_timer_disarm(&wiegand_timer);

os_timer_arm(&wiegand_timer, 25, 0);
}

static void lwiegand_timer_done(void *param)
{
lua_State *L = lua_getstate();

(void) param;

os_timer_disarm(&wiegand_timer);

if (wiegand_store_value()) {
if (wiegand_cb_ref != LUA_NOREF) {

lua_rawgeti(L, LUA_REGISTRYINDEX, wiegand_cb_ref);

lua_pushinteger(L, _code);
lua_pushinteger(L, _wiegandType);

lua_call(L, 2, 0);
}
}
}

static int lwiegand_close( lua_State* L)
{
if(wiegand_cb_ref != LUA_NOREF) {
luaL_unref(L, LUA_REGISTRYINDEX, wiegand_cb_ref);
wiegand_cb_ref = LUA_NOREF;
}
return 0;
}

// Lua: setup( d0pin, d1pin, function )
static int lwiegand_setup( lua_State* L)
{
unsigned pinD0 = luaL_checkinteger(L, 1);
unsigned pinD1 = luaL_checkinteger(L, 2);
luaL_argcheck(L, platform_gpio_exists(pinD0) && pinD0>0, 1, "Invalid pin for D0");
luaL_argcheck(L, platform_gpio_exists(pinD1) && pinD1>0, 2, "Invalid pin for D1");
luaL_checkfunction(L, 3);

lwiegand_close(L);
_cardTempHigh = 0;
_cardTemp = 0;
_code = 0;
_wiegandType = 0;
_bitCount = 0;
wiegand_pinD0 = pinD0;
wiegand_pinD0 = pinD1;
platform_gpio_mode( pinD0, PLATFORM_GPIO_INT, PLATFORM_GPIO_FLOAT);
platform_gpio_mode( pinD1, PLATFORM_GPIO_INT, PLATFORM_GPIO_FLOAT);

wiegand_cb_ref = luaL_ref(L, LUA_REGISTRYINDEX);

os_timer_setfn(&wiegand_timer, lwiegand_timer_done, NULL);
SWTIMER_REG_CB(lrotarwiegand_timer_done, SWTIMER_RESUME);

uint32_t mask = 1 << pin_num[pinD0];
platform_gpio_register_intr_hook(mask, wiegand_readD0);
mask = 1 << pin_num[pinD1];
platform_gpio_register_intr_hook(mask, wiegand_readD1);
platform_gpio_intr_init(pinD0, GPIO_PIN_INTR_NEGEDGE);
platform_gpio_intr_init(pinD1, GPIO_PIN_INTR_NEGEDGE);

return 0;
}

// Module function map
LROT_BEGIN(wiegand, NULL, 0)
LROT_FUNCENTRY( setup, lwiegand_setup )
LROT_FUNCENTRY( close, lwiegand_close )
LROT_END (wiegand, NULL, 0)

int luaopen_wiegand( lua_State *L ) {
wiegand_cb_ref = LUA_NOREF;
wiegand_tasknumber = task_get_id(lwiegand_cb);

return 0;
}

NODEMCU_MODULE(WIEGAND, "wiegand", wiegand, luaopen_wiegand);
39 changes: 39 additions & 0 deletions docs/modules/wiegand.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
# wiegand Module
| Since | Origin / Contributor | Maintainer | Source |
| :----- | :-------------------- | :---------- | :------ |
| 2020-07-08 | [Cody Cutrer](https://github.com/ccutrer) | [Cody Cutrer](https://github.com/ccutrer) | [wiegand.c](../../app/modules/wiegand.c)|

This module can read the input from RFID/keypad readers that support Wiegand outputs. 4 (keypress), 8 (keypress), 24, 26, 32, and 34 bit formats are supported. Wiegand requires three connections - two GPIOs connected to D0 and D1 datalines, and a ground connection.

## wiegand.setup()
Initialize the nodemcu to talk to a Wiegand keypad

#### Syntax
`wiegand.setup(pinD0, pinD1, callback)`

#### Parameters
- `pinD0` This is a GPIO number (excluding 0) and connects to the D0 data line
- `pinD1` This is a GPIO number (excluding 0) and connects to the D1 data line
- `callback` This is a function that will invoked when a full card or keypress is read.

The callback will be invoked with two arguments when a card is received. The first argument is the ID number,
the second is the number of bits in the format (4, 8, 24, 26, 32, 34).

#### Returns
Nothing. If the arguments are in error, or the operation cannot be completed, then an error is thrown.

#### Example

wiegand.setup(1, 2, function (card, type)
print("Card=" .. card .. " type=" .. type)
end)

## wiegand.close()
Releases the resources associated with the card reader.

#### Syntax
`wiegand.close()`

#### Example

wiegand.close()
1 change: 1 addition & 0 deletions mkdocs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,7 @@ pages:
- 'uart': 'modules/uart.md'
- 'ucg': 'modules/ucg.md'
- 'websocket': 'modules/websocket.md'
- 'wiegand': 'modules/wiegand.md'
- 'wifi': 'modules/wifi.md'
- 'wifi.monitor': 'modules/wifi_monitor.md'
- 'wps': 'modules/wps.md'
Expand Down
6 changes: 6 additions & 0 deletions tools/luacheck_config.lua
Original file line number Diff line number Diff line change
Expand Up @@ -717,6 +717,12 @@ stds.nodemcu_libs = {
createClient = empty
}
},
wiegand = {
fields = {
setup = empty,
close = empty
}
}
wifi = {
fields = {
COUNTRY_AUTO = empty,
Expand Down

0 comments on commit 85ba94d

Please sign in to comment.