Updated with new methods
This commit is contained in:
+1
@@ -0,0 +1 @@
|
||||
node_modules/
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
Copyright (c) 2012 Dominick Pham (dominick@dph.am)
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of this software and associated documentation files (the
|
||||
"Software"), to deal in the Software without restriction, including
|
||||
without limitation the rights to use, copy, modify, merge, publish,
|
||||
distribute, sublicense, and/or sell copies of the Software, and to
|
||||
permit persons to whom the Software is furnished to do so, subject to
|
||||
the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be
|
||||
included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
+155
@@ -0,0 +1,155 @@
|
||||
# gpio - talk to your Raspberry Pi's gpio headers
|
||||
|
||||
* demo using LED: http://www.youtube.com/watch?v=2Juo-CJ6eu4
|
||||
* demo using RC car: http://www.youtube.com/watch?v=klQdX8-YVaI
|
||||
|
||||
|
||||
## Important note
|
||||
I haven't maintained this project for a while now, and it's unlikely I will provide any updates going forward given other more mature gpio libraries out there. If you're looking for a reliable way to communicate with the raspberry pi in JavaScript, check out the [wiring-pi JavaScript library](https://www.npmjs.com/package/wiring-pi). It provides direct bindings to the fully-featured [Wiring Pi C library](http://wiringpi.com/).
|
||||
|
||||
---
|
||||
|
||||
##Installation
|
||||
##### Get node.js on your Raspberry Pi
|
||||
On Raspbian, you can simply run `apt-get install nodejs`,
|
||||
otherwise, [compile it](https://github.com/joyent/node/wiki/Installing-Node.js-via-package-manager)
|
||||
|
||||
## Usage
|
||||
|
||||
This library is an npm package, just define "gpio" in your package.json dependencies or
|
||||
```js
|
||||
npm install gpio
|
||||
```
|
||||
|
||||
##### Note: you must be running as root or have the proper priviledges to access the gpio headers
|
||||
|
||||
##### Standard setup
|
||||
|
||||
```js
|
||||
var gpio = require("gpio");
|
||||
|
||||
// Calling export with a pin number will export that header and return a gpio header instance
|
||||
var gpio4 = gpio.export(4, {
|
||||
// When you export a pin, the default direction is out. This allows you to set
|
||||
// the pin value to either LOW or HIGH (3.3V) from your program.
|
||||
direction: 'out',
|
||||
|
||||
// set the time interval (ms) between each read when watching for value changes
|
||||
// note: this is default to 100, setting value too low will cause high CPU usage
|
||||
interval: 200,
|
||||
|
||||
// Due to the asynchronous nature of exporting a header, you may not be able to
|
||||
// read or write to the header right away. Place your logic in this ready
|
||||
// function to guarantee everything will get fired properly
|
||||
ready: function() {
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
##### Header direction "in"
|
||||
If you plan to set the header voltage externally, use direction `in` and read value from your program.
|
||||
```js
|
||||
var gpio = require("gpio");
|
||||
var gpio4 = gpio.export(4, {
|
||||
direction: "in",
|
||||
ready: function() {
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
##### API Methods
|
||||
|
||||
```js
|
||||
// sets pin to high
|
||||
gpio4.set();
|
||||
```
|
||||
```js
|
||||
// sets pin to low (can also call gpio4.reset())
|
||||
gpio4.set(0);
|
||||
```
|
||||
```js
|
||||
// Since setting a value happens asynchronously, this method also takes a
|
||||
// callback argument which will get fired after the value is set
|
||||
gpio4.set(function() {
|
||||
console.log(gpio4.value); // should log 1
|
||||
});
|
||||
gpio4.set(0, function() {
|
||||
console.log(gpio4.value); // should log 0
|
||||
});
|
||||
```
|
||||
```js
|
||||
// unexport program when done
|
||||
gpio4.unexport();
|
||||
```
|
||||
|
||||
##### EventEmitter
|
||||
This library uses node's [EventEmitter](http://nodejs.org/api/events.html) which allows you to watch
|
||||
for value changes and fire a callback.
|
||||
```js
|
||||
// bind to the "change" event
|
||||
gpio4.on("change", function(val) {
|
||||
// value will report either 1 or 0 (number) when the value changes
|
||||
console.log(val)
|
||||
});
|
||||
|
||||
// you can bind multiple events
|
||||
var processPin4 = function(val) { console.log(val); };
|
||||
gpio4.on("change", processPin4);
|
||||
|
||||
// unbind a particular callback from the "change" event
|
||||
gpio4.removeListener("change", processPin4);
|
||||
|
||||
// unbind all callbacks from the "change" event
|
||||
gpio4.removeAllListeners("change");
|
||||
|
||||
// you can also manually change the direction anytime after instantiation
|
||||
gpio4.setDirection("out");
|
||||
gpio4.setDirection("in");
|
||||
```
|
||||
|
||||
## Example
|
||||
##### Cycle voltage every half a second
|
||||
```js
|
||||
var gpio = require("gpio");
|
||||
var gpio22, gpio4, intervalTimer;
|
||||
|
||||
// Flashing lights if LED connected to GPIO22
|
||||
gpio22 = gpio.export(22, {
|
||||
ready: function() {
|
||||
intervalTimer = setInterval(function() {
|
||||
gpio22.set();
|
||||
setTimeout(function() { gpio22.reset(); }, 500);
|
||||
}, 1000);
|
||||
}
|
||||
});
|
||||
|
||||
// Lets assume a different LED is hooked up to pin 4, the following code
|
||||
// will make that LED blink inversely with LED from pin 22
|
||||
gpio4 = gpio.export(4, {
|
||||
ready: function() {
|
||||
// bind to gpio22's change event
|
||||
gpio22.on("change", function(val) {
|
||||
gpio4.set(1 - val); // set gpio4 to the opposite value
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// reset the headers and unexport after 10 seconds
|
||||
setTimeout(function() {
|
||||
clearInterval(intervalTimer); // stops the voltage cycling
|
||||
gpio22.removeAllListeners('change'); // unbinds change event
|
||||
gpio22.reset(); // sets header to low
|
||||
gpio22.unexport(); // unexport the header
|
||||
|
||||
gpio4.reset();
|
||||
gpio4.unexport(function() {
|
||||
// unexport takes a callback which gets fired as soon as unexporting is done
|
||||
process.exit(); // exits your node program
|
||||
});
|
||||
}, 10000)
|
||||
```
|
||||
|
||||
|
||||
##### Controlling an RC car
|
||||
Source code here: https://github.com/EnotionZ/node-rc
|
||||
|
||||
+246
@@ -0,0 +1,246 @@
|
||||
var fs = require('fs');
|
||||
var util = require('util');
|
||||
var path = require('path');
|
||||
var EventEmitter = require('events').EventEmitter;
|
||||
var exists = fs.exists || path.exists;
|
||||
|
||||
var gpiopath = '/sys/class/gpio/';
|
||||
|
||||
var logError = function(e) { if(e) console.log(e.code, e.action, e.path); };
|
||||
var logMessage = function() { if (exports.logging) console.log.apply(console, arguments); };
|
||||
|
||||
var _write = function(str, file, fn, override) {
|
||||
if(typeof fn !== "function") fn = logError;
|
||||
fs.writeFile(file, str, function(err) {
|
||||
if(err && !override) {
|
||||
err.path = file;
|
||||
err.action = 'write';
|
||||
logError(err);
|
||||
} else {
|
||||
if(typeof fn === "function") fn();
|
||||
}
|
||||
});
|
||||
};
|
||||
var _read = function(file, fn) {
|
||||
fs.readFile(file, "utf-8", function(err, data) {
|
||||
if(err) {
|
||||
err.path = file;
|
||||
err.action = 'read';
|
||||
logError(err);
|
||||
} else {
|
||||
if(typeof fn === "function") fn(data);
|
||||
else logMessage("value: ", data);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
var _unexport = function(number, fn) {
|
||||
_write(number, gpiopath + 'unexport', function(err) {
|
||||
if(err) return logError(err);
|
||||
if(typeof fn === 'function') fn();
|
||||
}, 1);
|
||||
};
|
||||
var _export = function(n, fn) {
|
||||
if(exists(gpiopath + 'gpio'+n)) {
|
||||
// already exported, unexport and export again
|
||||
logMessage('Header already exported');
|
||||
_unexport(n, function() { _export(n, fn); });
|
||||
} else {
|
||||
logMessage('Exporting gpio' + n);
|
||||
_write(n, gpiopath + 'export', function(err) {
|
||||
// if there's an error when exporting, unexport and repeat
|
||||
if(err) _unexport(n, function() { _export(n, fn); });
|
||||
else if(typeof fn === 'function') fn();
|
||||
}, 1);
|
||||
}
|
||||
};
|
||||
var _testwrite = function(file, fn) {
|
||||
fs.open(file, 'w', function(err, fd) {
|
||||
if (err) {
|
||||
fn(false, err);
|
||||
return;
|
||||
}
|
||||
fs.close(fd, function(err){
|
||||
fn(true, null);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
// fs.watch doesn't get fired because the file never
|
||||
// gets 'accessed' when setting header via hardware
|
||||
// manually watching value changes
|
||||
var FileWatcher = function(path, interval, fn) {
|
||||
if(typeof fn === 'undefined') {
|
||||
fn = interval;
|
||||
interval = 100;
|
||||
}
|
||||
if(typeof interval !== 'number') return false;
|
||||
if(typeof fn !== 'function') return false;
|
||||
|
||||
var value;
|
||||
var readTimer = setInterval(function() {
|
||||
_read(path, function(val) {
|
||||
if(value !== val) {
|
||||
if(typeof value !== 'undefined') fn(val);
|
||||
value = val;
|
||||
}
|
||||
});
|
||||
}, interval);
|
||||
|
||||
this.stop = function() { clearInterval(readTimer); };
|
||||
};
|
||||
|
||||
|
||||
var GPIO = function(headerNum, opts) {
|
||||
opts = opts || {};
|
||||
|
||||
var self = this;
|
||||
var dir = opts.direction;
|
||||
var interval = opts.interval;
|
||||
if(typeof interval !== 'number') interval = 100;
|
||||
this.interval = interval;
|
||||
|
||||
this.headerNum = headerNum;
|
||||
this.value = 0;
|
||||
|
||||
this.PATH = {};
|
||||
this.PATH.PIN = gpiopath + 'gpio' + headerNum + '/';
|
||||
this.PATH.VALUE = this.PATH.PIN + 'value';
|
||||
this.PATH.DIRECTION = this.PATH.PIN + 'direction';
|
||||
|
||||
this.export(function() {
|
||||
var onSuccess = function() {
|
||||
self.setDirection(dir, function () {
|
||||
if(typeof opts.ready === 'function') opts.ready.call(self);
|
||||
});
|
||||
};
|
||||
var attempts = 0;
|
||||
var makeAttempt = function() {
|
||||
attempts += 1;
|
||||
_testwrite(self.PATH.DIRECTION, function(success, err){
|
||||
if (success) {
|
||||
onSuccess();
|
||||
} else {
|
||||
logMessage('Could not write to pin: ' + err.code);
|
||||
if (attempts <= 5) {
|
||||
logMessage('Trying again in 100ms');
|
||||
setTimeout(makeAttempt, 100);
|
||||
} else {
|
||||
logMessage('Failed to access pin after 5 attempts. Giving up.');
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
makeAttempt();
|
||||
});
|
||||
};
|
||||
|
||||
util.inherits(GPIO, EventEmitter);
|
||||
|
||||
|
||||
/**
|
||||
* Export and unexport gpio#, takes callback which fires when operation is completed
|
||||
*/
|
||||
GPIO.prototype.export = function(fn) { _export(this.headerNum, fn); };
|
||||
GPIO.prototype.unexport = function(fn) {
|
||||
if(this.valueWatcher) this.valueWatcher.stop();
|
||||
_unexport(this.headerNum, fn);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Sets direction, default is "out"
|
||||
*/
|
||||
GPIO.prototype.setDirection = function(dir, fn) {
|
||||
var self = this, path = this.PATH.DIRECTION;
|
||||
if(typeof dir !== "string" || dir !== "in") dir = "out";
|
||||
this.direction = dir;
|
||||
|
||||
logMessage('Setting direction "' + dir + '" on gpio' + this.headerNum);
|
||||
|
||||
function watch () {
|
||||
if(dir === 'in') {
|
||||
if (!self.valueWatcher) {
|
||||
// watch for value changes only for direction "in"
|
||||
// since we manually trigger event for "out" direction when setting value
|
||||
self.valueWatcher = new FileWatcher(self.PATH.VALUE, self.interval, function(val) {
|
||||
val = parseInt(val, 10);
|
||||
self.value = val;
|
||||
self.emit("valueChange", val);
|
||||
self.emit("change", val);
|
||||
});
|
||||
}
|
||||
} else {
|
||||
// if direction is "out", try to clear the valueWatcher
|
||||
if(self.valueWatcher) {
|
||||
self.valueWatcher.stop();
|
||||
self.valueWatcher = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
_read(path, function(currDir) {
|
||||
var changedDirection = false;
|
||||
if(currDir.indexOf(dir) !== -1) {
|
||||
logMessage('Current direction is already ' + dir);
|
||||
logMessage('Attempting to set direction anyway.');
|
||||
} else {
|
||||
changedDirection = true;
|
||||
}
|
||||
_write(dir, path, function() {
|
||||
watch();
|
||||
|
||||
if(typeof fn === 'function') fn();
|
||||
if (changedDirection) {
|
||||
self.emit('directionChange', dir);
|
||||
}
|
||||
}, 1);
|
||||
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Internal getter, stores value
|
||||
*/
|
||||
GPIO.prototype._get = function(fn) {
|
||||
var self = this, currVal = this.value;
|
||||
|
||||
if(this.direction === 'out') return currVal;
|
||||
|
||||
_read(this.PATH.VALUE, function(val) {
|
||||
val = parseInt(val, 10);
|
||||
if(val !== currVal) {
|
||||
self.value = val;
|
||||
if(typeof fn === "function") fn.call(this, self.value);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Sets the value. If v is specified as 0 or '0', reset will be called
|
||||
*/
|
||||
GPIO.prototype.set = function(v, fn) {
|
||||
var self = this;
|
||||
var callback = typeof v === 'function' ? v : fn;
|
||||
if(typeof v !== "number" || v !== 0) v = 1;
|
||||
|
||||
// if direction is out, just emit change event since we can reliably predict
|
||||
// if the value has changed; we don't have to rely on watching a file
|
||||
if(this.direction === 'out') {
|
||||
if(this.value !== v) {
|
||||
_write(v, this.PATH.VALUE, function() {
|
||||
self.value = v;
|
||||
self.emit('valueChange', v);
|
||||
self.emit('change', v);
|
||||
if(typeof callback === 'function') callback(self.value, true);
|
||||
});
|
||||
} else {
|
||||
if(typeof callback === 'function') callback(this.value, false);
|
||||
}
|
||||
}
|
||||
};
|
||||
GPIO.prototype.reset = function(fn) { this.set(0, fn); };
|
||||
|
||||
exports.logging = false;
|
||||
exports.export = function(headerNum, direction) { return new GPIO(headerNum, direction); };
|
||||
exports.unexport = _unexport;
|
||||
|
||||
+56
@@ -0,0 +1,56 @@
|
||||
{
|
||||
"name": "gpio",
|
||||
"version": "0.2.7",
|
||||
"author": {
|
||||
"name": "Dominick Pham",
|
||||
"email": "dominick@dph.am",
|
||||
"url": "http://dph.am"
|
||||
},
|
||||
"description": "Talk to your Raspberry PI's general purpose inputs and outputs",
|
||||
"keywords": [
|
||||
"gpio",
|
||||
"raspberry",
|
||||
"pi"
|
||||
],
|
||||
"main": "./lib/gpio.js",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git://github.com/EnotionZ/GpiO.git"
|
||||
},
|
||||
"devDependencies": {
|
||||
"sinon": "*"
|
||||
},
|
||||
"licenses": [
|
||||
{
|
||||
"type": "MIT",
|
||||
"url": "https://raw.github.com/EnotionZ/GpiO/master/LICENSE"
|
||||
}
|
||||
],
|
||||
"gitHead": "a8e3f5c72df067462b25343918af2718712cda17",
|
||||
"bugs": {
|
||||
"url": "https://github.com/EnotionZ/GpiO/issues"
|
||||
},
|
||||
"homepage": "https://github.com/EnotionZ/GpiO",
|
||||
"_id": "gpio@0.2.7",
|
||||
"scripts": {},
|
||||
"_shasum": "bf386c88961efd0a4f6a0ff7167ac0ff3997eab9",
|
||||
"_from": "gpio@*",
|
||||
"_npmVersion": "2.5.1",
|
||||
"_nodeVersion": "1.2.0",
|
||||
"_npmUser": {
|
||||
"name": "dph",
|
||||
"email": "dominick@dph.am"
|
||||
},
|
||||
"maintainers": [
|
||||
{
|
||||
"name": "dph",
|
||||
"email": "dominick@dph.am"
|
||||
}
|
||||
],
|
||||
"dist": {
|
||||
"shasum": "bf386c88961efd0a4f6a0ff7167ac0ff3997eab9",
|
||||
"tarball": "https://registry.npmjs.org/gpio/-/gpio-0.2.7.tgz"
|
||||
},
|
||||
"directories": {},
|
||||
"_resolved": "https://registry.npmjs.org/gpio/-/gpio-0.2.7.tgz"
|
||||
}
|
||||
+135
@@ -0,0 +1,135 @@
|
||||
var fs = require('fs');
|
||||
var assert = require('assert');
|
||||
var sinon = require('sinon');
|
||||
var gpio = require('../lib/gpio');
|
||||
|
||||
function read(file, fn) {
|
||||
fs.readFile(file, "utf-8", function(err, data) {
|
||||
if(!err && typeof fn === "function") fn(data);
|
||||
});
|
||||
}
|
||||
|
||||
// remove whitespace
|
||||
function rmws(str) {
|
||||
return str.replace(/\s+/g, '');
|
||||
}
|
||||
|
||||
describe('GPIO', function() {
|
||||
|
||||
var gpio4;
|
||||
|
||||
before(function(done) {
|
||||
gpio4 = gpio.export(4, {
|
||||
direction: 'out',
|
||||
ready: done
|
||||
});
|
||||
});
|
||||
|
||||
after(function() {
|
||||
gpio4.unexport();
|
||||
});
|
||||
|
||||
describe('Header Direction Out', function() {
|
||||
|
||||
describe('initializing', function() {
|
||||
it('should open specified header', function(done) {
|
||||
read('/sys/class/gpio/gpio4/direction', function(val) {
|
||||
assert.equal(rmws(val), 'out');
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('#set', function() {
|
||||
it('should set header value to high', function(done) {
|
||||
gpio4.set(function() {
|
||||
read('/sys/class/gpio/gpio4/value', function(val) {
|
||||
assert.equal(rmws(val), '1');
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('#reset', function() {
|
||||
it('should set header value to low', function(done) {
|
||||
gpio4.reset(function() {
|
||||
read('/sys/class/gpio/gpio4/value', function(val) {
|
||||
assert.equal(rmws(val), '0');
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('#on :change', function() {
|
||||
it('should fire callback when value changes', function(done) {
|
||||
var callback = sinon.spy();
|
||||
gpio4.on('change', callback);
|
||||
|
||||
// set, then reset
|
||||
gpio4.set(function() { gpio4.reset(); });
|
||||
|
||||
// set and reset is async, wait some time before running assertions
|
||||
setTimeout(function() {
|
||||
assert.ok(callback.calledTwice);
|
||||
done();
|
||||
gpio4.removeListener('change', callback);
|
||||
}, 10);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// For these tests, make sure header 4 is connected to header 25
|
||||
// header 25 is exported with direction "out" and header 4 is used
|
||||
// to simulate a hardware interrupt
|
||||
describe('Header Direction In', function() {
|
||||
|
||||
var gpio25;
|
||||
|
||||
before(function(done) {
|
||||
gpio25 = gpio.export(25, {
|
||||
direction: 'in',
|
||||
ready: done
|
||||
});
|
||||
});
|
||||
after(function() {
|
||||
gpio25.unexport();
|
||||
});
|
||||
|
||||
describe('#on :change', function() {
|
||||
it('should respond to hardware set', function(done) {
|
||||
var callback = sinon.spy();
|
||||
gpio25.on('change', callback);
|
||||
|
||||
// wait a little before setting
|
||||
setTimeout(function() { gpio4.set(); }, 500);
|
||||
|
||||
// filewatcher has default interval of 100ms
|
||||
setTimeout(function() {
|
||||
assert.equal(gpio25.value, 1);
|
||||
assert.ok(callback.calledOnce);
|
||||
gpio25.removeListener('change', callback);
|
||||
done();
|
||||
}, 600);
|
||||
});
|
||||
|
||||
it('should respond to hardware reset', function(done) {
|
||||
var callback = sinon.spy();
|
||||
gpio25.on('change', callback);
|
||||
|
||||
// wait a little before setting
|
||||
setTimeout(function() { gpio4.reset(); }, 500);
|
||||
|
||||
// filewatcher has default interval of 100ms
|
||||
setTimeout(function() {
|
||||
assert.equal(gpio25.value, 0);
|
||||
assert.ok(callback.calledOnce);
|
||||
gpio25.removeListener('change', callback);
|
||||
done();
|
||||
}, 600);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
Reference in New Issue
Block a user