Auswertung Impulsabstand mit GPIOZero ticks_diff

Heute ist Stammtischzeit:
Jeden Donnerstag 20:30 Uhr hier im Chat.
Wer Lust hat, kann sich gerne beteiligen. ;)
  • Hallo zusammen,

    ich möchte mit meinem ZeroW/Raspbian den Impulsausgang (S0) meines PV-Stromzählers auswerten, um bei ausreichender Leistung z.B. das Brauchwasser erwärmen zu können.

    Das Inputsignal liegt an GPIO3 an, die (Diagnose-) LED an GPIO27. Erwarteter Impulsabstand t>2s.

    Ich möchte die Funktion ticks_diff nutzen (Output in s), wie sie in LocalPiFactory oder gpiozero.Factory angeboten wird, und bei pull_up auf fallende Flanken triggern.

    Ich scheitere in der Regel in den Zeilen 7 oder 9 mit Fehlern in der Syntax (Abschließende Klammer kommt unerwartet bei geschachtelten Classes mit Parametern) oder fehlenden Attributen im alternativ verwendeten "Button", z.T. tief in GPIOZero.

    Ich habe folgenden python3 Quelltext für den Test:

    #
    from gpiozero.pins.local import LocalPiFactory
    from gpiozero import Pin, DigitalInputDevice, LED, Factory, TimeOfDay
    from datetime import time
    from signal import pause

    my_factory = LocalPiFactory
    led = LED(27)
    pin1 = DigitalInputDevice(3,pin_factory=my_factory)
    itime = 100.
    power = 0.
    pin1.function = 'input'
    pin1.pull_up = True
    Pin.edges = 'falling'
    tod = TimeOfDay(time(8), time(17))
    # if tod.when_activated:
    if pin1.when_pressed: print (" Impuls ")
    itime = pin1.ticks_diff(later)
    power = 7.2/itime
    print (" Power [kW] ", power)
    if power > 3.1: led.on
    pause()
    led.off
    #else:
    # tod.when_deactivated = led.off
    # led.off
    # pause()

    Da ich vom prozeduralen Programmieren komme, bin ich mit meinem Class-Latein am Ende. Die Dokumentation von GPIOZero hilft mir momentan nicht weiter, da für meine Anwendung die Beispiele fehlen.

    Kann mir jemand meinen Code zum Laufen bringen oder hat einer von Euch bereits eine Lösung für diese Aufgabe?

    Gruß HWolf

  • Einrückung ist bei Python wichtig, darum bitte als Code-Block in den Beitrag setzen (</> in der Leiste über dem Textfeld beim Beitragseditor).

    Was man so schon sehen kann ist das Du die `when_*`-Attribute falsch verwendest. Die sind dazu da um Rückruffunktionen zu registrieren die aufgerufen werden wenn das jeweilige Ereignis eintritt.

    Zudem muss man Methoden auch *aufrufen*. Ein ``led.on`` oder ``led.off`` hat keinen Effekt. Man muss schon ``led.on()`` und ``led.off()`` schreiben. Sonst greift man einfach nur auf das Attribut zu, das ergibt eine Methode, und mit der wird dann nichts gemacht.

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

  • Hier der Quelltext als Code-Block mit den ersten Verbesserungen...

  • Die Dokumentation von GPIOZero hilft mir momentan nicht weiter, da für meine Anwendung die Beispiele fehlen.

    Doch...einiges was du da eingebaut hast wird dort beschrieben.

    https://gpiozero.readthedocs.io/en/stable/recipes.html#button

    https://gpiozero.readthedocs.io/en/stable/recipes.html#led

    Dein pin1 = DigitalInputDevice(3,pin_factory=my_factory) ist schon als Input deklariert. Das sagt dir das DigitalInputDevice.

    Dann gibts da auch kein pin1.function = 'input' oder pin1.pull_up = True. Das macht man zusammen mit dem Objekt, also dem DigitalInputDevice.

    Wie wäre es, wenn du erstmal einen Schritt zurück gehst und schaust wie du das Signal ausgeben kannst?

    Wenn du bei steigender oder fallender Flanke noch mehr machen möchtest als die Led zu schalten, könnte man das folgendermaßen machen:

    oder so:

    oder oder....

  • Hallo keepfear,

    der LED-Test mit 32 Zeilen funktioniert leider nicht. Was ich gelernt habe ist, das die True Abfrage für das DigitalInputDevice ist .is_active, ob für False .not_active gilt, steht leider nicht in der Doc. Benötige ich dafür if trigger_pin.is_active: ... else: ...?

    "def mach_was_bei_high" stürzt mit Attributfehler ab. Ein Statement "global OUTPUT_PIN, led" in def main löst das Problem leider nicht.

    Gruß

    HWolf

  • Hallo,

    bitte immer genau den Code posten, der einen Fehler geworfen hat und bitte die vollständige Fehlermeldung mit dazu posten.

    Ich persönlich werde aus deinem Beitrag leider nicht schlau.


    Grüße

    Dennis

    Edit: Fast vergessen 'global' ist nicht dafür bekannt Probleme zu lösen, eher um welche zu verursachen. Zumindest auf langfristige Sicht.

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

  • Was ich gelernt habe ist, das die True Abfrage für das DigitalInputDevice ist .is_active, ob für False .not_active[...]

    Das setzt aber eine gewisse aktive Zeit voraus. mit when_activated, bzw. when_deactivated wird auf den Flankenwechsel des Pins reagiert und nicht darauf, wenn der Pin die Flanke schon ein Weilchen geändert hatte. (Und dann gibt es noch active_time, bzw. inactive_time...)

    >> https://gpiozero.readthedocs.io/en/stable/api_…italinputdevice

  • Danke hyle,

    der Link führte mich in den richtigen Abschnitt der Doc. Ich habe meine Änderung is_active durch when_activated ersetzt, der Attribute-Fehler ist weg.

    keepfair, Dennis, hyle

    es war meine Änderung, die den Attribute-Error in Zeile 25 verursachte.

    Ich werde mich bemühen Code und Fehlermeldungen zu transferrieren. Chromium läuft leider nicht auf dem ZeroW und ich bewege mich zwischen 2 Maschinen hin und her.

    Untenstehender Code läuft und schaltet die LED bei fallender und steigender Flanke.

    Verstehe ich partial() als Parallel-Verzweigung in die jeweilige def richtig?

    Wenn ich euch richtig interpretiere, übergebe ich trigger_pin an die beiden def, lese die inaktive Zeit in def mach_was_bei_high aus und implementiere meine weitere Auswertung ebenfalls dort?

    Danke erstmal!

  • Du brauchst das partial um beim callback das led übergeben zu können.

    Ohne dem würdest du eine Fehlermeldung bekommen. Da led innerhalb der Funktion nicht vorhanden ist. Stichwort: Scope.

    Wenn ein Pin als Input dient wie bei

    Code
    trigger_pin = DigitalInputDevice(INPUT_PIN, pull_up=True)

    dann liest du den ja aus und das macht man mit when_activated oder when_deactivated oder mit dem was gpiozero noch bietet. Wenn am Pin ein Wechsel von Low-Pegel auf High-Pegel oder umgedreht, registriert wird, wird die jeweilige Funktion aufgerufen.

    Es wäre sicher auch hilfreich, wenn du dir das Thema Klassen anschaust. Damit du einen Überblick bekommst was das trigger_pin.when_activated eigentlich bedeutet bzw. wie das zusammen hängt.

    Wenn du etwas schalten willst, gibts dafür LED oder DigitalOutputDevice oder oder... Das steht aber auch in der Doku, sogar mit Übersicht.

    gpiozero - Input base-classes

    gpiozero - Output base-classes

    Du kannst ja versuchen innerhalb der Funktionen deine Auswertung einzubauen.

    Chromium läuft leider nicht auf dem ZeroW und ich bewege mich zwischen 2 Maschinen hin und her.

    Hast du einen PC oder Laptop worauf du Putty oder VNC laufen lassen kannst?

  • Das sind aber Objekte mit Zustand, das sollte halt nicht global sein. Man kann dann das Modul nicht mal einfach nur importieren ohne das versucht wird auf irgendwelche Pins zuzugreifen. Zum Beispiel wenn man Dokumentation auf einem normalen Desktoprechner aus dem Quelltext erstellen möchte. Und für Unit-Tests ist es auch ungünstig das alles als globalen Zustand zu haben statt Mock-Pins in die Funktion(en) rein reichen zu können. `gpiozero` bietet da ja sogar schon Klassen für.

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

  • __blackjack__ Schreiben wir aneinander vorbei oder verstehe ich Deinen Beitrag nur nicht? :lol:

    Ob man in dem Fall die LED in der main() an einen Namen bindet, bzw. dieses Objekt erstellt oder schon auf der globalen Ebene, macht doch funktionell keinen Unterschied, da die main() eh läuft.

    Davon mal abgesehen, dass wie Du schon schriebst, das keine Konstanten im eigentlichen Sinn sind und man das sonst nicht macht.

  • Hallo,

    ich komme mit meiner Anwendung trotzdem nicht weiter: mir fehlt die active_time zwischen zwei fallenden Flanken. Wenn ich die in der inaktiven Zeit abfrage ist (1.) sie NonType (stürzt ab), frage ich sie (2.) während der aktiven Phase ab, bekomme ich ein Ergebnis im Millisekundenbereich zurück, obwohl ich Impulszeiten von 2s und 4s generiert habe, d.h. Ausgabe direkt nach Sprung in die Routine, nicht bei der nächsten negativen Flanke.

    Im Log "2s" und "4s" ergänzt.

    Code
    LED ist an, total [s]:  0.0016069850000803854  high [s]:  0.0016069850000803854
    LED ist aus,  2s                                low [s]:  0.0006849940000392962
    LED ist an, total [s]:  0.0011269899998751498  high [s]:  0.0011269899998751498
    LED ist aus, 4s                                 low [s]:  0.0006599939999887283
    LED ist an, total [s]:  0.0011289899998701003  high [s]:  0.0011289899998701003
    LED ist aus,  2s                                low [s]:  0.0006649939998624177
    LED ist an, total [s]:  0.001282989000173984   high [s]:  0.001282989000173984
    LED ist aus,  4s                                low [s]:  0.0006609940000998904
    LED ist an, total [s]:  0.0012999890000173764  high [s]:  0.0012999890000173764
    LED ist aus,                                    low [s]:  0.000664995000079216

    Der 3. Versuch war eine while-Schleife t1>timei, aber die lief übermehrere Impulsbetätigungen,ebenso der 4. mit float().

    Wie kann ich die Übergabe der active_time mit der nächsten fallenden Flanke synchronisieren oder zumindest kurz vorher stattfinden lassen?

  • hyle Das macht einen Unterschied. Wenn das in der `main()` steht, dann passiert das nur wenn die auch *ausgeführt* wird. Wenn es auf Modulebene steht, dann passiert es alleine durch das Importieren des Moduls, denn dann wird ja der Code auf Modulebene ausgeführt, auch wenn die `main()` nicht aufgerufen wird.

    Das hier kann ich nur auf einem Rechner importieren auf dem ``LED(42)`` tatsächlich funktioniert:

    Python
    from gpiozero import LED
    
    led = LED(42)
    
    def main():
        led.on()
    
    if __name__ == "__main__":
        main()

    Um also beispielsweise mit Sphinx Dokumentation aus dem Modul zu erzeugen, muss das auf einem Rechner laufen der GPIOs hat. Und selbst da kann man mit `gpiozero` Sachen schreiben die dann auf die Nase fallen, beispielsweise wenn man auf Modulebene was stehen hat das Kontakt zu einem per I²C angeschlossenen Gerät aufbauen will, dass dann aber auch tatsächlich angeschlossen sein muss.

    Hier dagegen reicht es das `gpiozero` installiert ist, was auch auf Rechnern komplett ohne GPIOs geht, und man kann das importieren ohne das es einen Fehler gibt, weil ``LED(42)`` nur durch den ``import`` nicht ausgeführt wird:

    Python
    from gpiozero import LED
    
    def main():
        led = LED(42)
        led.on()
    
    if __name__ == "__main__":
        main()

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

  • Wenn es auf Modulebene steht, dann passiert es alleine durch das Importieren des Moduls, denn dann wird ja der Code auf Modulebene ausgeführt, auch wenn die `main()` nicht aufgerufen wird.

    Jetzt verstehe ich worauf Du hinaus wolltest, auch danke an Dennis89 im "Hintergrund", der mir auch auf Sprünge half. :thumbup:

    Das Skript wird hier ja direkt ausgeführt, also "startet" die main(). Aber ok, mir ging es hier auch nur um diesen Fall, in dem ich persönlich diese Ausnahme favorisiert hätte, um partial nicht extra noch importieren und verwenden zu müssen. ;)

  • keepfear

    warum werden in deiner Programmstruktur keine Werte aus den def's zurückgegeben? (return(sec) hilft auch nicht.)

    Oder werden die Variablen ständig initialisiert? Was muß man ändern?

    Konkret: ttime und count erhalten jedesmal nach Rücksprung aus den def's den Wert =0., bzw. =0 , d.h. ich komme immer wieder in die Startphase in def duration und erhalte keine gemessene Zeitdifferenz sec (-> ttime).

    Der Log im Terminal sieht folgendermaßen aus:

    Code
     t.total: 30.0 ; time:  3882.8266971111298 ; 1
    LED ist an, total [s]:  0.0
    LED ist aus
     t.total: 30.0 ; time:  3885.8182570934296 ; 1
    LED ist an, total [s]:  0.0
    LED ist aus
     t.total: 30.0 ; time:  3890.1775419712067 ; 1
    LED ist an, total [s]:  0.0
    LED ist aus
  • HWolf Zuweisungen an Namen innerhalb einer Funktion sind innerhalb der Funktion. Die haben ausserhalb keinen Effekt. Das wäre auch ziemlich schräg und fehleranfällig wenn man bei jedem Aufruf darauf mit rechnen müsste, das sich ausserhalb die Werte von Variablen ändern könnten. Der Sinn von Funktionen ist ja gerade, das man saubere Schnittstellen hat, wo Werte über Argumente hinein kommen, und über den Rückgabewert das Ergebnis zurück kommt.

    `ttime` und `count` haben auf Modulebene nichts zu suchen. Da gehören keine Variablen hin.

    Und ich hatte es in #16 schon mal erwähnt: Du brauchst objektorientierte Programmierung wenn Du Dir etwas über Aufrufe hinweg merken willst. Rückgabewerte aus den Rückruffunktionen machen keinen Sinn, denn wohin sollten die denn zurückgegeben werden? Der Code im `gpiozero` hat keine Ahnung was er damit anstellen sollte.

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

Jetzt mitmachen!

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