Raspberry-Pi-Projekte: Auto-Shutdown

Prof. Jürgen Plate

Raspberry Pi: Auto-Shutdown, Ein- und Ausschalten per Taste

Allgemeines

Der Microcomputer Raspberry Pi ist so klein und minimalistisch, dass er keinen Ein-/Ausschalter hat. Wie schaltet man ihn dann aus oder führt einen Reset aus, wenn das mal nötig ist?

Das Einschalten des Raspberry Pi erfolgt einfach durch Anschließen an das Netzteil via USB-Kabel. Dann startet der Winzling automatisch und bootet sein Betriebssystem von der SD-Karte. Man kann ihn auch auf umgekehrtem Wege ausschalten: Einfach den Netz- bzw. USB-Stecker ziehen. Das machen die meisten und in den seltensten Fällen führt das zu Problemen -- manchmal aber doch. Ein Schaden an der Hardware des RasPi sind dabei zwar ausgeschlossen, aber die auf der SD-Karte gespeicherten Daten können Schaden nehmen. War das System beim brutalen Stromentzug gerade beim Schreiben auf der SD-Karte, wird das eventuell nicht korrekt abgeschlossen. Wenn es halbwegs gut ging, ist vielleicht nur eine Logdatei beschädigt, wenn das Pech zuschlägt, ist das gesamte Dateisystem kaputt und der kleine bootet nicht mehr. Da hilft dann nur noch eine Neuformatierung der SD-Karte unter komplettem Datenverlust. Das ist – wie gesagt – selten der Fall, aber es kann jederzeit vorkommen.

Shutdown/Reboot per Kommando

Um den Raspberry Pi etwas sanfter auszuschalten, wird das System vollständig heruntergefahren, bevor man den Stecker zieht. Dies ist nur per Kommando möglich. Sind am RasPi Bildschirm und Tastatur angeschlossen, gibt es damit keine Probleme: Entweder man klickt in der grafischen Oberfläche den entsprechenden Menüeintrag an oder man verwendet die Kommandozeile, wie auch beim Headless-Betrieb. Kurz und schmerzlos geht es beispielsweise mit

sudo shutdown -h now # Shutdown - Halt
sudo shutdown -r now # Reboot
Das sudo ist nur erforderlich, wenn man nicht als root-Benutzer angemeldet ist, da diese Befehle nur mit Superuser-Rechten ausgeführt werden dürfen. Als Synonyme gibt es auch noch die entsprechend funktionierenden Programme halt und reboot. Zum erneuten Starten des RasPi nach einem Shutdown muss auf jeden Fall die Stromzufuhr kurz unterbrochen werden.

Shutdown/Reboot per Taste

Ist der Raspberry Pi so "headless", dass er nur gelegentlich per SSH-Login administriert wird (etwa bei einer Steuerungs- oder Messwerterfassungs-Aufgabe) und soll er auch lokal ohne extra Login heruntergefahren werden, kann man das auch mittels einer Taste am GPIO-Port erledigen. Dazu ist aber ein Programm notwendig, das die Taste überwacht und daher ständig laufen muss. Das weiter unten beschriebene Programm erlaubt sogar zwei Alternativen, das Herunterfahren oder den Reboot - ja nachdem, wie lange die Taste gedrückt wird. Für diesen Betrieb muss eine Taste am GPIO-Port angeschlossen werden und das Programm bereits beim Hochfahren des Systems starten.

Die Taste kann an jeden beliebigen (freien) GPIO angschlossen werden. Empfehlenswert ist es, "Allerwelts"-GPIOs zu nehmen, also nicht unbedingt die I2C- oder Seriell-Schnittstelle. Als Beispiel verwende ich den GPIO 5, Pin 29. Der Taster soll gegen GND schließen (das erleichtert die Verdrahtung, wenn etliche GPIOs in Betrieb sind) und beim GPIO 5 ist GND gleich daneben auf Pin 30.

Damit das Programm beim Boot-Vorgang automatisch im Hintergrund startet, gibt es zwei Möglichkeiten der Einrichtung:

Das Shutdown-Programm

Das Programm erlaubt sowohl das komplette Anhalten des RasPi als auch das Rebooten. Was getan wird, hängt von der Zeitdauer des Tastendrucks ab. Wird die Taste weniger als 3 Sekunden gedrückt gehalten, erfolgt ein Reboot. Bei längerer Betätigung wird der Raspberry heruntergefahren. Zum erneuten Starten des Raspberry Pi muss im Shutdown-Fall die Stromzufuhr kurz unterbrochen werden.

Am Programmanfang werden alle wichtigen Konstanten festgelegt: GPIO-Port und Schaltzeiten. Danach überzeugt sich das Programm, ob der Aufruf auch mit Root-Berechtigung erfolgt und beendet sich gegebenenfalls mit einer Fehlermeldung. Danach wird der GPIO für die Taste initialisiert und auch der interne Pullup-Winderstand eingeschaltet, so dass bei offenem Tastenpin ein definierter Logikpegel anliegt und auch keine Störungen eingefangen werden.

Anschließend wird eine Interrupt-Serviceroutine (ISR) definiert, die auf die fallende Flanke am Tastenpin reagiert (nicht verwirrren lassen: "not GPIO.input(pin)" bedeutet ja gerade, dass die Taste gedrückt ist → negative Logik). Zu Beginn der Flanke wird die Variable für die Zeitmessung, duration, auf 0 gesetzt. Jetzt müssen Sie sich vorstellen, dass ja nach wenigen Mikrosekunden die ISR abgearbeitet ist. Bleibt die Taste gedrückt, ist natürlich die Interruptleitung immer noch aktiv und es passiert erst wieder etwas, wenn die Taste losgelassen wurde. Auch bei der steigenden Flanke wird die ISR wieder angesprungen. Ist duration ungleich 0, wird die Dauer des Tastendrucks berechnet und entsprechend reagiert: War die Taste lange genug betätigt, erfolgt ein Shutdown, bei kürzerer Dauer wird der Pi neu gestartet und eine zu kurze Zeit wird ignoriert.

Im Hauptprogramm wird dann nur noch die ISR aktiviert und in einer Endlosschleife "geschlafen". Der Aufruf von sleep() sorgt auch dafür, dass das Programm nahezu keine aktive Rechenzeit aufnimmt und die anderen Prozesse nicht behindert. Da das Programm als Dienst läuft werden alle Meldungen ins System-Logfile /var/log/syslog geschrieben.

#!/usr/bin/python
# shutdown/reboot Raspberry Pi mittels Taste

import RPi.GPIO as GPIO
import subprocess, time, sys, syslog, os

# GPIO-Port, an dem die Taste gegen GND angeschlossen ist
# GPIO 5, Pin 29 (GND waere daneben auf Pin 30)
PORT = 5

# Schwelle fuer Shutdown (in Sekunden), wird die Taste kuerzer
# gedruckt, erfolgt ein Reboot
T_SHUT = 3

# Entprellzeit fuer die Taste
T_PRELL = 0.05

uid = os.getuid()
if uid > 0:
  print ("Programm benoetigt root-Rechte!")
  sys.exit(0)

# GPIO initialisieren, BMC-Pinnummer, Pullup-Widerstand
GPIO.setwarnings(False)
GPIO.setmode(GPIO.BCM)
GPIO.setup(PORT, GPIO.IN, pull_up_down=GPIO.PUD_UP)

# Zeitdauer des Tastendrucks
duration = 0

# Interrupt-Routine fuer die Taste
def buttonISR(pin):
  global duration

  if not (GPIO.input(pin)):
    # Taste gedrueckt
    if duration == 0:
      duration = time.time()
  else:
    # Taste losgelassen
    if duration > 0:
      elapsed = (time.time() - duration)
      duration = 0
      if elapsed >= T_SHUT:
        syslog.syslog('Shutdown: System halted');
        subprocess.call(['shutdown', '-h', 'now'], shell=False) 
      elif elapsed >= T_PRELL:
        syslog.syslog('Shutdown: System rebooted');
        subprocess.call(['shutdown', '-r', 'now'], shell=False) 

# Interrupt fuer die Taste einschalten
GPIO.add_event_detect(PORT, GPIO.BOTH, callback=buttonISR)

syslog.syslog('Shutdown.py started');
while True:
  try:
    time.sleep(300)
  except KeyboardInterrupt:
    syslog.syslog('Shutdown terminated (Keyboard)');
    print ("Bye")
    sys.exit(0)

Wird der Rasperry Pi heruntergefahren, schaltet er leider nicht komplett ab, was am Leuchten der Power-LED auf dem Board deutlich wird. Auch eventuell angesteckte USB_Peripherie wird weiter versorgt. Um ihn dann wieder neu zu starten, muss er kurz vom Netzteil getrennt werden - oder man lötet nachträglich zwei Drähtchen für eine Reset-Taste an. Das komplette Abschalten der Stromversorgung und das Starten per Tastendruck soll nun auch automatisiert werden.

Selbsthaltung und Selbstabschaltung

Festzustellen, ob der Raspberry Pi vollständig heruntergefahren ist (nur noch Power-LED und die Spannungsversorgung aktiv), ist schwierig. Nach einm Shutdown ist der Rechner leider nicht komplett aus. Um ihn wieder zu starten, muss die Stromversorgung unterbrochen oder ein Reset ausgelöst werden. Ausserdemm verbrauchen der RasPi und eventuelle Peripherie weiterhin Energie. Das vollständige Abschalten müsste also Teil der Shutdown-Sequenz des Raspberry Pi sein und so spät wie möglich erfolgen, am sichersten wäre es erst nach dem Aushängen der Dateisysteme. Da aber seit den letzten Raspbian-Versionen nicht mehr das deterministische SysV-System für das Booten und Herunterfahren zuständig ist, sondern der neue Systemd, kann nicht mehr genau festgelegt werden, wann welches Script oder Programm ausgeführt wird (das ist ja gerade das Konzept des Systemd: vieles möglichst parallel ausführen). Insofern wäre ein Selbst-Abschalten ein klein wenig unsicher.

Eine zweite Überlegung führt daher dahin, das Abschalten per GPIO auszulösen, aber den eigentlichen Abschaltvorgang zeitlich zu verzögern. Das ist eine praktikable Lösung, wenn man die Zeitverzögerung genügend groß macht. Sie wäre auch recht einfach zu realisieren. Nach dem Einschalten muss sich der RasPi selbst aktiv halten und beim Shutdown wird dann nach einer gewissen Wartezeit der Saft abgedreht. Für das Detektieren, ob der Raspberry Pi aktiv oder heruntergefahren ist, bietet sich der serielle Ausgang an (TX, GPIO 14, Pin 8). Kurz nach dem Hochfahren geht er auf HIGH und erst nach dem Shutdown wieder auf LOW. Werden serielle Daten gesendet, wechselt der Pegel im Rhythmus der Daten zwischen HIGH und LOW, was aber nicht weiter stört, da ja sowieso eine Zeitverzögerung notwendig ist. Beim Modell 3 ist das leider nicht mehr ganz so. Da hängt der LOW-Pegel nach dem Herunterfahren leider davon ab, ob die serielle Schnittstelle per Konfiguration aktiviert wurde. Da muss also beim Herunterfahren per Programm nachgeholfen werden - was aber nicht problematisch ist, weil ja sowieso vor dem echten Abschalten etwas gewartet wird. Wichtig ist nur, dass die Selbsthaltung automatisch und ohne irgend eine Software auf dem Pi aktiviert wird.

Die Schaltung ist recht einfach, sie besteht nur aus zwei Transistoren, einem Relais, fünf Stiftleisten und einigen weiteren Bauteilen. Ein Relais anstelle einen P-MOSFET zur An- und Abschaltung wurde gewählt, um im ausgeschalteten Zustand eine wirkliche galvanische Trennung des RasPi vom Netzteil zu haben. Viel wichtiger ist jedoch, dass beim Schalten des Relaus der Strom komplett ein- oder ausgeschaltet wird. Die Stiftleisten haben folgende Aufgaben:

Stift-
leiste
Aufgabe
JP1Stromversorgung vom Netzteil (+5 V)
JP2Stromversorgung zum Raspberry Pi
JP3Einschalt-Taste - die Taste schaltet gegen +5 V.
JP4Ausschalt-Taste - die Taste schaltet gegen GND.
JP5GPIO-Anschlüsse des RaspPi
GPIO 5 (IN) für Shutdown, GPIO 14 (OUT) für Selbsthaltung

Für JP1 und JP2 trennt man einfach das Kabel vom Netzeil und verbindet die Netzteil-Seite mit JP1 sowie das Ende mit dem Mikro-USB-Stecker mit JP2. An JP3 und JP4 können beliebige Taster angeschlossen werden. Für den Einschalttaster an JP3 könnte man auch eine Taste mit LED-Beleuchtung verwenden und die LED im Taster anstelle der LED1 anschließen. Er schaltet die 5 V vom Netzteil über die Diode D2 auf die Basis von T2, der darauf hin durchschaltet. Das Relais zieht an und versorgt über seinen Kontakt K1 den Raspberry mit Energie. Gleichzeitig wird der Kondensator C1 aufgeladen. Er sorgt einerseits dafür, dass T2 für etliche Sekunden weiter durchgeschaltet bleibt, auch wenn der Taster losgelassen wird und gibt dem Rechner Zeit zum Booten. Der Kondensator endlädt sich über R3 und die Basis von T2 sowie R4. Bedingt durch die langsame Entladung von C1 sinkt die Spannung an der Basis von T2 ebenfalls langsam ab und beginnt er zu sperren. Würde anstelle des Relais ein P-MOSFET angesteuert, würde die Stromversorgung des RasPi auch nur "schleichend" getrennt, was nicht optimal ist. Wenn das Schalten des Raspberry elektronisch erfolgen soll, müsste man die einfache Schaltung mit T2 durch einen Schmitt-Trigger ersetzen.

JP5 führt zum GPIO-Port des RasPi. Die Ausschalttaste an JP4 ist über Pin 3 der Stiftleiste direkt mit dem GPIO 5 verbunden und triggert das Shutdown-Programm (siehe vorhergehendes Kapitel). Der GPIO 14 an Pin2 der Stiftleiste ist bei laufendem Rechner auf logisch HIGH (ca. 3 V) und steuert über den Widerstand R1 den Transister T1 an, der über D1 die Basis von T2 im durchgeschalteten Zustand hält. Dies ist die Selbsthaltung des RasPi im Normalbetrieb. Gleichzeitig wird auch C1 auf vollem Ladezustand gehalten, so dass Datenverkehr auf der TX-Leitung mit LOW-Impulsen keine Auswirkung auf die Selbsthaltung hat. Erst beim Herunterfahren muss diese Leitung dann automatisch oder manuell auf LOW geschatet werden.

Das Abschalten der TX-Leitung kann irgendwo im Shutdown-Prozess erfolgen, da ja über C1 der RasPi noch etliche Sekunden lang mit Strom versorgt wird. Ist die serielle Schnittstelle konfiguriert (bei den Modellen A, B, B+ und 2B ist das immer der Fall), muss man nichts weiter unternehmen. Beim Modell 3 hängt es von der aktuellen Konfiguration ab, ob TX beim Herunterfahren auf LOW schaltet. Notfalls wird hier nachgeholfen. Erstens wird ein Miniprogramm benötigt, das (mit Root-Rechten) den TX-Pin auf LOW schaltet:

#!/usr/bin/env python
# -*- coding: utf-8 -*-
# Programm: TX_OFF.py

import RPi.GPIO as GPIO

TX_LED = 8

GPIO.setmode(GPIO.BOARD)
GPIO.setwarnings(False)
GPIO.setup(TX_LED, GPIO.OUT)
GPIO.output(TX_LED, GPIO.LOW)
Es fehlt nun noch die Steuerdatei, die dafür zuständig ist, dass TX_OFF.py bein Herunterfahren gestartet wird. Ab "Jessie" kann auch hier wieder der systemd verwendet werden. Diese Datei habe ich "txoff.service" genannt. Sie wird (mit Root-Berechtigung) im Verzeichnis /lib/systemd/system/ abgelegt:
[Unit]
Description=GPIO-Port TX auf LOW setzen
DefaultDependencies=no
Before=shutdown.target

[Service]
Type=oneshot
ExecStart=/home/pi/bin/TX_OFF.py

[Install]
WantedBy=reboot.target halt.target poweroff.target
Unter "Unit" werden allgemeine Bedingungen festgelegt, u. a. wann das Script gestartet werden soll (Before=shutdown.target). Unter "Service" steht dann der Aufruf des Scripts mit vollständiger Pfadangabe. Um die Steuerdatei einzubinden, gibt man (als Root) das folgende Kommando ein:
systemctl daemon-reload
systemctl enable txoff.service
Mit systemctl disable txoff.service könnte man es wieder deaktivieren. Den Status erfragt man mit der "status"-Option:
root@pi:/home/pi# systemctl status txoff.service
* txoff.service - GPIO-Port TX auf LOW setzen
   Loaded: loaded (/lib/systemd/system/txoff.service; enabled)
   Active: inactive (dead)
Schließlich kann man die Steuerdatei mittels systemctl cat txoff.service auch noch auflisten lassen. Vergessen Sie auch nicht, das Programm mittels chmod +x TX_OFF.py ausführbar zu machen.

Bei den älteren Versionen von Raspbian kann man das entsprechende Halt-Script in der Datei /etc/init.d/halt modifizieren, indem man den Aufruf von TX_OFF.py einfügt. Das folgende Listing zeigt den entsprechenden Ausschnitt des Scripts. Die Einfügestelle ist fett gedruckt:

   ...

do_stop () {
   ...
        log_action_msg "Will now halt"
        /home/pi/bin/TX_OFF.py                 # <--- hier
        halt -d -f $netdown $poweroff $hddown
}

case "$1" in
  start)
        # No-op
        ;;
   ...

Links


Copyright © Hochschule München, FK 04, Prof. Jürgen Plate und die Autoren
Letzte Aktualisierung: