瀏覽代碼

add wiegand module (#3203)

* add wiegand module

* minor tweaks to wiegand module

 * fix a whitespace error (tabs!!!!)
 * remove an unnecessary volatile qualifier
Cody Cutrer 3 年之前
父節點
當前提交
5ee658e22b
共有 5 個文件被更改,包括 297 次插入0 次删除
  1. 1 0
      app/include/user_modules.h
  2. 244 0
      app/modules/wiegand.c
  3. 46 0
      docs/modules/wiegand.md
  4. 1 0
      mkdocs.yml
  5. 5 0
      tools/luacheck_config.lua

+ 1 - 0
app/include/user_modules.h

@@ -70,6 +70,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

+ 244 - 0
app/modules/wiegand.c

@@ -0,0 +1,244 @@
+// 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 (i == NUM_GPIO)
+      break;
+    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();
+
+  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 - 0
docs/modules/wiegand.md

@@ -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.create(1, 2, function (card, bits)
+      print("Card=" .. card .. " bits=" .. bits)
+    end)
+    w:close()
+
+# Wiegand Object Methods
+
+## wiegandobj:close()
+Releases the resources associated with the card reader.
+
+#### Syntax
+`wiegandobj:close()`
+
+#### Example
+
+    wiegandobj:close()

+ 1 - 0
mkdocs.yml

@@ -129,6 +129,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'

+ 5 - 0
tools/luacheck_config.lua

@@ -736,6 +736,11 @@ stds.nodemcu_libs = {
         createClient = empty
       }
     },
+    wiegand = {
+      fields = {
+        create = empty
+      }
+    },
     wifi = {
       fields = {
         COUNTRY_AUTO = empty,