While True in PyQt5

Wenn du dir nicht sicher bist, in welchem der anderen Foren du die Frage stellen sollst, dann bist du hier im Forum für allgemeine Fragen sicher richtig.
Antworten
PythonDonald
User
Beiträge: 27
Registriert: Donnerstag 20. Juni 2019, 07:35

Guten Morgen,

ich habe eine Frage zu Schleifen in PyQt5. Wenn ich eine While-True-Schleife verwende, hängt sich ja die GUI immer auf. Wie kann ich das umgehen, wenn ich aber trotzdem eine Funktion habe, die ich so lange im Hintergrund laufen lassen möchte, bis zum Beispiel jemand einen Beende-Button drückt?

Mit der Threading-Klasse habe ich das schon ohne Erfolg probiert. Gibt es noch andere Möglichkeiten?


LG
PythonDonald
Benutzeravatar
sparrow
User
Beiträge: 4193
Registriert: Freitag 17. April 2009, 10:28

Mit dem threading-Modul. Wenn du es probiert hast und keinen Erfolg hattest, hast du etwas falsch gemacht.
Wichtig ist, dass du nur aus dem Hauptthread die GUI manipulieren darfst. Dein Thread muss also ein Signal senden, das in dem Hauptthread bemerkt wird und auf das reagiert wird.
__deets__
User
Beiträge: 14539
Registriert: Mittwoch 14. Oktober 2015, 14:29

@sparrow: Es ist genau NICHT das threading Modul welches man benötigt. Sondern einen QThread. Denn nur dafür existiert das Konzept der Thread ownership und der queued signals.

@PythonDonald: ohne Code kann man nicht sagen, was du falsch machst.
PythonDonald
User
Beiträge: 27
Registriert: Donnerstag 20. Juni 2019, 07:35

Code: Alles auswählen

from __future__ import print_function
import sys, os
from PyQt5 import QtGui
from PyQt5.QtWidgets import*
import PyQt5.QtWidgets as widgets
import PyQt5.uic as uic
from PyQt5.QtGui import QIcon
import cv2 
import threading
import RPi.GPIO as GPIO
import time
import subprocess
import numpy
 
class Hauptfenster(QMainWindow):
    def __init__(self):
        super().__init__()
        uic.loadUi('/home/pi/Bildprogramm/Ui - Dateien/Bildprogramm.ui', self)
        self.setWindowTitle('Serienbildaufnahme')
        self.BeleuchtungAutomatisch.clicked.connect(self.BeleuchtungA)
        self.BeleuchtungEin.clicked.connect(self.BeleuchtungE)
        self.BeleuchtungAus.clicked.connect(self.BeleuchtungAu)
        self.show()
 
    def BeleuchtungA(self):
        self.BA = BeleuchtungA()
        self.BA.run()
 
 
class BeleuchtungA(threading.Thread):
    def __init__(self):
        threading.Thread.__init__(self)
        self.Lichtsteuerung()
    
    def Lichtsteuerung(self):
        video = cv2.VideoCapture(0)
        GPIO.setmode(GPIO.BCM)
        GPIO.setup(25, GPIO.OUT)
        self.True1 = True
        while self.True1:
            check, frame = video.read()
            gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
            if (numpy.mean(gray)) < 150:
                GPIO.output(25, GPIO.HIGH)
            if (numpy.mean(gray)) >= 150:
                GPIO.output(25, GPIO.LOW)
 
 
if __name__=="__main__":
    app=QApplication(sys.argv)
    app.setStyle('Fusion')
    F = Hauptfenster()
    sys.exit(app.exec())
Das ist mein Code. Die Funktion Lichtsteuerung soll permanent im Hintergrund ausgeführt werden. Leider sperrt mir PyQt5 dabei die Benutzeroberfläche, sodass sich das Programm durch die While-Schleife aufhängt..
__deets__
User
Beiträge: 14539
Registriert: Mittwoch 14. Oktober 2015, 14:29

Du musst ja auch Thread.start() aufrufen damit wirklich ein Thread entsteht. Der ruft dann von alleine run auf.

Und wie schon erwähnt - wenn du die GUI aus dem Thread manipulieren willst, benutz einen QThread.
PythonDonald
User
Beiträge: 27
Registriert: Donnerstag 20. Juni 2019, 07:35

Code: Alles auswählen

from __future__ import print_function
import sys, os
from PyQt5 import QtGui
from PyQt5.QtWidgets import*
import PyQt5.QtWidgets as widgets
import PyQt5.uic as uic
from PyQt5.QtGui import QIcon
import cv2 
import threading
import RPi.GPIO as GPIO
import time
import subprocess
import numpy
 
class Hauptfenster(QMainWindow):
    def __init__(self):
        super().__init__()
        uic.loadUi('/home/pi/Bildprogramm/Ui - Dateien/Bildprogramm.ui', self)
        self.setWindowTitle('Serienbildaufnahme')
        self.BeleuchtungAutomatisch.clicked.connect(self.BeleuchtungA)
        self.BeleuchtungEin.clicked.connect(self.BeleuchtungE)
        self.BeleuchtungAus.clicked.connect(self.BeleuchtungAu)
        self.show()
 
    def BeleuchtungA(self):
        self.thread = BeleuchtungA()
        self.thread.start()
 
 
class BeleuchtungA(QThread):
    def __init__(self):
        QThread.__init__(self)
        self.Lichtsteuerung()
    
    def Lichtsteuerung(self):
        video = cv2.VideoCapture(0)
        GPIO.setmode(GPIO.BCM)
        GPIO.setup(25, GPIO.OUT)
        self.True1 = True
        while self.True1:
            check, frame = video.read()
            gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
            if (numpy.mean(gray)) < 150:
                GPIO.output(25, GPIO.HIGH)
            if (numpy.mean(gray)) >= 150:
                GPIO.output(25, GPIO.LOW)
 
 
if __name__=="__main__":
    app=QApplication(sys.argv)
    app.setStyle('Fusion')
    F = Hauptfenster()
    sys.exit(app.exec())
Ich habe mir verschiedene Beispiele zu QThread angeguckt und versucht das umzusetzten.. leider wird die GUI immer noch gesperrt.
Hast du eine Idee was ich falsch mache?
__deets__
User
Beiträge: 14539
Registriert: Mittwoch 14. Oktober 2015, 14:29

Du musst auch die run Methode überladen. Hast du mal ein Beispiel für python threads angeschaut? So wie du das machst startest du Lichtsteuerung gleich im konstruktor. Natürlich läuft das dann endlos. Da gibt es ja noch keine Thread.

Thread Objekte (egal ob von Python oder Qt) sind keine magischen “alles was hier drin steht passiert in einem anderen Thread”-Objekte. Sondern müssen gestartet werden, und der eigentliche Thread started dann eine bestimmte, wohldefinierte Funktion. Eben run das man überladen muss. Oder noch besser gar nicht ableiten & stattdessen ein Target angeben. Steht auch in den Beispielen.

Und eine Anmerkung zu dem eigentlichen Vorgehen: eine Kamera als lichtsensor zu benutzen ist nicht ideal. Denn die regelt sich selbständig auf einen gleichmäßigen grauwert ein. Du bekommst also bereits prozessierte Wert, die dir ggf auf die Füße fallen je nach Kamera.
Benutzeravatar
__blackjack__
User
Beiträge: 13107
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@PythonDonald: Die Importe könnte man mal aufräumen. `os`, `PyQt5.QtWidgets`, `QtGui`, `QIcon`, `subprocess`, `time`, und `threading` werden importiert, aber nicht verwendet. Dazu Unmengen an Namen aus `PyQt5.QtWidgets` über den Sternchenimport. Allerdings ist da dann `QThread` nicht dabei, was aber im Code steht, und damit ein Indiz ist, dass Du das gar nicht hast laufen lassen bevor Du nachgefragt hast.

Du hast ``as`` beim Import falsch verstanden. Das ist zum umbenennen. Wenn man aber gar nicht umbenennt, dann ist das der falsche Weg, denn weder `GPIO` noch `uic` gibst Du damit einen anderen Namen. Dafür ist ``from … import …`` vorgesehen.

`BeleuchtungA()`, `BeleuchtungE`, und `BeleuchtungAu()` sind besch… Namen. Da muss der Leser jetzt raten was die kryptischen Anhängsel `A`, `E`, und `Au` bedeuten sollen. Zudem beschreibt nichts davon eine Tätigkeit, was bei Funktionen und Methoden aber der Fall sein sollte.

Fenster sollten sich nicht selbst anzeigen. Der `show()`-Aufruf gehört ins Hauptprogramm, welches in einer Funktion stehen sollte.

In der `__init__()` sollten alle Attribute eingeführt werden. Also auch `thread`. Dann kann man auch einfach prüfen ob bereits ein Thread-Objekt existiert, denn sonst hat man ein Problem wenn der Benutzer mehr als einmal auf die Schaltfläche klickt.

Den Board-Modus von GPIO sollte man im Hauptprogramm setzen und nicht irgendwo im Programm verstecken. Ausserdem muss man dafür sorgen das am Programmende die `cleanup()`-Funktion aufgerufen wird.

Die Pin-Nummer für das Licht sollte man als Konstante definieren.

``self.True1 = True`` – WTF‽ Der Name ist Superkacke und die Verwendung danach ist unsinnig. Entweder gleich literal `True` als Bedingung in die ``while``-Schleife schreiben, oder falls der Thread abbrechbar sein soll, die entsprechenden Mittel von `QThread` verwenden.

Den `check`-Rückgabewert von `cv2.VideoCapture.read()` sollte man auch auswerten und nicht einfach blind davon ausgehen, das alles in Ordnung ist.

Die beiden ``if``\s testen genau die entgegengesetzten Fälle, also ist das eigentlich ein ``if``/``else``. Aber selbst das braucht man nicht, denn das Ergebnis der ersten Bedingung kann man direkt als Wert für den Pin verwenden.

Code: Alles auswählen

#!/usr/bin/env python3
import sys

from PyQt5 import uic
from PyQt5.QtCore import QThread
from PyQt5.QtWidgets import QApplication, QMainWindow
from RPi import GPIO
import cv2
import numpy

LIGHT_PIN = 25


class Hauptfenster(QMainWindow):
    
    def __init__(self):
        super().__init__()
        uic.loadUi('/home/pi/Bildprogramm/Ui - Dateien/Bildprogramm.ui', self)
        self.setWindowTitle('Serienbildaufnahme')
        self.BeleuchtungAutomatisch.clicked.connect(
            self.start_automatic_lighting
        )
        self.BeleuchtungEin.clicked.connect(self.switch_light_on)
        self.BeleuchtungAus.clicked.connect(self.switch_light_off)
        self.thread = None
 
    def start_automatic_lighting(self):
        if self.thread is None or self.thread.isFinished():
            self.thread = AutomaticLightingControl()
            self.thread.start()
 
 
class AutomaticLightingControl(QThread):
    
    def run(self):
        video = cv2.VideoCapture(0)
        GPIO.setup(LIGHT_PIN, GPIO.OUT)
        while not self.isInterruptionRequested():
            is_ok, frame = video.read()
            if not is_ok:
                break
            GPIO.output(
                LIGHT_PIN,
                numpy.mean(cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)) < 150
            )


def main():
    GPIO.setmode(GPIO.BCM)
    try:
        app = QApplication(sys.argv)
        app.setStyle('Fusion')
        window = Hauptfenster()
        window.show()
        sys.exit(app.exec())
    finally:
        GPIO.cleanup()
 

if __name__== '__main__':
    main()
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
Antworten