Skip to main content

gpiodevice

Build Status Coverage Status PyPi Package Python Versions

A GPIO counterpart to i2cdevice, generated from the Pimoroni Python Boilerplate.

What is gpiodevice?

gpiodevice is a middleware library intended to make some user-facing aspects of interfacing with Linux's GPIO character device ABI (via gpiod) simpler and friendlier.

gpiodevice is not intended to replace gpiod, but collects some common patterns into a reusable library for GPIO-based Python projects.

Installing

We'd recommend using this library with Raspberry Pi OS Bookworm or later. It requires Python >=3.9.

gpiodevice is usually installed as a dependency of another library. To install gpiodevice:

  • Set up a virtual environment: python3 -m venv --system-site-packages $HOME/.virtualenvs/pimoroni
  • Switch to the virtual environment: source ~/.virtualenvs/pimoroni/bin/activate
  • Install the library: pip install gpiodevice

Development:

git clone https://github.com/pimoroni/gpiodevice-python
cd gpiodevice-python
./install.sh --unstable

Finding A gpiochip

A pin's /dev/gpiochip* varies between boards and kernel versions. These functions return a gpiod.Chip.

By Pin Name

import gpiodevice

chip = gpiodevice.find_chip_by_pins("GPIO4")
chip = gpiodevice.find_chip_by_pins(("GPIO4", "GPIO17"))
chip = gpiodevice.find_chip_by_pins("GPIO4,GPIO17")

Returns the first gpiochip carrying all of the named pins. Pin names are those reported by the kernel.

A pin claimed by another consumer counts as a failure. Pass ignore_claimed=True to match on the name alone.

By Chip Label

import gpiodevice

chip = gpiodevice.find_chip_by_label("pinctrl-rp1")
chip = gpiodevice.find_chip_by_label(("pinctrl-rp1", "pinctrl-bcm2711"))

Returns the first gpiochip whose label matches. Labels are matched as regular expressions.

import gpiodevice

chip = gpiodevice.find_chip_by_label("pinctrl-rp1", pins={"my sensor": "GPIO4"})

Supply pins to also require that those pins are free.

By Platform

import gpiodevice

chip = gpiodevice.find_chip_by_platform()

Reads the board model and matches the chip labels known for it. Raspberry Pi, Radxa, NVIDIA Jetson and the Alienware m15 are supported.

import gpiodevice

name = gpiodevice.platform.get_name()
labels = gpiodevice.platform.get_gpiochip_labels()

get_name returns the detected board name. get_gpiochip_labels returns the labels tried for it. Both raise RuntimeError on an unrecognised board.

Requesting Pins

import gpiod
import gpiodevice
from gpiod.line import Direction, Value

settings = gpiod.LineSettings(direction=Direction.OUTPUT)
request, offset = gpiodevice.get_pin("GPIO4", "my led", settings)

request.set_value(offset, Value.ACTIVE)

get_pin requests one pin by name. It finds the chip and resolves the name to a line offset. It returns the gpiod.LineRequest and that offset.

The second argument labels the pin. It forms part of the consumer name reported by gpioinfo.

pin also accepts:

  • An int line offset. The platform's chip is used.
  • A (request, offset) tuple. This is returned unchanged.
import gpiod
import gpiodevice
from gpiod.line import Direction

settings = gpiod.LineSettings(direction=Direction.OUTPUT)

pins = gpiodevice.get_pins_for_platform({
    "Raspberry Pi 5": {"my led": ("GPIO4", settings)},
    "Raspberry Pi 4": {"my led": ("GPIO4", settings)},
})

get_pins_for_platform takes a mapping of platform name prefix to pins. It returns a list of (request, offset) for the entry matching the detected board.

Edge Detection

A pin must be requested with edge detection for any of these to see events.

wait_for_edge

import gpiod
import gpiodevice
from gpiod.line import Bias, Edge

settings = gpiod.LineSettings(edge_detection=Edge.FALLING, bias=Bias.PULL_UP)
request, offset = gpiodevice.get_pin("GPIO4", "my button", settings)

event = gpiodevice.wait_for_edge(request, line=offset, timeout=5.0)

Blocks until an edge arrives. Returns the gpiod event, or None on timeout.

  • timeout is in seconds or a timedelta. None waits indefinitely.
  • line filters events to one offset. Omit it to take the first event on any line.
  • raise_on_timeout raises TimeoutError instead of returning None.

watch_pin

import time
import gpiodevice
from gpiod.line import Bias, Edge

def handle_button(event):
    print(f"edge on line {event.line_offset}")

watch = gpiodevice.watch_pin(
    "GPIO4",
    edge=Edge.FALLING,
    bias=Bias.PULL_UP,
    debounce=0.02,
    callback=handle_button,
)

try:
    while True:
        time.sleep(1.0)
finally:
    watch.close()

Requests one pin and returns a started Watch.

  • callback is called with the gpiod event on each edge.
  • debounce is in seconds or a timedelta.

watch_pin owns the request it made. close() stops the thread and releases the line.

Watch

import gpiod
import gpiodevice
from gpiod.line import Bias, Edge

BUTTONS = {"A": "GPIO5", "B": "GPIO6"}
settings = gpiod.LineSettings(edge_detection=Edge.FALLING, bias=Bias.PULL_UP)

chip = gpiodevice.find_chip_by_pins(tuple(BUTTONS.values()))
offsets = {label: chip.line_offset_from_id(pin) for label, pin in BUTTONS.items()}
request = chip.request_lines(
    consumer="buttons",
    config={offset: settings for offset in offsets.values()}
)

def handler(label):
    return lambda event: print(f"button {label}")

with gpiodevice.Watch(request, {offset: handler(label) for label, offset in offsets.items()}) as watch:
    input("Press Ctrl+C to exit!\n")

Watches a request you made yourself. Edges are dispatched on a background thread.

handlers is a mapping of line offset to callable. Pass a single callable to use it for every line. Edges on lines with no handler are ignored.

As a context manager the watch starts on entry and closes on exit. Otherwise call start() and stop(). start() is idempotent.

close() stops the thread. It also releases the request, but only if the Watch owns it. A Watch you construct does not, unless you pass manage_request=True.

Errors

import gpiodevice

chip = gpiodevice.find_chip_by_pins("GPIO4", fatal=False)
if chip is None:
    ...

The find_* functions and check_pins_available raise SystemExit with a digest of everything they tried:

Woah there, suitable gpiochip not found!
  ✅  GPIO22: (line 22) found - /dev/gpiochip4 (pinctrl-rp1)!
  ⚠️   GPIO22: (line 22, GPIO22) currently claimed by some-other-app
  ✅  GPIO22: (line 22) found - /dev/gpiochip0 (pinctrl-rp1)!
  ⚠️   GPIO22: (line 22, GPIO22) currently claimed by some-other-app
  ❌  GPIO22: not found - /dev/gpiochip13 (gpio-brcmstb@107d508520)!
  ❌  GPIO22: not found - /dev/gpiochip10 (gpio-brcmstb@107d508500)!

Pass fatal=False to return None instead. Set GPIODEVICE_DEBUG in the environment to raise a RuntimeError with a traceback.

import gpiodevice

chip = gpiodevice.find_chip_by_platform()
free = gpiodevice.check_pins_available(chip, {"my led": "GPIO4"}, fatal=False)

check_pins_available reports whether a set of pins are free. It does not request them.

Changelog

0.1.0

  • New: Watch, watch_pin and wait_for_edge edge/interrupt helpers
  • New: Nvidia Jetson platform support
  • Fix: find_chip_by_pins raised SystemError instead of returning None when fatal=False
  • Docs: document the API in README.md

0.0.5

  • Add support for int type in get_pin

0.0.4

  • Gracefully handle a tuple being passed to get_pin
  • Match all pinctrl- gpiodevices for RPi in get_gpiochip_labels

0.0.3

  • Deprecate the friendly_errors flag in favour of a new fatal flag on methods
  • Catch use of int pin numbers from unported code and raise a friendly error

0.0.2

  • Add platform detection
  • ROCK 5B support
  • Bug fixes

0.0.1

  • Initial Release

Release files for gpiodevice 0.1.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for gpiodevice 0.1.0
File Size Uploaded
gpiodevice-0.1.0.tar.gz 19.3 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for gpiodevice 0.1.0
File Interpreter ABI Platform
gpiodevice-0.1.0-py3-none-any.whl Python 3 none any Details

Total release size: 32.0 kB

Release files / gpiodevice-0.1.0.tar.gz

Download URL gpiodevice-0.1.0.tar.gz
Size 19.3 kB
Tags Source
SHA-256 checksum
How to use checksums
823185e2230aba7a015f00b9f8045840f8f9253d2d58c76950065f446eb417b2
BLAKE2b-256 checksum
How to use checksums
8c5a919e85d28bd761a53bfaec4e54eac217b216895473993fd0ee249a1f1ccc
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via uv/0.12.5 {"installer":{"name":"uv","version":"0.12.5","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

Release files / gpiodevice-0.1.0-py3-none-any.whl

Download URL gpiodevice-0.1.0-py3-none-any.whl
Size 12.7 kB
Tags Python 3
SHA-256 checksum
How to use checksums
4d1716ebed3068dd233e9851612773cbdf7a194289b8ea0aaf712f2fe90b1adb
BLAKE2b-256 checksum
How to use checksums
6eb018437d9b784086b70ff9503c18e8b51d6905d382f3c695f9b3c70f85e91d
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via uv/0.12.5 {"installer":{"name":"uv","version":"0.12.5","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

Release history Release notifications | RSS feed

This release

0.1.0 This release

2 release files

0.0.5

2 release files

0.0.4

2 release files

0.0.3

2 release files

0.0.2

2 release files

0.0.1

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page