Updated with new methods

This commit is contained in:
Yorick
2016-04-05 11:42:41 +00:00
parent 2bdcf41538
commit 75b1a4c9bf
71 changed files with 14055 additions and 403 deletions
+1
View File
@@ -0,0 +1 @@
node_modules/
Generated Vendored
+20
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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);
});
});
});
});