Flutter GPIOD: Linux GPIO Access with Flutter


  • migrated to null-safety
  • SignalEvent's time property is no-longer that accurate, but instead two new properties, timestampNanos and timestamp are now provided which are super accurate. (This is because of changes in the kernel)
  • Raspberry Pi's main GPIO chip is no longer called pinctrl-bcm2835 on Pi 4's with latest kernel version. Instead its called pinctrl-bcm2711.


A dart package for GPIO access on linux / Android (root required) using the linux GPIO character-device interface. Tested & working on ARM32 but should work on other 32-bit and 64-bit linux platforms as well.

Getting Started

Then, you can retrieve the list of GPIO chips attached to your system using [FlutterGpiod.chips]. Each chip has a name, label and a number of GPIO lines associated with it.

final chips = FlutterGpiod.instance.chips;

for (final chip in chips) {
    print("chip name: ${chip.name}, chip label: ${chip.label}");

    for (final line in chip.lines) {
        print("  line: $line");

Each line also has some information associated with it that can be retrieved using [GpioLine.info]. The information can change at any time if the line is not owned/requested by you.

// Get the main Raspberry Pi GPIO chip.
// On Raspberry Pi 4 the main GPIO chip is called `pinctrl-bcm2711` and
// on older Pi's or a Pi 4 with older kernel version it's called `pinctrl-bcm2835`.
final chip = FlutterGpiod.instance.chips.singleWhere(
  (chip) => chip.label == 'pinctrl-bcm2711',
  orElse: () => FlutterGpiod.instance.chips.singleWhere((chip) => chip.label == 'pinctrl-bcm2835'),

// Get line 22 of the GPIO chip.
// This is the BCM 22 pin of the Raspberry Pi.
final line = chip.lines[22];

print("line info: ${line.info}")

To control a line (to read or write values or to listen for edges), you need to request it using [GpioLine.requestInput] or [GpioLine.requestOutput].

final chip = FlutterGpiod.instance.chips.singleWhere((chip) => chip.label == 'pinctrl-bcm2835');
final line = chip.lines[22];

// request it as input.
print("line value: ${line.getValue()}");

// now we're requesting it as output.
line.requestOutput(initialValue: true);

// request it as input again, but this time we're also listening
// for edges; both in this case.
line.requestInput(triggers: {SignalEdge.falling, SignalEdge.rising});

print("line value: ${line.getValue()}");

// line.onEvent will not emit any events if no triggers
// are requested for the line.
// this will run forever
await for (final event in line.onEvent) {
  print("got GPIO line signal event: $event");


Use this package as a library

Depend on it

Run this command:

With Dart:

 $ dart pub add flutter_gpiod

With Flutter:

 $ flutter pub add flutter_gpiod

This will add a line like this to your package's pubspec.yaml (and run an implicit dart pub get):

  flutter_gpiod: ^0.5.1+5

Alternatively, your editor might support dart pub get or flutter pub get. Check the docs for your editor to learn more.

Import it

Now in your Dart code, you can use:

import 'package:flutter_gpiod/flutter_gpiod.dart';


import 'dart:async';

import 'package:async/async.dart';
import 'package:flutter_gpiod/flutter_gpiod.dart';

void main() async {
  /// Retrieve the list of GPIO chips.
  final chips = FlutterGpiod.instance.chips;

  /// Print out all GPIO chips and all lines
  /// for all GPIO chips.
  for (var chip in chips) {

    for (var line in chip.lines) {
      print("  $line");

  /// Retrieve the line with index 23 of the first chip.
  /// This is BCM pin 23 for the Raspberry Pi.
  /// I recommend finding the chip you want
  /// based on the chip label, as is done here.
  /// In this example, we search for the main Raspberry Pi GPIO chip and then
  /// retrieve the line with index 23 of it. So [line] is GPIO pin BCM 23.
  /// The main GPIO chip is called `pinctrl-bcm2711` on Pi 4 and `pinctrl-bcm2835`
  /// on older Raspberry Pis and it was also called that way on Pi 4 with older
  /// kernel versions.
  final chip = chips.singleWhere(
    (chip) => chip.label == 'pinctrl-bcm2711',
    orElse: () => chips.singleWhere((chip) => chip.label == 'pinctrl-bcm2835'),

  final line1 = chip.lines[23];
  final line2 = chip.lines[24];

  /// Request BCM 23 as output.
  line1.requestOutput(consumer: "flutter_gpiod test", initialValue: false);

  /// Pulse the line 2 times.
  await Future.delayed(Duration(milliseconds: 500));
  await Future.delayed(Duration(milliseconds: 500));
  await Future.delayed(Duration(milliseconds: 500));
  // setValue(false) is not needed since we're releasing it anyway

  /// Now we're listening for falling and rising edge events
  /// on BCM 23 and BCM 24.
  line1.requestInput(consumer: "test 1", triggers: {SignalEdge.falling, SignalEdge.rising});

  line2.requestInput(consumer: "test 2", triggers: {SignalEdge.falling, SignalEdge.rising});

  print("line value: ${line1.getValue()}");

  /// Print all received line events for
  /// line 1 and 2.
  final mergedEvents = StreamGroup.mergeBroadcast([
    line1.onEvent.map((event) => "(pin 23) $event"),
    line2.onEvent.map((event) => "(pin 24) $event"),

  var countEvents = 0;
  await for (final event in mergedEvents) {
    print("GPIO event: $event");

    if (countEvents > 100) {

  /// If you depend on a certain line event being triggered,
  /// for example, if you listen on an interrupt line and
  /// you block until you receive an interrupt (= a signal event)
  /// in your code, be careful to NOT write code such as the following:
  /// ```dart
  /// final interruptLine = ...;
  /// // do something that triggers an interrupt here
  /// // wait for an interrupt
  /// await for (final _ in interruptLine.onEvent) break;
  /// // continue here
  /// ```
  /// [GpioLine.onEvent] is a broadcast stream. If an event is
  /// added to a broadcast stream, but no listener is present,
  /// that event will simply be discarded. So if your chip
  /// triggers the interrupt faster than the dart code reaches
  /// the `await for`, the signal event will be discarded and
  /// the dart code will block there indefinitely, waiting for
  /// a signal event.
  /// Instead, do something like this:

  /// Get a single-subscription (buffering stream).
  /// The stream will start buffering the events immediately.
  /// If you use `listen` to subscribe to this Stream (or mapped / filtered / etc variants of it),
  /// it's important to cancel your subscription afterwards, otherwise
  /// memory will fill up with buffered signal events.

  /// `SingleSubscriptionTransformer` is contained in the `async` package btw.
  final bufferingStream = line1.onEvent.transform(SingleSubscriptionTransformer<SignalEvent, SignalEvent>());

  // Let's say, if we pulse line 2 (BCM 24) some device connected
  // to it will pulse line 1 (BCM 23) as an interrupt.
  line2.requestOutput(consumer: 'some device that has interrupts', initialValue: false);
  await Future.delayed(Duration(milliseconds: 500));

  print("waiting for interrupt...");

  /// `await for` just uses a [StreamSubscription] internally too and
  /// will cancel the stream subscription when the `await for` loop
  /// finishes, so no memory leak.
  await for (final _ in bufferingStream) break;

  print("got interrupt!");

  /// you also can't listen to / use `await for` with `bufferingStream`
  /// at this point anymore, since single subscription streams are not reusable
  /// after they were canceled.


Download details:

Author: ardera
Source: https://github.com/ardera/flutter_packages

License: MIT license

#flutter #dart 

Flutter GPIOD: Linux GPIO Access with Flutter
1.40 GEEK