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 13, 2020
1 parent 2fa63a1 commit f18752c
Show file tree
Hide file tree
Showing 5 changed files with 296 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
243 changes: 243 additions & 0 deletions app/modules/wiegand.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,243 @@
// Module for reading keycards via Wiegand protocol

// ## Contributors
// [Cody Cutrer](https://github.com/ccutrer) adapted to being a NodeMCU module

#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

typedef struct {
uint32_t current_card;
int bit_count;
uint32_t last_card;
uint32_t last_bit_count;
int cb_ref;
int self_ref;
ETSTimer timer;
int timer_running;
int task_posted;
int pinD0;
int pinD1;
uint32_t last_bit_time;
} wiegand_struct_t;
typedef wiegand_struct_t* wiegand_t;

static int tasknumber;
static volatile wiegand_t pins_to_wiegand_state[NUM_GPIO];

static wiegand_t wiegand_get( lua_State *L, int stack)
{
wiegand_t w = (wiegand_t)luaL_checkudata(L, stack, "wiegand.wiegand");
if (w == NULL)
return (wiegand_t)luaL_error(L, "wiegand object expected");
return w;
}

static uint32_t ICACHE_RAM_ATTR wiegand_intr(uint32_t ret_gpio_status)
{
uint32_t gpio_status = GPIO_REG_READ(GPIO_STATUS_ADDRESS);
uint32_t gpio_bits = gpio_status;

for(int i = 0; gpio_bits > 0; ++i, gpio_bits >>= 1) {
if ((gpio_bits & 1) == 0)
continue;
// find the struct registered for this pin
volatile wiegand_t w = pins_to_wiegand_state[i];
if (!w) {
continue;
}

++w->bit_count;
w->current_card <<= 1;
if (i == pin_num[w->pinD1])
w->current_card |= 1;

w->last_bit_time = system_get_time();

if (!w->task_posted) {
task_post_medium(tasknumber, (os_param_t)w);
w->task_posted = 1;
}

GPIO_REG_WRITE(GPIO_STATUS_W1TC_ADDRESS, gpio_status & (1 << i));
ret_gpio_status &= ~(1 << i);
}

return ret_gpio_status;
}

static int parity(int val)
{
int parity = 0;
while (val > 0) {
parity ^= val & 1;
val >>= 1;
}
return parity;
}

static bool wiegand_store_card(volatile wiegand_t w)
{
uint32_t card = w->current_card;
int bit_count = w->bit_count;
w->current_card = 0;
w->bit_count = 0;

switch(bit_count) {
case 4:
w->last_card = card;
w->last_bit_count = bit_count;
return true;
case 26:
// even parity over the first 13 bits, odd parity over the last 13 bits
if (parity((card & 0x3ffe000) >> 13) != 0 || parity(card & 0x1fff) != 1)
return false;

w->last_card = (card >> 1) & 0xffffff;
w->last_bit_count = bit_count;
return true;
}
return false;
}

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

volatile wiegand_t w = (wiegand_t) param;

os_timer_disarm(&w->timer);

if (wiegand_store_card(w)) {
lua_rawgeti(L, LUA_REGISTRYINDEX, w->cb_ref);

lua_pushinteger(L, w->last_card);
lua_pushinteger(L, w->last_bit_count);

lua_call(L, 2, 0);
}
}

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

*(volatile int *)&w->task_posted = 0;
if (w->timer_running)
os_timer_disarm(&w->timer);

int timeout = 25 - (system_get_time() - w->last_bit_time) / 1000;
if (timeout < 0) {
lwiegand_timer_done(w);
} else {
os_timer_arm(&w->timer, timeout, 0);
}
}

static void reregister_gpio_hooks()
{
uint32_t mask = 0;
for (int i = 0; i < NUM_GPIO; ++i) {
if (pins_to_wiegand_state[i])
mask |= (1 << i);
}
platform_gpio_register_intr_hook(mask, wiegand_intr);
}

static int lwiegand_close( lua_State* L)
{
wiegand_t w = wiegand_get(L, 1);
luaL_unref(L, LUA_REGISTRYINDEX, w->cb_ref);
w->cb_ref = LUA_NOREF;
if (w->timer_running) {
os_timer_disarm(&w->timer);
}
luaL_unref(L, LUA_REGISTRYINDEX, w->self_ref);
w->self_ref = LUA_NOREF;

pins_to_wiegand_state[pin_num[w->pinD0]] = NULL;
pins_to_wiegand_state[pin_num[w->pinD1]] = NULL;

reregister_gpio_hooks();
platform_gpio_intr_init(w->pinD0, GPIO_PIN_INTR_DISABLE);
platform_gpio_intr_init(w->pinD1, GPIO_PIN_INTR_DISABLE);

return 0;
}

// Lua: wiegand.created0pin, d1pin)
static int lwiegand_create(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 && pinD0 != pinD1, 2, "Invalid pin for D1");
luaL_checkfunction(L, 3);

if (pins_to_wiegand_state[pin_num[pinD0]] || pins_to_wiegand_state[pin_num[pinD1]])
return luaL_error(L, "pin already in use");

wiegand_t ud = (wiegand_t)lua_newuserdata(L, sizeof(wiegand_struct_t));
if (!ud) return luaL_error(L, "not enough memory");
luaL_getmetatable(L, "wiegand.wiegand");
lua_setmetatable(L, -2);

ud->current_card = 0;
ud->bit_count = 0;
ud->timer_running = 0;
ud->task_posted = 0;
ud->pinD0 = pinD0;
ud->pinD1 = pinD1;

platform_gpio_mode( pinD0, PLATFORM_GPIO_INT, PLATFORM_GPIO_FLOAT);
platform_gpio_mode( pinD1, PLATFORM_GPIO_INT, PLATFORM_GPIO_FLOAT);

lua_pushvalue(L, 3);
ud->cb_ref = luaL_ref(L, LUA_REGISTRYINDEX);
lua_pushvalue(L, -1);
ud->self_ref = luaL_ref(L, LUA_REGISTRYINDEX);

os_timer_setfn(&ud->timer, lwiegand_timer_done, ud);
SWTIMER_REG_CB(lwiegand_timer_done, SWTIMER_RESUME);

pins_to_wiegand_state[pin_num[pinD0]] = ud;
pins_to_wiegand_state[pin_num[pinD1]] = ud;

reregister_gpio_hooks();
platform_gpio_intr_init(pinD0, GPIO_PIN_INTR_NEGEDGE);
platform_gpio_intr_init(pinD1, GPIO_PIN_INTR_NEGEDGE);

return 1;
}

// Module function map
LROT_BEGIN(wiegand_dyn, NULL, LROT_MASK_GC_INDEX)
LROT_FUNCENTRY( __gc, lwiegand_close )
LROT_TABENTRY( __index, wiegand_dyn )
LROT_FUNCENTRY( close, lwiegand_close )
LROT_END(wiegand_dyn, NULL, LROT_MASK_GC_INDEX)

LROT_BEGIN(wiegand, NULL, 0)
LROT_FUNCENTRY( create, lwiegand_create )
LROT_END (wiegand, NULL, 0)

int luaopen_wiegand( lua_State *L ) {
luaL_rometatable(L, "wiegand.wiegand", LROT_TABLEREF(wiegand_dyn));
tasknumber = task_get_id(lwiegand_cb);
memset((void *)pins_to_wiegand_state, 0, sizeof(pins_to_wiegand_state));

return 0;
}

NODEMCU_MODULE(WIEGAND, "wiegand", wiegand, luaopen_wiegand);
46 changes: 46 additions & 0 deletions docs/modules/wiegand.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
# 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) and 26 (Wiegand standard) bit formats are supported. Wiegand requires three connections - two GPIOs connected to D0 and D1 datalines, and a ground connection.

## wiegand.create()
Creates a dynamic wiegand object that receives a callback when data is received.
Initialize the nodemcu to talk to a Wiegand keypad

#### Syntax
`wiegand.create(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 received code,
the second is the number of bits in the format (4, 26). For 4-bit format, it's just an integer of the key they
pressed; * is 10, and # is 11. For 26-bit format, it's the raw code. If you want to separate it into site codes
and card numbers, you'll need to do the arithmetic yourself (top 8 bits are site code; bottom 16 are card
numbers).

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

#### Example

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

# Wiegand Object Methods

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

#### Syntax
`wiegandobj:close()`

#### Example

wiegandobj: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
5 changes: 5 additions & 0 deletions tools/luacheck_config.lua
Original file line number Diff line number Diff line change
Expand Up @@ -717,6 +717,11 @@ stds.nodemcu_libs = {
createClient = empty
}
},
wiegand = {
fields = {
create = empty
}
},
wifi = {
fields = {
COUNTRY_AUTO = empty,
Expand Down

0 comments on commit f18752c

Please sign in to comment.