Shutdown Headless Raspberry Pi


Ready4Prod

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.

Voor dit ‘projectje‘ wordt gebruik gemaakt van twee General Purpose Input/Output (GPIO) poorten van de RPi. Dit zijn poorten die, via een softwarematige instelling, kunnen worden ingesteld als een Input óf als een Output poort. De RPi heeft een aantal van dit soort poorten. Deze bevinden zich op de P1 connector, maar er zitten er op het Rev.2 board ook vier (GPIO28, 29, 30 en 31) op de P5 connector. De P5 connector moet je dan zelf nog even op de RPi solderen. Omdat ik van plan ben de P1 connector nog voor andere uitbreidingen te gaan gebruiken heb ik ervoor gekozen om een connector op de P5 plek te solderen en die te gebruiken. Wil je dat niet doen dan moet je in de software aangeven welke GPIO poorten je wél wilt gebruiken.

RaspberryPi
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.

GPIO_IN
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.


Schema01
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).

Test01a
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:
GPIOshutdown
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.

Proto01b Proto01a

Ready2Test2 Ready2Test1

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!

P5_Header P1_Header

Test02a

This entry was posted in Computer. Bookmark the permalink.

3 Responses to Shutdown Headless Raspberry Pi

  1. Pingback: Shutdown Headless Raspberry Pi (English) | Willem's Website

  2. Patrick van der Poel says:

    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?

    • Willem says:

      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.

Comments are closed.