diff --git a/config/config.inc.php b/config/config.inc.php index 24439f962..1599ccd5b 100644 --- a/config/config.inc.php +++ b/config/config.inc.php @@ -198,7 +198,13 @@ // collagetime controls the time to distinguish picture from collage in seconds $config['remotebuzzer']['collagetime'] = '2'; $config['remotebuzzer']['port'] = 14711; -$config['remotebuzzer']['pin'] = 40; +$config['remotebuzzer']['picturebutton'] = true; +$config['remotebuzzer']['picturegpio'] = 21; +$config['remotebuzzer']['collagebutton'] = false; +$config['remotebuzzer']['collagegpio'] = 20; +$config['remotebuzzer']['shutdownbutton'] = false; +$config['remotebuzzer']['shutdowngpio'] = 16; +$config['remotebuzzer']['shutdownholdtime'] = '5'; // S Y N C T O U S B S T I C K diff --git a/faq/faq.md b/faq/faq.md index 99d9e318a..bada7b735 100644 --- a/faq/faq.md +++ b/faq/faq.md @@ -126,23 +126,49 @@ sudo crontab -e
tmp
folder and defaults to io_server.log
.",
+ "manual:remotebuzzer:remotebuzzer_picturebutton": "Connect the hardware button for PICTURE to this Raspberry Pi GPIO. Defaults to GPIO21. Pull GPIO to ground for to trigger. Long-Press will trigger COLLAGE, in case COLLAGE hardware button is disabled.",
"manual:remotebuzzer:remotebuzzer_pin": "Connect the hardware trigger to this Raspberry Pi PIN number (range 1-40). Defaults to PIN 40 == GPIO21. Set to 0 (zero) for to disable the GPIO harware monitoring. Raspberry PIN layout can be found here. Please use the actual PIN numbers, not the GPIO numners. Pull PIN to ground for to trigger.",
"manual:remotebuzzer:remotebuzzer_port": "Server TCP Port - example 14711.",
+ "manual:remotebuzzer:remotebuzzer_shutdownbutton": "Connect the hardware button to SHUTDOWN Raspberry Pi. Pull GPIO to ground for to trigger. Defaults to GPIO16",
+ "manual:remotebuzzer:remotebuzzer_shutdownholdtime": "Seconds to hold button until system shutdown will be initiated. Setting to Zero (0) means immediate shutdown without waiting time.",
"manual:reset:reset_button": "Will execute config reset. If you like to also reset images and / or the mail address database make sure you first activate those settings and save (!) the config, then perform the config reset itself.",
"manual:reset:reset_remove_config": "If enabled, personal config gets removed on reset.",
"manual:reset:reset_remove_images": "If enabled, all images gets removed on reset.",
@@ -392,11 +396,14 @@
"really_delete_image": "will be deleted! This cannot be undone! Really delte picture?",
"reload": "Reload Page",
"remotebuzzer": "Remote Buzzer Server",
+ "remotebuzzer:remotebuzzer_collagebutton": "Enable GPIO for collage",
"remotebuzzer:remotebuzzer_collagetime": "Seconds to trigger collage",
"remotebuzzer:remotebuzzer_enabled": "Enable Remote Buzzer",
"remotebuzzer:remotebuzzer_logfile": "Logfile",
- "remotebuzzer:remotebuzzer_pin": "Raspberry Pi PIN number (0 to disable)",
+ "remotebuzzer:remotebuzzer_picturebutton": "Enable GPIO for pictures",
"remotebuzzer:remotebuzzer_port": "Server Port",
+ "remotebuzzer:remotebuzzer_shutdownbutton": "Enable GPIO for shutdown",
+ "remotebuzzer:remotebuzzer_shutdownholdtime": "Seconds to initiate shutdown",
"reset": "Reset",
"reset:reset_button": "Execute config reset",
"reset:reset_remove_config": "Delete personal configuration (my.config.inc.php)",
diff --git a/scripts/pack-build.js b/scripts/pack-build.js
index 950e4f25a..499033ebe 100644
--- a/scripts/pack-build.js
+++ b/scripts/pack-build.js
@@ -114,9 +114,9 @@ function createArchive(fileName, archive) {
archive.directory('node_modules/nan');
archive.directory('node_modules/negotiator');
archive.directory('node_modules/object-component');
+ archive.directory('node_modules/onoff');
archive.directory('node_modules/parseqs');
archive.directory('node_modules/parseuri');
- archive.directory('node_modules/rpio');
archive.directory('node_modules/selectize');
archive.directory('node_modules/socket.io');
archive.directory('node_modules/socket.io-adapter');
diff --git a/src/js/remotebuzzer_server.js b/src/js/remotebuzzer_server.js
index 30232d36f..2b69c9acc 100644
--- a/src/js/remotebuzzer_server.js
+++ b/src/js/remotebuzzer_server.js
@@ -1,90 +1,68 @@
/* VARIABLES */
-const myPid = process.pid;
-let mTimeTrigger = 0,
- collageInProgress = false;
+let collageInProgress = false,
+ triggerArmed = true;
+const API_DIR_NAME = 'api';
+const API_FILE_NAME = 'config.php';
+const PID = process.pid;
-let triggerArmed = true,
- buttonIsPressed = false;
-
-const rpio = require('rpio');
+/* LOGGING FUNCTION */
+const log = (...optionalParams) => console.log(`Remote Buzzer Server [${PID}]:`, ...optionalParams);
/* HANDLE EXCEPTIONS */
-
process.on('uncaughtException', function (err) {
- console.log('socket.io server [', myPid, ']: Error: ', err.message);
+ log('Error: ', err.message);
fs.unlink(pidFilename, function (error) {
if (error) {
- console.log('socket.io server [', myPid, ']: Error deleting PID file ', error.message);
+ log('Error deleting PID file ', error.message);
}
});
- console.log('socket.io server [', myPid, ']: Exiting');
+ log('Exiting');
/* got to exit now and here - can not recover from error */
process.exit();
});
/* SOURCE PHOTOBOOTH CONFIG */
-
const {execSync} = require('child_process');
-const stdout = execSync('cd api && php ./config.php').toString();
+let cmd = `cd ${API_DIR_NAME} && php ./${API_FILE_NAME}`;
+let stdout = execSync(cmd).toString();
const config = JSON.parse(stdout.slice(stdout.indexOf('{'), -1));
/* WRITE PROCESS PID FILE */
-
const pidFilename = config.foldersRoot.tmp + '/remotebuzzer_server.pid';
-
const fs = require('fs');
-fs.writeFile(pidFilename, myPid, function (err) {
+fs.writeFile(pidFilename, PID, function (err) {
if (err) {
throw new Error('Unable to write PID file [' + pidFilename + '] - ' + err.message);
}
- console.log('socket.io server [', myPid, ']: PID file created [', pidFilename, ']');
+ log('PID file created [', pidFilename, ']');
});
/* START WEBSOCKET SERVER */
-
-console.log(
- 'socket.io server [',
- myPid,
- ']: Requested to start on http://' + config.webserver.ip + ':' + config.remotebuzzer.port,
- ', Pin ',
- config.remotebuzzer.pin
-);
+log('Server starting on http://' + config.webserver_ip + ':' + config.remotebuzzer.port);
function photoboothAction(type) {
switch (type) {
case 'picture':
triggerArmed = false;
collageInProgress = false;
- console.log(
- 'socket.io server [',
- myPid,
- ']: Photobooth trigger picture : [ photobooth-socket ] => [ All Clients ]: command [ picture ]'
- );
+ log('Photobooth trigger picture : [ photobooth-socket ] => [ All Clients ]: command [ picture ]');
ioServer.emit('photobooth-socket', 'start-picture');
break;
case 'collage':
triggerArmed = false;
collageInProgress = true;
- console.log(
- 'socket.io server [',
- myPid,
- ']: Photobooth trigger collage : [ photobooth-socket ] => [ All Clients ]: command [ collage ]'
- );
+ log('Photobooth trigger collage : [ photobooth-socket ] => [ All Clients ]: command [ collage ]');
ioServer.emit('photobooth-socket', 'start-collage');
break;
case 'completed':
triggerArmed = true;
collageInProgress = false;
- console.log(
- 'socket.io server [',
- myPid,
- ']: Photobooth activity completed : [ photobooth-socket ] => [ All Clients ]: command [ completed ]'
- );
+ log('Photobooth activity completed : [ photobooth-socket ] => [ All Clients ]: command [ completed ]');
ioServer.emit('photobooth-socket', 'completed');
break;
@@ -93,7 +71,7 @@ function photoboothAction(type) {
break;
default:
- console.log('socket.io server [', myPid, ']: Photobooth action [', type, '] not implemented - ignoring');
+ log('Photobooth action [', type, '] not implemented - ignoring');
break;
}
}
@@ -105,21 +83,13 @@ const ioServer = require('socket.io')(config.remotebuzzer.port, {
}
});
+/* NEW CLIENT CONNECTED */
ioServer.on('connection', function (client) {
- console.log('socket.io server [', myPid, ']: New client connected - ID', client.id);
+ log('New client connected - ID', client.id);
client.on('photobooth-socket', function (data) {
- console.log(
- 'socket.io server [',
- myPid,
- ']: Data from client ID ',
- client.id,
- ': [ photobooth-socket ] => [',
- data,
- ']'
- );
-
- /* COMMANDS RECEIVED */
+ log('Data from client ID ', client.id, ': [ photobooth-socket ] => [', data, ']');
+ /* CLIENT COMMANDS RECEIVED */
switch (data) {
case 'completed':
photoboothAction('completed');
@@ -142,88 +112,307 @@ ioServer.on('connection', function (client) {
break;
default:
- console.log('socket.io server [', myPid, ']: Received unknown command [', data, '] - ignoring');
+ log('Received unknown command [', data, '] - ignoring');
break;
}
});
+
+ /* CLIENT DISCONNECTED */
client.on('disconnect', function () {
- console.log('socket.io server [', myPid, ']: Client disconnected - ID ', client.id);
+ log('Client disconnected - ID ', client.id);
if (ioServer.engine.clientsCount == 0) {
- console.log('socket.io server [', myPid, ']: No more clients connected - removing lock and arming trigger');
+ log('No more clients connected - removing lock and arming trigger');
triggerArmed = true;
collageInProgress = false;
}
});
});
-console.log('socket.io server [', myPid, ']: socket.io server started');
-/* LISTEN TO GPIO STATUS https://www.npmjs.com/package/rpio */
+/* STARTUP COMPLETED */
+log('socket.io server started');
+
+/*
+ ** GPIO HANDLING
+ */
+
+/* SANITY CHECKS */
+function gpioSanity(gpioconfig) {
+ return !(isNaN(gpioconfig) || gpioconfig < 0 || gpioconfig > 21);
+}
+
+if (!gpioSanity(config.remotebuzzer.picturegpio)) {
+ log('GPIO configuration for Picture Button is invalid: ', config.remotebuzzer.picturegpio);
+}
+if (!gpioSanity(config.remotebuzzer.collagegpio)) {
+ log('GPIO configuration for Collage Button is invalid: ', config.remotebuzzer.collagegpio);
+}
+if (!gpioSanity(config.remotebuzzer.shutdowngpio)) {
+ log('GPIO configuration for Shutdown Button is invalid: ', config.remotebuzzer.shutdowngpio);
+}
+
+/* BUTTON SEMAPHORE HELPER FUNCTION */
+function buttonActiveCheck(gpio, value) {
+ /* init */
+ if (typeof buttonActiveCheck.buttonIsPressed == 'undefined') {
+ buttonActiveCheck.buttonIsPressed = 0;
+ }
+
+ /* clean state - no button pressed - activate lock */
+ if (buttonActiveCheck.buttonIsPressed == 0 && !value) {
+ // log('buttonActiveCheck: LOCK gpio ', gpio, ', value ', value);
+ buttonActiveCheck.buttonIsPressed = gpio;
+ buttonTimer(Date.now('millis'));
+
+ return false;
+ }
+
+ /* clean state - locked button release - release lock */
+ if (buttonActiveCheck.buttonIsPressed == gpio && value) {
+ // log('buttonActiveCheck: RELEASE gpio ', gpio, ', value ', value);
+ buttonActiveCheck.buttonIsPressed = 0;
+ buttonTimer(Date.now('millis'));
+
+ return false;
+ }
+
+ /* forced reset */
+ if (gpio == -1 && value == -1) {
+ // log('buttonActiveCheck - forced state reset');
+ buttonActiveCheck.buttonIsPressed = 0;
+ buttonTimer(0);
-if (config.remotebuzzer.pin >= 1 && config.remotebuzzer.pin <= 40) {
- const pollcb = function pollcb(pin) {
- /* if there is some activity in progress ignore GPIO pin for now */
- if (!triggerArmed) {
- return;
+ return false;
+ }
+
+ /* error state - do nothing */
+ log(
+ 'buttonActiveCheck error state - requested GPIO ',
+ gpio,
+ ', for value ',
+ value,
+ 'but buttonIsPressed:',
+ buttonActiveCheck.buttonIsPressed
+ );
+
+ return true;
+}
+
+/* TIMER HELPER FUNCTION */
+function buttonTimer(millis) {
+ /* init */
+ if (typeof buttonTimer.millis == 'undefined' || millis === 0) {
+ buttonTimer.millis = 0;
+ buttonTimer.duration = 0;
+ }
+
+ /* return timer value */
+ if (typeof millis == 'undefined') {
+ return buttonTimer.duration;
+ }
+
+ /* start timer */
+ if (buttonTimer.millis === 0) {
+ buttonTimer.millis = millis;
+ // log('buttonTimer started - value saved: ', millis);
+
+ return true;
+ }
+
+ /* too long button press */
+ if (millis - buttonTimer.millis > 10000) {
+ buttonTimer.millis = 0;
+ buttonTimer.duration = 0;
+
+ return false;
+ }
+
+ /* end timer */
+ if (millis - buttonTimer.millis > 0) {
+ buttonTimer.duration = millis - buttonTimer.millis;
+ buttonTimer.millis = 0;
+ // log('buttonTimer ended - ', buttonTimer.duration, ' ms');
+
+ return buttonTimer.duration;
+ }
+
+ /* error state */
+ log('buttonTimer error state encountered - millis: ', millis);
+
+ return false;
+}
+
+/* WATCH FUNCTION PICTURE BUTTON WITH LONGPRESS FOR COLLAGE*/
+const watchPictureGPIOwithCollage = function watchPictureGPIOwithCollage(err, gpioValue) {
+ //log('FUNCTION: watchPictureGPIOwithCollage()');
+ if (err) {
+ throw err;
+ }
+
+ /* if there is some activity in progress ignore GPIO pin for now */
+ if (!triggerArmed || buttonActiveCheck(config.remotebuzzer.picturegpio, gpioValue)) {
+ return;
+ }
+
+ if (gpioValue) {
+ /* Button released - raising flank detected */
+ const timeElapsed = buttonTimer();
+
+ if (!timeElapsed) {
+ /* Too long button press - timeout - reset server state machine */
+ log('GPIO', config.remotebuzzer.picturegpio, '- too long button press - Reset server state machine');
+ photoboothAction('reset');
+ buttonActiveCheck(-1, -1);
+ } else if (timeElapsed <= config.remotebuzzer.collagetime * 1000 && !collageInProgress) {
+ /* Start Picture */
+ log('GPIO', config.remotebuzzer.picturegpio, '- Picture button released - normal -', timeElapsed, ' [ms] ');
+ photoboothAction('picture');
+ } else {
+ /* Start Collage */
+ log('GPIO', config.remotebuzzer.picturegpio, '- Picture button released - long -', timeElapsed, ' [ms] ');
+ photoboothAction('collage');
}
+ } else {
+ /* Button pressed - falling flank detected (pull to ground) */
+ log('GPIO ', config.remotebuzzer.picturegpio, ' - Picture button pressed');
+ }
+};
- if (rpio.read(pin)) {
- if (!buttonIsPressed) {
- return;
- }
- buttonIsPressed = false;
-
- /* Button released - action following upwards flank transition of GPIO pin 1 -> 0 */
- const dTimeTrigger = Date.now('millis') - mTimeTrigger;
-
- if (dTimeTrigger > 10000) {
- /* Too long button press - timeout - reset server state machine */
- console.log(
- 'socket.io server [',
- myPid,
- ']: Reset server state machine - Time since button press [ms] ',
- dTimeTrigger
- );
- photoboothAction('reset');
- } else if (
- !config.collage.enabled ||
- (dTimeTrigger <= config.remotebuzzer.collagetime * 1000 && !collageInProgress)
- ) {
- /* Picture */
- console.log(
- 'socket.io server [',
- myPid,
- ']: GPIO button released - normal press - time since button press [ms] ',
- dTimeTrigger
- );
+/* WATCH FUNCTION PICTURE BUTTON */
+const watchPictureGPIO = function watchPictureGPIO(err, gpioValue) {
+ //log('FUNCTION: watchPictureGPIO()');
+ if (err) {
+ throw err;
+ }
+
+ /* if there is some activity in progress ignore GPIO pin for now */
+ if (!triggerArmed || buttonActiveCheck(config.remotebuzzer.picturegpio, gpioValue)) {
+ return;
+ }
+
+ if (gpioValue) {
+ /* Button released - raising flank detected */
+ const timeElapsed = buttonTimer();
+
+ if (timeElapsed) {
+ log('GPIO', config.remotebuzzer.picturegpio, '- Picture button released - normal -', timeElapsed, ' [ms] ');
+ /* Start Picture */
+ if (!collageInProgress) {
photoboothAction('picture');
- } else {
- /* Collage */
- console.log(
- 'socket.io server [',
- myPid,
- ']: GPIO button released - long press - time since button press [ms] ',
- dTimeTrigger
- );
- photoboothAction('collage');
}
} else {
- /* Button pressed - prepare state machine */
+ /* Too long button press - timeout - reset server state machine */
+ log('GPIO', config.remotebuzzer.picturegpio, '- too long button press - Reset server state machine');
+ photoboothAction('reset');
+ buttonActiveCheck(-1, -1);
+ }
+ } else {
+ /* Button pressed - falling flank detected (pull to ground) */
+ log('GPIO', config.remotebuzzer.picturegpio, '- Picture button pressed');
+ }
+};
+
+/* WATCH FUNCTION COLLAGE BUTTON */
+const watchCollageGPIO = function watchCollageGPIO(err, gpioValue) {
+ //log('FUNCTION: watchCollageGPIO()');
+ if (err) {
+ throw err;
+ }
+
+ /* if there is some activity in progress ignore GPIO pin for now */
+ if (!triggerArmed || buttonActiveCheck(config.remotebuzzer.collagegpio, gpioValue)) {
+ return;
+ }
+
+ if (gpioValue) {
+ /* Button released - raising flank detected */
+ const timeElapsed = buttonTimer();
+
+ if (timeElapsed) {
+ log('GPIO', config.remotebuzzer.collagegpio, '- Collage button released ', timeElapsed, ' [ms] ');
- if (buttonIsPressed) {
- return;
+ /* Start Collage */
+ if (!collageInProgress) {
+ photoboothAction('collage');
}
- buttonIsPressed = true;
+ } else {
+ /* Too long button press - timeout - reset server state machine */
+ log('GPIO', config.remotebuzzer.collagegpio, '- too long button press - Reset server state machine');
+ photoboothAction('reset');
+ buttonActiveCheck(-1, -1);
+ }
+ } else {
+ /* Button pressed - falling flank detected (pull to ground) */
+ log('GPIO', config.remotebuzzer.collagegpio, '- Collage button pressed');
+ }
+};
+
+/* WATCH FUNCTION SHUTDOWN BUTTON */
+const watchShutdownGPIO = function watchShutdownGPIO(err, gpioValue) {
+ //log('FUNCTION: watchShutdownGPIO()');
+ if (err) {
+ throw err;
+ }
+
+ /* if there is some activity in progress ignore GPIO pin for now */
+ if (!triggerArmed || buttonActiveCheck(config.remotebuzzer.shutdowngpio, gpioValue)) {
+ return;
+ }
+
+ if (gpioValue) {
+ /* Button released - raising flank detected */
+ const timeElapsed = buttonTimer();
+
+ if (timeElapsed) {
+ log('GPIO', config.remotebuzzer.collagegpio, '- Shutdown button released ', timeElapsed, ' [ms] ');
- console.log('socket.io server [', myPid, ']: GPIO button pressed on pin P', pin);
- mTimeTrigger = Date.now('millis');
+ if (timeElapsed >= config.remotebuzzer.shutdownholdtime * 1000) {
+ log('System shutdown initiated - bye bye');
+ /* Initiate system shutdown */
+ cmd = 'sudo /sbin/shutdown -r now';
+ stdout = execSync(cmd);
+ }
+ } else {
+ /* Too long button press - timeout - reset server state machine */
+ log('GPIO', config.remotebuzzer.shutdowngpio, '- too long button press - Reset server state machine');
+ photoboothAction('reset');
+ buttonActiveCheck(-1, -1);
}
+ } else {
+ /* Button pressed - falling flank detected (pull to ground) */
+ log('GPIO', config.remotebuzzer.shutdowngpio, '- Shutdown button pressed');
+ }
+};
- /* Hysteresis to filter false positives */
- rpio.msleep(200);
- };
+/* INIT ONOFF LIBRARY AND LINK CALLBACK FUNCTIONS */
+const Gpio = require('onoff').Gpio;
+
+/* PICTURE BUTTON */
+if (config.remotebuzzer.picturebutton) {
+ const pictureButton = new Gpio(config.remotebuzzer.picturegpio, 'in', 'both', {debounceTimeout: 20});
+
+ if (!config.remotebuzzer.collagebutton && config.collage.enabled) {
+ pictureButton.watch(watchPictureGPIOwithCollage);
+ log('config: collage enabled for picture button');
+ } else {
+ pictureButton.watch(watchPictureGPIO);
+ }
- console.log('socket.io server [', myPid, ']: Connecting to Raspberry pin P', config.remotebuzzer.pin);
- rpio.open(config.remotebuzzer.pin, rpio.INPUT, rpio.PULL_UP);
- rpio.poll(config.remotebuzzer.pin, pollcb, rpio.POLL_BOTH);
+ log('Connecting Picture Button to Raspberry GPIO', config.remotebuzzer.picturegpio);
}
+
+/* COLLAGE BUTTON */
+if (config.remotebuzzer.collagebutton && config.collage.enabled) {
+ const collageButton = new Gpio(config.remotebuzzer.collagegpio, 'in', 'both', {debounceTimeout: 20});
+ collageButton.watch(watchCollageGPIO);
+ log('Connecting Collage Button to Raspberry GPIO', config.remotebuzzer.collagegpio);
+}
+
+/* SHUTDOWN BUTTON */
+if (config.remotebuzzer.shutdownbutton) {
+ const shutdownButton = new Gpio(config.remotebuzzer.shutdowngpio, 'in', 'both', {debounceTimeout: 20});
+ shutdownButton.watch(watchShutdownGPIO);
+ log('Connecting Shutdown Button to Raspberry GPIO', config.remotebuzzer.shutdowngpio);
+}
+
+log('Initialization completed');
diff --git a/yarn.lock b/yarn.lock
index 8bf820029..206d665d6 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -1335,7 +1335,7 @@ binary-extensions@^1.0.0:
resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-1.13.1.tgz#598afe54755b2868a5330d2aff9d4ebb53209b65"
integrity sha512-Un7MIEDdUC5gNpcGDV97op1Ywk748MpHcFTHoYs6qnj1Z3j7I53VG3nwZhKzoBZmbdRNnb6WRdFlwl7tSDuZGw==
-bindings@^1.5.0, bindings@~1.5.0:
+bindings@^1.5.0:
version "1.5.0"
resolved "https://registry.yarnpkg.com/bindings/-/bindings-1.5.0.tgz#10353c9e945334bc0511a6d90b38fbc7c9c504df"
integrity sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==
@@ -2005,6 +2005,14 @@ enquirer@^2.3.5:
dependencies:
ansi-colors "^4.1.1"
+epoll@^4.0.0:
+ version "4.0.0"
+ resolved "https://registry.yarnpkg.com/epoll/-/epoll-4.0.0.tgz#60a7750bcb7ad8a796fb6689308e5b5c53b6aa1b"
+ integrity sha512-dENZbykco5w/vsFHuhD/5zla9VHm3htP1ROoX9MCc6L/7LVqGPFfcGS/g+/+pQLUclKw4uR9HnaZmsZ6fi5n+Q==
+ dependencies:
+ bindings "^1.5.0"
+ nan "^2.14.1"
+
error-ex@^1.2.0, error-ex@^1.3.1:
version "1.3.2"
resolved "https://registry.yarnpkg.com/error-ex/-/error-ex-1.3.2.tgz#b4ac40648107fdcdcfae242f428bea8a14d4f1bf"
@@ -3837,6 +3845,14 @@ once@^1.3.0, once@^1.3.1, once@^1.3.2, once@^1.4.0:
dependencies:
wrappy "1"
+onoff@^6.0.1:
+ version "6.0.1"
+ resolved "https://registry.yarnpkg.com/onoff/-/onoff-6.0.1.tgz#68ecaece4a4b4663fd59d72544dfec556d460ddd"
+ integrity sha512-lqnVyUiWLbb4T6sWTaOeCJn682EPyxaTyfJ5bP5LiTa0KMDlVPQR0ngVgzV3SkRkd0JnNxPvwTNj7QVvZTZsAw==
+ dependencies:
+ epoll "^4.0.0"
+ lodash.debounce "^4.0.8"
+
optimist@^0.6.1:
version "0.6.1"
resolved "https://registry.yarnpkg.com/optimist/-/optimist-0.6.1.tgz#da3ea74686fa21a19a111c326e90eb15a0196686"
@@ -4453,14 +4469,6 @@ rimraf@^3.0.2:
dependencies:
glob "^7.1.3"
-rpio@^2.1.1:
- version "2.2.0"
- resolved "https://registry.yarnpkg.com/rpio/-/rpio-2.2.0.tgz#fa267bbe056a56ba22daa2a1bdc02e96fb47c730"
- integrity sha512-zuOHIqKKxdS7RuY6wde70r+Nx4MsrypFotsgPuCkpxYlJQlq27KinJg5VlsKcU6b1ukwxTx9zA5UtWXaZFjn5g==
- dependencies:
- bindings "~1.5.0"
- nan "^2.14.1"
-
safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@^5.1.2, safe-buffer@~5.2.0:
version "5.2.1"
resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6"