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;