A GPIO counterpart to i2cdevice, generated from the Pimoroni Python Boilerplate.
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.
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
git clone https://github.com/pimoroni/gpiodevice-python
cd gpiodevice-python
./install.sh --unstableA pin's /dev/gpiochip* varies between boards and kernel versions. These functions return a gpiod.Chip.
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.
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.
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.
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.
A pin must be requested with edge detection for any of these to see events.
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.
timeoutis in seconds or atimedelta.Nonewaits indefinitely.linefilters events to one offset. Omit it to take the first event on any line.raise_on_timeoutraisesTimeoutErrorinstead of returningNone.
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.
callbackis called with thegpiodevent on each edge.debounceis in seconds or atimedelta.
watch_pin owns the request it made. close() stops the thread and releases the line.
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.
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.