Direkte Tastaturabfrage mit Python

  • Hallo an alle die es interesiert wie man direkt einzelne tasten der tastatur mit python abfrägt
    also ohne input()/raw_input() sondern direkt z.b.:wenn ich W drücke schreibe HI oder solange ich W drücke schreibe so oft wie es geht HI etc.


    Als erstes definieren wir die Funktion inkey()
    Dafür müssen wir sys, termios und tty importiren.
    das sieht dann so aus:


    Dann können wir mit der verwendung von inkey anfangen
    z.b.:

    Code
    1. while 1:
    2. key = (inkey())
    3. if key in ['w','a','s','d']:
    4. print key
    5. if key == 'q':
    6. exit()


    das schreibt den inhalt der variable key aber nur wenn diese W,A,S oder D enthält
    und wenn Q gedrückt wird wird das programm beendet.


    eine andere möglichkeit wäre:

    Code
    1. while 1:
    2. key = (inkey())
    3. if key == "h" :
    4. print ("Hallo Welt")
    5. else:
    6. print ("Bis Bald Welt")
    7. if key == "q":
    8. exit()


    Ich hoffe es geht bei euch wenn nicht antwortet mir einfach.


    LG Moritz

    Einmal editiert, zuletzt von moritz12 ()

  • Hallo,


    schönes kleines Tutorial.


    Zwei Anmerkungen:
    * im letzten Codeblock fehlen ein paar Singlequotes um die Ausgabe-Strings - so wie gezeigt funktioniert das nicht
    * die Klammern um inkey() sind überflüssig


    Gruß, noisefloor


  • danke für den hinweis habs korregirt

  • Danke für Deinen Beitrag Moritz!


    Ich nehme doch an, dass Du vor hattest, dass man in der Hauptschleife (while True:) auch sonstigen Programmcode ausführt.


    Da in Deinem Beispiel kein "sonstiger" Programmcode vorhanden ist, fällt nicht auf, dass das Programm beim Abfragen der Tastatur auf Eingabe wartet. Sprich Dein Programm steht eigentlich und kommt erst wieder zurück in die while-Schleife, wenn eine Taste gedrückt wird.


    Um zu verhindern, dass das Abfragen einer Eingabe das restliche Programm blockiert, kann man die Abfrage in einen thread packen. Dies wirkt wie ein separater Programmablauf. Der thread soll nun ruhig warten bis eine Taste gedrückt wird. Das stört uns nun im Hauptprogramm nicht mehr.


    Und hier ist das ganze als python code ausgedrückt:



    [code=php]#!/usr/bin/python
    # -*- coding: utf-8 -*-
    from __future__ import absolute_import, division, unicode_literals, print_function
    """
    Created on Tue Jul 18 15:42:02 2017


    @author: josef
    """
    import tty, termios
    import sys
    if sys.version_info.major < 3:
    import thread as _thread
    else:
    import _thread
    from time import sleep


    def inkey():
    global char
    fd=sys.stdin.fileno()
    remember_attributes=termios.tcgetattr(fd)
    tty.setraw(sys.stdin.fileno())
    char=sys.stdin.read(1) # wir lesen nur einzelne zeichen
    termios.tcsetattr(fd,termios.TCSADRAIN, remember_attributes)
       
    def main():
    global char
    char = None
    _thread.start_new_thread(inkey, ())


    while True:
    if char is not None:
    try:
    print("Key pressed is " + char.decode('utf-8'))
    except UnicodeDecodeError:
    print("character can not be decoded, sorry!")
    char = None
    _thread.start_new_thread(inkey, ())
    if char == 'q' or char == '\x1b': # x1b is ESC
    exit()
    char = None
    print("Program is running")
    sleep(1)


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

    Dateien

    Einmal editiert, zuletzt von josef64 ()

  • Netter Ansatz, allerdings sollte man so gut es geht zusätzliche Threads vermeiden.
    Auch sollte man das interne Module " _thread " nicht verwenden sondern stattdessen " threading " importierten.
    Desweiteren sollte man ebenfalls das explizierte setzen von "global" vermeiden.
    Und zu guter letzt sind die verwendeten print's für Python3, nicht für python2. Die Abfrage "sys.version_info.major < 3" am Anfang sagt aber dass das Script auch mit python2 ausgeführt werden könnte, dann führt print() aber zu einem anderen Ergebnis. Siehe dazu FAQ => Nützliche Links / Linksammlung => print


    Möchte man in nur einer while mehrere Sachen abhandeln empfehle ich: FAQ => Nützliche Links / Linksammlung => python: Schedule / mehrere Abhandlungen in einem Script / Parallelisierung


    Bezüglich des Vorhabens gibts mehrere Wege die Tastatur abzufragen: http://wiki.roxxs.org/index.php/Python/keyboard_event

  • Danke für die konstruktiven Worte!

    Zitat von &quot;meigrafd&quot; pid='291445' dateline='1500411582'


    "... allerdings sollte man so gut es geht zusätzliche Threads vermeiden."


    Tja - ich habe nach einer schnellen und FUNKTIONIERENDEN Lösung mit relativ wenig code Zeilen gesucht. Teile dieses Codes sind unzählige male
    im Netz zu finden ... allerdings ALLESAMT den Rest des Programms blockierend. Die Suche hat mich rund 10 Stunden gekostet und das nur weil ich ein paar Tasten simulieren wollte, um einen späteren Programmablauf des eigentlichen Projektes entwickeln und testen zu können, in welchem später ein Touch screen eingesetzt werden soll, der mir noch nicht vorliegt.


    Was wäre störend an threads an sich, weil du meinst sie unbedingt vermeiden zu müssen? hier laufen keine zusätzlicheN threadS, sondern genau EIN zusätzlicher und der ist noch dazu reichlich unterbeschäftigt, indem er auf Tastatureingabe wartet. Nur durch Einsatz des thread war es mir möglich den ursprünglichen Ansatz nicht blockierend hinzubekommen und dennoch einen Variablenwert übergeben zu können.

    Zitat von &quot;meigrafd&quot; pid='291445' dateline='1500411582'


    "Auch sollte man das interne Module " _thread " nicht verwenden sondern stattdessen " threading " importierten. Desweiteren sollte man ebenfalls das explizierte setzen von "global" vermeiden."


    Naja und das betreffend war auch der Einsatz von global unumgänglich, weil thread keinen return Wert der Funktion verarbeitet und objektorientiert wollte ich es nicht lösen indem ich Klassen anlege. Dafür brauche ich ehrlich gesagt viel länger, weil ich es wesentlich umständlicher finde und reichlich overhead dann da ist, den ich nicht brauche. ich erwähnte schon: in meinem Projekt geht es eigentlich um etwas ganz anderes, nämlich um eine Steuerung die eine SPS dermaßen ersetzt, dass sie OHNE neu programmieren zu müssen mit "Programmierung" über Tabellenkalkulationstabellen für mehrere leicht verschiedene Maschinen dann später in Serie eingesetzt werden kann. Aber eigentlich schweife ich mit meiner Antwort schon reichlich vom Thema ab und lasse es jetzt gut sein. :)
    .... und "intern" ist thread nur unter python3, weil man meinte es sei "obsolet" und es wurde als intern definiert, was es früher nicht war ... Nun - wie schon erwähnt bin ich Freund iterativer Lösungen und bin der Meinung, dass nicht alles objektorientiert gelöst werden muss und daher teile ich die Meinung der python Macher nicht, dass thread obsolet sei und verwende es auch - vor allem, wenn es zu einer Lösung führt wie in diesem Beispiel - so und jetzt kann ich mich wieder meinem eigentlichen code widmen, da ich ja jetzt mit dieser Lösung Softtasten eines touch screens simulieren kann :)


    Zitat von &quot;meigrafd&quot; pid='291445' dateline='1500411582'


    "Und zu guter letzt sind die verwendeten print's für Python3, nicht für python2. Die Abfrage "sys.version_info.major < 3" am Anfang sagt aber dass das Script auch mit python2 ausgeführt werden könnte, dann führt print() aber zu einem anderen Ergebnis."


    nope - da muss ich Dir leider widersprechen. Man KÖNNTE nicht nur, sondern ich fahre meine scripte definitiv noch unter python 2 und habe jede Menge Tips gelesen wie man code aufbauen sollte, damit man für python 3 gerüstet ist. - siehe future import am Anfang, wodurch sich print auch unter python 2 wie unter python 3 verhält. Durch den future import wird man angehalten schon gemäß python 3 zu programmieren.
    Den Einsatz hiervon siehst Du übrigens bei dem von Dir selbst angegebenen link bei der "curses" Lösung der Problematik. Die tkinter Lösung gefällt mir persönlich sehr gut .... dennoch lasse ich es jetzt so wie ist ist .... schließlich habe ich ja auch zumindest zum Teil selbst gemacht und das ist mir persönlich etwas wert (auch wenn es nicht jedem gefällt) :)

    Zitat von &quot;meigrafd&quot; pid='291445' dateline='1500411582'


    "Bezüglich des Vorhabens gibts mehrere Wege die Tastatur abzufragen: http://wiki.roxxs.org/index.php/Python/keyboard_event"


    Zumindest die Hälfte der dort angegebenen Lösungen blockieren den Programmfluss. GENAU das habe ich mit meinem Programmbeispiel gelöst - vergleiche hierzu 4 termios im link ... dort blockierts nämlich noch ... wie die ursprüngliche Lösung hier, welche fast identisch ist:
    curses
    termios
    stdin
    evdev ... all diese Lösungen bei angegebenem link (roxxs.org) blockieren den Programmablauf
    tkinter ... tut gleich gar nichts sichtbares und außerdem blockiert es ohne weitere Vorkehrungen auch das Hauptprogramm


    lg
    Josef

    Einmal editiert, zuletzt von josef64 ()

  • Zitat von &quot;josef64&quot; pid='291458' dateline='1500415269'

    Was wäre störend an threads an sich, weil du meinst sie unbedingt vermeiden zu müssen? hier laufen keine zusätzlicheN threads, sondern genau EINER


    Das Script selbst ist der Main-Thread und zusätzlich startest du einen weiteren (Child). Es sind also 2 Threads.


    (Multi-)Threads sind kein Allheilmittel. Mehrere Child-Threads bremsen sogar aus.


    Du startest zudem jedes mal erneut einen Thread sobald eine Taste gedrückt wurde und importierst dann auch jedesmal erneut "tty, termios"....


    Zitat von &quot;josef64&quot; pid='291458' dateline='1500415269'


    Naja und das betreffend war auch der Einsatz von global unumgänglich, weil thread keinen return Wert der Funktion verarbeitet und objektorientiert wollte ich es nicht lösen indem ich Klassen anlege (was Bedingung ist bei threading Einsatz).


    Wie gesagt sollte man "global" möglichst vermeiden. Das lässt sich auch mit "threading" oder in deinem Fall "_thread" bewerkstelligen, beispielsweise über ein Queue-Objekt was bei Initialisierung des Threads übergeben wird.


    Aus meiner Sicht wäre es besser eine eigene Klasse zu erzeugen, da die Handhabung IMHO einfacher und effektiver wäre.
    Klassen sind aber keine Bedingung für threading... Den Zusammenhang versteh ich grad nicht? :s


    Allerdings geht ein bisschen unter dass es nicht nur eine Möglichkeit gibt die Tastatur abzufragen. Du nutzt "termios", es gibt aber noch mind. 3 andere ;)


    Zitat von &quot;josef64&quot; pid='291458' dateline='1500415269'


    nope - da muss ich Dir leider widersprechen. Man KÖNNTE nicht nur, sondern ich fahre meine scripte definitiv noch unter python 2 und habe jede Menge Tips gelesen wie man code aufbauen sollte, damit man für python 3 gerüstet ist. - siehe future import am Anfang, wodurch sich print auch unter python 2 wie unter python 3 verhält. Durch den future import wird man angehalten schon gemäß python 3 zu programmieren.


    Ahja, die Zeile hab ich glatt übersehen - steht an einer ziemlich untypischen Position... Aber ja, da hast du natürlich Recht "from __future__ import print_function" macht das was es even importiert, print als Funktion ;)



    Vorschlag für den von Dir gewählten Weg, allerdings mit "threading" (ohne Klasse) und ohne "global" usw.:
    [code=php]
    #!/usr/bin/python
    # -*- coding: utf-8 -*-
    from __future__ import print_function
    import sys
    import threading
    import tty, termios
    from time import sleep
    if sys.version_info.major < 3:
    from Queue import Queue
    else:
    from queue import Queue



    def inkey(q):
    while True:
    fd = sys.stdin.fileno()
    remember_attributes = termios.tcgetattr(fd)
    tty.setraw(sys.stdin.fileno())
    character = sys.stdin.read(1) # wir lesen nur einzelne zeichen
    termios.tcsetattr(fd, termios.TCSADRAIN, remember_attributes)
    q.put(character)
    if character == 'q' or character == '\x1b': # x1b is ESC
    break



    def main():
    char_queue = Queue()
    inkey_thread = threading.Thread(target=inkey, args=(char_queue,))
    inkey_thread.start()
    while True:
    if not char_queue.empty():
    char = char_queue.get()
    try:
    print("Key pressed is: %s" % char.decode('utf-8'))
    except (AttributeError, UnicodeDecodeError):
    print("character can not be decoded, sorry!")
    if char == 'q' or char == '\x1b': # x1b is ESC
    break
    print("Program is running")
    sleep(1)



    if __name__ == "__main__":
    main()
    print("\nQuit\n")
    [/php]


    Vielleicht kommt für Dich auch "multiprocessing" in Frage? Aber ja, wir schweifen etwas ab :daumendreh2:

  • bei mir zeigt dass

    Traceback (most recent call last):

    File "<pyshell#1>", line 1, in <module>

    inkey()

    File "/home/pi/Tastentest.py", line 6, in inkey

    fd=sys.stdin.fileno()

    io.UnsupportedOperation: fileno

    an.

    Kann mir jemand helfen?:helpnew::conf::?::denker::geek:

  • Ich les nur durch wie einer seine Datei benennt um auszuschliessen, dass sie nicht wie ein Modul heisst. Pyshell war wir vollkommen fremd - aber ich vermute es liegt daran, starte das Script direkt vom Terminal und es wird laufen

    Der Unterschied zwischen Genie und Wahnsinn definiert sich im Erfolg.