diff --git a/packages/flutter_gpiod/README.md b/packages/flutter_gpiod/README.md index d62a343..cd3c3f9 100644 --- a/packages/flutter_gpiod/README.md +++ b/packages/flutter_gpiod/README.md @@ -2,6 +2,9 @@ - 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`. +- Raspberry Pi's kernel changed so that its GPIO indexes are no longer a perfect sequence. + Two different device files may now refer to the same GPIO chip. + Therefore, a map represents this relationship instead of a list going forward. # flutter_gpiod @@ -9,18 +12,18 @@ A dart package for GPIO access on linux / Android (*root required*) using the li ## Getting Started -Then, you can retrieve the list of GPIO chips attached to +Then, you can retrieve the map 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. +label, and a number of GPIO lines associated with it. ```dart final chips = FlutterGpiod.instance.chips; -for (final chip in chips) { - print("chip name: ${chip.name}, chip label: ${chip.label}"); +for (final chip in chips.values) { + print("chip name: ${chip.name}, chip label: ${chip.label}"); - for (final line in chip.lines) { - print(" line: $line"); - } + for (final line in chip.lines) { + print(" line: $line"); + } } ``` @@ -31,9 +34,10 @@ 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( +// On newer kernel version the chips may appear twice, so using `singleWhere` would fail. +final chip = FlutterGpiod.instance.chips.values.firstWhere( (chip) => chip.label == 'pinctrl-bcm2711', - orElse: () => FlutterGpiod.instance.chips.singleWhere((chip) => chip.label == 'pinctrl-bcm2835'), + orElse: () => FlutterGpiod.instance.chips.values.firstWhere((chip) => chip.label == 'pinctrl-bcm2835'), ); // Get line 22 of the GPIO chip. @@ -46,7 +50,7 @@ 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]. ```dart -final chip = FlutterGpiod.instance.chips.singleWhere((chip) => chip.label == 'pinctrl-bcm2835'); +final chip = FlutterGpiod.instance.chips.values.firstWhere((chip) => chip.label == 'pinctrl-bcm2835'); final line = chip.lines[22]; // request it as input. diff --git a/packages/flutter_gpiod/example/lib/main.dart b/packages/flutter_gpiod/example/lib/main.dart index 99c0f92..6ebf552 100644 --- a/packages/flutter_gpiod/example/lib/main.dart +++ b/packages/flutter_gpiod/example/lib/main.dart @@ -4,12 +4,12 @@ import 'package:async/async.dart'; import 'package:flutter_gpiod/flutter_gpiod.dart'; void main() async { - /// Retrieve the list of GPIO chips. + /// Retrieve the map 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 chip in chips.values) { print("$chip"); for (var line in chip.lines) { @@ -29,9 +29,11 @@ void main() async { /// 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( + /// On newer kernel version the chips may appear twice, so using `singleWhere` + /// would fail. + final chip = chips.values.firstWhere( (chip) => chip.label == 'pinctrl-bcm2711', - orElse: () => chips.singleWhere((chip) => chip.label == 'pinctrl-bcm2835'), + orElse: () => chips.values.firstWhere((chip) => chip.label == 'pinctrl-bcm2835'), ); final line1 = chip.lines[23]; diff --git a/packages/flutter_gpiod/lib/src/gpiod.dart b/packages/flutter_gpiod/lib/src/gpiod.dart index 670a12e..61520c5 100644 --- a/packages/flutter_gpiod/lib/src/gpiod.dart +++ b/packages/flutter_gpiod/lib/src/gpiod.dart @@ -233,7 +233,6 @@ Future _eventIsolateEntry2(List args) async { class PlatformInterface { PlatformInterface._construct( this.libc, - this._numChips, this._chipIndexToFd, this._epollFd, this._eventReceivePort, @@ -247,15 +246,22 @@ class PlatformInterface { libc = LibC(ffi.DynamicLibrary.open("libc.so.6")); } - final numChips = Directory('/dev') + final deviceType = 'gpiochip'; + + final chips = Directory('/dev') .listSync(followLinks: false, recursive: false) - .where((element) => basename(element.path).startsWith('gpiochip')) - .length; + .where((element) => basename(element.path).startsWith(deviceType)); final chipIndexToFd = {}; - for (var i = 0; i < numChips; i++) { - final pathPtr = '/dev/gpiochip$i'.toNativeUtf8(); + for (final chip in chips) { + final path = chip.absolute.path; + final pathPtr = path.toNativeUtf8(); + + final chipIndex = int.parse( + basename(path).substring(deviceType.length), + radix: 10, + ); final fd = libc.open(pathPtr.cast(), O_RDWR | O_CLOEXEC); @@ -263,10 +269,11 @@ class PlatformInterface { if (fd < 0) { chipIndexToFd.values.forEach((fd) => libc.close(fd)); - throw FileSystemException('Could not open GPIO chip $i', '/dev/gpiochip$i'); + + throw FileSystemException('Could not open GPIO chip $chipIndex', path); } - chipIndexToFd[i] = fd; + chipIndexToFd[chipIndex] = fd; } final epollFd = libc.epoll_create1(0); @@ -291,11 +298,10 @@ class PlatformInterface { throw RemoteError(message[0], message[1]); }); - return PlatformInterface._construct(libc, numChips, chipIndexToFd, epollFd, receivePort); + return PlatformInterface._construct(libc, chipIndexToFd, epollFd, receivePort); } final LibC libc; - final int _numChips; final Map _chipIndexToFd; final int _epollFd; final ReceivePort _eventReceivePort; @@ -322,6 +328,7 @@ class PlatformInterface { } int _chipFdFromChipIndex(int chipIndex) { + assert(_chipIndexToFd.containsKey(chipIndex)); return _chipIndexToFd[chipIndex]!; } @@ -330,7 +337,10 @@ class PlatformInterface { } int _chipIndexFromLineHandle(int lineHandle) { - return lineHandle >> 32; + final chipIndex = lineHandle >> 32; + assert(_chipIndexToFd.containsKey(chipIndex)); + + return chipIndex; } int _chipFdFromLineHandle(int lineHandle) { @@ -375,8 +385,8 @@ class PlatformInterface { return _eventStream!; } - int getNumChips() { - return _numChips; + Iterable getChipIndexes() { + return _chipIndexToFd.keys; } Map getChipDetails(int chipIndex) { @@ -720,8 +730,8 @@ class FlutterGpiod { static FlutterGpiod? _instance; - /// The list of GPIO chips attached to this system. - final List chips; + /// The map of GPIO chips and their indexes attached to this system. + final Map chips; /// Whether setting and getting GPIO line bias is supported. /// @@ -741,13 +751,14 @@ class FlutterGpiod { /// If none exists, one will be constructed. static FlutterGpiod get instance { if (_instance == null) { - final chips = - List.generate(PlatformInterface.instance.getNumChips(), (i) => GpioChip._fromIndex(i), growable: false); + final chips = { + for (final index in PlatformInterface.instance.getChipIndexes()) index: GpioChip._fromIndex(index) + }; final bias = PlatformInterface.instance.supportsBias(); final reconfig = PlatformInterface.instance.supportsLineReconfiguration(); - _instance = FlutterGpiod._internal(List.unmodifiable(chips), bias, reconfig); + _instance = FlutterGpiod._internal(Map.unmodifiable(chips), bias, reconfig); } return _instance!; @@ -767,8 +778,10 @@ class FlutterGpiod { /// some number of GPIO lines / pins. @immutable class GpioChip { - /// The index of the GPIO chip in the [FlutterGpiod.chips] list, - /// and at the same time, the numerical suffix of [name]. + /// The key of the GPIO chip in the [FlutterGpiod.chips] map, + /// and most of the time, the numerical suffix of [name]. + /// A noteable exception to this rule is the Raspberry Pi's main GPIO chip + /// when using newer kernel versions. final int index; /// The name of this GPIO chip. diff --git a/packages/flutter_gpiod_test_app/integration_test/gpio_test.dart b/packages/flutter_gpiod_test_app/integration_test/gpio_test.dart index 00fda41..671608d 100644 --- a/packages/flutter_gpiod_test_app/integration_test/gpio_test.dart +++ b/packages/flutter_gpiod_test_app/integration_test/gpio_test.dart @@ -125,76 +125,80 @@ void main() { expect(chips, hasLength(3)); }, tags: ['pi4']); - testWidgets('test pi 4 main gpio chip', (_) async { - final chip = FlutterGpiod.instance.chips[0]; - expect(chip.index, 0); - expect(chip.name, 'gpiochip0'); - expect(chip.label, 'pinctrl-bcm2711'); - - final lines = chip.lines; - expect(lines, hasLength(58)); - expect(lines[0], isFreeInputLine('ID_SDA')); - expect(lines[1], isFreeInputLine('ID_SCL')); - expect(lines[2], isFreeInputLine('SDA1')); - expect(lines[3], isFreeInputLine('SCL1')); - expect(lines[4], isFreeInputLine('GPIO_GCLK')); - expect(lines[5], isFreeInputLine('GPIO5')); - expect(lines[6], isFreeInputLine('GPIO6')); - expect(lines[7], isFreeInputLine('SPI_CE1_N')); - expect(lines[8], isFreeInputLine('SPI_CE0_N')); - expect(lines[9], isFreeInputLine('SPI_MISO')); - expect(lines[10], isFreeInputLine('SPI_MOSI')); - expect(lines[11], isFreeInputLine('SPI_SCLK')); - expect(lines[12], isFreeInputLine('GPIO12')); - expect(lines[13], isFreeInputLine('GPIO13')); - expect(lines[14], isFreeInputLine('TXD1')); - expect(lines[15], isFreeInputLine('RXD1')); - expect(lines[16], isFreeInputLine('GPIO16')); - expect(lines[17], isFreeInputLine('GPIO17')); - expect(lines[18], isFreeInputLine('GPIO18')); - expect(lines[19], isFreeInputLine('GPIO19')); - expect(lines[20], isFreeInputLine('GPIO20')); - expect(lines[21], isFreeInputLine('GPIO21')); - expect(lines[22], isFreeInputLine('GPIO22')); - expect(lines[23], isFreeInputLine('GPIO23')); - expect(lines[24], isFreeInputLine('GPIO24')); - expect(lines[25], isFreeInputLine('GPIO25')); - expect(lines[26], isFreeInputLine('GPIO26')); - expect(lines[27], isFreeInputLine('GPIO27')); - expect(lines[28], isFreeInputLine('RGMII_MDIO')); - expect(lines[29], isFreeInputLine('RGMIO_MDC')); - expect(lines[30], isFreeInputLine('CTS0')); - expect(lines[31], isFreeInputLine('RTS0')); - expect(lines[32], isFreeInputLine('TXD0')); - expect(lines[33], isFreeInputLine('RXD0')); - expect(lines[34], isFreeInputLine('SD1_CLK')); - expect(lines[35], isFreeInputLine('SD1_CMD')); - expect(lines[36], isFreeInputLine('SD1_DATA0')); - expect(lines[37], isFreeInputLine('SD1_DATA1')); - expect(lines[38], isFreeInputLine('SD1_DATA2')); - expect(lines[39], isFreeInputLine('SD1_DATA3')); - expect(lines[40], isFreeInputLine('PWM0_MISO')); - expect(lines[41], isFreeInputLine('PWM1_MOSI')); - expect(lines[42], isKernelOutputLine('STATUS_LED_G_CLK', 'led0')); - expect(lines[43], isFreeInputLine('SPIFLASH_CE_N')); - expect(lines[44], isFreeInputLine('SDA0')); - expect(lines[45], isFreeInputLine('SCL0')); - expect(lines[46], isFreeInputLine('RGMII_RXCLK')); - expect(lines[47], isFreeInputLine('RGMII_RXCTL')); - expect(lines[48], isFreeInputLine('RGMII_RXD0')); - expect(lines[49], isFreeInputLine('RGMII_RXD1')); - expect(lines[50], isFreeInputLine('RGMII_RXD2')); - expect(lines[51], isFreeInputLine('RGMII_RXD3')); - expect(lines[52], isFreeInputLine('RGMII_TXCLK')); - expect(lines[53], isFreeInputLine('RGMII_TXCTL')); - expect(lines[54], isFreeInputLine('RGMII_TXD0')); - expect(lines[55], isFreeInputLine('RGMII_TXD1')); - expect(lines[56], isFreeInputLine('RGMII_TXD2')); - expect(lines[57], isFreeInputLine('RGMII_TXD3')); - }, tags: ['pi4']); + group('test pi 4 main gpio chip', () { + for (final chipIndex in [0, 4]) { + testWidgets('/dev/gpiochip$chipIndex', (_) async { + final chip = FlutterGpiod.instance.chips[chipIndex]!; + expect(chip.index, chipIndex); + expect(chip.name, 'gpiochip0'); + expect(chip.label, 'pinctrl-bcm2711'); + + final lines = chip.lines; + expect(lines, hasLength(58)); + expect(lines[0], isFreeInputLine('ID_SDA')); + expect(lines[1], isFreeInputLine('ID_SCL')); + expect(lines[2], isFreeInputLine('SDA1')); + expect(lines[3], isFreeInputLine('SCL1')); + expect(lines[4], isFreeInputLine('GPIO_GCLK')); + expect(lines[5], isFreeInputLine('GPIO5')); + expect(lines[6], isFreeInputLine('GPIO6')); + expect(lines[7], isFreeInputLine('SPI_CE1_N')); + expect(lines[8], isFreeInputLine('SPI_CE0_N')); + expect(lines[9], isFreeInputLine('SPI_MISO')); + expect(lines[10], isFreeInputLine('SPI_MOSI')); + expect(lines[11], isFreeInputLine('SPI_SCLK')); + expect(lines[12], isFreeInputLine('GPIO12')); + expect(lines[13], isFreeInputLine('GPIO13')); + expect(lines[14], isFreeInputLine('TXD1')); + expect(lines[15], isFreeInputLine('RXD1')); + expect(lines[16], isFreeInputLine('GPIO16')); + expect(lines[17], isFreeInputLine('GPIO17')); + expect(lines[18], isFreeInputLine('GPIO18')); + expect(lines[19], isFreeInputLine('GPIO19')); + expect(lines[20], isFreeInputLine('GPIO20')); + expect(lines[21], isFreeInputLine('GPIO21')); + expect(lines[22], isFreeInputLine('GPIO22')); + expect(lines[23], isFreeInputLine('GPIO23')); + expect(lines[24], isFreeInputLine('GPIO24')); + expect(lines[25], isFreeInputLine('GPIO25')); + expect(lines[26], isFreeInputLine('GPIO26')); + expect(lines[27], isFreeInputLine('GPIO27')); + expect(lines[28], isFreeInputLine('RGMII_MDIO')); + expect(lines[29], isFreeInputLine('RGMIO_MDC')); + expect(lines[30], isFreeInputLine('CTS0')); + expect(lines[31], isFreeInputLine('RTS0')); + expect(lines[32], isFreeInputLine('TXD0')); + expect(lines[33], isFreeInputLine('RXD0')); + expect(lines[34], isFreeInputLine('SD1_CLK')); + expect(lines[35], isFreeInputLine('SD1_CMD')); + expect(lines[36], isFreeInputLine('SD1_DATA0')); + expect(lines[37], isFreeInputLine('SD1_DATA1')); + expect(lines[38], isFreeInputLine('SD1_DATA2')); + expect(lines[39], isFreeInputLine('SD1_DATA3')); + expect(lines[40], isFreeInputLine('PWM0_MISO')); + expect(lines[41], isFreeInputLine('PWM1_MOSI')); + expect(lines[42], isKernelOutputLine('STATUS_LED_G_CLK', 'led0')); + expect(lines[43], isFreeInputLine('SPIFLASH_CE_N')); + expect(lines[44], isFreeInputLine('SDA0')); + expect(lines[45], isFreeInputLine('SCL0')); + expect(lines[46], isFreeInputLine('RGMII_RXCLK')); + expect(lines[47], isFreeInputLine('RGMII_RXCTL')); + expect(lines[48], isFreeInputLine('RGMII_RXD0')); + expect(lines[49], isFreeInputLine('RGMII_RXD1')); + expect(lines[50], isFreeInputLine('RGMII_RXD2')); + expect(lines[51], isFreeInputLine('RGMII_RXD3')); + expect(lines[52], isFreeInputLine('RGMII_TXCLK')); + expect(lines[53], isFreeInputLine('RGMII_TXCTL')); + expect(lines[54], isFreeInputLine('RGMII_TXD0')); + expect(lines[55], isFreeInputLine('RGMII_TXD1')); + expect(lines[56], isFreeInputLine('RGMII_TXD2')); + expect(lines[57], isFreeInputLine('RGMII_TXD3')); + }, tags: ['pi4']); + } + }); testWidgets('test pi 4 secondary gpio chip', (_) async { - final chip = FlutterGpiod.instance.chips[1]; + final chip = FlutterGpiod.instance.chips[1]!; expect(chip.index, 1); expect(chip.name, 'gpiochip1'); expect(chip.label, 'raspberrypi-exp-gpio'); @@ -212,7 +216,7 @@ void main() { }, tags: ['pi4']); testWidgets('test pi 4 third gpio chip', (_) async { - final chip = FlutterGpiod.instance.chips[2]; + final chip = FlutterGpiod.instance.chips[2]!; expect(chip.index, 2); expect(chip.name, 'gpiochip2'); expect(chip.label, '7inch-touchscreen-p'); @@ -240,7 +244,7 @@ void main() { ); testWidgets('test odroid c4 first gpio chip', (_) async { - final chip = FlutterGpiod.instance.chips[0]; + final chip = FlutterGpiod.instance.chips[0]!; expect(chip.index, 0); expect(chip.name, 'gpiochip0'); expect(chip.label, 'aobus-banks'); @@ -266,7 +270,7 @@ void main() { }, tags: ['odroidc4']); testWidgets('test odroid c4 second gpio chip', (_) async { - final chip = FlutterGpiod.instance.chips[1]; + final chip = FlutterGpiod.instance.chips[1]!; expect(chip.index, 1); expect(chip.name, 'gpiochip1'); expect(chip.label, 'periphs-banks'); @@ -438,7 +442,7 @@ void main() { } if (rerequestGpiox0AsInput) { - final gpiox0 = FlutterGpiod.instance.chips[1].lines[476 - 410]; + final gpiox0 = FlutterGpiod.instance.chips[1]!.lines[476 - 410]; requestInput(gpiox0); release(gpiox0); } @@ -449,7 +453,7 @@ void main() { }); testWidgets('test odroid c4 first gpio chip requesting lines', (_) async { - final lines = FlutterGpiod.instance.chips[0].lines; + final lines = FlutterGpiod.instance.chips[0]!.lines; // request all the lines expect(() => requestInput(lines[0], consumer: 'test0'), returnsNormally); @@ -490,7 +494,7 @@ void main() { }, tags: ['odroidc4']); testWidgets('test odroid c4 second gpio chip requesting lines', (_) async { - final lines = FlutterGpiod.instance.chips[1].lines; + final lines = FlutterGpiod.instance.chips[1]!.lines; // request all the lines expect(() => requestInput(lines[0], consumer: 'test0'), returnsNormally); @@ -683,10 +687,10 @@ void main() { // aobus-banks has export numbers 496-511. // This is GPIOX.4 (export number 480) - final gpiox4 = FlutterGpiod.instance.chips[1].lines[480 - 410]; + final gpiox4 = FlutterGpiod.instance.chips[1]!.lines[480 - 410]; // This is GPIOX.0 (export number 476) - final gpiox0 = FlutterGpiod.instance.chips[1].lines[476 - 410]; + final gpiox0 = FlutterGpiod.instance.chips[1]!.lines[476 - 410]; // Now request GPIOX.4 as input and GPIOX.0 as output requestInput(gpiox4, consumer: 'test', triggers: const {SignalEdge.rising}); @@ -761,7 +765,7 @@ void main() { ); testWidgets('test lattepanda first gpio chip', (_) async { - final chip = FlutterGpiod.instance.chips[0]; + final chip = FlutterGpiod.instance.chips[0]!; expect(chip.index, 0); expect(chip.name, 'gpiochip0'); expect(chip.label, 'aobus-banks'); @@ -777,7 +781,7 @@ void main() { }, tags: ['panda']); testWidgets('test lattepanda second gpio chip', (_) async { - final chip = FlutterGpiod.instance.chips[1]; + final chip = FlutterGpiod.instance.chips[1]!; expect(chip.index, 1); expect(chip.name, 'gpiochip1'); expect(chip.label, 'aobus-banks'); @@ -818,7 +822,7 @@ void main() { }, tags: ['panda']); testWidgets('test lattepanda third gpio chip', (_) async { - final chip = FlutterGpiod.instance.chips[2]; + final chip = FlutterGpiod.instance.chips[2]!; expect(chip.index, 2); expect(chip.name, 'gpiochip2'); expect(chip.label, 'aobus-banks'); @@ -837,7 +841,7 @@ void main() { }, tags: ['panda']); testWidgets('test lattepanda fourth gpio chip', (_) async { - final chip = FlutterGpiod.instance.chips[3]; + final chip = FlutterGpiod.instance.chips[3]!; expect(chip.index, 3); expect(chip.name, 'gpiochip3'); expect(chip.label, 'aobus-banks'); @@ -859,7 +863,7 @@ void main() { }, tags: ['panda']); testWidgets('test lattepanda fifth gpio chip', (_) async { - final chip = FlutterGpiod.instance.chips[4]; + final chip = FlutterGpiod.instance.chips[4]!; expect(chip.index, 4); expect(chip.name, 'gpiochip4'); expect(chip.label, 'aobus-banks');