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
### Hardware Button for WLAN connected screen (i.e. iPad) - Remote Buzzer Server -This feature enables a GPIO pin connected hardware button / buzzer for a setup where the display / screen is connected via WLAN / network to the photobooth webserver (e.g. iPad). Configuration takes place in the admin settings - Remote Buzzer Server area. +This feature enables multiple GPIO pin connected hardware buttons (buzzer) in a setup where the display / screen is connected via WLAN / network to the photobooth webserver (e.g. iPad). Configuration takes place in the admin settings - Remote Buzzer Server area. -**Important: You must make sure to set the IP address of the Photobooth web server in the admin settings - section "General"**. The loopback IP (127.0.0.1) does not work, it has to be the exact IP address of the Photobooth web server, to which the remote display connects to. +Troubleshooting / Debugging: -Debugging: switch on dev settings for server logs to be written to the "tmp" directory of the photobooth installation (i.e. `data/tmp/io_server.log`). Clients will log server communication information to the browser console. - -If you experience crashes or access permission problems to GPIO pins, check [https://www.npmjs.com/package/rpio](https://www.npmjs.com/package/rpio) for additional settings required on the Pi +- **Important: You must make sure to set the IP address of the Photobooth web server in the admin settings - section "General"**. The loopback IP (127.0.0.1) does not work, it has to be the exact IP address of the Photobooth web server, to which the remote display connects to. +- Switch on dev settings for server logs to be written to the "tmp" directory of the photobooth installation (i.e. `data/tmp/remotebuzzer_server.log`). Clients will log server communication information to the browser console. +- If hardware buttons do not trigger, GPIO interrupts might be disabled. Check file `/boot/config.txt` and remove / disable the following overlay `dtoverlay=gpio-no-irq` to enable interrupts for GPIOs. *************** -Hardware Buzzer / Button +Hardware Button (Buzzer) *************** -The hardware buzzer connects to a GPIO pin, the server will watch for a PIN_DOWN event (pull to ground). This will initiate a message to the photobooth screen over network / WLAN, to trigger the action (thrill). +The server supports up to three connected hardware buttons for the following functionalities. + +1) **Picture Button** + +- Defaults to GPIO21 +- Short button press (default <= 2 sec) will trigger a single picture in Photobooth +- Long button press (default > 2 sec) will trigger a collage in Photobooth + +Note: + - If collage is configured with interruption, next button presses will trigger the next collage pictures. + - If collage is disabled in the admin settings, long button press also triggers a single picture + - If the collage button is activated (see next), the picture button will never trigger a collage, regardless + +2) **Collage Button** + +- Defaults to GPIO20 +- Button press will trigger a collage in Photobooth. + +Note: +- If collage is configured with interruption, next button presses will trigger the next collage pictures. + - If collage is disabled in the admin settings, this button will do nothing even if it is activated in the admin settings + +3) **Shutdown Button** + +- Defaults to GPIO16 +- This button will initate a safe system shutdown and halt (`shutdown -h now`). + +Note: +- One needs to hold the button for a defined time to initiate the shut down (defaults to 3 seconds). This can be adjusted in the admin settings. +- The shutdown button will only trigger if there is currently no action in progress in Photobooth (picture, collage). -- Short button press (default <= 2 sec) will trigger a single picture -- Long button press (default > 2 sec) will trigger a collage - - If collage is configured with interruption, next button presses will trigger the next collage pictures. - - If collage is disabled in the admin settings, long button press also triggers a single picture +All hardware buttons connect to a GPIO pin and the server will watch for a PIN_DOWN event (pull to ground). This will initiate a message to the photobooth screen over network / WLAN, to trigger the action (thrill). After triggered, the hardware button remains disabled until an action (picture / collage) has fully completed. Then the hardware button re-arms / is active again. @@ -155,7 +181,7 @@ The trigger server controls and coordinates sending commands via socket.io to th - Commands: `start-picture`, `start-collage` - Response: `completed` will be emitted to the client, once photobooth finished the task -This functionality is experimental and largely untested. +This functionality is experimental and largely untested. Not sure if there is a use-case but if you have one, happy to learn about it.
diff --git a/install-raspbian.sh b/install-raspbian.sh index 8839b847a..95da7afed 100755 --- a/install-raspbian.sh +++ b/install-raspbian.sh @@ -309,15 +309,29 @@ EOF fi -info "### Enable Nodejs GPIO access - please reboot in order to use the Remote Buzzer Feature" +info "### Remote Buzzer Feature" +info "### Configure Raspberry PI GPIOs for Photobooth - please reboot in order use the Remote Buzzer Feature" +# remove old artifacts from node-rpio library, if there was +if [ -f '/etc/udev/rules.d/20-photobooth-gpiomem.rules' ]; then + info "### Remotebuzzer switched from node-rpio to onoff library. We detected an old remotebuzzer installation and will remove artifacts" + rm -f /etc/udev/rules.d/20-photobooth-gpiomem.rules + sed -i '/dtoverlay=gpio-no-irq/d' /boot/config.txt +fi +# add configuration required for onoff library usermod -a -G gpio www-data -cat > /etc/udev/rules.d/20-photobooth-gpiomem.rules <> /boot/config.txt << EOF -dtoverlay=gpio-no-irq +# Photobooth +gpio=16,20,21=pu +# Photobooth End EOF +# add configuration required for www-data to be able to initiate system shutdown +info "### Note: In order for the shutdown button to work we install /etc/sudoers.d/020_www-data-shutdown" +cat >> /etc/sudoers.d/020_www-data-shutdown << EOF +## Photobooth Remotebuzzer shutdown button for www-data to shutdown the system +www-data ALL=NOPASSWD: /sbin/shutdown +EOF +echo -e "\033[0m" echo -e "\033[0;33m### Sync to USB - this feature will automatically copy (sync) new pictures to a USB stick." echo -e "### The actual configuration will be done in the admin panel but we need to setup Raspberry Pi OS first" diff --git a/lib/configsetup.inc.php b/lib/configsetup.inc.php index 0ed75c4c6..b1f818357 100644 --- a/lib/configsetup.inc.php +++ b/lib/configsetup.inc.php @@ -1071,7 +1071,7 @@ 'value' => $config['remotebuzzer']['enabled'], ], 'remotebuzzer_collagetime' => [ - 'view' => 'advanced', + 'view' => 'expert', 'type' => 'range', 'placeholder' => $defaultConfig['remotebuzzer']['collagetime'], 'name' => 'remotebuzzer[collagetime]', @@ -1081,20 +1081,55 @@ 'range_step' => 1, 'unit' => 'seconds', ], - 'remotebuzzer_port' => [ - 'view' => 'expert', - 'type' => 'input', - 'placeholder' => $defaultConfig['remotebuzzer']['port'], - 'name' => 'remotebuzzer[port]', - 'value' => $config['remotebuzzer']['port'], + 'remotebuzzer_picturebutton' => [ + 'view' => 'advanced', + 'type' => 'checkbox', + 'name' => 'remotebuzzer[picturebutton]', + 'value' => $config['remotebuzzer']['picturebutton'], ], - 'remotebuzzer_pin' => [ + 'remotebuzzer_picturegpio' => [ + 'view' => 'advanced', + 'type' => 'hidden', + 'placeholder' => $defaultConfig['remotebuzzer']['picturegpio'], + 'name' => 'remotebuzzer[picturegpio]', + 'value' => $config['remotebuzzer']['picturegpio'], + ], + 'remotebuzzer_collagebutton' => [ + 'view' => 'advanced', + 'type' => 'checkbox', + 'name' => 'remotebuzzer[collagebutton]', + 'value' => $config['remotebuzzer']['collagebutton'], + ], + 'remotebuzzer_collagegpio' => [ 'view' => 'expert', + 'type' => 'hidden', + 'placeholder' => $defaultConfig['remotebuzzer']['collagegpio'], + 'name' => 'remotebuzzer[collagegpio]', + 'value' => $config['remotebuzzer']['collagegpio'], + ], + 'remotebuzzer_shutdownbutton' => [ 'view' => 'advanced', - 'type' => 'input', - 'placeholder' => $defaultConfig['remotebuzzer']['pin'], - 'name' => 'remotebuzzer[pin]', - 'value' => $config['remotebuzzer']['pin'], + 'type' => 'checkbox', + 'name' => 'remotebuzzer[shutdownbutton]', + 'value' => $config['remotebuzzer']['shutdownbutton'], + ], + 'remotebuzzer_shutdowngpio' => [ + 'view' => 'expert', + 'type' => 'hidden', + 'placeholder' => $defaultConfig['remotebuzzer']['shutdowngpio'], + 'name' => 'remotebuzzer[shutdowngpio]', + 'value' => $config['remotebuzzer']['shutdowngpio'], + ], + 'remotebuzzer_shutdownholdtime' => [ + 'view' => 'expert', + 'type' => 'range', + 'placeholder' => $defaultConfig['remotebuzzer']['shutdownholdtime'], + 'name' => 'remotebuzzer[shutdownholdtime]', + 'value' => $config['remotebuzzer']['shutdownholdtime'], + 'range_min' => 0, + 'range_max' => 9, + 'range_step' => 1, + 'unit' => 'seconds', ], 'remotebuzzer_logfile' => [ 'view' => 'expert', @@ -1102,6 +1137,13 @@ 'name' => 'remotebuzzer[logfile]', 'value' => $config['remotebuzzer']['logfile'], ], + 'remotebuzzer_port' => [ + 'view' => 'expert', + 'type' => 'input', + 'placeholder' => $defaultConfig['remotebuzzer']['port'], + 'name' => 'remotebuzzer[port]', + 'value' => $config['remotebuzzer']['port'], + ], ], 'synctodrive' => [ 'platform' => 'linux', diff --git a/package.json b/package.json index 312ebd2b7..df9b61bae 100644 --- a/package.json +++ b/package.json @@ -49,7 +49,7 @@ "marvinj": "^1.0.0", "normalize.css": "^8.0.1", "npm-run-all": "^4.1.5", - "rpio": "^2.1.1", + "onoff": "^6.0.1", "selectize": "^0.12.6", "socket.io": "^3.1.2", "socket.io-client": "^3.1.2", diff --git a/resources/lang/en.json b/resources/lang/en.json index 76ac4e1fe..1e726fdcc 100644 --- a/resources/lang/en.json +++ b/resources/lang/en.json @@ -300,11 +300,15 @@ "manual:print:textonprint_locationx": "X-Coordinates of the text while printing text on your picture.", "manual:print:textonprint_locationy": "Y-Coordinates of the text while printing text on your picture.", "manual:print:textonprint_rotation": "Enter a value which is used as degrees a picture gets rotated at print.", + "manual:remotebuzzer:remotebuzzer_collagebutton": "Connect the hardware button for COLLAGE to this Raspberry Pi GPIO. Defaults to GPIO20. Pull GPIO to ground for to trigger. If enabled, long-press on PICTURE button will not trigger collage.", "manual:remotebuzzer:remotebuzzer_collagetime": "If trigger button is pressed (GPIO pulled down) less or equal number of seconds configured here, a picture is triggered. If button is pressed more seconds as what is configured here, a collage is triggered. If collage is disabled in the admin settings, no collage will be triggered at all.", "manual:remotebuzzer:remotebuzzer_enabled": "This feature enables a GPIO monitoring for a hardware trigger connected to Raspberry GPIO pins in combination with WLAN connected displays and screens (i.e. iPad). IMPORTANT: Please make sure to configure the IP address of the Photobooth web server in the section \"General\", for this feature to work properly.", "manual:remotebuzzer:remotebuzzer_logfile": "If Dev-Mode is on, server debugging information will be written to the logfile, located in the 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"