Een modern Operating System (OS) vindt het niet prettig als het niet netjes wordt afgesloten voordat de computer wordt uitgezet. Voor computers met een beeldscherm en toetsenbord is dat niet zo’n probleem. Bij een systeem met een grafische interface is het vaak zelfs een kwestie van “op de juiste knop klikken“.
Als de computer echter geen beeldscherm en/of toetsenbord heeft, zoals een Raspberry Pi (RPi) die als server ergens in een donker hoekje zijn ‘ding doet‘, wordt het een stuk lastiger. De enige manier is dan om met SSH in te loggen en het “shutdown -h now” (of gewoon het “halt”) commando in te toetsen.
Here you will find an English translation.
In deze post beschrijf ik een manier om met twee drukknopjes, vier weerstandjes, één LED en een stukje software, de RPi netjes af te sluiten of eventueel te resetten.
Op de Rev.2 boards van de RPi is er ook plaats voor een connector waarmee de RPi ge-reset kan worden of waarmee hij, na een ‘shutdown -h now’ commando, weer gestart kan worden. Hiervoor moet een P6 connector op de RPi worden gesoldeerd. Door pin 1 (het ‘vierkante’ eilandje op het plaatje) ‘laag’ te maken (verbinden met ‘aarde’) zal de RPi opnieuw opstarten.
Er zijn twee manieren om een schakelaar op een als Input gedefinieerde GPIO poort aan te sluiten. In het linker voorbeeld hiernaast is de rust toestand van de gebruikte GPIO poort ‘hoog’ (oftewel +3.3V). Dit komt door de weerstand van 10K ohm die de poort naar +3.3V trekt (dit is een zgn. Pull-Up weerstand). Wordt op de schakelaar gedrukt, dan wordt de poort ‘laag’ (GND). De extra weerstand in serie met de GPIO poort is strikt genomen niet nodig, maar zorgt ervoor dat een, per ongeluk als Output poort gedefinieerde GPIO poort, niet direct de geest zal geven.
In het rechter voorbeeld is de rust toestand van de gebruikte GPIO poort ‘laag’ (oftewel GND, 0V). Dit komt door de weerstand van 10K ohm die de poort naar GND trekt (dit is een zgn. Pull-Down weerstand). Wordt op de schakelaar gedrukt, dan wordt de poort ‘hoog’ (+3.3V). Ook hier dient de extra weerstand in serie met de GPIO poort als extra beveiliging voor het geval deze als Output poort gedefinieerde is.
Hiernaast het schema waarmee, met wat software, de RPi netjes kan worden afgesloten. Bij mij heeft de waarschuwing: “Pas op: Niet op deze knop drukken” altijd het effect dat ik juist wél op die knop wil drukken. Daarom heb ik de reset knop in serie met de Shutdown knop geschakeld en heb ik voor de GPIO-Input poort de “in rust ‘hoog’” variant gebruikt. Pin 1 van P6 wordt dan alleen ‘laag’ als de twee drukknoppen gelijktijdig worden ingedrukt.
Omdat je niet wilt dat de RPi bij het ‘per ongeluk indrukken‘ van de Shutdown knop direct het ‘Shutdown -h now’ commando uitvoert, wordt dit commando pas na het voor langere tijd ingedrukt houden van de knop uitgevoerd (in de software instelbaar, standaard tussen de vier en zes seconden).
Tijdens het testen van deze schakeling merkte ik dat het ontbreken van feedback over het functioneren van de schakeling en de software niet prettig is. Door een LED, die door de software wordt aangestuurd, in de schakeling op te nemen is het mogelijk om te zien in welke toestand de software zich bevindt. In rust en als de software ‘loopt’ knippert de LED met een frequentie van 1 maal per twee seconden. Wordt de Shutdown knop ingedrukt, dan wordt de frequentie 4x per seconden. Is het Shutdown commando gegeven, dan brandt de LED constant.
Dit is het definitieve schema:
Ik heb de onderdelen op een stukje experimenteer printplaat gesoldeerd (waarbij ik er achter kwam dat experimenteer printplaat met dubbelzijdige en door-gemetaliseerde eilandjes een stuk handiger werkt dan de enkelzijdige die ik gebruikt heb) waarbij het printje direct over de gebruikte rij pinnen van P5 en P6 valt.
|
Dit is het programma om het één en ander aan te sturen:
#!/bin/sh #-----------------------# # GPIOshutdown.sh # #-----------------------# BASENAME=basename ${0}
PIDFILE="/var/run/${BASENAME%.sh}.pid" if [ -f ${PIDFILE} ]; then echo "${BASENAME} allready running..." exit else echo ${$} > ${PIDFILE} fi # # GPIO numbers should be from this list (P1) # 0, 1, 4, 7, 8, 9, 10, 11, 14, 15, 17, 18, 21, 22, 23, 24, 25 # # GPIO numbers should be from this list (P5) # 29, 31 # # Note that the GPIO numbers that you program here refer to the pins # of the BCM2835 and *not* the numbers on the pin header. # So, if you want to activate GPIO7 on the header you should be # using GPIO4 in this script. Likewise if you want to activate GPIO0 # on the header you should be using GPIO17 here. # GPIO_IN=29 # Input - gebruik de poort die jij handig vindt GPIO_LED=31 # Ouput - gebruik de poort die jij handig vindt # # Set up GPIO_IN and set to Input echo "${GPIO_IN}" > /sys/class/gpio/export echo "in" > /sys/class/gpio/gpio${GPIO_IN}/direction # # test of input hoog is, anders is er niets op # deze GPIO_IN aangesloten... I=0 SW=cat /sys/class/gpio/gpio${GPIO_IN}/value
echo "value GPIO_${GPIO_IN} is [${SW}]" while [ ${SW} -eq 0 ]; do I=$((I+1)) if [ ${I} -gt 10 ]; then echo "..pfff" echo "GPIO_${GPIO_IN} niet aangesloten" rm -f ${PIDFILE} exit fi SW=cat /sys/class/gpio/gpio${GPIO_IN}/value
echo -n "${I}" sleep 1 done # # Set up GPIO_LED and set to output echo "${GPIO_LED}" > /sys/class/gpio/export echo "out" > /sys/class/gpio/gpio${GPIO_LED}/direction # I=0 # Duur van de KeyPress (Ticks-Teller) LED=0 # LED Aan=1, Uit=0 S=2 # Seconden sleep while true do sleep ${S} # if [ ${LED} -eq 1 ]; then echo "0" > /sys/class/gpio/gpio${GPIO_LED}/value LED=0 else echo "1" > /sys/class/gpio/gpio${GPIO_LED}/value LED=1 fi # SW=cat /sys/class/gpio/gpio${GPIO_IN}/value
if [ ${SW} -eq 0 ] then I=$((I+1)) # Tel het aantal Ticks dat de Key is ingedrukt S=0.25 # KeyPress, dan 0.25 seconden sleep echo -n "${I}" if [ ${I} -gt 16 ] then echo "..." echo "Key pressed for [${I}] ticks.." I=0 echo "1" > /sys/class/gpio/gpio${GPIO_LED}/value # Clean up echo "${GPIO_IN}" > /sys/class/gpio/unexport echo "/sbin/shutdown -h now" # Gebruik dit om te testen # /sbin/shutdown -h now # Gebruik dit voor productie rm -f ${PIDFILE} # Verwijder PID file sleep 5 # Settle down.. exit fi else I=0 # Toets is weer los gelaten, reset Ticks Teller S=2 # Sleep weer op 2 seconden zetten fi done # echo "0" > /sys/class/gpio/gpio${GPIO_LED}/value # # Clean up echo "${GPIO_IN}" > /sys/class/gpio/unexport #echo "${GPIO_LED}" > /sys/class/gpio/unexport # echo "Done...." #
Plaats deze code in “/usr/local/bin/GPIOshutdown.sh” en maak het executable:
sudo chmod +x /usr/local/bin/GPIOshutdown.sh
Om het programma automatisch te starten bij het opstarten van je RPi zijn er verschillende mogelijkheden. Ik vind het prettig om het programma als ‘daemon’ te draaien. Wil je dat ook, dan moet je de volgende code in “/etc/init.d/GPIOshutdown” zetten:
#! /bin/sh ### BEGIN INIT INFO # Provides: GPIOshutdown # Required-Start: $remote_fs $syslog # Required-Stop: $remote_fs $syslog # Default-Start: 2 3 4 5 # Default-Stop: 0 1 6 # Short-Description: check if GPIO29 is low and shutdown the system # Description: check if GPIO29 is low and shutdown the system # bla bla bla ### END INIT INFO # # Author: Willem Aandewiel (Willem@Aandewiel.nl) # # Do NOT "set -e" # # PATH should only include /usr/* if it runs after the mountnfs.sh script PATH=/sbin:/usr/sbin:/bin:/usr/bin DESC="check GPIO input for LOW and halt the system" NAME=GPIOshutdown DAEMON=/usr/local/bin/${NAME}.sh DAEMON_ARGS="" PIDFILE=/var/run/$NAME.pid SCRIPTNAME=/etc/init.d/$NAME # Exit if the package is not installed [ -x "$DAEMON" ] || exit 0 # Read configuration variable file if it is present [ -r /etc/default/$NAME ] && . /etc/default/$NAME # Load the VERBOSE setting and other rcS variables . /lib/init/vars.sh # Define LSB log_* functions. # Depend on lsb-base (>= 3.2-14) to ensure that this file is present # and status_of_proc is working. . /lib/lsb/init-functions # # Function that starts the daemon/service # do_start() { # Return # 0 if daemon has been started # 1 if daemon was already running # 2 if daemon could not be started start-stop-daemon --start --quiet --background --pidfile $PIDFILE --nicelevel 19 --exec $DAEMON --test > /dev/null \ || return 1 start-stop-daemon --start --quiet --background --pidfile $PIDFILE --nicelevel 19 --exec $DAEMON -- \ || return 2 # Add code here, if necessary, that waits for the process to be ready # to handle requests from services started subsequently which depend # on this one. As a last resort, sleep for some time. } # # Function that stops the daemon/service # do_stop() { # Return # 0 if daemon has been stopped # 1 if daemon was already stopped # 2 if daemon could not be stopped # other if a failure occurred start-stop-daemon --stop --quiet --retry=TERM/30/KILL/5 --pidfile $PIDFILE --name $NAME RETVAL="$?" [ "$RETVAL" = 2 ] && return 2 # Wait for children to finish too if this is a daemon that forks # and if the daemon is only ever run from this initscript. # If the above conditions are not satisfied then add some other code # that waits for the process to drop all resources that could be # needed by services started subsequently. A last resort is to # sleep for some time. start-stop-daemon --stop --quiet --oknodo --retry=0/30/KILL/5 --exec $DAEMON [ "$?" = 2 ] && return 2 # Many daemons don't delete their pidfiles when they exit. rm -f $PIDFILE return "$RETVAL" } case "$1" in start) [ "$VERBOSE" != no ] && log_daemon_msg "Starting $DESC" "$NAME" do_start case "$?" in 0|1) [ "$VERBOSE" != no ] && log_end_msg 0 ;; 2) [ "$VERBOSE" != no ] && log_end_msg 1 ;; esac ;; stop) [ "$VERBOSE" != no ] && log_daemon_msg "Stopping $DESC" "$NAME" do_stop case "$?" in 0|1) [ "$VERBOSE" != no ] && log_end_msg 0 ;; 2) [ "$VERBOSE" != no ] && log_end_msg 1 ;; esac ;; status) status_of_proc "$DAEMON" "$NAME" && exit 0 || exit $? ;; restart) # # If the "reload" option is implemented then remove the # 'force-reload' alias # log_daemon_msg "Restarting $DESC" "$NAME" do_stop case "$?" in 0|1) do_start case "$?" in 0) log_end_msg 0 ;; 1) log_end_msg 1 ;; # Old process is still running *) log_end_msg 1 ;; # Failed to start esac ;; *) # Failed to stop log_end_msg 1 ;; esac ;; *) echo "Usage: $SCRIPTNAME {start|stop|status|restart}" >&2 exit 3 ;; esac :
Vergeet niet dit bestand execute rechten te geven:
sudo chmod +x /etc/init.d/GPIOshutdown
Om de daemon te starten tik je in:
sudo /etc/init.d/GPIOshutdown start
De LED zal nu met een frequentie van 0.5Hz gaan knipperen. Als je de Shutdown knop indrukt dan zal binnen twee seconden de LED met een frequentie van 4Hz gaan knipperen. Laat je de knop binnen vier seconden weer los, dan gaat de LED weer met 0.5Hz knipperen en blijft de RPi ‘draaien’. Hou de de Shutdown knop lang genoeg ingedrukt, tot de LED continu gaat branden, dan wordt de RPi netjes afgesloten (uiteindelijk blijft er nog slechts één intern LEDje op de RPi branden).
Als je de beide knoppen tegelijkertijd even indrukt, zal de RPi weer opstarten en zal, als je de service goed hebt geïnstalleerd de LED weer gaan knipperen.
Let op: Druk je beide knoppen gelijk in terwijl de RPi gewoon draait, dan Reset de RPi zichzelf.
Je kunt de daemon stoppen met het commando:
sudo /etc/init.d/GPIOshutdown stop
De LED gaat nu uit.
Om de daemon automatisch te starten bij iedere reboot van de RPi moet je de service installeren met het commando:
cd /etc/init.d
sudo insserv GPIOshutdown
Wees niet bang dat dit script teveel tijd van je beperk aanwezige processor capaciteit op slokt. Het ‘sleep’ commando gebruikt nagenoeg geen processor tijd!
Let op: de P5 connector is bedoeld om aan de onderkant van de RPi te solderen. Omdat ik dat voor dit project niet handig vind heb ik hem aan de bovenkant (component side) gesoldeerd. Hierdoor is de pin-nummering echter omgedraaid. De vierkante eilandjes op de printplaat geven altijd pin 1 aan. Ik heb alleen de even pin-nummers gebruikt waarbij de +3.3v aan de buitenkant van de RPi zit!
|
Pingback: Shutdown Headless Raspberry Pi (English) | Willem's Website
Geweldig artikel. Het werkt allemaal prima op mijn Raspberry Pi 2 met Rasbian.
Ik moest een kleine aanpassing doen overigens.
NAME=checkKeyPress moet zijn NAME=GPIOshutdown
Het enige wat ik nu nog niet voor elkaar krijg, en waar jij hopelijk een antwoord voor hebt, is om de boel werkend te krijgen in openelec.
Het lijkt erop dat daar GPIO helemaal niet te gebruiken is.
Ook bestaan een aantal directories niet, waardoor ik het o.a. neit voor elkaar krijg de boel als daemon te starten. Weet jij hoe ik het onder openelec ook aan de gang kan krijgen?
Hoi Patrick,
Ik heb geen ervaring met openELEC, maar wat ik er van gelezen heb is dat het OS geen standaard Linux is. Of de libraries om GPIO aan te sturen beschikbaar zijn weet ik niet. Misschien op een openELEC forum navragen?
Ik vond deze link op het openElec forum.