Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 // Copyright (c) 2015, the Fletch project authors. Please see the AUTHORS file | 1 // Copyright (c) 2015, the Fletch project authors. Please see the AUTHORS file |
| 2 // for details. All rights reserved. Use of this source code is governed by a | 2 // for details. All rights reserved. Use of this source code is governed by a |
| 3 // BSD-style license that can be found in the LICENSE.md file. | 3 // BSD-style license that can be found in the LICENSE.md file. |
| 4 | 4 |
| 5 /// GPIO support. | 5 /// GPIO support. |
| 6 /// | 6 /// |
| 7 /// Provides access to controlling GPIO pins. | 7 /// Provides access to controlling GPIO pins. |
| 8 /// | 8 /// |
| 9 /// The library provide two ways of accessing the GPIO pins: direct access or | 9 /// The library provide two ways of accessing the GPIO pins: direct access or |
| 10 /// access through a Sysfs driver. | 10 /// access through a Sysfs driver. |
| 11 /// | 11 /// |
| 12 /// When direct access is used, the physical memory addresses, where the | 12 /// When direct access is used, the physical memory addresses, where the |
| 13 /// SoC registers for the GPIO pins are mapped, are accessed directly. This | 13 /// SoC registers for the GPIO pins are mapped, are accessed directly. This |
| 14 /// always require root access. | 14 /// always require root access. |
| 15 /// | 15 /// |
| 16 /// When the Sysfs driver is used the filesystem under `/sys/class/gpio` is | 16 /// When the Sysfs driver is used the filesystem under `/sys/class/gpio` is |
| 17 /// used; root access is also required by default. However this can be changed | 17 /// used; root access is also required by default. However this can be changed |
| 18 /// through udev rules, e.g. by adding a file to ` /etc/udev/rules.d`. | 18 /// through udev rules, e.g. by adding a file to ` /etc/udev/rules.d`. |
| 19 /// In addition the Sysfs driver supports state change notifications. | 19 /// In addition the Sysfs driver supports state change notifications. |
| 20 /// | 20 /// |
| 21 /// Currently this has only been tested with a Raspberry Pi 2. | 21 /// Currently this has only been tested with a Raspberry Pi 2. |
| 22 library gpio; | 22 library gpio; |
| 23 | 23 |
| 24 import 'dart:fletch.ffi'; | 24 import 'dart:fletch.ffi'; |
| 25 import 'dart:typed_data'; | 25 import 'dart:typed_data'; |
| 26 | 26 |
| 27 import 'package:file/file.dart'; | 27 import 'package:file/file.dart'; |
| 28 | 28 |
| 29 // Foreign functions used. | 29 // Foreign functions used. |
| 30 final ForeignFunction _open = ForeignLibrary.main.lookup('open'); | |
| 31 final ForeignFunction _lseek = ForeignLibrary.main.lookup('lseek'); | 30 final ForeignFunction _lseek = ForeignLibrary.main.lookup('lseek'); |
| 32 final ForeignFunction _mmap = ForeignLibrary.main.lookup('mmap'); | |
| 33 final ForeignFunction _poll = ForeignLibrary.main.lookup('poll'); | 31 final ForeignFunction _poll = ForeignLibrary.main.lookup('poll'); |
| 34 | 32 |
| 35 /// GPIO modes. | 33 /// GPIO modes. |
| 36 enum Mode { | 34 enum Mode { |
| 37 /// GPIO pin input mode. | 35 /// GPIO pin input mode. |
| 38 input, | 36 input, |
| 39 /// GPIO pin output mode. | 37 /// GPIO pin output mode. |
| 40 output, | 38 output, |
| 41 /// GPIO has other functions than `input` and `output`. Most GPIO pins have | 39 /// GPIO has other functions than `input` and `output`. Most GPIO pins have |
| 42 /// several special functions, so when receiving the mode this value can be | 40 /// several special functions, so when receiving the mode this value can be |
| 43 /// returned. This value cannot be used when setting the mode. | 41 /// returned. This value cannot be used when setting the mode. |
| 44 other | 42 other |
| 45 } | 43 } |
| 46 | 44 |
| 47 /// Base GPIO interface supported by all GPIO implementations. | 45 /// Base GPIO interface supported by all GPIO implementations. |
| 48 abstract class GPIO { | 46 abstract class GPIO { |
| 47 /// The default number of pins for GPIO is 50. | |
| 48 static const int defaultPins = 50; | |
| 49 | |
| 50 /// Number of pins exposed by this GPIO. | |
| 51 int get pins; | |
| 52 | |
| 49 /// Set the mode of [pin] to [mode]. | 53 /// Set the mode of [pin] to [mode]. |
| 50 void setMode(int pin, Mode mode); | 54 void setMode(int pin, Mode mode); |
| 51 | 55 |
| 52 /// Get the current mode of [pin]. | 56 /// Get the current mode of [pin]. |
| 53 Mode getMode(int pin); | 57 Mode getMode(int pin); |
| 54 | 58 |
| 55 /// Set the value of the [pin] to [value]. The boolean value | 59 /// Set the value of the [pin] to [value]. The boolean value |
| 56 /// [true] represents high (1) and the boolean value [false] | 60 /// [true] represents high (1) and the boolean value [false] |
| 57 /// represents low (0). | 61 /// represents low (0). |
| 58 void setPin(int pin, bool value); | 62 void setPin(int pin, bool value); |
| 59 | 63 |
| 60 /// Get the value of the [pin]. The boolean value | 64 /// Get the value of the [pin]. The boolean value |
| 61 /// [true] represents high (1) and the boolean value [false] | 65 /// [true] represents high (1) and the boolean value [false] |
| 62 /// represents low (0). | 66 /// represents low (0). |
| 63 bool getPin(int pin); | 67 bool getPin(int pin); |
| 64 } | 68 } |
| 65 | 69 |
| 66 /// Pull-up/down resistor state. | 70 // Internal base class. |
| 67 enum PullUpDown { | 71 abstract class GPIOBase implements GPIO { |
| 68 floating, | 72 // Number of GPIO pins. |
| 69 pullDown, | 73 final int _pins; |
| 70 pullUp, | |
| 71 } | |
| 72 | 74 |
| 73 // Internal base class. | 75 GPIOBase(this._pins); |
| 74 class _GPIOBase { | |
| 75 // Number of GPIO pins. | |
| 76 final int _maxPins; | |
| 77 | 76 |
| 78 _GPIOBase(this._maxPins); | 77 get pins => _pins; |
| 79 | 78 |
| 80 void _checkPinRange(int pin) { | 79 void checkPinRange(int pin) { |
| 81 if (pin < 0 || _maxPins <= pin) { | 80 if (pin < 0 || _pins <= pin) { |
| 82 throw new RangeError.index(pin, this, 'pin', null, _maxPins); | 81 throw new RangeError.index(pin, this, 'pin', null, _pins); |
| 83 } | 82 } |
| 84 } | 83 } |
| 85 } | 84 } |
| 86 | 85 |
| 87 /// Provide GPIO access on Raspberry Pi using direct memory access. | |
| 88 /// | |
| 89 /// The following code shows how to turn on GPIO pin 4: | |
| 90 /// | |
| 91 /// ``` | |
| 92 /// PiMemoryMappedGPIO gpio = new PiMemoryMappedGPIO(); | |
| 93 /// gpio.setMode(4, Mode.output); | |
| 94 /// gpio.setPin(4, true)); | |
| 95 /// ``` | |
| 96 /// | |
| 97 /// The following code shows how to read GPIO pin 17: | |
| 98 /// | |
| 99 /// ``` | |
| 100 /// PiMemoryMappedGPIO gpio = new PiMemoryMappedGPIO(); | |
| 101 /// gpio.setMode(17, Mode.input); | |
| 102 /// print(gpio.getPin(17)); | |
| 103 /// ``` | |
| 104 class PiMemoryMappedGPIO extends _GPIOBase implements GPIO { | |
| 105 // See datasheet: | |
| 106 // https://www.raspberrypi.org/wp-content/uploads/2012/02/BCM2835-ARM-Peripher als.pdf | |
| 107 | |
| 108 // Raspberry Pi model 1 (A/A+/B) | |
| 109 // BCM2708 / BCM 2835 | |
| 110 | |
| 111 // Raspberry Pi model 2 | |
| 112 // BCM2709 / BCM 2836 | |
| 113 | |
| 114 // Peripherals base address. | |
| 115 static const int _baseAddressModel1 = 0x20000000; | |
| 116 static const int _baseAddressModel2 = 0x3F000000; | |
| 117 static const int _baseAddressGPIOOffset = 0x00200000; | |
| 118 | |
| 119 // Size of the peripherals area. | |
| 120 static const int _blockSize = 4096; | |
| 121 | |
| 122 // Offsets (in bytes) to various areas. | |
| 123 static const int _gpioFunctionSelectBase = 0 << 2; | |
| 124 static const int _gpioOutputSetBase = 7 << 2; | |
| 125 static const int _gpioOutputClearBase = 10 << 2; | |
| 126 static const int _gpioPinLevelBase = 13 << 2; | |
| 127 static const int _gpioPullUpPullDown = 37 << 2; | |
| 128 static const int _gpioPullUpPullDownClockBase = 38 << 2; | |
| 129 | |
| 130 // All alternative functions are mapped to `Mode.other` for now. | |
| 131 static const _functionToMode = | |
| 132 [Mode.input, Mode.output, | |
| 133 Mode.other, Mode.other, Mode.other, Mode.other, Mode.other]; | |
| 134 | |
| 135 int _fd; // File descriptor for /dev/mem. | |
| 136 ForeignPointer _addr; | |
| 137 ForeignMemory _mem; | |
| 138 | |
| 139 PiMemoryMappedGPIO(): super(54) { | |
| 140 // From /usr/include/x86_64-linux-gnu/bits/fcntl-linux.h. | |
| 141 const int oRDWR = 02; // O_RDWR | |
| 142 // Found from C code 'printf("%x\n", O_SYNC);'. | |
| 143 const int oSync = 0x101000; // O_SYNC | |
| 144 | |
| 145 // Open /dev/mem to get to the physical memory. | |
| 146 var devMem = new ForeignMemory.fromStringAsUTF8('/dev/mem'); | |
| 147 _fd = _open.icall$2Retry(devMem, oRDWR | oSync); | |
| 148 if (_fd < 0) { | |
| 149 throw new GPIOException("Failed to open '/dev/mem'", Foreign.errno); | |
| 150 } | |
| 151 devMem.free(); | |
| 152 | |
| 153 // From /usr/include/x86_64-linux-gnu/bits/mman-linux.h. | |
| 154 const int protRead = 0x1; // PROT_READ. | |
| 155 const int protWrite = 0x2; // PROT_WRITE. | |
| 156 const int mapShared = 0x01; // MAP_SHARED. | |
| 157 | |
| 158 _addr = _mmap.pcall$6(0, _blockSize, protRead | protWrite, mapShared, | |
| 159 _fd, _baseAddressModel2 + _baseAddressGPIOOffset); | |
| 160 _mem = new ForeignMemory.fromAddress(_addr.address, _blockSize); | |
| 161 } | |
| 162 | |
| 163 void setMode(int pin, Mode mode) { | |
| 164 _checkPinRange(pin); | |
| 165 // GPIO function select registers each have 3 bits for 10 pins. | |
| 166 var fsel = (pin ~/ 10); | |
| 167 var shift = (pin % 10) * 3; | |
| 168 var function = mode == Mode.input ? 0 : 1; | |
| 169 var offset = _gpioFunctionSelectBase + (fsel << 2); | |
| 170 var value = _mem.getUint32(offset); | |
| 171 value = (value & ~(0x07 << shift)) | function << shift; | |
| 172 _mem.setUint32(offset, value); | |
| 173 } | |
| 174 | |
| 175 Mode getMode(int pin) { | |
| 176 _checkPinRange(pin); | |
| 177 // GPIO function select registers each have 3 bits for 10 pins. | |
| 178 var fsel = (pin ~/ 10); | |
| 179 var shift = (pin % 10) * 3; | |
| 180 var offset = _gpioFunctionSelectBase + (fsel << 2); | |
| 181 var function = (_mem.getUint32(offset) >> shift) & 0x07; | |
| 182 return _functionToMode[function]; | |
| 183 } | |
| 184 | |
| 185 void setPin(int pin, bool value) { | |
| 186 _checkPinRange(pin); | |
| 187 // GPIO output set and output clear registers each have 1 bits for 32 pins. | |
| 188 int register = pin ~/ 32; | |
| 189 int shift = pin % 32; | |
| 190 if (value) { | |
| 191 _mem.setUint32(_gpioOutputSetBase + (register << 2), 1 << shift); | |
| 192 } else { | |
| 193 _mem.setUint32(_gpioOutputClearBase + (register << 2), 1 << shift); | |
| 194 } | |
| 195 } | |
| 196 | |
| 197 bool getPin(int pin) { | |
| 198 _checkPinRange(pin); | |
| 199 // GPIO pin level registers each have 1 bits for 32 pins. | |
| 200 int register = pin ~/ 32; | |
| 201 int shift = pin % 32; | |
| 202 return | |
| 203 (_mem.getUint32(_gpioPinLevelBase + (register << 2)) & 1 << shift) != 0; | |
| 204 } | |
| 205 | |
| 206 /// Set the floating/pull-up/pull-down state of [pin]. | |
| 207 /// | |
| 208 /// Use `0` for floating, `1` for pull down and `2` for pull-up. | |
| 209 void setPullUpDown(int pin, PullUpDown pullUpDown) { | |
| 210 _checkPinRange(pin); | |
| 211 int register = pin ~/ 32; | |
| 212 int shift = pin % 32; | |
| 213 // First set the value in the update register. | |
| 214 _mem.setUint32(_gpioPullUpPullDown, pullUpDown.index); | |
| 215 sleep(1); // Datasheet says: "Wait for 150 cycles". | |
| 216 // Then set the clock bit. | |
| 217 _mem.setUint32(_gpioPullUpPullDownClockBase + (register << 2), 1 << shift); | |
| 218 sleep(1); // Datasheet says: "Wait for 150 cycles". | |
| 219 // Clear value and clock bit. | |
| 220 _mem.setUint32(_gpioPullUpPullDown, 0); | |
| 221 _mem.setUint32(_gpioPullUpPullDownClockBase + (register << 2), 0); | |
| 222 } | |
| 223 } | |
| 224 | |
| 225 /// Interrupt triggers. | 86 /// Interrupt triggers. |
| 226 enum Trigger { | 87 enum Trigger { |
| 227 none, | 88 none, |
| 228 rising, | 89 rising, |
| 229 falling, | 90 falling, |
| 230 both, | 91 both, |
| 231 } | 92 } |
| 232 | 93 |
| 233 /// Provide GPIO access using the Sysfs Interface for Userspace. | 94 /// Provide GPIO access using the Sysfs Interface for Userspace. |
| 234 /// | 95 /// |
| (...skipping 25 matching lines...) Expand all Loading... | |
| 260 /// gpio.exportPin(17); | 121 /// gpio.exportPin(17); |
| 261 /// gpio.setMode(17, Mode.input); | 122 /// gpio.setMode(17, Mode.input); |
| 262 /// gpio.setTrigger(17, Trigger.both); | 123 /// gpio.setTrigger(17, Trigger.both); |
| 263 /// bool value = gpio.getPin(17) | 124 /// bool value = gpio.getPin(17) |
| 264 /// gpio.setPin(4, value); | 125 /// gpio.setPin(4, value); |
| 265 /// while (true) { | 126 /// while (true) { |
| 266 /// var value = gpio.waitFor(17, !value, -1); | 127 /// var value = gpio.waitFor(17, !value, -1); |
| 267 /// gpio.setPin(4, value); | 128 /// gpio.setPin(4, value); |
| 268 /// } | 129 /// } |
| 269 /// ``` | 130 /// ``` |
| 270 class SysfsGPIO extends _GPIOBase implements GPIO { | 131 class SysfsGPIO extends GPIOBase { |
| 271 // For documentation on the GPIO Sysfs Interface for Userspace see | 132 // For documentation on the GPIO Sysfs Interface for Userspace see |
| 272 // https://www.kernel.org/doc/Documentation/gpio/sysfs.txt. | 133 // https://www.kernel.org/doc/Documentation/gpio/sysfs.txt. |
| 273 static const String _basePath = '/sys/class/gpio/'; | 134 static const String _basePath = '/sys/class/gpio/'; |
| 274 | 135 |
| 275 // Cached constants. | 136 // Cached constants. |
| 276 ByteBuffer _zero; | 137 ByteBuffer _zero; |
| 277 ByteBuffer _one; | 138 ByteBuffer _one; |
| 278 ByteBuffer _in; | 139 ByteBuffer _in; |
| 279 ByteBuffer _out; | 140 ByteBuffer _out; |
| 280 ByteBuffer _none; | 141 ByteBuffer _none; |
| 281 ByteBuffer _rising; | 142 ByteBuffer _rising; |
| 282 ByteBuffer _falling; | 143 ByteBuffer _falling; |
| 283 ByteBuffer _both; | 144 ByteBuffer _both; |
| 284 | 145 |
| 285 // Open value files for all tracked pins. Indexed by pin number. | 146 // Open value files for all tracked pins. Indexed by pin number. |
| 286 List<File> _tracked; | 147 List<File> _tracked; |
| 287 | 148 |
| 288 /// Create a GPIO controller using the GPIO Sysfs Interface. | 149 /// Create a GPIO controller using the GPIO Sysfs Interface. |
| 289 SysfsGPIO([int maxPins = 54]): super(maxPins) { | 150 SysfsGPIO([int pins = GPIO.defaultPins]): super(pins) { |
|
sigurdm
2015/10/05 07:22:18
How come this changed from 54 to 50? Was the other
Søren Gjesse
2015/10/05 07:43:39
The value 50 is a pretty arbitrary default. 54 is
| |
| 290 // Byte buffers for string constants. | 151 // Byte buffers for string constants. |
| 291 var data; | 152 var data; |
| 292 data = new Uint8List(1); | 153 data = new Uint8List(1); |
| 293 data.setRange(0, 1, '0'.codeUnits); | 154 data.setRange(0, 1, '0'.codeUnits); |
| 294 _zero = data.buffer; | 155 _zero = data.buffer; |
| 295 data = new Uint8List(1); | 156 data = new Uint8List(1); |
| 296 data.setRange(0, 1, '1'.codeUnits); | 157 data.setRange(0, 1, '1'.codeUnits); |
| 297 _one = data.buffer; | 158 _one = data.buffer; |
| 298 data = new Uint8List(2); | 159 data = new Uint8List(2); |
| 299 data.setRange(0, 2, 'in'.codeUnits); | 160 data.setRange(0, 2, 'in'.codeUnits); |
| 300 _in = data.buffer; | 161 _in = data.buffer; |
| 301 data = new Uint8List(3); | 162 data = new Uint8List(3); |
| 302 data.setRange(0, 3, 'out'.codeUnits); | 163 data.setRange(0, 3, 'out'.codeUnits); |
| 303 _out = data.buffer; | 164 _out = data.buffer; |
| 304 data = new Uint8List(4); | 165 data = new Uint8List(4); |
| 305 data.setRange(0, 4, 'none'.codeUnits); | 166 data.setRange(0, 4, 'none'.codeUnits); |
| 306 _none = data.buffer; | 167 _none = data.buffer; |
| 307 data = new Uint8List(6); | 168 data = new Uint8List(6); |
| 308 data.setRange(0, 6, 'rising'.codeUnits); | 169 data.setRange(0, 6, 'rising'.codeUnits); |
| 309 _rising = data.buffer; | 170 _rising = data.buffer; |
| 310 data = new Uint8List(7); | 171 data = new Uint8List(7); |
| 311 data.setRange(0, 7, 'falling'.codeUnits); | 172 data.setRange(0, 7, 'falling'.codeUnits); |
| 312 _falling = data.buffer; | 173 _falling = data.buffer; |
| 313 data = new Uint8List(4); | 174 data = new Uint8List(4); |
| 314 data.setRange(0, 4, 'both'.codeUnits); | 175 data.setRange(0, 4, 'both'.codeUnits); |
| 315 _both = data.buffer; | 176 _both = data.buffer; |
| 316 | 177 |
| 317 // Find the exported pins by just running through all trying to open | 178 // Find the exported pins by just running through all trying to open |
| 318 // the value file. | 179 // the value file. |
| 319 _tracked = new List<File>(_maxPins); | 180 _tracked = new List<File>(pins); |
| 320 for (int pin = 0; pin < _maxPins; pin++) { | 181 for (int pin = 0; pin < pins; pin++) { |
| 321 _track(pin); | 182 _track(pin); |
| 322 } | 183 } |
| 323 } | 184 } |
| 324 | 185 |
| 325 void _track(int pin) { | 186 void _track(int pin) { |
| 326 _untrack(pin); | 187 _untrack(pin); |
| 327 var f; | 188 var f; |
| 328 try { | 189 try { |
| 329 // Open the value file for this pin. | 190 // Open the value file for this pin. |
| 330 f = new File.open('${_basePath}gpio${pin}/value', mode: File.WRITE); | 191 f = new File.open('${_basePath}gpio${pin}/value', mode: File.WRITE); |
| 331 } catch (e) { | 192 } catch (e) { |
| 332 // Ignore pins which cannot be opened. | 193 // Ignore pins which cannot be opened. |
| 333 return; | 194 return; |
| 334 } | 195 } |
| 335 _tracked[pin] = f; | 196 _tracked[pin] = f; |
| 336 } | 197 } |
| 337 | 198 |
| 338 void _untrack(pin) { | 199 void _untrack(pin) { |
| 339 if (_tracked[pin] != null) { | 200 if (_tracked[pin] != null) { |
| 340 _tracked[pin].close(); | 201 _tracked[pin].close(); |
| 341 _tracked[pin] = null; | 202 _tracked[pin] = null; |
| 342 } | 203 } |
| 343 } | 204 } |
| 344 | 205 |
| 345 void _checkTracked(int pin) { | 206 void _checkTracked(int pin) { |
| 346 _checkPinRange(pin); | 207 checkPinRange(pin); |
| 347 if (!isTracked(pin)) throw 'Pin $pin is not tracked'; | 208 if (!isTracked(pin)) throw 'Pin $pin is not tracked'; |
| 348 } | 209 } |
| 349 | 210 |
| 350 /// Returns a list with the pins currently tracked. | 211 /// Returns a list with the pins currently tracked. |
| 351 List tracked() { | 212 List tracked() { |
| 352 var result = []; | 213 var result = []; |
| 353 for (int pin = 0; pin < _maxPins; pin++) { | 214 for (int pin = 0; pin < pins; pin++) { |
| 354 if (_tracked[pin] != null) result.add(pin); | 215 if (_tracked[pin] != null) result.add(pin); |
| 355 } | 216 } |
| 356 return result; | 217 return result; |
| 357 } | 218 } |
| 358 | 219 |
| 359 /// Checks if [pin] is tracked. | 220 /// Checks if [pin] is tracked. |
| 360 bool isTracked(int pin) { | 221 bool isTracked(int pin) { |
| 361 return _tracked[pin] != null; | 222 return _tracked[pin] != null; |
| 362 } | 223 } |
| 363 | 224 |
| (...skipping 107 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 471 var f = new File.open('${_basePath}${exportOrUnexport}', | 332 var f = new File.open('${_basePath}${exportOrUnexport}', |
| 472 mode: File.WRITE_ONLY); | 333 mode: File.WRITE_ONLY); |
| 473 var value = '$pin'.codeUnits; | 334 var value = '$pin'.codeUnits; |
| 474 var bytes = new Uint8List(value.length); | 335 var bytes = new Uint8List(value.length); |
| 475 bytes.setRange(0, value.length, value); | 336 bytes.setRange(0, value.length, value); |
| 476 f.write(bytes.buffer); | 337 f.write(bytes.buffer); |
| 477 f.close(); | 338 f.close(); |
| 478 } | 339 } |
| 479 | 340 |
| 480 void exportPin(int pin) { | 341 void exportPin(int pin) { |
| 481 _checkPinRange(pin); | 342 checkPinRange(pin); |
| 482 // If already exported do nothing. | 343 // If already exported do nothing. |
| 483 if (isTracked(pin)) return; | 344 if (isTracked(pin)) return; |
| 484 _exportUnexport(true, pin); | 345 _exportUnexport(true, pin); |
| 485 _track(pin); // This is now tracked. | 346 _track(pin); // This is now tracked. |
| 486 } | 347 } |
| 487 | 348 |
| 488 void unexportPin(int pin) { | 349 void unexportPin(int pin) { |
| 489 _checkPinRange(pin); | 350 checkPinRange(pin); |
| 490 // If not exported do nothing. | 351 // If not exported do nothing. |
| 491 if (!isTracked(pin)) return; | 352 if (!isTracked(pin)) return; |
| 492 _exportUnexport(false, pin); | 353 _exportUnexport(false, pin); |
| 493 _untrack(pin); // This is no longer tracked. | 354 _untrack(pin); // This is no longer tracked. |
| 494 } | 355 } |
| 495 } | 356 } |
| 496 | 357 |
| 497 /// Exceptions thrown by GPIO. | 358 /// Exceptions thrown by GPIO. |
| 498 class GPIOException implements Exception { | 359 class GPIOException implements Exception { |
| 499 /// Exception message. | 360 /// Exception message. |
| 500 final String message; | 361 final String message; |
| 501 /// OS error number if any. | 362 /// OS error number if any. |
| 502 final int errno; | 363 final int errno; |
| 503 const GPIOException(this.message, [this.errno]); | 364 const GPIOException(this.message, [this.errno]); |
| 504 String toString() { | 365 String toString() { |
| 505 if (errno == null) return message; | 366 if (errno == null) return message; |
| 506 return '$message (error ${errno})'; | 367 return '$message (error ${errno})'; |
| 507 } | 368 } |
| 508 } | 369 } |
| OLD | NEW |