From: Bartosz Golaszewski <brgl@bgdev.pl>
To: Kent Gibson <warthog618@gmail.com>,
Linus Walleij <linus.walleij@linaro.org>,
Andy Shevchenko <andriy.shevchenko@linux.intel.com>,
Darrien <darrien@freenet.de>,
Viresh Kumar <viresh.kumar@linaro.org>, Jiri Benc <jbenc@upir.cz>,
Joel Savitz <joelsavitz@gmail.com>
Cc: linux-gpio@vger.kernel.org, Bartosz Golaszewski <brgl@bgdev.pl>
Subject: [libgpiod v2][PATCH 5/5] bindings: python: add the implementation for v2 API
Date: Wed, 25 May 2022 16:07:04 +0200 [thread overview]
Message-ID: <20220525140704.94983-6-brgl@bgdev.pl> (raw)
In-Reply-To: <20220525140704.94983-1-brgl@bgdev.pl>
This is the implementation of the new python API for libgpiod v2.
Signed-off-by: Bartosz Golaszewski <brgl@bgdev.pl>
---
bindings/python/.gitignore | 1 +
bindings/python/Makefile.am | 40 +
bindings/python/chip-info.c | 126 +++
bindings/python/chip.c | 552 +++++++++++
bindings/python/edge-event-buffer.c | 301 ++++++
bindings/python/edge-event.c | 191 ++++
bindings/python/exception.c | 182 ++++
bindings/python/info-event.c | 175 ++++
bindings/python/line-config.c | 1338 +++++++++++++++++++++++++++
bindings/python/line-info.c | 286 ++++++
bindings/python/line-request.c | 713 ++++++++++++++
bindings/python/line.c | 239 +++++
bindings/python/module.c | 281 ++++++
bindings/python/module.h | 57 ++
bindings/python/request-config.c | 320 +++++++
configure.ac | 3 +-
16 files changed, 4804 insertions(+), 1 deletion(-)
create mode 100644 bindings/python/.gitignore
create mode 100644 bindings/python/Makefile.am
create mode 100644 bindings/python/chip-info.c
create mode 100644 bindings/python/chip.c
create mode 100644 bindings/python/edge-event-buffer.c
create mode 100644 bindings/python/edge-event.c
create mode 100644 bindings/python/exception.c
create mode 100644 bindings/python/info-event.c
create mode 100644 bindings/python/line-config.c
create mode 100644 bindings/python/line-info.c
create mode 100644 bindings/python/line-request.c
create mode 100644 bindings/python/line.c
create mode 100644 bindings/python/module.c
create mode 100644 bindings/python/module.h
create mode 100644 bindings/python/request-config.c
diff --git a/bindings/python/.gitignore b/bindings/python/.gitignore
new file mode 100644
index 0000000..bee8a64
--- /dev/null
+++ b/bindings/python/.gitignore
@@ -0,0 +1 @@
+__pycache__
diff --git a/bindings/python/Makefile.am b/bindings/python/Makefile.am
new file mode 100644
index 0000000..3f7ee5f
--- /dev/null
+++ b/bindings/python/Makefile.am
@@ -0,0 +1,40 @@
+# SPDX-License-Identifier: GPL-2.0-or-later
+# SPDX-FileCopyrightText: 2017-2021 Bartosz Golaszewski <bartekgola@gmail.com>
+
+pyexec_LTLIBRARIES = gpiod.la
+
+gpiod_la_SOURCES = \
+ chip.c \
+ chip-info.c \
+ edge-event.c \
+ edge-event-buffer.c \
+ exception.c \
+ info-event.c \
+ line.c \
+ line-config.c \
+ line-info.c \
+ line-request.c \
+ module.c \
+ module.h \
+ request-config.c
+
+gpiod_la_CFLAGS = -I$(top_srcdir)/include/
+gpiod_la_CFLAGS += -Wall -Wextra -g -std=gnu89 $(PYTHON_CPPFLAGS)
+gpiod_la_CFLAGS += -include $(top_builddir)/config.h
+gpiod_la_LDFLAGS = -module -avoid-version
+gpiod_la_LIBADD = $(top_builddir)/lib/libgpiod.la $(PYTHON_LIBS)
+gpiod_la_LIBADD += $(top_builddir)/bindings/python/enum/libpycenum.la
+
+SUBDIRS = enum .
+
+if WITH_TESTS
+
+SUBDIRS += tests
+
+endif
+
+if WITH_EXAMPLES
+
+SUBDIRS += examples
+
+endif
diff --git a/bindings/python/chip-info.c b/bindings/python/chip-info.c
new file mode 100644
index 0000000..e48cf74
--- /dev/null
+++ b/bindings/python/chip-info.c
@@ -0,0 +1,126 @@
+// SPDX-License-Identifier: LGPL-2.1-or-later
+// SPDX-FileCopyrightText: 2022 Bartosz Golaszewski <brgl@bgdev.pl>
+
+#include "module.h"
+
+typedef struct {
+ PyObject_HEAD;
+ struct gpiod_chip_info *info;
+} chip_info_object;
+
+static int chip_info_init(PyObject *Py_UNUSED(self),
+ PyObject *Py_UNUSED(ignored0),
+ PyObject *Py_UNUSED(ignored1))
+{
+ PyErr_SetString(PyExc_TypeError,
+ "cannot create 'gpiod.ChipInfo' instances");
+ return -1;
+}
+
+static void chip_info_finalize(chip_info_object *self)
+{
+ if (self->info)
+ gpiod_chip_info_free(self->info);
+}
+
+PyDoc_STRVAR(chip_info_name_doc,
+"Name of the chip as represented in the kernel.");
+
+static PyObject *chip_info_name(chip_info_object *self,
+ void *Py_UNUSED(ignored))
+{
+ return PyUnicode_FromString(gpiod_chip_info_get_name(self->info));
+}
+
+PyDoc_STRVAR(chip_info_label_doc,
+"Label of the chip as represented in the kernel.");
+
+static PyObject *chip_info_label(chip_info_object *self,
+ void *Py_UNUSED(ignored))
+{
+ return PyUnicode_FromString(gpiod_chip_info_get_label(self->info));
+}
+
+PyDoc_STRVAR(chip_info_num_lines_doc,
+"Number of GPIO lines exposed by the chip.");
+
+static PyObject *chip_info_num_lines(chip_info_object *self,
+ void *Py_UNUSED(ignored))
+{
+ return PyLong_FromUnsignedLong(
+ gpiod_chip_info_get_num_lines(self->info));
+}
+
+static PyGetSetDef chip_info_getset[] = {
+ {
+ .name = "name",
+ .get = (getter)chip_info_name,
+ .doc = chip_info_name_doc,
+ },
+ {
+ .name = "label",
+ .get = (getter)chip_info_label,
+ .doc = chip_info_label_doc,
+ },
+ {
+ .name = "num_lines",
+ .get = (getter)chip_info_num_lines,
+ .doc = chip_info_num_lines_doc
+ },
+ { }
+};
+
+static PyObject *chip_info_str(PyObject *self)
+{
+ PyObject *name, *label, *num_lines, *str = NULL;
+
+ name = PyObject_GetAttrString(self, "name");
+ label = PyObject_GetAttrString(self, "label");
+ num_lines = PyObject_GetAttrString(self, "num_lines");
+ if (!name || !label || !num_lines)
+ goto out;
+
+ str = PyUnicode_FromFormat("<gpiod.ChipInfo name=\"%S\" label=\"%S\" num_lines=%S>",
+ name, label, num_lines);
+
+out:
+ Py_XDECREF(name);
+ Py_XDECREF(label);
+ Py_XDECREF(num_lines);
+ return str;
+}
+
+PyDoc_STRVAR(chip_info_type_doc,
+"Chip info object contains an immutable snapshot of a chip's status.");
+
+static PyTypeObject chip_info_type = {
+ PyVarObject_HEAD_INIT(NULL, 0)
+ .tp_name = "gpiod.ChipInfo",
+ .tp_basicsize = sizeof(chip_info_object),
+ .tp_flags = Py_TPFLAGS_DEFAULT,
+ .tp_doc = chip_info_type_doc,
+ .tp_new = PyType_GenericNew,
+ .tp_init = (initproc)chip_info_init,
+ .tp_finalize = (destructor)chip_info_finalize,
+ .tp_dealloc = (destructor)Py_gpiod_dealloc,
+ .tp_getset = chip_info_getset,
+ .tp_str = (reprfunc)chip_info_str
+};
+
+int Py_gpiod_RegisterChipInfoType(PyObject *module)
+{
+ return PyModule_AddType(module, &chip_info_type);
+}
+
+PyObject *Py_gpiod_MakeChipInfo(struct gpiod_chip_info *info)
+{
+ chip_info_object *info_obj;
+
+ info_obj = PyObject_New(chip_info_object, &chip_info_type);
+ if (!info_obj)
+ return NULL;
+
+ info_obj->info = info;
+
+ return (PyObject *)info_obj;
+}
diff --git a/bindings/python/chip.c b/bindings/python/chip.c
new file mode 100644
index 0000000..47b8e1c
--- /dev/null
+++ b/bindings/python/chip.c
@@ -0,0 +1,552 @@
+// SPDX-License-Identifier: LGPL-2.1-or-later
+// SPDX-FileCopyrightText: 2022 Bartosz Golaszewski <brgl@bgdev.pl>
+
+#include <stdint.h>
+
+#include "module.h"
+
+typedef struct {
+ PyObject_HEAD;
+ struct gpiod_chip *chip;
+} chip_object;
+
+static int chip_init(chip_object *self, PyObject *args,
+ PyObject *Py_UNUSED(ignored))
+{
+ char *path;
+ int ret;
+
+ ret = PyArg_ParseTuple(args, "s", &path);
+ if (!ret)
+ return -1;
+
+ Py_BEGIN_ALLOW_THREADS;
+ self->chip = gpiod_chip_open(path);
+ Py_END_ALLOW_THREADS;
+ if (!self->chip) {
+ Py_gpiod_SetErrFromErrno();
+ return -1;
+ }
+
+ return 0;
+}
+
+static bool chip_is_closed(chip_object *self)
+{
+ return !self->chip;
+}
+
+static bool chip_check_closed(chip_object *self)
+{
+ if (chip_is_closed(self)) {
+ Py_gpiod_SetChipClosedError();
+ return true;
+ }
+
+ return false;
+}
+
+static void chip_finalize(chip_object *self)
+{
+ if (!chip_is_closed(self))
+ PyObject_CallMethod((PyObject *)self, "close", "");
+}
+
+PyDoc_STRVAR(chip_path_doc,
+"Path to the file passed as argument to the constructor.");
+
+static PyObject *chip_path(chip_object *self, void *Py_UNUSED(ignored))
+{
+ if (chip_check_closed(self))
+ return NULL;
+
+ return PyUnicode_FromString(gpiod_chip_get_path(self->chip));
+}
+
+PyDoc_STRVAR(chip_fd_doc,
+"Number of the file descriptor associated with this chip.");
+
+static PyObject *chip_fd(chip_object *self, void *Py_UNUSED(ignored))
+{
+ if (chip_check_closed(self))
+ return NULL;
+
+ return PyLong_FromLong(gpiod_chip_get_fd(self->chip));
+}
+
+static PyGetSetDef chip_getset[] = {
+ {
+ .name = "path",
+ .get = (getter)chip_path,
+ .doc = chip_path_doc,
+ },
+ {
+ .name = "fd",
+ .get = (getter)chip_fd,
+ .doc = chip_fd_doc
+ },
+ { }
+};
+
+PyDoc_STRVAR(chip_enter_doc, "Controlled execution enter callback.");
+
+static PyObject *chip_enter(PyObject *self, PyObject *Py_UNUSED(ignored))
+{
+ if (PyObject_Not(self)) {
+ Py_gpiod_SetChipClosedError();
+ return NULL;
+ }
+
+ Py_INCREF(self);
+ return self;
+}
+
+PyDoc_STRVAR(chip_exit_doc, "Controlled execution exit callback.");
+
+static PyObject *chip_exit(PyObject *self, PyObject *Py_UNUSED(ignored))
+{
+ return PyObject_CallMethod(self, "close", "");
+}
+
+PyDoc_STRVAR(chip_close_doc,
+"close() -> None\n"
+"\n"
+"Close the associated GPIO chip descriptor. The chip object must no longer\n"
+"be used after this method is called.");
+
+static PyObject *chip_close(chip_object *self, PyObject *Py_UNUSED(ignored))
+{
+ if (chip_check_closed(self))
+ return NULL;
+
+ Py_BEGIN_ALLOW_THREADS;
+ gpiod_chip_close(self->chip);
+ Py_END_ALLOW_THREADS;
+ self->chip = NULL;
+
+ Py_RETURN_NONE;
+}
+
+PyDoc_STRVAR(chip_get_info_doc,
+"get_info() -> gpiod.ChipInfo\n"
+"\n"
+"Get the information about the chip.");
+
+static PyObject *chip_get_info(chip_object *self, PyObject *Py_UNUSED(ignored))
+{
+ struct gpiod_chip_info *info;
+ PyObject *info_obj;
+
+ if (chip_check_closed(self))
+ return NULL;
+
+ Py_BEGIN_ALLOW_THREADS;
+ info = gpiod_chip_get_info(self->chip);
+ Py_END_ALLOW_THREADS;
+ if (!info)
+ return Py_gpiod_SetErrFromErrno();
+
+ info_obj = Py_gpiod_MakeChipInfo(info);
+ if (!info_obj) {
+ gpiod_chip_info_free(info);
+ return NULL;
+ }
+
+ return info_obj;
+}
+
+static PyObject *
+do_chip_get_line_info(chip_object *self, PyObject *args, bool watch)
+{
+ struct gpiod_line_info *info;
+ unsigned int offset;
+ PyObject *info_obj;
+ int ret;
+
+ if (chip_check_closed(self))
+ return NULL;
+
+ ret = PyArg_ParseTuple(args, "I", &offset);
+ if (!ret)
+ return NULL;
+
+ Py_BEGIN_ALLOW_THREADS;
+ if (watch)
+ info = gpiod_chip_watch_line_info(self->chip, offset);
+ else
+ info = gpiod_chip_get_line_info(self->chip, offset);
+ Py_END_ALLOW_THREADS;
+ if (!info)
+ return Py_gpiod_SetErrFromErrno();
+
+ info_obj = Py_gpiod_MakeLineInfo(info);
+ if (!info_obj)
+ gpiod_line_info_free(info);
+
+ return info_obj;
+}
+
+PyDoc_STRVAR(chip_get_line_info_doc,
+"get_line_info(offset) -> gpiod.LineInfo\n"
+"\n"
+"Get the snapshot of information about the line at given offset.\n"
+"\n"
+" offset\n"
+" Offset of the GPIO line to get information for");
+
+static PyObject *chip_get_line_info(chip_object *self, PyObject *args)
+{
+ return do_chip_get_line_info(self, args, false);
+}
+
+PyDoc_STRVAR(chip_watch_line_info_doc,
+"watch_line_info(offset) -> gpiod.LineInfo\n"
+"\n"
+"Get the snapshot of information about the line at given offset and start\n"
+"watching it for future changes.\n"
+"\n"
+" offset\n"
+" Offset of the GPIO line to get information for");
+
+static PyObject *chip_watch_line_info(chip_object *self, PyObject *args)
+{
+ return do_chip_get_line_info(self, args, true);
+}
+
+PyDoc_STRVAR(chip_unwatch_line_info_doc,
+"unwatch_line_info(offset) -> None\n"
+"\n"
+"Stop watching a line for status changes.\n"
+"\n"
+" offset\n"
+" Offset of the line to stop watching");
+
+static PyObject *chip_unwatch_line_info(chip_object *self, PyObject *args)
+{
+ unsigned int offset;
+ int ret;
+
+ if (chip_check_closed(self))
+ return NULL;
+
+ ret = PyArg_ParseTuple(args, "I", &offset);
+ if (!ret)
+ return NULL;
+
+ Py_BEGIN_ALLOW_THREADS;
+ ret = gpiod_chip_unwatch_line_info(self->chip, offset);
+ Py_END_ALLOW_THREADS;
+ if (ret)
+ return Py_gpiod_SetErrFromErrno();
+
+ Py_RETURN_NONE;
+}
+
+PyDoc_STRVAR(chip_wait_info_event_doc,
+"wait_info_event(timeout) -> Bool\n"
+"\n"
+"Wait for line status change events on any of the watched lines on the chip.\n"
+"\n"
+" timeout\n"
+" Wait time limit stored represented as a datetime.timedelta object");
+
+static PyObject *chip_wait_info_event(chip_object *self, PyObject *args)
+{
+ uint64_t timeout_us, timeout_ns;
+ PyObject *timedelta;
+ int ret;
+
+ if (chip_check_closed(self))
+ return NULL;
+
+ ret = PyArg_ParseTuple(args, "O", &timedelta);
+ if (!ret)
+ return NULL;
+
+ timeout_us = Py_gpiod_TimedeltaToMicroseconds(timedelta);
+ if (PyErr_Occurred())
+ return NULL;
+
+ timeout_ns = timeout_us * 1000;
+
+ Py_BEGIN_ALLOW_THREADS;
+ ret = gpiod_chip_wait_info_event(self->chip, timeout_ns);
+ Py_END_ALLOW_THREADS;
+ if (ret < 0)
+ return Py_gpiod_SetErrFromErrno();
+
+ return PyBool_FromLong(ret);
+}
+
+PyDoc_STRVAR(chip_read_info_event_doc,
+"read_info_event() -> gpiod.InfoEvent\n"
+"\n"
+"Read a single line status change event from the chip.");
+
+static PyObject *
+chip_read_info_event(chip_object *self, PyObject *Py_UNUSED(ignored))
+{
+ struct gpiod_info_event *event;
+ PyObject *event_obj;
+
+ if (chip_check_closed(self))
+ return NULL;
+
+ Py_BEGIN_ALLOW_THREADS;
+ event = gpiod_chip_read_info_event(self->chip);
+ Py_END_ALLOW_THREADS;
+ if (!event)
+ return Py_gpiod_SetErrFromErrno();
+
+ event_obj = Py_gpiod_MakeInfoEvent(event);
+ if (!event_obj)
+ gpiod_info_event_free(event);
+
+ return event_obj;
+}
+
+PyDoc_STRVAR(chip_get_line_offset_from_name_doc,
+"find_line(name) -> int\n"
+"\n"
+"Map a line's name to its offset within the chip."
+"\n"
+" name\n"
+" Name of the GPIO line to map");
+
+static PyObject *
+chip_get_line_offset_from_name(chip_object *self, PyObject *args)
+{
+ const char *name;
+ int ret, offset;
+
+ if (chip_check_closed(self))
+ return NULL;
+
+ ret = PyArg_ParseTuple(args, "s", &name);
+ if (!ret)
+ return NULL;
+
+ Py_BEGIN_ALLOW_THREADS;
+ offset = gpiod_chip_get_line_offset_from_name(self->chip, name);
+ Py_END_ALLOW_THREADS;
+ if (offset < 0) {
+ if (errno == ENOENT)
+ Py_RETURN_NONE;
+
+ return Py_gpiod_SetErrFromErrno();
+ }
+
+ return PyLong_FromUnsignedLong(offset);
+}
+
+PyDoc_STRVAR(chip_request_lines_doc,
+"request_lines(req_cfg, line_cfg) -> gpiod.LineRequest\n"
+"\n"
+"Request a set of lines for exclusive usage.\n"
+"\n"
+" req_cfg\n"
+" Request config object\n"
+" line_cfg\n"
+" Line config object");
+
+static PyObject *chip_request_lines(chip_object *self, PyObject *args)
+{
+ PyObject *req_cfg_obj, *line_cfg_obj, *req_obj;
+ struct gpiod_request_config *req_cfg;
+ struct gpiod_line_config *line_cfg;
+ struct gpiod_line_request *req;
+ int ret;
+
+ if (chip_check_closed(self))
+ return NULL;
+
+ ret = PyArg_ParseTuple(args, "OO", &req_cfg_obj, &line_cfg_obj);
+ if (!ret)
+ return NULL;
+
+ req_cfg = Py_gpiod_RequestConfigGetData(req_cfg_obj);
+ if (!req_cfg)
+ return NULL;
+
+ line_cfg = Py_gpiod_LineConfigGetData(line_cfg_obj);
+ if (!line_cfg)
+ return NULL;
+
+ req = gpiod_chip_request_lines(self->chip, req_cfg, line_cfg);
+ if (!req) {
+ Py_gpiod_SetErrFromErrno();
+ return NULL;
+ }
+
+ req_obj = Py_gpiod_MakeLineRequest(req);
+ if (!req_obj) {
+ gpiod_line_request_release(req);
+ return NULL;
+ }
+
+ return req_obj;
+}
+
+static PyMethodDef chip_methods[] = {
+ {
+ .ml_name = "__enter__",
+ .ml_meth = (PyCFunction)chip_enter,
+ .ml_flags = METH_NOARGS,
+ .ml_doc = chip_enter_doc,
+ },
+ {
+ .ml_name = "__exit__",
+ .ml_meth = (PyCFunction)chip_exit,
+ .ml_flags = METH_VARARGS,
+ .ml_doc = chip_exit_doc,
+ },
+ {
+ .ml_name = "close",
+ .ml_meth = (PyCFunction)chip_close,
+ .ml_flags = METH_NOARGS,
+ .ml_doc = chip_close_doc,
+ },
+ {
+ .ml_name = "get_info",
+ .ml_meth = (PyCFunction)chip_get_info,
+ .ml_flags = METH_NOARGS,
+ .ml_doc = chip_get_info_doc,
+ },
+ {
+ .ml_name = "get_line_info",
+ .ml_meth = (PyCFunction)chip_get_line_info,
+ .ml_flags = METH_VARARGS,
+ .ml_doc = chip_get_line_info_doc,
+ },
+ {
+ .ml_name = "watch_line_info",
+ .ml_meth = (PyCFunction)chip_watch_line_info,
+ .ml_flags = METH_VARARGS,
+ .ml_doc = chip_watch_line_info_doc,
+ },
+ {
+ .ml_name = "unwatch_line_info",
+ .ml_meth = (PyCFunction)chip_unwatch_line_info,
+ .ml_flags = METH_VARARGS,
+ .ml_doc = chip_unwatch_line_info_doc,
+ },
+ {
+ .ml_name = "wait_info_event",
+ .ml_meth = (PyCFunction)chip_wait_info_event,
+ .ml_flags = METH_VARARGS,
+ .ml_doc = chip_wait_info_event_doc,
+ },
+ {
+ .ml_name = "read_info_event",
+ .ml_meth = (PyCFunction)chip_read_info_event,
+ .ml_flags = METH_NOARGS,
+ .ml_doc = chip_read_info_event_doc,
+ },
+ {
+ .ml_name = "get_line_offset_from_name",
+ .ml_meth = (PyCFunction)chip_get_line_offset_from_name,
+ .ml_flags = METH_VARARGS,
+ .ml_doc = chip_get_line_offset_from_name_doc,
+ },
+ {
+ .ml_name = "request_lines",
+ .ml_meth = (PyCFunction)chip_request_lines,
+ .ml_flags = METH_VARARGS,
+ .ml_doc = chip_request_lines_doc,
+ },
+ { }
+};
+
+static PyObject *chip_str_closed(void)
+{
+ return PyUnicode_FromString("<gpiod.Chip CLOSED>");
+}
+
+static PyObject *chip_repr(chip_object *self)
+{
+ if (chip_is_closed(self))
+ return chip_str_closed();
+
+ return PyUnicode_FromFormat("gpiod.Chip(\"%s\")",
+ gpiod_chip_get_path(self->chip));
+}
+
+static PyObject *chip_str(PyObject *self)
+{
+ PyObject *path, *fd, *info, *str = NULL;
+
+ if (PyObject_Not(self))
+ return chip_str_closed();
+
+ path = PyObject_GetAttrString(self, "path");
+ fd = PyObject_GetAttrString(self, "fd");
+ info = PyObject_CallMethod(self, "get_info", NULL);
+ if (!path || !fd || !info)
+ goto out;
+
+ str = PyUnicode_FromFormat("<gpiod.Chip path=\"%S\" fd=%S info=%S>",
+ path, fd, info);
+
+out:
+ Py_XDECREF(path);
+ Py_XDECREF(fd);
+ Py_XDECREF(info);
+ return str;
+}
+
+static int chip_bool(chip_object *self)
+{
+ return !chip_is_closed(self);
+}
+
+static PyNumberMethods chip_number_methods = {
+ .nb_bool = (inquiry)chip_bool,
+};
+
+PyDoc_STRVAR(chip_type_doc,
+"Represents a GPIO chip.\n"
+"\n"
+"Chip object manages all resources associated with the GPIO chip\n"
+"it represents.\n"
+"\n"
+"The gpiochip device file is opened during the object's construction.\n"
+"The Chip object's constructor takes the path to the GPIO chip device file\n"
+"as the only argument.\n"
+"\n"
+"Callers must close the chip by calling the close() method when it's no\n"
+"longer used.\n"
+"\n"
+"Example:\n"
+"\n"
+" chip = gpiod.Chip(\"/dev/gpiochip0\")\n"
+" do_something(chip)\n"
+" chip.close()\n"
+"\n"
+"The gpiod.Chip class also supports controlled execution ('with' statement).\n"
+"\n"
+"Example:\n"
+"\n"
+" with gpiod.Chip(\"/dev/gpiochip0\") as chip:\n"
+" do_something(chip)");
+
+static PyTypeObject chip_type = {
+ PyVarObject_HEAD_INIT(NULL, 0)
+ .tp_name = "gpiod.Chip",
+ .tp_basicsize = sizeof(chip_object),
+ .tp_flags = Py_TPFLAGS_DEFAULT,
+ .tp_doc = chip_type_doc,
+ .tp_as_number = &chip_number_methods,
+ .tp_new = PyType_GenericNew,
+ .tp_init = (initproc)chip_init,
+ .tp_finalize = (destructor)chip_finalize,
+ .tp_dealloc = (destructor)Py_gpiod_dealloc,
+ .tp_getset = chip_getset,
+ .tp_methods = chip_methods,
+ .tp_repr = (reprfunc)chip_repr,
+ .tp_str = (reprfunc)chip_str
+};
+
+int Py_gpiod_RegisterChipType(PyObject *module)
+{
+ return PyModule_AddType(module, &chip_type);
+}
diff --git a/bindings/python/edge-event-buffer.c b/bindings/python/edge-event-buffer.c
new file mode 100644
index 0000000..5dba3ec
--- /dev/null
+++ b/bindings/python/edge-event-buffer.c
@@ -0,0 +1,301 @@
+// SPDX-License-Identifier: LGPL-2.1-or-later
+// SPDX-FileCopyrightText: 2022 Bartosz Golaszewski <brgl@bgdev.pl>
+
+#include "module.h"
+
+typedef struct {
+ PyObject_HEAD;
+ struct gpiod_edge_event_buffer *buf;
+ Py_ssize_t seq;
+} edge_event_buffer_object;
+
+static int edge_event_buffer_init(edge_event_buffer_object *self,
+ PyObject *args, PyObject *Py_UNUSED(ignored))
+{
+ Py_ssize_t capacity = 64;
+ int ret;
+
+ ret = PyArg_ParseTuple(args, "|n", &capacity);
+ if (!ret)
+ return -1;
+
+ self->buf = gpiod_edge_event_buffer_new(capacity);
+ if (!self->buf) {
+ Py_gpiod_SetErrFromErrno();
+ return -1;
+ }
+
+ self->seq = -1;
+
+ return 0;
+}
+
+static void edge_event_buffer_finalize(edge_event_buffer_object *self)
+{
+ if (self->buf)
+ gpiod_edge_event_buffer_free(self->buf);
+}
+
+PyDoc_STRVAR(edge_event_buffer_capacity_doc, "Maximum capacity of the buffer.");
+
+static PyObject *edge_event_buffer_capacity(edge_event_buffer_object *self,
+ void *Py_UNUSED(ignored))
+{
+ return PyLong_FromSize_t(
+ gpiod_edge_event_buffer_get_capacity(self->buf));
+}
+
+PyDoc_STRVAR(edge_event_buffer_num_events_doc,
+"Number of events a buffer has stored.");
+
+static PyObject *edge_event_buffer_num_events(edge_event_buffer_object *self,
+ void *Py_UNUSED(ignored))
+{
+ return PyLong_FromSize_t(
+ gpiod_edge_event_buffer_get_num_events(self->buf));
+}
+
+static PyGetSetDef edge_event_buffer_getset[] = {
+ {
+ .name = "capacity",
+ .get = (getter)edge_event_buffer_capacity,
+ .doc = edge_event_buffer_capacity_doc,
+ },
+ {
+ .name = "num_events",
+ .get = (getter)edge_event_buffer_num_events,
+ .doc = edge_event_buffer_num_events_doc,
+ },
+ { }
+};
+
+PyDoc_STRVAR(edge_event_buffer_get_event_doc,
+"get_event(index) -> gpiod.EdgeEvent\n"
+"\n"
+"Get an event stored in the buffer.\n"
+"\n"
+" index\n"
+" Index of the event in the buffer.");
+
+static PyObject *
+do_get_event(struct gpiod_edge_event_buffer *buf, unsigned long index)
+{
+ struct gpiod_edge_event *event, *cpy;
+ PyObject *event_obj;
+
+ event = gpiod_edge_event_buffer_get_event(buf, index);
+ if (!event) {
+ Py_gpiod_SetErrFromErrno();
+ return NULL;
+ }
+
+ cpy = gpiod_edge_event_copy(event);
+ if (!cpy) {
+ Py_gpiod_SetErrFromErrno();
+ return NULL;
+ }
+
+ event_obj = Py_gpiod_MakeEdgeEvent(cpy);
+ if (!event_obj) {
+ gpiod_edge_event_free(cpy);
+ return NULL;
+ }
+
+ return event_obj;
+}
+
+static PyObject *
+edge_event_buffer_get_event(edge_event_buffer_object *self, PyObject *args)
+{
+ unsigned long index;
+ int ret;
+
+ ret = PyArg_ParseTuple(args, "k", &index);
+ if (!ret)
+ return NULL;
+
+ return do_get_event(self->buf, index);
+}
+
+static PyMethodDef edge_event_buffer_methods[] = {
+ {
+ .ml_name = "get_event",
+ .ml_meth = (PyCFunction)edge_event_buffer_get_event,
+ .ml_flags = METH_VARARGS,
+ .ml_doc = edge_event_buffer_get_event_doc,
+ },
+ { }
+};
+
+static PyObject *edge_event_buffer_repr(PyObject *self)
+{
+ PyObject *capacity, *repr;
+
+ capacity = PyObject_GetAttrString(self, "capacity");
+ if (!capacity)
+ return NULL;
+
+ repr = PyUnicode_FromFormat("gpiod.EdgeEventBuffer(%S)", capacity);
+ Py_DECREF(capacity);
+ return repr;
+}
+
+static PyObject *events_str(edge_event_buffer_object *self)
+{
+ PyObject *iter, *next, *list, *str, *joined;
+ size_t num_events;
+ Py_ssize_t i;
+ int ret;
+
+ num_events = gpiod_edge_event_buffer_get_num_events(self->buf);
+
+ list = PyList_New(num_events);
+ if (!list)
+ return NULL;
+
+ iter = PyObject_GetIter((PyObject *)self);
+ if (!iter) {
+ Py_DECREF(list);
+ return NULL;
+ }
+
+ for (i = 0;; i++) {
+ next = PyIter_Next(iter);
+ if (!next) {
+ Py_DECREF(iter);
+ break;
+ }
+
+ str = PyObject_Str(next);
+ Py_DECREF(next);
+ if (!str) {
+ Py_DECREF(iter);
+ Py_DECREF(list);
+ return NULL;
+ }
+
+ ret = PyList_SetItem(list, i, str);
+ if (ret) {
+ Py_DECREF(str);
+ Py_DECREF(iter);
+ Py_DECREF(list);
+ return NULL;
+ }
+ }
+
+ str = PyUnicode_FromString(", ");
+ if (!str) {
+ Py_DECREF(list);
+ return NULL;
+ }
+
+ joined = PyObject_CallMethod(str, "join", "O", list);
+ Py_DECREF(list);
+ return joined;
+}
+
+static PyObject *edge_event_buffer_str(PyObject *self)
+{
+ PyObject *events, *capacity, *num_events, *str = NULL;
+
+ capacity = PyObject_GetAttrString(self, "capacity");
+ num_events = PyObject_GetAttrString(self, "num_events");
+ events = events_str((edge_event_buffer_object *)self);
+ if (!capacity || !num_events || !events)
+ goto out;
+
+ str = PyUnicode_FromFormat(
+ "<gpiod.EdgeEventBuffer capacity=%S num_events=%S events=[%S]>",
+ capacity, num_events, events);
+
+out:
+ Py_XDECREF(capacity);
+ Py_XDECREF(num_events);
+ Py_XDECREF(events);
+ return str;
+}
+
+static Py_ssize_t edge_event_buffer_length(edge_event_buffer_object *self)
+{
+ return gpiod_edge_event_buffer_get_num_events(self->buf);
+}
+
+static PyObject *edge_event_buffer_item(PyObject *self, Py_ssize_t index)
+{
+ return PyObject_CallMethod(self, "get_event", "n", index);
+}
+
+static PySequenceMethods edge_event_buffer_sequence_methods = {
+ .sq_length = (lenfunc)edge_event_buffer_length,
+ .sq_item = (ssizeargfunc)edge_event_buffer_item,
+};
+
+static PyObject *edge_event_buffer_iternext(edge_event_buffer_object *self)
+{
+ PyObject *event;
+
+ if (self->seq < 0)
+ self->seq = 0;
+
+ if ((size_t)self->seq == gpiod_edge_event_buffer_get_num_events(self->buf)) {
+ self->seq = -1;
+ return NULL;
+ }
+
+ event = do_get_event(self->buf, self->seq);
+ if (!event)
+ return NULL;
+
+ self->seq++;
+
+ return event;
+}
+
+PyDoc_STRVAR(edge_event_buffer_type_doc,
+"Object into which edge events are read.");
+
+static PyTypeObject edge_event_buffer_type = {
+ PyVarObject_HEAD_INIT(NULL, 0)
+ .tp_name = "gpiod.EdgeEventBuffer",
+ .tp_basicsize = sizeof(edge_event_buffer_object),
+ .tp_flags = Py_TPFLAGS_DEFAULT,
+ .tp_doc = edge_event_buffer_type_doc,
+ .tp_as_sequence = &edge_event_buffer_sequence_methods,
+ .tp_iter = PyObject_SelfIter,
+ .tp_iternext = (iternextfunc)edge_event_buffer_iternext,
+ .tp_new = PyType_GenericNew,
+ .tp_init = (initproc)edge_event_buffer_init,
+ .tp_finalize = (destructor)edge_event_buffer_finalize,
+ .tp_dealloc = (destructor)Py_gpiod_dealloc,
+ .tp_getset = edge_event_buffer_getset,
+ .tp_methods = edge_event_buffer_methods,
+ .tp_repr = (reprfunc)edge_event_buffer_repr,
+ .tp_str = (reprfunc)edge_event_buffer_str
+};
+
+int Py_gpiod_RegisterEdgeEventBufferType(PyObject *module)
+{
+ return PyModule_AddType(module, &edge_event_buffer_type);
+}
+
+struct gpiod_edge_event_buffer *Py_gpiod_EdgeEventBufferGetData(PyObject *obj)
+{
+ edge_event_buffer_object *bufobj;
+ PyObject *type;
+
+ type = PyObject_Type(obj);
+ if (!type)
+ return NULL;
+
+ if ((PyTypeObject *)type != &edge_event_buffer_type) {
+ PyErr_SetString(PyExc_TypeError,
+ "not a gpiod.EdgeEventBuffer object");
+ Py_DECREF(type);
+ return NULL;
+ }
+ Py_DECREF(type);
+
+ bufobj = (edge_event_buffer_object *)obj;
+
+ return bufobj->buf;
+}
diff --git a/bindings/python/edge-event.c b/bindings/python/edge-event.c
new file mode 100644
index 0000000..7b908e0
--- /dev/null
+++ b/bindings/python/edge-event.c
@@ -0,0 +1,191 @@
+// SPDX-License-Identifier: LGPL-2.1-or-later
+// SPDX-FileCopyrightText: 2022 Bartosz Golaszewski <brgl@bgdev.pl>
+
+#include "enum/enum.h"
+#include "module.h"
+
+typedef struct {
+ PyObject_HEAD;
+ struct gpiod_edge_event *event;
+} edge_event_object;
+
+static const PyCEnum_EnumVal event_type_vals[] = {
+ {
+ .name = "RISING_EDGE",
+ .value = GPIOD_EDGE_EVENT_RISING_EDGE,
+ },
+ {
+ .name = "FALLING_EDGE",
+ .value = GPIOD_EDGE_EVENT_FALLING_EDGE,
+ },
+ { }
+};
+
+static const PyCEnum_EnumDef edge_event_enums[] = {
+ {
+ .name = "Type",
+ .values = event_type_vals,
+ },
+ { }
+};
+
+static int edge_event_init(PyObject *Py_UNUSED(self),
+ PyObject *Py_UNUSED(ignored0),
+ PyObject *Py_UNUSED(ignored1))
+{
+ PyErr_SetString(PyExc_TypeError,
+ "cannot create 'gpiod.EdgeEvent' instances");
+ return -1;
+}
+
+static void edge_event_finalize(edge_event_object *self)
+{
+ if (self->event)
+ gpiod_edge_event_free(self->event);
+}
+
+PyDoc_STRVAR(edge_event_get_type_doc, "Type of the event.");
+
+static PyObject *edge_event_get_type(edge_event_object *self,
+ void *Py_UNUSED(ignored))
+{
+ int type = gpiod_edge_event_get_event_type(self->event);
+
+ return PyCEnum_MapCToPy((PyObject *)self, "Type", type);
+}
+
+PyDoc_STRVAR(edge_event_timestamp_ns_doc, "Time of the event in nanoseconds.");
+
+static PyObject *edge_event_timestamp_ns(edge_event_object *self,
+ void *Py_UNUSED(ignored))
+{
+ return PyLong_FromUnsignedLongLong(
+ gpiod_edge_event_get_timestamp_ns(self->event));
+}
+
+PyDoc_STRVAR(edge_event_line_offset_doc,
+"Offset of the line on which this event was registered.");
+
+static PyObject *edge_event_line_offset(edge_event_object *self,
+ void *Py_UNUSED(ignored))
+{
+ return PyLong_FromUnsignedLong(
+ gpiod_edge_event_get_line_offset(self->event));
+}
+
+PyDoc_STRVAR(edge_event_global_seqno_doc,
+"Sequence number of the event relative to all lines in the associated line\n"
+"request.");
+
+static PyObject *edge_event_global_seqno(edge_event_object *self,
+ void *Py_UNUSED(ignored))
+{
+ return PyLong_FromUnsignedLong(
+ gpiod_edge_event_get_global_seqno(self->event));
+}
+
+PyDoc_STRVAR(edge_event_line_seqno_doc,
+"Sequence number of the event relative to this line within the lifetime of\n"
+"the associated line request..");
+
+static PyObject *edge_event_line_seqno(edge_event_object *self,
+ void *Py_UNUSED(ignored))
+{
+ return PyLong_FromUnsignedLong(
+ gpiod_edge_event_get_line_seqno(self->event));
+}
+
+static PyGetSetDef edge_event_getset[] = {
+ {
+ .name = "type",
+ .get = (getter)edge_event_get_type,
+ .doc = edge_event_get_type_doc,
+ },
+ {
+ .name = "timestamp_ns",
+ .get = (getter)edge_event_timestamp_ns,
+ .doc = edge_event_timestamp_ns_doc,
+ },
+ {
+ .name = "line_offset",
+ .get = (getter)edge_event_line_offset,
+ .doc = edge_event_line_offset_doc,
+ },
+ {
+ .name = "global_seqno",
+ .get = (getter)edge_event_global_seqno,
+ .doc = edge_event_global_seqno_doc,
+ },
+ {
+ .name = "line_seqno",
+ .get = (getter)edge_event_line_seqno,
+ .doc = edge_event_line_seqno_doc,
+ },
+ { }
+};
+
+static PyObject *edge_event_str(PyObject *self)
+{
+ PyObject *type, *ts, *offset, *gseqno, *lseqno, *str = NULL;
+
+ type = PyObject_GetAttrString(self, "type");
+ ts = PyObject_GetAttrString(self, "timestamp_ns");
+ offset = PyObject_GetAttrString(self, "line_offset");
+ gseqno = PyObject_GetAttrString(self, "global_seqno");
+ lseqno = PyObject_GetAttrString(self, "line_seqno");
+ if (!type || !ts || !offset || !gseqno || !lseqno)
+ goto out;
+
+ str = PyUnicode_FromFormat(
+ "<gpiod.EdgeEvent type=%S timestamp_ns=%S line_offset=%S global_seqno=%S line_seqno=%S>",
+ type, ts, offset, gseqno, lseqno);
+
+out:
+ Py_XDECREF(type);
+ Py_XDECREF(ts);
+ Py_XDECREF(offset);
+ Py_XDECREF(gseqno);
+ Py_XDECREF(lseqno);
+ return str;
+}
+
+PyDoc_STRVAR(edge_event_type_doc,
+"Immutable object containing data about a single line edge event.");
+
+static PyTypeObject edge_event_type = {
+ PyVarObject_HEAD_INIT(NULL, 0)
+ .tp_name = "gpiod.EdgeEvent",
+ .tp_basicsize = sizeof(edge_event_object),
+ .tp_flags = Py_TPFLAGS_DEFAULT,
+ .tp_doc = edge_event_type_doc,
+ .tp_new = PyType_GenericNew,
+ .tp_init = (initproc)edge_event_init,
+ .tp_finalize = (destructor)edge_event_finalize,
+ .tp_dealloc = (destructor)Py_gpiod_dealloc,
+ .tp_getset = edge_event_getset,
+ .tp_str = (reprfunc)edge_event_str
+};
+
+int Py_gpiod_RegisterEdgeEventType(PyObject *module)
+{
+ int ret;
+
+ ret = PyModule_AddType(module, &edge_event_type);
+ if (ret)
+ return ret;
+
+ return PyCEnum_AddEnumsToType(edge_event_enums, &edge_event_type);
+}
+
+PyObject *Py_gpiod_MakeEdgeEvent(struct gpiod_edge_event *event)
+{
+ edge_event_object *event_obj;
+
+ event_obj = PyObject_New(edge_event_object, &edge_event_type);
+ if (!event_obj)
+ return NULL;
+
+ event_obj->event = event;
+
+ return (PyObject *)event_obj;
+}
diff --git a/bindings/python/exception.c b/bindings/python/exception.c
new file mode 100644
index 0000000..30e2dd0
--- /dev/null
+++ b/bindings/python/exception.c
@@ -0,0 +1,182 @@
+// SPDX-License-Identifier: LGPL-2.1-or-later
+// SPDX-FileCopyrightText: 2022 Bartosz Golaszewski <brgl@bgdev.pl>
+
+#include <stdarg.h>
+#include <stdio.h>
+
+#include "module.h"
+
+struct exception_desc {
+ char *name;
+ char *base;
+ char *doc;
+};
+
+static const struct exception_desc exceptions[] = {
+ {
+ .name = "ChipClosedError",
+ .base = "Exception",
+ .doc = "Error raised when an already closed chip is used.",
+ },
+ {
+ .name = "RequestReleasedError",
+ .base = "Exception",
+ .doc = "Error raised when a released request is used.",
+ },
+ {
+ .name = "BadMappingError",
+ .base = "Exception",
+ .doc = "Exception thrown when the core C library returns an invalid value for any of the line properties.",
+ },
+ { }
+};
+
+PyObject *_Py_gpiod_SetErrFromErrno(const char *filename)
+{
+ PyObject *exc;
+
+ if (errno == ENOMEM)
+ return PyErr_NoMemory();
+
+ switch (errno) {
+ case EINVAL:
+ exc = PyExc_ValueError;
+ break;
+ case EOPNOTSUPP:
+ exc = PyExc_NotImplementedError;
+ break;
+ case EPIPE:
+ exc = PyExc_BrokenPipeError;
+ break;
+ case ECHILD:
+ exc = PyExc_ChildProcessError;
+ break;
+ case EINTR:
+ exc = PyExc_InterruptedError;
+ break;
+ case EEXIST:
+ exc = PyExc_FileExistsError;
+ break;
+ case ENOENT:
+ exc = PyExc_FileNotFoundError;
+ break;
+ case EISDIR:
+ exc = PyExc_IsADirectoryError;
+ break;
+ case ENOTDIR:
+ exc = PyExc_NotADirectoryError;
+ break;
+ case EPERM:
+ exc = PyExc_PermissionError;
+ break;
+ case ETIMEDOUT:
+ exc = PyExc_TimeoutError;
+ break;
+ default:
+ exc = PyExc_OSError;
+ break;
+ }
+
+ return PyErr_SetFromErrnoWithFilename(exc, filename);
+}
+
+static int add_exception_type(PyObject *module, PyObject *globals,
+ PyObject *locals,
+ const struct exception_desc *exc)
+{
+ static const char *const fmt =
+ "class %s(%s):\n"
+ " \"\"\"%s\"\"\"\n"
+ " pass";
+
+ PyObject *code, *res, *type;
+ char *src;
+ int ret;
+
+ ret = asprintf(&src, fmt, exc->name, exc->base, exc->doc);
+ if (ret < 0) {
+ Py_gpiod_SetErrFromErrno();
+ return -1;
+ }
+
+ code = Py_CompileString(src, __FILE__, Py_single_input);
+ free(src);
+ if (!code)
+ return -1;
+
+ res = PyEval_EvalCode(code, globals, locals);
+ Py_DECREF(code);
+ if (!res)
+ return -1;
+
+ Py_DECREF(res);
+
+ type = PyDict_GetItemString(locals, exc->name);
+ if (!type)
+ return -1;
+
+ return PyModule_AddType(module, (PyTypeObject *)type);
+}
+
+int Py_gpiod_RegisterExceptionTypes(PyObject *module)
+{
+ const struct exception_desc *exc;
+ PyObject *globals, *locals;
+ int ret;
+
+ globals = PyModule_GetDict(module);
+ if (!globals)
+ return -1;
+
+ locals = PyDict_New();
+ if (!locals)
+ return -1;
+
+ for (exc = exceptions; exc->name; exc++) {
+ ret = add_exception_type(module, globals, locals, exc);
+ if (ret) {
+ Py_DECREF(locals);
+ return -1;
+ }
+ }
+
+ Py_DECREF(locals);
+ return 0;
+}
+
+static void set_error(const char *name, const char *fmt, ...)
+{
+ PyObject *mod, *dict, *type;
+ va_list va;
+
+ mod = Py_gpiod_GetModule();
+ if (!mod)
+ return;
+
+ dict = PyModule_GetDict(mod);
+ if (!dict)
+ return;
+
+ type = PyDict_GetItemString(dict, name);
+ if (!type)
+ return;
+
+ va_start(va, fmt);
+ PyErr_FormatV(type, fmt, va);
+ va_end(va);
+}
+
+void Py_gpiod_SetChipClosedError(void)
+{
+ set_error("ChipClosedError", "I/O operation on closed chip");
+}
+
+void Py_gpiod_SetRequestReleasedError(void)
+{
+ set_error("RequestReleasedError", "GPIO lines have been released");
+}
+
+void Py_gpiod_SetBadMappingError(const char *name)
+{
+ set_error("BadMappingError", "Bad mapping for %s", name);
+}
diff --git a/bindings/python/info-event.c b/bindings/python/info-event.c
new file mode 100644
index 0000000..9e9bcb6
--- /dev/null
+++ b/bindings/python/info-event.c
@@ -0,0 +1,175 @@
+// SPDX-License-Identifier: LGPL-2.1-or-later
+// SPDX-FileCopyrightText: 2022 Bartosz Golaszewski <brgl@bgdev.pl>
+
+#include "enum/enum.h"
+#include "module.h"
+
+typedef struct {
+ PyObject_HEAD;
+ struct gpiod_info_event *event;
+ PyObject *info;
+} info_event_object;
+
+static const PyCEnum_EnumVal event_type_vals[] = {
+ {
+ .name = "LINE_REQUESTED",
+ .value = GPIOD_INFO_EVENT_LINE_REQUESTED,
+ },
+ {
+ .name = "LINE_RELEASED",
+ .value = GPIOD_INFO_EVENT_LINE_RELEASED,
+ },
+ {
+ .name = "LINE_CONFIG_CHANGED",
+ .value = GPIOD_INFO_EVENT_LINE_CONFIG_CHANGED,
+ },
+ { }
+};
+
+static const PyCEnum_EnumDef info_event_enums[] = {
+ {
+ .name = "Type",
+ .values = event_type_vals,
+ },
+ { }
+};
+
+static int info_event_init(PyObject *Py_UNUSED(self),
+ PyObject *Py_UNUSED(ignored0),
+ PyObject *Py_UNUSED(ignored1))
+{
+ PyErr_SetString(PyExc_TypeError,
+ "cannot create 'gpiod.InfoEvent' instances");
+ return -1;
+}
+
+static void info_event_finalize(info_event_object *self)
+{
+ Py_XDECREF(self->info);
+
+ if (self->event)
+ gpiod_info_event_free(self->event);
+}
+
+PyDoc_STRVAR(info_event_get_type_doc, "Type of the event.");
+
+static PyObject *info_event_get_type(info_event_object *self,
+ void *Py_UNUSED(ignored))
+{
+ int type = gpiod_info_event_get_event_type(self->event);
+
+ return PyCEnum_MapCToPy((PyObject *)self, "Type", type);
+}
+
+PyDoc_STRVAR(info_event_timestamp_ns_doc, "Time of the event in nanoseconds.");
+
+static PyObject *info_event_timestamp_ns(info_event_object *self,
+ void *Py_UNUSED(ignored))
+{
+ return PyLong_FromUnsignedLongLong(
+ gpiod_info_event_get_timestamp_ns(self->event));
+}
+
+PyDoc_STRVAR(info_event_line_info_doc, "New line information.");
+
+static PyObject *info_event_line_info(info_event_object *self,
+ void *Py_UNUSED(ignored))
+{
+ struct gpiod_line_info *info, *cpy;
+
+ if (!self->info) {
+ info = gpiod_info_event_get_line_info(self->event);
+ cpy = gpiod_line_info_copy(info);
+ if (!cpy)
+ return NULL;
+
+ self->info = Py_gpiod_MakeLineInfo(cpy);
+ if (!self->info)
+ return NULL;
+ }
+
+ Py_INCREF(self->info);
+ return self->info;
+}
+
+static PyGetSetDef info_event_getset[] = {
+ {
+ .name = "type",
+ .get = (getter)info_event_get_type,
+ .doc = info_event_get_type_doc,
+ },
+ {
+ .name = "timestamp_ns",
+ .get = (getter)info_event_timestamp_ns,
+ .doc = info_event_timestamp_ns_doc,
+ },
+ {
+ .name = "line_info",
+ .get = (getter)info_event_line_info,
+ .doc = info_event_line_info_doc,
+ },
+ { }
+};
+
+static PyObject *info_event_str(PyObject *self)
+{
+ PyObject *type, *ts, *info, *str = NULL;
+
+ type = PyObject_GetAttrString(self, "type");
+ ts = PyObject_GetAttrString(self, "timestamp_ns");
+ info = PyObject_GetAttrString(self, "line_info");
+ if (!type || !ts || !info)
+ goto out;
+
+ str = PyUnicode_FromFormat(
+ "<gpiod.InfoEvent type=%S timestamp_ns=%S line_info=%S>",
+ type, ts, info);
+
+out:
+ Py_XDECREF(type);
+ Py_XDECREF(ts);
+ Py_XDECREF(info);
+ return str;
+}
+
+PyDoc_STRVAR(info_event_type_doc,
+"Immutable object containing data about a single line info event.");
+
+static PyTypeObject info_event_type = {
+ PyVarObject_HEAD_INIT(NULL, 0)
+ .tp_name = "gpiod.InfoEvent",
+ .tp_basicsize = sizeof(info_event_object),
+ .tp_flags = Py_TPFLAGS_DEFAULT,
+ .tp_doc = info_event_type_doc,
+ .tp_new = PyType_GenericNew,
+ .tp_init = (initproc)info_event_init,
+ .tp_finalize = (destructor)info_event_finalize,
+ .tp_dealloc = (destructor)Py_gpiod_dealloc,
+ .tp_getset = info_event_getset,
+ .tp_str = (reprfunc)info_event_str
+};
+
+int Py_gpiod_RegisterInfoEventType(PyObject *module)
+{
+ int ret;
+
+ ret = PyModule_AddType(module, &info_event_type);
+ if (ret)
+ return ret;
+
+ return PyCEnum_AddEnumsToType(info_event_enums, &info_event_type);
+}
+
+PyObject *Py_gpiod_MakeInfoEvent(struct gpiod_info_event *event)
+{
+ info_event_object *event_obj;
+
+ event_obj = PyObject_New(info_event_object, &info_event_type);
+ if (!event_obj)
+ return NULL;
+
+ event_obj->event = event;
+ event_obj->info = NULL;
+
+ return (PyObject *)event_obj;
+}
diff --git a/bindings/python/line-config.c b/bindings/python/line-config.c
new file mode 100644
index 0000000..35d0589
--- /dev/null
+++ b/bindings/python/line-config.c
@@ -0,0 +1,1338 @@
+// SPDX-License-Identifier: LGPL-2.1-or-later
+// SPDX-FileCopyrightText: 2022 Bartosz Golaszewski <brgl@bgdev.pl>
+
+#include <limits.h>
+
+#include "enum/enum.h"
+#include "module.h"
+
+typedef struct {
+ PyObject_HEAD;
+ struct gpiod_line_config *cfg;
+} line_config_object;
+
+struct properties {
+ PyObject *direction;
+ PyObject *edge;
+ PyObject *bias;
+ PyObject *drive;
+ PyObject *active_low;
+ PyObject *debounce_period;
+ PyObject *event_clock;
+ PyObject *output_value;
+ PyObject *output_values;
+};
+
+enum property {
+ PROP_DIRECTION = 1,
+ PROP_EDGE_DETECTION,
+ PROP_BIAS,
+ PROP_DRIVE,
+ PROP_ACTIVE_LOW,
+ PROP_DEBOUNCE_PERIOD,
+ PROP_EVENT_CLOCK,
+ PROP_OUTPUT_VALUE,
+ PROP_OUTPUT_VALUES
+};
+
+static const PyCEnum_EnumVal property_vals[] = {
+ {
+ .name = "DIRECTION",
+ .value = PROP_DIRECTION,
+ },
+ {
+ .name = "EDGE_DETECTION",
+ .value = PROP_EDGE_DETECTION,
+ },
+ {
+ .name = "BIAS",
+ .value = PROP_BIAS,
+ },
+ {
+ .name = "DRIVE",
+ .value = PROP_DRIVE,
+ },
+ {
+ .name = "ACTIVE_LOW",
+ .value = PROP_ACTIVE_LOW,
+ },
+ {
+ .name = "DEBOUNCE_PERIOD",
+ .value = PROP_DEBOUNCE_PERIOD,
+ },
+ {
+ .name = "EVENT_CLOCK",
+ .value = PROP_EVENT_CLOCK,
+ },
+ {
+ .name = "OUTPUT_VALUE",
+ .value = PROP_OUTPUT_VALUE,
+ },
+ {
+ .name = "OUTPUT_VALUES",
+ .value = PROP_OUTPUT_VALUES,
+ },
+ { }
+};
+
+static const PyCEnum_EnumDef line_config_enums[] = {
+ {
+ .name = "Property",
+ .values = property_vals,
+ },
+ { }
+};
+
+static int set_default_enum(struct gpiod_line_config *cfg,
+ void (*set_func)(struct gpiod_line_config *, int),
+ int prop, PyObject *valobj)
+{
+ int val;
+
+ if (!valobj)
+ return 0;
+
+ val = Py_gpiod_MapLinePropPyToC(prop, valobj);
+ if (val < 0)
+ return -1;
+
+ set_func(cfg, val);
+
+ return 0;
+}
+
+static int set_defaults(struct gpiod_line_config *cfg, struct properties *props)
+{
+ unsigned long debounce_period;
+ bool active_low;
+ int ret;
+
+ ret = set_default_enum(cfg, gpiod_line_config_set_direction_default,
+ PY_GPIOD_LINE_DIRECTION, props->direction);
+ if (ret)
+ return ret;
+
+ ret = set_default_enum(cfg,
+ gpiod_line_config_set_edge_detection_default,
+ PY_GPIOD_LINE_EDGE, props->edge);
+ if (ret)
+ return ret;
+
+ ret = set_default_enum(cfg, gpiod_line_config_set_bias_default,
+ PY_GPIOD_LINE_BIAS, props->bias);
+ if (ret)
+ return ret;
+
+ ret = set_default_enum(cfg, gpiod_line_config_set_drive_default,
+ PY_GPIOD_LINE_DRIVE, props->drive);
+ if (ret)
+ return ret;
+
+ if (props->active_low) {
+ if (props->active_low == Py_True) {
+ active_low = true;
+ } else if (props->active_low == Py_False) {
+ active_low = false;
+ } else {
+ PyErr_SetString(PyExc_TypeError,
+ "active_low must be a boolean value");
+ return -1;
+ }
+
+ gpiod_line_config_set_active_low_default(cfg, active_low);
+ }
+
+ if (props->debounce_period) {
+ debounce_period = Py_gpiod_TimedeltaToMicroseconds(
+ props->debounce_period);
+ if (PyErr_Occurred())
+ return -1;
+
+ gpiod_line_config_set_debounce_period_us_default(cfg,
+ debounce_period);
+ }
+
+ ret = set_default_enum(cfg, gpiod_line_config_set_event_clock_default,
+ PY_GPIOD_LINE_CLOCK, props->event_clock);
+ if (ret)
+ return ret;
+
+ ret = set_default_enum(cfg, gpiod_line_config_set_output_value_default,
+ PY_GPIOD_LINE_VALUE, props->output_value);
+ if (ret)
+ return ret;
+
+ return 0;
+}
+
+static int line_config_init(line_config_object *self,
+ PyObject *args, PyObject *kwargs)
+{
+ static char *kwlist[] = {
+ "direction",
+ "edge_detection",
+ "bias",
+ "drive",
+ "active_low",
+ "debounce_period",
+ "event_clock",
+ "output_value",
+ "output_values",
+ NULL
+ };
+
+ struct properties props;
+ PyObject *retobj;
+ int ret;
+
+ self->cfg = gpiod_line_config_new();
+ if (!self->cfg) {
+ Py_gpiod_SetErrFromErrno();
+ return -1;
+ }
+
+ memset(&props, 0, sizeof(props));
+
+ ret = PyArg_ParseTupleAndKeywords(args, kwargs, "|$OOOOOOOOO", kwlist,
+ &props.direction,
+ &props.edge,
+ &props.bias,
+ &props.drive,
+ &props.active_low,
+ &props.debounce_period,
+ &props.event_clock,
+ &props.output_value,
+ &props.output_values);
+ if (!ret)
+ return -1;
+
+ if (props.output_values) {
+ retobj = PyObject_CallMethod((PyObject *)self,
+ "set_output_values",
+ "O", props.output_values);
+ if (!retobj)
+ return -1;
+
+ Py_DECREF(retobj);
+ }
+
+ return set_defaults(self->cfg, &props);
+}
+
+static void line_config_finalize(line_config_object *self)
+{
+ if (self->cfg)
+ gpiod_line_config_free(self->cfg);
+}
+
+PyDoc_STRVAR(line_config_num_overrides_doc,
+"Number of configuration overrides.");
+
+static PyObject *
+line_config_num_overrides(line_config_object *self, void *Py_UNUSED(ignored))
+{
+ return PyLong_FromSize_t(
+ gpiod_line_config_get_num_overrides(self->cfg));
+}
+
+PyDoc_STRVAR(line_config_overrides_doc,
+"Dictionary of property overrides with keys representing the overridden\n"
+"offsets and values representing the properties.");
+
+static PyObject *
+line_config_overrides(line_config_object *self, void *Py_UNUSED(ignored))
+{
+ PyObject *overrides, *key, *val;
+ size_t num_overrides, i;
+ unsigned int *offsets;
+ int *props, ret;
+
+ num_overrides = gpiod_line_config_get_num_overrides(self->cfg);
+
+ offsets = PyMem_Calloc(num_overrides, sizeof(unsigned int));
+ if (!offsets) {
+ PyErr_NoMemory();
+ return NULL;
+ }
+
+ props = PyMem_Calloc(num_overrides, sizeof(int));
+ if (!props) {
+ PyErr_NoMemory();
+ PyMem_Free(offsets);
+ return NULL;
+ }
+
+ gpiod_line_config_get_overrides(self->cfg, offsets, props);
+
+ overrides = PyDict_New();
+ if (!overrides) {
+ PyMem_Free(offsets);
+ PyMem_Free(props);
+ return NULL;
+ }
+
+ for (i = 0; i < num_overrides; i++) {
+ key = PyLong_FromUnsignedLong(offsets[i]);
+ if (PyErr_Occurred()) {
+ Py_DECREF(overrides);
+ PyMem_Free(offsets);
+ PyMem_Free(props);
+ return NULL;
+ }
+
+ val = PyCEnum_MapCToPy((PyObject *)self, "Property", props[i]);
+ if (!val) {
+ Py_DECREF(key);
+ Py_DECREF(overrides);
+ PyMem_Free(offsets);
+ PyMem_Free(props);
+ return NULL;
+ }
+
+ ret = PyDict_SetItem(overrides, key, val);
+ Py_DECREF(key);
+ Py_DECREF(val);
+ if (ret) {
+ Py_DECREF(overrides);
+ PyMem_Free(offsets);
+ PyMem_Free(props);
+ return NULL;
+ }
+ }
+
+ PyMem_Free(offsets);
+ PyMem_Free(props);
+
+ return overrides;
+}
+
+static PyGetSetDef line_config_getset[] = {
+ {
+ .name = "num_overrides",
+ .get = (getter)line_config_num_overrides,
+ .doc = line_config_num_overrides_doc,
+ },
+ {
+ .name = "overrides",
+ .get = (getter)line_config_overrides,
+ .doc = line_config_overrides_doc,
+ },
+ { }
+};
+
+PyDoc_STRVAR(line_config_reset_doc,
+"reset() -> None\n"
+"\n"
+"Reset the line config object.");
+
+static PyObject *
+line_config_reset(line_config_object *self, PyObject *Py_UNUSED(ignored))
+{
+ gpiod_line_config_reset(self->cfg);
+
+ Py_RETURN_NONE;
+}
+
+PyDoc_STRVAR(line_config_set_props_default_doc,
+"set_props_default(**kwargs) -> None\n"
+"\n"
+"Set the defaults for properties.\n"
+"\n"
+" direction\n"
+" default direction\n"
+" edge\n"
+" default edge detection\n"
+" bias\n"
+" default bias\n"
+" drive\n"
+" default drive\n"
+" active_low\n"
+" default active-low setting\n"
+" debounce_period\n"
+" default debounce period\n"
+" event_clock\n"
+" default event clock\n"
+" output_value\n"
+" default output value");
+
+static PyObject *
+line_config_set_props_default(line_config_object *self,
+ PyObject *args, PyObject *kwargs)
+{
+ static char *kwlist[] = {
+ "direction",
+ "edge_detection",
+ "bias",
+ "drive",
+ "active_low",
+ "debounce_period",
+ "event_clock",
+ "output_value",
+ NULL
+ };
+
+ struct properties props;
+ int ret;
+
+ memset(&props, 0, sizeof(props));
+
+ ret = PyArg_ParseTupleAndKeywords(args, kwargs, "|$OOOOOOOO", kwlist,
+ &props.direction,
+ &props.edge,
+ &props.bias,
+ &props.drive,
+ &props.active_low,
+ &props.debounce_period,
+ &props.event_clock,
+ &props.output_value);
+ if (!ret)
+ return NULL;
+
+ ret = set_defaults(self->cfg, &props);
+ if (ret)
+ return NULL;
+
+ Py_RETURN_NONE;
+}
+
+static int set_override_enum(struct gpiod_line_config *cfg,
+ void (*set_func)(struct gpiod_line_config *,
+ int, unsigned int),
+ unsigned int offset, int prop, PyObject *valobj)
+{
+ int val;
+
+ if (!valobj)
+ return 0;
+
+ val = Py_gpiod_MapLinePropPyToC(prop, valobj);
+ if (val < 0)
+ return -1;
+
+ set_func(cfg, val, offset);
+
+ return 0;
+}
+
+PyDoc_STRVAR(line_config_set_props_override_doc,
+"set_props_override(offset, **kwargs) -> None\n"
+"\n"
+"Set property overrides for line.\n"
+"\n"
+" offset\n"
+" offset of the line for which to set the overrides\n"
+" direction\n"
+" default direction\n"
+" edge\n"
+" default edge detection\n"
+" bias\n"
+" default bias\n"
+" drive\n"
+" default drive\n"
+" active_low\n"
+" default active-low setting\n"
+" debounce_period\n"
+" default debounce period\n"
+" event_clock\n"
+" default event clock\n"
+" output_value\n"
+" default output value");
+
+static PyObject *
+line_config_set_props_override(line_config_object *self,
+ PyObject *args, PyObject *kwargs)
+{
+ static char *kwlist[] = {
+ "",
+ "direction",
+ "edge_detection",
+ "bias",
+ "drive",
+ "active_low",
+ "debounce_period",
+ "event_clock",
+ "output_value",
+ NULL
+ };
+
+ struct gpiod_line_config *cfg = self->cfg;
+ unsigned long debounce_period;
+ struct properties props;
+ unsigned int offset;
+ bool active_low;
+ int ret;
+
+ memset(&props, 0, sizeof(props));
+
+ ret = PyArg_ParseTupleAndKeywords(args, kwargs, "I|$OOOOOOOO", kwlist,
+ &offset,
+ &props.direction,
+ &props.edge,
+ &props.bias,
+ &props.drive,
+ &props.active_low,
+ &props.debounce_period,
+ &props.event_clock,
+ &props.output_value);
+ if (!ret)
+ return NULL;
+
+ ret = set_override_enum(cfg, gpiod_line_config_set_direction_override,
+ offset, PY_GPIOD_LINE_DIRECTION,
+ props.direction);
+ if (ret)
+ return NULL;
+
+ ret = set_override_enum(cfg,
+ gpiod_line_config_set_edge_detection_override,
+ offset, PY_GPIOD_LINE_EDGE, props.edge);
+ if (ret)
+ return NULL;
+
+ ret = set_override_enum(cfg, gpiod_line_config_set_bias_override,
+ offset, PY_GPIOD_LINE_BIAS, props.bias);
+ if (ret)
+ return NULL;
+
+ ret = set_override_enum(cfg, gpiod_line_config_set_drive_override,
+ offset, PY_GPIOD_LINE_DRIVE, props.drive);
+ if (ret)
+ return NULL;
+
+ if (props.active_low) {
+ if (props.active_low == Py_True) {
+ active_low = true;
+ } else if (props.active_low == Py_False) {
+ active_low = false;
+ } else {
+ PyErr_SetString(PyExc_TypeError,
+ "active_low must be a boolean value");
+ return NULL;
+ }
+
+ gpiod_line_config_set_active_low_override(cfg, active_low,
+ offset);
+ }
+
+ if (props.debounce_period) {
+ debounce_period = Py_gpiod_TimedeltaToMicroseconds(
+ props.debounce_period);
+ if (PyErr_Occurred())
+ return NULL;
+
+ gpiod_line_config_set_debounce_period_us_override(cfg,
+ debounce_period,
+ offset);
+ }
+
+ ret = set_override_enum(cfg, gpiod_line_config_set_event_clock_override,
+ offset, PY_GPIOD_LINE_CLOCK, props.event_clock);
+ if (ret)
+ return NULL;
+
+ ret = set_override_enum(cfg,
+ gpiod_line_config_set_output_value_override,
+ offset, PY_GPIOD_LINE_VALUE,
+ props.output_value);
+ if (ret)
+ return NULL;
+
+ Py_RETURN_NONE;
+}
+
+PyDoc_STRVAR(line_config_get_props_default_doc,
+"get_prop_default(**kwargs) -> (val0, val1, ...)\n"
+"\n"
+"Get default values for a set of line properties.\n"
+"\n"
+"Takes a variable number of property types as defined by the\n"
+"gpiod.LineConfig.Property enum.");
+
+static PyObject *
+line_config_get_props_default(line_config_object *self, PyObject *args)
+{
+ PyObject *iter, *props, *next, *item;
+ struct gpiod_line_config *cfg;
+ unsigned long debounce_period;
+ Py_ssize_t num_props, i;
+ int prop, val, ret;
+ bool active_low;
+
+ num_props = PyTuple_GET_SIZE(args);
+ if (num_props < 0)
+ return NULL;
+
+ if (num_props < 1)
+ Py_RETURN_NONE;
+
+ props = PyTuple_New(num_props);
+ if (!props)
+ return NULL;
+
+ iter = PyObject_GetIter(args);
+ if (!iter) {
+ Py_DECREF(props);
+ return NULL;
+ }
+
+ cfg = self->cfg;
+
+ for (i = 0;; i++) {
+ next = PyIter_Next(iter);
+ if (!next)
+ break;
+
+ prop = PyCEnum_MapPyToC((PyObject *)self, "Property", next);
+ Py_DECREF(next);
+ if (prop < 0) {
+ Py_DECREF(props);
+ return NULL;
+ }
+
+ switch (prop) {
+ case PROP_DIRECTION:
+ val = gpiod_line_config_get_direction_default(cfg);
+ item = Py_gpiod_MapLinePropCToPy(
+ PY_GPIOD_LINE_DIRECTION, val);
+ break;
+ case PROP_EDGE_DETECTION:
+ val = gpiod_line_config_get_edge_detection_default(cfg);
+ item = Py_gpiod_MapLinePropCToPy(PY_GPIOD_LINE_EDGE,
+ val);
+ break;
+ case PROP_BIAS:
+ val = gpiod_line_config_get_bias_default(cfg);
+ item = Py_gpiod_MapLinePropCToPy(PY_GPIOD_LINE_BIAS,
+ val);
+ break;
+ case PROP_DRIVE:
+ val = gpiod_line_config_get_drive_default(cfg);
+ item = Py_gpiod_MapLinePropCToPy(PY_GPIOD_LINE_DRIVE,
+ val);
+ break;
+ case PROP_ACTIVE_LOW:
+ active_low =
+ gpiod_line_config_get_active_low_default(cfg);
+ item = active_low ? Py_True : Py_False;
+ Py_INCREF(item);
+ break;
+ case PROP_DEBOUNCE_PERIOD:
+ debounce_period =
+ gpiod_line_config_get_debounce_period_us_default(cfg);
+ item = Py_gpiod_MicrosecondsToTimedelta(
+ debounce_period);
+ break;
+ case PROP_EVENT_CLOCK:
+ val = gpiod_line_config_get_event_clock_default(cfg);
+ item = Py_gpiod_MapLinePropCToPy(PY_GPIOD_LINE_CLOCK,
+ val);
+ break;
+ case PROP_OUTPUT_VALUE:
+ val = gpiod_line_config_get_output_value_default(cfg);
+ item = Py_gpiod_MapLinePropCToPy(PY_GPIOD_LINE_VALUE,
+ val);
+ break;
+ default:
+ Py_DECREF(props);
+ PyErr_SetString(PyExc_ValueError,
+ "unsupported property type");
+ return NULL;
+ }
+
+ if (!item) {
+ Py_DECREF(props);
+ return NULL;
+ }
+
+ ret = PyTuple_SetItem(props, i, item);
+ if (ret < 0) {
+ Py_DECREF(props);
+ return NULL;
+ }
+ }
+
+ if (num_props == 1) {
+ item = PyTuple_GetItem(props, 0);
+ Py_INCREF(item);
+ Py_DECREF(props);
+ return item;
+ }
+
+ return props;
+}
+
+PyDoc_STRVAR(line_config_get_props_offset_doc,
+"get_prop_offset(offset, **kwargs) -> (val0, val1, ...)\n"
+"\n"
+"Get actual values for a set of line properties for a line.\n"
+"\n"
+"Takes a variable number of property types as defined by the\n"
+"gpiod.LineConfig.Property enum.\n"
+"\n"
+" offset\n"
+" The offset of the line for which to read the properties");
+
+static PyObject *
+line_config_get_props_offset(line_config_object *self, PyObject *args)
+{
+ unsigned long tmp, debounce_period;
+ PyObject *props, *item, *next;
+ struct gpiod_line_config *cfg;
+ Py_ssize_t num_args, i;
+ unsigned int offset;
+ int ret, prop, val;
+ bool active_low;
+
+ num_args = PyTuple_GET_SIZE(args);
+ if (num_args < 0)
+ return NULL;
+
+ if (num_args < 1) {
+ PyErr_SetString(PyExc_TypeError, "line offset must be given");
+ return NULL;
+ }
+
+ item = PyTuple_GetItem(args, 0);
+ if (!item)
+ return NULL;
+
+ tmp = PyLong_AsUnsignedLong(item);
+ if (PyErr_Occurred())
+ return NULL;
+
+ if (tmp > UINT_MAX) {
+ PyErr_SetString(PyExc_ValueError, "max offset value exceeded");
+ return NULL;
+ }
+
+ offset = tmp;
+
+ props = PyTuple_New(num_args - 1);
+ if (!props)
+ return NULL;
+
+ cfg = self->cfg;
+
+ for (i = 1; i < num_args; i++) {
+ next = PyTuple_GetItem(args, i);
+ if (!next) {
+ Py_DECREF(props);
+ return NULL;
+ }
+
+ prop = PyCEnum_MapPyToC((PyObject *)self, "Property", next);
+ if (prop < 0) {
+ Py_DECREF(props);
+ return NULL;
+ }
+
+ switch (prop) {
+ case PROP_DIRECTION:
+ val = gpiod_line_config_get_direction_offset(cfg,
+ offset);
+ item = Py_gpiod_MapLinePropCToPy(
+ PY_GPIOD_LINE_DIRECTION, val);
+ break;
+ case PROP_EDGE_DETECTION:
+ val = gpiod_line_config_get_edge_detection_offset(cfg,
+ offset);
+ item = Py_gpiod_MapLinePropCToPy(PY_GPIOD_LINE_EDGE,
+ val);
+ break;
+ case PROP_BIAS:
+ val = gpiod_line_config_get_bias_offset(cfg, offset);
+ item = Py_gpiod_MapLinePropCToPy(PY_GPIOD_LINE_BIAS,
+ val);
+ break;
+ case PROP_DRIVE:
+ val = gpiod_line_config_get_drive_offset(cfg, offset);
+ item = Py_gpiod_MapLinePropCToPy(PY_GPIOD_LINE_DRIVE,
+ val);
+ break;
+ case PROP_ACTIVE_LOW:
+ active_low =
+ gpiod_line_config_get_active_low_offset(cfg,
+ offset);
+ item = active_low ? Py_True : Py_False;
+ Py_INCREF(item);
+ break;
+ case PROP_DEBOUNCE_PERIOD:
+ debounce_period =
+ gpiod_line_config_get_debounce_period_us_offset(cfg,
+ offset);
+ item = Py_gpiod_MicrosecondsToTimedelta(
+ debounce_period);
+ break;
+ case PROP_EVENT_CLOCK:
+ val = gpiod_line_config_get_event_clock_offset(cfg,
+ offset);
+ item = Py_gpiod_MapLinePropCToPy(PY_GPIOD_LINE_CLOCK,
+ val);
+ break;
+ case PROP_OUTPUT_VALUE:
+ val = gpiod_line_config_get_output_value_offset(cfg,
+ offset);
+ item = Py_gpiod_MapLinePropCToPy(PY_GPIOD_LINE_VALUE,
+ val);
+ break;
+ default:
+ Py_DECREF(props);
+ PyErr_SetString(PyExc_ValueError,
+ "unsupported property type");
+ return NULL;
+ }
+
+ if (!item) {
+ Py_DECREF(props);
+ return NULL;
+ }
+
+ ret = PyTuple_SetItem(props, i - 1, item);
+ if (ret < 0) {
+ Py_DECREF(props);
+ return NULL;
+ }
+ }
+
+ if (num_args == 2) {
+ item = PyTuple_GetItem(props, 0);
+ Py_INCREF(item);
+ Py_DECREF(props);
+ return item;
+ }
+
+ return props;
+}
+
+PyDoc_STRVAR(line_config_prop_is_overridden_doc,
+"prop_is_overridden(offset, prop) -> bool\n"
+"\n"
+"Check if the property is overridden for a line.\n"
+"\n"
+" offset\n"
+" Offset of the line for which to check the property\n"
+" prop\n"
+" Which property to check");
+
+static PyObject *
+line_config_prop_is_overridden(line_config_object *self, PyObject *args)
+{
+ struct gpiod_line_config *cfg = self->cfg;
+ unsigned int offset;
+ PyObject *prop_obj;
+ int ret, prop;
+ bool val;
+
+ ret = PyArg_ParseTuple(args, "IO", &offset, &prop_obj);
+ if (!ret)
+ return NULL;
+
+ prop = PyCEnum_MapPyToC((PyObject *)self, "Property", prop_obj);
+ if (prop < 0)
+ return NULL;
+
+ switch (prop) {
+ case PROP_DIRECTION:
+ val = gpiod_line_config_direction_is_overridden(cfg, offset);
+ break;
+ case PROP_EDGE_DETECTION:
+ val = gpiod_line_config_edge_detection_is_overridden(cfg,
+ offset);
+ break;
+ case PROP_BIAS:
+ val = gpiod_line_config_bias_is_overridden(cfg, offset);
+ break;
+ case PROP_DRIVE:
+ val = gpiod_line_config_drive_is_overridden(cfg, offset);
+ break;
+ case PROP_ACTIVE_LOW:
+ val = gpiod_line_config_active_low_is_overridden(cfg, offset);
+ break;
+ case PROP_DEBOUNCE_PERIOD:
+ val = gpiod_line_config_debounce_period_us_is_overridden(cfg,
+ offset);
+ break;
+ case PROP_EVENT_CLOCK:
+ val = gpiod_line_config_event_clock_is_overridden(cfg, offset);
+ break;
+ case PROP_OUTPUT_VALUE:
+ val = gpiod_line_config_output_value_is_overridden(cfg, offset);
+ break;
+ default:
+ PyErr_SetString(PyExc_ValueError,
+ "unsupported property type");
+ return NULL;
+ }
+
+ return PyBool_FromLong(val);
+}
+
+PyDoc_STRVAR(line_config_clear_prop_override_doc,
+"clear_override(offset, prop) -> None\n"
+"\n"
+"Check if the property is overridden for a line.\n"
+"\n"
+" offset\n"
+" Offset of the line for which to check the property\n"
+" prop\n"
+" Which property to check");
+
+static PyObject *
+line_config_clear_prop_override(line_config_object *self, PyObject *args)
+{
+ struct gpiod_line_config *cfg = self->cfg;
+ unsigned int offset;
+ PyObject *prop_obj;
+ int ret, prop;
+
+ ret = PyArg_ParseTuple(args, "IO", &offset, &prop_obj);
+ if (!ret)
+ return NULL;
+
+ prop = PyCEnum_MapPyToC((PyObject *)self, "Property", prop_obj);
+ if (prop < 0)
+ return NULL;
+
+ switch (prop) {
+ case PROP_DIRECTION:
+ gpiod_line_config_clear_direction_override(cfg, offset);
+ break;
+ case PROP_EDGE_DETECTION:
+ gpiod_line_config_clear_edge_detection_override(cfg, offset);
+ break;
+ case PROP_BIAS:
+ gpiod_line_config_clear_bias_override(cfg, offset);
+ break;
+ case PROP_DRIVE:
+ gpiod_line_config_clear_drive_override(cfg, offset);
+ break;
+ case PROP_ACTIVE_LOW:
+ gpiod_line_config_clear_active_low_override(cfg, offset);
+ break;
+ case PROP_DEBOUNCE_PERIOD:
+ gpiod_line_config_clear_debounce_period_us_override(cfg,
+ offset);
+ break;
+ case PROP_EVENT_CLOCK:
+ gpiod_line_config_clear_event_clock_override(cfg, offset);
+ break;
+ case PROP_OUTPUT_VALUE:
+ gpiod_line_config_clear_output_value_override(cfg, offset);
+ break;
+ default:
+ PyErr_SetString(PyExc_ValueError,
+ "unsupported property type");
+ return NULL;
+ }
+
+ Py_RETURN_NONE;
+}
+
+PyDoc_STRVAR(line_config_set_output_values_doc,
+"set_output_values(values) -> None\n"
+"\n"
+"Override the output values for multiple lines.\n"
+"\n"
+" values\n"
+" Dictionary mapping line offsets to their values");
+
+static PyObject *
+line_config_set_output_values(line_config_object *self, PyObject *args)
+{
+ PyObject *dict, *items, *iter, *next, *key, *val;
+ unsigned int offset;
+ unsigned long tmp;
+ int ret, value;
+
+ ret = PyArg_ParseTuple(args, "O", &dict);
+ if (!ret)
+ return NULL;
+
+ if (!PyDict_Check(dict)) {
+ PyErr_SetString(PyExc_TypeError,
+ "argument must be a dictionary");
+ return NULL;
+ }
+
+ items = PyDict_Items(dict);
+ if (!items)
+ return NULL;
+
+ iter = PyObject_GetIter(items);
+ if (!iter) {
+ Py_DECREF(items);
+ return NULL;
+ }
+
+ for (;;) {
+ next = PyIter_Next(iter);
+ if (!next) {
+ Py_DECREF(iter);
+ break;
+ }
+
+ key = PyTuple_GetItem(next, 0);
+ val = PyTuple_GetItem(next, 1);
+ if (!key || !val) {
+ Py_DECREF(next);
+ Py_DECREF(iter);
+ Py_DECREF(items);
+ return NULL;
+ }
+
+ tmp = PyLong_AsUnsignedLong(key);
+ if (PyErr_Occurred()) {
+ Py_DECREF(next);
+ Py_DECREF(iter);
+ Py_DECREF(items);
+ return NULL;
+ }
+
+ if (tmp > UINT_MAX) {
+ Py_DECREF(next);
+ Py_DECREF(iter);
+ Py_DECREF(items);
+ return NULL;
+ }
+
+ offset = tmp;
+
+ value = Py_gpiod_MapLinePropPyToC(PY_GPIOD_LINE_VALUE, val);
+ if (value < 0) {
+ Py_DECREF(next);
+ Py_DECREF(iter);
+ Py_DECREF(items);
+ return NULL;
+ }
+
+ gpiod_line_config_set_output_value_override(self->cfg,
+ value, offset);
+ Py_DECREF(next);
+ }
+
+ Py_DECREF(items);
+
+ Py_RETURN_NONE;
+}
+
+static PyMethodDef line_config_methods[] = {
+ {
+ .ml_name = "reset",
+ .ml_meth = (PyCFunction)line_config_reset,
+ .ml_flags = METH_NOARGS,
+ .ml_doc = line_config_reset_doc,
+ },
+ {
+ .ml_name = "set_props_default",
+ .ml_meth = (PyCFunction)(void(*)(void))
+ line_config_set_props_default,
+ .ml_flags = METH_VARARGS | METH_KEYWORDS,
+ .ml_doc = line_config_set_props_default_doc,
+ },
+ {
+ .ml_name = "set_props_override",
+ .ml_meth = (PyCFunction)(void(*)(void))
+ line_config_set_props_override,
+ .ml_flags = METH_VARARGS | METH_KEYWORDS,
+ .ml_doc = line_config_set_props_override_doc,
+ },
+ {
+ .ml_name = "get_props_default",
+ .ml_meth = (PyCFunction)line_config_get_props_default,
+ .ml_flags = METH_VARARGS,
+ .ml_doc = line_config_get_props_default_doc,
+ },
+ {
+ .ml_name = "get_props_offset",
+ .ml_meth = (PyCFunction)line_config_get_props_offset,
+ .ml_flags = METH_VARARGS,
+ .ml_doc = line_config_get_props_offset_doc,
+ },
+ {
+ .ml_name = "prop_is_overridden",
+ .ml_meth = (PyCFunction)line_config_prop_is_overridden,
+ .ml_flags = METH_VARARGS,
+ .ml_doc = line_config_prop_is_overridden_doc,
+ },
+ {
+ .ml_name = "clear_prop_override",
+ .ml_meth = (PyCFunction)line_config_clear_prop_override,
+ .ml_flags = METH_VARARGS,
+ .ml_doc = line_config_clear_prop_override_doc,
+ },
+ {
+ .ml_name = "set_output_values",
+ .ml_meth = (PyCFunction)line_config_set_output_values,
+ .ml_flags = METH_VARARGS,
+ .ml_doc = line_config_set_output_values_doc,
+ },
+ { }
+};
+
+static PyObject *str_get_defaults(PyObject *self)
+{
+ static const int enums[] = {
+ PROP_DIRECTION,
+ PROP_EDGE_DETECTION,
+ PROP_BIAS,
+ PROP_DRIVE,
+ PROP_ACTIVE_LOW,
+ PROP_DEBOUNCE_PERIOD,
+ PROP_EVENT_CLOCK,
+ PROP_OUTPUT_VALUE,
+ PROP_OUTPUT_VALUES
+ };
+
+ PyObject *defaults = NULL, *enum_objs[8], *str = NULL;
+ int i;
+
+ memset(enum_objs, 0, sizeof(enum_objs));
+
+ for (i = 0; i < 8; i++) {
+ enum_objs[i] = PyCEnum_MapCToPy(self, "Property", enums[i]);
+ if (!enum_objs[i]) {
+ for (i = 0; i < 8; i++)
+ Py_XDECREF(enum_objs[i]);
+ return NULL;
+ }
+ }
+
+ defaults = PyObject_CallMethod(self, "get_props_default", "OOOOOOOO",
+ enum_objs[0], enum_objs[1], enum_objs[2],
+ enum_objs[3], enum_objs[4], enum_objs[5],
+ enum_objs[6], enum_objs[7]);
+ for (i = 0; i < 8; i++)
+ Py_XDECREF(enum_objs[i]);
+ if (!defaults)
+ return NULL;
+
+ str = PyUnicode_FromFormat(
+ "direction=%S edge_detection=%S bias=%S drive=%S active_low=%S debounce_period=%S event_clock=%S output_value=%S",
+ PyTuple_GetItem(defaults, 0), PyTuple_GetItem(defaults, 1),
+ PyTuple_GetItem(defaults, 2), PyTuple_GetItem(defaults, 3),
+ PyTuple_GetItem(defaults, 4), PyTuple_GetItem(defaults, 5),
+ PyTuple_GetItem(defaults, 6), PyTuple_GetItem(defaults, 7));
+ Py_DECREF(defaults);
+ return str;
+}
+
+static int
+str_fill_override_strings(PyObject *self, Py_ssize_t num_overrides,
+ const int *props, const unsigned int *offsets,
+ PyObject *list)
+{
+ Py_ssize_t i;
+ const char *propname;
+ int prop, ret;
+ unsigned int offset;
+ PyObject *str, *propobj, *val;
+
+ for (i = 0; i < num_overrides; i++) {
+ prop = props[i];
+ offset = offsets[i];
+
+ switch (prop) {
+ case GPIOD_LINE_CONFIG_PROP_DIRECTION:
+ prop = PROP_DIRECTION;
+ propname = "direction";
+ break;
+ case GPIOD_LINE_CONFIG_PROP_EDGE_DETECTION:
+ prop = PROP_EDGE_DETECTION;
+ propname = "edge_detection";
+ break;
+ case GPIOD_LINE_CONFIG_PROP_BIAS:
+ prop = PROP_BIAS;
+ propname = "bias";
+ break;
+ case GPIOD_LINE_CONFIG_PROP_DRIVE:
+ prop = PROP_DRIVE;
+ propname = "drive";
+ break;
+ case GPIOD_LINE_CONFIG_PROP_ACTIVE_LOW:
+ prop = PROP_ACTIVE_LOW;
+ propname = "active_low";
+ break;
+ case GPIOD_LINE_CONFIG_PROP_DEBOUNCE_PERIOD_US:
+ prop = PROP_DEBOUNCE_PERIOD;
+ propname = "debounce_period";
+ break;
+ case GPIOD_LINE_CONFIG_PROP_EVENT_CLOCK:
+ prop = PROP_EVENT_CLOCK;
+ propname = "event_clock";
+ break;
+ case GPIOD_LINE_CONFIG_PROP_OUTPUT_VALUE:
+ prop = PROP_OUTPUT_VALUE;
+ propname = "output_value";
+ break;
+ default:
+ Py_gpiod_SetBadMappingError("LineConfig property");
+ return -1;
+ }
+
+ propobj = PyCEnum_MapCToPy(self, "Property", prop);
+ if (!propobj)
+ return -1;
+
+ val = PyObject_CallMethod(self, "get_props_offset",
+ "IO", offset, propobj);
+ Py_DECREF(propobj);
+ if (!val)
+ return -1;
+
+ str = PyUnicode_FromFormat("%u: %s=%S", offset, propname, val);
+ Py_DECREF(val);
+ if (!str)
+ return -1;
+
+ ret = PyList_SetItem(list, i, str);
+ if (ret) {
+ Py_DECREF(str);
+ return -1;
+ }
+ }
+
+ return 0;
+}
+
+static PyObject *str_get_override_list(line_config_object *self)
+{
+ Py_ssize_t num_overrides;
+ unsigned int *offsets;
+ PyObject *overrides;
+ int *props, ret;
+
+ num_overrides = gpiod_line_config_get_num_overrides(self->cfg);
+ if (num_overrides == 0)
+ return NULL;
+
+ overrides = PyList_New(num_overrides);
+ if (!overrides)
+ return NULL;
+
+ offsets = PyMem_Calloc(num_overrides, sizeof(unsigned int));
+ if (!offsets) {
+ PyErr_NoMemory();
+ Py_DECREF(overrides);
+ return NULL;
+ }
+
+ props = PyMem_Calloc(num_overrides, sizeof(int));
+ if (!props) {
+ PyErr_NoMemory();
+ Py_DECREF(overrides);
+ PyMem_Free(offsets);
+ return NULL;
+ }
+
+ gpiod_line_config_get_overrides(self->cfg, offsets, props);
+
+ ret = str_fill_override_strings((PyObject *)self, num_overrides,
+ props, offsets, overrides);
+ PyMem_Free(props);
+ PyMem_Free(offsets);
+ if (ret) {
+ Py_DECREF(overrides);
+ return NULL;
+ }
+
+ return overrides;
+}
+
+static PyObject *str_get_overrides(line_config_object *self)
+{
+ PyObject *overrides, *joined, *str, *final;
+
+ overrides = str_get_override_list(self);
+ if (!overrides)
+ return NULL;
+
+ str = PyUnicode_FromString(", ");
+ if (!str) {
+ Py_DECREF(overrides);
+ return NULL;
+ }
+
+ joined = PyObject_CallMethod(str, "join", "O", overrides);
+ Py_DECREF(overrides);
+ Py_DECREF(str);
+
+ final = PyUnicode_FromFormat("{%S}", joined);
+ Py_DECREF(joined);
+ return final;
+}
+
+static PyObject *line_config_str(PyObject *self)
+{
+ PyObject *defaults, *overrides, *str;
+
+ defaults = str_get_defaults(self);
+ if (!defaults)
+ return NULL;
+
+ overrides = str_get_overrides((line_config_object *)self);
+ if (PyErr_Occurred()) {
+ Py_DECREF(defaults);
+ return NULL;
+ }
+
+ if (overrides)
+ str = PyUnicode_FromFormat("<gpiod.LineConfig %S overrides=%S>",
+ defaults, overrides);
+ else
+ str = PyUnicode_FromFormat("<gpiod.LineConfig %S>", defaults);
+ Py_DECREF(defaults);
+ Py_XDECREF(overrides);
+ return str;
+}
+
+PyDoc_STRVAR(line_config_type_doc,
+"Contains a set of line config options used in line requests and\n"
+"reconfiguration.");
+
+static PyTypeObject line_config_type = {
+ PyVarObject_HEAD_INIT(NULL, 0)
+ .tp_name = "gpiod.LineConfig",
+ .tp_basicsize = sizeof(line_config_object),
+ .tp_flags = Py_TPFLAGS_DEFAULT,
+ .tp_doc = line_config_type_doc,
+ .tp_new = PyType_GenericNew,
+ .tp_init = (initproc)line_config_init,
+ .tp_finalize = (destructor)line_config_finalize,
+ .tp_getset = line_config_getset,
+ .tp_methods = line_config_methods,
+ .tp_dealloc = (destructor)Py_gpiod_dealloc,
+ .tp_str = (reprfunc)line_config_str
+};
+
+int Py_gpiod_RegisterLineConfigType(PyObject *module)
+{
+ int ret;
+
+ ret = PyModule_AddType(module, &line_config_type);
+ if (ret)
+ return -1;
+
+ return PyCEnum_AddEnumsToType(line_config_enums, &line_config_type);
+}
+
+struct gpiod_line_config *Py_gpiod_LineConfigGetData(PyObject *obj)
+{
+ line_config_object *linecfg;
+ PyObject *type;
+
+ type = PyObject_Type(obj);
+ if (!type)
+ return NULL;
+
+ if ((PyTypeObject *)type != &line_config_type) {
+ PyErr_SetString(PyExc_TypeError,
+ "not a gpiod.LineConfig object");
+ Py_DECREF(type);
+ return NULL;
+ }
+ Py_DECREF(type);
+
+ linecfg = (line_config_object *)obj;
+
+ return linecfg->cfg;
+}
diff --git a/bindings/python/line-info.c b/bindings/python/line-info.c
new file mode 100644
index 0000000..47ed2da
--- /dev/null
+++ b/bindings/python/line-info.c
@@ -0,0 +1,286 @@
+// SPDX-License-Identifier: LGPL-2.1-or-later
+// SPDX-FileCopyrightText: 2022 Bartosz Golaszewski <brgl@bgdev.pl>
+
+#include "module.h"
+
+typedef struct {
+ PyObject_HEAD;
+ struct gpiod_line_info *info;
+} line_info_object;
+
+static int line_info_init(PyObject *Py_UNUSED(self),
+ PyObject *Py_UNUSED(ignored0),
+ PyObject *Py_UNUSED(ignored1))
+{
+ PyErr_SetString(PyExc_TypeError,
+ "cannot create 'gpiod.LineInfo' instances");
+ return -1;
+}
+
+static void line_info_finalize(line_info_object *self)
+{
+ if (self->info)
+ gpiod_line_info_free(self->info);
+}
+
+PyDoc_STRVAR(line_info_offset_doc,
+"Offset of the line within the parent chip.");
+
+static PyObject *line_info_offset(line_info_object *self,
+ void *Py_UNUSED(ignored))
+{
+ return PyLong_FromUnsignedLong(gpiod_line_info_get_offset(self->info));
+}
+
+PyDoc_STRVAR(line_info_name_doc,
+"Name of the line as represented in the kernel.");
+
+static PyObject *line_info_name(line_info_object *self,
+ void *Py_UNUSED(ignored))
+{
+ const char *name = gpiod_line_info_get_name(self->info);
+
+ if (!name)
+ Py_RETURN_NONE;
+
+ return PyUnicode_FromString(name);
+}
+
+PyDoc_STRVAR(line_info_used_doc,
+"True if the line is in use, False otherwise.");
+
+static PyObject *line_info_used(line_info_object *self,
+ void *Py_UNUSED(ignored))
+{
+ return PyBool_FromLong(gpiod_line_info_is_used(self->info));
+}
+
+PyDoc_STRVAR(line_info_consumer_doc,
+"Consumer of the line as represented in the kernel.\n"
+"\n"
+"None if the line is unused");
+
+static PyObject *line_info_consumer(line_info_object *self,
+ void *Py_UNUSED(ignored))
+{
+ const char *consumer = gpiod_line_info_get_consumer(self->info);
+
+ if (!consumer)
+ Py_RETURN_NONE;
+
+ return PyUnicode_FromString(consumer);
+}
+
+PyDoc_STRVAR(line_info_direction_doc, "Line direction.");
+
+static PyObject *line_info_direction(line_info_object *self,
+ void *Py_UNUSED(ignored))
+{
+ return Py_gpiod_MapLinePropCToPy(PY_GPIOD_LINE_DIRECTION,
+ gpiod_line_info_get_direction(self->info));
+}
+
+PyDoc_STRVAR(line_info_active_low_doc,
+"True if the line is active-low, false otherwise.");
+
+static PyObject *line_info_active_low(line_info_object *self,
+ void *Py_UNUSED(ignored))
+{
+ return PyBool_FromLong(gpiod_line_info_is_active_low(self->info));
+}
+
+PyDoc_STRVAR(line_info_bias_doc, "Line bias.");
+
+static PyObject *line_info_bias(line_info_object *self,
+ void *Py_UNUSED(ignored))
+{
+ return Py_gpiod_MapLinePropCToPy(PY_GPIOD_LINE_BIAS,
+ gpiod_line_info_get_bias(self->info));
+}
+
+PyDoc_STRVAR(line_info_drive_doc, "Line drive.");
+
+static PyObject *line_info_drive(line_info_object *self,
+ void *Py_UNUSED(ignored))
+{
+ return Py_gpiod_MapLinePropCToPy(PY_GPIOD_LINE_DRIVE,
+ gpiod_line_info_get_drive(self->info));
+}
+
+PyDoc_STRVAR(line_info_edge_detection_doc, "Edge event detection.");
+
+static PyObject *line_info_edge_detection(line_info_object *self,
+ void *Py_UNUSED(ignored))
+{
+ return Py_gpiod_MapLinePropCToPy(PY_GPIOD_LINE_EDGE,
+ gpiod_line_info_get_edge_detection(self->info));
+}
+
+PyDoc_STRVAR(line_info_event_clock_doc, "Clock used to timestamp edge events.");
+
+static PyObject *line_info_event_clock(line_info_object *self,
+ void *Py_UNUSED(ignored))
+{
+ return Py_gpiod_MapLinePropCToPy(PY_GPIOD_LINE_CLOCK,
+ gpiod_line_info_get_event_clock(self->info));
+}
+
+PyDoc_STRVAR(line_info_debounced_doc,
+"True if the line is debounced, false otherwise.");
+
+static PyObject *line_info_debounced(line_info_object *self,
+ void *Py_UNUSED(ignored))
+{
+ return PyBool_FromLong(gpiod_line_info_is_debounced(self->info));
+}
+
+PyDoc_STRVAR(line_info_debounce_period_doc, "Debounce period.");
+
+static PyObject *line_info_debounce_period(line_info_object *self,
+ void *Py_UNUSED(ignored))
+{
+ return Py_gpiod_MicrosecondsToTimedelta(
+ gpiod_line_info_get_debounce_period_us(self->info));
+}
+
+static PyGetSetDef line_info_getset[] = {
+ {
+ .name = "offset",
+ .get = (getter)line_info_offset,
+ .doc = line_info_offset_doc,
+ },
+ {
+ .name = "name",
+ .get = (getter)line_info_name,
+ .doc = line_info_name_doc,
+ },
+ {
+ .name = "used",
+ .get = (getter)line_info_used,
+ .doc = line_info_used_doc,
+ },
+ {
+ .name = "consumer",
+ .get = (getter)line_info_consumer,
+ .doc = line_info_consumer_doc,
+ },
+ {
+ .name = "direction",
+ .get = (getter)line_info_direction,
+ .doc = line_info_direction_doc,
+ },
+ {
+ .name = "active_low",
+ .get = (getter)line_info_active_low,
+ .doc = line_info_active_low_doc,
+ },
+ {
+ .name = "bias",
+ .get = (getter)line_info_bias,
+ .doc = line_info_bias_doc,
+ },
+ {
+ .name = "drive",
+ .get = (getter)line_info_drive,
+ .doc = line_info_drive_doc,
+ },
+ {
+ .name = "edge_detection",
+ .get = (getter)line_info_edge_detection,
+ .doc = line_info_edge_detection_doc,
+ },
+ {
+ .name = "event_clock",
+ .get = (getter)line_info_event_clock,
+ .doc = line_info_event_clock_doc,
+ },
+ {
+ .name = "debounced",
+ .get = (getter)line_info_debounced,
+ .doc = line_info_debounced_doc,
+ },
+ {
+ .name = "debounce_period",
+ .get = (getter)line_info_debounce_period,
+ .doc = line_info_debounce_period_doc,
+ },
+ { }
+};
+
+static PyObject *line_info_str(PyObject *self)
+{
+ PyObject *offset, *name, *used, *consumer, *direction, *active_low,
+ *bias, *drive, *edge_detection, *event_clock, *debounced,
+ *debounce_period, *str = NULL;
+
+ offset = PyObject_GetAttrString(self, "offset");
+ name = PyObject_GetAttrString(self, "name");
+ used = PyObject_GetAttrString(self, "used");
+ consumer = PyObject_GetAttrString(self, "consumer");
+ direction = PyObject_GetAttrString(self, "direction");
+ active_low = PyObject_GetAttrString(self, "active_low");
+ bias = PyObject_GetAttrString(self, "bias");
+ drive = PyObject_GetAttrString(self, "drive");
+ edge_detection = PyObject_GetAttrString(self, "edge_detection");
+ event_clock = PyObject_GetAttrString(self, "event_clock");
+ debounced = PyObject_GetAttrString(self, "debounced");
+ debounce_period = PyObject_GetAttrString(self, "debounce_period");
+ if (!offset || !name || !used || !consumer || !direction ||
+ !active_low || !bias || !drive || !edge_detection || !event_clock ||
+ !debounced || !debounce_period)
+ goto out;
+
+ str = PyUnicode_FromFormat(
+"<gpiod.LineInfo offset=%S name=\"%S\" used=%S consumer=\"%S\" direction=%S active_low=%S bias=%S drive=%S edge_detection=%S event_clock=%S debounced=%S debounce_period=%S>",
+offset, name, used, consumer, direction, active_low, bias, drive, edge_detection, event_clock, debounced, debounce_period);
+
+out:
+ Py_XDECREF(offset);
+ Py_XDECREF(name);
+ Py_XDECREF(used);
+ Py_XDECREF(consumer);
+ Py_XDECREF(direction);
+ Py_XDECREF(active_low);
+ Py_XDECREF(bias);
+ Py_XDECREF(drive);
+ Py_XDECREF(edge_detection);
+ Py_XDECREF(event_clock);
+ Py_XDECREF(debounced);
+ Py_XDECREF(debounce_period);
+ return str;
+}
+
+PyDoc_STRVAR(line_info_type_doc,
+"Line info object contains an immutable snapshot of a line's status.");
+
+static PyTypeObject line_info_type = {
+ PyVarObject_HEAD_INIT(NULL, 0)
+ .tp_name = "gpiod.LineInfo",
+ .tp_basicsize = sizeof(line_info_object),
+ .tp_flags = Py_TPFLAGS_DEFAULT,
+ .tp_doc = line_info_type_doc,
+ .tp_new = PyType_GenericNew,
+ .tp_init = (initproc)line_info_init,
+ .tp_finalize = (destructor)line_info_finalize,
+ .tp_dealloc = (destructor)Py_gpiod_dealloc,
+ .tp_getset = line_info_getset,
+ .tp_str = (reprfunc)line_info_str
+};
+
+int Py_gpiod_RegisterLineInfoType(PyObject *module)
+{
+ return PyModule_AddType(module, &line_info_type);
+}
+
+PyObject *Py_gpiod_MakeLineInfo(struct gpiod_line_info *info)
+{
+ line_info_object *info_obj;
+
+ info_obj = PyObject_New(line_info_object, &line_info_type);
+ if (!info_obj)
+ return NULL;
+
+ info_obj->info = info;
+
+ return (PyObject *)info_obj;
+}
diff --git a/bindings/python/line-request.c b/bindings/python/line-request.c
new file mode 100644
index 0000000..f3f4de9
--- /dev/null
+++ b/bindings/python/line-request.c
@@ -0,0 +1,713 @@
+// SPDX-License-Identifier: LGPL-2.1-or-later
+// SPDX-FileCopyrightText: 2022 Bartosz Golaszewski <brgl@bgdev.pl>
+
+#include "module.h"
+
+typedef struct {
+ PyObject_HEAD;
+ struct gpiod_line_request *request;
+} line_request_object;
+
+static int line_request_init(PyObject *Py_UNUSED(self),
+ PyObject *Py_UNUSED(ignored0),
+ PyObject *Py_UNUSED(ignored1))
+{
+ PyErr_SetString(PyExc_TypeError,
+ "cannot create 'gpiod.LineRequest' instances");
+ return -1;
+}
+
+static bool line_request_released(line_request_object *self)
+{
+ return !self->request;
+}
+
+static bool line_request_check_released(line_request_object *self)
+{
+ if (line_request_released(self)) {
+ Py_gpiod_SetRequestReleasedError();
+ return true;
+ }
+
+ return false;
+}
+
+static void line_request_finalize(line_request_object *self)
+{
+ if (!line_request_released(self))
+ PyObject_CallMethod((PyObject *)self, "release", "");
+}
+
+PyDoc_STRVAR(line_request_fd_doc,
+"Number of the file descriptor associated with this request.");
+
+static PyObject *
+line_request_fd(line_request_object *self, void *Py_UNUSED(ignored))
+{
+ if (line_request_check_released(self))
+ return NULL;
+
+ return PyLong_FromLong(gpiod_line_request_get_fd(self->request));
+}
+
+PyDoc_STRVAR(line_request_num_lines_doc, "Number of requested lines.");
+
+static PyObject *
+line_request_num_lines(line_request_object *self, void *Py_UNUSED(ignored))
+{
+ if (line_request_check_released(self))
+ return NULL;
+
+ return PyLong_FromSize_t(
+ gpiod_line_request_get_num_lines(self->request));
+}
+
+PyDoc_STRVAR(line_request_offsets_doc, "Offsets of the lines in the request.");
+
+static PyObject *
+line_request_offsets(line_request_object *self, void *Py_UNUSED(ignored))
+{
+ PyObject *offset_list, *offset_obj;
+ size_t num_offsets, i;
+ unsigned int *offsets;
+ int ret;
+
+ if (line_request_check_released(self))
+ return NULL;
+
+ num_offsets = gpiod_line_request_get_num_lines(self->request);
+
+ offsets = PyMem_Calloc(num_offsets, sizeof(unsigned int));
+ if (!offsets) {
+ PyErr_NoMemory();
+ return NULL;
+ }
+
+ gpiod_line_request_get_offsets(self->request, offsets);
+
+ offset_list = PyList_New(num_offsets);
+ if (!offset_list) {
+ PyMem_Free(offsets);
+ return NULL;
+ }
+
+ for (i = 0; i < num_offsets; i++) {
+ offset_obj = PyLong_FromUnsignedLong(offsets[i]);
+ if (!offset_obj) {
+ Py_DECREF(offset_list);
+ PyMem_Free(offsets);
+ return NULL;
+ }
+
+ ret = PyList_SetItem(offset_list, i, offset_obj);
+ if (ret) {
+ Py_DECREF(offset_obj);
+ Py_DECREF(offset_list);
+ PyMem_Free(offsets);
+ return NULL;
+ }
+ }
+
+ PyMem_Free(offsets);
+
+ return offset_list;
+}
+
+static PyGetSetDef line_request_getset[] = {
+ {
+ .name = "fd",
+ .get = (getter)line_request_fd,
+ .doc = line_request_fd_doc,
+ },
+ {
+ .name = "num_lines",
+ .get = (getter)line_request_num_lines,
+ .doc = line_request_num_lines_doc,
+ },
+ {
+ .name = "offsets",
+ .get = (getter)line_request_offsets,
+ .doc = line_request_offsets_doc,
+ },
+ { }
+};
+
+PyDoc_STRVAR(line_request_enter_doc,
+"Controlled execution enter callback.");
+
+static PyObject *
+line_request_enter(PyObject *self, PyObject *Py_UNUSED(ignored))
+{
+ if (PyObject_Not(self)) {
+ Py_gpiod_SetRequestReleasedError();
+ return NULL;
+ }
+
+ Py_INCREF(self);
+ return self;
+}
+
+PyDoc_STRVAR(line_request_exit_doc,
+"Controlled execution exit callback.");
+
+static PyObject *line_request_exit(PyObject *self, PyObject *Py_UNUSED(ignored))
+{
+ return PyObject_CallMethod(self, "release", "");
+}
+
+PyDoc_STRVAR(line_request_release_doc,
+"release() -> None\n"
+"\n"
+"Close the associated request file descriptor. The request object must no\n"
+"longer be used after this method is called.");
+
+static PyObject *
+line_request_release(line_request_object *self, PyObject *Py_UNUSED(ignored))
+{
+ if (line_request_check_released(self))
+ return NULL;
+
+ Py_BEGIN_ALLOW_THREADS;
+ gpiod_line_request_release(self->request);
+ Py_END_ALLOW_THREADS;
+ self->request = NULL;
+
+ Py_RETURN_NONE;
+}
+
+PyDoc_STRVAR(line_request_get_value_doc,
+"get_value(offset) -> gpiod.Line.Value\n"
+"\n"
+"Get a single line value.");
+
+static PyObject *
+line_request_get_value(line_request_object *self, PyObject *args)
+{
+ unsigned int offset;
+ int ret;
+
+ ret = PyArg_ParseTuple(args, "I", &offset);
+ if (!ret)
+ return NULL;
+
+ return PyObject_CallMethod((PyObject *)self, "get_values", "I", offset);
+}
+
+PyDoc_STRVAR(line_request_get_values_doc,
+"get_values(offset(s)) -> value|[values]\n"
+"\n"
+"Get the values of all or a subset of requested lines");
+
+static PyObject *
+line_request_get_values(line_request_object *self, PyObject *args)
+{
+ PyObject *offsets_obj = NULL, *values_obj, *val, *offset;
+ Py_ssize_t num_values, i;
+ unsigned int *offsets;
+ int ret, *values;
+
+ if (line_request_check_released(self))
+ return NULL;
+
+ ret = PyArg_ParseTuple(args, "|O", &offsets_obj);
+ if (!ret)
+ return NULL;
+
+ if (!offsets_obj) {
+ num_values = gpiod_line_request_get_num_lines(self->request);
+ } else if (PyLong_Check(offsets_obj)) {
+ num_values = 1;
+ } else if (PyList_Check(offsets_obj)) {
+ num_values = PyList_Size(offsets_obj);
+ if (num_values < 0)
+ return NULL;
+ } else {
+ PyErr_SetString(PyExc_TypeError,
+ "offsets must be either a single integer or a list of integers");
+ return NULL;
+ }
+
+ offsets = PyMem_Calloc(num_values, sizeof(unsigned int));
+ if (!offsets)
+ return NULL;
+
+ values = PyMem_Calloc(num_values, sizeof(int));
+ if (!values) {
+ PyMem_Free(offsets);
+ return NULL;
+ }
+
+ if (!offsets_obj) {
+ gpiod_line_request_get_offsets(self->request, offsets);
+ } else if (num_values == 1) {
+ offsets[0] = Py_gpiod_PyLongAsUnsignedInt(offsets_obj);
+ if (PyErr_Occurred()) {
+ PyMem_Free(values);
+ PyMem_Free(offsets);
+ return NULL;
+ }
+ } else {
+ for (i = 0; i < num_values; i++) {
+ offset = PyList_GetItem(offsets_obj, i);
+ if (!offset) {
+ PyMem_Free(values);
+ PyMem_Free(offsets);
+ return NULL;
+ }
+
+ offsets[i] = Py_gpiod_PyLongAsUnsignedInt(offset);
+ if (PyErr_Occurred()){
+ PyMem_Free(values);
+ PyMem_Free(offsets);
+ return NULL;
+ }
+ }
+ }
+
+ Py_BEGIN_ALLOW_THREADS;
+ ret = gpiod_line_request_get_values_subset(self->request, num_values,
+ offsets, values);
+ Py_END_ALLOW_THREADS;
+ PyMem_Free(offsets);
+ if (ret) {
+ PyMem_Free(values);
+ Py_gpiod_SetErrFromErrno();
+ return NULL;
+ }
+
+ if (num_values == 1) {
+ values_obj = Py_gpiod_MapLinePropCToPy(PY_GPIOD_LINE_VALUE,
+ values[0]);
+ if (!values_obj) {
+ PyMem_Free(values);
+ return NULL;
+ }
+ } else {
+ values_obj = PyList_New(num_values);
+ if (!values_obj) {
+ PyMem_Free(values);
+ return NULL;
+ }
+
+ for (i = 0; i < num_values; i++) {
+ val = Py_gpiod_MapLinePropCToPy(PY_GPIOD_LINE_VALUE,
+ values[i]);
+ if (!val) {
+ Py_DECREF(values_obj);
+ PyMem_Free(values);
+ return NULL;
+ }
+
+ ret = PyList_SetItem(values_obj, i, val);
+ if (ret) {
+ Py_DECREF(val);
+ Py_DECREF(values_obj);
+ PyMem_Free(values);
+ return NULL;
+ }
+ }
+ }
+
+ PyMem_Free(values);
+
+ return values_obj;
+}
+
+PyDoc_STRVAR(line_request_set_value_doc,
+"set_value(offset, value) -> None\n"
+"\n"
+"Set value of a single line.");
+
+static PyObject *
+line_request_set_value(line_request_object *self, PyObject *args)
+{
+ PyObject *offset, *value, *dict, *result;
+ int ret;
+
+ ret = PyArg_ParseTuple(args, "OO", &offset, &value);
+ if (!ret)
+ return NULL;
+
+ dict = PyDict_New();
+ if (!dict)
+ return NULL;
+
+ ret = PyDict_SetItem(dict, offset, value);
+ if (ret)
+ return NULL;
+
+ result = PyObject_CallMethod((PyObject *)self, "set_values", "O", dict);
+ Py_DECREF(dict);
+ return result;
+}
+
+PyDoc_STRVAR(line_request_set_values_doc,
+"set_values({offset: value}) -> None\n"
+"\n"
+"Set the values of all or a subset of requested lines");
+
+static PyObject *
+line_request_set_values(line_request_object *self, PyObject *args)
+{
+ PyObject *valobj, *off, *val, *iter;
+ Py_ssize_t num_values, pos = 0;
+ unsigned int *offsets;
+ int *values;
+ int ret;
+
+ if (line_request_check_released(self))
+ return NULL;
+
+ ret = PyArg_ParseTuple(args, "O", &valobj);
+ if (!ret)
+ return NULL;
+
+ num_values = PyObject_Size(valobj);
+ if (num_values < 0)
+ return NULL;
+
+ values = PyMem_Calloc(num_values, sizeof(int));
+ if (!values) {
+ PyErr_NoMemory();
+ return NULL;
+ }
+
+ if (PyDict_Check(valobj)) {
+ offsets = PyMem_Calloc(num_values, sizeof(unsigned int));
+ if (!offsets) {
+ PyErr_NoMemory();
+ return NULL;
+ }
+
+ while (PyDict_Next(valobj, &pos, &off, &val)) {
+ offsets[pos - 1] = Py_gpiod_PyLongAsUnsignedInt(off);
+ if (PyErr_Occurred()) {
+ PyMem_Free(offsets);
+ PyMem_Free(values);
+ PyErr_NoMemory();
+ return NULL;
+ }
+
+ values[pos - 1] = Py_gpiod_MapLinePropPyToC(
+ PY_GPIOD_LINE_VALUE, val);
+ if (values[pos - 1] < 0) {
+ PyMem_Free(offsets);
+ PyMem_Free(values);
+ PyErr_NoMemory();
+ return NULL;
+ }
+ }
+
+ Py_BEGIN_ALLOW_THREADS;
+ ret = gpiod_line_request_set_values_subset(self->request,
+ num_values,
+ offsets, values);
+ Py_END_ALLOW_THREADS;
+ PyMem_Free(offsets);
+ } else if (PyList_Check(valobj)) {
+ if ((size_t)num_values != gpiod_line_request_get_num_lines(
+ self->request)) {
+ PyErr_SetString(PyExc_ValueError,
+ "list of values must be the same size as the number of requested lines");
+ PyMem_Free(values);
+ return NULL;
+ }
+
+ iter = PyObject_GetIter(valobj);
+ if (!iter) {
+ PyMem_Free(values);
+ return NULL;
+ }
+
+ for (pos = 0;; pos++) {
+ val = PyIter_Next(iter);
+ if (!val) {
+ Py_DECREF(iter);
+ break;
+ }
+
+ values[pos] = Py_gpiod_MapLinePropPyToC(
+ PY_GPIOD_LINE_VALUE, val);
+ Py_DECREF(val);
+ if (values[pos] < 0) {
+ PyMem_Free(values);
+ return NULL;
+ }
+ }
+
+ Py_BEGIN_ALLOW_THREADS;
+ ret = gpiod_line_request_set_values(self->request, values);
+ Py_END_ALLOW_THREADS;
+ }
+
+ PyMem_Free(values);
+ if (ret) {
+ Py_gpiod_SetErrFromErrno();
+ return NULL;
+ }
+
+ Py_RETURN_NONE;
+}
+
+PyDoc_STRVAR(line_request_reconfigure_lines_doc,
+"reconfigure_lines(line_cfg) -> None\n"
+"\n"
+"Update the configuration of lines associated with a line request\n"
+"\n"
+" line_cfg\n"
+" gpiod.LineConfig containing new configuration");
+
+static PyObject *
+line_request_reconfigure_lines(line_request_object *self, PyObject *args)
+{
+ struct gpiod_line_config *cfg;
+ PyObject *cfgobj;
+ int ret;
+
+ if (line_request_check_released(self))
+ return NULL;
+
+ ret = PyArg_ParseTuple(args, "O", &cfgobj);
+ if (!ret)
+ return NULL;
+
+ cfg = Py_gpiod_LineConfigGetData(cfgobj);
+ if (!cfg)
+ return NULL;
+
+ Py_BEGIN_ALLOW_THREADS;
+ ret = gpiod_line_request_reconfigure_lines(self->request, cfg);
+ Py_END_ALLOW_THREADS;
+ if (ret) {
+ Py_gpiod_SetErrFromErrno();
+ return NULL;
+ }
+
+ Py_RETURN_NONE;
+}
+
+PyDoc_STRVAR(line_request_wait_edge_event_doc,
+"wait_edge_event(timeout) -> bool\n"
+"\n"
+"Wait for edge events on any of the requested lines.\n"
+"\n"
+" timeout\n"
+" datetime.timedelta containing the max time to wait for events");
+
+static PyObject *
+line_request_wait_edge_event(line_request_object *self, PyObject *args)
+{
+ PyObject *timedelta = NULL;
+ int64_t timeout_us = 0, timeout_ns;
+ int ret;
+
+ if (line_request_check_released(self))
+ return NULL;
+
+ ret = PyArg_ParseTuple(args, "|O", &timedelta);
+ if (!ret)
+ return NULL;
+
+ if (timedelta) {
+ timeout_us = Py_gpiod_TimedeltaToMicroseconds(timedelta);
+ if (PyErr_Occurred())
+ return NULL;
+ }
+
+ timeout_ns = timeout_us * 1000;
+
+ Py_BEGIN_ALLOW_THREADS;
+ ret = gpiod_line_request_wait_edge_event(self->request, timeout_ns);
+ Py_END_ALLOW_THREADS;
+ if (ret < 0) {
+ Py_gpiod_SetErrFromErrno();
+ return NULL;
+ }
+
+ return PyBool_FromLong(ret);
+}
+
+PyDoc_STRVAR(line_request_read_edge_event_doc,
+"read_edge_event(buffer, **kwargs) -> int\n"
+"\n"
+"Read a number of edge events from a line request.\n"
+"\n"
+" buffer\n"
+" gpiod.EdgeEventBuffer into which events will be read");
+
+static PyObject *
+line_request_read_edge_event(line_request_object *self,
+ PyObject *args, PyObject *kwargs)
+{
+ static char *kwlist[] = {
+ "",
+ "max_events",
+ NULL
+ };
+
+ struct gpiod_edge_event_buffer *buffer;
+ Py_ssize_t max_events;
+ PyObject *bufobj;
+ int ret;
+
+ if (line_request_check_released(self))
+ return NULL;
+
+ ret = PyArg_ParseTupleAndKeywords(args, kwargs, "O|n", kwlist,
+ &bufobj, &max_events);
+ if (!ret)
+ return NULL;
+
+ buffer = Py_gpiod_EdgeEventBufferGetData(bufobj);
+ if (!buffer)
+ return NULL;
+
+ if (!max_events)
+ max_events = gpiod_edge_event_buffer_get_capacity(buffer);
+
+ Py_BEGIN_ALLOW_THREADS;
+ ret = gpiod_line_request_read_edge_event(self->request,
+ buffer, max_events);
+ Py_END_ALLOW_THREADS;
+ if (ret < 0) {
+ Py_gpiod_SetErrFromErrno();
+ return NULL;
+ }
+
+ return PyLong_FromLong(ret);
+}
+
+static PyMethodDef line_request_methods[] = {
+ {
+ .ml_name = "__enter__",
+ .ml_meth = (PyCFunction)line_request_enter,
+ .ml_flags = METH_NOARGS,
+ .ml_doc = line_request_enter_doc,
+ },
+ {
+ .ml_name = "__exit__",
+ .ml_meth = (PyCFunction)line_request_exit,
+ .ml_flags = METH_VARARGS,
+ .ml_doc = line_request_exit_doc,
+ },
+ {
+ .ml_name = "release",
+ .ml_meth = (PyCFunction)line_request_release,
+ .ml_flags = METH_NOARGS,
+ .ml_doc = line_request_release_doc,
+ },
+ {
+ .ml_name = "get_value",
+ .ml_meth = (PyCFunction)line_request_get_value,
+ .ml_flags = METH_VARARGS,
+ .ml_doc = line_request_get_value_doc,
+ },
+ {
+ .ml_name = "get_values",
+ .ml_meth = (PyCFunction)line_request_get_values,
+ .ml_flags = METH_VARARGS,
+ .ml_doc = line_request_get_values_doc,
+ },
+ {
+ .ml_name = "set_value",
+ .ml_meth = (PyCFunction)line_request_set_value,
+ .ml_flags = METH_VARARGS,
+ .ml_doc = line_request_set_value_doc,
+ },
+ {
+ .ml_name = "set_values",
+ .ml_meth = (PyCFunction)line_request_set_values,
+ .ml_flags = METH_VARARGS,
+ .ml_doc = line_request_set_values_doc,
+ },
+ {
+ .ml_name = "reconfigure_lines",
+ .ml_meth = (PyCFunction)line_request_reconfigure_lines,
+ .ml_flags = METH_VARARGS,
+ .ml_doc = line_request_reconfigure_lines_doc,
+ },
+ {
+ .ml_name = "wait_edge_event",
+ .ml_meth = (PyCFunction)line_request_wait_edge_event,
+ .ml_flags = METH_VARARGS,
+ .ml_doc = line_request_wait_edge_event_doc,
+ },
+ {
+ .ml_name = "read_edge_event",
+ .ml_meth = (PyCFunction)(void(*)(void))
+ line_request_read_edge_event,
+ .ml_flags = METH_VARARGS | METH_KEYWORDS,
+ .ml_doc = line_request_read_edge_event_doc,
+ },
+ { }
+};
+
+static PyObject *line_request_str(PyObject *self)
+{
+ PyObject *num_lines, *offsets, *fd, *str = NULL;
+
+ if (PyObject_Not(self))
+ return PyUnicode_FromString("<gpiod.LineRequest RELEASED>");
+
+ num_lines = PyObject_GetAttrString(self, "num_lines");
+ offsets = PyObject_GetAttrString(self, "offsets");
+ fd = PyObject_GetAttrString(self, "fd");
+ if (!num_lines || !offsets || !fd)
+ goto out;
+
+ str = PyUnicode_FromFormat(
+ "<gpiod.LineRequest num_lines=%S offsets=%S fd=%S>",
+ num_lines, offsets, fd);
+
+out:
+ Py_XDECREF(num_lines);
+ Py_XDECREF(offsets);
+ Py_XDECREF(fd);
+ return str;
+}
+
+static int line_request_bool(line_request_object *self)
+{
+ return !line_request_released(self);
+}
+
+static PyNumberMethods line_request_number_methods = {
+ .nb_bool = (inquiry)line_request_bool,
+};
+
+PyDoc_STRVAR(line_request_doc,
+"Stores the context of a set of requested GPIO lines.");
+
+static PyTypeObject line_request_type = {
+ PyVarObject_HEAD_INIT(NULL, 0)
+ .tp_name = "gpiod.LineRequest",
+ .tp_basicsize = sizeof(line_request_object),
+ .tp_flags = Py_TPFLAGS_DEFAULT,
+ .tp_doc = line_request_doc,
+ .tp_as_number = &line_request_number_methods,
+ .tp_new = PyType_GenericNew,
+ .tp_init = (initproc)line_request_init,
+ .tp_finalize = (destructor)line_request_finalize,
+ .tp_dealloc = (destructor)Py_gpiod_dealloc,
+ .tp_getset = line_request_getset,
+ .tp_methods = line_request_methods,
+ .tp_str = (reprfunc)line_request_str
+};
+
+int Py_gpiod_RegisterLineRequestType(PyObject *module)
+{
+ return PyModule_AddType(module, &line_request_type);
+}
+
+PyObject *Py_gpiod_MakeLineRequest(struct gpiod_line_request *req)
+{
+ line_request_object *req_obj;
+
+ req_obj = PyObject_New(line_request_object, &line_request_type);
+ if (!req_obj)
+ return NULL;
+
+ req_obj->request = req;
+
+ return (PyObject *)req_obj;
+}
diff --git a/bindings/python/line.c b/bindings/python/line.c
new file mode 100644
index 0000000..7003ab0
--- /dev/null
+++ b/bindings/python/line.c
@@ -0,0 +1,239 @@
+// SPDX-License-Identifier: LGPL-2.1-or-later
+// SPDX-FileCopyrightText: 2022 Bartosz Golaszewski <brgl@bgdev.pl>
+
+#include "enum/enum.h"
+#include "module.h"
+
+static const PyCEnum_EnumVal value_enum_vals[] = {
+ {
+ .name = "INACTIVE",
+ .value = GPIOD_LINE_VALUE_INACTIVE
+ },
+ {
+ .name = "ACTIVE",
+ .value = GPIOD_LINE_VALUE_ACTIVE
+ },
+ { }
+};
+
+static const PyCEnum_EnumVal direction_enum_vals[] = {
+ {
+ .name = "AS_IS",
+ .value = GPIOD_LINE_DIRECTION_AS_IS
+ },
+ {
+ .name = "INPUT",
+ .value = GPIOD_LINE_DIRECTION_INPUT
+ },
+ {
+ .name = "OUTPUT",
+ .value = GPIOD_LINE_DIRECTION_OUTPUT
+ },
+ { }
+};
+
+static const PyCEnum_EnumVal bias_enum_vals[] = {
+ {
+ .name = "AS_IS",
+ .value = GPIOD_LINE_BIAS_AS_IS
+ },
+ {
+ .name = "UNKNOWN",
+ .value = GPIOD_LINE_BIAS_UNKNOWN
+ },
+ {
+ .name = "DISABLED",
+ .value = GPIOD_LINE_BIAS_DISABLED
+ },
+ {
+ .name = "PULL_UP",
+ .value = GPIOD_LINE_BIAS_PULL_UP
+ },
+ {
+ .name = "PULL_DOWN",
+ .value = GPIOD_LINE_BIAS_PULL_DOWN
+ },
+ { }
+};
+
+static const PyCEnum_EnumVal drive_enum_vals[] = {
+ {
+ .name = "PUSH_PULL",
+ .value = GPIOD_LINE_DRIVE_PUSH_PULL
+ },
+ {
+ .name = "OPEN_DRAIN",
+ .value = GPIOD_LINE_DRIVE_OPEN_DRAIN
+ },
+ {
+ .name = "OPEN_SOURCE",
+ .value = GPIOD_LINE_DRIVE_OPEN_SOURCE
+ },
+ { }
+};
+
+static const PyCEnum_EnumVal edge_enum_vals[] = {
+ {
+ .name = "NONE",
+ .value = GPIOD_LINE_EDGE_NONE
+ },
+ {
+ .name = "RISING",
+ .value = GPIOD_LINE_EDGE_RISING
+ },
+ {
+ .name = "FALLING",
+ .value = GPIOD_LINE_EDGE_FALLING
+ },
+ {
+ .name = "BOTH",
+ .value = GPIOD_LINE_EDGE_BOTH
+ },
+ { }
+};
+
+static const PyCEnum_EnumVal event_clock_enum_vals[] = {
+ {
+ .name = "MONOTONIC",
+ .value = GPIOD_LINE_EVENT_CLOCK_MONOTONIC
+ },
+ {
+ .name = "REALTIME",
+ .value = GPIOD_LINE_EVENT_CLOCK_REALTIME
+ },
+ { }
+};
+
+static const PyCEnum_EnumDef line_enums[] = {
+ {
+ .name = "Value",
+ .values = value_enum_vals
+ },
+ {
+ .name = "Direction",
+ .values = direction_enum_vals
+ },
+ {
+ .name = "Bias",
+ .values = bias_enum_vals
+ },
+ {
+ .name = "Drive",
+ .values = drive_enum_vals
+ },
+ {
+ .name = "Edge",
+ .values = edge_enum_vals
+ },
+ {
+ .name = "Clock",
+ .values = event_clock_enum_vals
+ },
+ { }
+};
+
+PyDoc_STRVAR(line_type_doc,
+"Container for common definitions related to GPIO lines.\n");
+
+static PyTypeObject line_type = {
+ PyVarObject_HEAD_INIT(NULL, 0)
+ .tp_name = "gpiod.Line",
+ .tp_flags = Py_TPFLAGS_DEFAULT,
+ .tp_doc = line_type_doc
+};
+
+int Py_gpiod_RegisterLineType(PyObject *module)
+{
+ int ret;
+
+ ret = PyType_Ready(&line_type);
+ if (ret)
+ return -1;
+
+ Py_INCREF(&line_type);
+ ret = PyModule_AddObject(module, "Line", (PyObject *)&line_type);
+ if (ret) {
+ Py_DECREF(&line_type);
+ return -1;
+ }
+
+ ret = PyCEnum_AddEnumsToType(line_enums, &line_type);
+ if (ret) {
+ Py_DECREF(&line_type);
+ return -1;
+ }
+
+ return 0;
+}
+
+static const char *get_enum_name(int prop)
+{
+ switch (prop) {
+ case PY_GPIOD_LINE_VALUE:
+ return "Value";
+ case PY_GPIOD_LINE_DIRECTION:
+ return "Direction";
+ case PY_GPIOD_LINE_EDGE:
+ return "Edge";
+ case PY_GPIOD_LINE_BIAS:
+ return "Bias";
+ case PY_GPIOD_LINE_DRIVE:
+ return "Drive";
+ case PY_GPIOD_LINE_CLOCK:
+ return "Clock";
+ }
+
+ PyErr_SetString(PyExc_ValueError, "unsupported line property");
+ return NULL;
+}
+
+static PyObject *get_line_type(void)
+{
+ PyObject *mod, *dict, *type;
+
+ mod = Py_gpiod_GetModule();
+ if (!mod)
+ return NULL;
+
+ dict = PyModule_GetDict(mod);
+ if (!dict)
+ return NULL;
+
+ type = PyDict_GetItemString(dict, "Line");
+ if (!type)
+ return NULL;
+
+ return type;
+}
+
+PyObject *Py_gpiod_MapLinePropCToPy(int prop, int value)
+{
+ const char *enum_name;
+ PyObject *type;
+
+ enum_name = get_enum_name(prop);
+ if (!enum_name)
+ return NULL;
+
+ type = get_line_type();
+ if (!type)
+ return NULL;
+
+ return PyCEnum_MapCToPy(type, enum_name, value);
+}
+
+int Py_gpiod_MapLinePropPyToC(int prop, PyObject *value)
+{
+ const char *enum_name;
+ PyObject *type;
+
+ enum_name = get_enum_name(prop);
+ if (!enum_name)
+ return -1;
+
+ type = get_line_type();
+ if (!type)
+ return -1;
+
+ return PyCEnum_MapPyToC(type, enum_name, value);
+}
diff --git a/bindings/python/module.c b/bindings/python/module.c
new file mode 100644
index 0000000..2175426
--- /dev/null
+++ b/bindings/python/module.c
@@ -0,0 +1,281 @@
+// SPDX-License-Identifier: LGPL-2.1-or-later
+// SPDX-FileCopyrightText: 2022 Bartosz Golaszewski <brgl@bgdev.pl>
+
+#include <errno.h>
+#include <limits.h>
+
+#include "module.h"
+
+/* Generic dealloc callback for all gpiod objects. */
+void Py_gpiod_dealloc(PyObject *self)
+{
+ int ret;
+
+ ret = PyObject_CallFinalizerFromDealloc(self);
+ if (ret < 0)
+ return;
+
+ PyObject_Del(self);
+}
+
+PyDoc_STRVAR(module_is_gpiochip_device_doc,
+"is_gpiochip_device(path) -> boolean\n"
+"\n"
+"Check if the file pointed to by path is a GPIO chip character device.\n"
+"Returns true if so, False otherwise.\n"
+"\n"
+" path\n"
+" Path to the file that should be checked.\n");
+
+static PyObject *
+module_is_gpiochip_device(PyObject *Py_UNUSED(self), PyObject *args)
+{
+ const char *path;
+ int ret;
+
+ ret = PyArg_ParseTuple(args, "s", &path);
+ if (!ret)
+ return NULL;
+
+ return PyBool_FromLong(gpiod_is_gpiochip_device(path));
+}
+
+PyDoc_STRVAR(module_request_lines_doc,
+"request_lines(path, req_cfg, line_cfg) -> gpiod.LineRequest\n"
+"\n"
+"Open a GPIO chip indicated by path, request a set of lines for exclusive\n"
+"usage and close the chip.\n"
+"\n"
+" path\n"
+" Path to the GPIO chip character device\n"
+" req_cfg\n"
+" Request config object\n"
+" line_cfg\n"
+" Line config object");
+
+static PyObject *module_request_lines(PyObject *self, PyObject *args)
+{
+ PyObject *path, *req_cfg, *line_cfg, *dict, *type, *chip, *req,
+ *errtype, *errvalue, *errtraceback;
+ int ret;
+
+ ret = PyArg_ParseTuple(args, "OOO", &path, &req_cfg, &line_cfg);
+ if (!ret)
+ return NULL;
+
+ dict = PyModule_GetDict(self);
+ if (!dict)
+ return NULL;
+
+ type = PyDict_GetItemString(dict, "Chip");
+ if (!type)
+ return NULL;
+
+ chip = PyObject_CallOneArg(type, path);
+ if (!chip)
+ return NULL;
+
+ req = PyObject_CallMethod(chip, "request_lines",
+ "OO", req_cfg, line_cfg);
+ PyErr_Fetch(&errtype, &errvalue, &errtraceback);
+ PyObject_CallMethod(chip, "close", NULL);
+ PyErr_Restore(errtype, errvalue, errtraceback);
+ Py_DECREF(chip);
+ return req;
+}
+
+static PyMethodDef module_methods[] = {
+ {
+ .ml_name = "is_gpiochip_device",
+ .ml_meth = (PyCFunction)module_is_gpiochip_device,
+ .ml_flags = METH_VARARGS,
+ .ml_doc = module_is_gpiochip_device_doc,
+ },
+ {
+ .ml_name = "request_lines",
+ .ml_meth = (PyCFunction)module_request_lines,
+ .ml_flags = METH_VARARGS,
+ .ml_doc = module_request_lines_doc,
+ },
+ { }
+};
+
+struct module_state {
+ PyObject *timedelta_type;
+};
+
+static void free_module_state(void *mod)
+{
+ struct module_state *state = PyModule_GetState((PyObject *)mod);
+
+ Py_XDECREF(state->timedelta_type);
+}
+
+PyDoc_STRVAR(module_doc,
+"Python bindings for libgpiod.\n\
+\n\
+This module wraps the native C API of libgpiod in a set of python classes.");
+
+static PyModuleDef module_def = {
+ PyModuleDef_HEAD_INIT,
+ .m_name = "gpiod",
+ .m_doc = module_doc,
+ .m_size = sizeof(struct module_state),
+ .m_free = free_module_state,
+ .m_methods = module_methods,
+};
+
+typedef int (*register_func)(PyObject *);
+
+static const register_func register_type_funcs[] = {
+ Py_gpiod_RegisterChipType,
+ Py_gpiod_RegisterChipInfoType,
+ Py_gpiod_RegisterEdgeEventType,
+ Py_gpiod_RegisterEdgeEventBufferType,
+ Py_gpiod_RegisterExceptionTypes,
+ Py_gpiod_RegisterInfoEventType,
+ Py_gpiod_RegisterLineConfigType,
+ Py_gpiod_RegisterLineType,
+ Py_gpiod_RegisterLineInfoType,
+ Py_gpiod_RegisterLineRequestType,
+ Py_gpiod_RegisterRequestConfigType,
+};
+
+static int init_timedelta_type(struct module_state *state)
+{
+ PyObject *datetime;
+
+ datetime = PyImport_ImportModule("datetime");
+ if (!datetime)
+ return -1;
+
+ state->timedelta_type = PyObject_GetAttrString(datetime, "timedelta");
+ Py_DECREF(datetime);
+ if (!state->timedelta_type)
+ return -1;
+
+ return 0;
+}
+
+PyMODINIT_FUNC PyInit_gpiod(void)
+{
+ struct module_state *state;
+ size_t num_funcs, i;
+ PyObject *module;
+ int ret;
+
+ module = PyModule_Create(&module_def);
+ if (!module)
+ return NULL;
+
+ ret = PyState_AddModule(module, &module_def);
+ if (ret) {
+ Py_DECREF(module);
+ return NULL;
+ }
+
+ state = PyModule_GetState(module);
+
+ ret = init_timedelta_type(state);
+ if (ret) {
+ Py_DECREF(module);
+ return NULL;
+ }
+
+ num_funcs = sizeof(register_type_funcs) / sizeof(*register_type_funcs);
+ for (i = 0; i < num_funcs; i++) {
+ ret = register_type_funcs[i](module);
+ if (ret) {
+ Py_DECREF(module);
+ return NULL;
+ }
+ }
+
+ ret = PyModule_AddStringConstant(module, "__version__",
+ gpiod_version_string());
+ if (ret < 0) {
+ Py_DECREF(module);
+ return NULL;
+ }
+
+ return module;
+}
+
+PyObject *Py_gpiod_GetModule(void)
+{
+ return PyState_FindModule(&module_def);
+}
+
+PyObject *Py_gpiod_MicrosecondsToTimedelta(unsigned long us)
+{
+ PyObject *module, *timedelta, *args, *kwargs, *val;
+ struct module_state *state;
+ int ret;
+
+ module = PyState_FindModule(&module_def);
+ if (!module)
+ return NULL;
+
+ state = PyModule_GetState(module);
+
+ kwargs = PyDict_New();
+ if (!kwargs)
+ return NULL;
+
+ val = PyLong_FromUnsignedLong(us);
+ if (!val) {
+ Py_DECREF(kwargs);
+ return NULL;
+ }
+
+ ret = PyDict_SetItemString(kwargs, "microseconds", val);
+ Py_DECREF(val);
+ if (ret) {
+ Py_DECREF(kwargs);
+ return NULL;
+ }
+
+ args = PyTuple_New(0);
+ if (!args) {
+ Py_DECREF(kwargs);
+ return NULL;
+ }
+
+ timedelta = PyObject_Call(state->timedelta_type, args, kwargs);
+ Py_DECREF(args);
+ Py_DECREF(kwargs);
+ return timedelta;
+}
+
+unsigned long Py_gpiod_TimedeltaToMicroseconds(PyObject *timedelta)
+{
+ PyObject *total_seconds;
+ double val;
+
+ total_seconds = PyObject_CallMethod(timedelta, "total_seconds", NULL);
+ if (!total_seconds)
+ return 0;
+
+ val = PyFloat_AsDouble(total_seconds);
+ Py_DECREF(total_seconds);
+ if (PyErr_Occurred())
+ return 0;
+
+ return val * 1000000;
+}
+
+unsigned int Py_gpiod_PyLongAsUnsignedInt(PyObject *pylong)
+{
+ unsigned long tmp;
+
+ tmp = PyLong_AsUnsignedLong(pylong);
+ if (PyErr_Occurred())
+ return 0;
+
+ if (tmp > UINT_MAX) {
+ PyErr_SetString(PyExc_ValueError, "value exceeding UINT_MAX");
+ return 0;
+ }
+
+ return tmp;
+}
diff --git a/bindings/python/module.h b/bindings/python/module.h
new file mode 100644
index 0000000..6a135d5
--- /dev/null
+++ b/bindings/python/module.h
@@ -0,0 +1,57 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/* SPDX-FileCopyrightText: 2022 Bartosz Golaszewski <brgl@bgdev.pl> */
+
+#ifndef __LIBGPIOD_PYTHON_MODULE_H__
+#define __LIBGPIOD_PYTHON_MODULE_H__
+
+#include <gpiod.h>
+#include <Python.h>
+
+PyObject *Py_gpiod_GetModule(void);
+void Py_gpiod_dealloc(PyObject *self);
+PyObject *Py_gpiod_MicrosecondsToTimedelta(unsigned long us);
+unsigned long Py_gpiod_TimedeltaToMicroseconds(PyObject *timedelta);
+unsigned int Py_gpiod_PyLongAsUnsignedInt(PyObject *pylong);
+
+enum {
+ PY_GPIOD_LINE_VALUE = 1,
+ PY_GPIOD_LINE_DIRECTION,
+ PY_GPIOD_LINE_EDGE,
+ PY_GPIOD_LINE_BIAS,
+ PY_GPIOD_LINE_DRIVE,
+ PY_GPIOD_LINE_CLOCK,
+};
+
+PyObject *Py_gpiod_MapLinePropCToPy(int prop, int value);
+int Py_gpiod_MapLinePropPyToC(int prop, PyObject *value);
+
+PyObject *_Py_gpiod_SetErrFromErrno(const char *filename);
+#define Py_gpiod_SetErrFromErrno() _Py_gpiod_SetErrFromErrno(__FILE__)
+
+PyObject *Py_gpiod_MakeChipInfo(struct gpiod_chip_info *info);
+PyObject *Py_gpiod_MakeEdgeEvent(struct gpiod_edge_event *event);
+PyObject *Py_gpiod_MakeInfoEvent(struct gpiod_info_event *event);
+PyObject *Py_gpiod_MakeLineInfo(struct gpiod_line_info *info);
+PyObject *Py_gpiod_MakeLineRequest(struct gpiod_line_request *req);
+
+int Py_gpiod_RegisterChipType(PyObject *module);
+int Py_gpiod_RegisterChipInfoType(PyObject *module);
+int Py_gpiod_RegisterEdgeEventType(PyObject *module);
+int Py_gpiod_RegisterEdgeEventBufferType(PyObject *module);
+int Py_gpiod_RegisterExceptionTypes(PyObject *module);
+int Py_gpiod_RegisterInfoEventType(PyObject *module);
+int Py_gpiod_RegisterLineConfigType(PyObject *module);
+int Py_gpiod_RegisterLineType(PyObject *module);
+int Py_gpiod_RegisterLineInfoType(PyObject *module);
+int Py_gpiod_RegisterLineRequestType(PyObject *module);
+int Py_gpiod_RegisterRequestConfigType(PyObject *module);
+
+void Py_gpiod_SetChipClosedError(void);
+void Py_gpiod_SetRequestReleasedError(void);
+void Py_gpiod_SetBadMappingError(const char *name);
+
+struct gpiod_edge_event_buffer *Py_gpiod_EdgeEventBufferGetData(PyObject *obj);
+struct gpiod_line_config *Py_gpiod_LineConfigGetData(PyObject *obj);
+struct gpiod_request_config *Py_gpiod_RequestConfigGetData(PyObject *obj);
+
+#endif /* __LIBGPIOD_PYTHON_MODULE_H__ */
diff --git a/bindings/python/request-config.c b/bindings/python/request-config.c
new file mode 100644
index 0000000..3e45847
--- /dev/null
+++ b/bindings/python/request-config.c
@@ -0,0 +1,320 @@
+// SPDX-License-Identifier: LGPL-2.1-or-later
+// SPDX-FileCopyrightText: 2022 Bartosz Golaszewski <brgl@bgdev.pl>
+
+#include "module.h"
+#include "enum/enum.h"
+
+typedef struct {
+ PyObject_HEAD;
+ struct gpiod_request_config *cfg;
+} request_config_object;
+
+static int set_offsets(struct gpiod_request_config *cfg, PyObject *offsets_obj)
+{
+ PyObject *iter, *item;
+ unsigned int *offsets;
+ Py_ssize_t len;
+ int i;
+
+ len = PyObject_Size(offsets_obj);
+ if (len < 0)
+ return -1;
+
+ if (len == 0) {
+ gpiod_request_config_set_offsets(cfg, 0, NULL);
+ return 0;
+ }
+
+ offsets = PyMem_Calloc(len, sizeof(unsigned int));
+ if (!offsets) {
+ PyErr_NoMemory();
+ return -1;
+ }
+
+ iter = PyObject_GetIter(offsets_obj);
+ if (!iter) {
+ PyMem_Free(offsets);
+ return -1;
+ }
+
+ for (i = 0;; i++) {
+ item = PyIter_Next(iter);
+ if (!item) {
+ Py_DECREF(iter);
+ break;
+ }
+
+ offsets[i] = PyLong_AsUnsignedLong(item);
+ Py_DECREF(item);
+ if (PyErr_Occurred()) {
+ PyMem_Free(offsets);
+ Py_DECREF(iter);
+ return -1;
+ }
+ }
+
+ gpiod_request_config_set_offsets(cfg, len, offsets);
+ PyMem_Free(offsets);
+
+ return 0;
+}
+
+static int request_config_init(request_config_object *self,
+ PyObject *args, PyObject *kwargs)
+{
+ static char *kwlist[] = {
+ "consumer",
+ "offsets",
+ "event_buffer_size",
+ NULL
+ };
+
+ PyObject *event_buffer_size = NULL, *offsets = NULL;
+ char *consumer = NULL;
+ size_t evbufsiz;
+ int ret;
+
+ ret = PyArg_ParseTupleAndKeywords(args, kwargs, "|$sOO", kwlist,
+ &consumer, &offsets,
+ &event_buffer_size);
+ if (!ret)
+ return -1;
+
+ self->cfg = gpiod_request_config_new();
+ if (!self->cfg) {
+ Py_gpiod_SetErrFromErrno();
+ return -1;
+ }
+
+ if (consumer)
+ gpiod_request_config_set_consumer(self->cfg, consumer);
+
+ if (offsets) {
+ ret = set_offsets(self->cfg, offsets);
+ if (ret)
+ return -1;
+ }
+
+ if (event_buffer_size) {
+ evbufsiz = PyLong_AsSize_t(event_buffer_size);
+ if (PyErr_Occurred())
+ return -1;
+
+ gpiod_request_config_set_event_buffer_size(self->cfg, evbufsiz);
+ }
+
+ return 0;
+}
+
+static void request_config_finalize(request_config_object *self)
+{
+ if (self->cfg)
+ gpiod_request_config_free(self->cfg);
+}
+
+PyDoc_STRVAR(request_config_prop_consumer_doc,
+"Consumer name for the request.");
+
+static PyObject *request_config_get_consumer(request_config_object *self,
+ void *Py_UNUSED(ignored))
+{
+ const char *consumer;
+
+ consumer = gpiod_request_config_get_consumer(self->cfg);
+ if (!consumer)
+ Py_RETURN_NONE;
+
+ return PyUnicode_FromString(consumer);
+}
+
+static int request_config_set_consumer(request_config_object *self,
+ PyObject *val, void *Py_UNUSED(ignored))
+{
+ const char *consumer;
+
+ consumer = PyUnicode_AsUTF8(val);
+ if (!consumer)
+ return -1;
+
+ gpiod_request_config_set_consumer(self->cfg, consumer);
+
+ return 0;
+}
+
+PyDoc_STRVAR(request_config_prop_offsets_doc,
+"Offsets of the lines to be requested.");
+
+static PyObject *request_config_get_offsets(request_config_object *self,
+ void *Py_UNUSED(ignored))
+{
+ unsigned int *offsets, i;
+ PyObject *list, *item;
+ size_t num_offsets;
+ int ret;
+
+ num_offsets = gpiod_request_config_get_num_offsets(self->cfg);
+ if (num_offsets == 0)
+ Py_RETURN_NONE;
+
+ offsets = PyMem_Calloc(num_offsets, sizeof(unsigned int));
+ if (!offsets) {
+ PyErr_NoMemory();
+ return NULL;
+ }
+
+ gpiod_request_config_get_offsets(self->cfg, offsets);
+
+ list = PyList_New(num_offsets);
+ if (!list) {
+ PyMem_Free(offsets);
+ return NULL;
+ }
+
+ for (i = 0; i < num_offsets; i++) {
+ item = PyLong_FromUnsignedLong(offsets[i]);
+ if (!item) {
+ Py_DECREF(list);
+ PyMem_Free(offsets);
+ return NULL;
+ }
+
+ ret = PyList_SetItem(list, i, item);
+ if (ret) {
+ Py_DECREF(item);
+ Py_DECREF(list);
+ PyMem_Free(offsets);
+ return NULL;
+ }
+ }
+
+ PyMem_Free(offsets);
+ return list;
+}
+
+static int request_config_set_offsets(request_config_object *self,
+ PyObject *val, void *Py_UNUSED(ignored))
+{
+ return set_offsets(self->cfg, val);
+}
+
+PyDoc_STRVAR(request_config_prop_event_buffer_size_doc,
+"Size of the kernel event buffer for the request.");
+
+static PyObject *
+request_config_get_event_buffer_size(request_config_object *self,
+ void *Py_UNUSED(ignored))
+{
+ return PyLong_FromSize_t(
+ gpiod_request_config_get_event_buffer_size(self->cfg));
+}
+
+static int request_config_set_event_buffer_size(request_config_object *self,
+ PyObject *val, void *Py_UNUSED(ignored))
+{
+ size_t event_buffer_size;
+
+ event_buffer_size = PyLong_AsSize_t(val);
+ if (PyErr_Occurred())
+ return -1;
+
+ gpiod_request_config_set_event_buffer_size(self->cfg,
+ event_buffer_size);
+
+ return 0;
+}
+
+static PyGetSetDef request_config_getset[] = {
+ {
+ .name = "consumer",
+ .get = (getter)request_config_get_consumer,
+ .set = (setter)request_config_set_consumer,
+ .doc = request_config_prop_consumer_doc,
+ },
+ {
+ .name = "offsets",
+ .get = (getter)request_config_get_offsets,
+ .set = (setter)request_config_set_offsets,
+ .doc = request_config_prop_offsets_doc,
+ },
+ {
+ .name = "event_buffer_size",
+ .get = (getter)request_config_get_event_buffer_size,
+ .set = (setter)request_config_set_event_buffer_size,
+ .doc = request_config_prop_event_buffer_size_doc,
+ },
+ { }
+};
+
+static PyObject *make_str(PyObject *self, const char *fmt)
+{
+ PyObject *consumer, *offsets, *event_buffer_size, *str = NULL;
+
+ consumer = PyObject_GetAttrString(self, "consumer");
+ offsets = PyObject_GetAttrString(self, "offsets");
+ event_buffer_size = PyObject_GetAttrString(self, "event_buffer_size");
+ if (!consumer || !offsets || !event_buffer_size)
+ goto out;
+
+ str = PyUnicode_FromFormat(fmt, consumer, offsets, event_buffer_size);
+
+out:
+ Py_XDECREF(consumer);
+ Py_XDECREF(offsets);
+ Py_XDECREF(event_buffer_size);
+ return str;
+}
+
+static PyObject *request_config_repr(PyObject *self)
+{
+ return make_str(self, "gpiod.RequestConfig(consumer=\"%S\", offsets=%S, event_buffer_size=%S)");
+}
+
+static PyObject *request_config_str(PyObject *self)
+{
+ return make_str(self, "<gpiod.RequestConfig consumer=\"%S\" offsets=%S event_buffer_size=%S>");
+}
+
+PyDoc_STRVAR(request_config_type_doc,
+"Stores a set of options passed to the kernel when making a line request.");
+
+static PyTypeObject request_config_type = {
+ PyVarObject_HEAD_INIT(NULL, 0)
+ .tp_name = "gpiod.RequestConfig",
+ .tp_basicsize = sizeof(request_config_object),
+ .tp_flags = Py_TPFLAGS_DEFAULT,
+ .tp_doc = request_config_type_doc,
+ .tp_new = PyType_GenericNew,
+ .tp_init = (initproc)request_config_init,
+ .tp_finalize = (destructor)request_config_finalize,
+ .tp_dealloc = (destructor)Py_gpiod_dealloc,
+ .tp_getset = request_config_getset,
+ .tp_repr = (reprfunc)request_config_repr,
+ .tp_str = (reprfunc)request_config_str
+};
+
+int Py_gpiod_RegisterRequestConfigType(PyObject *module)
+{
+ return PyModule_AddType(module, &request_config_type);
+}
+
+struct gpiod_request_config *Py_gpiod_RequestConfigGetData(PyObject *obj)
+{
+ request_config_object *reqcfg;
+ PyObject *type;
+
+ type = PyObject_Type(obj);
+ if (!type)
+ return NULL;
+
+ if ((PyTypeObject *)type != &request_config_type) {
+ PyErr_SetString(PyExc_TypeError,
+ "not a gpiod.RequestConfig object");
+ Py_DECREF(type);
+ return NULL;
+ }
+ Py_DECREF(type);
+
+ reqcfg = (request_config_object *)obj;
+
+ return reqcfg->cfg;
+}
diff --git a/configure.ac b/configure.ac
index ab03673..7a794e2 100644
--- a/configure.ac
+++ b/configure.ac
@@ -198,7 +198,7 @@ AM_CONDITIONAL([WITH_BINDINGS_PYTHON], [test "x$with_bindings_python" = xtrue])
if test "x$with_bindings_python" = xtrue
then
- AM_PATH_PYTHON([3.0], [],
+ AM_PATH_PYTHON([3.9], [],
[AC_MSG_ERROR([python3 not found - needed for python bindings])])
AC_CHECK_PROG([has_python_config], [python3-config], [true], [false])
if test "x$has_python_config" = xfalse
@@ -243,6 +243,7 @@ AC_CONFIG_FILES([Makefile
bindings/cxx/examples/Makefile
bindings/cxx/tests/Makefile
bindings/python/Makefile
+ bindings/python/enum/Makefile
bindings/python/examples/Makefile
bindings/python/tests/Makefile
man/Makefile])
--
2.34.1
prev parent reply other threads:[~2022-05-25 14:07 UTC|newest]
Thread overview: 18+ messages / expand[flat|nested] mbox.gz Atom feed top
2022-05-25 14:06 [libgpiod v2][PATCH 0/5] bindings: implement python bindings for libgpiod v2 Bartosz Golaszewski
2022-05-25 14:07 ` [libgpiod v2][PATCH 1/5] bindings: python: remove old version Bartosz Golaszewski
2022-05-25 14:07 ` [libgpiod v2][PATCH 2/5] bindings: python: enum: add a piece of common code for using python's enums from C Bartosz Golaszewski
2022-05-25 14:07 ` [libgpiod v2][PATCH 3/5] bindings: python: add examples for v2 API Bartosz Golaszewski
2022-06-03 12:46 ` Kent Gibson
2022-06-04 2:41 ` Kent Gibson
2022-06-06 10:14 ` Andy Shevchenko
2022-06-07 1:52 ` Kent Gibson
2022-06-07 10:43 ` Andy Shevchenko
2022-06-08 15:39 ` Bartosz Golaszewski
2022-06-09 4:49 ` Kent Gibson
2022-06-09 8:42 ` Bartosz Golaszewski
2022-06-09 13:21 ` Jiri Benc
2022-06-09 16:06 ` Bartosz Golaszewski
2022-06-10 4:23 ` Kent Gibson
2022-06-10 6:57 ` Bartosz Golaszewski
2022-05-25 14:07 ` [libgpiod v2][PATCH 4/5] bindings: python: add tests " Bartosz Golaszewski
2022-05-25 14:07 ` Bartosz Golaszewski [this message]
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20220525140704.94983-6-brgl@bgdev.pl \
--to=brgl@bgdev.pl \
--cc=andriy.shevchenko@linux.intel.com \
--cc=darrien@freenet.de \
--cc=jbenc@upir.cz \
--cc=joelsavitz@gmail.com \
--cc=linus.walleij@linaro.org \
--cc=linux-gpio@vger.kernel.org \
--cc=viresh.kumar@linaro.org \
--cc=warthog618@gmail.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.