initial
This commit is contained in:
273
node_modules/onoff/onoff.js
generated
vendored
Normal file
273
node_modules/onoff/onoff.js
generated
vendored
Normal file
@ -0,0 +1,273 @@
|
||||
"use strict";
|
||||
|
||||
const fs = require('fs');
|
||||
const debounce = require('lodash.debounce');
|
||||
const Epoll = require('epoll').Epoll;
|
||||
|
||||
const GPIO_ROOT_PATH = '/sys/class/gpio/';
|
||||
|
||||
// fs reads and writes use Buffers
|
||||
const HIGH_BUF = Buffer.from('1');
|
||||
const LOW_BUF = Buffer.from('0');
|
||||
|
||||
// lib returns numeric data and expects numeric data as arguments
|
||||
const HIGH = 1;
|
||||
const LOW = 0;
|
||||
|
||||
class Gpio {
|
||||
constructor(gpio, direction, edge, options) {
|
||||
if (typeof edge === 'object' && !options) {
|
||||
options = edge;
|
||||
edge = undefined;
|
||||
}
|
||||
|
||||
options = options || {};
|
||||
|
||||
this._gpio = gpio;
|
||||
this._gpioPath = GPIO_ROOT_PATH + 'gpio' + this._gpio + '/';
|
||||
this._debounceTimeout = options.debounceTimeout || 0;
|
||||
this._readBuffer = Buffer.alloc(16);
|
||||
this._listeners = [];
|
||||
|
||||
if (!fs.existsSync(this._gpioPath)) {
|
||||
// The pin hasn't been exported yet so export it
|
||||
fs.writeFileSync(GPIO_ROOT_PATH + 'export', this._gpio);
|
||||
|
||||
// A hack to avoid the issue described here:
|
||||
// https://github.com/raspberrypi/linux/issues/553
|
||||
// I don't like this solution, but it enables compatibility with older
|
||||
// versions of onoff, i.e., the Gpio constructor was and still is
|
||||
// synchronous.
|
||||
let permissionRequiredPaths = [
|
||||
this._gpioPath + 'direction',
|
||||
this._gpioPath + 'active_low',
|
||||
this._gpioPath + 'value',
|
||||
];
|
||||
|
||||
// On some systems the edge file will not exist if the GPIO does not
|
||||
// support interrupts
|
||||
// https://github.com/fivdi/onoff/issues/77#issuecomment-321980735
|
||||
if (edge && direction === 'in') {
|
||||
permissionRequiredPaths.push(this._gpioPath + 'edge');
|
||||
}
|
||||
|
||||
permissionRequiredPaths.forEach((path) => {
|
||||
let tries = 0;
|
||||
|
||||
while (true) {
|
||||
try {
|
||||
tries += 1;
|
||||
const fd = fs.openSync(path, 'r+');
|
||||
fs.closeSync(fd);
|
||||
break;
|
||||
} catch (e) {
|
||||
if (tries === 10000) {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
fs.writeFileSync(this._gpioPath + 'direction', direction);
|
||||
|
||||
// On some systems writing to the edge file for an output GPIO will
|
||||
// result in an "EIO, i/o error"
|
||||
// https://github.com/fivdi/onoff/issues/87
|
||||
if (edge && direction === 'in') {
|
||||
fs.writeFileSync(this._gpioPath + 'edge', edge);
|
||||
}
|
||||
|
||||
if (!!options.activeLow) {
|
||||
fs.writeFileSync(this._gpioPath + 'active_low', HIGH_BUF);
|
||||
}
|
||||
} else {
|
||||
// The pin has already been exported, perhaps by onoff itself, perhaps
|
||||
// by quick2wire gpio-admin on the Pi, perhaps by the WiringPi gpio
|
||||
// utility on the Pi, or perhaps by something else. In any case, an
|
||||
// attempt is made to set the direction and edge to the requested
|
||||
// values here. If quick2wire gpio-admin was used for the export, the
|
||||
// user should have access to both direction and edge files. This is
|
||||
// important as gpio-admin sets niether direction nor edge. If the
|
||||
// WiringPi gpio utility was used, the user should have access to edge
|
||||
// file, but not the direction file. This is also ok as the WiringPi
|
||||
// gpio utility can set both direction and edge. If there are any
|
||||
// errors while attempting to perform the modifications, just keep on
|
||||
// truckin'.
|
||||
try {
|
||||
fs.writeFileSync(this._gpioPath + 'direction', direction);
|
||||
} catch (ignore) {
|
||||
}
|
||||
try {
|
||||
// On some systems writing to the edge file for an output GPIO will
|
||||
// result in an "EIO, i/o error"
|
||||
// https://github.com/fivdi/onoff/issues/87
|
||||
if (edge && direction === 'in') {
|
||||
fs.writeFileSync(this._gpioPath + 'edge', edge);
|
||||
}
|
||||
try {
|
||||
fs.writeFileSync(this._gpioPath + 'active_low',
|
||||
!!options.activeLow ? HIGH_BUF : LOW_BUF
|
||||
);
|
||||
} catch (ignore) {
|
||||
}
|
||||
} catch (ignore) {
|
||||
}
|
||||
}
|
||||
|
||||
// Cache fd for performance
|
||||
this._valueFd = fs.openSync(this._gpioPath + 'value', 'r+');
|
||||
|
||||
{
|
||||
// A poller is created for both inputs and outputs. A poller isn't
|
||||
// actully needed for an output but the setDirection method can be
|
||||
// invoked to change the direction of a GPIO from output to input and
|
||||
// then a poller may be needed.
|
||||
const pollerEventHandler = (err, fd, events) => {
|
||||
const value = this.readSync();
|
||||
|
||||
if ((value === LOW && this._fallingEnabled) ||
|
||||
(value === HIGH && this._risingEnabled)) {
|
||||
this._listeners.slice(0).forEach((callback) => {
|
||||
callback(err, value);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
this._risingEnabled = edge === 'both' || edge == 'rising';
|
||||
this._fallingEnabled = edge === 'both' || edge == 'falling';
|
||||
|
||||
// Read GPIO value before polling to prevent an initial unauthentic
|
||||
// interrupt
|
||||
this.readSync();
|
||||
|
||||
if (this._debounceTimeout > 0) {
|
||||
const db = debounce(pollerEventHandler, this._debounceTimeout);
|
||||
|
||||
this._poller = new Epoll((err, fd, events) => {
|
||||
this.readSync(); // Clear interrupt
|
||||
db(err, fd, events);
|
||||
});
|
||||
} else {
|
||||
this._poller = new Epoll(pollerEventHandler);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
read(callback) {
|
||||
fs.read(this._valueFd, this._readBuffer, 0, 1, 0, (err, bytes, buf) => {
|
||||
if (typeof callback === 'function') {
|
||||
if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
|
||||
callback(null, buf[0] === HIGH_BUF[0] ? HIGH : LOW);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
readSync() {
|
||||
fs.readSync(this._valueFd, this._readBuffer, 0, 1, 0);
|
||||
return this._readBuffer[0] === HIGH_BUF[0] ? HIGH : LOW;
|
||||
}
|
||||
|
||||
write(value, callback) {
|
||||
const writeBuffer = value === HIGH ? HIGH_BUF : LOW_BUF;
|
||||
fs.write(this._valueFd, writeBuffer, 0, writeBuffer.length, 0, callback);
|
||||
}
|
||||
|
||||
writeSync(value) {
|
||||
const writeBuffer = value === HIGH ? HIGH_BUF : LOW_BUF;
|
||||
fs.writeSync(this._valueFd, writeBuffer, 0, writeBuffer.length, 0);
|
||||
}
|
||||
|
||||
watch(callback) {
|
||||
this._listeners.push(callback);
|
||||
|
||||
if (this._listeners.length === 1) {
|
||||
this._poller.add(this._valueFd, Epoll.EPOLLPRI);
|
||||
}
|
||||
}
|
||||
|
||||
unwatch(callback) {
|
||||
if (this._listeners.length > 0) {
|
||||
if (typeof callback !== 'function') {
|
||||
this._listeners = [];
|
||||
} else {
|
||||
this._listeners = this._listeners.filter((listener) => {
|
||||
return callback !== listener;
|
||||
});
|
||||
}
|
||||
|
||||
if (this._listeners.length === 0) {
|
||||
this._poller.remove(this._valueFd);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
unwatchAll() {
|
||||
this.unwatch();
|
||||
}
|
||||
|
||||
direction() {
|
||||
return fs.readFileSync(this._gpioPath + 'direction').toString().trim();
|
||||
}
|
||||
|
||||
setDirection(direction) {
|
||||
fs.writeFileSync(this._gpioPath + 'direction', direction);
|
||||
}
|
||||
|
||||
edge() {
|
||||
return fs.readFileSync(this._gpioPath + 'edge').toString().trim();
|
||||
}
|
||||
|
||||
setEdge(edge) {
|
||||
fs.writeFileSync(this._gpioPath + 'edge', edge);
|
||||
|
||||
this._risingEnabled = edge === 'both' || edge == 'rising';
|
||||
this._fallingEnabled = edge === 'both' || edge == 'falling';
|
||||
}
|
||||
|
||||
activeLow() {
|
||||
return fs.readFileSync(
|
||||
this._gpioPath + 'active_low')[0] === HIGH_BUF[0] ? true : false;
|
||||
}
|
||||
|
||||
setActiveLow(invert) {
|
||||
fs.writeFileSync(this._gpioPath + 'active_low', !!invert ? HIGH_BUF : LOW_BUF);
|
||||
}
|
||||
|
||||
unexport() {
|
||||
this.unwatchAll();
|
||||
fs.closeSync(this._valueFd);
|
||||
try {
|
||||
fs.writeFileSync(GPIO_ROOT_PATH + 'unexport', this._gpio);
|
||||
} catch (ignore) {
|
||||
// Flow of control always arrives here when cape_universal is enabled on
|
||||
// the bbb.
|
||||
}
|
||||
}
|
||||
|
||||
static get accessible() {
|
||||
let fd;
|
||||
|
||||
try {
|
||||
fd = fs.openSync(GPIO_ROOT_PATH + 'export', 'w');
|
||||
} catch(e) {
|
||||
// e.code === 'ENOENT' / 'EACCES' are most common
|
||||
// though any failure to open will also result in a gpio
|
||||
// failure to export.
|
||||
return false;
|
||||
} finally {
|
||||
if (fd) {
|
||||
fs.closeSync(fd);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
Gpio.HIGH = HIGH;
|
||||
Gpio.LOW = LOW;
|
||||
|
||||
exports.Gpio = Gpio;
|
Reference in New Issue
Block a user