|
| 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); |
0 commit comments