Und die Threads werden sequentiell abgearbeitet.
Callback in Callback-Prozedur aufrufen
-
maksimilian -
26. Januar 2021 um 16:57 -
Erledigt
-
-
Callback in Callback-Prozedur aufrufen? Schau mal ob du hier fündig wirst!
-
Thread. Einer für alle. Darum muss, was darin passiert, so kurz wie möglich sein. Und ist zwangsweise auch seriell.
-
Deshalb funktioniert das in #9 beschriebene Beispiel nicht mit Callbacks für die Reeds. Die frage ich bisher mit value ab. Da ich bei meiner Schaltung manchmal Probleme mit den Pin-Pegeln habe (gpiozero!), bekomme ich falsche Reaktionen. Mir ist nicht klar, ob es sicherer ist, wenn ein Reed mit when_pressed abgefragt wird.
-
- Offizieller Beitrag
a ich bei meiner Schaltung manchmal Probleme mit den Pin-Pegeln habe (gpiozero!), bekomme ich falsche Reaktionen.
Soetwas kann passieren, wenn man Fehler eingebaut hat, aber wir haben hier bisher noch nicht einen Schnipsel Code von Dir gesehen und wissen auch nicht was Du wie verkabelt hast. Da bleibt uns leider nur raten.
-
Das ist nicht ganz richtig, siehe dort #3.
Zufällig bekam ich bei einer ganz einfachen Test-Konfiguration (Pi 3B+ und offizielles Netzteil mit 2 Tastern der Klasse Button mit pull_up und externem 10k Pullup ) bei Betätigung eines Tasters ein Signal vom anderen. Deshalb bin auch noch auf der Suche nach einer stabilen Beschaltung der Pins. Gnom hat mir eine Schaltung mit Optokoppler empfohlen (siehe auch obiger Link #6), welche ich noch nicht getestet habe.
-
- Offizieller Beitrag
Das ist nicht ganz richtig
Bringt aber leider trotzdem nichts, ohne Deinen aktuellen Code zu kennen.
-
Du kannst dir z.B. den Quellcode von RPi.GPIO laden: https://sourceforge.net/projects/raspb…tar.gz/download
In RPi.GPIO-0.7.0/source/event_gpio.c ist in Zeile 455 add_edge_detect definiert und dort werden auch die Threads erstellt, falls ich jetzt nicht völlig daneben liege
So wie ich es sehe, wird in event_gpio.c der Ablauf im Thread beschrieben. Der Thread wird mit pthread_create() erzeugt. Im Thread wird eine Liste (gpio_list) verwaltet, welche alle Callbacks enthält. Ein während des Ablaufs einer Callback-Prozedur eintreffendes Ereignis wird verworfen.
Bitte, kann mir jemand einen Tipp geben, wie ich mir für die Compilierung eines Pakets (hier jetzt RPI.GPIO) eine Entwicklungsumgebung aufbaue. Ich würde gerne z.B. in event_gpio.c Traces einbauen, um die dortigen Abläufe besser erkennen zu können.
-
Hab ich noch nie gemacht. C ist auch nicht meine Stärke, aber Debuggen geht auf jeden Fall.
Hier mal was ich gefunden habe: https://pythonextensionpatterns.readthedocs.io/en/latest/debu…bug_in_ide.html
-
Ich würde mir diesen deep dive sparen. Ich habe den Quelltext schon vor Jahren analysiert, und das was da passiert ist ganz einfach: es werden die Kernel GPIO primitive und deren User Space exponierung genutzt. Und dadurch gehen auch keine Ereignisse verloren. Wenn ein IRQ auftaucht, dann wird da eine Routine angesprungen, und das vermerkt. Im Kernel. Da kann auch Zeit vergehen, bis man mal wieder vorbeischaut. Das macht nichts. Außer, dass man macht eben den Fehler und denkt, man kann beliebigen und lang laufenden Code im Händler starten. Denn dann kehrt der Thread natürlich spät zurück, und die eigene Erwartungshaltung, das inzwischen Ereignisse ank9mmen würden, wir nicht erfüllt.
Das Problem hier ist nicht die Art, wie die IO Libs arbeiten. Und das die Fehler machen. Es ist der Anwendungscode. Den du stoisch unter Verschluss hältst.
Wenn du weiterhin drauf bestehst, diesen Karnickelbau zu erforschen (lehrhaft ist das unzweifelhaft), dann wirst du Kernel-Instrumentierung nutzen müssen. perf zb.
-
Auch wenn Ihr mir abratet, in die Tiefe zu steigen, bedanke ich mich nochmal für Eure weiterhin vorhandene Motivation, mir zu helfen (hoffe ich doch). Wenn Ihr Euch unbedingt damit weiter beschäftigen wollt, könnt Ihr Euch die Anhänge anschauen, welche den aktuellen Stand des Projekts beschreiben. Dafür habe ich mich endlich durchgerungen, den Schaltplan in einer vorzeigbaren Form zu zeichnen.
-
Ok, es ist wie es zu erwarten war. Das ist ein hochverschachteltes Gebilde, das um Größenordnungen Zuviel in seinen callbacks macht, und mit großen Mengen globalem Zustand verwirrt.
Fangen wir klein an: debouncing kann gpiozero selbst. Das kann alles rausfliegen. Ein callback darf des weiteren (aus den Gründen, die hier schon diskutiert wurden) nicht Zuviel machen. So wenig wie möglich sogar. Ein gerne genutztes Muster ist darum lediglich die Information, welcher Pin getriggert hat, in eine Queue zu stecken. Zb den String “Open” wenn der entsprechende Knopf gedrückt wurde. Diese Information wird in der bei dir ja ansonsten toten Hauptschleiffe blockierend (also wartend) aus der Queue geholt. Damit hast du schon mal die Trennung der verantwortlichen Threads erreicht, und kannst wenigstens theoretisch auf mehrere Ereignisse reagieren.
Allerdings würde ich deine Aufgabe tatsächlich NICHT vollkommen mit Ereignissen lösen. Denn es gibt bei dir zwei kritische Systemzustände, in denen du sinnvoll eh auf nichts reagieren kannst. Nämlich immer dann, wenn ein Motor dreht. Dann MUSS bis zum anschlagen des Reeds gewartet werden.
Die Reeds werden immer synchron abgefragt. Wenn das Kommando zum öffnen oder schließen reinkommt, wird erst geprüft, ob der korrespondierende Reed schon geschlossen ist. Wenn ja, dann wird das Kommando ignoriert. Wenn nicht, dann wird der Motor angeworfen. Und mit
So lange gewartet, bis der gegenüberliegende Reed Vollzug meldet. Erst dann gehts im Hauptprogramm weiter. Und sollten in der Zwischenzeit andere Ereignisse eingetreten sein, warten die eben brav in der Queue. Und nichts geht verloren.
Dieser Ansatz ist robust, und simpel. Und das halte ich angesichts der Gefahr, den Motor über die Endpunkte zu drehen, auch für ein entscheidendes Entwurfskriterium.
Und zu guter letzt noch zur IPC. Der Würgaround mit dem Pin kommt weg. Die FIFO Datei bleibt geöffnet. Das auslesen erfolgt in einem eigenen thread, der einlaufende Instruktionen auch einfach zeilenweise einliest, und in die gleiche Queue steckt, in der auch die PIN-Ereignisse landen. Wenn man da zb Uniform mit Kommandos arbeitet wie “Open” in “Close” etc, die man von allen Ereignissen erzeugt, kommt diese Erweiterung fast umsonst daher.
-
- Offizieller Beitrag
maksimilian Ich bin mir nicht ganz sicher, aber vermutlich ist Dir der Begriff oder die Möglichkeit eines (pseudo) Interrupt nicht bekannt. Meine Vermutung bezieht sich auf verschiedene z.T. unnötige Schleifen in Deinem Skript, die als Abfallprodukt nicht nur sinnlos Prozessorlast erzeugen.
Das was @__deets__ mit Button.wait_for_press() angesprochen hat, wäre genau das was es in dem Fall braucht und ich damit meine. Im Netz gibt es viele Beispiele, auch für Python, zum Verständnis dazu. Das Suchwort hast Du ja nun.
Falls ich mich mit meiner Vermutung doch irren sollte, dann schon mal ein dickes "Sorry!" dafür!
-
Danke, __deets__, dass Du Dir so schnell Zeit zur Durchsicht meiner Skripte genommen und sogar meine "IPC" kommentiert hast (was ich auch gehofft hatte). Dass letztere zwar eine funktionierende aber nicht gerade elegante Lösung ist (den Begriff "Würgaround finde ich gut), war mir klar. Ich werde spontane Fragen zurückhalten und erst einmal auf der Basis Deiner Vorschläge, so wie ich sie verstehe, experimentieren. Das eingestellte Steuerungsskript stellt auch nicht mehr den aktuellen Stand dar, weil ich natürlich selber über eine Weiterentwicklung nachdenke. So beispielsweise über das Queuing zur Entlastung der Callbacks.
Ich bin eigentlich froh über mein Anfangsprojekt, weil es mir gleich eine etwas fortgeschrittenere Anwendung von Python ermöglicht. Sonst wäre es auch langweilig.
-
Hier die neue Version (ohne Kommentare und Test-Prints):
Python
Alles anzeigen#!/usr/bin/env python3 # -*- coding: utf-8 -* # Programm zur Steuerung der Außentüre und der inneren Falltüre der Hünerhütte. # from gpiozero import * import os, sys, datetime from time import sleep import time import signal import subprocess from pathlib import Path import threading from concurrent import futures from chicken_pins import * # Tür Aktionen: D_OPENING = 0 D_CLOSING = 1 D_STOP = 2 D_CLOS = 3 # Tuer ganz geschlossen D_OPEN = 4 # Tuer ganz offen # zugehörige Texte: D_TEXT = ["öffnend", "schliessend", "stop", "geschlossen", "geöffnet"] # Doors Index fuer: D_ENA = 0 # Motortreiber Enable D_IN1 =1 # Motortreiber Eingang 1 D_IN2 = 2 # Motortreiber Eingang2 D_DUTY = 3 # PWM Duty D_RD_O = 4 # Offen-REED D_RD_U = 5 # Geschlossen-REED D_PROT = 6 # Deltazeit für Motor-Timeout, dient zum Motorschutz D_LOCK = 7 # Schloss vorhanden D_STAT = 8 # Tuerstatus # Contact_U1 = Button(Pin_REED_U1, pull_up=True) Contact_U2 = Button(Pin_REED_U2, pull_up=True) Contact_O1 = Button(Pin_REED_O1, pull_up=True) Contact_O2 = Button(Pin_REED_O2, pull_up=True) # # Aggregat zum Sammeln der Türeigenschaften: Doors = [[Pin_ENA1,Pin_IN1,Pin_IN2,0.2,Contact_O1,Contact_U1, 15.0,True,D_STOP], [Pin_ENA2,Pin_IN3,Pin_IN4,0.4,Contact_O2,Contact_U2, 20.0, False,D_STOP]] # Doors Index: DOOR1 = 0 # Außentür DOOR2 = 1 # Falltür # # Motor Objekte: Motor1 = Motor(Doors[DOOR1][D_IN1], Doors[DOOR1][D_IN2], Doors[DOOR1][D_ENA], pwm=True) Motor2 = Motor(Doors[DOOR2][D_IN1], Doors[DOOR2][D_IN2], Doors[DOOR2][D_ENA], pwm=True) # Aggregat zum Sammeln der Motortreibereigenschaften: Motors= [Motor1, Motor2] # Button_DOOR1 = Button(Pin_DOOR1, pull_up=True, bounce_time=0.1) Button_DOOR2 = Button(Pin_DOOR2, pull_up=True, bounce_time=0.1) Button_OPEN = Button(Pin_OPEN, pull_up=True, bounce_time=0.1) Button_CLOSE = Button(Pin_CLOSE, pull_up=True, bounce_time=0.1) Button_STOP = Button(Pin_STOP, pull_up=True, bounce_time=0.1) Remote_STOP = False # Stop vom Handy aus ausgelöst Button_SHUT = Button(Pin_SHUT, pull_up=True, bounce_time=0.1) # # Interprozesskommunikation für Steuerungsaufrufe vom Handy: FIFO = 'mypipe' FIFO_path = str(Path.home())+"/"+FIFO # # Initialzustand: CurDoor = DOOR1 # aktuelle Tuer mit Vorbesetzung OpeningStop = Doors[DOOR1][D_RD_O] # das Öffnen begrenzender Reed ClosingStop = Doors[DOOR1][D_RD_U] # das Schließen begrenzender Reed Queue = [] # eintreffende Ereignisse sammelnder Fifo # def door_sel(index): global OpeningStop, ClosingStop, CurDoor # CurDoor = DOOR2 if int(index) == DOOR1: CurDoor = DOOR1 OpeningStop = Doors[CurDoor][D_RD_O] ClosingStop = Doors[CurDoor][D_RD_U] # def motor_do(action): # Motoraktion: Drehung vorwärts oder rückwärts, Anhalten # Es wird die Zeit gemessen, welche ein Vorgang benötigt. Wenn eine in der globalen Variablen Doors # definierte Zeit verstrichen ist, wird ein fehlerhaftes Verhalten angenommen und das Programm beendet. # Dieses Verhalten dient dem Schutz des Motors. # Vor Ablauf der Schutzzeit wird eine Aktion beendet durch # Ansprechen des Reed-Kontaktes # durch Betätigen der Stop-Taste oder Stop Aufrufs der Handy-App # global Remote_STOP # duty = Doors[CurDoor][D_DUTY] lock_active = False if action == D_OPENING: stop_switch = Doors[CurDoor][D_RD_O] # Reed final_state = D_OPEN if Doors[CurDoor][D_LOCK] and ClosingStop.value: lock_thread = futures.ThreadPoolExecutor(max_workers=1) lock_thread.submit(lock_do) # Thread starten lock_active = True Doors[CurDoor][D_STAT] = action Motors[CurDoor].forward(duty) else: stop_switch = Doors[CurDoor][D_RD_U] final_state = D_CLOS Motors[CurDoor].backward(duty) schutzzeit = Doors[CurDoor][D_PROT] startzeit = float(time.time()) Remote_STOP = False while True: # Button.wait_for_press kann nicht verwendetb werden, da Stop erkannt werden soll endzeit = float(time.time()) if( stop_switch.value == True or Button_STOP.is_pressed or Remote_STOP or schutzzeit > 0 and endzeit > startzeit + schutzzeit): if stop_switch.value == True: Doors[CurDoor][D_STAT] = final_state if Button_STOP.is_pressed == True: Doors[CurDoor][D_STAT] = D_STOP if Remote_STOP: pass Motors[CurDoor].stop() if schutzzeit > 0 and endzeit > startzeit + schutzzeit: print("Ablauf der Motor-Schutzzeit fuehrt zum Beenden des Programms") do_exit() break else: continue if lock_active: lock_thread.shutdown() # Thread schließen # def lock_do(): # Türchloss betätigen. Die Funktion wird in einem Thread aufgerufen, damit sichergestellt ist, # dass bei Start des Motors das Schloss nicht blockiert. # Out_Lock1=DigitalOutputDevice(Pin_LOCK1) Out_Lock1.on() sleep(1) Out_Lock1.off() # def del_pipe(): if os.path.exists(FIFO_path): cmd = "rm " + FIFO_path subprocess.check_output(cmd, shell=True) # def clear(): del_pipe() # def do_exit(): clear() exit("ERROR - Programm wurde wegen Ablaufs der Motorschutzzeit beendet!") # def shutdown(): return # shutdown fürTest nicht durchgeführt clear() sleep(2) print("*** SHUTDOWN WIRD AUSGEFUEHRT ***") os.system( "sudo shutdown -h now" ) # def test_reeds(): # Bei Skript-Start werden die Schaltzustände der Reed-Kontakte und damit die Zustände # ermittelt, in welchen sich die Türen befinden (ganz offen oder geschlossen, geöffnet). # #!!! Die Pegel-Abfrage der Reed-Pins ist unzuverlässig (interner Pullup-Widerstand + externer 10k). #!!! Es wird für beide Türen gleichzeitig offen und geschlossen angezeigt. Mögliche Ursache #!!! wäre ein nicht stabiler Zustand des Spannungspegels an den zugeordneten Pins. # Doors[DOOR1][D_STAT] = D_STOP # Vorbesetzung Doors[DOOR2][D_STAT] = D_STOP # Vorbesetzung # # Pruefen, welche Reed-Kontakte betätigt sind if Doors[DOOR1][D_RD_O].value: Doors[DOOR1][D_STAT] = D_OPEN if Doors[DOOR1][D_RD_U].value: Doors[DOOR1][D_STAT] = D_CLOS if Doors[DOOR2][D_RD_O].value: Doors[DOOR2][D_STAT] = D_OPEN if Doors[DOOR2][D_RD_U].value: Doors[DOOR2][D_STAT] = D_CLOS # def interprocess(): # Durch eine SSH-Button App wird vom Handy aus das Skript chicken-com.py aufgerufen, # welches seinen Aufrufparameter in die vom Steuerungsskript eingerichtete Pipe schreibt, # Das Steuerungsskript liest in einem eigenen Thread in einer Schleife die Pipe aus und # trägt den Aufrufparameter in die Queue ein, welche die Hauptschleife abarbeitet. # Eine Ausnahme bildet die Stop Anweisung, bei welcher die globale Variable Remote_STOP # gesetzt wird, damit die laufende Motor-Aktion unmittelbar unterbrochen werden kann. # global Remote_STOP # try: os.remove(FIFO_path) except FileNotFoundError: pass os.mkfifo(FIFO_path) try: com_file = open(FIFO_path) except IOError as err: raise RuntimeError("FIFO kann nicht geöffnet werden") from err while True: par = com_file.readline() if not par: continue if par == 'shut': Queue.append(KEY_SHUT) elif par == 'open': Queue.append(KEY_OPEN) elif par == 'stop': Remote_STOP = True # Stop muss sofort wirken elif par == 'close': Queue.append(KEY_CLOSE) elif par == str(DOOR1): Queue.append(KEY_DOOR1) elif par == str(DOOR2): Queue.append(KEY_DOOR2) else: print("unbekannter Parameter ", par) # def callback_key(inst): key = int(Pins[str(inst.pin)]) Queue.append(key) # Ereignis stacken # test_reeds() # da sich die den Reed-Kontakten zugeordneten Pins gegenseitig beeinflussen, # können, ist die Ermittlung des Anfangszustands unsicher ! # Button_SHUT.when_pressed = callback_key Button_OPEN.when_pressed = callback_key Button_STOP.when_pressed = callback_key Button_CLOSE.when_pressed = callback_key Button_DOOR1.when_pressed = callback_key Button_DOOR2.when_pressed = callback_key # ComThread = futures.ThreadPoolExecutor(max_workers=1) ComThread.submit(interprocess) # Thread starten # print("*** Programm Start ***") # try: while True: if Queue: key=Queue.pop() if key == KEY_SHUT: shutdown() elif key == KEY_OPEN: #if not OpeningStop.value: # True, obwohl Reed offen !!! if not Doors[CurDoor][D_STAT] == D_OPEN: motor_do(D_OPENING) elif key == KEY_CLOSE: if not ClosingStop.value: motor_do(D_CLOSING) elif key == KEY_STOP: pass # KEY_STOP wird während des Öffnens/Schließens abgefragt elif key == KEY_DOOR1: door_sel(DOOR1) elif key == KEY_DOOR2: door_sel(DOOR2) #signal.pause() blockiert ! sleep(1) except (KeyboardInterrupt, SystemExit): clear() os.system("clear") exit("ERROR - Programm wurde durch Benutzer beendet!")
..... Diese Information wird in der bei dir ja ansonsten toten Hauptschleiffe blockierend (also wartend) aus der Queue geholt.
"blockierend" sieht wie aus ?
-
"blockierend" sieht wie aus ?
Probier es einfach aus:
Python
Alles anzeigenimport os from pathlib import Path FIFO = Path("my_fifo") def mkfifo(): FIFO.unlink(missing_ok=True) os.mkfifo(FIFO) mkfifo() print("FIFO erstellt") with FIFO.open("w") as fd: print("FIFO geöffnet") fd.write("Hello World") print("Daten geschrieben") print("Daten gesendet")
Ohne einen anderen Teilnehmer auf der anderen Seite der PIPE blockiert open.
Es wird dann nur FIFO erstellt ausgegeben.
Erst wenn die PIPE vom anderen Prozess geöffnet worden ist, werden die Daten geschrieben.
FIFO geöffnet und Daten geschrieben.
-
Das ist nicht die Queue, die ich meinte. Das ist schon wirklich die Klasse Queue aus dem Modul queue. Und die hat eine put und eine get Methode. Und die letztere blockiert so lange, bis etwas in die Queue gesteckt wurde. Du hast da das Rad schlecht neu erfunden, weil es nicht die richtige Semantik hat.
Und natürlich funktioniert wait_for_pressed, und hat auch einen timeout. Warum benutzt du das nicht? Das sind Dutzende Zeilen Code, die weg können.
Und diese Ding ist VIEL zu groß. Du wirst es nicht schaffen, diesen ganzen Moloch auf einmal zu implementieren. Und erst recht nicht mit diesem gigantischen Verhau an mehrfach indizierten globalen Variablen. Da steigt keiner durch. Schreib ein Programm, das nichts anderes macht, als bei EINEM GPIO durch when_pressed etwas in eine Queue gesteckt wird, und das in der Hauptschleife aus der richtigen Queue Holt, und ausgibt.
Und das zeigst du, und der nächste Schritt ist dann, genau EINE Tür zu steuern.
-
Danke, __deets__ , harte Schule !
-
Kurze Anmerkung: wait_for_pressed hat natürlich auch eine invertierte Variante, https://gpiozero.readthedocs.io/en/stable/api_…ait_for_release - wenn es nur um die Logik des Pegels geht.
-
Ohne einen anderen Teilnehmer auf der anderen Seite der PIPE blockiert open.
Es wird dann nur FIFO erstellt ausgegeben.
Erst wenn die PIPE vom anderen Prozess geöffnet worden ist, werden die Daten geschrieben.
FIFO geöffnet und Daten geschrieben.
Wichtiger Hinweis !
Ich habe ein Verständnisproblem bei der Pipe.
Der Reader muss in der Schleife vor jedem read einen open machen, in welchem er hängt bis der Writer den write abgibt.
-
Ich habe ein Verständnisproblem bei der Pipe.
Die habe ich auch, je länger ich mir das ansehe, ausprobiere und darüber ärgere, dass ich mich überhaupt noch mit Threaded code beschäftige.
Der Aufruf von os.mkfifo erzeugt die PIPE. Auf der anderen Seite öffnet der Worker die PIPE. Dieser Aufruf blockiert unter Linux. Dann öffnet der Erzeuger der PIPE die PIPE im WRITE oder APPEND Modus (ich glaube "a" für append ist besser). In dem Moment, wo der Erzeuger die Datei schreibend geöffnet hat, wird open() beim Worker (lesend) fertig. Der Aufruf von readline blockiert so lange, bis ein Steuerzeichen für eine neue Zeile übertragen worden ist. Die flush Methode auf der Erzeugerseite sorgt dafür, dass die Daten in die Named PIPE aus dem Buffer heraus auch geschrieben werden. So kann der Wroker Zeilenweise die Kommandos abarbeiten und ggf. in einem Thread in eine Queue packen.
Ehrlich gesagt betreibe ich weitaus weniger Aufwand mit pyzmq und das ist eine Message Queue für IPC (Inter Prozess Kommunikation) sehr low level. Anfängern ist das jedenfalls nicht zu empfehlen. MQTT würde sich z.B. anbieten. Dafür braucht man dann aber auch einen MQTT-Broker.
Wenn innerhalb eines Prozesses zwischen Threads kommuniziert werden soll, macht man das auch am besten über Queues.
Die named pipes braucht man dann überhaupt nicht.
-
Jetzt mitmachen!
Du hast noch kein Benutzerkonto auf unserer Seite? Registriere dich kostenlos und nimm an unserer Community teil!