Shutdown & Reboot Script - Python

Heute ist Stammtischzeit:
Jeden Donnerstag 20:30 Uhr hier im Chat.
Wer Lust hat, kann sich gerne beteiligen. ;)
  • Liebe Community,

    das nachfolgende Python Script, mit dem sich ein Raspberry Pi herunterfahren oder neu starten lässt, möchte ich gerne mit euch teilen.

    Es werden zwei GPIO-Pins verwendet. Ein Pin dient dem Input mit einem Drucktaster, ein zweiter dem Anschluss einer Status-LED.

    Das Script wurde getestet auf dem Raspberry Pi Model B+ und dem Raspberry Pi 2B.

    Die Funktionsweise des Scripts:

    Wird ein Druckknopf am Raspberry Pi länger als eine Sekunde gehalten (LED leuchtet auf) und sofort wieder losgelassen, startet der Pi neu.

    Lässt man den Knopf nicht wieder los, beginnt der Pi sich nach insgesamt drei Sekunden herunterzufahren und die LED erlischt.

    Der Schaltplan am Raspberry Pi:

    schaltplan-shutdown.py.svg

    • Der im Beispiel verwendete Pin GPIO2 wird über einen Druckknopf und den Widerstand R1 mit Masse verbunden.
      R1 muss niedrig genug sein, um GPIO2 gegen R3 auf LOW ziehen zu können.
      Hier wurde R1=470Ω gewählt.
    • Eine Status-LED wird mit dem Vorwiderstand R2 zwischen Masse und GPIO4 verbunden.
      Im Beispiel wurde auch für R2 470Ω gewählt.
    • Der Pull-Up Widerstand R3 hält Pin GPIO2 auf 3,3V, solange der Schalter nicht betätigt wird.
      Die GPIO-Pins 2 und 3 im Raspberry Pi Model B+ und Raspberry Pi 2 sind intern mit einem 1,8kΩ Widerstand ausgestattet, sodass R3 in diesem Fall nicht extern beschaltet werden muss.


    Das Script:
    …kann zum Beispiel unter /usr/local/bin/shutdown.py abgelegt werden und mit dem Eintrag python /usr/local/bin/shutdown.py & in die Datei /etc/rc.local (vor 'exit 0') beim booten automatisch gestartet werden.

    Ausprobieren:
    Das Shutdownscript muss mit superuser-Rechten gestartet werden, damit es auf den GPIO zugreifen darf: sudo python /usr/local/bin/shutdown.py



    Viel Spass und Erfolg beim Herunterfahren und Neustarten!

    (Das Script ist ausserdem auf resources@holzkischtle veröffentlicht und beschrieben.)

    • Bei einem GPIO als IN wäre es besser einen Pull Up/Down zu verwenden, da sich sonst der Zustand des Pins willkürlich ändern könnte. Da kann via Software der Interne eines jeden GPIOs aktiviert, oder Hardwareseitig ein eigener eingelötet werden (wie du es hast).
    • os.system() ist veraltet und sollte durch subprocess ersetzt werden.
    • sudo sollte in diesem Fall nicht nötig sein da das Script über /etc/rc.local bereits mit root-Rechten ausgeführt wird.
    • Wenn rebooted oder shutdown'd (allgemein das Script beendet) wird müsste die LED eigentlich von alleine ausgehen, da ja auch über den except die GPIO's zurückgesetzt werden.
    • 'return 0' macht an beiden Stellen kein Sinn. Zum einen wird die Rückgabe 0 nirgends verwendet, zum anderen verfehlt das glaub ich den Grund wieso du das dort eingesetzt hast: Nutze stattdessen 'break' um die while zu unterbrechen, wobei das eigentlich auch unnötig wäre da die Bedingung der while irgendwann ja nicht mehr zutrifft und diese somit automatisch beendet wird.
    • Die erste 'while(t < 1)' müsste eigentlich überflüssig sein. Die verzögert anscheint nur das einschalten der LED? Drückt man kürzer als 1 Sekunde geht die LED an - und dann bleibt sie an? :s
    • Der von dir eingestellte Interrupt reagiert nur auf einen Flankenwechsel von HIGH auf LOW (Falling), also erst dann wenn man den Knopf los lässt.
      Allgemein würde ich diese while Geschichte noch mal überdenken :) Ich würd den Interrupt auf BOTH stellen, beim RISING die Ausgabe von time.time() in ein Dictionary speichern und beim FALLING dann anhand der Differenz von 'jetzt' und 'zuvor' prüfen wie lange der GPIO gedrückt wurde. Ein Beispiel dafür siehst du im Spoiler:
    &quot;GPIO Multifunktion&quot;


    [code=php]
    #!/usr/bin/python
    # -*- coding: utf-8 -*-
    #
    # Taster mit Mehreren Funktionen
    #
    # v0.1 by meigrafd
    #
    from __future__ import print_function
    import sys, signal
    from time import sleep, time
    import RPi.GPIO as GPIO
    #------------------------------------------------------------------------

    #GPIO pins
    Taster = 11

    #special function time (in sec)
    SpecialTime = 1

    # only one of following:
    PULL = GPIO.PUD_DOWN #GPIO -> GND
    #PULL = GPIO.PUD_UP #GPIO -> 3V3

    # to use RaspberryPi gpio# (BCM) or pin# (BOARD)
    #GPIO.setmode(GPIO.BCM)
    GPIO.setmode(GPIO.BOARD)

    #------------------------------------------------------------------------

    # set up GPIO input channels
    GPIO.setup(Taster, GPIO.IN, pull_up_down = PULL)

    #------------------------------------------------------------------------

    globalVar['timeTrigger'] = {}
    globalVar['timeTrigger'] = 0

    def Interrupt_event(pin):
    # only for debug:
    if GPIO.input(pin) == GPIO.HIGH:
    print("rising edge on %s" % pin)
    elif GPIO.input(pin) == GPIO.LOW:
    print("falling edge on %s" % pin)

    if pin == Taster:
    if GPIO.input(Taster) == GPIO.HIGH:
    globalVar['timeTrigger'] = time()
    elif GPIO.input(Taster) == GPIO.LOW:
    globalVar['timeTrigger'] = time() - globalVar['timeTrigger']
    if globalVar['timeTrigger'] >= SpecialTime:
    #special function:
    print("special function!")
    else: #normal
    print("normal function!")

    else:
    print("ERROR! Unknown GPIO pin triggered: %s" % pin)

    #------------------------------------------------------------------------

    try:
    GPIO.add_event_detect(Taster, GPIO.BOTH, callback=Interrupt_event, bouncetime=100)
    #keep script running
    signal.pause()
    except (KeyboardInterrupt, SystemExit):
    GPIO.cleanup()
    print("\nQuit\n")
    [/php]

  • noisefloor
    Vielen Dank. Stimmt. Der Returnvalue ist tatsächlich völlig überflüssig.


    meigrafd
    Auch dir zunächst vielen Dank für die ausführliche Antwort.

    1. Laut Schaltplan ist eben genau R3 als pull up vorgesehen.
    Entweder extern beschaltet oder der an den GPIO-Pins 2 und 3 intern verbaute 1K8 Widerstand.
    Wem die 50 kOhm - 65 kOhm der via Software aktivierbaren pull ups genügen, mag diese gerne verwenden.
    (Quelle zu den Widerstandswerten: http://elinux.org/RPi_Low-level_peripherals)

    2. (os->subprocess) Danke für den Hinweis. Als Notiz hierzu PEP 324 - PEP proposing the subprocess module

    3. sudo ist unnötig. Danke.

    4. Das unmittelbare Ausschalten der LED dient als Rückmeldung für den Benutzer und ist gewollt. Die Reihenfolge von reboot() und led_off() muss bei Verwendung von subprocess berichtigt werden. Mit os schaltete sich die LED noch beim Loslassen aus.

    5. Der Rückgabewert 0 ist überflüssig, stimmt. break verfehlt aber die Absicht, den thread check_periodically sofort zu beenden. Der Code nach den Schleifen würde dann weiter ausgeführt und der Raspi ungewollt ausgeschaltet oder neugestartet.
    Gibt es noch eine Alternative zur Verwendung von return (ohne returnvalue)?

    6. Die Schleife 'while(t < 1)' ist sehr wichtig! Man muss den Taster erst eine Sekunde lang drücken, damit überhaupt etwas passiert. Erst dann geht die LED an und abhängig davon, wann man loslässt, wird eine Aktion durchgeführt. Kurze, ungewollte Tastendrücke oder aus anderen Gründen auftretende low-level Zustände am Inputpin trotz pull up werden mit der ersten Schleife ausgefiltert. (Ein zuvor von mir verwendeter Script liess meinen Pi trotz eines internen pull ups gelegentlich ungewollt herunterfahren, deswegen hier erhöhte Sicherheit.)

    7. Das ist gewollt und richtig. Interrupt reagiert nur auf einen Flankenwechsel von HIGH auf LOW (Falling), also genau dann, wenn der Knopf den Inputpin gegen den pull up R3 auf low zieht. (gedrückt/geschlossen wird)
    Deine Lösung, die Zeit eines Tastendrucks zu messen ist natürlich auch schön. Mein erster Ansatz war auch, 'Falling' zu registrieren und dann im Interrupt wieder auf die steigende Flanke zu warten. Dazu müsste man aber den Interrupt-channel erst wieder freiräumen, weswegen ich bei der diskreten Lösung mit den Schleifen gelandet bin.


    Aktualisiertes Script:

    &quot;Shutdown &amp; Reboot Script 20150711&quot;

    Einmal editiert, zuletzt von Holzkischtle (11. Juli 2015 um 13:53)

  • Am Rande erwähnt:
    bei ``subprocess.call()`` den ``shell=True`` Parameter weglassen. Sonnst kannst du gleich wieder ``os.system()`` benutzen. Zudem sollten die Argumente als Liste übergeben werden. Dies hättest du auch in der von dir verlinkten PEP 324 nachlesen können.

    ``while`` ist keine Funktion sondern ein Ausdruck, darum lass die Klammern da weg.

    Verwende aussagekräftige Namen für Variablen anstelle von ``t``.
    Die Funktion ``check_periodically()`` ist auch unschön. Schreib eine eigene Funktion, welche die Zeit misst, während der Button gedrückt ist und gib das Resultat an die Funktion check_periodically und prüfe in dieser, ob länger als 1 Sekunde gedrückt wurde oder nicht.

    Zu guter letzt sei noch auf den "Duden" bzw auf die "Rechtschreibung" hinzuweisen PEP 8.

    Auf Modulebene sollten nur Konstanten, Klassen + Methoden und Funktionen stehen. Also fehlt dir noch eine Funktion zb setup_gpio() und eine Hauptfunktion, in welcher dein eigentliches Programm läuft (``add_event_detect()``)

  • Lieber bootsmann,

    dir gebührt nun ebenso mein Dank für deine aufmerksamen Hinweise. Habe jetzt wieder ein bisschen Python dazugelernt.

    Die Funktion check_periodically() ist zugegeben nicht das Highlight meiner Karriere als Programmierer, wird der angestrebten (im ersten Post erläuterten) Funktionsweise des Scripts jedoch auf einfache Weise nachvollziehbar gerecht. Eine schönere Alternativlösung werde ich möglicherweise noch in Zukunft erarbeiten.


    &quot;Shutdown &amp; Reboot Script 20150714&quot;
  • Hallo

    Ich meinte das mit einer zeitzählenden Funktion eigentlich so ;)

    Code
    if GPIO.input(sdbuttonpin):
                # button pressed for <1sec
                return

    ist sinnlost und macht nichts. Auch das zweite ``return`` macht nichts ausser ``None`` an niemand zu übergeben. Wenn du eine Schleife verlassen willst, dann mit ``break``

  • ist sinnlost und macht nichts. Auch das zweite ``return`` macht nichts ausser ``None`` an niemand zu übergeben. Wenn du eine Schleife verlassen willst, dann mit ``break``

    Die returns sind nicht sinnlos, da ich nicht die Schleifen, sondern die Funktion verlassen möchte:
    Das erste ist sehr wichtig, damit nichts passiert, wenn der Knopf nicht >1sec gedrückt gehalten wird.
    Das zweite return könnte man sich vielleicht tatsächlich sparen, soll aber die weitere Ausführung von check_periodically() nach dem Aufruf von reboot() sauber beenden.

    Bitte nochmal genau die gewünschte Funktion beachten. Die LED ändert ihren Zustand während des Drückens und zeigt an, wenn das Script "scharf" ist.

  • Kleiner Schreibfehler: time_during_pushed = pushed_button_time()
    Deine Funktion heißt nicht 'pushed_button_time()' sondern 'get_pushed_button_time()' :fies:

Jetzt mitmachen!

Du hast noch kein Benutzerkonto auf unserer Seite? Registriere dich kostenlos und nimm an unserer Community teil!