QThread emit wird nicht sofort in GUI angezeigt

Python und das Qt-Toolkit, erstellen von GUIs mittels des Qt-Designers.
Antworten
michpro
User
Beiträge: 19
Registriert: Samstag 27. Dezember 2014, 21:30

Hallo Forum,

habe ein Problem mit einem QThread mit welchem ich je Sekunde eine Variable zwischen true und false toggle. Abhägig von dem Zustand soll in meiner Gui zwischen zwei pxmap umgeschaltet werden. Das Togglesignal funktioniert auch soweit (sehe ich in meinem shell) Der Umschalten der Pxmap ist allerding weit von einem Sekundentakt entfernt.
Vielleicht könnt Ihr mir helfen wo ich da meinen Fehler mache.

Die Klasse Lebenszeichen ist mein Thread welcher das Signal BlinktaktOn sendet - im der Klasse MainView soll im def LebenzeichenDone das Signal ausgewertet werden.

danke schon mal im voraus
michpro

Code: Alles auswählen

[Codebox=python file=Unbenannt.py]#Import für Qt
import sys
from PyQt4 import QtGui, QtCore, uic
from PyQt4.QtCore import QThread

#Import für Python
import datetime, time, threading
import configparser

#Import für Raspberry
import RPi.GPIO as GPIO

IMAGE1 = ("/ablage/nfs/pi/LedOn.png")
IMAGE2 = ("/ablage/nfs/pi/LedOff.png")




#Klasse MainView anlegen
class MainView(QtGui.QMainWindow):
    def __init__(self, lebenszeichen):
        super(MainView, self).__init__()
        uic.loadUi("/ablage/nfs/pi/MainView.ui", self)
        self.pxTaktOn = QtGui.QPixmap(IMAGE1)
        self.pxTaktOff = QtGui.QPixmap(IMAGE2)

        #Variable initalisiern
        self.bwert = False

        #GPIO Zuweisung: Pin 11 --> GPIO17 / Pin 12 --> GPIO18
        GPIO.setmode(GPIO.BOARD)
        #GPIO.setup(13, GPIO.OUT)

        #Ablauftimer definieren um alle 10 Sekunden Isttemperatur zu prüfen
        self.timer=QtCore.QTimer()
        self.timer.start(10000)

        #Update Isttemperatur
        self.constantUpdate()

        #Thread für Lebenszeichen starten
        self.Blinken = Lebenszeichen()
        self.Blinken.start()

        #Initalisieren Signal / Slots
        self.timer.timeout.connect(self.constantUpdate)
        self.pB_TestEinAus.clicked.connect(self.TestEinAus)
        self.connect(self.Blinken, QtCore.SIGNAL("Zustand"), self.LebenzeichenDone)

        #MainView anzeigen
        self.show()


    #Update Isttemperatur
    def constantUpdate(self):

        # Feld für Temperaturwerte anlgen / Laufvariable anlegen und initalisieren
        fTemperatur = []

        # 1-Wire Slave-Liste lesen
        file = open('/sys/devices/w1_bus_master1/w1_master_slaves')
        w1_slaves = file.readlines()
        file.close()


        # Fuer jeden 1-Wire Slave aktuelle Temperatur ausgeben
        for line in w1_slaves:
            # 1-wire Slave extrahieren
            w1_slave = line.split("\n")[0]
            # 1-wire Slave Datei lesen
            file = open('/sys/bus/w1/devices/' + str(w1_slave) + '/w1_slave')
            filecontent = file.read()
            file.close()

            # Temperaturwerte auslesen, konvertieren und in Array speichern
            stringvalue = filecontent.split("\n")[1].split(" ")[9]
            temperature = float(stringvalue[2:]) / 1000
            fTemperatur.append(temperature)
            # Temperatur ausgeben

        #Rueckgabe Temperaturwete
        self.lb_aktTempWr1Value.setText(str(fTemperatur[0]))
        self.lb_aktTempWr2Value.setText(str(fTemperatur[1]))


    #clicked
    def TestEinAus(self):
        if self.bwert == True:
            self.lb_EinAus.setText("EIN")
            self.bwert = False
            #GPIO.output(13, GPIO.HIGH)
        elif self.bwert ==False:
            self.lb_EinAus.setText("AUS")
            self.bwert = True
            #GPIO.output(13, GPIO.LOW)


    #Lebenszeichen aus Thread empfangen
    def LebenzeichenDone(self, Takt):
        if Takt == True:
            print("true")
            self.px_Lebenszeichen.setPixmap(self.pxTaktOn)
        if Takt == False:
            print("false")
            self.px_Lebenszeichen.setPixmap(self.pxTaktOff)
   


class Lebenszeichen(QThread):
    def __init__(self, parent=None):
        super(Lebenszeichen, self).__init__(parent)

    def run(self):
        while True:
            BlinktaktOn = True
            self.emit(QtCore.SIGNAL("Zustand"), BlinktaktOn)
            time.sleep(1)
            BlinktaktOn = False
            self.emit(QtCore.SIGNAL("Zustand"), BlinktaktOn)
            time.sleep(1)





if __name__ == '__main__':
    app = QtGui.QApplication(sys.argv)
    lebenszeichen = Lebenszeichen()
    window = MainView(lebenszeichen)
    sys.exit(app.exec_())[/Codebox]
BlackJack

@michpro: Damit das mit den Signalen funktioniert muss auch in dem `QThread` eine Qt-Ereignisschleife laufen die dafür sorgt, dass die Ergeignisse auch tatsächlich abgearbeitet werden. In diesem Fall wäre aber ein `QTimer` glaube ich einfacher einen Thread zu verwenden.
michpro
User
Beiträge: 19
Registriert: Samstag 27. Dezember 2014, 21:30

hmmm, also so wie ich es verstehe sieht das doch so aus dass hier der Thread Lebenszeichen jede Sekunde den geänderten Zustand der Variablen BlinktaktOn abfeuert.

Code: Alles auswählen

[Codebox=python file=Unbenannt.py]
#!/usr/bin/python
# -*- coding: utf-8 -*-
class Lebenszeichen(QThread):
    def __init__(self, parent=None):
        super(Lebenszeichen, self).__init__(parent)

    def run(self):
        while True:
            BlinktaktOn = True
            self.emit(QtCore.SIGNAL("Zustand"), BlinktaktOn)
            time.sleep(1)
            BlinktaktOn = False
            self.emit(QtCore.SIGNAL("Zustand"), BlinktaktOn)
            time.sleep(1)
[/Codebox]


in meinem MainView starte ich den Thread und "verknüpfe das Signal mit der Funktion LebenszeichenDon()

Code: Alles auswählen

[Codebox=python file=Unbenannt.py]
	#Thread für Lebenszeichen starten
        self.Blinken = Lebenszeichen()
        self.Blinken.start()

        #Initalisieren Signal / Slots
        self.connect(self.Blinken, QtCore.SIGNAL("Zustand"), self.LebenzeichenDone)
[/Codebox]

in der Funktion LebenszeichenDone() setze ich abhängig von dem Zustand der Variable BlinktaktOn unterschiedliche pxmaps in mein label.

Code: Alles auswählen

[Codebox=python file=Unbenannt.py]
    #Lebenszeichen aus Thread empfangen
    def LebenzeichenDone(self, Takt):
        if Takt == True:
            print("true")
            self.px_Lebenszeichen.setPixmap(self.pxTaktOn)
        if Takt == False:
            print("false")
            self.px_Lebenszeichen.setPixmap(self.pxTaktOff)
[/Codebox]

@BlackJack: Ich mache das mit dem Thread um die Funktionsweise eines hreads mal zu verstehen. Ich fasse das so auf dass dieser asynchon zu meinem Hauptprogramm abläuft.
Umstellung auf QTimer kann ich dann ja immernoch machen... trotzdem Danke für den Tip
BlackJack

@michpro: Ich sehe da jetzt kein Problem mehr. Kannst Du ein Beispiel zeigen mit dem Problem das man auch tatsächlich mal ausprobieren kann?

Anmerkungen: Es werden nicht alle Importe verwendet.

Die Klammern um die beiden Konstanten Werte machen keinen Sinn und die Namen sind zu generisch und nummeriert, statt das die Namen die Bedeutung reflektieren.

`BlinktaktOn` in der `Lebenszeichen.run()`-Methode macht keinen Sinn, da kann man auch einfach die beiden Werte direkt in den Aufruf schreiben. Oder man spart sich die Wiederholung des Aufrufs und des Wartens, und arbeitet dann tatsächlich mit einer Variablen die nicht nur feste Werte bekommt.

`QtCore.SIGNAL()` würde ich nicht mehr benutzen sondern mit `QtCore.pyqtSignal()` ein Klassenattribut erstellen was man dann so benutzen kann wie Du auch schon die anderen Signale verwendest. Das ist kürzer und sicherer.

Das Argument `lebenszeichen` vom Hauptfenster wird nicht verwendet. Dabei fällt mir gerade die Mischung zwischen deutschen und englischen Bezeichnern auf. Und noch schlimmer: Bezeichner wo innerhalb des Bezeichners beide Sprachen verwendet werden.

`bwert` ist ein schlechter Name. Gibt's auch einen `awert` und einen `cwert`? `constantUpdate()` ist ein unpassender Name für etwas was nicht konstant aktualisiert sondern periodisch. Zudem könnte man den Namen spezifischer wählen, so das man keinen Kommentar dafür benötigt was die Methode macht. Und den dann auch noch an zwei Stellen im Quelltext kopieren. `Blinken` ist ein Name für eine Tätigkeit, und dann auch noch so geschrieben das man eine Klasse erwartet und keinen (anderen) Wert. `blinker` wäre vielleicht ein besserer Name dafür. `lb_aktTempWr1Value`? Ernsthaft?

Widgets sollten sich in ihrer `__init__()` nicht selber anzeigen. Das macht keines der vorhandenen Widgets weil es dem Aufrufer diese Entscheidung entreisst.

Das 'f' in `fTemperatur` ist ein schönes Beispiel warum kryptische Kürzel keine gute Idee sind. Steht das für ”float” oder für ”Feld”? Zumal ”Feld” eine Bezeichnung ist die nicht von Python kommt. Sollte es für ”float” stehen, dann sieht man dem Namen nicht an, das es sich nicht um *einen* Wert sondern um eine Sequenz von Werten handelt.

Die Kommentare in der Methode zum auslesen und aktualisieren der Temperaturwerte sind teilweise schlicht falsch.

Die Rückrufmethoden für das Umschalten von `self.bwert` und das Lebenszeichen sind unnötig kompliziert. Man vergleich nicht mit literalen Wahrheitswerten, und man macht keine Vergleiche deren Ergebnis immer das gleiche ist. Wenn ein Flag im ``if`` wahr ist, dann muss man nicht mit ``elif`` testen ob es denn vielleicht unwahr ist, da reicht ein ``else``. Wenn ein Wahrheitswert ”umgedreht” werden soll, braucht man keine literalen Werte sondern verwendet einfach ``not``.

Ungetestet:

Code: Alles auswählen

from __future__ import absolute_import, division, print_function
import os
import sys
import time
from itertools import cycle

from PyQt4 import QtGui, QtCore, uic
from PyQt4.QtCore import QThread
from RPi import GPIO

LED_ON_FILENAME = '/ablage/nfs/pi/LedOn.png'
LED_OFF_FILENAME = '/ablage/nfs/pi/LedOff.png'


class Lebenszeichen(QThread):

    stateChanged = QtCore.pyqtSignal(bool)

    def __init__(self, parent=None):
        super(Lebenszeichen, self).__init__(parent)

    def run(self):
        for state in cycle([True, False]):
            self.stateChanged.emit(state)
            time.sleep(1)


class MainView(QtGui.QMainWindow):

    def __init__(self):
        super(MainView, self).__init__()
        uic.loadUi("/ablage/nfs/pi/MainView.ui", self)
        self.state2led_pixmap = {
            True: QtGui.QPixmap(LED_ON_FILENAME),
            False: QtGui.QPixmap(LED_OFF_FILENAME),
        }
        self.state = False

        GPIO.setmode(GPIO.BOARD)

        self.timer = QtCore.QTimer()
        self.timer.start(10000)

        self.updateTemperature()

        self.blinker = Lebenszeichen()
        self.blinker.start()

        self.timer.timeout.connect(self.updateTemperature)
        self.pB_TestEinAus.clicked.connect(self.toggleState)
        self.blinker.stateChanged.connect(self.setLedState)

    def updateTemperature(self):

        with open('/sys/devices/w1_bus_master1/w1_master_slaves') as lines:
            w1_slave_filenames = [line.rstrip() for line in lines]

        temperatures = list()
        for filename in w1_slave_filenames:
            path = os.path.join('/sys/bus/w1/devices', filename, 'w1_slave')
            with open(path) as slave_file:
                content = slave_file.read()

            string_value = content.split('\n', 1)[1].split(' ')[9]
            temperatures.append(string_value[2:] / 1000)

        self.lb_aktTempWr1Value.setText(str(temperatures[0]))
        self.lb_aktTempWr2Value.setText(str(temperatures[1]))

    def toggleState(self):
        self.lb_EinAus.setText('EIN' if self.state else 'AUS')
        # GPIO.output(13, GPIO.HIGH if self.state else GPIO.LOW)
        self.state = not self.state

    #Lebenszeichen aus Thread empfangen
    def setLedState(self, state):
        print(state)
        self.px_Lebenszeichen.setPixmap(self.state2led_pixmap[state])


def main():
    app = QtGui.QApplication(sys.argv)
    window = MainView()
    window.show()
    sys.exit(app.exec_())


if __name__ == '__main__':
    main()
Antworten