diff mbox series

[libgpiod] Add Lua 5.1 binding for libgpiod v1.0.x branch

Message ID 8a49314e-f727-aace-9c54-122b038d1fad@acrios.com
State New
Headers show
Series [libgpiod] Add Lua 5.1 binding for libgpiod v1.0.x branch | expand

Commit Message

Marek Novak | ACRIOS Systems s.r.o. Sept. 7, 2021, 4:02 p.m. UTC
From ca1b5688de2d1cb63bb9823e28b87c52f23df449 Mon Sep 17 00:00:00 2001
From: Marek NOVAK <novak@acrios.com>
Date: Fri, 3 Sep 2021 18:41:02 +0200
Subject: [PATCH] Adding Lua 5.1 bindings as 'gpiod' Lua module

- Adding bindings directory with wrapper code
- Adding Makefile.am for building and distributing as a Lua module
- Adding --enable-bindings-lua option for autogen.sh
- Adding examples with basic lua gpioset and gpioget implementation
- Output, input and event input with new(), get(), set() and wait()
   methods are implemented.

Signed-off-by: Marek NOVAK <novak@acrios.com>
---
  Makefile.am                       |   7 +
  bindings/Makefile.am              |  10 +
  bindings/lua/Makefile.am          |  20 +
  bindings/lua/examples/Makefile.am |   6 +
  bindings/lua/examples/gpioget.lua |  28 ++
  bindings/lua/examples/gpioset.lua |  47 ++
  bindings/lua/gpiod.c              | 686 ++++++++++++++++++++++++++++++
  configure.ac                      |  24 +-
  8 files changed, 827 insertions(+), 1 deletion(-)
  create mode 100644 bindings/Makefile.am
  create mode 100644 bindings/lua/Makefile.am
  create mode 100644 bindings/lua/examples/Makefile.am
  create mode 100644 bindings/lua/examples/gpioget.lua
  create mode 100644 bindings/lua/examples/gpioset.lua
  create mode 100644 bindings/lua/gpiod.c

  AM_CONDITIONAL([HAS_DOXYGEN], [test "x$has_doxygen" = xtrue])
  if test "x$has_doxygen" = xfalse
@@ -126,6 +145,9 @@ AC_CONFIG_FILES([libgpiod.pc
  		 src/Makefile
  		 src/lib/Makefile
  		 src/tools/Makefile
-		 tests/Makefile])
+		 tests/Makefile
+		 bindings/Makefile
+		 bindings/lua/Makefile
+		 bindings/lua/examples/Makefile])

  AC_OUTPUT

Comments

Bartosz Golaszewski Sept. 9, 2021, 8:20 a.m. UTC | #1
On Tue, Sep 7, 2021 at 6:10 PM Marek Novak | ACRIOS Systems s.r.o.
<novak@acrios.com> wrote:
>
>  From ca1b5688de2d1cb63bb9823e28b87c52f23df449 Mon Sep 17 00:00:00 2001
> From: Marek NOVAK <novak@acrios.com>
> Date: Fri, 3 Sep 2021 18:41:02 +0200
> Subject: [PATCH] Adding Lua 5.1 bindings as 'gpiod' Lua module
>
> - Adding bindings directory with wrapper code
> - Adding Makefile.am for building and distributing as a Lua module
> - Adding --enable-bindings-lua option for autogen.sh
> - Adding examples with basic lua gpioset and gpioget implementation
> - Output, input and event input with new(), get(), set() and wait()
>    methods are implemented.
>
> Signed-off-by: Marek NOVAK <novak@acrios.com>
> ---

Hi Marek!

Thanks for the patch, any new bindings are much appreciated! However
the 1.0.x branch has been unsupported for over 3 years. Why did you
decide to base your work on this one?

The currently supported branch for the 1.x series is 1.6.x but even
then I don't really want to add new features to it as we're currently
developing the 2.0 version which will become the new preferred base
for all new work. Any chance you could base your work on the
next/libgpiod-2.0 branch just like Viresh did with his Rust bindings?

Bartosz
Marek Novak | ACRIOS Systems s.r.o. Sept. 10, 2021, 7:59 p.m. UTC | #2
On 09. 09. 21 10:20, Bartosz Golaszewski wrote:
> On Tue, Sep 7, 2021 at 6:10 PM Marek Novak | ACRIOS Systems s.r.o.
> <novak@acrios.com> wrote:
>>
>>   From ca1b5688de2d1cb63bb9823e28b87c52f23df449 Mon Sep 17 00:00:00 2001
>> From: Marek NOVAK <novak@acrios.com>
>> Date: Fri, 3 Sep 2021 18:41:02 +0200
>> Subject: [PATCH] Adding Lua 5.1 bindings as 'gpiod' Lua module
>>
>> - Adding bindings directory with wrapper code
>> - Adding Makefile.am for building and distributing as a Lua module
>> - Adding --enable-bindings-lua option for autogen.sh
>> - Adding examples with basic lua gpioset and gpioget implementation
>> - Output, input and event input with new(), get(), set() and wait()
>>     methods are implemented.
>>
>> Signed-off-by: Marek NOVAK <novak@acrios.com>
>> ---
> 
> Hi Marek!
> 
> Thanks for the patch, any new bindings are much appreciated! However
> the 1.0.x branch has been unsupported for over 3 years. Why did you
> decide to base your work on this one?
> 
> The currently supported branch for the 1.x series is 1.6.x but even
> then I don't really want to add new features to it as we're currently
> developing the 2.0 version which will become the new preferred base
> for all new work. Any chance you could base your work on the
> next/libgpiod-2.0 branch just like Viresh did with his Rust bindings?
> 
> Bartosz
> 

Hi Bartosz!

Thanks for checking my patch. I later realized it was not well processed 
when sent using Thunderbird (I will use git send-email next time). I 
also learnt that on some systems, the macros from ax_lua.m4 are not 
available and I added it in a m4 folder. I have a v2 of the patch almost 
ready (build being tested on different environments by my colleagues). 
So I can post it here if anybody needs it on 1.x version supported...

To my motivation to select v1.0.x branch - I wanted to select a 1.x 
branch as base since this one is used in OpenWRT distribution 
(https://github.com/openwrt/packages/blob/openwrt-21.02/libs/libgpiod/Makefile). 
And my target use-case is to have Lua support for GPIO interaction there 
for some late device-specific initialization scripts.

However, I think I can switch to a 2.x version of libgpiod on our fork 
of OpenWRT v21.02 and base my contribution on it... Currently I use a 
package which builds Lua binding "out of tree", being quite ugly, but 
available for public here (https://sw.acrios.com/acrios/lua-gpiod). I 
could / should maybe also support not just Lua 5.1, but also the other 
actively used version of Lua...

I will update my contibution to 2.x, test it and then post it here...

---
Marek Novak
ACRIOS Systems s.r.o.
Kent Gibson Sept. 13, 2021, 10:45 a.m. UTC | #3
On Fri, Sep 10, 2021 at 09:59:36PM +0200, Marek Novak | ACRIOS Systems s.r.o. wrote:
> On 09. 09. 21 10:20, Bartosz Golaszewski wrote:
> > On Tue, Sep 7, 2021 at 6:10 PM Marek Novak | ACRIOS Systems s.r.o.
> > <novak@acrios.com> wrote:
> > > 
> > >   From ca1b5688de2d1cb63bb9823e28b87c52f23df449 Mon Sep 17 00:00:00 2001
> > > From: Marek NOVAK <novak@acrios.com>
> > > Date: Fri, 3 Sep 2021 18:41:02 +0200
> > > Subject: [PATCH] Adding Lua 5.1 bindings as 'gpiod' Lua module
> > > 
> > > - Adding bindings directory with wrapper code
> > > - Adding Makefile.am for building and distributing as a Lua module
> > > - Adding --enable-bindings-lua option for autogen.sh
> > > - Adding examples with basic lua gpioset and gpioget implementation
> > > - Output, input and event input with new(), get(), set() and wait()
> > >     methods are implemented.
> > > 
> > > Signed-off-by: Marek NOVAK <novak@acrios.com>
> > > ---
> > 
> > Hi Marek!
> > 
> > Thanks for the patch, any new bindings are much appreciated! However
> > the 1.0.x branch has been unsupported for over 3 years. Why did you
> > decide to base your work on this one?
> > 
> > The currently supported branch for the 1.x series is 1.6.x but even
> > then I don't really want to add new features to it as we're currently
> > developing the 2.0 version which will become the new preferred base
> > for all new work. Any chance you could base your work on the
> > next/libgpiod-2.0 branch just like Viresh did with his Rust bindings?
> > 
> > Bartosz
> > 
> 
> Hi Bartosz!
> 
> Thanks for checking my patch. I later realized it was not well processed
> when sent using Thunderbird (I will use git send-email next time). I also
> learnt that on some systems, the macros from ax_lua.m4 are not available and
> I added it in a m4 folder. I have a v2 of the patch almost ready (build
> being tested on different environments by my colleagues). So I can post it
> here if anybody needs it on 1.x version supported...
> 
> To my motivation to select v1.0.x branch - I wanted to select a 1.x branch
> as base since this one is used in OpenWRT distribution (https://github.com/openwrt/packages/blob/openwrt-21.02/libs/libgpiod/Makefile).
> And my target use-case is to have Lua support for GPIO interaction there for
> some late device-specific initialization scripts.
> 
> However, I think I can switch to a 2.x version of libgpiod on our fork of
> OpenWRT v21.02 and base my contribution on it... Currently I use a package
> which builds Lua binding "out of tree", being quite ugly, but available for
> public here (https://sw.acrios.com/acrios/lua-gpiod). I could / should maybe
> also support not just Lua 5.1, but also the other actively used version of
> Lua...
> 
> I will update my contibution to 2.x, test it and then post it here...
> 

Be aware that libgpiod 2.x relies on the GPIO uAPI v2 which was released
in Linux v5.10, while OpenWRT v21.02 has only just transitioned to
Linux v5.4.
So you might be stuck between a rock and a hard place - unless OpenWRT
happens to be planning another kernel jump in the near future.

Cheers,
Kent.
diff mbox series

Patch

diff --git a/Makefile.am b/Makefile.am
index 9b1d00b..174f7a2 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -20,6 +20,13 @@  SUBDIRS += tests

  endif

+if WITH_BINDINGS_LUA
+
+SUBDIRS += bindings
+
+endif
+
+
  if HAS_DOXYGEN

  doc:
diff --git a/bindings/Makefile.am b/bindings/Makefile.am
new file mode 100644
index 0000000..753b377
--- /dev/null
+++ b/bindings/Makefile.am
@@ -0,0 +1,10 @@ 
+# SPDX-License-Identifier: GPL-2.0-or-later
+# SPDX-FileCopyrightText: 2021 Marek Novak <novak@acrios.com>
+
+SUBDIRS = .
+
+if WITH_BINDINGS_LUA
+
+SUBDIRS += lua
+
+endif
diff --git a/bindings/lua/Makefile.am b/bindings/lua/Makefile.am
new file mode 100644
index 0000000..b66ae05
--- /dev/null
+++ b/bindings/lua/Makefile.am
@@ -0,0 +1,20 @@ 
+# SPDX-License-Identifier: GPL-2.0-or-later
+# SPDX-FileCopyrightText: 2021 Marek Novak <novak@acrios.com>
+
+lualibdir = ${libdir}/lua/${LUA_VERSION}
+
+lualib_LTLIBRARIES = gpiod.la
+
+gpiod_la_SOURCES = gpiod.c
+gpiod_la_CFLAGS = -Wall -Wextra -g -fPIC -pedantic -std=c11 $(LUA_INCLUDE)
+gpiod_la_CFLAGS += -I$(top_srcdir)/include/
+gpiod_la_LDFLAGS = -std=c11 -shared -module -lgpiod -version-info 1:0:0 
$(LUA_LDFLAGS)
+gpiod_la_LIBADD = $(LUA_LIB)
+
+SUBDIRS = .
+
+if WITH_EXAMPLES
+
+SUBDIRS += examples
+
+endif
diff --git a/bindings/lua/examples/Makefile.am 
b/bindings/lua/examples/Makefile.am
new file mode 100644
index 0000000..b2abaf1
--- /dev/null
+++ b/bindings/lua/examples/Makefile.am
@@ -0,0 +1,6 @@ 
+# SPDX-License-Identifier: GPL-2.0-or-later
+# SPDX-FileCopyrightText: 2021 Marek Novak <novak@acrios.com>
+
+EXTRA_DIST =				\
+		gpioget.lua		\
+		gpioset.lua
diff --git a/bindings/lua/examples/gpioget.lua 
b/bindings/lua/examples/gpioget.lua
new file mode 100644
index 0000000..57c8051
--- /dev/null
+++ b/bindings/lua/examples/gpioget.lua
@@ -0,0 +1,28 @@ 
+-- SPDX-License-Identifier: GPL-2.0-or-later
+-- SPDX-FileCopyrightText: 2021 Marek Novak <novak@acrios.com>
+
+-- Simplified reimplementation of the gpioget tool in Lua.
+
+require 'gpiod'
+
+
+chip = arg[1]
+offsets = {unpack(arg, 2)}
+if chip == nil or #offsets < 1 then
+    print("usage: gpioget.lua <gpiochip> <offset1> <offset2> ...")
+else
+    ans = ""
+    for index, offset in ipairs(offsets) do
+        line = gpiod:new({chip, offset, "in"})
+        if line == nil then
+            print("failed accessing gpio " .. tostring(chip) .. ":" .. 
tostring(offset))
+            ans = nil
+            break
+        end
+
+        ans = ans .. line.get() .. " "
+    end
+    if ans ~= nil then
+	print(ans)
+    end
+end
diff --git a/bindings/lua/examples/gpioset.lua 
b/bindings/lua/examples/gpioset.lua
new file mode 100644
index 0000000..c0be02a
--- /dev/null
+++ b/bindings/lua/examples/gpioset.lua
@@ -0,0 +1,47 @@ 
+-- SPDX-License-Identifier: GPL-2.0-or-later
+-- SPDX-FileCopyrightText: 2021 Marek Novak <novak@acrios.com>
+
+-- Simplified reimplementation of the gpioset tool in Lua.
+
+require 'gpiod'
+
+-- helper split function
+function split(s, delimiter)
+    result = {};
+    for match in (s..delimiter):gmatch("(.-)"..delimiter) do
+        table.insert(result, match);
+    end
+    return result;
+end
+
+function usage()
+    print("usage: gpioset.lua <gpiochip> <offset1=value1> 
<offset2=value2> ...")
+end
+
+
+chip = arg[1]
+offsets_values = {unpack(arg, 2)}
+if chip == nil or #offsets_values < 1 then
+    usage()
+else
+    print(offsets_values[1])
+    for index, offset_value in ipairs(offsets_values) do
+        offset_value_table = split(offset_value, "=")
+        if #offset_value_table ~= 2 then
+            usage()
+            break
+        end
+
+        offset = tonumber(offset_value_table[1])
+        value = tonumber(offset_value_table[2])
+
+        if offset == nil or (value ~= 0 and value ~= 1) then
+            usage()
+            break
+        end
+
+        line = gpiod:new({chip, offset, "in", value})
+        -- unnecessary - the value is set as default on previous line
+        -- line:set(value)
+    end
+end
diff --git a/bindings/lua/gpiod.c b/bindings/lua/gpiod.c
new file mode 100644
index 0000000..7e19af8
--- /dev/null
+++ b/bindings/lua/gpiod.c
@@ -0,0 +1,686 @@ 
+// SPDX-License-Identifier: LGPL-2.1-or-later
+// SPDX-FileCopyrightText: 2021 Marek Novak <novak@acrios.com>
+//
+// gpiod.c
+//
+// When this module is loaded, it registers new gpiod metatable
+// and makes it possible to control GPIOs using libgpiod.
+// Three GPIO line modes are available - in, out and event.
+// Out mode is used to output value to the line,
+// in mode is used to synchronously read the value and
+// event mode is used to wait for a rising or falling or both edges
+// and to read the time of event and type of event which happened.
+// By assigning a nil value to a line created using gpiod:new(),
+// it gets released eventually using the garbage collector or
+// you can force collection using collectgarbage().
+// On loading this module, all GPIO CHIPS/Ports are opened
+// and used later when allocating individual lines.
+
+#include <stdarg.h>
+#include <stdio.h>
+#include <string.h>
+
+#include <gpiod.h>
+
+#include <lua.h>
+#include <lauxlib.h>
+
+// macros
+#define GPIOD_METATABLE "gpiod"
+#define GPIOD_CONSUMER_NAME "Lua"
+#define MAX_GPIO_CHIPS (16)
+#define GPIOD_DBG(verbose, ...) dbg_print(verbose, __func__, __VA_ARGS__)
+// #define PULL_UP_DOWN_SUPPORTED // uncomment to enable pull-up and 
pull-down support
+
+// typedefs
+struct gpiod_ud {
+	lua_Integer chip;
+	lua_Integer line;
+	const char *mode;
+	struct gpiod_chip *chip_obj;
+	struct gpiod_line *line_obj;
+	bool verbose;
+};
+
+// functions
+static int gpiod_mt_index(lua_State *L);
+static int gpiod_finalize(lua_State *L);
+static int gpiod_new(lua_State *L);
+static int gpiod_get(lua_State *L);
+static int gpiod_set(lua_State *L);
+static int gpiod_wait(lua_State *L);
+static void dbg_print(bool enable, const char *func_name, char *fmt, ...);
+
+// module global data
+static int gpiochips_opened; // zero since static
+static struct gpiod_chip *gpiochips[MAX_GPIO_CHIPS];
+
+// for verbose mode printing
+static void dbg_print(bool enable, const char *func_name, char *fmt, ...)
+{
+	va_list argptr;
+
+	if (!enable)
+		return;
+
+	va_start(argptr, fmt);
+	printf("[%s]: ", func_name);
+	vprintf(fmt, argptr);
+	printf("\n");
+	va_end(argptr);
+}
+
+// gpiod:new(p)
+static int gpiod_new(lua_State *L)
+{
+	int chip, line, def_val;
+	bool verbose = false;
+	const char *mode;
+	struct gpiod_chip *chip_obj;
+	struct gpiod_line *line_obj;
+	struct gpiod_line_bulk bulk;
+	bool is_rising_edge = false;
+	bool is_falling_edge = false;
+	bool is_open_drain = false;
+	bool is_open_source = false;
+	bool is_active_low = false;
+
+#ifdef PULL_UP_DOWN_SUPPORTED // not available in all versions of libgpiod
+	bool has_pull_up = false;
+	bool has_pull_down = false;
+#endif
+
+	int rv, flags = 0;
+	size_t arglen;
+
+	// Extract the gpiod initialization data from the stack.
+	luaL_checktype(L, 2, LUA_TTABLE);
+	arglen = lua_objlen(L, 2);
+
+	if (arglen < 3) {
+		lua_settop(L, 0);
+		lua_pushnil(L);
+		return 1;
+	}
+
+	// stack = [self, p]
+	lua_rawgeti(L, 2, 1); // 2, 1 = idx in stack, idx in table
+	// stack = [self, p, p[1]]
+	lua_rawgeti(L, 2, 2);
+	// stack = [self, p, p[1], p[2]]
+	lua_rawgeti(L, 2, 3);
+	// stack = [self, p, p[1], p[2], p[3]]
+
+	chip = lua_tointeger(L, -3);  // p[1]
+	line = lua_tointeger(L, -2);  // p[2]
+	mode = lua_tostring(L, -1); // p[3]
+
+	if (!strcmp(mode, "out")) {
+
+		switch (lua_type(L, 3)) {
+
+		case LUA_TTABLE:
+			arglen = lua_objlen(L, 3);
+			switch (arglen) {
+			case 0:
+				def_val = 0;
+			break;
+
+			case 1:
+				lua_rawgeti(L, 3, 1);
+				def_val = lua_tointeger(L, -1);
+			break;
+
+			case 2:
+				lua_rawgeti(L, 3, 1);
+				lua_rawgeti(L, 3, 2);
+				def_val = lua_tointeger(L, -2);
+				if (lua_tointeger(L, -1)) // open drain ?
+					is_open_drain = true;
+
+			break;
+
+			case 3:
+				lua_rawgeti(L, 3, 1);
+				lua_rawgeti(L, 3, 2);
+				lua_rawgeti(L, 3, 3);
+				def_val = lua_tointeger(L, -3);
+				if (lua_tointeger(L, -2)) // open drain ?
+					is_open_drain = true;
+				if (lua_tointeger(L, -1)) // open source ?
+					is_open_source = true;
+			break;
+
+			case 4:
+				lua_rawgeti(L, 3, 1);
+				lua_rawgeti(L, 3, 2);
+				lua_rawgeti(L, 3, 3);
+				lua_rawgeti(L, 3, 4);
+				def_val = lua_tointeger(L, -4);
+				if (lua_tointeger(L, -3)) // open drain ?
+					is_open_drain = true;
+				if (lua_tointeger(L, -2)) // open source ?
+					is_open_source = true;
+				if (lua_tointeger(L, -1)) // active low ?
+					is_active_low = true;
+			break;
+
+			default:
+				lua_settop(L, 0);
+				lua_pushnil(L);
+				return 1;
+			}
+
+			verbose = luaL_optinteger(L, 4, 0);
+		break;
+
+		case LUA_TNONE:
+		case LUA_TNUMBER:
+			verbose = luaL_optinteger(L, 3, 0);
+			def_val = 0;
+		break;
+
+		default:
+			lua_settop(L, 0);
+			lua_pushnil(L);
+			return 1;
+		}
+
+		if (is_open_drain)
+			flags |= GPIOD_LINE_REQUEST_FLAG_OPEN_DRAIN;
+		if (is_open_source)
+			flags |= GPIOD_LINE_REQUEST_FLAG_OPEN_SOURCE;
+		if (is_active_low)
+			flags |= GPIOD_LINE_REQUEST_FLAG_ACTIVE_LOW;
+	} else if (!strcmp(mode, "in")) {
+
+		switch (lua_type(L, 3)) {
+
+		case LUA_TTABLE:
+
+			arglen = lua_objlen(L, 3);
+			switch (arglen) {
+			case 0:
+				break;
+
+#ifdef PULL_UP_DOWN_SUPPORTED // not available in all versions of libgpiod
+			case 1:
+				lua_rawgeti(L, 3, 1);
+				if (lua_tointeger(L, -1)) // pull up ?
+					has_pull_up = true;
+				break;
+
+			case 2:
+				lua_rawgeti(L, 3, 1);
+				lua_rawgeti(L, 3, 2);
+				if (lua_tointeger(L, -2)) // pull up ?
+					has_pull_up = true;
+
+				if (lua_tointeger(L, -1)) // pull down ?
+					has_pull_down = true;
+				break;
+#endif
+			default:
+				lua_settop(L, 0);
+				lua_pushnil(L);
+				return 1;
+			}
+			verbose = luaL_optinteger(L, 4, 0);
+			break;
+
+		case LUA_TNONE:
+		case LUA_TNUMBER:
+			verbose = luaL_optinteger(L, 3, 0);
+			break;
+
+		default:
+			lua_settop(L, 0);
+			lua_pushnil(L);
+			return 1;
+		}
+
+#ifdef PULL_UP_DOWN_SUPPORTED
+
+		if (has_pull_up)
+			flags |= GPIOD_LINE_REQUEST_FLAG_BIAS_PULL_UP;
+		if (has_pull_down)
+			flags |= GPIOD_LINE_REQUEST_FLAG_BIAS_PULL_DOWN;
+#endif
+
+	} else if (!strcmp(mode, "event")) {
+
+		switch (lua_type(L, 3)) {
+
+		case LUA_TTABLE:
+
+			arglen = lua_objlen(L, 3);
+			switch (arglen) {
+			case 0:
+				is_rising_edge = true;
+				is_falling_edge = false;
+				break;
+
+			case 1:
+				lua_rawgeti(L, 3, 1);
+				is_rising_edge = lua_tointeger(L, -1);
+				break;
+
+			case 2:
+				lua_rawgeti(L, 3, 1);
+				lua_rawgeti(L, 3, 2);
+				is_rising_edge = lua_tointeger(L, -2);
+				is_falling_edge = lua_tointeger(L, -1);
+				break;
+
+#ifdef PULL_UP_DOWN_SUPPORTED
+			case 3:
+				lua_rawgeti(L, 3, 1);
+				lua_rawgeti(L, 3, 2);
+				lua_rawgeti(L, 3, 3);
+				is_rising_edge = lua_tointeger(L, -3);
+				is_falling_edge = lua_tointeger(L, -2);
+				if (lua_tointeger(L, -1)) // pull up ?
+					has_pull_up = true;
+				break;
+
+			case 4:
+				lua_rawgeti(L, 3, 1);
+				lua_rawgeti(L, 3, 2);
+				lua_rawgeti(L, 3, 3);
+				is_rising_edge = lua_tointeger(L, -4);
+				is_falling_edge = lua_tointeger(L, -3);
+				if (lua_tointeger(L, -2)) // pull up ?
+					has_pull_up = true;
+				if (lua_tointeger(L, -1)) // pull down ?
+					has_pull_down = true;
+				break;
+#endif
+			default:
+				lua_settop(L, 0);
+				lua_pushnil(L);
+				return 1;
+			}
+			verbose = luaL_optinteger(L, 4, 0);
+			break;
+
+		case LUA_TNONE:
+		case LUA_TNUMBER:
+			verbose = luaL_optinteger(L, 3, 0);
+			break;
+
+		default:
+			lua_settop(L, 0);
+			lua_pushnil(L);
+			return 1;
+		}
+
+#ifdef PULL_UP_DOWN_SUPPORTED
+		if (has_pull_up)
+			flags |= GPIOD_LINE_REQUEST_FLAG_BIAS_PULL_UP;
+		if (has_pull_down)
+			flags |= GPIOD_LINE_REQUEST_FLAG_BIAS_PULL_DOWN;
+#endif
+
+	}
+
+	lua_settop(L, 0);
+	// stack = []
+
+	// validate chip number, line number and mode...
+	if (chip < gpiochips_opened)
+		chip_obj = gpiochips[chip];
+	else {
+		GPIOD_DBG(verbose, "Bad GPIOCHIP!");
+		lua_settop(L, 0);
+		lua_pushnil(L);
+		return 1;
+	}
+
+	gpiod_line_bulk_init(&bulk);
+	line_obj = gpiod_chip_get_line(chip_obj, line);
+	if (!line_obj) {
+		GPIOD_DBG(verbose, "Failed to get line %d", line);
+		lua_settop(L, 0);
+		lua_pushnil(L);
+		return 1;
+	}
+
+	gpiod_line_bulk_add(&bulk, line_obj);
+
+	if (!strcmp("out", mode)) {
+
+		rv = gpiod_line_request_bulk_output_flags(&bulk,
+			GPIOD_CONSUMER_NAME, flags, &def_val);
+
+		if (rv < 0) {  // failed to open line
+			GPIOD_DBG(verbose,
+				"Fail: line %d, mode 'out', flags 0x%08X!",
+				 line, flags);
+			lua_settop(L, 0);
+			lua_pushnil(L);
+			return 1;
+		}
+	} else if (!strcmp("in", mode)) {
+		rv = gpiod_line_request_bulk_input_flags(&bulk,
+			GPIOD_CONSUMER_NAME, flags);
+		if (rv < 0) {  // failed to open line
+			GPIOD_DBG(verbose,
+				"Fail: line %d, mode 'in', flags 0x%08X!",
+				line, flags);
+			lua_settop(L, 0);
+			lua_pushnil(L);
+			return 1;
+		}
+	} else if (!strcmp("event", mode)) {
+		if (is_rising_edge)
+			rv = gpiod_line_request_bulk_rising_edge_events_flags(
+				&bulk, GPIOD_CONSUMER_NAME, flags);
+		else if (is_falling_edge)
+			rv = gpiod_line_request_bulk_falling_edge_events_flags(
+				&bulk, GPIOD_CONSUMER_NAME, flags);
+		else
+			rv = gpiod_line_request_bulk_both_edges_events_flags(
+				&bulk, GPIOD_CONSUMER_NAME, flags);
+
+		if (rv < 0) {  // failed to open line
+			GPIOD_DBG(verbose,
+				"Fail: line %d, mode '%s', flags 0x%08X!",
+				(is_rising_edge)?"evt rising" :
+				(is_falling_edge)?"evt falling" :
+				"evt rising/falling",
+				line, flags);
+			lua_settop(L, 0);
+			lua_pushnil(L);
+			return 1;
+		}
+
+	} else { // bad mode
+		GPIOD_DBG(verbose, "Bad mode '%s'!", mode);
+		lua_settop(L, 0);
+		lua_pushnil(L);
+		return 1;
+	}
+
+
+	// Create a gpiod instance and set its metatable.
+	struct gpiod_ud *gpiod = (struct gpiod_ud *)lua_newuserdata(L,
+						sizeof(struct gpiod_ud));
+	// stack = [gpiod]
+	luaL_getmetatable(L, GPIOD_METATABLE);
+	// stack = [gpiod, mt]
+	lua_setmetatable(L, 1);
+	// stack = [gpiod]
+
+	// Set up the C data.
+	gpiod->chip = chip;
+	gpiod->line = line;
+	gpiod->mode = mode;
+	gpiod->chip_obj = chip_obj;
+	gpiod->line_obj = line_obj;
+	gpiod->verbose = verbose;
+
+	GPIOD_DBG(gpiod->verbose,
+		"new line chip=%d, line=%d, mode='%s'", chip, line, mode);
+
+	return 1;
+}
+
+// gpiod:set(p)
+static int gpiod_set(lua_State *L)
+{
+	// Expected: stack = [self, key]
+	struct gpiod_ud *gpiod = (struct gpiod_ud *)luaL_checkudata(L, 1,
+							GPIOD_METATABLE);
+	int value = 0;
+	int rv;
+	int t = lua_type(L, 2);
+
+	if (t == LUA_TNUMBER) {
+		value = (int)lua_tointeger(L, 2);
+		value = !(!value);
+	} else {
+		GPIOD_DBG(gpiod->verbose,
+			"Number required as new value, bad type!");
+		return 0;
+	}
+
+	rv = gpiod_line_set_value(gpiod->line_obj, value);
+
+	if (rv)
+		GPIOD_DBG(gpiod->verbose, "Failed to set new value!");
+	else
+		GPIOD_DBG(gpiod->verbose, "GPIO %d.%d=%d",
+			gpiod->chip, gpiod->line, value);
+	return 0;
+}
+
+// gpiod:get(p)
+static int gpiod_get(lua_State *L)
+{
+	// Expected: stack = [self, key]
+	struct gpiod_ud *gpiod = (struct gpiod_ud *)luaL_checkudata(L, 1,
+							GPIOD_METATABLE);
+	int t = lua_type(L, 2);
+	int rv;
+
+	if (t != LUA_TNONE) {
+		GPIOD_DBG(gpiod->verbose, "No extra arguments required!");
+		return 0;
+	}
+
+	rv = gpiod_line_get_value(gpiod->line_obj);
+
+	if (rv < 0) {
+		GPIOD_DBG(gpiod->verbose, "Failed to set new value!");
+		return 0;
+	}
+
+	GPIOD_DBG(gpiod->verbose, "GPIO %d.%d=%d",
+		gpiod->chip, gpiod->line, rv);
+	lua_pushboolean(L, rv);
+	return 1;
+
+}
+
+// gpiod:wait(p)
+static int gpiod_wait(lua_State *L)
+{
+	// Expected: stack = [self, key]
+	struct gpiod_ud *gpiod = (struct gpiod_ud *)luaL_checkudata(L, 1, 
GPIOD_METATABLE);
+
+	struct gpiod_line_event evt;
+	int timeout = 0;
+	int rv;
+	int t = lua_type(L, 2);
+	struct timespec tout;
+
+	if (t != LUA_TNUMBER) {
+		GPIOD_DBG(gpiod->verbose, "Expected numeric timeout value in sec");
+		return 0;
+	}
+
+	timeout = lua_tointeger(L, 2);
+
+	t = lua_type(L, 3);
+	if (t != LUA_TNONE) {
+		GPIOD_DBG(gpiod->verbose, "Only one argument expected!");
+		return 0;
+	}
+
+	tout.tv_nsec = 0;
+	tout.tv_sec = timeout;
+
+	rv = gpiod_line_event_wait(gpiod->line_obj, &tout);
+	if (rv < 0) {
+		GPIOD_DBG(gpiod->verbose, "Failed waiting for event!");
+		return 0;
+	} else if (rv == 0) {
+		GPIOD_DBG(gpiod->verbose, "Timed out waiting for event!");
+		return 0;
+	}
+
+	rv = gpiod_line_event_read(gpiod->line_obj, &evt);
+	if (rv < 0) {
+		GPIOD_DBG(gpiod->verbose,
+			"Failed reading event type!");
+		return 0;
+	}
+	if (evt.event_type == GPIOD_LINE_EVENT_RISING_EDGE)
+		lua_pushstring(L, "rising");
+	else
+		lua_pushstring(L, "falling");
+
+	lua_pushinteger(L, evt.ts.tv_sec);
+	lua_pushinteger(L, evt.ts.tv_nsec);
+
+	GPIOD_DBG(gpiod->verbose, "GPIO %d.%d -> %s @ %d.%09d",
+		gpiod->chip, gpiod->line,
+		(evt.event_type == GPIOD_LINE_EVENT_RISING_EDGE) ?
+		"rising" : "falling",
+		evt.ts.tv_sec, evt.ts.tv_nsec);
+
+	return 3;
+}
+
+// gpiod_mt:index(key)
+static int gpiod_mt_index(lua_State *L)
+{
+
+	// Expected: stack = [self, key]
+	struct gpiod_ud *gpiod = (struct gpiod_ud *)luaL_checkudata(L, 1,
+							GPIOD_METATABLE);
+	int t = lua_type(L, 2);
+	const char *str_key;
+
+	if (t != LUA_TSTRING) {
+		GPIOD_DBG(gpiod->verbose, "Bad property!");
+		lua_pushnil(L);
+		return 1;
+	}
+
+	str_key = lua_tostring(L, 2);
+
+	if (strcmp(str_key, "chip") == 0) {
+		lua_pushinteger(L, gpiod->chip);
+		return 1;
+	} else if (strcmp(str_key, "line") == 0) {
+		lua_pushinteger(L, gpiod->line);
+		return 1;
+	} else if (strcmp(str_key, "mode") == 0) {
+		lua_pushstring(L, gpiod->mode);
+		return 1;
+	}
+
+	if (!strcmp(gpiod->mode, "out")) {
+		static struct luaL_Reg fns_out[] = {
+		{"set", gpiod_set},
+		{NULL, NULL}};
+		for (int idx = 0; fns_out[idx].func; idx++) {
+			if (!strcmp(str_key, fns_out[idx].name)) {
+				lua_pushcfunction(L,
+					fns_out[idx].func);
+				return 1;
+			}
+		}
+	} else if (!strcmp(gpiod->mode, "in")) {
+		static struct luaL_Reg fns_in[] = {
+		{"get", gpiod_get},
+		{NULL, NULL}};
+
+		for (int idx = 0; fns_in[idx].func; idx++) {
+			if (!strcmp(str_key, fns_in[idx].name)) {
+				lua_pushcfunction(L, fns_in[idx].func);
+				return 1;
+			}
+		}
+	} else if (!strcmp(gpiod->mode, "event")) {
+		static struct luaL_Reg fns_event[] = {
+		{"wait", gpiod_wait},
+		{NULL, NULL}};
+
+		for (int idx = 0; fns_event[idx].func; idx++) {
+			if (!strcmp(str_key, fns_event[idx].name)) {
+				lua_pushcfunction(L,
+					fns_event[idx].func);
+				return 1;
+			}
+		}
+	}
+
+
+	GPIOD_DBG(gpiod->verbose, "Bad property!");
+	lua_pushnil(L);
+	return 1;
+}
+
+static int gpiod_finalize(lua_State *L)
+{
+	struct gpiod_ud *gpiod = (struct gpiod_ud *)luaL_checkudata(L, 1,
+							GPIOD_METATABLE);
+	GPIOD_DBG(gpiod->verbose, "finalizing GPIO %d.%d",
+		gpiod->chip, gpiod->line);
+	gpiod_line_release(gpiod->line_obj);
+	return 0;
+}
+
+
+
+// entrypoint
+int luaopen_gpiod(lua_State *L)
+{
+
+	// The user may pass in values here,
+	// but we'll ignore those values.
+	lua_settop(L, 0);
+
+	// stack = []
+
+	// If this metatable already exists, the library is already
+	// loaded.
+	if (luaL_newmetatable(L, GPIOD_METATABLE)) {
+		// stack = [mt]
+		static struct luaL_Reg metamethods[] = {
+			{"__index", gpiod_mt_index},
+			{"__gc", gpiod_finalize},
+			{NULL, NULL}};
+		lua_setglobal(L, GPIOD_METATABLE);
+		luaL_register(L, GPIOD_METATABLE, metamethods);
+		// in lua 5.2+ luaL_setfuncs(L, metamethods, 0);
+		lua_pop(L, 1); // The table is saved in the Lua's registry.
+
+		// stack = []
+		struct gpiod_chip_iter *iter;
+		struct gpiod_chip *chip;
+
+		iter = gpiod_chip_iter_new();
+		gpiochips_opened = 0;
+		if (!iter)
+			return 0;
+		gpiod_foreach_chip_noclose(iter, chip) {
+			gpiochips[gpiochips_opened] = chip;
+			gpiochips_opened++;
+			if (gpiochips_opened >= MAX_GPIO_CHIPS) {
+				GPIOD_DBG(true,
+					"Limiting number of GPIOCHIPS!");
+				break;
+			}
+		}
+
+		gpiod_chip_iter_free(iter);
+
+	}
+
+	static struct luaL_Reg fns[] = {
+	{"new", gpiod_new},
+	{"set", gpiod_set},
+	{"get", gpiod_get},
+	{"wait", gpiod_wait},
+	{NULL, NULL}};
+
+	luaL_register(L, GPIOD_METATABLE, fns); // Push a new table with fns 
key/vals.
+	// lua 5.2+ luaL_newlib(L, fns);
+
+	// stack = [gpiod = {new = new}]
+
+	return 1; // Return the top item, the gpiod table.
+}
diff --git a/configure.ac b/configure.ac
index 523c21e..2ea0067 100644
--- a/configure.ac
+++ b/configure.ac
@@ -113,6 +113,25 @@  then
  	PKG_CHECK_MODULES([UDEV], [libudev >= 215])
  fi

+AC_ARG_ENABLE([examples],
+	[AS_HELP_STRING([--enable-examples], [enable building code 
examples[default=no]])],
+	[if test "x$enableval" = xyes; then with_examples=true; fi],
+	[with_examples=false])
+AM_CONDITIONAL([WITH_EXAMPLES], [test "x$with_examples" = xtrue])
+
+AC_ARG_ENABLE([bindings-lua],
+	[AS_HELP_STRING([--enable-bindings-lua],[enable lua 5.1 bindings 
[default=no]])],
+	[if test "x$enableval" = xyes; then with_bindings_lua=true; fi],
+	[with_bindings_lua=false])
+AM_CONDITIONAL([WITH_BINDINGS_LUA], [test "x$with_bindings_lua" = xtrue])
+
+if test "x$with_bindings_lua" = xtrue
+then
+	AX_PROG_LUA([5.1], [5.2])
+	AX_LUA_HEADERS
+	AX_LUA_LIBS
+fi
+
  AC_CHECK_PROG([has_doxygen], [doxygen], [true], [false])