bindings: implement python bindings for libgpiod v2
v4 cover bindings: implement python bindings for libgpiod v2
This is the fourth iteration of python bindings for libgpiod v2.
The code has been split into high-level python and low-level C layers with the latter only doing the bare minimum.
The data model is mostly based on the C++ one with the main difference being utilizing dynamic typing and keyword arguments in place of the builder pattern. That allows us to reduce the number of methods and objects.
Because python doesn't have RAII, unlike C++, we provide a module-level request_lines() helper as gpiod.Chip(path).request_lines(...) one-liner could lead to the chip left dangling even after the last reference is dropped.
Because python forces us to dynamically allocate objects all the time even for fundamental types (which are also immutable) there's no point in trying to replicate the edge-event buffer. Instead LineRequest.read_edge_event() just returns a list of events.
v3 -> v4:
- use list and dict comprehensions where applicable
- add a helper for detecting GPIO devices in examples
- remove commented out code
- fix whitespace errors
- cache chip_info in the chip object
- rename Chip.map_line() to Chip.line_name_from_id()
- disallow repeating line offsets/names as well as offset-name conflicts in request configs
- don't modify python objects with GIL released (self->chip assignment)
- fix type conversion strings
- fix error check in request_offsets()
- fix type comparison warnings
Bartosz Golaszewski (4):
bindings: python: remove old version
bindings: python: add examples
bindings: python: add tests
bindings: python: implement python bindings for libgpiod v2
bindings/python/.gitignore | 8 + bindings/python/Makefile.am | 26 +- bindings/python/examples/Makefile.am | 16 +- bindings/python/examples/gpiodetect.py | 17 +- bindings/python/examples/gpiofind.py | 14 +- bindings/python/examples/gpioget.py | 32 +- bindings/python/examples/gpioinfo.py | 38 +- bindings/python/examples/gpiomon.py | 50 +- bindings/python/examples/gpioset.py | 45 +- bindings/python/examples/helpers.py | 15 + bindings/python/gpiod/Makefile.am | 17 + bindings/python/gpiod/__init__.py | 53 + bindings/python/gpiod/chip.py | 329 +++ bindings/python/gpiod/chip_info.py | 21 + bindings/python/gpiod/edge_event.py | 46 + bindings/python/gpiod/exception.py | 20 + bindings/python/gpiod/ext/Makefile.am | 11 + bindings/python/gpiod/ext/chip.c | 339 +++ bindings/python/gpiod/ext/common.c | 92 + bindings/python/gpiod/ext/internal.h | 21 + bindings/python/gpiod/ext/line-config.c | 133 + bindings/python/gpiod/ext/line-settings.c | 130 + bindings/python/gpiod/ext/module.c | 193 ++ bindings/python/gpiod/ext/request.c | 402 +++ bindings/python/gpiod/info_event.py | 33 + bindings/python/gpiod/internal.py | 19 + bindings/python/gpiod/line.py | 56 + bindings/python/gpiod/line_info.py | 73 + bindings/python/gpiod/line_request.py | 247 ++ bindings/python/gpiod/line_settings.py | 62 + bindings/python/gpiodmodule.c | 2662 ------------------ bindings/python/setup.py | 47 + bindings/python/tests/Makefile.am | 26 +- bindings/python/tests/__init__.py | 17 + bindings/python/tests/__main__.py | 16 + bindings/python/tests/gpiod_py_test.py | 832 ------ bindings/python/tests/gpiomockupmodule.c | 309 -- bindings/python/tests/gpiosim/Makefile.am | 7 + bindings/python/tests/gpiosim/__init__.py | 4 + bindings/python/tests/gpiosim/chip.py | 65 + bindings/python/tests/gpiosim/ext.c | 345 +++ bindings/python/tests/helpers.py | 16 + bindings/python/tests/tests_chip.py | 231 ++ bindings/python/tests/tests_chip_info.py | 52 + bindings/python/tests/tests_edge_event.py | 212 ++ bindings/python/tests/tests_info_event.py | 189 ++ bindings/python/tests/tests_line_info.py | 101 + bindings/python/tests/tests_line_request.py | 485 ++++ bindings/python/tests/tests_line_settings.py | 79 + bindings/python/tests/tests_module.py | 59 + configure.ac | 3 + 51 files changed, 4386 insertions(+), 3929 deletions(-) create mode 100644 bindings/python/.gitignore create mode 100644 bindings/python/examples/helpers.py create mode 100644 bindings/python/gpiod/Makefile.am create mode 100644 bindings/python/gpiod/__init__.py create mode 100644 bindings/python/gpiod/chip.py create mode 100644 bindings/python/gpiod/chip_info.py create mode 100644 bindings/python/gpiod/edge_event.py create mode 100644 bindings/python/gpiod/exception.py create mode 100644 bindings/python/gpiod/ext/Makefile.am create mode 100644 bindings/python/gpiod/ext/chip.c create mode 100644 bindings/python/gpiod/ext/common.c create mode 100644 bindings/python/gpiod/ext/internal.h create mode 100644 bindings/python/gpiod/ext/line-config.c create mode 100644 bindings/python/gpiod/ext/line-settings.c create mode 100644 bindings/python/gpiod/ext/module.c create mode 100644 bindings/python/gpiod/ext/request.c create mode 100644 bindings/python/gpiod/info_event.py create mode 100644 bindings/python/gpiod/internal.py create mode 100644 bindings/python/gpiod/line.py create mode 100644 bindings/python/gpiod/line_info.py create mode 100644 bindings/python/gpiod/line_request.py create mode 100644 bindings/python/gpiod/line_settings.py delete mode 100644 bindings/python/gpiodmodule.c create mode 100644 bindings/python/setup.py create mode 100644 bindings/python/tests/__init__.py create mode 100644 bindings/python/tests/__main__.py delete mode 100755 bindings/python/tests/gpiod_py_test.py delete mode 100644 bindings/python/tests/gpiomockupmodule.c create mode 100644 bindings/python/tests/gpiosim/Makefile.am create mode 100644 bindings/python/tests/gpiosim/__init__.py create mode 100644 bindings/python/tests/gpiosim/chip.py create mode 100644 bindings/python/tests/gpiosim/ext.c create mode 100644 bindings/python/tests/helpers.py create mode 100644 bindings/python/tests/tests_chip.py create mode 100644 bindings/python/tests/tests_chip_info.py create mode 100644 bindings/python/tests/tests_edge_event.py create mode 100644 bindings/python/tests/tests_info_event.py create mode 100644 bindings/python/tests/tests_line_info.py create mode 100644 bindings/python/tests/tests_line_request.py create mode 100644 bindings/python/tests/tests_line_settings.py create mode 100644 bindings/python/tests/tests_module.py
Signature · 1 line
2.34.1
PATCH v4 1/4 bindings: python: remove old version
This removes v1 python bindings for easier review of v2.
--- bindings/python/Makefile.am | 25 - bindings/python/examples/Makefile.am | 10 - bindings/python/examples/gpiodetect.py | 16 - bindings/python/examples/gpiofind.py | 20 - bindings/python/examples/gpioget.py | 25 - bindings/python/examples/gpioinfo.py | 28 - bindings/python/examples/gpiomon.py | 42 - bindings/python/examples/gpioset.py | 25 - bindings/python/gpiodmodule.c | 2662 ---------------------- bindings/python/tests/Makefile.am | 13 - bindings/python/tests/gpiod_py_test.py | 832 ------- bindings/python/tests/gpiomockupmodule.c | 309 --- 12 files changed, 4007 deletions(-) delete mode 100644 bindings/python/Makefile.am delete mode 100644 bindings/python/examples/Makefile.am delete mode 100755 bindings/python/examples/gpiodetect.py delete mode 100755 bindings/python/examples/gpiofind.py delete mode 100755 bindings/python/examples/gpioget.py delete mode 100755 bindings/python/examples/gpioinfo.py delete mode 100755 bindings/python/examples/gpiomon.py delete mode 100755 bindings/python/examples/gpioset.py delete mode 100644 bindings/python/gpiodmodule.c delete mode 100644 bindings/python/tests/Makefile.am delete mode 100755 bindings/python/tests/gpiod_py_test.py delete mode 100644 bindings/python/tests/gpiomockupmodule.c
bindings/python/Makefile.am deleted +0 -25
@@ -1,25 +0,0 @@ -# SPDX-License-Identifier: GPL-2.0-or-later -# SPDX-FileCopyrightText: 2017-2021 Bartosz Golaszewski <[email protected]> - -pyexec_LTLIBRARIES = gpiod.la - -gpiod_la_SOURCES = gpiodmodule.c - -gpiod_la_CFLAGS = -I$(top_srcdir)/include/ -gpiod_la_CFLAGS += -Wall -Wextra -g -std=gnu89 $(PYTHON_CPPFLAGS) -gpiod_la_LDFLAGS = -module -avoid-version -gpiod_la_LIBADD = $(top_builddir)/lib/libgpiod.la $(PYTHON_LIBS) - -SUBDIRS = . - -if WITH_TESTS - -SUBDIRS += tests - -endif - -if WITH_EXAMPLES - -SUBDIRS += examples - -endif
bindings/python/examples/Makefile.am deleted +0 -10
@@ -1,10 +0,0 @@ -# SPDX-License-Identifier: GPL-2.0-or-later -# SPDX-FileCopyrightText: 2017-2021 Bartosz Golaszewski <[email protected]> - -EXTRA_DIST = \ - gpiodetect.py \ - gpiofind.py \ - gpioget.py \ - gpioinfo.py \ - gpiomon.py \ - gpioset.py
bindings/python/examples/gpiodetect.py deleted +0 -16
@@ -1,16 +0,0 @@ -#!/usr/bin/env python3 -# SPDX-License-Identifier: GPL-2.0-or-later -# SPDX-FileCopyrightText: 2017-2021 Bartosz Golaszewski <[email protected]> - -'''Reimplementation of the gpiodetect tool in Python.''' - -import gpiod -import os - -if __name__ == '__main__': - for entry in os.scandir('/dev/'): - if gpiod.is_gpiochip_device(entry.path): - with gpiod.Chip(entry.path) as chip: - print('{} [{}] ({} lines)'.format(chip.name(), - chip.label(), - chip.num_lines()))
bindings/python/examples/gpiofind.py deleted +0 -20
@@ -1,20 +0,0 @@ -#!/usr/bin/env python3 -# SPDX-License-Identifier: GPL-2.0-or-later -# SPDX-FileCopyrightText: 2017-2021 Bartosz Golaszewski <[email protected]> - -'''Reimplementation of the gpiofind tool in Python.''' - -import gpiod -import os -import sys - -if __name__ == '__main__': - for entry in os.scandir('/dev/'): - if gpiod.is_gpiochip_device(entry.path): - with gpiod.Chip(entry.path) as chip: - offset = chip.find_line(sys.argv[1], unique=True) - if offset is not None: - print('{} {}'.format(line.owner().name(), offset)) - sys.exit(0) - - sys.exit(1)
bindings/python/examples/gpioget.py deleted +0 -25
@@ -1,25 +0,0 @@ -#!/usr/bin/env python3 -# SPDX-License-Identifier: GPL-2.0-or-later -# SPDX-FileCopyrightText: 2017-2021 Bartosz Golaszewski <[email protected]> - -'''Simplified reimplementation of the gpioget tool in Python.''' - -import gpiod -import sys - -if __name__ == '__main__': - if len(sys.argv) < 3: - raise TypeError('usage: gpioget.py <gpiochip> <offset1> <offset2> ...') - - with gpiod.Chip(sys.argv[1]) as chip: - offsets = [] - for off in sys.argv[2:]: - offsets.append(int(off)) - - lines = chip.get_lines(offsets) - lines.request(consumer=sys.argv[0], type=gpiod.LINE_REQ_DIR_IN) - vals = lines.get_values() - - for val in vals: - print(val, end=' ') - print()
bindings/python/examples/gpioinfo.py deleted +0 -28
@@ -1,28 +0,0 @@ -#!/usr/bin/env python3 -# SPDX-License-Identifier: GPL-2.0-or-later -# SPDX-FileCopyrightText: 2017-2021 Bartosz Golaszewski <[email protected]> - -'''Simplified reimplementation of the gpioinfo tool in Python.''' - -import gpiod -import os - -if __name__ == '__main__': - for entry in os.scandir('/dev/'): - if gpiod.is_gpiochip_device(entry.path): - with gpiod.Chip(entry.path) as chip: - print('{} - {} lines:'.format(chip.name(), chip.num_lines())) - - for line in gpiod.LineIter(chip): - offset = line.offset() - name = line.name() - consumer = line.consumer() - direction = line.direction() - active_low = line.is_active_low() - - print('\tline {:>3}: {:>18} {:>12} {:>8} {:>10}'.format( - offset, - 'unnamed' if name is None else name, - 'unused' if consumer is None else consumer, - 'input' if direction == gpiod.Line.DIRECTION_INPUT else 'output', - 'active-low' if active_low else 'active-high'))
bindings/python/examples/gpiomon.py deleted +0 -42
42 lines not on this page · raw message
bindings/python/examples/gpioset.py deleted +0 -25
@@ -1,25 +0,0 @@ -#!/usr/bin/env python3 -# SPDX-License-Identifier: GPL-2.0-or-later -# SPDX-FileCopyrightText: 2017-2021 Bartosz Golaszewski <[email protected]> - -'''Simplified reimplementation of the gpioset tool in Python.''' - -import gpiod -import sys - -if __name__ == '__main__': - if len(sys.argv) < 3: - raise TypeError('usage: gpioset.py <gpiochip> <offset1>=<value1> ...') - - with gpiod.Chip(sys.argv[1]) as chip: - offsets = [] - values = [] - for arg in sys.argv[2:]: - arg = arg.split('=') - offsets.append(int(arg[0])) - values.append(int(arg[1])) - - lines = chip.get_lines(offsets) - lines.request(consumer=sys.argv[0], type=gpiod.LINE_REQ_DIR_OUT) - lines.set_values(values) - input()
bindings/python/gpiodmodule.c deleted +0 -2662
2,662 lines not on this page · raw message
bindings/python/tests/Makefile.am deleted +0 -13
@@ -1,13 +0,0 @@ -# SPDX-License-Identifier: GPL-2.0-or-later -# SPDX-FileCopyrightText: 2017-2021 Bartosz Golaszewski <[email protected]> - -dist_bin_SCRIPTS = gpiod_py_test.py - -pyexec_LTLIBRARIES = gpiomockup.la - -gpiomockup_la_SOURCES = gpiomockupmodule.c -gpiomockup_la_CFLAGS = -I$(top_srcdir)/tests/mockup/ -gpiomockup_la_CFLAGS += -Wall -Wextra -g -std=gnu89 $(PYTHON_CPPFLAGS) -gpiomockup_la_LDFLAGS = -module -avoid-version -gpiomockup_la_LIBADD = $(top_builddir)/tests/mockup/libgpiomockup.la -gpiomockup_la_LIBADD += $(PYTHON_LIBS)
bindings/python/tests/gpiod_py_test.py deleted +0 -832
134 lines not on this page · raw message
bindings/python/tests/gpiomockupmodule.c deleted +0 -309
309 lines not on this page · raw message
The rest of this diff is not shown. View the raw message.
Signature · 1 line
2.34.1
Part of the diff is not shown here. View the raw message.
PATCH v4 2/4 bindings: python: add examples
In reply to Bartosz GolaszewskiThis adds the regular set of example programs implemented using libgpiod python bindings.
--- bindings/python/examples/Makefile.am | 10 +++++++ bindings/python/examples/gpiodetect.py | 15 +++++++++++ bindings/python/examples/gpiofind.py | 20 ++++++++++++++ bindings/python/examples/gpioget.py | 29 +++++++++++++++++++++ bindings/python/examples/gpioinfo.py | 28 ++++++++++++++++++++ bindings/python/examples/gpiomon.py | 26 +++++++++++++++++++ bindings/python/examples/gpioset.py | 36 ++++++++++++++++++++++++++ bindings/python/examples/helpers.py | 15 +++++++++++ 8 files changed, 179 insertions(+) create mode 100644 bindings/python/examples/Makefile.am create mode 100755 bindings/python/examples/gpiodetect.py create mode 100755 bindings/python/examples/gpiofind.py create mode 100755 bindings/python/examples/gpioget.py create mode 100755 bindings/python/examples/gpioinfo.py create mode 100755 bindings/python/examples/gpiomon.py create mode 100755 bindings/python/examples/gpioset.py create mode 100644 bindings/python/examples/helpers.py
bindings/python/examples/Makefile.am new file +10 -0
@@ -0,0 +1,10 @@ +# SPDX-License-Identifier: GPL-2.0-or-later +# SPDX-FileCopyrightText: 2022 Bartosz Golaszewski <[email protected]> + +EXTRA_DIST = \ + gpiodetect.py \ + gpiofind.py \ + gpioget.py \ + gpioinfo.py \ + gpiomon.py \ + gpioset.py
bindings/python/examples/gpiodetect.py new file +15 -0
@@ -0,0 +1,15 @@ +#!/usr/bin/env python3 +# SPDX-License-Identifier: GPL-2.0-or-later +# SPDX-FileCopyrightText: 2022 Bartosz Golaszewski <[email protected]> + +"""Reimplementation of the gpiodetect tool in Python.""" + +import gpiod +import os + +from helpers import gpio_chips + +if __name__ == "__main__": + for chip in gpio_chips(): + info = chip.get_info() + print("{} [{}] ({} lines)".format(info.name, info.label, info.num_lines))
bindings/python/examples/gpiofind.py new file +20 -0
@@ -0,0 +1,20 @@ +#!/usr/bin/env python3 +# SPDX-License-Identifier: GPL-2.0-or-later +# SPDX-FileCopyrightText: 2022 Bartosz Golaszewski <[email protected]> + +"""Reimplementation of the gpiofind tool in Python.""" + +import gpiod +import os +import sys + +if __name__ == "__main__": + for entry in os.scandir("/dev/"): + if gpiod.is_gpiochip_device(entry.path): + with gpiod.Chip(entry.path) as chip: + offset = chip.line_offset_from_id(sys.argv[1]) + if offset is not None: + print("{} {}".format(chip.get_info().name, offset)) + sys.exit(0) + + sys.exit(1)
bindings/python/examples/gpioget.py new file +29 -0
@@ -0,0 +1,29 @@ +#!/usr/bin/env python3 +# SPDX-License-Identifier: GPL-2.0-or-later +# SPDX-FileCopyrightText: 2022 Bartosz Golaszewski <[email protected]> + +"""Simplified reimplementation of the gpioget tool in Python.""" + +import gpiod +import sys + +from gpiod.line import Direction + +if __name__ == "__main__": + if len(sys.argv) < 3: + raise TypeError("usage: gpioget.py <gpiochip> <offset1> <offset2> ...") + + path = sys.argv[1] + lines = [int(line) if line.isdigit() else line for line in sys.argv[2:]] + + request = gpiod.request_lines( + path, + consumer="gpioget.py", + config={tuple(lines): gpiod.LineSettings(direction=Direction.INPUT)}, + ) + + vals = request.get_values() + + for val in vals: + print("{} ".format(val.value), end="") + print()
bindings/python/examples/gpioinfo.py new file +28 -0
@@ -0,0 +1,28 @@ +#!/usr/bin/env python3 +# SPDX-License-Identifier: GPL-2.0-or-later +# SPDX-FileCopyrightText: 2022 Bartosz Golaszewski <[email protected]> + +"""Simplified reimplementation of the gpioinfo tool in Python.""" + +import gpiod +import os + +from helpers import gpio_chips + +if __name__ == "__main__": + for chip in gpio_chips(): + cinfo = chip.get_info() + print("{} - {} lines:".format(cinfo.name, cinfo.num_lines)) + + for offset in range(0, cinfo.num_lines): + linfo = chip.get_line_info(offset) + is_input = linfo.direction == gpiod.line.Direction.INPUT + print( + "\tline {:>3}: {:>18} {:>12} {:>8} {:>10}".format( + linfo.offset, + linfo.name or "unnamed", + linfo.consumer or "unused", + "input" if is_input else "output", + "active-low" if linfo.active_low else "active-high", + ) + )
bindings/python/examples/gpiomon.py new file +26 -0
@@ -0,0 +1,26 @@ +#!/usr/bin/env python3 +# SPDX-License-Identifier: GPL-2.0-or-later +# SPDX-FileCopyrightText: 2022 Bartosz Golaszewski <[email protected]> + +"""Simplified reimplementation of the gpiomon tool in Python.""" + +import gpiod +import sys + +from gpiod.line import Edge + +if __name__ == "__main__": + if len(sys.argv) < 3: + raise TypeError("usage: gpiomon.py <gpiochip> <offset1> <offset2> ...") + + path = sys.argv[1] + lines = [int(line) if line.isdigit() else line for line in sys.argv[2:]] + + with gpiod.request_lines( + path, + consumer="gpiomon.py", + config={tuple(lines): gpiod.LineSettings(edge_detection=Edge.BOTH)}, + ) as request: + while True: + for event in request.read_edge_event(): + print(event)
bindings/python/examples/gpioset.py new file +36 -0
@@ -0,0 +1,36 @@ +#!/usr/bin/env python3 +# SPDX-License-Identifier: GPL-2.0-or-later +# SPDX-FileCopyrightText: 2022 Bartosz Golaszewski <[email protected]> + +"""Simplified reimplementation of the gpioset tool in Python.""" + +import gpiod +import sys + +from gpiod.line import Direction, Value + +if __name__ == "__main__": + if len(sys.argv) < 3: + raise TypeError( + "usage: gpioset.py <gpiochip> <offset1>=<value1> <offset2>=<value2> ..." + ) + + path = sys.argv[1] + + def parse_value(arg): + x, y = arg.split("=") + return (x, Value(int(y))) + + lvs = [parse_value(arg) for arg in sys.argv[2:]] + lines = [x[0] for x in lvs] + values = dict(lvs) + + request = gpiod.request_lines( + path, + consumer="gpioset.py", + config={tuple(lines): gpiod.LineSettings(direction=Direction.OUTPUT)}, + ) + + vals = request.set_values(values) + + input()
bindings/python/examples/helpers.py new file +15 -0
@@ -0,0 +1,15 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later +# SPDX-FileCopyrightText: 2022 Bartosz Golaszewski <[email protected]> + +import gpiod +import os + + +def gpio_chips(): + for path in [ + entry.path + for entry in os.scandir("/dev/") + if gpiod.is_gpiochip_device(entry.path) + ]: + with gpiod.Chip(path) as chip: + yield chip
Signature · 1 line
2.34.1
Re: [libgpiod v2][PATCH v4 2/4] bindings: python: add examples
Quoted 2 lines · Bartosz Golaszewski
This adds the regular set of example programs implemented using libgpiod python bindings.
...
bindings/python/examples/gpiodetect.py line 15
Quoted 4 lines
bindings/python/examples/gpiodetect.py new file +4
-0+if __name__ == "__main__": + for chip in gpio_chips(): + info = chip.get_info() + print("{} [{}] ({} lines)".format(info.name, info.label, info.num_lines))
In all of them I would prefer to see the main() explicitly, like
def main():
...
if __name__ == "__main__":
main()
(In this case the module can be imported by another one and main be reused)
Also have you considered use of SystemExit() wrapper?
...
bindings/python/examples/gpiofind.py line 20
Quoted 3 lines
bindings/python/examples/gpiofind.py new file +3
-0+ sys.exit(0) + + sys.exit(1)
Is it in the original C code?!
I would expect that no chips -- no error.
...
bindings/python/examples/gpioget.py line 14
Quoted 3 lines
bindings/python/examples/gpioget.py new file +3
-0+if __name__ == "__main__": + if len(sys.argv) < 3: + raise TypeError("usage: gpioget.py <gpiochip> <offset1> <offset2> ...")
SystemExit(main(sys.argv)) ?
Quoted 5 lines
bindings/python/examples/gpioget.py new file +5
-0+ path = sys.argv[1] + lines = [int(line) if line.isdigit() else line for line in sys.argv[2:]] + + request = gpiod.request_lines( + path,
bindings/python/examples/gpioget.py line 29
Quoted 10 lines
bindings/python/examples/gpioget.py new file +9
-0+ consumer="gpioget.py", + config={tuple(lines): gpiod.LineSettings(direction=Direction.INPUT)}, + ) + + vals = request.get_values() + + for val in vals: + print("{} ".format(val.value), end="") + print()
Without any conditional it will print an empty line, was it originally in the C variant?
Signature · 2 lines
With Best Regards, Andy Shevchenko
Re: [libgpiod v2][PATCH v4 2/4] bindings: python: add examples
Quoted 55 lines · Andy Shevchenko
Quoted 2 lines · Bartosz Golaszewski
This adds the regular set of example programs implemented using libgpiod python bindings.
...
bindings/python/examples/gpiodetect.py line 15
Quoted 4 lines
bindings/python/examples/gpiodetect.py new file +4
-0+if __name__ == "__main__": + for chip in gpio_chips(): + info = chip.get_info() + print("{} [{}] ({} lines)".format(info.name, info.label, info.num_lines))In all of them I would prefer to see the
main()explicitly, likedef
main():...if
__name__== "__main__":main()(In this case the module can be imported by another one and main be reused)
Also have you considered use of
SystemExit()wrapper?...
bindings/python/examples/gpiofind.py line 20
Quoted 3 lines
bindings/python/examples/gpiofind.py new file +3
-0+ sys.exit(0) + + sys.exit(1)Is it in the original C code?!
I would expect that no chips -- no error....
bindings/python/examples/gpioget.py line 14
Quoted 3 lines
bindings/python/examples/gpioget.py new file +3
-0+if __name__ == "__main__": + if len(sys.argv) < 3: + raise TypeError("usage: gpioget.py <gpiochip> <offset1> <offset2> ...")SystemExit(main(sys.argv)) ?
Quoted 13 lines
bindings/python/examples/gpioget.py new file +13
-0+ path = sys.argv[1] + lines = [int(line) if line.isdigit() else line for line in sys.argv[2:]] + + request = gpiod.request_lines( + path, + consumer="gpioget.py", + config={tuple(lines): gpiod.LineSettings(direction=Direction.INPUT)}, + ) + + vals = request.get_values() + + for val in vals: + print("{} ".format(val.value), end="")
Quoted 10 lines
Quoted 1 line
+
print()Without any conditional it will print an empty line, was it originally in the C variant?
--
With Best Regards,
Andy Shevchenko
Thanks Andy but this is unnecessary churn, these are literally just code samples. Unless some new issues pop up for the other patches, I'll leave it like that and apply it to master. Then we can work on it further in there.
Bart
PATCH v4 3/4 bindings: python: add tests
In reply to Bartosz GolaszewskiThis adds a test-suite for python bindings based on the gpio-sim kernel module.
--- bindings/python/tests/Makefile.am | 17 + bindings/python/tests/__init__.py | 17 + bindings/python/tests/__main__.py | 16 + bindings/python/tests/gpiosim/Makefile.am | 7 + bindings/python/tests/gpiosim/__init__.py | 4 + bindings/python/tests/gpiosim/chip.py | 65 +++ bindings/python/tests/gpiosim/ext.c | 345 +++++++++++++ bindings/python/tests/helpers.py | 16 + bindings/python/tests/tests_chip.py | 231 +++++++++ bindings/python/tests/tests_chip_info.py | 52 ++ bindings/python/tests/tests_edge_event.py | 212 ++++++++ bindings/python/tests/tests_info_event.py | 189 ++++++++ bindings/python/tests/tests_line_info.py | 101 ++++ bindings/python/tests/tests_line_request.py | 485 +++++++++++++++++++ bindings/python/tests/tests_line_settings.py | 79 +++ bindings/python/tests/tests_module.py | 59 +++ 16 files changed, 1895 insertions(+) create mode 100644 bindings/python/tests/Makefile.am create mode 100644 bindings/python/tests/__init__.py create mode 100644 bindings/python/tests/__main__.py create mode 100644 bindings/python/tests/gpiosim/Makefile.am create mode 100644 bindings/python/tests/gpiosim/__init__.py create mode 100644 bindings/python/tests/gpiosim/chip.py create mode 100644 bindings/python/tests/gpiosim/ext.c create mode 100644 bindings/python/tests/helpers.py create mode 100644 bindings/python/tests/tests_chip.py create mode 100644 bindings/python/tests/tests_chip_info.py create mode 100644 bindings/python/tests/tests_edge_event.py create mode 100644 bindings/python/tests/tests_info_event.py create mode 100644 bindings/python/tests/tests_line_info.py create mode 100644 bindings/python/tests/tests_line_request.py create mode 100644 bindings/python/tests/tests_line_settings.py create mode 100644 bindings/python/tests/tests_module.py
bindings/python/tests/Makefile.am new file +17 -0
@@ -0,0 +1,17 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later +# SPDX-FileCopyrightText: 2022 Bartosz Golaszewski <[email protected]> + +SUBDIRS = gpiosim + +EXTRA_DIST = \ + helpers.py \ + __init__.py \ + __main__.py \ + tests_chip_info.py \ + tests_chip.py \ + tests_edge_event.py \ + tests_info_event.py \ + tests_line_info.py \ + tests_line_request.py \ + tests_line_settings.py \ + tests_module.py
bindings/python/tests/__init__.py new file +17 -0
@@ -0,0 +1,17 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later +# SPDX-FileCopyrightText: 2022 Bartosz Golaszewski <[email protected]> + +import os +import unittest + +from packaging import version + +required_kernel_version = "5.19.0" +current_version = os.uname().release.split("-")[0] + +if version.parse(current_version) < version.parse(required_kernel_version): + raise NotImplementedError( + "linux kernel version must be at least {} - got {}".format( + required_kernel_version, current_version + ) + )
bindings/python/tests/__main__.py new file +16 -0
@@ -0,0 +1,16 @@ +#!/usr/bin/python3 +# SPDX-License-Identifier: GPL-2.0-or-later +# SPDX-FileCopyrightText: 2022 Bartosz Golaszewski <[email protected]> + +import unittest + +from .tests_chip import * +from .tests_chip_info import * +from .tests_edge_event import * +from .tests_info_event import * +from .tests_line_info import * +from .tests_line_settings import * +from .tests_module import * +from .tests_line_request import * + +unittest.main()
bindings/python/tests/gpiosim/Makefile.am new file +7 -0
@@ -0,0 +1,7 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later +# SPDX-FileCopyrightText: 2022 Bartosz Golaszewski <[email protected]> + +EXTRA_DIST = \ + chip.py \ + ext.c \ + __init__.py
bindings/python/tests/gpiosim/__init__.py new file +4 -0
@@ -0,0 +1,4 @@ +# SPDX-License-Identifier: GPL-2.0-or-later +# SPDX-FileCopyrightText: 2022 Bartosz Golaszewski <[email protected]> + +from .chip import Chip
bindings/python/tests/gpiosim/chip.py new file +65 -0
65 lines not on this page · raw message
bindings/python/tests/gpiosim/ext.c new file +345 -0
345 lines not on this page · raw message
bindings/python/tests/helpers.py new file +16 -0
@@ -0,0 +1,16 @@ +# SPDX-License-Identifier: GPL-2.0-or-later +# SPDX-FileCopyrightText: 2022 Bartosz Golaszewski <[email protected]> + +import os + + +class LinkGuard: + def __init__(self, src, dst): + self.src = src + self.dst = dst + + def __enter__(self): + os.symlink(self.src, self.dst) + + def __exit__(self, type, val, tb): + os.unlink(self.dst)
bindings/python/tests/tests_chip.py new file +231 -0
231 lines not on this page · raw message
bindings/python/tests/tests_chip_info.py new file +52 -0
52 lines not on this page · raw message
bindings/python/tests/tests_edge_event.py new file +212 -0
212 lines not on this page · raw message
bindings/python/tests/tests_info_event.py new file +189 -0
189 lines not on this page · raw message
bindings/python/tests/tests_line_info.py new file +101 -0
101 lines not on this page · raw message
bindings/python/tests/tests_line_request.py new file +485 -0
485 lines not on this page · raw message
bindings/python/tests/tests_line_settings.py new file +79 -0
79 lines not on this page · raw message
bindings/python/tests/tests_module.py new file +59 -0
59 lines not on this page · raw message
Signature · 1 line
2.34.1
PATCH v4 4/4 bindings: python: implement python bindings for libgpiod v2
In reply to Bartosz GolaszewskiThis adds python bindings for libgpiod v2. As opposed to v1, they are mostly written in python with just low-level elements written in C and interfacing with libgpiod.so.
We've also added setup.py which will allow to use pip for managing the bindings and split them into a separate meta-openembedded recipe.
--- bindings/python/.gitignore | 8 + bindings/python/Makefile.am | 35 ++ bindings/python/gpiod/Makefile.am | 17 + bindings/python/gpiod/__init__.py | 53 +++ bindings/python/gpiod/chip.py | 329 ++++++++++++++++++ bindings/python/gpiod/chip_info.py | 21 ++ bindings/python/gpiod/edge_event.py | 46 +++ bindings/python/gpiod/exception.py | 20 ++ bindings/python/gpiod/ext/Makefile.am | 11 + bindings/python/gpiod/ext/chip.c | 339 ++++++++++++++++++ bindings/python/gpiod/ext/common.c | 92 +++++ bindings/python/gpiod/ext/internal.h | 21 ++ bindings/python/gpiod/ext/line-config.c | 133 +++++++ bindings/python/gpiod/ext/line-settings.c | 130 +++++++ bindings/python/gpiod/ext/module.c | 193 +++++++++++ bindings/python/gpiod/ext/request.c | 402 ++++++++++++++++++++++ bindings/python/gpiod/info_event.py | 33 ++ bindings/python/gpiod/internal.py | 19 + bindings/python/gpiod/line.py | 56 +++ bindings/python/gpiod/line_info.py | 73 ++++ bindings/python/gpiod/line_request.py | 247 +++++++++++++ bindings/python/gpiod/line_settings.py | 62 ++++ bindings/python/setup.py | 47 +++ configure.ac | 3 + 24 files changed, 2390 insertions(+) create mode 100644 bindings/python/.gitignore create mode 100644 bindings/python/Makefile.am create mode 100644 bindings/python/gpiod/Makefile.am create mode 100644 bindings/python/gpiod/__init__.py create mode 100644 bindings/python/gpiod/chip.py create mode 100644 bindings/python/gpiod/chip_info.py create mode 100644 bindings/python/gpiod/edge_event.py create mode 100644 bindings/python/gpiod/exception.py create mode 100644 bindings/python/gpiod/ext/Makefile.am create mode 100644 bindings/python/gpiod/ext/chip.c create mode 100644 bindings/python/gpiod/ext/common.c create mode 100644 bindings/python/gpiod/ext/internal.h create mode 100644 bindings/python/gpiod/ext/line-config.c create mode 100644 bindings/python/gpiod/ext/line-settings.c create mode 100644 bindings/python/gpiod/ext/module.c create mode 100644 bindings/python/gpiod/ext/request.c create mode 100644 bindings/python/gpiod/info_event.py create mode 100644 bindings/python/gpiod/internal.py create mode 100644 bindings/python/gpiod/line.py create mode 100644 bindings/python/gpiod/line_info.py create mode 100644 bindings/python/gpiod/line_request.py create mode 100644 bindings/python/gpiod/line_settings.py create mode 100644 bindings/python/setup.py
bindings/python/.gitignore new file +8 -0
@@ -0,0 +1,8 @@ +# SPDX-License-Identifier: GPL-2.0-or-later +# SPDX-FileCopyrightText: 2017-2022 Bartosz Golaszewski <[email protected]> + +build/ +__pycache__/ +dist/ +gpiod.egg-info/ +*.so
bindings/python/Makefile.am new file +35 -0
@@ -0,0 +1,35 @@ +# SPDX-License-Identifier: GPL-2.0-or-later +# SPDX-FileCopyrightText: 2022 Bartosz Golaszewski <[email protected]> + +EXTRA_DIST = setup.py + +if WITH_TESTS + +BUILD_TESTS = 1 + +endif + +all-local: + GPIOD_VERSION_STRING=$(VERSION_STR) \ + GPIOD_WITH_TESTS=$(BUILD_TESTS) \ + $(PYTHON) setup.py build_ext --inplace \ + --include-dirs=$(top_srcdir)/include/:$(top_srcdir)/tests/gpiosim/ \ + --library-dirs=$(top_builddir)/lib/.libs/:$(top_srcdir)/tests/gpiosim/.libs/ + +install-exec-local: + GPIOD_WITH_TESTS= \ + $(PYTHON) setup.py install --prefix=$(prefix) + +SUBDIRS = gpiod + +if WITH_TESTS + +SUBDIRS += tests + +endif + +if WITH_EXAMPLES + +SUBDIRS += examples + +endif
bindings/python/gpiod/Makefile.am new file +17 -0
@@ -0,0 +1,17 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later +# SPDX-FileCopyrightText: 2022 Bartosz Golaszewski <[email protected]> + +SUBDIRS = ext + +EXTRA_DIST = \ + chip_info.py \ + chip.py \ + edge_event.py \ + exception.py \ + info_event.py \ + __init__.py \ + internal.py \ + line_info.py \ + line.py \ + line_request.py \ + line_settings.py
bindings/python/gpiod/__init__.py new file +53 -0
53 lines not on this page · raw message
bindings/python/gpiod/chip.py new file +329 -0
329 lines not on this page · raw message
bindings/python/gpiod/chip_info.py new file +21 -0
@@ -0,0 +1,21 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later +# SPDX-FileCopyrightText: 2022 Bartosz Golaszewski <[email protected]> + + +from dataclasses import dataclass + + +@dataclass(frozen=True, repr=False) +class ChipInfo: + """ + Snapshot of a chip's status. + """ + + name: str + label: str + num_lines: int + + def __str__(self): + return '<ChipInfo name="{}" label="{}" num_lines={}>'.format( + self.name, self.label, self.num_lines + )
bindings/python/gpiod/edge_event.py new file +46 -0
46 lines not on this page · raw message
bindings/python/gpiod/exception.py new file +20 -0
@@ -0,0 +1,20 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later +# SPDX-FileCopyrightText: 2022 Bartosz Golaszewski <[email protected]> + + +class ChipClosedError(Exception): + """ + Error raised when an already closed chip is used. + """ + + def __init__(self): + super().__init__("I/O operation on closed chip") + + +class RequestReleasedError(Exception): + """ + Error raised when a released request is used. + """ + + def __init__(self): + super().__init__("GPIO lines have been released")
bindings/python/gpiod/ext/Makefile.am new file +11 -0
@@ -0,0 +1,11 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later +# SPDX-FileCopyrightText: 2022 Bartosz Golaszewski <[email protected]> + +EXTRA_DIST = \ + chip.c \ + common.c \ + internal.h \ + line-config.c \ + line-settings.c \ + module.c \ + request.c
bindings/python/gpiod/ext/chip.c new file +339 -0
339 lines not on this page · raw message
bindings/python/gpiod/ext/common.c new file +92 -0
92 lines not on this page · raw message
bindings/python/gpiod/ext/internal.h new file +21 -0
@@ -0,0 +1,21 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* SPDX-FileCopyrightText: 2022 Bartosz Golaszewski <[email protected]> */ + +#ifndef __LIBGPIOD_PYTHON_MODULE_H__ +#define __LIBGPIOD_PYTHON_MODULE_H__ + +#include <gpiod.h> +#include <Python.h> + +PyObject *_Py_gpiod_SetErrFromErrno (const char *filename); +#define Py_gpiod_SetErrFromErrno () _Py_gpiod_SetErrFromErrno(__FILE__) + +PyObject *Py_gpiod_GetGlobalType (const char *type_name); +unsigned int Py_gpiod_PyLongAsUnsignedInt (PyObject *pylong); +void Py_gpiod_dealloc (PyObject *self); +PyObject *Py_gpiod_MakeRequestObject (struct gpiod_line_request *request, +size_t event_buffer_size); +struct gpiod_line_config *Py_gpiod_LineConfigGetData (PyObject *obj); +struct gpiod_line_settings *Py_gpiod_LineSettingsGetData (PyObject *obj); + +#endif /* __LIBGPIOD_PYTHON_MODULE_H__ */
bindings/python/gpiod/ext/line-config.c new file +133 -0
133 lines not on this page · raw message
bindings/python/gpiod/ext/line-settings.c new file +130 -0
130 lines not on this page · raw message
bindings/python/gpiod/ext/module.c new file +193 -0
193 lines not on this page · raw message
bindings/python/gpiod/ext/request.c new file +402 -0
402 lines not on this page · raw message
bindings/python/gpiod/info_event.py new file +33 -0
@@ -0,0 +1,33 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later +# SPDX-FileCopyrightText: 2022 Bartosz Golaszewski <[email protected]> + +from . import _ext +from .line_info import LineInfo +from dataclasses import dataclass +from enum import Enum + + +@dataclass(frozen=True, init=False, repr=False) +class InfoEvent: + """ + Immutable object containing data about a single line info event. + """ + + class Type(Enum): + LINE_REQUESTED = _ext.INFO_EVENT_TYPE_LINE_REQUESTED + LINE_RELEASED = _ext.INFO_EVENT_TYPE_LINE_RELEASED + LINE_CONFIG_CHANGED = _ext.INFO_EVENT_TYPE_LINE_CONFIG_CHANGED + + event_type: Type + timestamp_ns: int + line_info: LineInfo + + def __init__(self, event_type: int, timestamp_ns: int, line_info: LineInfo): + object.__setattr__(self, "event_type", InfoEvent.Type(event_type)) + object.__setattr__(self, "timestamp_ns", timestamp_ns) + object.__setattr__(self, "line_info", line_info) + + def __str__(self): + return "<InfoEvent type={} timestamp_ns={} line_info={}>".format( + self.event_type, self.timestamp_ns, self.line_info + )
bindings/python/gpiod/internal.py new file +19 -0
@@ -0,0 +1,19 @@ +# SPDX-License-Identifier: GPL-2.0-or-later +# SPDX-FileCopyrightText: 2022 Bartosz Golaszewski <[email protected]> + +from datetime import timedelta +from select import select +from typing import Optional, Union + + +def poll_fd(fd: int, timeout: Optional[Union[timedelta, float]] = None) -> bool: + if timeout is None: + timeout = 0.0 + + if isinstance(timeout, timedelta): + sec = timeout.total_seconds() + else: + sec = timeout + + readable, _, _ = select([fd], [], [], sec) + return True if fd in readable else False
bindings/python/gpiod/line.py new file +56 -0
56 lines not on this page · raw message
bindings/python/gpiod/line_info.py new file +73 -0
73 lines not on this page · raw message
bindings/python/gpiod/line_request.py new file +247 -0
247 lines not on this page · raw message
bindings/python/gpiod/line_settings.py new file +62 -0
62 lines not on this page · raw message
bindings/python/setup.py new file +47 -0
47 lines not on this page · raw message
configure.ac +3 -0
@@ -244,8 +244,11 @@ AC_CONFIG_FILES([Makefile bindings/cxx/examples/Makefile bindings/cxx/tests/Makefile bindings/python/Makefile + bindings/python/gpiod/Makefile + bindings/python/gpiod/ext/Makefile bindings/python/examples/Makefile bindings/python/tests/Makefile + bindings/python/tests/gpiosim/Makefile man/Makefile]) AC_OUTPUT
Signature · 1 line
2.34.1
Quoted 21 lines · Bartosz Golaszewski
This is the fourth iteration of python bindings for libgpiod v2.
The code has been split into high-level python and low-level C layers with the latter only doing the bare minimum.
The data model is mostly based on the C++ one with the main difference being utilizing dynamic typing and keyword arguments in place of the builder pattern. That allows us to reduce the number of methods and objects.
Because python doesn't have RAII, unlike C++, we provide a module-level
request_lines()helper as gpiod.Chip(path).request_lines(...) one-liner could lead to the chip left dangling even after the last reference is dropped.Because python forces us to dynamically allocate objects all the time even for fundamental types (which are also immutable) there's no point in trying to replicate the edge-event buffer. Instead
LineRequest.read_edge_event()just returns a list of events.
Quoted 13 lines
v3 -> v4:
- use list and dict comprehensions where applicable
- add a helper for detecting GPIO devices in examples
- remove commented out code
- fix whitespace errors
- cachechip_infoin the chip object
- renameChip.map_line()toChip.line_name_from_id()
- disallow repeating line offsets/names as well as offset-name conflicts in request configs
- don't modify python objects with GIL released (self->chipassignment)
- fix type conversion strings
- fix error check inrequest_offsets()
- fix type comparison warnings
I applied these patches, squashed the entire libgpiod v2 patchset to preserve bisectability and pulled the entire thing into the master branch. Let's continue the development from here. The new release is definitely not done yet but let's just have some solid base to work on.
Bart