Einknopfbedienung GPIO Abfrage mit Python

  • Hallo,
    für meinen DIY Camcorder möchte ich eine Einknopfbedienung konstruieren, in dem Sinne:

    a) 2x kurz hintereinander drücken: Aufnahme Start

    b) 1x drücken: Aufnahme Stopp

    c) 1x Lang drücken: System herunterfahren

    für die Pin Abfrage verwende ich modifizierte Skripte dieser Seite:

    http://indibit.de/raspberry-pi-g…ingaenge-lesen/

    und zwar folgendes:


    Das Skript "pushevent" ist eine Bash Skript, welches zwischen Doppel und Einfachklick unterscheidet. Ich mach das mit Bash, weil ich keine Ahnung von python habe ;)

    "pushevent" sieht folgendermaßen aus:

    a) und b) funktionieren so einwandfrei. Die Frage ist jetzt: Wie kann er feststellen, ob der Pin 18 für längere Zeit (ca 1-2s) gedrückt gehalten wird?

  • Ein Bash-Script dranzuhängen ist nun wirklich keine gute Idee. Mach dich doch lieber schlau, wie das in Python geht. Zumal das nun wirklich keine große Sache ist.
    Mach ne Schleife, speichere bei bei jeder Änderung des GPIO-Eingangs die Zeit (Millisekundengenau) und vergleiche dann. die Abstände zwischen Drücken und loslassen.
    Mit Interrupts gehts natürlich noch eleganter. Das Prinzip ist aber das Gleiche.

    Oh, man kann hier unliebsame Nutzer blockieren. Wie praktisch!

    Einmal editiert, zuletzt von Gnom (15. April 2017 um 11:34)


  • Hallöchen,

    ich habe zufälligerweise für eine ähnliche Aufgabe die gleiche Idee gehabt. Auch die Quelle für die ersten Programmzeilen sind die gleichen. Bei meier Lösung benutze ich auch 'Longpress' zum herunterfahren, und mehrfachdruck für einzelne andere Aufgaben wie z.b. reboot.

    Vielleicht kannst Du damit ja was anfangen. Falls der Code ein wenig wirr ist, sorry, bin Anfänger :s

    Gruß Mickes

  • FAQ => Nützliche Links / Linksammlung => Interrupt
    In dem Thread von #9 findest du eine "special" Funktionalität, sprich, je nach dem wie lange der Taster gedrückt wurde können unterschiedliche Aktionen durchgeführt werden.

    In meiner Sample-Code Sammlung finden sich dazu auch einige Beispiele:
    https://github.com/meigrafd/Sampl…zero_special.py
    https://github.com/meigrafd/Sampl…t_stoppuhr_2.py


    PS: Bitte nicht Beiträge vollständig quoten/zitieren, vor allem wenn diese genau da drüber stehen.

  • Hallo Modellflieger!
    Ich hab's mal so gelöst. Einschränkungen: Sehr kurze Tastendrücke (< bouncetime) werden fehlinterpretiert. Das ist aber wohl unvermeidlich (so lange man softwareseitig entprellt). Auf einen einfachen Tastendruck wird erst nach der erase-Zeit reagiert - klar, denn das Programm muss ja warten, ob nicht ein zweites Mal gedrückt wird. Auch das ist unvermeidlich.
    Verändere die Parameter short, long und erase, um die Reaktionszeiten zu beeinflussen.
    Da es mit Interrupts arbeitet, ist das Programm recht ressourcenschonend. Außerdem kommt die Callback-Funktion gerade mal 20 Zeilen aus.

    Oh, man kann hier unliebsame Nutzer blockieren. Wie praktisch!

    Einmal editiert, zuletzt von Gnom (15. April 2017 um 14:58)

  • Naja wie in #9 erklärt wird, sind allzu viele Zeilen in der ISR Funktion nicht wirklich toll, insbesondere dann wenn es auf genaue Zeiten ankommt. Diese ganzen Abhandlungen direkt in der ISR Funktion blockieren diese und sorgen dafür das weitere Events möglicherweise verpasst werden.

    Ich hab auch lange Zeit gebraucht und viel Unterstützung aus diesem Forum, um bessere Wege zu finden bzw entwickeln.

    Code ohne Erklärung ist ebenfalls nicht sehr hilfreich - Kommentare sollten die Zeile aber auch aufwerten nichts offensichtliches beschreiben.

    Zu welchem Zeitpunkt ein Event eingetreten ist kann man dem Queue hinzufügen, ebenso wie den "channel" und den jeweiligen "state". Dadurch kann man die eigentliche Behandlung der aufgetretenen Events im eigentlich Script abhandeln - siehe Erklärung aus #9 der "Interrupt" Sammlung....
    Ich wiederhole mich eigentlich nur ungerne, aber anscheint geht es nicht anders, daher wandel ich #9 für dieses Vorhaben etwas an:


    Normalerweise wird der Interrupt-Callback nur "channel" übergeben, also der GPIO-Pin der das Event ausgelöst hat. Wir müssen der Funktion aber auch das Queue zugänglich machen und die hierfür eleganteste Vorgehensweise ist mithilfe von 'partial' direkt die Übergabe zu modifizieren:
    [code=php]
    queue=Queue()
    GPIO.add_event_detect(18, GPIO.BOTH, callback=partial(interrupt_Event, queue), bouncetime=100)
    [/php]

    Und die Callback Funktion sieht dann so aus:
    [code=php]
    def interrupt_Event(q, channel):
    q.put( (channel, GPIO.input(channel), time.time()) )
    [/php]

    Sobald ein Flankenwechsel stattfindet - egal was für einer - wird ins Queue ein Eintrag in Form einer Tuple vorgenommen -> In diesem Fall der ausgelöste Channel (18), dessen Status (HIGH oder LOW bzw 1 oder 0) und Epoch Unix-Timestamp des Events.

    So, was brauchen wir jetzt noch?
    => Das Script muss das Queue abarbeiten
    Wie bereits erwähnt läuft die Interrupt_Callback im Hintergrund und trägt munter Dinge ins Queue ein. Parallel dazu können wir nun im Script ganz wilde Sachen anstellen und sozusagen unendlich viel Zeit verplempern, die ISR wird davon nicht beeinträchtigt.

    Gehen wir das noch mal gedanklich durch:
    - Taster wird gedrückt. Was passiert nun? die interrupt_Event Funktion wird ausgeführt und ein entsprechender Eintrag ins Queue vorgenommen... Und das sozusagen sofort, ohne jeglicher Verzögerung egal ob das Script bereits mit irgendwas beschäftigt ist.

    Um Einträge aus dem Queue auszulesen nutzt man queue.get() ... Dabei wartet dies solange bis etwas eingetragen wurde. Ruft man das also auf wird der Script-Thread solange blockiert bis etwas ins Queue eingetragen wurde... Für unseren Fall hier perfekt weil nichts anderes gemacht werden soll. Da wir aber nicht nur einmal sondern immer wieder Einträge verarbeiten wollen nutzen wir vorher eine while Schleife.

    Soweit so gut. Jetzt folgt der schwierige Teil :fies:

    Wir wissen was im Queue steht und wie es da drin steht. Wir speichern uns also queue.get() in eine Variable und holen uns dann jeweils aus dem tuple die jeweilige Position/Index. In diesem Fall benenne ich's "job":
    [code=php]
    job = queue.get()
    pin = job[0]
    state = job[1]
    dt = job[2]
    [/php]

    Eine kürzere und angemessenere Schreibweise wäre:
    [code=php]
    pin, state, dt = queue.get()
    [/php]

    Dann prüfen wir ob der Pin einer unserer gewünschten Taster entspricht sowie verarbeiten entsprechende Aktionen..

    Wir wollen:
    Wenn der Taster 2x kurz nacheinander gedrückt wurde...
    Wenn der Taster 1x gedrückt wurde...
    Wenn der Taster 1x länger gedrückt wurde...
    ... etwas bestimmtes tun.

    Hierfür müssen wir die Zeiträume genau festlegen, sprich, was gilt als "kurz" und welche Zeitspanne als "lang".
    Desweiteren müssen wir wissen Wann das jeweilige Event eingetreten ist.

    Um das zu erreichen müssen wir uns also die Zeit merken wann das jeweilige Event eingetreten ist. Am einfachsten wären natürlich Sekunden und da liegt dann quasi der Unix-Timestamp auf der Hand - also die Sekunden die seit dem 1.1.1970 vergangen sind, was man in Python durch time.time() erhält.

    Wenn also ein Event eingetreten ist und GPIO.HIGH gesetzt wurde, haben wir im Queue bereits die sog. trigger_time.
    Nachdem der Taster dann wieder losgelassen wurde, ein erneutes Event auslöst und somit GPIO.LOW setzt, rechnen wir die aktuelle Zeit abzüglich trigger_time und erhalten dann die verstrichene Zeit, also wie lange der Taster gedrückt wurde.... Dann nur noch prüfen ob das mehr oder weniger 3 Sekunden ist, entsprechende Aktion ausführen und voilà thats it :cool:

    Als Vorlage nehmen wir uns den Code aus #9: http://codepad.org/b5IkKMY2
    [code=php]
    from __future__ import print_function
    from time import sleep, time
    from RPi import GPIO
    from Queue import Queue
    from functools import partial


    def interrupt_Event(q, channel):
    q.put( (channel, GPIO.input(channel)) )


    def main(switchPi=4, switchWin=17, specialTime=3):
    GPIO.setmode(GPIO.BCM)
    GPIO.setup(switchPi, GPIO.IN, pull_up_down=GPIO.PUD_DOWN)
    GPIO.setup(switchWin, GPIO.IN, pull_up_down=GPIO.PUD_DOWN)
    queue=Queue()
    triggerTime=0
    GPIO.add_event_detect(switchPi, GPIO.BOTH, callback=partial(interrupt_Event, queue), bouncetime=150)
    GPIO.add_event_detect(switchWin, GPIO.BOTH, callback=partial(interrupt_Event, queue), bouncetime=150)
    try:
    while True:
    job = queue.get() # blockiert bis Eintrag vorhanden
    pin = job[0]
    state = job[1]
    print(pin, state)

    if state == GPIO.HIGH:
    triggerTime = time()
    elif state == GPIO.LOW:
    triggerTime = time() - triggerTime

    if pin == switchPi:
    if triggerTime < specialTime:
    print("%s: reboot pi" % triggerTime)

    elif triggerTime > specialTime:
    print("%s: shutdown pi" % triggerTime)

    elif pin == switchWin:
    if triggerTime > specialTime:
    print("%s: shutdown win" % triggerTime)

    else:
    print("time '%s' not valid" % triggerTime)


    except (KeyboardInterrupt, SystemExit):
    GPIO.cleanup()
    print("\nQuit\n")


    if __name__ == "__main__":
    main()


    #EOF
    [/php]

    ...und modifizieren diese Zeilen für das hiesige Vorhaben:
    [code=php]
    from __future__ import print_function
    import time
    from RPi import GPIO
    from Queue import Queue
    from functools import partial


    def interrupt_Event(q, channel):
    q.put( (channel, GPIO.input(channel), time.time()) )


    def main(switch=17, specialTime=3, specialCount=2):
    GPIO.setmode(GPIO.BCM)
    GPIO.setup(switch, GPIO.IN, pull_up_down=GPIO.PUD_DOWN)
    queue=Queue()
    GPIO.add_event_detect(switch, GPIO.BOTH, callback=partial(interrupt_Event, queue), bouncetime=150)
    try:
    while True:
    pin, state, timestamp = queue.get()
    msec = repr(timestamp).split('.')[1][:3]
    date_time = time.strftime('%d.%m.%Y %H:%M:%S.{}'.format(msec), time.localtime(timestamp))
    print('[{}] GPIO {} State: {}'.format(date_time, pin, state)

    except (KeyboardInterrupt, SystemExit):
    GPIO.cleanup()
    print("\nQuit\n")


    if __name__ == "__main__":
    main()
    [/php]

    Resumé:
    Wir haben den Zeitpunkt wann der Event ausgelöst wurde, also wann der Taster gedrückt und losgelassen wurde.
    Wir haben eine specialTime, also ein von uns definierter Zeitraum den wir als "kürzer" oder "länger" verwenden können - in diesem Fall: alles < 3 Sekunden gilt als "kurz" und alles > 3 Sekunden gilt als "lang".
    Wir haben specialCount, also wie oft ein Event aufgetreten ist - auch von uns definiert.

    Was fehlt jetzt noch?
    Genau, die eigentliche Behandlung... :fies:

    Wichtig ist jetzt eigentlich nur noch das wir auch mitzählen wie oft ein Event aufgetreten ist, damit wir die erste Anforderung "Wenn der Taster 2x kurz nacheinander gedrückt wurde..." umsetzen können. Und dann fehlt auch noch das behandeln der verstrichenen Zeit zwischen den Events damit 'specialTime' zur Geltung kommt.

    Drücken wir den Taster tritt ein GPIO.HIGH Event auf - hier merken wir uns den Timestamp des Events und zählen zu einer bestimmten Variablen eins hinzu.
    Lassen wir den Taster dann wieder los tritt ein GPIO.LOW Event auf - hier prüfen wir dann wie viel Zeit zwischen Drücken und Loslassen vergangen ist. Liegt die verstrichene Zeit unter 'specialTime' prüfen wir zusätzlich unseren Counter, da das ja einer deiner Anforderungen war..


    ...das dumme ist nur, dass mir heute die Lust ausführlicher zu helfen vergangen ist... gewisser User hier im Forum nerven mal wieder. Wär aber schade um das zuvor bereits geschriebene

  • Interessant.
    Verstehe ich das richtig, dass die Queue sozusagen die erfassten Events mit ergänztem Zeitstempel speichert und man sie dann im Hauptprogramm abarbeiten kann? Sehr praktisch. Könnte man das, wenn man nicht so tiefe Kenntnisse in Python hat, auch machen, indem man die Events in globalen Variablen speichern?

    Im Prinzip verstehe ich deinen Ansatz. Aber ist denn die Ausführungszeit in der Interruptroutine in einem Fall wie diesem (manuelle Tasterbedienung) tatsächlich zeitkritisch?

    Wer nervt denn?

    Oh, man kann hier unliebsame Nutzer blockieren. Wie praktisch!

  • Verstehe ich das richtig, dass die Queue sozusagen die erfassten Events mit ergänztem Zeitstempel speichert und man sie dann im Hauptprogramm abarbeiten kann?

    Nicht nur sozusagen...
    Was ins Queue gespeichert wird legt man selbst fest.

    Könnte man das, wenn man nicht so tiefe Kenntnisse in Python hat, auch machen, indem man die Events in globalen Variablen speichern?

    Ist das eine Fangfrage? :fies:

    Globale Variablen sollte man vermeiden... Über die oben gezeigte Struktur aber auch nicht nötig.

    Im Prinzip verstehe ich deinen Ansatz. Aber ist denn die Ausführungszeit in der Interruptroutine in einem Fall wie diesem (manuelle Tasterbedienung) tatsächlich zeitkritisch?

    Jein.
    Wir wissen nicht was Modellflieger unter "kurz" versteht, aber generell wird wie gesagt die interrupt_Event Funktion für die Zeit der Verarbeitung blockiert. Um so mehr man da drin abhandelt um so schlechter.


    Wer nervt denn?

    Noch nicht hier anwesende

  • Die Queue kümmert sich also um die Bereitstellung in der richtigen Reihenfolge - first in first out... das ist natürlich praktisch. Da können Ereignisse auflaufen so viele wollen und man kann sie nacheinander abarbeiten...
    Ne, keine Fangfrage. Ich bin sehr interessiert und hab auch schon einiges gemacht, aber ich bin weder Programmierer noch Elektronikingenieur. Da gibts reichlich Leute die viel mehr davon verstehen.
    Für meine kleinen Spielereien genügen die unsauberen Konstrukte völlig. Da geht dann mal Funktionalität vor Perfektion. Aber man lernt ja nie aus.
    In diesem Fall wirds wohl eher nicht zeitkritisch - bis der Videorekorder geruht, in die Puschen zu kommen... da kann man sich Zeit lassen. :lol:

    Oh, man kann hier unliebsame Nutzer blockieren. Wie praktisch!

  • Nachdem man mir gesagt hat, man müsse das Problem mit Python lösen, habe ich das Problem inzwischen auf meine Art gelöst. Wie gesagt, ich kenne Python praktisch gar nicht und ich habe momentan nicht die Zeit eine neue Sprache zu lernen. Die empfolende Bibliothek habe ich erst gar nicht ans Laufen bekommen. Die Kombination auf phyton und bash ist sicher nicht elegant, aber sie funktioniert. Genau genommen sind es 2 Python Skripte geworden: Das erste oben leitet die push events an die bash weiter, welche zwischen einfach und doppelklicks unterscheidet indem es Subprozesse abschickt, die temporäre Daten anlegen und aufeinander warten. Das 2 Python Skript fragt den Status des Pins mehrere Male ab und fährt den Rechner herunter, wenn die über 3 Sekunden permanent auf HIGH steht.
    So umständlich das alles klingt, ich habe es ausgiebig getestet und es funktionert. Ich könnte mir vorstellen, dass es fehlschlägt wenn der Rechner unter extrem starker Last ist weil die Laufzeiten dann nicht mehr determiniert sind aber dieser Fall wird nicht auftreten. Beizeiten werde ich mir Eure Vorschläge ansehen, die sicher besser sind, und meine Lösung ggf. ersetzten. Jetzt kommt es mir erstmal darauf an, dass es funktionert und ich mich dem Design des Gehäuses widmen kann. Falls jemand Interesse an meiner Lösung hat, kann ich sie hier noch gerne posten.

  • Noch nicht hier anwesende

    verdammt noch mal was habe ich getan das ich schon OHNE Anwesenheit hier nerve?

    lasst die PIs & ESPs am Leben !
    Energiesparen:
    Das Gehirn kann in Standby gehen. Abschalten spart aber noch mehr Energie, was immer mehr nutzen. Dieter Nuhr
    (ich kann leider nicht schneller fahren, vor mir fährt ein GTi)

  • Ausnahmsweise warst es nicht Du der mich heute nervte - egal in welchem Thread :rolleyes:

    Die Personen die mich heute nervten sind bis jetzt noch nicht hier anwesend. Viel Lust allgemein ausführliche Beiträge zu erstellen hatte ich dann trotzdem nicht mehr - #7 hatte ich vorher angefangen.


    Modellflieger Interesse besteht immer

  • Modellflieger, deine Lösung hat zwar ungefähr so den Charme als hättest du ein Abschleppseil und ein Pferd im Kofferraum, um dich aus der Parklücke zu ziehen, weil du nicht weißt, wie man den Rückwärtsgang einlegt... aber wenn's funktioniert, ist es auch gut. :thumbs1:

    Oh, man kann hier unliebsame Nutzer blockieren. Wie praktisch!

Jetzt mitmachen!

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