forked from fivdi/onoff
-
Notifications
You must be signed in to change notification settings - Fork 0
/
onoff.js
348 lines (309 loc) · 10.6 KB
/
onoff.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
"use strict";
var fs = require('fs'),
Epoll = require('epoll').Epoll;
var GPIO_ROOT_PATH = '/sys/class/gpio/',
ZERO = new Buffer('0'),
ONE = new Buffer('1');
exports.version = '1.1.1';
function pollerEventHandler(err, fd, events) {
var value = this.readSync(),
callbacks = this.listeners.slice(0);
if (this.opts.debounceTimeout > 0) {
setTimeout(function () {
if (this.listeners.length > 0) {
// Read current value before polling to prevent unauthentic interrupts.
this.readSync();
this.poller.modify(this.valueFd, Epoll.EPOLLPRI | Epoll.EPOLLONESHOT);
}
}.bind(this), this.opts.debounceTimeout);
}
callbacks.forEach(function (callback) {
callback(err, value);
});
}
/**
* Constructor. Exports a GPIO to userspace.
*
* The constructor is written to function for both superusers and
* non-superusers. See README.md for more details.
*
* gpio: number // The Linux GPIO identifier; an unsigned integer.
* direction: string // Specifies whether the GPIO should be configured as an
* // input or output. The valid values are: 'in', 'out',
* // 'high', and 'low'. 'high' and 'low' are variants of
* // 'out' that configure the GPIO as an output with an
* // initial level of high or low respectively.
* [edge: string] // The interrupt generating edge for the GPIO. Can be
* // specified for GPIO inputs and outputs. The edge
* // specified determine what watchers watch for. The valid
* // values are: 'none', 'rising', 'falling' or 'both'.
* // The default value is 'none'. [optional]
* [options: object] // Additional options. [optional]
*
* The options argument supports the following:
* debounceTimeout: number // Can be used to software debounce a button or
* // switch using a timeout. Specified in
* // milliseconds. The default value is 0.
* activeLow: boolean // Specifies whether the values read from or
* // written to the GPIO should be inverted. The
* // interrupt generating edge for the GPIO also
* // follow this this setting. The valid values for
* // activeLow are true and false. Setting activeLow
* // to true inverts. The default value is false.
*/
function Gpio(gpio, direction, edge, options) {
var valuePath,
directionSet = false,
tries = 0;
if (!(this instanceof Gpio)) {
return new Gpio(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.opts = {};
this.opts.debounceTimeout = options.debounceTimeout || 0;
this.readBuffer = new Buffer(16);
this.listeners = [];
valuePath = this.gpioPath + 'value';
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.
directionSet = false;
while (!directionSet) {
try {
tries += 1;
fs.writeFileSync(this.gpioPath + 'direction', direction);
directionSet = true;
} catch (e) {
if (tries === 10000) {
throw e;
}
}
}
if (edge) {
fs.writeFileSync(this.gpioPath + 'edge', edge);
}
if (!!options.activeLow) {
fs.writeFileSync(this.gpioPath + 'active_low', ONE);
}
} 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 {
if (edge) {
fs.writeFileSync(this.gpioPath + 'edge', edge);
}
try {
fs.writeFileSync(this.gpioPath + 'active_low',
!!options.activeLow ? ONE : ZERO
);
} catch (ignore) {
}
} catch (ignore) {
}
}
this.valueFd = fs.openSync(valuePath, 'r+'); // Cache fd for performance.
// Read current value before polling to prevent unauthentic interrupts.
this.readSync();
this.poller = new Epoll(pollerEventHandler.bind(this));
}
exports.Gpio = Gpio;
/**
* Read GPIO value asynchronously.
*
* [callback: (err: error, value: number) => {}] // Optional callback
*/
Gpio.prototype.read = function (callback) {
fs.read(this.valueFd, this.readBuffer, 0, 1, 0, function (err, bytes, buf) {
if (typeof callback === 'function') {
if (err) {
return callback(err);
}
callback(null, buf[0] === ONE[0] ? 1 : 0);
}
});
};
/**
* Read GPIO value synchronously.
*
* Returns - number // 0 or 1
*/
Gpio.prototype.readSync = function () {
fs.readSync(this.valueFd, this.readBuffer, 0, 1, 0);
return this.readBuffer[0] === ONE[0] ? 1 : 0;
};
/**
* Write GPIO value asynchronously.
*
* value: number // 0 or 1
* [callback: (err: error) => {}] // Optional callback
*/
Gpio.prototype.write = function (value, callback) {
var writeBuffer = value === 1 ? ONE : ZERO;
fs.write(this.valueFd, writeBuffer, 0, writeBuffer.length, 0, callback);
};
/**
* Write GPIO value synchronously.
*
* value: number // 0 or 1
*/
Gpio.prototype.writeSync = function (value) {
var writeBuffer = value === 1 ? ONE : ZERO;
fs.writeSync(this.valueFd, writeBuffer, 0, writeBuffer.length, 0);
};
/**
* Watch for hardware interrupts on the GPIO. Inputs and outputs can be
* watched. The edge argument that was passed to the constructor determines
* which hardware interrupts are watcher for.
*
* Note that the value passed to the callback does not represent the value of
* the GPIO the instant the interrupt occured, it represents the value of the
* GPIO the instant the GPIO value file is read which may be several
* milliseconds after the actual interrupt. By the time the GPIO value is read
* the value may have changed. There are scenarios where this is likely to
* occur, for example, with buttons or switches that are not hadrware
* debounced.
*
* callback: (err: error, value: number) => {}
*/
Gpio.prototype.watch = function (callback) {
var events;
this.listeners.push(callback);
if (this.listeners.length === 1) {
events = Epoll.EPOLLPRI;
if (this.opts.debounceTimeout > 0) {
events |= Epoll.EPOLLONESHOT;
}
this.poller.add(this.valueFd, events);
}
};
/**
* Stop watching for hardware interrupts on the GPIO.
*/
Gpio.prototype.unwatch = function (callback) {
if (this.listeners.length > 0) {
if (typeof callback !== 'function') {
this.listeners = [];
} else {
this.listeners = this.listeners.filter(function (listener) {
return callback !== listener;
});
}
if (this.listeners.length === 0) {
this.poller.remove(this.valueFd);
}
}
};
/**
* Remove all watchers for the GPIO.
*/
Gpio.prototype.unwatchAll = function () {
this.unwatch();
};
/**
* Get GPIO direction.
*
* Returns - string // 'in', or 'out'
*/
Gpio.prototype.direction = function () {
return fs.readFileSync(this.gpioPath + 'direction').toString().trim();
};
/**
* Set GPIO direction.
*
* direction: string // Specifies whether the GPIO should be configured as an
* // input or output. The valid values are: 'in', 'out',
* // 'high', and 'low'. 'high' and 'low' are variants of
* // 'out' that configure the GPIO as an output with an
* // initial level of high or low respectively.
*/
Gpio.prototype.setDirection = function (direction) {
fs.writeFileSync(this.gpioPath + 'direction', direction);
};
/**
* Get GPIO interrupt generating edge.
*
* Returns - string // 'none', 'rising', 'falling' or 'both'
*/
Gpio.prototype.edge = function () {
return fs.readFileSync(this.gpioPath + 'edge').toString().trim();
};
/**
* Set GPIO interrupt generating edge.
*
* edge: string // The interrupt generating edge for the GPIO. Can be
* // specified for GPIO inputs and outputs. The edge
* // specified determine what watchers watch for. The valid
* // values are: 'none', 'rising', 'falling' or 'both'.
*/
Gpio.prototype.setEdge = function (edge) {
fs.writeFileSync(this.gpioPath + 'edge', edge);
};
/**
* Get GPIO activeLow setting.
*
* Returns - boolean
*/
Gpio.prototype.activeLow = function () {
return fs.readFileSync(
this.gpioPath + 'active_low')[0] === ONE[0] ? true : false;
};
/**
* Set GPIO activeLow setting.
*
* invert: boolean // Specifies whether the values read from or
* // written to the GPIO should be inverted. The
* // interrupt generating edge for the GPIO also
* // follow this this setting. The valid values for
* // activeLow are true and false. Setting activeLow
* // to true inverts. The default value is false.
*/
Gpio.prototype.setActiveLow = function (invert) {
fs.writeFileSync(this.gpioPath + 'active_low', !!invert ? ONE : ZERO);
};
/**
* Get GPIO options.
*
* Returns - object // Must not be modified
*/
Gpio.prototype.options = function () {
return this.opts;
};
/**
* Reverse the effect of exporting the GPIO to userspace. The Gpio object
* should not be used after calling this method.
*/
Gpio.prototype.unexport = function () {
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.
}
};