Callback in Callback-Prozedur aufrufen

  • Thread. Einer für alle. Darum muss, was darin passiert, so kurz wie möglich sein. Und ist zwangsweise auch seriell.

    Einmal editiert, zuletzt von MistyFlower59469 (3. Februar 2021 um 16:56)

  • 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. :gk1:

  • 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.

  • 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.

  • 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

    Code
    Button.wait_for_press()

    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.

    Einmal editiert, zuletzt von MistyFlower59469 (8. Februar 2021 um 21:04)

    • 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):


    ..... 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:

    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.

  • 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!