Problem: LEDs am Raspberry Pi über Blynk-app und Python-Code steuern

Python auf Einplatinencomputer wie Raspberry Pi, Banana Pi / Python für Micro-Controller
stefanpc81
User
Beiträge: 23
Registriert: Montag 14. Februar 2022, 13:38

Hallo,
ich bin noch Anfänger mit Python und habe im forum-raspberrypi.de keine Antwort auf mein Problem gefunden. Ich weiß nicht, ob man den Link des Themas dessen Forums hier posten darf? Mein Problem steuert zuletzt mehr in Richtung Python-Programmierung, weswegen ich hier eher auf eine Antwort hoffen kann. Aber nun zum Sachverhalt:
Ich habe einen Raspberry Pi 3B+ und ich möchte erreichen, dass über den GPIO 22 und 23 LEDs geschaltet sind, welche abwechselnd für je eine Sekunde leuchten sollen. Ich möchte dies mit der Blynk app steuern (virtual pin 1), welches zwar prinzipiell aber noch nicht endgültig funktioniert. Was ich konkret erreichen will:
Wenn der virtuelle Pin 1 aus ist, sollen beide LED-Stränge 22 & 23 aus sein. Sobald der V1-Pin eingeschaltet wird soll für 1 Sekunde nur der GPIO 22 an und der 23er aus sein. Danach soll GPIO 22 aus und der 23er an sein, wieder für 1 Sekunde. Dieses abwechselnde "blinken" soll solange laufen bis der V1-Pin ausgeschaltet wird.
Am Wochenende habe ich versucht einen Code aufzusetzen, welchen ich erst einmal mit Visual Studio testen wollte ohne den ganzen LED/GPIO-Code, da diesen VS ja nicht kennt. Ziel ist es mit der "test"-Funktion den Status des virtuellen Pins 1 zu simulieren. Nach Ausführen diesen Codes ist das Ergebnis an der Konsole aber noch folgendes:
wait
1
2
wait
wait

Code dazu:

Code: Alles auswählen

#!/usr/bin/env python3
from time import sleep
import threading

def loop():
    print(1)
    sleep(1)
    print(2)

def endloop():
    print("wait")

class MyCallback():
    def register(self, function):
        self.cb=function

class setInterval():
    def __init__(self, interval, function, o):
        self.interval=interval
        self.function=function
        self.o=o
        self.o.register(self.callback)
        self.stopEvent=threading.Event()
        thread=threading.Thread(target=self.__setInterval)
        thread.start()

    #self.o.cb=
    def callback(self, val):
        if val == ["1"]:
            self.run()
        else:
            self.cancel()

    def __setInterval(self):
        #while not self.stopEvent.wait():
            self.function()
            
    def run(self):
        self.stopEvent.clear()
        loop()

    def cancel(self):
        self.stopEvent.set()
        endloop()

    #@blynk.handle_event("write V1")
    #def write_virtual_pin_handler(self, pin, value):
        #self.o.cb(value)
    
    #temp
    def test(self, value):
        self.o.cb(value)

def main():
    myc=MyCallback()
    inter=setInterval(2, loop, myc)
    inter.test(1)
    sleep(2)
    inter.test(0)
    sleep(1)
    inter.test(1)

if __name__ == "__main__":
    main()
Mein Problem ist, dass das setInterval-Objekt "inter" noch nicht wie gewünscht als Schleife läuft und über einen einzigen Thread laufen soll, damit man den Thread stoppen (V1=0) und wieder starten (V1=1) kann. Ich hoffe, ihr versteht was ich meine bzw. erreichen will? Ansonsten bin ich für jeden Lösungsvorschlag dankbar!
Viele Grüße,
Stefan
Benutzeravatar
Dennis89
User
Beiträge: 1154
Registriert: Freitag 11. Dezember 2020, 15:13

Hallo,

es wurde hier schon öfters ins Raspberry-Forum verlinkt. Du wirst auch merken, dass es teilweise die gleichen User sind.

Dein Code ist ziemlich verstrickt, schau mal ist folgender Code nicht das was du willst?

Code: Alles auswählen

from time import sleep
from threading import Thread
from random import choice


class ControlLeds(Thread):
    def __init__(self, virtual_pin):
        Thread.__init__(self)
        self.virtual_pin = virtual_pin
        self.leds = [22, 23]
        
    def run(self):
        while True:
            if self.virtual_pin:
                for led in self.leds:
                    print(f'{led}.on()')
                    sleep(1)
                    print(f'{led}.off()')
                    sleep(1)
    
        
def main():
    virtual_pin = None
    control_leds = ControlLeds(virtual_pin)
    control_leds.start()
    while True:
        control_leds.virtual_pin = choice([True, False])
        print(f'V1-Pin ist jetzt auf {control_leds.virtual_pin}')
        sleep(5)
    
    
if __name__ == '__main__':
    main()

Grüße Dennis

P.S. Vllt kannst du im anderen Forum dein Thema noch als erledigt markieren und Bescheid geben, das es hier weiter geht?
"When I got the music, I got a place to go" [Rancid, 1993]
stefanpc81
User
Beiträge: 23
Registriert: Montag 14. Februar 2022, 13:38

Hallo,
@Dennis
Danke für deine Antwort. Aber ich glaube, du hast es auch noch nicht verstanden, was das Ziel ist ;-)
Die test-Funktion dient mir momentan unter main() nur als Rückmeldung mit der "print" Ausgabe ob mein Code funktioniert oder nicht. Es soll kein Zufallsgenerator sein.
Ich versuche es nochmal den gewünschten Ablauf zu erklären:

1. Start des Python-Skripts: LED 22+23 aus
2. Blynk V1 an => setInterval mit Funktion loop() soll alle 2 sekunden ausgeführt werden.
Also 3. LED 22 an, 23 aus, sleep(1) LED 23 an, 22 aus und nach 1 weiteren Sekunde setInterval mit loop() wieder und wieder. Solange bis
3. Blynk V1 = aus => setInterval() pausiert bzw. LED 22+23 aus. Solange aus bis
4. Blynk V1 an, siehe 2.

Wie im Raspberry Pi-Forum mit meinem Beitrag https://forum-raspberrypi.de/forum/thre ... -3-funkti/ geantwortet wurde, würde dort mit meinem ursprünglichem Code der setInterval-Thread ständig neu erzeugt werden bzw. ist nicht mit einem einzelnen Thread löschbar. Deswegen habe ich jetzt hier in diesem Forum versucht, den setInterval() als Thread zu registrieren, allerdings weiß ich nicht ob da so funktioniert. Scheinbar ja nicht. Und den Thread des setInterval() mit der loop-Funktion wollte ich anhalten (V1 aus) bzw,. fortführen (V1 an). Das ist offenbar auch gescheitert.
Im Raspberry-Forum meinte ein User auch, dass ich eine Callback einbauen solle. Da man mir dort keine Antwort gab, für WAS es einen Callback braucht (setInterval, Virtual Pin status, Funktion loop) habe ich es mit diesem Code angenommen, es sei der Zustand des virtuellen Pins 1. Ob das richtig ist, weiß ich leider auch nicht.
Lange Rede kurzer Sinn: Ich habe mehr Fragen als Antworten um mein Problem zu lösen.
Grüße.
Stefan
Benutzeravatar
__blackjack__
User
Beiträge: 13100
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@stefanpc81: Aber genau das macht doch das Beispiel, LEDs blinken in einem Thread das man anhalten und weiterlaufen lassen kann. Und hier ist es halt zufällig, was Du halt über eine externe Eingabe machen willst, die hier halt keiner hat, darum mit Zufall simuliert.
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
stefanpc81
User
Beiträge: 23
Registriert: Montag 14. Februar 2022, 13:38

Hallo,
Entschuldigung, dann habe ich das missverstanden. Vielen Dank soweit. Bleibt noch offen, wie ich das mit dem Blynk-Objekt verbinde. Ich habe mal nach bestem Wissen und Gewissen versucht, das so zu schreiben:

Code: Alles auswählen

#!/usr/bin/env python3
from time import sleep
from threading import Thread

import blynklib
from RPi import GPIO

BLYNK_AUTH = 'MYAUTHTOKEN'

blynk = blynklib.Blynk(BLYNK_AUTH)

class ControlLeds(Thread):
    def __init__(self, virtual_pin):
        Thread.__init__(self)
        self.virtual_pin=virtual_pin
        self.leds=[22, 23]
        GPIO.setmode(GPIO.BCM)
        GPIO.setup(self.leds, GPIO.OUT, initial=GPIO.LOW)

    def run(self):
        while True:
            if self.virtual_pin:
                GPIO.output(self.leds, (GPIO.HIGH, GPIO.LOW))
                sleep(1)
                GPIO.output(self.leds, (GPIO.LOW, GPIO.HIGH))
                sleep(1)

    @blynk.handle_event("write V1")
    def write_virtual_pin_handler(self, virtual_pin):
        if self.virtual_pin == ["1"]:
            self.run()
        else:
            GPIO.output(self.leds, GPIO.LOW)

def main():
    try:
        virtual_pin=None #None durch 0 ersetzen?
        control_leds=ControlLeds(virtual_pin)
        control_leds.start()
        while True:
            blynk.run()
    finally:
        GPIO.cleanup()

if __name__ == '__main__':
    main()
Könntet ihr da bitte noch mal drüber schauen? Da sind bestimmt noch Fehler drin... ;-) Deswegen habe ich das Ganze noch nicht am RPi ausprobiert.
__deets__
User
Beiträge: 14537
Registriert: Mittwoch 14. Oktober 2015, 14:29

Das ist quatsch. self.virtual_pin ist ein bool, das bestimmt, ob der schon laufende Thread die LEDs blinken laesst oder nicht. Alles, was in deinem virtual pin handler also passieren muss ist, eben diese boolsche Eigenschaft umzusetzen. So wie das in Dennis Code vorgemacht wird, durch den randomisierten Teil. Und natuerlich gehoert da auch keine direkte Manipulation des GPIOs im event hin.

Und sowohl das Argument an den handler als auch die Eigenschat virtual_pin zu nennen ist schlecht, und hat auch prompt zu einem Fehler gefuehrt.

Im Thread sollte das sowas wie "blink" heissen, denn das steuert es ja. Und im event-handler setzt du das, basierend auf dem Argument des Handlers (denke ich mal. Blynk API hast du ja selbst eingebaut).
Benutzeravatar
__blackjack__
User
Beiträge: 13100
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

Wobei das mit dem Dekorator so nicht klappen wird, denn der ist ja für Funktionen und nicht für Methoden. Das hatte ich im anderen Forum ja schon geschrieben, das `blynk` so etwas nicht wirklich vorsieht, das ist halt eine blöde API die eigentlich ein globales Objekt will und einen dazu mehr oder weniger dazu drängelt selbst auch globale Variablen zu verwenden.

Also erst mal müsste das `blynk` von Modulebene verschwinden. Das müsste in der `main()` definiert, an die `__init__()` übergeben werden und dort drin kann man dann den Dekorator ”manuell”, also nicht mit der Dekoratorsyntax, dafür verwenden um die Methode für den Rückruf zu registrieren. Schön ist anders, aber noch gruseliger wäre ja noch mehr globale Variablen und dann auch noch Threads.
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
stefanpc81
User
Beiträge: 23
Registriert: Montag 14. Februar 2022, 13:38

Hallo,
danke soweit. Jetzt bin ich aber gespannt, wie viele (hoffentlich wenige) Fehler noch drin sind... Das mit dem Callback und -Registrieren habe ich nicht verstanden. Das kann so noch nicht stimmen, aber ich weiß nicht wie es hier in meinem Fall richtig gehen muss. Der Code:

Code: Alles auswählen

#!/usr/bin/env python3
from time import sleep
from threading import Thread

import blynklib
from RPi import GPIO

class ControlLeds(Thread, object):
    def __init__(self, isblinking, callback):
        Thread.__init__(self)
        self.isblinking=isblinking
        self.cb=callback
        self.leds=[22, 23]
        GPIO.setmode(GPIO.BCM)
        GPIO.setup(self.leds, GPIO.OUT)

    def run(self):
        while True:
            if self.isblinking:
                self.blink(1)
                sleep(1)
                self.blink(2)
                sleep(1)

    def blink(self, num):
        if num == 1:
            GPIO.output(self.leds, (GPIO.HIGH, GPIO.LOW))
        elif num == 2:
            GPIO.output(self.leds, (GPIO.LOW, GPIO.HIGH))

def main():
    try:
        BLYNK_AUTH = 'MYAUTHTOKEN'
        blynk = blynklib.Blynk(BLYNK_AUTH)

        @blynk.handle_event('write V1')
        def write_virtual_pin_handler(pin, value):
            if value == ['1']:
                control_leds.isblinking=True 
            else:
                control_leds.isblinking=False

        isblinking=None
        control_leds=ControlLeds(isblinking, write_virtual_pin_handler)
        control_leds.start()

        while True:
            blynk.run()
    finally:
        GPIO.cleanup()

if __name__ == '__main__':
    main()
__deets__
User
Beiträge: 14537
Registriert: Mittwoch 14. Oktober 2015, 14:29

Das sieht an sich schon besser aus. Leider habe ich blynk nicht zum laufen bekommen, der schmeisst mir immer auth-Fehler. Ich wuerde es mal ausprobieren, wobei ich Fragezeichen bezueglich des pin/value habe - das ist alles nicht so doll dokumentiert, oder ich hab's nicht gefunden. Und Dennis hat da ein etwas unnoetiges Konstrukt gewaehlt, das isblinking als Argument an den Konstruktor (und vorher in main) kann weg, und stattdessen setzt du self.isblinking gleich auf False.

Last but not least: der thread wie er ist, verballert bei isblinking == False 100% CPU, weil er nur loopt. Da sollte zB ein else-Zweig rein, der mit time.sleep(.1) oder so kurz wartet.
Benutzeravatar
__blackjack__
User
Beiträge: 13100
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@stefanpc81: Die Übergabe von `write_virtual_pin_handler()` an die Klasse sieht überflüssig aus Das wird an ein Attribut gebunden das nirgends verwendet wird. Und die Reihenfolge ist so natürlich falsch, denn sobald die Funktion als Callback bei `blynk` registriert ist, kann sie natürlich aufgerufen werden, aber zu dem Zeitpunkt ist noch nicht garantiert, dass `control_leds` schon existiert und das führt dann zu einer Ausnahme.

Du hast da jetzt den Weg über ein Closure gewählt, statt eine Klasse zu verwenden.
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
stefanpc81
User
Beiträge: 23
Registriert: Montag 14. Februar 2022, 13:38

@__blackjack__:
Wie sollte ich jetzt deiner Meinung nach den Code konkret ändern? Ich habe das jetzt nicht verstanden. Brauche ich besser eine zweite Klasse? Ich dachte, der write_virtual_pin_handler müsse den Wert von 1 oder 0 auf True/False umgesetzt werden und
@ __deets__ schreibt, dass das weg kann?!
PS: Das MYAUTHTOKEN ersetzt eine individuelle Zeichenfolge, die niemals vom User im Forum gepostet werden sollte, vielleicht lag es daran, dass es einen Fehler gab?
Sirius3
User
Beiträge: 17747
Registriert: Sonntag 21. Oktober 2012, 17:20

Konstanten wie `BLYNK_AUTH` schreibt man üblicherweise ganz oben hin, direkt nach den Importen.
Funktionen definiert man nur selten innerhalb anderer Funktionen. Wenn man in einer if-Abfrage eh nur wieder einen Wahrheitswert setzt, kann man den Wahrheitswert der if-Bedingung auch direkt verwenden.
Warum erbt ControlLeds direkt von object? Das passiert schon indirekt immer.
Du übergibst `isblinking` als None, obwohl das ein bool sein sollte.

Code: Alles auswählen

#!/usr/bin/env python3
from time import sleep
from threading import Thread
from functools import partial

import blynklib
from RPi import GPIO

BLYNK_AUTH = 'MYAUTHTOKEN'

class ControlLeds(Thread):
    def __init__(self):
        Thread.__init__(self)
        self.isblinking = False
        self.leds = [22, 23]
        GPIO.setmode(GPIO.BCM)
        GPIO.setup(self.leds, GPIO.OUT)

    def run(self):
        while True:
            if self.isblinking:
                self.blink(1)
                sleep(1)
                self.blink(2)
                sleep(1)

    def blink(self, num):
        if num == 1:
            GPIO.output(self.leds, (GPIO.HIGH, GPIO.LOW))
        elif num == 2:
            GPIO.output(self.leds, (GPIO.LOW, GPIO.HIGH))


def write_virtual_pin_handler(control_leds, pin, value):
    control_leds.isblinking = value == ['1']

def main():
    try:
        control_leds=ControlLeds()
        control_leds.start()
        
        blynk = blynklib.Blynk(BLYNK_AUTH)
        @blynk.handle_event('write V1')(partial(write_virtual_pin_handler, control_leds))

        while True:
            blynk.run()
    finally:
        GPIO.cleanup()

if __name__ == '__main__':
    main()
Jetzt sollte man bei Threads nicht direkt auf geteilte Variablen zugreifen. Das was Du hier als isblinking hast, ist der typische Anwendungsfall eines Events.
Die Werte 1 und 2 sind bei `blink` relativ willkürlich. Der LED-Pin wäre eine passende Variable.
setmode sollte nicht irgendwo innerhalb einer Klasse stattfinden, sondern möglichst am Anfang von main.

Code: Alles auswählen

#!/usr/bin/env python3
from time import sleep
from threading import Thread
from functools import partial
from itertools import cycle

import blynklib
from RPi import GPIO

BLYNK_AUTH = 'MYAUTHTOKEN'
LEDS = [22, 23]

class ControlLeds(Thread):
    def __init__(self, leds):
        Thread.__init__(self)
        self._blink_event = Event()
        self.leds = leds
        GPIO.setup(self.leds, GPIO.OUT)
        
    @property
    def isblinking(self):
        return self._blink_event.is_set()
        
    @isblinking.setter
    def isblinking(self, value):
        if value:
            self._blink_event.set()
        else:
            self._blink_event.clear()

    def run(self):
        active_led = cycle(self.leds)
        while True:
            self._blink_event.wait()
            self.blink(next(active_led))
            self.sleep(1)

    def blink(self, active_led):
        GPIO.output(self.leds, [GPIO.HIGH if active_led == led for led in self.leds])


def write_virtual_pin_handler(control_leds, pin, value):
    control_leds.isblinking = value == ['1']


def main():
    GPIO.setmode(GPIO.BCM)
    try:
        control_leds = ControlLeds(LEDS)
        control_leds.start()
        
        blynk = blynklib.Blynk(BLYNK_AUTH)
        @blynk.handle_event('write V1')(partial(write_virtual_pin_handler, control_leds))

        while True:
            blynk.run()
    finally:
        GPIO.cleanup()


if __name__ == '__main__':
    main()
__deets__
User
Beiträge: 14537
Registriert: Mittwoch 14. Oktober 2015, 14:29

stefanpc81 hat geschrieben: Mittwoch 16. Februar 2022, 06:47 @ __deets__ schreibt, dass das weg kann?!
PS: Das MYAUTHTOKEN ersetzt eine individuelle Zeichenfolge, die niemals vom User im Forum gepostet werden sollte, vielleicht lag es daran, dass es einen Fehler gab?
Ich habe nicht geschrieben, dass der weg kann. Ich habe geschrieben, dass er als Argument weg kann. Und ich habe ein eigenes auth-token.
stefanpc81
User
Beiträge: 23
Registriert: Montag 14. Februar 2022, 13:38

@Sirius3:
Ich habe das jetzt mal 1 zu 1 übernommen in meinem RPi, beim Starten der PY-Datei mit Python3 kommen aber noch Fehlermeldungen:

GPIO.output(self.leds, [GPIO.HIGH if active_led == led for led in self.leds])
"for" Syntax Error: invalid syntax
bzw. VS sagt: unexpected token "for"

und in VS steht für Zeile
@blynk.handle_event('write V1')(partial(write_virtual_pin_handler, control_leds))
unexpected token "("
unexpected token "("

Also wenn wir das noch beheben könnten und es dann endlich funktioniert, bin ich glücklich und an alle Mitwirkenden dankbar!
__deets__
User
Beiträge: 14537
Registriert: Mittwoch 14. Oktober 2015, 14:29

Das @ am Anfang der Zeile muss weg.
stefanpc81
User
Beiträge: 23
Registriert: Montag 14. Februar 2022, 13:38

@__deets__
Danke.

@all
Und was bitte stimmt in der Zeile mit dem "for" nicht?!
Benutzeravatar
Dennis89
User
Beiträge: 1154
Registriert: Freitag 11. Dezember 2020, 15:13

Code: Alles auswählen

[GPIO.HIGH for led in self.leds if active_led == led]
So?

Grüße
Dennis
"When I got the music, I got a place to go" [Rancid, 1993]
stefanpc81
User
Beiträge: 23
Registriert: Montag 14. Februar 2022, 13:38

@Dennis
Wenn man es so schreibt, zeigt VS keinen Fehler mehr an. LEIDER allerdings, wenn ich das Skript auf meinem RPi starte, kommt jetzt ein neuer Fehler:
self.blink_event = Event()
NameError: name "Event" is not defined
__deets__
User
Beiträge: 14537
Registriert: Mittwoch 14. Oktober 2015, 14:29

Event muss auch aus threading importiert werden.
stefanpc81
User
Beiträge: 23
Registriert: Montag 14. Februar 2022, 13:38

@__deets__
Habe ich gemacht.

Eine gute, eine schlechte Nachricht: Das Skript läuft jetzt ohne Fehler und die Blynk-App startet. Aber die LEDs blinken oder leuchten nicht, sobald ich den V1 pin auf meiner App einschalte. Wieder aus und ein nützt auch nichts. Der V1 pin ist in meiner app mit 0 = aus und 1 = an definiert. Da habe ich nichts dran geändert. Schade, irgendwelche Vorschläge?
Antworten