📰 NEWS
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)pinctrl-bcm2835
on Pi 4's with latest kernel version. Instead its called pinctrl-bcm2711
.flutter_gpiod
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.
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.
line.requestInput();
print("line value: ${line.getValue()}");
line.release();
// now we're requesting it as output.
line.requestOutput(initialValue: true);
line.setValue(false);
line.release();
// 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");
}
line.release();
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
):
dependencies:
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.
Now in your Dart code, you can use:
import 'package:flutter_gpiod/flutter_gpiod.dart';
example/lib/main.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) {
print("$chip");
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.
line1.setValue(true);
await Future.delayed(Duration(milliseconds: 500));
line1.setValue(false);
await Future.delayed(Duration(milliseconds: 500));
line1.setValue(true);
await Future.delayed(Duration(milliseconds: 500));
// setValue(false) is not needed since we're releasing it anyway
line1.release();
/// 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");
countEvents++;
if (countEvents > 100) {
break;
}
}
/// 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.release();
line2.requestOutput(consumer: 'some device that has interrupts', initialValue: false);
line2.setValue(true);
await Future.delayed(Duration(milliseconds: 500));
line2.setValue(false);
line2.release();
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.
line1.release();
}
Author: ardera
Source: https://github.com/ardera/flutter_packages
License: MIT license