Seite 1 von 1
lange und kurze Tastendrücke (python-evdev)
Verfasst: Samstag 14. August 2021, 23:25
von smutbert
Hallo liebe Leute,
ihr habt mir ja schon einige Male großartig geholfen (
hier und
hier), deshalb versuche ich es noch einmal
Es geht um an den Raspberry Pi angeschlossene Taster, die sich dank devicetree overlays und dem Kerneltreiber gpio-key wie eine Tastatur verhalten.
Bis jetzt verwende ich python-evdev um die Events mitzubekommen und das meiste funktioniert, allerdings schaffe ich es nicht lange Tastendrücke zu erkennen. Laut [1] sollten lange Tastendrücke einen Event mit value=2 erzeugen, aber wenn ich einen Taster lange drücke kommt kein solcher event
Code: Alles auswählen
# python3 -m evdev.evtest
ID Device Name Phys Uniq
-----------------------------------------------------------------------------------------------------
0 /dev/input/event0 button@19 gpio-keys/input0
1 /dev/input/event1 button@18 gpio-keys/input0
2 /dev/input/event2 soc:shutdown_button@17 gpio-keys/input0
3 /dev/input/event3 rotary@a
4 /dev/input/event4 rotary@1b
Select devices [0-4]: 1
Listening for events (press ctrl-c to exit) ...
time 1628979450.041453 type 1 (EV_KEY), code 59 (KEY_F1), value 1
time 1628979450.041453 --------- SYN_REPORT --------
time 1628979466.986278 type 1 (EV_KEY), code 59 (KEY_F1), value 0
time 1628979466.986278 --------- SYN_REPORT --------
Hier habe ich den Taster etwa 16 Sekunden gedrückt, aber es wurde das Drücken (value 1) und loslassen (value 0) erfasst.
Gibt es da vielleicht irgendeinen Trick, dass auch Events für lange Tastendrücke erzeugt werden – gefunden habe ich in der Dokumentation darüber nichts.
lg smutbert
[1]
https://github.com/gvalkov/python-evdev/issues/70
Re: lange und kurze Tastendrücke (python-evdev)
Verfasst: Samstag 14. August 2021, 23:42
von __deets__
Ich glaube das ist ein Missverständnis. Das ist wenn ein repeat-Event. Siehe zb hier:
https://stackoverflow.com/questions/209 ... put-device
Wenn dir das trotzdem reicht (du wirst aber dann viele davon bekommen), dann kann man das ggf Konfigurieren.
Ansonsten muss man für sowas eben einen Timer beim press starten, der dann vom Release gestoppt wird. Oder eben triggert, dann war es ein Long press.
Re: lange und kurze Tastendrücke (python-evdev)
Verfasst: Sonntag 15. August 2021, 11:59
von smutbert
Ohje, danke.
Leider habe ich schon einmal aus einem anderen Grund (vergeblich) versucht die "Tastaturwiederholungsrate" zu konfigurieren bzw. aktivieren – das scheint in Verbindung mit gpio-keys nicht möglich zu sein.
Da gibt es nicht zufällig ein fertiges python-Modul, das die Sache vereinfacht?
(gpiozero statt den Kerneltreibern habe ich schon ausprobiert. Das bietet für lange Tastendrücke zwar eigene Events/Callback-Funktionen, aber verglichen mit den Kerneltreibern treibt es meinen Beobachtungen nach die CPU-Last in die Höhe und es füht sich nicht so „flüssig“ an.)
Aktuell habe ich den Beispielcode in der python-evdev-Dokumentation [1] für meine Zwecke angepasst und diese Klasse geschrieben.
Code: Alles auswählen
#!/usr/bin/env python3
import threading
import queue
import evdev
import selectors
INPUT_DEVICES = [
'/dev/input/event0',
'/dev/input/event1',
'/dev/input/event3',
'/dev/input/event4',
]
INPUT_EVENTS = {
59: { 1: 'play'},
60: { 1: 'select'},
5: { 1: 'right', -1: 'left'},
6: { 1: 'up', -1: 'down'}
}
class Input:
def __init__(self):
self.queue = queue.Queue()
self.selector = selectors.DefaultSelector()
for i in INPUT_DEVICES:
device = evdev.InputDevice(i)
self.selector.register(device, selectors.EVENT_READ)
device.grab()
self.thread = threading.Thread(target=self.__keep_reading__)
self.thread.daemon = True
self.thread.start()
def __input_to_command__(self, event):
try:
self.queue.put(INPUT_EVENTS[event.code][event.value])
except KeyError:
return
def __keep_reading__(self):
while True:
for key, _mask in self.selector.select():
for event in key.fileobj.read():
self.__input_to_command__(event)
def get(self, timeout):
return self.queue.get(timeout=timeout)
Ich bin sicher, dass ich da (wieder) vieles suboptimal gemacht habe, aber außerdem scheint mir das denkbar ungünstig zu sein um da für jeden Taster einen Timer einzubauen?
[1]
https://python-evdev.readthedocs.io/en/ ... -selectors
Re: lange und kurze Tastendrücke (python-evdev)
Verfasst: Sonntag 15. August 2021, 12:42
von __deets__
Man erfindet keine eigenen __dingsbums__-Methoden. Diese Namenskonvention dient dazu dem Leser mitzuteilen, dass da etwas besonderes passiert, das abhaengt von der Python-Semantik. ZB __init__, dass eben aufgerufen wird, wenn das Objekt initialisiert werden soll. Das stimmt fuer selbstausgedachte Sachen natuerlich nicht.
Man benutzt auch ohne Not keine __namen, die sind *nicht* als Ersatz fuer private Attribute gedacht, sondern zur Vermeidung von Namenskollisionen, und man handelt sich subtile Bugs ein, wenn man das Feature nicht gut versteht.
Zu deinem eigentlichen Problem: einen Timer pro Taste zu haben ist an sich ueberhaupt kein Problem. Da der evdev-Code basierend auf dem select/poll-File-Descriptor-Ansatz ist, kann man auch einfach Timer-File-Descriptoren machen, und dadurch bekommt man dann einen zentralen Mainloop, der einen wieder aufweckt, wenn irgendwas passiert ist. Timer oder Taste. Siehe zB
https://abelbeck.wordpress.com/2014/01/ ... n-example/
Ich wuerde dir aber raten, auf gpiozero zu wechseln. Deine Beobachtungen in allen Ehren, aber ich sehe keinen technischen Grund dafuer, und solche Konzepte umzusetzen ist fuer Anfaeger oft eine Ueberforderung. Darum haben die sowas ja eingebaut. Ich vermute mal eher, du hast einen Fehler bei der Gestaltung der Hautpschleife gemacht, und dadurch eine Endlosschleife erzeugt, die dann das Verhalten erklaert. Das darf natuerlich nicht sein, gilt aber auch hier.
Re: lange und kurze Tastendrücke (python-evdev)
Verfasst: Montag 16. August 2021, 22:29
von smutbert
Ok verstanden und Dankeschön.
Also keine Methoden, die mit __ beginnen/aufhören. (Ich habe das im Netz an vielen Stellen anders gelesen und gesehen, aber das heißt ja nichts ☺)
Das mit der CPU-Last werde ich mir noch einmal genau ansehen, aber zuerst muss ich das mit den Callbacks hinbekommen. Eventuell bitte ich da noch einmal um Hilfe, wenn mir die Ideen ausgegangen sind...
Re: lange und kurze Tastendrücke (python-evdev)
Verfasst: Montag 16. August 2021, 23:11
von __blackjack__
@smutbert: An vielen Stellen? Ich kenne nur recht wenige. Wo findet man so etwas?
Re: lange und kurze Tastendrücke (python-evdev)
Verfasst: Mittwoch 18. August 2021, 22:37
von smutbert
In meinem Browser sind die meiste Zeit viele Tabs offen und die zu dem Thema habe ich alle nach __deets__ Antwort geschlossen.
Bis jetzt habe ich davon kaum etwas wieder gefunden, aber ich habe noch von mindestens einer Frage auf stackoverflow und einem Blog die Erklärung (ok, das ist zugegebenermaßen noch nicht viel) in Erinnerung, dass _x_ (oder _x ?) ein leichter Hinweis sein soll, dass die Methode nur zur Verwendung innerhalb der Klasse gedacht ist und dass __x__ dasselbe in verschärfter Form bedeutet. Zumindest bei den beiden war nicht die Rede davon, dass man solche Namen selbst nicht verwenden soll.
Vielleicht habe ich nach der Lektüre der falschen Erklärungen auch viele richtige (vielleicht zum Teil etwas schwammige oder unvollständige) Erklärungen falsch verstanden – man (ich) versteht, sieht, liest ja oft eher das was man erwartet und nicht das was da wirklich steht.

Re: lange und kurze Tastendrücke (python-evdev)
Verfasst: Mittwoch 18. August 2021, 22:47
von __deets__
Einfacher unterstrich ja. Doppelter nein, macht was anderes, als du denkst. Aber in beiden Fällen geht es dabei nur um den Anfang. Vorne & hinten ist “falsch” im Sinne von macht man nicht, weil es eine anders belegte Konvention ist.
Re: lange und kurze Tastendrücke (python-evdev)
Verfasst: Mittwoch 18. August 2021, 23:59
von __blackjack__
Um den
Style Guide for Python Code zu zitieren:
__double_leading_and_trailing_underscore__: “magic” objects or attributes that live in user-controlled namespaces. E.g. __init__, __import__ or __file__. Never invent such names; only use them as documented.
Re: lange und kurze Tastendrücke (python-evdev)
Verfasst: Freitag 20. August 2021, 20:19
von smutbert
Die dunders sind weg, aber jetzt habe ich bei gpiozero ein Problem, das ich nicht verstehe. Als Ersatz für die gepostete Input-Klasse mit evdev habe ich dasselbe mit gpiozero umgesetzt. Wenn ich eine gekürzte funktionierende Variante posten darf
Code: Alles auswählen
#!/usr/bin/env python3
import queue
import gpiozero
class Input:
def __init__(self):
self.queue = queue.Queue()
self.setup_elements()
self.set_callbacks()
def setup_elements(self):
self.select_encoder = gpiozero.RotaryEncoder(27, 22)
self.volume_encoder = gpiozero.RotaryEncoder(10, 9)
def set_callbacks(self):
self.select_encoder.when_rotated_counter_clockwise = lambda: self.queue.put('left')
self.select_encoder.when_rotated_clockwise = lambda: self.queue.put('right')
self.volume_encoder.when_rotated_counter_clockwise = lambda: self.queue.put('down')
self.volume_encoder.when_rotated_clockwise = lambda: self.queue.put('up')
def get(self, timeout=None):
return self.queue.get(block=True, timeout=timeout)
wenn ich das versuche die Drehimpulsgeber und deren Callbacks in Schleifen zu definieren funktioniert es nicht mehr – es verhält sich so als würde das Setzen des Callbacks des zweiten Drehimpulsgebers ('volume') das des ersten Überschreiben und in der queue landen nur mehr 'down' und 'up' Einträge unabhängig davon welchen Drehimpulsgeber ich betätige:
Code: Alles auswählen
#!/usr/bin/env python3
import queue
import gpiozero
ENCODERS = {
'select': { 'pins': ( 27, 22 ), 'events': ( 'left', 'right' ) },
'volume': { 'pins': ( 10, 9 ), 'events': ( 'down', 'up' ) },
}
class Input:
def __init__(self):
self.queue = queue.Queue()
self.encoders = {}
self.setup_elements()
self.set_callbacks()
def setup_elements(self):
for encoder in ENCODERS.keys():
self.encoders[encoder] = gpiozero.RotaryEncoder(*ENCODERS[encoder]['pins'])
def set_callbacks(self):
for encoder in ENCODERS.keys():
self.encoders[encoder].when_rotated_counter_clockwise = lambda: self.queue.put(ENCODERS[encoder]['events'][0])
self.encoders[encoder].when_rotated_clockwise = lambda: self.queue.put(ENCODERS[encoder]['events'][1])
def get(self, timeout=None):
return self.queue.get(block=True, timeout=timeout)
ich finde da keinen prinzipiellen Fehler
Re: lange und kurze Tastendrücke (python-evdev)
Verfasst: Freitag 20. August 2021, 20:59
von __blackjack__
Die ``lambda``-Ausdrücke nehmen nur den Namen `encoder` mit, nicht dessen Wert. Erst wenn die ``lambda``-Funktion aufgerufen wird, wird der *aktuelle* Wert von `encoder` ermittelt, und das ist der vom letzten Schleifendurchlauf.
Schau Dir mal `functools.partial()` an. Und ich würde den Wert für die `put()`-Aufrufe auch schon in dieser Schleife auflösen.
Das sieht übrigens alles zu weit eingerückt aus. Konvention sind vier Leerzeichen pro Ebene.
Nachtrag: Wenn Du am Ende *immer* sowohl Schlüssel als auch Wert brauchst, mach gleich eine Schleife über Schlüssel/Wert-Paare, statt nur über die Schlüssel.
Die beiden Extra-Methoden sind auch ein bisschen Overkill.
Code: Alles auswählen
#!/usr/bin/env python3
import queue
from functools import partial
import gpiozero
ENCODERS = {
"select": {"pins": (27, 22), "events": ("left", "right")},
"volume": {"pins": (10, 9), "events": ("down", "up")},
}
class Input:
def __init__(self):
self.queue = queue.Queue()
self.encoders = {}
for name, data in ENCODERS.items():
encoder = gpiozero.RotaryEncoder(*data["pins"])
direction_a, direction_b = data["events"]
encoder.when_rotated_counter_clockwise(
partial(self.queue.put, direction_a)
)
encoder.when_rotated_clockwise(
partial(self.queue.put, direction_b)
)
self.encoders[name] = encoder
def get(self, timeout=None):
return self.queue.get(timeout=timeout)
Re: lange und kurze Tastendrücke (python-evdev)
Verfasst: Freitag 20. August 2021, 21:23
von smutbert
Oh, an die functools.partial kann ich mich natürlich erinnern – das hast du mir in mein Hauptprogramm eingebaut und auch wenn ich sonst einiges geändert habe, steht das noch immer dort.
Meinst du die Zuordnung könnte/sollte etwa so aussehen?
Code: Alles auswählen
from functools import partial
encoder_to_event = {
"select": {
"pins": (27, 22),
"events": (
partial(queue.put, "left"),
partial(queue.put, "right")
)
}
}
(für die Einrückungen mache ich zumindest zum Teil die Forensoftware verantwortlich, aber ich bemühe mich mich zu bessern ☺)
edit:
Hoppla, das hat sich jetzt etwas überschnitten. Du meinst es also nicht so!
__blackjack__ hat geschrieben: Freitag 20. August 2021, 20:59
Nachtrag: Wenn Du am Ende *immer* sowohl Schlüssel als auch Wert brauchst, mach gleich eine Schleife über Schlüssel/Wert-Paare, statt nur über die Schlüssel.
Dass solche Dinge in python möglich sind vergesse ich immer wieder.
__blackjack__ hat geschrieben: Freitag 20. August 2021, 20:59
Die beiden Extra-Methoden sind auch ein bisschen Overkill.
Das habe ich mir zuerst auch gedacht, aber insgesamt habe ich da in meiner Klasse am Ende, 2 Schalter mit je 2 Events (mit der Option auf bis zu 6 Taster mit dann insgesamt 12 Events), die beiden Drehimpulsgeber ebenfalls mit je zwei Events und zwei thematisch nur bedingt dazupassende LEDs.
Damit verdreifacht sich die Zahl der Schleifen immerhin schon einmal und ich habe es mit eigenen Methoden übersichtlicher gefunden.
wieder einmal Dankeschön!
Re: lange und kurze Tastendrücke (python-evdev)
Verfasst: Sonntag 22. August 2021, 11:40
von smutbert
Jetzt muss ich noch einmal auf das Thema python-evdev oder python-gpiozero kommen. Ich habe jetzt nämlich zwei untereinander austauschbare Klassen, ähnlich der, die ich bereits gepostet habe, wobei eine davon gpiozero und die andere evdev nutzt und die hohe CPU-Last tritt nur mit gpiozero auf.
__deets__ hat geschrieben: Sonntag 15. August 2021, 12:42[...]
Ich wuerde dir aber raten, auf gpiozero zu wechseln. Deine Beobachtungen in allen Ehren, aber ich sehe keinen technischen Grund dafuer, und solche Konzepte umzusetzen ist fuer Anfaeger oft eine Ueberforderung. Darum haben die sowas ja eingebaut. Ich vermute mal eher, du hast einen Fehler bei der Gestaltung der Hautpschleife gemacht, und dadurch eine Endlosschleife erzeugt, die dann das Verhalten erklaert. Das darf natuerlich nicht sein, gilt aber auch hier.
Ich habe versucht das mit möglichst wenig Code zu provozieren und es passiert auch mit diesem übersichtlichen Progrämmchen
Code: Alles auswählen
#!/usr/bin/env python3
import gpiozero
import time
from functools import partial
ENCODERS = {
'select': { 'pins': ( 27, 22 ), 'events': ( 'left', 'right' ) },
'volume': { 'pins': ( 10, 9 ), 'events': ( 'down', 'up' ) },
}
encoders = {}
for name, data in ENCODERS.items():
encoder = gpiozero.RotaryEncoder(*data['pins'])
encoder.when_rotated_counter_clockwise = partial(print, data['events'][0])
encoder.when_rotated_clockwise = partial(print, data['events'][1])
encoders[name] = encoder
while True:
time.sleep(0.5)
in top sieht das so aus
Code: Alles auswählen
top - 12:15:18 up 48 min, 2 users, load average: 0.89, 0.30, 0.10
Tasks: 138 total, 2 running, 136 sleeping, 0 stopped, 0 zombie
%Cpu(s): 8.5 us, 11.6 sy, 0.0 ni, 79.9 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st
MiB Mem : 909.9 total, 505.7 free, 81.9 used, 322.4 buff/cache
MiB Swap: 0.0 total, 0.0 free, 0.0 used. 770.6 avail Mem
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
733 root 20 0 26884 10668 5496 S 42.5 1.1 0:27.34 python3
[...]
Es scheint allerdings abhängig von einen der beiden Drehimpulsgebers zu passieren – dann ist die CPU-Last allerdings konstant hoch.
Der fragliche Drehimpulsgeber hat keine Rasterung und eine optische Abtastung – es könnte also sein, dass er in einigen Stellungen ständig zwischen zwei Zuständen hin und her schwingt/wackelt.
Mein Verdacht ist, dass das dem Kernel-Treiber (rotary-encoder) nichts ausmacht (da gibt es auch in den Kernelprozessen keine auffällige CPU-Last), während gpiozero unter diesen Umständen diese CPU-Last verursacht.
Kann ich irgendwie nachprüfen ob die Last in der RotaryEncoder-Klasse und in welcher Instanz dieser Klasse die Last verursacht wird?
Allerdings verursacht dieses Verhalten bis jetzt keine ernstzunehmenden Probleme, weder ist die Temperatur auffällig hoch noch läuft irgendetwas zäh.
Re: lange und kurze Tastendrücke (python-evdev)
Verfasst: Dienstag 24. August 2021, 10:20
von __deets__
Also so fein Granular zu Profilen ist mE nicht möglich. Ich bin aber immer noch ein bisschen erstaunt, dass der soooo viel last erzeugt. Klar ist das im Kernel ein bisschen effizienter, aber das der da gleich auf 40% rumrödelt ist schon hart. Gibt der denn auch andauern was aus? Auch wenn du gar nichts machst?
Re: lange und kurze Tastendrücke (python-evdev)
Verfasst: Donnerstag 26. August 2021, 20:42
von smutbert
Nein, Ausgabe gibt es keine – bis auf die Last verhält sich alles wie erwartet.
Wenn ich dem fraglichen Drehimpulsgeber einen kleinen Ruck gebe (und das nötigenfalls ein paar Mal wiederhole) ist die CPU-Last wieder auf dem üblichen niedrigen Niveau. Deshalb auch mein Verdacht, dass der Drehimpulsgeber da gerade zwischen 2 Zuständen „zittert“ und gpiozero damit nicht so gut zurecht kommt.
Re: lange und kurze Tastendrücke (python-evdev)
Verfasst: Donnerstag 26. August 2021, 22:06
von __deets__
Aber sollte der dann nicht auch neue Werte liefern? Selbst wenn die immer nur +/- 1 schwanken?
Wenn das wirklich dein Problem ist, dann sollte da ggf. ein microcontroller oder ein Schmitt trigger zwischen.
Re: lange und kurze Tastendrücke (python-evdev)
Verfasst: Freitag 27. August 2021, 13:58
von smutbert
Die beiden Ausgänge des Drehimpulsgeber beim Drehen insgesamt vier Zustände durchlaufen, im Uhrzeigersinn z. B.:
- 00
- 10
- 11
- 01
und das wiederholt sich dann. An der Abfolge der Zustände wird die Drehrichtung erkannt und meistens (auch bei mir) ist es so, dass 4 Zustandsänderungen in einer Richtung, also ein kompletter Zyklus dieser 4 Zustände, als ein „Impuls“ gewertet wird.
Ein Drehimpulsgeber könnte also ohne weiteres, etwa durch Vibrationen tatsächlich zwischen zwei Zuständen zittern ohne, dass es neue Werte gibt und bei 400 Impulsen, also 1600 Zustandsänderungen pro Umdrehung kommt mir das auch einigermaßen plausibel vor.
Bei nächster Gelegenheit werde ich die Zustände einmal überwachen und prüfen ob die sich bei erhöhter CPU-Last tatsächlich ändern und bei niedriger Last gleich bleiben.