247 lines
6.2 KiB
JavaScript
247 lines
6.2 KiB
JavaScript
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;
|
|
|