├── .gitignore ├── package.json ├── LICENSE ├── example ├── index.js ├── input.js └── output.js ├── test └── tests.js ├── README.md └── lib └── gpio.js /.gitignore: -------------------------------------------------------------------------------- 1 | *~ 2 | tmp/ 3 | node_modules/ 4 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "gpio", 3 | "version": "0.2.10", 4 | "author": { 5 | "name": "Dominick Pham", 6 | "email": "dominick@dph.am", 7 | "url": "http://dph.am" 8 | }, 9 | "description": "Talk to your Single Board Computer's general purpose inputs and outputs", 10 | "keywords": [ 11 | "artik", 12 | "gpio", 13 | "pi", 14 | "raspberry", 15 | "rpi", 16 | "sbc", 17 | "sysfs" 18 | ], 19 | "main": "./lib/gpio.js", 20 | "scripts": { 21 | "start": "NODE_PATH=lib/ node example" 22 | }, 23 | "repository": "git://github.com/EnotionZ/GpiO.git", 24 | "devDependencies": { 25 | "sinon": "*" 26 | }, 27 | "licenses": [ 28 | { 29 | "type": "MIT", 30 | "url": "https://raw.github.com/EnotionZ/GpiO/master/LICENSE" 31 | } 32 | ] 33 | } 34 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2012 Dominick Pham (dominick@dph.am) 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /example/index.js: -------------------------------------------------------------------------------- 1 | // -*- mode: js; js-indent-level:2; -*- 2 | // Copyright: 2018-present Samsung Electronics France and other contributors 3 | //{ 4 | // Permission is hereby granted, free of charge, to any person obtaining 5 | // a copy of this software and associated documentation files (the 6 | // "Software"), to deal in the Software without restriction, including 7 | // without limitation the rights to use, copy, modify, merge, publish, 8 | // distribute, sublicense, and/or sell copies of the Software, and to 9 | // permit persons to whom the Software is furnished to do so, subject to 10 | // the following conditions: 11 | // 12 | // The above copyright notice and this permission notice shall be 13 | // included in all copies or substantial portions of the Software. 14 | // 15 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 16 | // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 17 | // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 18 | // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 19 | // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 20 | // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 21 | // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 22 | //} 23 | 24 | function main() 25 | { 26 | console.log("log: make sure to have access to /sys/class/gpio/"); 27 | var input = require('./input'); 28 | var output = require('./output'); 29 | var inconfig = process.argv[2] ? { 'pin': Number(process.argv[2]) } : null; 30 | var outconfig = process.argv[3] ? { 'pin' : Number(process.argv[3]) } : null; 31 | new input(inconfig); 32 | new output(outconfig); 33 | } 34 | 35 | module.exports = main; 36 | 37 | if (require.main === module) { 38 | main(); 39 | } 40 | -------------------------------------------------------------------------------- /example/input.js: -------------------------------------------------------------------------------- 1 | // -*- mode: js; js-indent-level:2; -*- 2 | // Copyright: 2018-present Samsung Electronics France and other contributors 3 | //{ 4 | // Permission is hereby granted, free of charge, to any person obtaining 5 | // a copy of this software and associated documentation files (the 6 | // "Software"), to deal in the Software without restriction, including 7 | // without limitation the rights to use, copy, modify, merge, publish, 8 | // distribute, sublicense, and/or sell copies of the Software, and to 9 | // permit persons to whom the Software is furnished to do so, subject to 10 | // the following conditions: 11 | // 12 | // The above copyright notice and this permission notice shall be 13 | // included in all copies or substantial portions of the Software. 14 | // 15 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 16 | // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 17 | // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 18 | // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 19 | // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 20 | // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 21 | // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 22 | //} 23 | 24 | var gpio = require("gpio"); 25 | 26 | /// SW403 on ARTIK710 interposer board 27 | var DEFAULT_INPUT_PIN = 30; 28 | 29 | var main = function(config) 30 | { 31 | var self = this; 32 | self.config = config || { 33 | pin: DEFAULT_INPUT_PIN 34 | } 35 | self.config.direction || (self.config.direction = gpio.DIRECTION.IN); 36 | console.log("log: pin" + self.config.pin + ": opening: direction: " + self.config.direction); 37 | self.port = gpio.export(self.config.pin, { 38 | direction: self.config.direction, 39 | ready: function() { 40 | console.log("log: pin" + self.config.pin + ": ready:"); 41 | this.on("change", function(val) { 42 | console.log("log: pin" + self.config.pin + ": change: " + val); 43 | }); 44 | } 45 | }); 46 | } 47 | 48 | module.exports = main; 49 | 50 | if (!module.parent) { 51 | var pin = process.argv[2] ? Number(process.argv[2]) : DEFAULT_INPUT_PIN; 52 | main({pin: pin}); 53 | } 54 | -------------------------------------------------------------------------------- /example/output.js: -------------------------------------------------------------------------------- 1 | // -*- mode: js; js-indent-level:2; -*- 2 | // Copyright: 2018-present Samsung Electronics France and other contributors 3 | //{ 4 | // Permission is hereby granted, free of charge, to any person obtaining 5 | // a copy of this software and associated documentation files (the 6 | // "Software"), to deal in the Software without restriction, including 7 | // without limitation the rights to use, copy, modify, merge, publish, 8 | // distribute, sublicense, and/or sell copies of the Software, and to 9 | // permit persons to whom the Software is furnished to do so, subject to 10 | // the following conditions: 11 | // 12 | // The above copyright notice and this permission notice shall be 13 | // included in all copies or substantial portions of the Software. 14 | // 15 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 16 | // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 17 | // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 18 | // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 19 | // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 20 | // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 21 | // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 22 | //} 23 | 24 | var gpio = require("gpio"); 25 | 26 | /// RedLed on ARTIK710 interposer board 27 | var DEFAULT_OUTPUT_PIN = 28; 28 | 29 | var main = function(config) 30 | { 31 | var self = this; 32 | self.config = config || { 33 | pin: DEFAULT_OUTPUT_PIN 34 | } 35 | self.config.direction || (self.config.direction = gpio.DIRECTION.OUT); 36 | console.log("log: pin" + self.config.pin + ": opening: direction: " + self.config.direction); 37 | self.port = gpio.export(self.config.pin, { 38 | direction: self.config.direction, 39 | ready: function() { 40 | console.log("log: pin" + self.config.pin + ": ready:"); 41 | var intervalTimer = setInterval(function() { 42 | self.value = !self.value; 43 | self.port.set(self.value); 44 | console.log("log: pin" + self.config.pin + ": change: " + self.value); 45 | }, 1000); 46 | }}); 47 | } 48 | 49 | module.exports = main; 50 | 51 | if (!module.parent) { 52 | var pin = process.argv[2] ? Number(process.argv[2]) : DEFAULT_OUTPUT_PIN; 53 | main({pin: pin }); 54 | } 55 | -------------------------------------------------------------------------------- /test/tests.js: -------------------------------------------------------------------------------- 1 | var fs = require('fs'); 2 | var assert = require('assert'); 3 | var sinon = require('sinon'); 4 | var gpio = require('../lib/gpio'); 5 | 6 | function read(file, fn) { 7 | fs.readFile(file, "utf-8", function(err, data) { 8 | if(!err && typeof fn === "function") fn(data); 9 | }); 10 | } 11 | 12 | // remove whitespace 13 | function rmws(str) { 14 | return str.replace(/\s+/g, ''); 15 | } 16 | 17 | describe('GPIO', function() { 18 | 19 | var gpio4; 20 | 21 | before(function(done) { 22 | gpio4 = gpio.export(4, { 23 | direction: gpio.DIRECTION.OUT, 24 | ready: done 25 | }); 26 | }); 27 | 28 | after(function() { 29 | gpio4.unexport(); 30 | }); 31 | 32 | describe('Header Direction Out', function() { 33 | 34 | describe('initializing', function() { 35 | it('should open specified header', function(done) { 36 | read('/sys/class/gpio/gpio4/direction', function(val) { 37 | assert.equal(rmws(val), gpio.DIRECTION.OUT); 38 | done(); 39 | }); 40 | }); 41 | }); 42 | 43 | describe('#set', function() { 44 | it('should set header value to high', function(done) { 45 | gpio4.set(function() { 46 | read('/sys/class/gpio/gpio4/value', function(val) { 47 | assert.equal(rmws(val), '1'); 48 | done(); 49 | }); 50 | }); 51 | }); 52 | }); 53 | 54 | describe('#reset', function() { 55 | it('should set header value to low', function(done) { 56 | gpio4.reset(function() { 57 | read('/sys/class/gpio/gpio4/value', function(val) { 58 | assert.equal(rmws(val), '0'); 59 | done(); 60 | }); 61 | }); 62 | }); 63 | }); 64 | 65 | describe('#on :change', function() { 66 | it('should fire callback when value changes', function(done) { 67 | var callback = sinon.spy(); 68 | gpio4.on('change', callback); 69 | 70 | // set, then reset 71 | gpio4.set(function() { gpio4.reset(); }); 72 | 73 | // set and reset is async, wait some time before running assertions 74 | setTimeout(function() { 75 | assert.ok(callback.calledTwice); 76 | done(); 77 | gpio4.removeListener('change', callback); 78 | }, 10); 79 | }); 80 | }); 81 | }); 82 | 83 | // For these tests, make sure header 4 is connected to header 25 84 | // header 25 is exported with direction OUT and header 4 is used 85 | // to simulate a hardware interrupt 86 | describe('Header Direction In', function() { 87 | 88 | var gpio25; 89 | 90 | before(function(done) { 91 | gpio25 = gpio.export(25, { 92 | direction: gpio.DIRECTION.IN, 93 | ready: done 94 | }); 95 | }); 96 | after(function() { 97 | gpio25.unexport(); 98 | }); 99 | 100 | describe('#on :change', function() { 101 | it('should respond to hardware set', function(done) { 102 | var callback = sinon.spy(); 103 | gpio25.on('change', callback); 104 | 105 | // wait a little before setting 106 | setTimeout(function() { gpio4.set(); }, 500); 107 | 108 | // filewatcher has default interval of 100ms 109 | setTimeout(function() { 110 | assert.equal(gpio25.value, 1); 111 | assert.ok(callback.calledOnce); 112 | gpio25.removeListener('change', callback); 113 | done(); 114 | }, 600); 115 | }); 116 | 117 | it('should respond to hardware reset', function(done) { 118 | var callback = sinon.spy(); 119 | gpio25.on('change', callback); 120 | 121 | // wait a little before setting 122 | setTimeout(function() { gpio4.reset(); }, 500); 123 | 124 | // filewatcher has default interval of 100ms 125 | setTimeout(function() { 126 | assert.equal(gpio25.value, 0); 127 | assert.ok(callback.calledOnce); 128 | gpio25.removeListener('change', callback); 129 | done(); 130 | }, 600); 131 | }); 132 | }); 133 | }); 134 | 135 | }); 136 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # gpio - talk to your Single Board Computer's gpio headers 2 | 3 | [![NPM](https://nodei.co/npm/gpio.png)](https://npmjs.org/package/gpio) 4 | 5 | 6 | ## Introduction 7 | 8 | This plain JavaScript module is generic and only rely on system's *sysfs*. 9 | 10 | Please consider other (more mature) gpio libraries out there which support better your hardware, 11 | 12 | For instance, of you're looking for a reliable way to communicate with the Raspberry Pi using JavaScript, 13 | check out the [wiring-pi JavaScript library](https://www.npmjs.com/package/wiring-pi). 14 | It provides direct bindings to the fully-featured [Wiring Pi C library](http://wiringpi.com/). 15 | 16 | But if you want/need a generic lightweight module, this one can be used as fallback. 17 | 18 | ## Support 19 | 20 | Following hardware was reported to work (with some limitations or workarounds) 21 | 22 | * ARTIK10 (inputs' pull down resistors are enabled by default) 23 | * Raspberry Pi (use wiringpi's /usr/bin/gpio to change mode: gpio -g mode 11 up) 24 | 25 | 26 | ## Installation 27 | 28 | Get node.js for your SBC, 29 | If using Debian or deviates (Raspbian for RPi), you can simply run: 30 | sudo apt-get install nodejs 31 | 32 | otherwise, install from node or [compile it](https://github.com/joyent/node/wiki/Installing-Node.js-via-package-manager) 33 | 34 | ## Usage 35 | 36 | This library is an npm package, just define "gpio" in your package.json dependencies or 37 | ```js 38 | npm install gpio 39 | ``` 40 | 41 | Note: you must be have proper privileges to access the GPIO headers (or run node as root). 42 | 43 | ### Standard setup 44 | 45 | ```js 46 | var gpio = require("gpio"); 47 | 48 | // Calling export with a pin number will export that header and return a gpio header instance 49 | var gpio4 = gpio.export(4, { 50 | // When you export a pin, the default direction is out. This allows you to set 51 | // the pin value to either LOW or HIGH (3.3V) from your program. 52 | direction: gpio.DIRECTION.OUT, 53 | 54 | // set the time interval (ms) between each read when watching for value changes 55 | // note: this is default to 100, setting value too low will cause high CPU usage 56 | interval: 200, 57 | 58 | // Due to the asynchronous nature of exporting a header, you may not be able to 59 | // read or write to the header right away. Place your logic in this ready 60 | // function to guarantee everything will get fired properly 61 | ready: function() { 62 | } 63 | }); 64 | ``` 65 | 66 | ### Header direction IN 67 | 68 | If you plan to set the header voltage externally, use direction `in` and read value from your program. 69 | ```js 70 | var gpio = require("gpio"); 71 | var gpio4 = gpio.export(4, { 72 | direction: gpio.DIRECTION.IN, 73 | ready: function() { 74 | } 75 | }); 76 | ``` 77 | 78 | ## API Methods 79 | 80 | ```js 81 | // sets pin to high 82 | gpio4.set(); 83 | ``` 84 | ```js 85 | // sets pin to low (can also call gpio4.reset()) 86 | gpio4.set(0); 87 | ``` 88 | ```js 89 | // Since setting a value happens asynchronously, this method also takes a 90 | // callback argument which will get fired after the value is set 91 | gpio4.set(function() { 92 | console.log(gpio4.value); // should log 1 93 | }); 94 | gpio4.set(0, function() { 95 | console.log(gpio4.value); // should log 0 96 | }); 97 | ``` 98 | ```js 99 | // unexport program when done 100 | gpio4.unexport(); 101 | ``` 102 | 103 | ### EventEmitter 104 | 105 | This library uses node's [EventEmitter](http://nodejs.org/api/events.html) which allows you to watch 106 | for value changes and fire a callback. 107 | ```js 108 | // bind to the "change" event 109 | gpio4.on("change", function(val) { 110 | // value will report either 1 or 0 (number) when the value changes 111 | console.log(val) 112 | }); 113 | 114 | // you can bind multiple events 115 | var processPin4 = function(val) { console.log(val); }; 116 | gpio4.on("change", processPin4); 117 | 118 | // unbind a particular callback from the "change" event 119 | gpio4.removeListener("change", processPin4); 120 | 121 | // unbind all callbacks from the "change" event 122 | gpio4.removeAllListeners("change"); 123 | 124 | // you can also manually change the direction anytime after instantiation 125 | gpio4.setDirection(gpio.DIRECTION.OUT); 126 | gpio4.setDirection(gpio.DIRECTION.IN); 127 | ``` 128 | 129 | ## Example 130 | 131 | ### Cycle voltage every half a second 132 | 133 | ```js 134 | var gpio = require("gpio"); 135 | var gpio22, gpio4, intervalTimer; 136 | 137 | // Flashing lights if LED connected to GPIO22 138 | gpio22 = gpio.export(22, { 139 | ready: function() { 140 | intervalTimer = setInterval(function() { 141 | gpio22.set(); 142 | setTimeout(function() { gpio22.reset(); }, 500); 143 | }, 1000); 144 | } 145 | }); 146 | 147 | // Lets assume a different LED is hooked up to pin 4, the following code 148 | // will make that LED blink inversely with LED from pin 22 149 | gpio4 = gpio.export(4, { 150 | ready: function() { 151 | // bind to gpio22's change event 152 | gpio22.on("change", function(val) { 153 | gpio4.set(1 - val); // set gpio4 to the opposite value 154 | }); 155 | } 156 | }); 157 | 158 | // reset the headers and unexport after 10 seconds 159 | setTimeout(function() { 160 | clearInterval(intervalTimer); // stops the voltage cycling 161 | gpio22.removeAllListeners('change'); // unbinds change event 162 | gpio22.reset(); // sets header to low 163 | gpio22.unexport(); // unexport the header 164 | 165 | gpio4.reset(); 166 | gpio4.unexport(function() { 167 | // unexport takes a callback which gets fired as soon as unexporting is done 168 | process.exit(); // exits your node program 169 | }); 170 | }, 10000) 171 | ``` 172 | 173 | ## References 174 | 175 | Demos on Raspberry Pi: 176 | 177 | * Demo using LED: http://www.youtube.com/watch?v=2Juo-CJ6eu4 178 | * Demo using RC car: http://www.youtube.com/watch?v=klQdX8-YVaI 179 | * Source code here: https://github.com/EnotionZ/node-rc 180 | -------------------------------------------------------------------------------- /lib/gpio.js: -------------------------------------------------------------------------------- 1 | var fs = require('fs'); 2 | var util = require('util'); 3 | var path = require('path'); 4 | var EventEmitter = require('events').EventEmitter; 5 | var exists = fs.existsSync || path.exists; 6 | 7 | var gpiopath = '/sys/class/gpio/'; 8 | var DIRECTION = { 'IN': 'in', 'OUT': 'out' }; 9 | 10 | var logError = function(e) { if(e) console.log(e.code, e.action, e.path); }; 11 | var logMessage = function() { if (exports.logging) console.log.apply(console, arguments); }; 12 | 13 | 14 | var _write = function(str, file, fn, override) { 15 | if(typeof fn !== "function") fn = logError; 16 | fs.writeFile(file, str + "", function(err) { 17 | if(err && !override) { 18 | err.path = file; 19 | err.action = 'write'; 20 | logError(err); 21 | } else { 22 | if(typeof fn === "function") fn(); 23 | } 24 | }); 25 | }; 26 | var _read = function(file, fn) { 27 | fs.readFile(file, "utf-8", function(err, data) { 28 | if(err) { 29 | err.path = file; 30 | err.action = 'read'; 31 | logError(err); 32 | } else { 33 | if(typeof fn === "function") fn(data); 34 | else logMessage("value: ", data); 35 | } 36 | }); 37 | }; 38 | 39 | var _unexport = function(number, fn) { 40 | _write(number, gpiopath + 'unexport', function(err) { 41 | if(err) return logError(err); 42 | if(typeof fn === 'function') fn(); 43 | }, 1); 44 | }; 45 | var _export = function(n, fn) { 46 | if(exists(gpiopath + 'gpio'+n)) { 47 | // already exported, unexport and export again 48 | logMessage('Header already exported'); 49 | _unexport(n, function() { _export(n, fn); }); 50 | } else { 51 | logMessage('Exporting gpio' + n); 52 | _write(n, gpiopath + 'export', function(err) { 53 | // if there's an error when exporting, unexport and repeat 54 | if(err) _unexport(n, function() { _export(n, fn); }); 55 | else if(typeof fn === 'function') fn(); 56 | }, 1); 57 | } 58 | }; 59 | var _testwrite = function(file, fn) { 60 | fs.open(file, 'w', function(err, fd) { 61 | if (err) { 62 | fn(false, err); 63 | return; 64 | } 65 | fs.close(fd, function(err){ 66 | fn(true, null); 67 | }); 68 | }); 69 | }; 70 | 71 | // fs.watch doesn't get fired because the file never 72 | // gets 'accessed' when setting header via hardware 73 | // manually watching value changes 74 | var FileWatcher = function(path, interval, fn) { 75 | if(typeof fn === 'undefined') { 76 | fn = interval; 77 | interval = 100; 78 | } 79 | if(typeof interval !== 'number') return false; 80 | if(typeof fn !== 'function') return false; 81 | 82 | var value; 83 | var readTimer = setInterval(function() { 84 | _read(path, function(val) { 85 | if(value !== val) { 86 | if(typeof value !== 'undefined') fn(val); 87 | value = val; 88 | } 89 | }); 90 | }, interval); 91 | 92 | this.stop = function() { clearInterval(readTimer); }; 93 | }; 94 | 95 | 96 | var GPIO = function(headerNum, opts) { 97 | opts = opts || {}; 98 | 99 | var self = this; 100 | var dir = opts.direction; 101 | var interval = opts.interval; 102 | if(typeof interval !== 'number') interval = 100; 103 | this.interval = interval; 104 | 105 | this.headerNum = headerNum; 106 | this.value = 0; 107 | 108 | this.PATH = {}; 109 | this.PATH.PIN = gpiopath + 'gpio' + headerNum + '/'; 110 | this.PATH.VALUE = this.PATH.PIN + 'value'; 111 | this.PATH.DIRECTION = this.PATH.PIN + 'direction'; 112 | 113 | this.export(function() { 114 | var onSuccess = function() { 115 | self.setDirection(dir, function () { 116 | if ( dir === DIRECTION.OUT) { 117 | if(typeof opts.ready === 'function') opts.ready.call(self); 118 | } else { 119 | self.value = undefined; 120 | self._get(function(val) { 121 | self.value = val; 122 | if (typeof opts.ready === 'function') opts.ready.call(self); 123 | }); 124 | } 125 | }); 126 | }; 127 | var attempts = 0; 128 | var makeAttempt = function() { 129 | attempts += 1; 130 | _testwrite(self.PATH.DIRECTION, function(success, err){ 131 | if (success) { 132 | onSuccess(); 133 | } else { 134 | logMessage('Could not write to pin: ' + err.code); 135 | if (attempts <= 5) { 136 | logMessage('Trying again in 100ms'); 137 | setTimeout(makeAttempt, 100); 138 | } else { 139 | logMessage('Failed to access pin after 5 attempts. Giving up.'); 140 | } 141 | } 142 | }); 143 | }; 144 | makeAttempt(); 145 | }); 146 | }; 147 | 148 | util.inherits(GPIO, EventEmitter); 149 | 150 | 151 | /** 152 | * Export and unexport gpio#, takes callback which fires when operation is completed 153 | */ 154 | GPIO.prototype.export = function(fn) { _export(this.headerNum, fn); }; 155 | GPIO.prototype.unexport = function(fn) { 156 | if(this.valueWatcher) this.valueWatcher.stop(); 157 | _unexport(this.headerNum, fn); 158 | }; 159 | 160 | 161 | /** 162 | * Sets direction, default is OUT 163 | */ 164 | GPIO.prototype.setDirection = function(dir, fn) { 165 | var self = this, path = this.PATH.DIRECTION; 166 | if(typeof dir !== "string" || dir !== DIRECTION.IN) dir = DIRECTION.OUT; 167 | this.direction = dir; 168 | 169 | logMessage('Setting direction "' + dir + '" on gpio' + this.headerNum); 170 | 171 | function watch () { 172 | if(dir === DIRECTION.IN) { 173 | if (!self.valueWatcher) { 174 | // watch for value changes only for direction IN 175 | // since we manually trigger event for OUT direction when setting value 176 | self.valueWatcher = new FileWatcher(self.PATH.VALUE, self.interval, function(val) { 177 | val = parseInt(val, 10); 178 | self.value = val; 179 | self.emit("valueChange", val); 180 | self.emit("change", val); 181 | }); 182 | } 183 | } else { 184 | // if direction is OUT, try to clear the valueWatcher 185 | if(self.valueWatcher) { 186 | self.valueWatcher.stop(); 187 | self.valueWatcher = null; 188 | } 189 | } 190 | } 191 | _read(path, function(currDir) { 192 | var changedDirection = false; 193 | if(currDir.indexOf(dir) !== -1) { 194 | logMessage('Current direction is already ' + dir); 195 | logMessage('Attempting to set direction anyway.'); 196 | } else { 197 | changedDirection = true; 198 | } 199 | _write(dir, path, function() { 200 | watch(); 201 | 202 | if(typeof fn === 'function') fn(); 203 | if (changedDirection) { 204 | self.emit('directionChange', dir); 205 | } 206 | }, 1); 207 | 208 | }); 209 | }; 210 | 211 | /** 212 | * Internal getter, stores value 213 | */ 214 | GPIO.prototype._get = function(fn) { 215 | var self = this, currVal = this.value; 216 | 217 | if(this.direction === DIRECTION.OUT) return currVal; 218 | 219 | _read(this.PATH.VALUE, function(val) { 220 | val = parseInt(val, 10); 221 | if(val !== currVal) { 222 | self.value = val; 223 | if(typeof fn === "function") fn.call(this, self.value); 224 | } 225 | }); 226 | }; 227 | 228 | /** 229 | * Sets the value. If v is specified as 0 or '0', reset will be called 230 | */ 231 | GPIO.prototype.set = function(v, fn) { 232 | var self = this; 233 | var callback = typeof v === 'function' ? v : fn; 234 | if (typeof v === "boolean") v = v ? 1 : 0; 235 | if (typeof v !== "number" || v !== 0) v = 1; 236 | 237 | // if direction is out, just emit change event since we can reliably predict 238 | // if the value has changed; we don't have to rely on watching a file 239 | if(this.direction === DIRECTION.OUT) { 240 | if(this.value !== v) { 241 | _write(v, this.PATH.VALUE, function() { 242 | self.value = v; 243 | self.emit('valueChange', v); 244 | self.emit('change', v); 245 | if(typeof callback === 'function') callback(self.value, true); 246 | }); 247 | } else { 248 | if(typeof callback === 'function') callback(this.value, false); 249 | } 250 | } 251 | }; 252 | GPIO.prototype.reset = function(fn) { this.set(0, fn); }; 253 | 254 | exports.logging = false; 255 | exports.export = function(headerNum, opts) { return new GPIO(headerNum, opts); }; 256 | exports.DIRECTION = DIRECTION; 257 | exports.unexport = _unexport; 258 | 259 | --------------------------------------------------------------------------------