Werte aus ext. Funktion in QT darstellen

Python und das Qt-Toolkit, erstellen von GUIs mittels des Qt-Designers.
Antworten
MirkoB
User
Beiträge: 3
Registriert: Dienstag 1. Oktober 2019, 18:24

Dienstag 1. Oktober 2019, 18:46

Hallo!

Ein kurzes Vorwort: Ich komme eigentlich aus der Hardwarenahen Programmierung und benutze Python als Mittel zum Zweck. Aus dem Grund habe ich meine Probleme mit Objektorientierter Programmierung. Vorkenntnisse in Assembler, C und Labview sind vorhanden... :?
Ich habe mehrere Messgeräte/Aktoren/Sensoren (Prüffeld) welche ich bisher als via Python auf der Konsole ausgelesen und verarbeitet habe. Für mich reicht es...alles super.
Jetzt muss ich es aber bedienbar für andere machen. Also einen "Start" Button und eine Anzeige was passiert. Naja...und vielleicht irgendwann ein bisschen mehr KlickiBunti.

Ich habe mit dem QTDesigner Oberflächen erstellt und die machen, was sie sollen, so lange ich feste Werte vorgebe. Jetzt sind meine ganzen externen Geräte bereits fertig geschrieben und in einzelnen Funktionen gekapselt. Das hängt auch mit teilweise vorhanden State-Machines zusammen.

Mein Problem ist jetzt, wie ich ein altes print("Hello World") in ein Textfeld in PyQt bekomme.

Der Beispielcode sieht so aus:

Code: Alles auswählen

import sys
from PyQt5 import QtWidgets, uic
from PyQt5.QtWidgets import *
from PyQt5.QtCore import QTimer

status=0

def timeslot():
    global status
    status+=1
    print(status)
    # geht nicht: LogWindow.appendPlainText(str(status))
    if (status<10):
        QTimer.singleShot(1000, timeslot)

class Ui(QtWidgets.QMainWindow):

    def __init__(self):
        super(Ui, self).__init__()
        uic.loadUi('test.ui', self)
        self.show()
        self.OK.clicked.connect(self.OK_pressed)

    def OK_pressed(self):
        self.LogWindow.appendPlainText(str(status))
        self.OK.setEnabled(False)
        timeslot()

def main():
    app = QtWidgets.QApplication(sys.argv)
    window = Ui()
    app.exec_()

if __name__ == "__main__":
    main()
Die "test.ui" besteht aus einem Button "OK" und einem mehrzeiligen Textfeld "LogWindow". Über den QTimer wird eine externer "Funktionsblob" aufgerufen, wo Messungen, Einstellungen ect. gemacht werden. Dieser liefert ein Ergebnis auf der Konsole zurück.

Vom Prinzip her will ich den Wert der Variable "status" in das Feld LogWindow schreiben.

Die Variable wird auf der Konsole bis 10 hochgezählt -> Funktioniert also. Auch wird die Variable (einmalig) beim drücken des Buttons in das Textfeld geschrieben. Die in Frage kommende Zeile ist mit # auskommentiert.

Als Fehlermeldung kommt (egal ob es ein Syntaxfehler oder sonstwas in der Zeile ist) ein: Process finished with exit code 1073741845

Das oben gezeigte ist nur das Problem. In der "Timeslot" befindet sich in meinem Fall eine StateMachine, welche einen Teststand steuert und ihre Ausgaben auf der Konsole macht. Das würde ich gerne umbiegen.

Ich hoffe ihr konnt mir helfen...

Mirko
Sirius3
User
Beiträge: 10548
Registriert: Sonntag 21. Oktober 2012, 17:20

Mittwoch 2. Oktober 2019, 07:03

Wie Du schon in OK_pressed geschrieben hast, erreichst Du LogWindow nur über self, also das Objekt das das Fenster repräsentiert.
In der Klasse kannst Du auch den Zustand von status speichern, vergiss gleich wieder, dass es soetwas wie `global` überhaupt gibt.

Code: Alles auswählen

import sys
from PyQt5 import QtWidgets, uic
from PyQt5.QtCore import QTimer

class Ui(QtWidgets.QMainWindow):
    def __init__(self):
        super().__init__()
        self.status = 0
        uic.loadUi('test.ui', self)
        self.OK.clicked.connect(self.OK_pressed)

    def OK_pressed(self):
        self.LogWindow.appendPlainText(str(self.status))
        self.OK.setEnabled(False)
        QTimer.singleShot(1000, self.timeslot)

    def timeslot():
        self.status += 1
        self.LogWindow.appendPlainText(str(self.status))
        if status<10:
            QTimer.singleShot(1000, self.timeslot)


def main():
    app = QtWidgets.QApplication(sys.argv)
    window = Ui()
    window.show()
    app.exec_()

if __name__ == "__main__":
    main()
MirkoB
User
Beiträge: 3
Registriert: Dienstag 1. Oktober 2019, 18:24

Mittwoch 2. Oktober 2019, 11:35

Vielen Dank für die Antwort!
Wie Du schon in OK_pressed geschrieben hast, erreichst Du LogWindow nur über self, also das Objekt das das Fenster repräsentiert.
In der Klasse kannst Du auch den Zustand von status speichern, vergiss gleich wieder, dass es soetwas wie `global` überhaupt gibt.
Ich habe mein Messgerät jetzt ausgelagert und via "import DMM" eingebunden. Meine Informationen lasse ich mir über ein return zurückgeben:

Code: Alles auswählen

self.LogWindow.appendPlainText(str(DMM.readDMM(self.status)))

Code: Alles auswählen

def readDMM(state):
    print (state)
    voltage=5.0
    return voltage
Jetzt lösche ich das oben mal und baue meine Socket Verbindung zu einem Messgerät auf:

Code: Alles auswählen

     global dmm_01

    msg = 'READ?\n'
    msg = bytearray(msg.encode())
    try:
        dmm_01.send(msg)
        print ("Message: ",msg)
    except:
        dmm_01 = socket(AF_INET, SOCK_STREAM)
        dmm_01.connect(('192.168.5.1', 5025))
        print ("Verbindung hergestellt")
        dmm_01.send(msg)
        print ("Message: ", msg)
     #Ab hier abgeschnitten...wird noch ausgelesen usw...
Ohne das "global dmm_01" baut er mir jedes mal die Verbindung neu auf...was etwas ungeschickt ist. Mit Global zu arbeiten ist aber auch nicht viel geschickter. Gibt es was besseres, was ich versuchen kann?

Ich möchte gerne das ganze Messen und Steuern in externen Bibliotheken haben, im Falle das ich was anpassen muss.

Was ich versucht habe ist ist:

Code: Alles auswählen

    def __init__(self):
        super(Ui, self).__init__()
        self.status=0
        self.dmm_01 = socket(AF_INET, SOCK_STREAM)
        self.dmm_01.connect(('192.168.5.1', 5025))


Aufruf über:

Code: Alles auswählen

self.LogWindow.appendPlainText(str(DMM.readDC(self.dmm_01)))
readDMM:

Code: Alles auswählen

def readDMM(connect):
    print ("Bis hier her")
    msg = 'READ?\n'
    msg = bytearray(msg.encode())
    connect.send(msg)
    print ("Message: ",msg)
    raw_data = connect.recv(1024)
    SerStr = raw_data.decode("utf-8")
    result=round((float(SerStr)),8)
    return result
hängt er sich aber ohne Fehlermeldung auf. :?
Also doch Variante 1 mit Global?

Mirko
Sirius3
User
Beiträge: 10548
Registriert: Sonntag 21. Oktober 2012, 17:20

Mittwoch 2. Oktober 2019, 13:26

Aus den Bruchstücken von Code kann ich nicht genau sagen, was Du machst, versucht hast, oder wo jetzt noch ein Problem ist.

Wie 99.9999% aller Quellen im Internet ist Deine Socket-Anbindung kaputt und funktioniert so nicht. SOCK_STREAM sagt schon im Namen, dass es sich um einen Strom handelt, und es keine vorgegebenen Grenzen einer Nachricht gibt, ein einfaches recv kann daher eine beliebige Anzahl Bytes liefern, ob das jetzt eine komplette Zahl ist, oder nur die halbe, ist nicht definiert.
Es sieht so aus, als ob das Messgerät ein Zeilenbasiertes Protokoll benutzt, dann kannst Du mit makefile dir ein Fileobjekt geben lassen, mit dem Du arbeiten kannst.

Code: Alles auswählen

connection = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
connection = connection.makefile('rw', encoding='utf-8')  # TODO: check correct encoding
while True:
    connection.write("READ?\n")
    result = float(connection.readline())
    print(f"{result:.8f}")
Nächster Punkt ist, dass GUI und Messwerte lesen nebenläufig arbeiten müssen, Du mußt Dich also auch noch in QT-Threads einarbeiten und wie die mit dem Hauptprogramm kommunizieren.

Dritter Punkt, man benutzt kein round, sondern rundet bei der Ausgabe, indem man den passenden Format-String benutzt ('.8f').

Was soll das 01 bei dmm_01? `connect` ist eher der Name eine Methode, connection der einer Verbindung. `StrStr` hält sich nicht an die Namenskonvention, dass das ein String ist sollte nicht im Namen vorkommen und `Ser` verstehe ich nicht, was das in diesem Kontext heißen soll.
MirkoB
User
Beiträge: 3
Registriert: Dienstag 1. Oktober 2019, 18:24

Freitag 4. Oktober 2019, 10:38

Vielen Dank erstmal bis hier her!
Ich bin gerade dabei alle alten Module sauber neu zu schreiben. Ein Großteil der Kommunikation ist zwar SCPI basiert, aber da ist nichts spezifiziert. Es kann also alles zwischen einem Nullstring und einem gigantischen Datenblob alles zurückkommen.

Mein dmm_01 ist "DigitalMultiMeter_01". Es gibt auch noch "oszi_xx" und "KSQ_yy". :wink:
Und damit ich nicht durcheinander komme, sind die durchnummeriert. Anschlüsse sind seriell, USB, Ethernet und GBIP. Also ein Sammelsorium von Schnittstellen. Nebenbei existieren noch Sensoren und Aktoren...
Vieles habe ich über Threads gelöst, welche untereinander über globale Variablen kommuniziert haben.
(Wer darf wann messen, Status des Prüflings usw.)
Das klappt ja nun nicht mehr so leicht. :wink:

Ich knie mich dann mal weiter rein und hoffe das Beste! 8)

Mirko
Antworten