GPIO.add_event_detect() fällt nach ca. einer Stunde aus

  • Hallo zusammen,

    ich will in einem größeren, komplexen System (Gewächshaussteuerung) den Wasserdurchfluss in einer Leitung messen. Dazu habe ich einen digitalen Durchflussmesser (Modell YF-B10).

    In diesem ist ein Rädchen, das pro Umdrehung einen Impuls sendet.

    Diese messe ich mit der Funktion GPIO.add_event_detect(). Der Pin, an dem gemessen wird, wird von keinem anderen Programmteil angefasst (also keine Zugriffskonflikte)

    Das funktioniert auch alles gut, aber nach einer halben oder ganzen Stunde stürzt das System ab mit der Fehlermeldung:

    RuntimeError: Failed to add edge detection.

    Die Funktion Abwasser () wird von einem übergeordneten Loop alle 5 Sekunden aufgerufen. Am Schluss der aufrufenden Funktion steht auch GPIO.cleanup().

    Auch der Einbau von GPIO.remove_event_detect() am Ende jedes Durchgangs nutzt nichts.


  • GPIO.add_event_detect() fällt nach ca. einer Stunde aus? Schau mal ob du hier fündig wirst!

  • Moin,

    irgendwie ist dei Programmstruktur durcheinander.

    Die def Abwasser() rechnet und deklariert den Pin fortlaufend? Das ist nicht sinnvoll.

    Einmal im Hauptlauf den Pin mit der eventbehandlung deklarieren und im count_pulse alles zaehlen/rechnen.

  • Grüße,

    wie ottosieben schon schrieb solltest du das GPIO.add_event_detect(FLOW_SENSOR_GPIO, GPIO.BOTH, callback=countPulse) in deiner Hauptschleife einbauen und als Callback die Abwasserfunktion aufrufen.

    Diese Abwasserfunktion erechnet dir dann deine Menge.

    Deine Pins usw. deklarierst du, in der Regel, am Anfang des Scripts.

    Das es Global gibt vergisst du am besten gleich wieder. Damit machst du es dir nicht leichter.

    Nimm stattdessen eine Klasse.

    Besser wäre auch gpiozero zu benutzen. Das ist schöner, eleganter und sehr gut dokumentiert.

    In etwa so:

    EDIT:

    In dem Event solltest du auch das GPIO.BOTH in GPIO.RISING ändern.

    Du willst ja nur zählen wenn der Impuls kommt also die Flanke steigt und nicht auch wenn die Flanke wieder fällt.

    EDITEDIT:

    Code angepasst.

    5 Mal editiert, zuletzt von keepfear (24. April 2021 um 21:34)

  • Hallo,

    keepfear

    In der Funktion 'errechne_abwassermenge' hast du ein 'Impulse' stehen, das ist wohl ein 'e' am Schluss zuviel.

    Wieso die Umwandlung in 'int' und die Ausgabe wieder als String?

    in deiner print-Zeile fehlt ein `"` im f-String und das 'e' taucht hier auch wieder auf.

    Wenn das Skript beendet wird sollte die GPIO-Pins noch aufgeräumt werden.

    Ich habe mal gelernt, das man ein Klassenobjekt einmal erzeugt und wenn man Werte speichern will, bindet man sie an 'self'. Das soll jetzt keine Kritik an dir sein, ich habe gelesen dass du den Code nur als Orientierung gepostet hast. Und auch wenn ich auch zu 'gpiozero' raten würde, poste ich mal noch meine ungetestete Version des Programms(ob das mit der Schleife und dem Callback funktioniert weis ich nicht):

    Grüße

    Dennis

    🎧 With the music execution and the talk of revolution, it bleeds in me and it goes 🎧

  • Dennis89 ich hab den Code nochmal angepasst. Danke

    Ich habe mal gelernt, das man ein Klassenobjekt einmal erzeugt und wenn man Werte speichern will, bindet man sie an 'self'.

    In dem Fall ist es, meiner Meinung nach, egal ob du nun ein Instanzattribut oder ein Klassenattribut setzt.

    Es geht ja erstmal nur um das Speichern der Werte ohne Globals.

    Da Brazzelhuber, offensichtlich, Anfänger ist macht es für mich auch keinen Sinn da OOP zu Programmieren. Das kann, wie du sicher auch selbst weißt, schnell mal überfordern. Vor allem als Anfänger. Zumindest war das bei mir so.

    Wieso die Umwandlung in 'int' und die Ausgabe wieder als String?

    Wenn das Skript beendet wird sollte die GPIO-Pins noch aufgeräumt werden

    Darauf hab ich nicht geachtet und nur Teile des Codes aus dem Anfangspost kopiert. Wir kennen auch seinen restlichen Code nicht und von daher sag ich einfach, das es erstmal egal ist.

    Ich hab auch nur wenig bis gar keine Erfahrung mit RPi.GPIO.

  • Hallo zusammen,

    vielen Dank für die vielen Hinweise und Tipps. Vor allem auch Danke für den Hinweis auf gpiozero, das ich bisher nicht kannte.

    Leider bin ich noch nicht groß weiter gekommen. Das Problem ist offenbar, dass, wenn ich "add_event_detect" oder bei gpiozero "when_pressed" in eine Schleife einbaue, bei beiden nach ca. 30 bis 60 Minuten ein RuntimeError kommt, mit der Meldung, dass der thread nicht gestartet werden kann (die Überwachung des Pins läuft offenbar als separater Thread, der tausendemal aufgerufen wird). Es scheint bei beiden Bibliotheken einen Art "stackoverflow" zu geben, was das Aufrufen des Überwachungsthreads angeht. Es hilft auch nichts, wenn ich bei GPIO "remove_detect" oder cleanup() ans Ende der Schleife setze.

    Mein Programm ist modular aufgebaut, wo ausgehend von der Hauptschleife (Durchlauf 1 mal pro Sekunde) neben vielen anderen Funktion und Unterfunktionen die Funktion Wertelesen() aufgerufen wird. Ich versuche jetzt, die Edgedetection vor die oberste Hauptschleife zu setzen, damit sie nur ein einziges mal aufgerufen wird.

    Folgender Testcode funktioniert (Langzeittest steht noch aus), im Hauptprogramm geht's noch nicht. Aber ich arbeite dran.

    Vielen Dank nochmal und schönes Wochenende!

  • Es wäre hilfreich wenn du deinen kompletten Code zeigen würdest und auch die Fehlermeldungen.

    Wie gesagt: Globalevariabeln sind BÖSE. Die wollen wir nicht. Das geht auch mit der Klasse.

    Das macht eher wenig Sinn:

    Durch die while-Schleife in deinem Hauptprogramm zählt der Counter ständig hoch und wird dir ein falsches Ergebnis bringen

    und

    wie schon gesagt, musst du das Event_detection in deiner Mainfunktion/Hauptprogramm unterbringen und damit rufst du dann deine Funktion auf die dir deine Abwassermenge berechnet.

    Hier nochmal ein Beispiel für GpioZero:

    EDIT:

    Das Problem ist offenbar, dass, wenn ich "add_event_detect" oder bei gpiozero "when_pressed" in eine Schleife einbaue, bei beiden nach ca. 30 bis 60 Minuten ein RuntimeError kommt, mit der Meldung, dass der thread nicht gestartet werden kann

    Jup, das Event darf in keiner Schleife liegen, weil das Event_detection beim Start deines Scripts einmal geladen wird (so hab ich es mal gelernt) und darauf wartet bis am Pin ein Flankenwechsel eintritt und du sagst dann ob bei steigender Flanke, bei fallender Flanke oder bei beiden Flankenwechsel etwas passieren soll.

    Mein Programm ist modular aufgebaut, wo ausgehend von der Hauptschleife (Durchlauf 1 mal pro Sekunde) neben vielen anderen Funktion und Unterfunktionen die Funktion Wertelesen()

    Wenn du eine Funktion hast die dir die Werte liest kannst du beim Messen deiner Abwassermenge ja einfach wie oben gezeigt den Wert Impuls.abwassermenge in deiner Funktion Wertelesen() weiter verarbeiten.

    Und nochmal, vergiss das es Global gibt.

    Einmal editiert, zuletzt von keepfear (25. April 2021 um 09:36)

  • keepfear ``global`` vergessen bringt aber nichts wenn Du das jetzt so löst. Es ist ja nicht das Schlüsselwort an sich Böse™, sondern globale Variablen, und ob Du die nun mit ``global`` anlegst, oder als Klassenattribute, ändert ja nix daran, dass das globale Variablen sind.

    Spätestens wenn man zusätzliche Argumente an Rückrufe binden will und das sauber ohne globale Variablen lösen will, braucht man mindestens Closures oder `functools.partial()` oder eben Klassen. Da müssen auch Anfänger durch.

    Ich frage mich bei so etwas ja manchmal ob es auch Leute gibt die bei C- oder Pascal-Anfängern sagen, lasst das mit ``struct`` oder ``record`` mal weg wenn das noch zu kompliziert ist. Denn mal so ganz grundsätzlich sind Klassen in Python ja auch *der* selbstdefinierte Verbunddatentyp. Ohne geht es einfach ab einem gewissen Punkt nicht, wenn man nicht immer sehr viele Einzelwerte übergeben möchte, die eigentlich zusammen gehören.

    “Dawn, n.: The time when men of reason go to bed.” — Ambrose Bierce, “The Devil's Dictionary”

  • __blackjack__

    ``global`` vergessen bringt aber nichts wenn Du das jetzt so löst. ...

    Warum? Eine halbwegs vernünftig benannte Klasse mit halbwegs vernünftig benannten Klassenattributen find ich übersichtlicher als das olle global. Die Klasse ist sicher etwas Overkill zeigt aber das man da eben eine Klasse nutzen kann und wenn Brazzelhuber später dazu fragen haben sollte ist es doch um so besser.

    Ich frage mich bei so etwas ja manchmal ob es auch Leute gibt die bei C- oder Pascal-Anfängern sagen, lasst das mit ``struct`` oder ``record`` mal weg wenn das noch zu kompliziert ist. ...

    Ich sage auch nicht das Brazzelhuber sich nicht mit Klassen auseinandersetzen soll, nur, wenn ich den Code im 1. Post sehe, ist es doch offensichtlich das er eher wenig Erfahrung mit Python hat. Wozu da also OOP? Zumal keiner weiß wie der restliche Code von Brazzelhuber aussieht.

  • keepfear Da wird keine Klasse benutzt, denn das ist ja keine Klasse. Da wird das Klassenkonstrukt als Namensraum für globale Variablen missbraucht.

    Wir sind hier bei nebenläufiger Programmierung, wir haben Anfängerterritorium sowas von hinter uns gelassen. Wem Klassen zu kompliziert sind, sollte sicher nicht mit Threads und globalen Variablen spielen. `start_counter` wird in dem Code beispielsweise anscheinend als Flag zwischen Threads benutzt, wo man eigentlich ein `threading.Event` verwenden sollte.

    “Dawn, n.: The time when men of reason go to bed.” — Ambrose Bierce, “The Devil's Dictionary”

  • __blackjack__ dann missbrauche ich da halt das Klassenkonstrukt. Ist mir auch egal. Ich finde es übersichtlicher. Wie gesagt das ist sicher eh Overkill.

    Für mich sieht das eher so aus als hätte er sein Programm falsch strukturiert, weil er alle 5 sek. seine "Abwasserfunktion" aus einer Hauptschleife aufruft. In dieser Abwasserfunktion ist das GPIO.add_event_detect() die dann wiederum eine andere Funktion aufruft die dann anfängt die Impulse zu zaehlen.

    Ich behaupte jetzt einfach das Brazzelhuber das GPIO.add_event_detect() falsch benutzt und dadurch das er das Event ständig aufruft ihm irgndwann das Script abschmiert.

  • keepfear Ich vermute eher das Gegenteil von Overkill. Wenn über Rückrufe hinweg Zustand gemerkt werden muss, dann ist das ein Paradebeispiel für den Einsatz von Klassen. Also über das zusammenfassen von Daten in einem Verbunddatentyp hinaus, wirklich objektorientierte Programmierung.

    Insbesondere weil man neben dem Zähler wohl auch noch wissen muss ob der gerade aktiv sein soll oder nicht, denn dynamisch immer wieder `add_event_detect()` aufzurufen funktioniert mit `GPIO` erfahrungsgemäss nicht. Das darf man nur ein mal machen.

    “Dawn, n.: The time when men of reason go to bed.” — Ambrose Bierce, “The Devil's Dictionary”

  • Hallo,

    ich habe mir gerade überlegt, wie ich das mit 'gpiozero' programmieren würde. Die Rahmenbedingungen sind ja so, das pro Umdrehung ein Impuls gesendet wird. Also haben wir ein 'InputDevice'. Um das aber kontrolliert zählen zu können, sollte ja die immer wiederkehrende steigende Flanke abgefangen werden?

    Mal unabhängig davon ob der Impuls jetzt so langsam kommt, damit es zu keinem Überlauf kommt oder ob er rasend schnell immer wieder kommt.

    Ich würde um Fehler zu vermeiden gerne die Flanken abfragen.

    So etwas kann ich in der Doku gerade nicht finden. Oder verbirgt sich das schon hinter 'when_activated' und 'gpiozero' nimmt einem das Denken in diesem Fall ab?

    Danke und Grüße

    Denns

    🎧 With the music execution and the talk of revolution, it bleeds in me and it goes 🎧

  • Dennis89

    Ich würde um Fehler zu vermeiden gerne die Flanken abfragen

    Das macht man doch mit when_pressed oder when_released bzw. wait_for_press oder wait_for_release.

    Je nachdem wie du es machen willst oder brauchst.

    ..... Ich versuche jetzt, die Edgedetection vor die oberste Hauptschleife zu setzen, damit sie nur ein einziges mal aufgerufen wird. ....

    Und das wird höchstwahrscheinlich des Rätsels Lösung sein.

    Dennis89 du kannst das auch nach empfinden wenn du zufällig nen Button, einen Magnetsensor o.ä. zu Hause rum liegen hast.

    Und mit:

    durchspielst.

    Da schmiert dir nichts ab.

    So hingegen schon:

    Code
    def say_hello():
        print("Hello!"
    
    def main():
        while True:
            button.when_pressed = say_hello
  • Moin keepfear ,

    dass ist nicht das was ich meinte, trotzdem Danke für deine Mühe.

    Mir ging es darum wie man Signalüberschneidungen programmiertechnisch abfangen könnte. Also wenn das zweite Signal schon kommt bevor das erste verarbeitet wird.

    Vielleicht habe ich mich etwas schlecht ausgedrückt, weil es eigentlich nicht unbedingt direkt zum Problem hier passt, aber es je nach Aufbau und Drehzahl durch aus vor kommen könnte.

    Grüße

    Dennis

    🎧 With the music execution and the talk of revolution, it bleeds in me and it goes 🎧

  • Dennis89

    Mir ging es darum wie man Signalüberschneidungen programmiertechnisch abfangen könnte. Also wenn das zweite Signal schon kommt bevor das erste verarbeitet wird.

    Jetzt hab ich es verstanden.

    Softwaretechnisch wahrscheinlich gar nicht.

    Wenn dein Programm die eingehenden Signale zu langsam verarbeitet und du schon alles super sauber programmiert hast wirst du an der Hardware schrauben müssen. Beim Wasserzähler dann eben den Durchfluss verringern.

    Eventuell das Signal auf 2 Pins verteilen?

Jetzt mitmachen!

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