Skip to content

Commit 63e1fcd

Browse files
authored
add wiegand module (#3203)
* add wiegand module * minor tweaks to wiegand module * fix a whitespace error (tabs!!!!) * remove an unnecessary volatile qualifier
1 parent 73df18d commit 63e1fcd

File tree

5 files changed

+297
-0
lines changed

5 files changed

+297
-0
lines changed

app/include/user_modules.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@
7070
//#define LUA_USE_MODULES_U8G2
7171
//#define LUA_USE_MODULES_UCG
7272
//#define LUA_USE_MODULES_WEBSOCKET
73+
//#define LUA_USE_MODULES_WIEGAND
7374
#define LUA_USE_MODULES_WIFI
7475
//#define LUA_USE_MODULES_WIFI_MONITOR
7576
//#define LUA_USE_MODULES_WPS

app/modules/wiegand.c

Lines changed: 244 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,244 @@
1+
// Module for reading keycards via Wiegand protocol
2+
3+
// ## Contributors
4+
// [Cody Cutrer](https://github.com/ccutrer) adapted to being a NodeMCU module
5+
6+
#include "module.h"
7+
#include "lauxlib.h"
8+
#include "platform.h"
9+
#include "task/task.h"
10+
#include "user_interface.h"
11+
#include "pm/swtimer.h"
12+
13+
#ifdef LUA_USE_MODULES_WIEGAND
14+
#if !defined(GPIO_INTERRUPT_ENABLE) || !defined(GPIO_INTERRUPT_HOOK_ENABLE)
15+
#error Must have GPIO_INTERRUPT and GPIO_INTERRUPT_HOOK if using WIEGAND module
16+
#endif
17+
#endif
18+
19+
typedef struct {
20+
uint32_t current_card;
21+
int bit_count;
22+
uint32_t last_card;
23+
uint32_t last_bit_count;
24+
int cb_ref;
25+
int self_ref;
26+
ETSTimer timer;
27+
int timer_running;
28+
int task_posted;
29+
int pinD0;
30+
int pinD1;
31+
uint32_t last_bit_time;
32+
} wiegand_struct_t;
33+
typedef wiegand_struct_t* wiegand_t;
34+
35+
static int tasknumber;
36+
static volatile wiegand_t pins_to_wiegand_state[NUM_GPIO];
37+
38+
static wiegand_t wiegand_get( lua_State *L, int stack)
39+
{
40+
wiegand_t w = (wiegand_t)luaL_checkudata(L, stack, "wiegand.wiegand");
41+
if (w == NULL)
42+
return (wiegand_t)luaL_error(L, "wiegand object expected");
43+
return w;
44+
}
45+
46+
static uint32_t ICACHE_RAM_ATTR wiegand_intr(uint32_t ret_gpio_status)
47+
{
48+
uint32_t gpio_status = GPIO_REG_READ(GPIO_STATUS_ADDRESS);
49+
uint32_t gpio_bits = gpio_status;
50+
for(int i = 0; gpio_bits > 0; ++i, gpio_bits >>= 1) {
51+
if (i == NUM_GPIO)
52+
break;
53+
if ((gpio_bits & 1) == 0)
54+
continue;
55+
// find the struct registered for this pin
56+
volatile wiegand_t w = pins_to_wiegand_state[i];
57+
if (!w) {
58+
continue;
59+
}
60+
61+
++w->bit_count;
62+
w->current_card <<= 1;
63+
if (i == pin_num[w->pinD1])
64+
w->current_card |= 1;
65+
66+
w->last_bit_time = system_get_time();
67+
68+
if (!w->task_posted) {
69+
task_post_medium(tasknumber, (os_param_t)w);
70+
w->task_posted = 1;
71+
}
72+
73+
GPIO_REG_WRITE(GPIO_STATUS_W1TC_ADDRESS, gpio_status & (1 << i));
74+
ret_gpio_status &= ~(1 << i);
75+
}
76+
77+
return ret_gpio_status;
78+
}
79+
80+
static int parity(int val)
81+
{
82+
int parity = 0;
83+
while (val > 0) {
84+
parity ^= val & 1;
85+
val >>= 1;
86+
}
87+
return parity;
88+
}
89+
90+
static bool wiegand_store_card(volatile wiegand_t w)
91+
{
92+
uint32_t card = w->current_card;
93+
int bit_count = w->bit_count;
94+
w->current_card = 0;
95+
w->bit_count = 0;
96+
97+
switch(bit_count) {
98+
case 4:
99+
w->last_card = card;
100+
w->last_bit_count = bit_count;
101+
return true;
102+
case 26:
103+
// even parity over the first 13 bits, odd parity over the last 13 bits
104+
if (parity((card & 0x3ffe000) >> 13) != 0 || parity(card & 0x1fff) != 1)
105+
return false;
106+
107+
w->last_card = (card >> 1) & 0xffffff;
108+
w->last_bit_count = bit_count;
109+
return true;
110+
}
111+
return false;
112+
}
113+
114+
static void lwiegand_timer_done(void *param)
115+
{
116+
lua_State *L = lua_getstate();
117+
118+
wiegand_t w = (wiegand_t) param;
119+
120+
os_timer_disarm(&w->timer);
121+
122+
if (wiegand_store_card(w)) {
123+
lua_rawgeti(L, LUA_REGISTRYINDEX, w->cb_ref);
124+
125+
lua_pushinteger(L, w->last_card);
126+
lua_pushinteger(L, w->last_bit_count);
127+
128+
lua_call(L, 2, 0);
129+
}
130+
}
131+
132+
static void lwiegand_cb(os_param_t param, uint8_t prio)
133+
{
134+
wiegand_t w = (wiegand_t) param;
135+
(void) prio;
136+
137+
*(volatile int *)&w->task_posted = 0;
138+
if (w->timer_running)
139+
os_timer_disarm(&w->timer);
140+
141+
int timeout = 25 - (system_get_time() - w->last_bit_time) / 1000;
142+
if (timeout < 0) {
143+
lwiegand_timer_done(w);
144+
} else {
145+
os_timer_arm(&w->timer, timeout, 0);
146+
}
147+
}
148+
149+
static void reregister_gpio_hooks()
150+
{
151+
uint32_t mask = 0;
152+
for (int i = 0; i < NUM_GPIO; ++i) {
153+
if (pins_to_wiegand_state[i])
154+
mask |= (1 << i);
155+
}
156+
platform_gpio_register_intr_hook(mask, wiegand_intr);
157+
}
158+
159+
static int lwiegand_close( lua_State* L)
160+
{
161+
wiegand_t w = wiegand_get(L, 1);
162+
luaL_unref(L, LUA_REGISTRYINDEX, w->cb_ref);
163+
w->cb_ref = LUA_NOREF;
164+
if (w->timer_running) {
165+
os_timer_disarm(&w->timer);
166+
}
167+
luaL_unref(L, LUA_REGISTRYINDEX, w->self_ref);
168+
w->self_ref = LUA_NOREF;
169+
170+
pins_to_wiegand_state[pin_num[w->pinD0]] = NULL;
171+
pins_to_wiegand_state[pin_num[w->pinD1]] = NULL;
172+
173+
reregister_gpio_hooks();
174+
platform_gpio_intr_init(w->pinD0, GPIO_PIN_INTR_DISABLE);
175+
platform_gpio_intr_init(w->pinD1, GPIO_PIN_INTR_DISABLE);
176+
177+
return 0;
178+
}
179+
180+
// Lua: wiegand.created0pin, d1pin)
181+
static int lwiegand_create(lua_State* L)
182+
{
183+
unsigned pinD0 = luaL_checkinteger(L, 1);
184+
unsigned pinD1 = luaL_checkinteger(L, 2);
185+
luaL_argcheck(L, platform_gpio_exists(pinD0) && pinD0>0, 1, "Invalid pin for D0");
186+
luaL_argcheck(L, platform_gpio_exists(pinD1) && pinD1>0 && pinD0 != pinD1, 2, "Invalid pin for D1");
187+
luaL_checkfunction(L, 3);
188+
189+
if (pins_to_wiegand_state[pin_num[pinD0]] || pins_to_wiegand_state[pin_num[pinD1]])
190+
return luaL_error(L, "pin already in use");
191+
192+
wiegand_t ud = (wiegand_t)lua_newuserdata(L, sizeof(wiegand_struct_t));
193+
if (!ud) return luaL_error(L, "not enough memory");
194+
luaL_getmetatable(L, "wiegand.wiegand");
195+
lua_setmetatable(L, -2);
196+
197+
ud->current_card = 0;
198+
ud->bit_count = 0;
199+
ud->timer_running = 0;
200+
ud->task_posted = 0;
201+
ud->pinD0 = pinD0;
202+
ud->pinD1 = pinD1;
203+
204+
platform_gpio_mode( pinD0, PLATFORM_GPIO_INT, PLATFORM_GPIO_FLOAT);
205+
platform_gpio_mode( pinD1, PLATFORM_GPIO_INT, PLATFORM_GPIO_FLOAT);
206+
207+
lua_pushvalue(L, 3);
208+
ud->cb_ref = luaL_ref(L, LUA_REGISTRYINDEX);
209+
lua_pushvalue(L, -1);
210+
ud->self_ref = luaL_ref(L, LUA_REGISTRYINDEX);
211+
212+
os_timer_setfn(&ud->timer, lwiegand_timer_done, ud);
213+
SWTIMER_REG_CB(lwiegand_timer_done, SWTIMER_RESUME);
214+
215+
pins_to_wiegand_state[pin_num[pinD0]] = ud;
216+
pins_to_wiegand_state[pin_num[pinD1]] = ud;
217+
218+
reregister_gpio_hooks();
219+
platform_gpio_intr_init(pinD0, GPIO_PIN_INTR_NEGEDGE);
220+
platform_gpio_intr_init(pinD1, GPIO_PIN_INTR_NEGEDGE);
221+
222+
return 1;
223+
}
224+
225+
// Module function map
226+
LROT_BEGIN(wiegand_dyn, NULL, LROT_MASK_GC_INDEX)
227+
LROT_FUNCENTRY( __gc, lwiegand_close )
228+
LROT_TABENTRY( __index, wiegand_dyn )
229+
LROT_FUNCENTRY( close, lwiegand_close )
230+
LROT_END(wiegand_dyn, NULL, LROT_MASK_GC_INDEX)
231+
232+
LROT_BEGIN(wiegand, NULL, 0)
233+
LROT_FUNCENTRY( create, lwiegand_create )
234+
LROT_END (wiegand, NULL, 0)
235+
236+
int luaopen_wiegand( lua_State *L ) {
237+
luaL_rometatable(L, "wiegand.wiegand", LROT_TABLEREF(wiegand_dyn));
238+
tasknumber = task_get_id(lwiegand_cb);
239+
memset((void *)pins_to_wiegand_state, 0, sizeof(pins_to_wiegand_state));
240+
241+
return 0;
242+
}
243+
244+
NODEMCU_MODULE(WIEGAND, "wiegand", wiegand, luaopen_wiegand);

docs/modules/wiegand.md

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
# wiegand Module
2+
| Since | Origin / Contributor | Maintainer | Source |
3+
| :----- | :-------------------- | :---------- | :------ |
4+
| 2020-07-08 | [Cody Cutrer](https://github.com/ccutrer) | [Cody Cutrer](https://github.com/ccutrer) | [wiegand.c](../../app/modules/wiegand.c)|
5+
6+
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.
7+
8+
## wiegand.create()
9+
Creates a dynamic wiegand object that receives a callback when data is received.
10+
Initialize the nodemcu to talk to a Wiegand keypad
11+
12+
#### Syntax
13+
`wiegand.create(pinD0, pinD1, callback)`
14+
15+
#### Parameters
16+
- `pinD0` This is a GPIO number (excluding 0) and connects to the D0 data line
17+
- `pinD1` This is a GPIO number (excluding 0) and connects to the D1 data line
18+
- `callback` This is a function that will invoked when a full card or keypress is read.
19+
20+
The callback will be invoked with two arguments when a card is received. The first argument is the received code,
21+
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
22+
pressed; * is 10, and # is 11. For 26-bit format, it's the raw code. If you want to separate it into site codes
23+
and card numbers, you'll need to do the arithmetic yourself (top 8 bits are site code; bottom 16 are card
24+
numbers).
25+
26+
#### Returns
27+
`wiegand` object. If the arguments are in error, or the operation cannot be completed, then an error is thrown.
28+
29+
#### Example
30+
31+
local w = wiegand.create(1, 2, function (card, bits)
32+
print("Card=" .. card .. " bits=" .. bits)
33+
end)
34+
w:close()
35+
36+
# Wiegand Object Methods
37+
38+
## wiegandobj:close()
39+
Releases the resources associated with the card reader.
40+
41+
#### Syntax
42+
`wiegandobj:close()`
43+
44+
#### Example
45+
46+
wiegandobj:close()

mkdocs.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,7 @@ pages:
129129
- 'uart': 'modules/uart.md'
130130
- 'ucg': 'modules/ucg.md'
131131
- 'websocket': 'modules/websocket.md'
132+
- 'wiegand': 'modules/wiegand.md'
132133
- 'wifi': 'modules/wifi.md'
133134
- 'wifi.monitor': 'modules/wifi_monitor.md'
134135
- 'wps': 'modules/wps.md'

tools/luacheck_config.lua

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -736,6 +736,11 @@ stds.nodemcu_libs = {
736736
createClient = empty
737737
}
738738
},
739+
wiegand = {
740+
fields = {
741+
create = empty
742+
}
743+
},
739744
wifi = {
740745
fields = {
741746
COUNTRY_AUTO = empty,

0 commit comments

Comments
 (0)