gpio 0.1.0 copy "gpio: ^0.1.0" to clipboard
gpio: ^0.1.0 copied to clipboard

Linux GPIO from pure Dart, over the /dev/gpiochip character device using the v2 userspace ABI. No native library, no bundled binaries — just dart:ffi and libc. Kernel debounce, edge-event streams with [...]

gpio #

Linux GPIO from pure Dart, over the /dev/gpiochip* character device using the v2 userspace ABI.

No native library. No bundled binaries. No apt install step. Just dart:ffi and the libc that is already in your process.

Status: pre-release (0.1.0). The API is complete and tested; it has not yet been exercised on real hardware, so treat it as unproven on a live board until it has been.

Why another GPIO package #

Because the v2 ABI carries three things v1 does not, and every existing Dart option gives up at least one of them:

  • Kernel-side debounce, so contact bounce is filtered below userspace instead of by a timer you wrote.
  • Event sequence numbers, so when the kernel's buffer overflows and edges are dropped, you find out. Every other Dart package silently loses them.
  • A selectable event clock, so timestamps mean something specific.

Edge events arrive as a Stream, timestamped by the kernel at the interrupt — so scheduling affects when you see an event, never what time it says. The raw timestampNs is what the event carries, because Dart's Duration is microsecond-resolution and would quietly drop the low three digits; timestamp is there as a convenience when that does not matter.

Requirements #

  • Linux 5.10 or newer (when the v2 ABI landed). There is no v1 fallback, by design — you cannot silently end up on the deprecated ABI. An older kernel answers EINVAL to every v2 ioctl (not ENOTTY, which means "not a gpiochip"), and the error message says so.

  • Access to /dev/gpiochip*. Root works; a udev rule is better:

    # /etc/udev/rules.d/60-gpio.rules
    SUBSYSTEM=="gpio", KERNEL=="gpiochip*", GROUP="gpio", MODE="0660"
    

    then add your user to the gpio group and re-login.

Usage #

import 'package:gpio/gpio.dart';

final chip = GpioChip.byLabel('pinctrl-rp1');

final request = chip.request(
  consumer: 'my-app',
  lines: [
    LineConfig.input(17,
      bias: Bias.pullUp,
      activeLow: true,
      edge: Edge.both,
      debounce: Duration(milliseconds: 5)),
    LineConfig.output(27, initialValue: false),
  ],
);

// One ioctl, one atomic sample across every held line.
print(request.getValues());          // {17: false, 27: false}
request.setValue(27, value: true);

request.events.listen((event) => switch (event) {
  LineEdgeEvent(:final edge, :final timestampNs) =>
    print('$edge at $timestampNs ns'),
  LineEventsDropped(:final count) =>
    print('the kernel dropped $count edges'),
});

await request.close();
chip.close();

Runnable versions of both halves are in example/.

Testing your own code #

FakeKernel models the character device in memory, so code that talks to GPIO is testable on a machine that has none:

import 'package:gpio/gpio_testing.dart';

final fake = FakeChip(name: 'gpiochip0', label: 'test', lineCount: 8);
final chip = GpioChip.byLabel('test', syscalls: FakeKernel([fake]));

fake.setLevel(3, value: true);       // drive a pin from "outside"
expect(request.getValue(3), isTrue);

It models ownership and EBUSY, masked atomic access, activeLow inversion, kernel debounce, and edge events with sequence numbers — including dropNextEvents, which reproduces a FIFO overflow so you can test that your code notices.

Finding your chip #

Look chips up by label, not by index:

final chip = GpioChip.byLabel('pinctrl-rp1');   // Raspberry Pi 5

Chip numbering is assigned by probe order, so it moves. On the Raspberry Pi 5 the RP1 southbridge sits on PCIe and enumerates late: older kernels expose the 40-pin header as gpiochip4, kernels after mid-2024 renumber it to gpiochip0. Code with a hardcoded index is a bug waiting for a kernel update.

Platform support #

target how GPIO is reached there status
Linux /dev/gpiochipN, uAPI v2 ✅ supported
Android the same character device (root + SELinux permits) should work, untested
Windows on ARM, incl. a Pi 3/4/5 GpioClx + rhproxy → WinRT Windows.Devices.Gpio not supported — a second backend
FreeBSD, NetBSD /dev/gpiocN, a different ioctl set not supported — a second backend
macOS no Mac has GPIO pins out of scope

Windows deserves a word, because "Windows has no GPIO" is wrong. Windows on ARM runs on a Raspberry Pi 3/4/5, and it is the same silicon — the pins are physically there. What Windows does not do is expose them as a character device: GPIO arrives through the GpioClx driver and rhproxy, surfaced to user mode as the WinRT Windows.Devices.Gpio API (the thing .NET's System.Device.Gpio wraps on that platform). So Windows is a second backend behind the same public types, not a port of this one — and the same is true of the BSDs, whose /dev/gpiocN speaks its own ioctls.

None of that is planned work. It is why the package is called gpio rather than linux_gpio, why ioctl and file descriptors stay out of the public API, and why the Linux-specific entry point is a single named constructor (GpioChip.byPath) rather than the shape of the whole library.

macOS is the one genuine "no": no Mac has GPIO pins, and macOS does not run on a Pi. Reaching pins from a Mac means a USB bridge (FT232H, MCP2221), which is a device driver rather than an OS backend.

Running the tests #

dart test                  # 83 tests, no hardware needed
dart test -t integration   # real ioctls against the kernel's gpio-sim

The default suite runs against an in-memory model of the character device, so it passes on any machine.

The integration suite is the one that talks to a real /dev/gpiochipN, via the kernel's own gpio-sim module — the same thing libgpiod's test suite uses. It needs root and a kernel built with CONFIG_GPIO_SIM. Note that GitHub's hosted runners do not have it (their Azure kernel ships no such module), so CI reports a warning and skips; a stock Debian, Ubuntu or Raspberry Pi OS kernel does have it.

Licence #

MIT. The bindings are generated from the kernel's own <linux/gpio.h>, which carries the Linux-syscall-note exception precisely so userspace programs under any licence may use those definitions. libgpiod (LGPL) is deliberately not a source for any part of this package.

0
likes
0
points
162
downloads

Publisher

verified publisheraquiles.dev

Weekly Downloads

Linux GPIO from pure Dart, over the /dev/gpiochip character device using the v2 userspace ABI. No native library, no bundled binaries — just dart:ffi and libc. Kernel debounce, edge-event streams with kernel timestamps, and detection of edges the kernel dropped.

Repository (GitHub)
View/report issues

Topics

#gpio #linux #embedded #raspberry-pi #ffi

License

unknown (license)

Dependencies

ffi, meta

More

Packages that depend on gpio