Skip to content

para:gpio

import gpio from "para:gpio";

A small module wrapping the Linux GPIO character device interface (/dev/gpiochipN, uAPI v2). Same call shape across Raspberry Pi 4, Raspberry Pi 5 (where the new pinctrl-rp1 driver exposes the same uAPI), Jetson, and any other Linux SBC. No vendored libgpiod; pure ioctl on the kernel character device.

para:gpio is currently Linux-only. macOS / Windows hosts don’t have an equivalent kernel surface for SBC pin headers — those don’t ship without an external bridge.

Synchronously enumerates /dev/gpiochipN entries with their driver label and line count.

gpio.chips();
// [
// { path: "/dev/gpiochip0", label: "gpio-brcmstb@107d508500", lines: 32 },
// { path: "/dev/gpiochip4", label: "pinctrl-rp1", lines: 54 },
// ...
// ]

On a Pi 5 the user-accessible header pins live on /dev/gpiochip4 (the RP1). On a Pi 4 they’re on /dev/gpiochip0 (BCM2711). The label field is how you tell them apart.

Opens a chip and runs GPIO_GET_CHIPINFO_IOCTL to confirm it’s a real gpiochip. Returns a Chip.

await using chip = gpio.open("/dev/gpiochip4");
chip.path; // "/dev/gpiochip4"
chip.label; // "pinctrl-rp1"
chip.lines; // 54

Chip is AsyncDisposableawait using releases the chip fd at scope exit. Lines acquired through this chip stay open until they’re individually closed (you can drop the chip handle without affecting in-flight Line requests).

Acquire a single line on this chip. Returns a Line you can read() / write() / toggle() / edges().

// Output:
const led = chip.line(17, { mode: "out", initial: 0 });
led.write(1);
led.toggle();
led.value.get(); // 0 | 1 — Signal of the most recent observed value
// Input with hardware debounce + edge events:
const button = chip.line(27, {
mode: "in",
pull: "up",
debounceMs: 5,
edge: "falling",
});
for await (const e of button.edges()) {
// e.kind: "rising" | "falling"
// e.timestampNs: bigint (kernel monotonic)
// e.value: 0 | 1
}

Options:

  • mode: "in" | "out" — required.
  • pull: "up" | "down" | "off" — input bias resistor. Default "off".
  • debounceMs: number — hardware debounce. 0 disables. RPi 5 (RP1) supports this; RPi 4 (BCM2711) returns ENOTSUP at request time.
  • edge: "rising" | "falling" | "both" | "none" — edge events to deliver via edges(). Default "none".
  • initial: 0 | 1 — output starting value. Default 0.

Line is AsyncDisposable. The reactive line.value Signal updates on read() and on edge events — pair with para:signals for effect { ... } / ~> composition.

Acquire several lines as one atomic unit. Up to 64 lines per call (kernel uAPI v2 cap). All lines share mode / pull / edge / debounceMs. Reads + writes go through a single ioctl, so multi-pin transitions hit the bus simultaneously.

await using bank = chip.bank([17, 22, 23, 27], {
mode: "out",
initial: 0b1010n, // BigInt: bit i = offsets[i]
});
bank.read(); // BigInt — bit i = current value of offsets[i]
bank.write(0b0101n); // all lines
bank.write(0b0001n, 0b0001n); // values + mask: only modify bit 0

Named bank rather than lines because chip.lines is already the chip’s line count.

The full IoT control loop in one effect block. chip.line({ pollHz: 50 }) updates button.value from the kernel; effect { ... } re-runs on every change and writes the LED — no manual setInterval, no callback wiring.

import gpio from "para:gpio";
import { effect } from "para:signals";
await using chip = gpio.open("/dev/gpiochip4");
await using button = chip.line(27, {
mode: "in", pull: "up", debounceMs: 5, pollHz: 50,
});
await using led = chip.line(17, { mode: "out", initial: 0 });
effect(() => {
const pressed = button.value.get() === 0;
led.write(pressed ? 1 : 0);
});
await new Promise(() => {}); // keep alive

Measured on a Pi 5 RP1 (bench/parabun-gpio-toggle):

  • Single-line write() — ~2.1 M writes/s, ~470 ns per ioctl
  • Single-line toggle() — ~2.0 M toggles/s, ~490 ns per call
  • 4-line bank.write() — ~920 k writes/s = ~3.7 M pin-writes/s (1.1 µs covers 4 lines atomically)

uAPI v2 is ioctl-bound at ~1 MHz toggle rate per pin (two writes per toggle cycle). For sustained > 1 MHz toggle rates or DMA-driven PWM, see bun:mmio (off by default — bypasses the kernel and pokes the controller’s GPIO registers directly).

  • para:i2c — i2c-dev character device on the same Linux SBCs.
  • para:spi — spidev wrapper for the same hardware family.
  • para:signals — reactive composition over line.value.