Anfänger braucht Hilfe bei Qt4

Python und das Qt-Toolkit, erstellen von GUIs mittels des Qt-Designers.
opccorsa
User
Beiträge: 34
Registriert: Samstag 20. Juli 2013, 07:43

Hallo liebe Profis. Ich schreibe gerade mit Python und QtDesigner eine Steuerung für einen Adapter. Die Kommunikation läuft über RS232. Soweit ja alles OK. Jetzt habe ich das Problem das das Fenster nach dem Aufruf von onPGP nach onWaitAdapternicht aktualisiert. Diese wird erst nach der While-Schleife aktualisiert. Habe schon mit time.sleep, sleep, QTimer versucht. Leider ohne Erfolg :K . Ich muss auch dazu sagen das ich Anfänger bin in Python. Gibt es eine Möglichkeit das GUI komplett zu updaten ? Ich Danke euch erstmal.
Viele Grüße

Code: Alles auswählen

import time
import serial		#serielle Comm
import sys 
import os
from PyQt4 import QtGui, QtCore 
from kt2193_gui import Ui_Hauptdialog as Dlg


class MeinDialog(QtGui.QDialog, Dlg): 
	def __init__(self): 
		QtGui.QDialog.__init__(self) 
		self.setupUi(self)

        # Slots einrichten 
		self.connect(self.buttonOK, QtCore.SIGNAL("clicked()"), self.onOK)
		self.connect(self.buttonWait, QtCore.SIGNAL("clicked()"), self.onWait)
		self.connect(self.buttonInit, QtCore.SIGNAL("clicked()"), self.onInit)
		self.connect(self.buttonQuit, QtCore.SIGNAL("clicked()"), self.onQuit)
		self.connect(self.pgpButton, QtCore.SIGNAL("clicked()"), self.onPGP)
		
		#self.onInit()		#Initialisierung bei Programmstart
		self.serin.setReadOnly(True)		#nur lesen einschalten
		self.serout.setReadOnly(True) 		#nur lesen einschalten
		
#############################################################
	def onPGP(self): 	
		x = self.pgpedit.text()
		self.serin.append(x + " geladen")
		self.serin.append("warte auf Adapter ...")
		print "PGP: " + x
		self.PGPbar.setText(x)
		self.pgpButton.setEnabled(False)
		self.pgpedit.setReadOnly(True)
		
		######hier keine Aktualisierung
		
		self.onWaitAdapter()
		#sleep(2)
		
		return x
		#self.close()			#schliesse Fenster
########################################################

#############################################################
	def onWaitAdapter(self): 	
		ser = serial.Serial('/dev/ttyUSB0', 38400, timeout=1)
		
		str = ""

		while 1:
			ch = ser.read()
			print ch
			time.sleep(0.05)
			if(ch == '\r'):  
				print str
				self.serin.append(str)
				if(str == "Start"):
					print "Start"
					break
					
			str += ch
	
		print str
		ser.close()
		#print ser.isOpen()
		#ser.write("CENT_IBVB\r")      # write Test CENT_IB to Adapter
		return str				#beende Sub
		#self.close()			#schliesse Fenster
########################################################


#############################################################
	def onOK(self): 
        # Daten auslesen 
		d = {} 
		print "Vorname: %s" % self.serout.text()
		fobj = open("test.txt", "r") 	#Test oeffne Datei read
		for line in fobj: 		#For Schleife zum Lineauslesen
			print line 		#print Zeilenweise
		fobj.close()			#schliesse Datei
	
		fobj = open("ausgabe.txt", "w") #oeffne Datei zum schreiben
		for i in range(5): 		#(range(stop) range(start, stop) range(start, stop, step)
			fobj.write("TEst" + "\n")# schreibe Test 
		fobj.close()			#schliesse Datei

		#os.system("ls")			#Befehl ausfuehren
	
		self.close()			#schliesse Sub
########################################################

#############################################################
	def onWait(self): 	
		ser = serial.Serial('/dev/ttyUSB0', 38400, timeout=1)
		
		self.serin.setText("")		#Text loeschen
		self.serout.setText("")		#Text loeschen
		
		ser.write("CENT_IBVB\r")      # write Test CENT_IB to Adapter
		self.serout.setText("Centralis IB_VB Test")
		
		str = ""

		while 1:
			ch = ser.read()
			if(ch == '\r'):  
				print str
				self.serin.setText(str)
				ser.write(str)
				str = ""
			if(ch == "\x40"):
				break

			str += ch
	
		print str
		ser.close()
		#print ser.isOpen()
		return str				#beende Sub
		#self.close()			#schliesse Fenster
########################################################

########################################################
	def onInit(self):
		
		self.serin.setText("")		#Text loeschen
		self.serout.setText("")		#Text loeschen
		self.progressBar.setValue(10)
		RS232 = "/dev/ttyUSB0"
		ser = serial.Serial(RS232, 38400, timeout=1)
		
		self.progressBar.setValue(20)	
		ret = os.access(RS232, os.F_OK);
		if ret == True:
			 self.statRS232.setChecked(True)
		else:
			self.statRS232.setChecked(False)
		self.progressBar.setValue(30)
		
		ret1 = os.access(RS232, os.R_OK);
		ret2 = os.access(RS232, os.W_OK);
		if ret1 == True and ret2 == True:
			self.statrw.setChecked(True)
		else:
			self.statrw.setChecked(False)
		self.progressBar.setValue(40)
			
		ser.write("init\r")      # write the Init to Adapter
		self.serout.setText("Adapter Initialisierung...")
		self.progressBar.setValue(50)
		time.sleep(1)
		self.progressBar.setValue(60)
		
		s = ser.read(10)       # read up to 10 bytes (timeout)
		if s == "OK\r\n":
			self.statadapter.setChecked(True)
			self.serin.append("OK")
			
			self.progressBar.setValue(70)
			
			ser.write("status\r")      # write the Init to Adapter
			time.sleep(0.05)
			s = ser.read(10)       # read up to 10 bytes (timeout)
			if s == "fail\r\n":
				self.serin.append("Status Adapter PASS")
			else:
				self.serin.append("Status Adapter FAIL")
				#	self.serout.clear()
		else:
			self.statadapter.setChecked(False)
			self.serin.setPlainText("Timeout: " + s)
		
		self.progressBar.setValue(80)
		ser.write("ver\r")      # write the Init to Adapter
		ant_ver = ser.read(50)       # read up to 50 bytes (timeout)
		self.serin.append(ant_ver)
		time.sleep(0.5)
		
		self.progressBar.setValue(99)
		
		time.sleep(2)
		self.progressBar.setValue(0)
		self.serout.clear()
		self.serout.setText("Adapter Initialisierung fertig")
		self.statusbar.setText(ant_ver + " Online")
					
		#print s
		#print "F_OK - return value %s"% ret
		
		ser.close()             # close port
		time.sleep(2)
		self.progressBar.setValue(0)
		return
########################################################

#########################################################
	def onQuit(self):
		print "Quit"
		self.close()
#########################################################


app = QtGui.QApplication(sys.argv) 
dialog = MeinDialog() 
dialog.setFixedSize(720, 540)
dialog.show()
sys.exit(app.exec_())
Sirius3
User
Beiträge: 17750
Registriert: Sonntag 21. Oktober 2012, 17:20

@opccorsa: die Antwort ist Thread-Programmierung. Bei GUI-Programmen dürfen keine lang laufenden Methoden aufgerufen werden, da Updates auch durch den Eventloop abgearbeitet werden. Du mußt also das Lesen der Schnittstelle in einen eigenen Thread auslagern. Eine Suche hier im Forum wird Dir dutzende ähnliche Fragen und Antworten liefern.
BlackJack

@opccorsa: Noch ein paar Anmerkungen zum Quelltext:

Einrücktiefe ist per Konvention vier Leerzeichen pro Ebene.

`QtCore` scheint nirgends verwendet zu werden.

Abkürzungen bei Namen sollte man vermeiden solange sie nicht allgemein bekannt sind oder Konventionen entsprechen. `Ui_Hauptdialog` zum Beispiel mit `Dlg` abzukürzen obwohl das nur einmal benutzt wird macht wenig Sinn. Einbuchstabige Namen wie `x` für einen Wert aus einem Widget der keine Zahl ist, sind nichtssagend. Auch `ch` ist noch zu kurz um aussagekräftig zu sein. Genau wie `d`, was in `onOK()` noch nicht einmal benutzt wird.

`MeinDialog` ist ein sinnfreier Name. Namen sollten besser beschreiben was der Wert dahinter bedeutet. Deutsch und Englisch bei den Bezeichnern zu mischen wird auf dauer auch verwirrend.

Namen von eingebauten Funktionen oder Datentypen wie `str` sollte man nicht an andere Werte binden.

Kommentare sollten dem Leser einen Mehrwert über den Quelltext liefern. Ein Kommentar ``#nur lesen einschalten`` bei ``self.serin.setReadOnly(True)`` ist beispielsweise sinnfrei. Wert das nicht am Methodenaufruf ablesen kann, dem wird auch der Kommentar nicht weiter helfen. Schlimmer als sinnlose Kommentare sind solche die offensichtlich dem Quelltext widersprechen, zum Beispiel wenn man ``write('ver\r\n')`` schreibt, aber etwas wie ``# write init`` kommentiert.

Die Kommentar”linien” die Methdendefinitionen einschliessen sind ungewöhnlich.

Statt Quelltext für die zusammengeklickte GUI zu generieren kann man die *.ui-Datei auch mit dem `PyQt4.uic`-Modul zur Laufzeit laden und spart sich somit einen manuellen Zwischenschritt.

Es gibt eine kürzere und etwas sicherere Methode um Signale und Slots zu verbinden, in dem man die `connect()`-Methode direkt auf den Signalobjekten aufruft. Also zum Beispiel:

Code: Alles auswählen

        # Alt:
        self.connect(self.buttonOK, QtCore.SIGNAL("clicked()"), self.onOK)
        # Neu:
        self.buttonOK.clicked.connect(self.onOK)
Die Rückgabe von Werten bei Methoden die durch den Benutzer aufgerufen werden in dem er eine Schaltfläche drückt, macht keinen Sinn. Der Aufrufer ist die GUI-Hauptschleife und die kann mit so etwas nichts anfangen.

Wenn man Wahrheitswerte meint, sollte man `True` und `False` schreiben statt 1 und 0.

``if`` ist keine Funktion und Klammern um den gesamten Ausdruck sind überflüssig.

Das lesen aus der seriellen Verbindung gehört eigentlich nicht in die GUI. Das kann man in eine eigene Funktion auslagern. Insbesondere weil der Code zweimal sehr ähnlich im Quelltext steht. Ausserdem sollte zeilenweise Lesen einfacher/effizienter möglich sein als das so selber zu schreiben.

Vielleicht würde es auch Sinn machen das externe Gerät beziehungsweise die Kommunikation damit in einer eigenen Klasse zu kapseln. Dann muss man die Verbindungsparameter auch nicht mehrfach literal in den Quelltext schreiben. Ich sehe ausserdem ein Problem das zwei verschiedene Rückruffunktionen eine serielle Verbindung auf dem selben Port aufbauen. Wenn das asynchron umgeschrieben wird, wird das ein Problem. Ausserdem könnte man die Verbindungsklasse einfacher und losgelöst von der GUI testen.

Dateien öffnet man am besten zusammen mit der ``with``-Anweisung um das Schliessen in jedem Fall sicher zu stellen.

In Python werden Anweisungen nicht mit einem Semikolon abgeschlossen.

Vergleiche von Wahrheitswerten mit literalen Wahrheitswerten machen keinen Sinn, denn da kommt nur wieder ein Wahrheitswerte bei heraus. Entweder der den man schon mal hatte — dann kann man den auch direkt verwenden, oder das Gegenteil — dann kann man den ursprünglichen mit ``not`` negieren. Und wenn man dann ein ``if``/``else`` verwendet bei dem sich die Zweige nur dadurch unterscheiden, dass ein `True` oder `False` verwendet wird, dann kann man sich das ``if``/``else`` komplett sparen und das Ergebnis der Bedingung direkt verwenden. Also:

Code: Alles auswählen

            # Anstelle von:
            # 
            ret = os.access(port, os.F_OK)
            if ret == True:
                self.statRS232.setChecked(True)
            else:
                self.statRS232.setChecked(False)
            # 
            # Kürzer und direkter nur eine Zeile:
            # 
            self.statRS232.setChecked(os.access(port, os.F_OK))
In diesem speziellen Fall wäre `os.path.exists()` wohl verständlicher.

Einiges davon umgesetzt komme ich zu diesem Zwischenergebnis (ungetestet):

Code: Alles auswählen

import sys
import os
import time
import serial
from contextlib import closing
from PyQt4 import QtGui
from kt2193_gui import Ui_Hauptdialog


class MainDialog(QtGui.QDialog, Ui_Hauptdialog):
    def __init__(self):
        QtGui.QDialog.__init__(self)
        self.setupUi(self)

        self.buttonOK.clicked.connect(self.onOK)
        self.buttonWait.clicked.connect(self.onWait)
        self.buttonInit.clicked.connect(self.onInit)
        self.buttonQuit.clicked.connect(self.onQuit)
        self.pgpButton.clicked.connect(self.onPGP)

        self.serin.setReadOnly(True)
        self.serout.setReadOnly(True)

    def onPGP(self):
        content = self.pgpedit.text()
        self.serin.append(content + ' geladen')
        self.serin.append('warte auf Adapter ...')
        print 'PGP:', content
        self.PGPbar.setText(content)
        self.pgpButton.setEnabled(False)
        self.pgpedit.setReadOnly(True)
        line = self.onWaitAdapter()  # FIXME Async.
        print line

    def onWaitAdapter(self):
        with closing(serial.Serial('/dev/ttyUSB0', 38400, timeout=1)) as ser:
            line = ''
            while True:
                character = ser.read()
                print character
                if character == '\r':
                    print line
                    self.serin.append(line)
                    if line == 'Start':
                        print 'Start'
                        break
                time.sleep(0.05)
                line += character
            return line

    def onOK(self):
        print 'Vorname:', self.serout.text()

        with open('test.txt', 'r') as lines:
            for line in lines:
                print line

        with open('ausgabe.txt', 'w') as out_file:
            out_file.writelines('TEst\n' for _ in xrange(5))

        self.close()

    def onWait(self):
        with closing(serial.Serial('/dev/ttyUSB0', 38400, timeout=1)) as ser:

            self.serin.setText('')
            self.serout.setText('Centralis IB_VB Test')

            ser.write('CENT_IBVB\r')

            line = ''
            while True:
                character = ser.read()
                if character == '\r':
                    print line
                    self.serin.setText(line)
                    ser.write(line)
                    line = ''
                if character == '\x40':
                    break

                line += character

            print line

    def onInit(self):
        self.serin.setText('')
        self.serout.setText('')
        self.progressBar.setValue(10)
        port = '/dev/ttyUSB0'
        with closing(serial.Serial(port, 38400, timeout=1)) as ser:
            self.progressBar.setValue(20)
            self.statRS232.setChecked(os.path.exists(port))
            self.progressBar.setValue(30)
            self.statrw.setChecked(port, os.R_OK | os.W_OK)
            self.progressBar.setValue(40)

            ser.write('init\r')
            self.serout.setText('Adapter Initialisierung...')
            self.progressBar.setValue(50)
            time.sleep(1)
            self.progressBar.setValue(60)

            response = ser.read(10)
            if response == 'OK\r\n':
                self.statadapter.setChecked(True)
                self.serin.append('OK')
                self.progressBar.setValue(70)
                ser.write('status\r')
                time.sleep(0.05)
                response = ser.read(10)
                self.serin.append(
                    'Status Adapter ' + (
                        'FAIL' if response == 'fail\r\n' else 'PASS'
                    )
                )
            else:
                self.statadapter.setChecked(False)
                self.serin.setPlainText('Timeout: ' + response)
            self.progressBar.setValue(80)
            ser.write('ver\r')
            response = ser.read(50)
            self.serin.append(response)
            time.sleep(0.5)

            self.progressBar.setValue(99)

            time.sleep(2)
            self.progressBar.setValue(0)
            self.serout.clear()
            self.serout.setText('Adapter Initialisierung fertig')
            self.statusbar.setText(response + ' Online')

        time.sleep(2)
        self.progressBar.setValue(0)

    def onQuit(self):
        print 'Quit'
        self.close()


def main():
    app = QtGui.QApplication(sys.argv)
    dialog = MainDialog()
    dialog.setFixedSize(720, 540)
    dialog.show()
    sys.exit(app.exec_())


if __name__ == '__main__':
    main()
opccorsa
User
Beiträge: 34
Registriert: Samstag 20. Juli 2013, 07:43

DANKE euch beiden. Bin daran den Code zu überarbeiten. Staune immer was da noch so falsch ist. Würde mich nochmal melden wenn ich Ergebnisse oder Fragen habe. Danke :)
opccorsa
User
Beiträge: 34
Registriert: Samstag 20. Juli 2013, 07:43

Hallo

So da bin ich wieder. Ich habe mich jetzt in die Thread Geschichte ein wenig eingearbeitet. Mit Erfolg. Nun habe ich ein anderes Problem. Nach dem starten des Thread "empfang" fragt er schön den Port ab. Jetzt bekomme ich vom µC ein "Start". Dieses sehe ich mit dem Print Befehl auch. Nun möchte ich das alles was anschließend kommt in meine Textbox übernehmen "self.serin.append(werte)". Da bekomme ich die Fehlermeldung wegen dem "self". Kann ich die Variable "werte" auch parallel außerhalb erhalten ?

Danke.

Code: Alles auswählen

import time, thread
import serial
import sys 
import os
from PyQt4 import QtGui, QtCore 
from kt2193_gui import Ui_Hauptdialog


class MeinDialog(QtGui.QDialog, Ui_Hauptdialog): 
    def __init__(self): 
        QtGui.QDialog.__init__(self) 
        self.setupUi(self)

        # Slots einrichten 
        self.connect(self.buttonInit, QtCore.SIGNAL("clicked()"), self.onInit)
        self.connect(self.buttonQuit, QtCore.SIGNAL("clicked()"), self.onQuit)
        self.connect(self.pgpButton, QtCore.SIGNAL("clicked()"), self.onPGP)
        
        self.serin.setReadOnly(True)
        self.serout.setReadOnly(True)
        
#############################################################
    def onPGP(self):    
        
        x = self.pgpedit.text()
        self.serin.append(x + " geladen")
        self.serin.append("warte auf Adapter ...")
        print "PGP: " + x
        self.PGPbar.setText(x)
        self.pgpButton.setEnabled(False)
        self.pgpedit.setReadOnly(True)
        thread.start_new_thread(empfang,())
        return x
########################################################
    def send_set_commands(serial, interval):
        while True:
            ser.write('set\x0d')
            ser.flush()
            time.sleep(interval)            
 ##########################################################

########################################################
    def onInit(self):
        
        self.serin.setText("")
        self.serout.setText("")
        self.progressBar.setValue(10)
        
        self.progressBar.setValue(20)   
        ret = os.access(ser.port, os.F_OK);
        
        if ret == True:
             self.statRS232.setChecked(True)
        else:
            self.statRS232.setChecked(False)
        self.progressBar.setValue(30)
        
        ret1 = os.access(ser.port, os.R_OK);
        ret2 = os.access(ser.port, os.W_OK);
        if ret1 == True and ret2 == True:
            self.statrw.setChecked(True)
        else:
            self.statrw.setChecked(False)
        self.progressBar.setValue(40)
            
        ser.write("init\r")      # write the Init to Adapter
        self.serout.setText("Adapter Initialisierung...")
        self.progressBar.setValue(50)
        time.sleep(1)
        self.progressBar.setValue(60)
        
        s = ser.read(10)       # read up to 10 bytes (timeout)
        if s == "OK\r\n":
            self.statadapter.setChecked(True)
            self.serin.append("OK")
            
            self.progressBar.setValue(70)
            
            ser.write("status\r")      # write the Init to Adapter
            time.sleep(0.05)
            s = ser.read(10)            # read up to 10 bytes (timeout)
            if s == "fail\r\n":
                self.serin.append("Status Adapter PASS")
            else:
                self.serin.append("Status Adapter FAIL")
        else:
            self.statadapter.setChecked(False)
            self.serin.setPlainText("Timeout: " + s)
        
        self.progressBar.setValue(80)
        ser.write("ver\r")              # write the Init to Adapter
        ant_ver = ser.read(50)          # read up to 50 bytes (timeout)
        self.serin.append(ant_ver)
        time.sleep(0.5)
        
        self.progressBar.setValue(99)
        
        time.sleep(2)
        self.progressBar.setValue(0)
        self.serout.clear()
        self.serout.setText("Adapter Initialisierung fertig")
        self.statusbar.setText(ant_ver + " Online")
        
        time.sleep(2)
        self.progressBar.setValue(0)
        return
########################################################

#########################################################
    def onQuit(self):
        print ser.isOpen()
        ser.close()
        print ser.isOpen()
        print "Quit"
        self.close()
#########################################################

#############################################################
def empfang():
    # Werte Variable durch global bekanntmachen
    global werte
    id = thread.get_ident()
    print("Empfangs-thread lauuft, id={0}.".format(id))
    while 1:
        try:
            werte = ser.readline()
            print werte
            self.serin.append(werte)    #funktioniert nicht
            if(werte == "Start\r\n"):
                ser.write("CENT_IBVB\r")      # write Test CENT_IB to Adapter
        except Exception, e:
            print("Lesefehler in Thread", id,"  ", e)
#############################################################

# Hauptprogramm
werte = ""
# Seriellen Port Konfigurieren:
ser = serial.Serial()
ser.port = '/dev/ttyUSB0'
ser.baudrate = 38400
ser.timeout = 1
 
# Port oeffnen
try:
    ser.open()
    print("Port open")
except:
    print("Unable to open port")
    exit(0)

app = QtGui.QApplication(sys.argv) 
dialog = MeinDialog() 
dialog.setFixedSize(720, 540)
dialog.show()
sys.exit(app.exec_())
opccorsa
User
Beiträge: 34
Registriert: Samstag 20. Juli 2013, 07:43

Habe nochmal weiter eingekürzt

Code: Alles auswählen

import time, thread
import serial
import sys 
import os
from PyQt4 import QtGui, QtCore 
from kt2193_gui import Ui_Hauptdialog


class MeinDialog(QtGui.QDialog, Ui_Hauptdialog): 
    def __init__(self): 
        QtGui.QDialog.__init__(self) 
        self.setupUi(self)

        # Slots einrichten 
        self.connect(self.buttonInit, QtCore.SIGNAL("clicked()"), self.onInit)
        self.connect(self.buttonQuit, QtCore.SIGNAL("clicked()"), self.onQuit)
        self.connect(self.pgpButton, QtCore.SIGNAL("clicked()"), self.onPGP)
        
        self.serin.setReadOnly(True)
        self.serout.setReadOnly(True)


    def onPGP(self):    
        
        x = self.pgpedit.text()
        self.serin.append(x + " geladen")
        self.serin.append("warte auf Adapter ...")
        print "PGP: " + x
        self.PGPbar.setText(x)
        self.pgpButton.setEnabled(False)
        self.pgpedit.setReadOnly(True)
        thread.start_new_thread(empfang,())
        return x

    def onInit(self):
        
        self.serin.setText("")
        self.serout.setText("")
        self.progressBar.setValue(10)
        
        self.progressBar.setValue(20)   
        ret = os.access(ser.port, os.F_OK);
        
        if ret == True:
             self.statRS232.setChecked(True)
        else:
            self.statRS232.setChecked(False)
        self.progressBar.setValue(30)
        
        ret1 = os.access(ser.port, os.R_OK);
        ret2 = os.access(ser.port, os.W_OK);
        if ret1 == True and ret2 == True:
            self.statrw.setChecked(True)
        else:
            self.statrw.setChecked(False)
        self.progressBar.setValue(40)
            
        ser.write("init\r")      # write the Init to Adapter
        self.serout.setText("Adapter Initialisierung...")
        self.progressBar.setValue(50)
        time.sleep(1)
        self.progressBar.setValue(60)
        
        s = ser.read(10)       # read up to 10 bytes (timeout)
        if s == "OK\r\n":
            self.statadapter.setChecked(True)
            self.serin.append("OK")
            
            self.progressBar.setValue(70)
            
            ser.write("status\r")      # write the Init to Adapter
            time.sleep(0.05)
            s = ser.read(10)            # read up to 10 bytes (timeout)
            if s == "fail\r\n":
                self.serin.append("Status Adapter PASS")
            else:
                self.serin.append("Status Adapter FAIL")
        else:
            self.statadapter.setChecked(False)
            self.serin.setPlainText("Timeout: " + s)
        
        self.progressBar.setValue(80)
        ser.write("ver\r")              # write the Init to Adapter
        ant_ver = ser.read(50)          # read up to 50 bytes (timeout)
        self.serin.append(ant_ver)
        time.sleep(0.5)
        
        self.progressBar.setValue(99)
        
        time.sleep(2)
        self.progressBar.setValue(0)
        self.serout.clear()
        self.serout.setText("Adapter Initialisierung fertig")
        self.statusbar.setText(ant_ver + " Online")
        
        time.sleep(2)
        self.progressBar.setValue(0)
        return

    def onQuit(self):
        print ser.isOpen()
        ser.close()
        print ser.isOpen()
        print "Quit"
        self.close()

def empfang():
    
    global werte
    id = thread.get_ident()
    print("Empfangs-thread lauuft, id={0}.".format(id))
    while 1:
        try:
            werte = ser.readline()
            print werte
            self.serin.append(werte)    #funktioniert nicht
            if(werte == "Start\r\n"):
                ser.write("CENT_IBVB\r")
        except Exception, e:
            print("Lesefehler in Thread", id,"  ", e)
            

# Hauptprogramm

# Seriellen Port Konfigurieren:
ser = serial.Serial()
ser.port = '/dev/ttyUSB0'
ser.baudrate = 38400
ser.timeout = 1
# Port oeffnen
try:
    ser.open()
    print("Port open")
except:
    print("Unable to open port")
    exit(0)

app = QtGui.QApplication(sys.argv) 
dialog = MeinDialog() 
dialog.setFixedSize(720, 540)
dialog.show()
sys.exit(app.exec_())
Benutzeravatar
Madmartigan
User
Beiträge: 200
Registriert: Donnerstag 18. Juli 2013, 07:59
Wohnort: Berlin

Code: Alles auswählen

def empfang():
Das ist fehlerhaft. Laut Einrückung gehört die Methode nicht zur Klasse, kann daher auch keine Instanz self kennen!
Also Einrückung ändern und die Methode entsprechend korrigieren:

Code: Alles auswählen

def empfang(self):
Demensprechend den Thread Aufruf auch ändern:

Code: Alles auswählen

thread.start_new_thread(self.empfang,())
opccorsa
User
Beiträge: 34
Registriert: Samstag 20. Juli 2013, 07:43

SUPER !! :D Hat soweit geklappt. bekomme nur jetzt noch eine Fehlermeldung mittendrin

QObject::connect: Cannot queue arguments of type 'QTextCursor'
(Make sure 'QTextCursor' is registered using qRegisterMetaType().)
Benutzeravatar
pillmuncher
User
Beiträge: 1484
Registriert: Samstag 21. März 2009, 22:59
Wohnort: Pfaffenwinkel

@opccorsa": Ich verstehe es wirklich nicht. Wenn man in der Doku das Verzeichnis der Standardmodule durchgeht, dann findet man das hier:

Code: Alles auswählen

16.2. threading — Higher-level threading interface
16.3. thread — Multiple threads of control
Warum verwendet jemand die unbequemere und schwierigere Lösung, statt der bequemeren und einfacheren, die man zudem, wenn man ganz normal von oben nach unten liest, sogar zuerst findet?

Wenn man dem zweiten Link folgt, dann steht übrigens als erstes, ganz oben, das hier:

Code: Alles auswählen

16.3. thread — Multiple threads of control

Note

The thread module has been renamed to _thread in Python 3. The 2to3 tool will automatically adapt imports when converting your sources to Python 3; however, you should consider using the high-level threading module instead.
Warum liest jemand nicht nicht die ersten paar Zeilen in der Dokumentation?

Anfänger scheinen magisch von der jeweils schwierigeren und unbequemeren Möglichkeit angezogen zu werden. Warum? ich verstehe es wirklich nicht.

Aber langsam beginne ich zu verstehen. Wenn ich nach thread python google, dann ist der erste Treffer ein Link auf die offizielle Doku des threading Moduls, aber der zweite ist gleich einer auf http://www.python-kurs.eu/threads.php. Dort wird zuerst die Verwendung des thread Moduls erklärt, und erst anschließend und viel kürzer threading. Bah!
In specifications, Murphy's Law supersedes Ohm's.
Benutzeravatar
pillmuncher
User
Beiträge: 1484
Registriert: Samstag 21. März 2009, 22:59
Wohnort: Pfaffenwinkel

Noch ein paar Anmerkungen.

In Python 2.x ist print keine Funktion. Deswegen hat es keinen Sinn, print mit Klammern zu verwenden:

Code: Alles auswählen

            print("Lesefehler in Thread", id,"  ", e)
Das gibt ein Tupel aus. Ist es wirklich das, was du möchtest? Oder eher das hier:

Code: Alles auswählen

            print "Lesefehler in Thread", id,"  ", e
Auch if ist keine Funktion. Also statt

Code: Alles auswählen

            if(werte == "Start\r\n"):
lieber

Code: Alles auswählen

            if werte == "Start\r\n":
Wenn du auf Ausnahmen testest, solltest du genau hinschreiben, auf welche. Also nicht

Code: Alles auswählen

        except Exception, e:
sondern zB.

Code: Alles auswählen

        except MyException, e:
            ...
        except YourException, e:
            ...
id ist der name einer eingebauten Funktion. Wenn du diesen Namen für etwas anderes verwendest, überdeckst du damit diese Funktion und kannst sie ggf. nicht mehr verwenden.

Ein return am Ende einer Funktion ist sinnlos, weil es nichts anderes tut, als None zurückzugeben, und eine Funktion ohne ein solches return gibt ebenfalls ein None zurück. Du sagst mit dem return also: Liebes Python, darf ich dich an dieser Stelle bitten, genau das zu machen, was du tun würdest, wenn ich dich an dieser Stelle um nichts bitten würde.

ret == True ist dasselbe wie ret, jedenfalls wenn man weiß, dass ret entweder True oder False ist. Deswegen sollte nicht man schreiben:

Code: Alles auswählen

        if ret == True:
                self.statRS232.setChecked(True)
        else:
            self.statRS232.setChecked(False)
sondern

Code: Alles auswählen

        if ret:
                self.statRS232.setChecked(True)
        else:
            self.statRS232.setChecked(False)
oder noch besser einfach

Code: Alles auswählen

        self.statRS232.setChecked(ret)
Dann hat man auch weniger Probleme mit schlampiger Einrückung. Ein paar Zeilen weiter unten kann man das so schreiben:

Code: Alles auswählen

        self.statrw.setChecked(ret1 and ret2)
Und das hier:

Code: Alles auswählen

    # Werte Variable durch global bekanntmachen
    global werte
Abgesehen davon, dass der Kommentar sinnlos ist, weil er nur komplizierter beschreibt, was eine Zeile darunter einfacher schon dasteht, ist Kommunikation über globale Variablen idR. ein Designfehler, und als Mechanismus zur Kommunikation zwischen Threads ein probates Mittel, um Desaster herbeizuführen. Lass das bleiben, vergiss dass es global gibt, und überleg dir ein vernünftiges Design. Übrigens wird werte nirgends sonst in deinem Programm verwendet. Du könntest werte also auch am Anfang von empfang() initialisieren und dir das gobal sparen.

Ein Problem ist auch, dass du keinerlei Anstalten triffst, zu verhindern, dass gleichzeitig an die serielle Schnittstelle gesendet und von ihr empfangen wird. Je nach angeschlossenem Gerät kann das funktionieren oder auch nicht. Öfters nicht als schon. Nebenläufige Programme brauchen idR. auch einen Ausschluss-Mechanismus für gemeinsam genutzte Ressourcen, wie zB. gemeinsam genutzte Speicherbereiche oder Schnittstellen. In Python gibt es dafür Locks (threading.Lock bzw. threading.RLock). Damit umzugehen ist aber nicht trivial und selbst erfahrene Programmierer bauen da oft Mist, und das nicht etwa, weil sie schlampig wären, oder nichts können, sondern weil Nebenläufigkeit mit Locks sehr schwierig ist. Deswegen nehmen erfahrene Pythonistas da gerne queue.Queue aus der Standard-Bibliothek her. Das ist eine asynchrone Queue, die selbst schon für den benötigten Zugriffs-Ausschluss sorgt. Damit wäre ein vernünftiges Design schon wesentlich einfacher zu erreichen.

Ein vernünftiges Design bestünde auch darin, zuerst einmal die Anzeige-Logik von der Verarbeitungs-Logik zu trennen. So wie es jetzt ist, also aus der GUI heraus mit der seriellen Schnittsteelle zu kommunizieren, und ein paar dazwischengesprenkelte time.sleep(...), ist es nicht nur unübersichtlich zu lesen, sondern auch zu debuggen. Wenn die Verarbeitungs-Logik statt dessen unabhängig von der GUI funktioniert, muss man sich beim Debuggen um weniger bewegliche Teile kümmern. Wenn man es dann soweit hat, dass es ordentlich funktioniert, kann man auch leichter eine GUI-Anzeige drüber legen.
In specifications, Murphy's Law supersedes Ohm's.
opccorsa
User
Beiträge: 34
Registriert: Samstag 20. Juli 2013, 07:43

Hallo Leute

Danke für die Antworten, die ich mir auch zu Herzen genommen habe und es sind fast alle umgesetzt. Jetzt habe ich aktuell noch ein Problem. Ich möchte von einem extra Thread auf ein QPlainTextEdit zugreifen bzw. Text schreiben. Hintergrund ist die Funktion den Scrollbar zu steuern und die Ansicht auf der letzten Zeile zu lassen. Ich weis das man außer vom Haupthread nicht auf QWidgets zugreifen kann. Die Lösung sollten wohl Signals sein. :K Leider weis ich nicht wie das gehen soll und wie ich damit den Cursor gesteuert bekomme. :shock:

Danke.
Benutzeravatar
Madmartigan
User
Beiträge: 200
Registriert: Donnerstag 18. Juli 2013, 07:59
Wohnort: Berlin

QPlainTextEdit hat eine nützliche Methode namens ensureCursorVisible(). Eventuell ist das bereits das, was du möchtest. ;-)
http://qt-project.org/doc/qt-4.8/qplain ... sorVisible

Ansonsten ist die Verwendung von Signalen nicht weiter schwer.

Zuerst importierst du pyqtSignal (ich nutze immer noch PyQt4, ggf. musst du das für dich anpassen):

Code: Alles auswählen

from PyQt4.QtCore import pyqtSignal
Als nächstes deklarierst du das Signal. Wichtig! Nicht im Konstruktor respektive für self deklarieren. (Siehe Doku für weitere Infos)

Code: Alles auswählen

my_signal = pyqtSignal(optionaler Parameter)
Dann sagst du (meist im Konstruktor) nur noch was beim Auslösen des Signals passieren soll:

Code: Alles auswählen

self.my_signal.connect(self.my_function)
Als letztes ermöglichst du dem Thread, das Signal zu emittieren:

Code: Alles auswählen

self.my_signal.emit(optionaler Parameter)
Mit optionaler Parameter ist gemeint, du kannst da übergeben, was du möchtest/brauchst. Für eine Fortschrittsanzeige zum Beispiel übergebe ich oft einfach einen Float.
Die Funktion my_function übernimmt dann die Verarbeitung es optionalen Parameters. Von dort aus kannst du wieder auf die GUI zugreifen.
Wenn du dem Signal einen Parameter spendierst, muss die Funktion my_function natürlich äquivalent deklariert werden!
opccorsa
User
Beiträge: 34
Registriert: Samstag 20. Juli 2013, 07:43

Hallo Madmartigan

Bin irgendwie zu dumm zu. Habe es so wie du es geschrieben hast versucht umzusetzen. Leider Ohne Erfolg. Ich denke mal ich habe die "Wege" der Programmabarbeitung noch nicht verstanden. Kannst du mir da nochmal helfen. Anbei habe ich dir den kompletten Code geschickt mit der Kommentierung "Versuch"

DANKE :wink:

Code: Alles auswählen

import time, thread
import serial
import sys 
import os
from PyQt4 import QtGui, QtCore 
from kt2193_gui import Ui_Hauptdialog
from PyQt4.QtCore import pyqtSignal


class MeinDialog(QtGui.QDialog, Ui_Hauptdialog): 
    werte = ""
    
    my_signal = pyqtSignal(Ui_Hauptdialog.serin.insertPlainText)   ### Versuch 
    
    def __init__(self): 
        QtGui.QDialog.__init__(self) 
        self.setupUi(self)
        
        self.my_signal.connect(self.asdf)   ### Versuch 

        # Slots einrichten 
        self.connect(self.buttonInit, QtCore.SIGNAL("clicked()"), self.onInit)
        self.connect(self.buttonQuit, QtCore.SIGNAL("clicked()"), self.onQuit)
        self.connect(self.pgpButton, QtCore.SIGNAL("clicked()"), self.onPGP)
        
        self.serin.setReadOnly(True)
        self.serout.setReadOnly(True)
        
    def asdf(werte):   ### Versuch 
        self.serin.insertPlainText(werte + "\r\n");  ### Versuch 
        


    def onPGP(self):    
        
        x = self.pgpedit.text()
        self.serin.insertPlainText(x + " geladen\r\n");
        self.serin.insertPlainText("warte auf Adapter ...");
        print "PGP: " + x
        self.PGPbar.setText(x)
        self.pgpButton.setEnabled(False)
        self.pgpedit.setReadOnly(True)
        thread.start_new_thread(self.empfang,())

    def onInit(self):
        
        self.serin.insertPlainText("")
        self.serout.setText("")
        self.progressBar.setValue(10)
        
        self.progressBar.setValue(20)   
        
        ret = os.access(ser.port, os.F_OK);
        self.statRS232.setChecked(ret)
        
        self.progressBar.setValue(30)
        
        ret1 = os.access(ser.port, os.R_OK);
        ret2 = os.access(ser.port, os.W_OK);
        self.statrw.setChecked(ret1 and ret2)
        
        self.progressBar.setValue(40)
            
        ser.write("init\r")      # write the Init to Adapter
        self.serout.setText("Adapter Initialisierung...")
        self.progressBar.setValue(50)
        time.sleep(1)
        self.progressBar.setValue(60)
        
        s = ser.read(10)       # read up to 10 bytes (timeout)
        if s == "OK\r\n":
            self.statadapter.setChecked(True)
            self.serin.insertPlainText("OK\r\n")
            
            self.progressBar.setValue(70)
            
            ser.write("status\r")      # write the Init to Adapter
            time.sleep(0.05)
            s = ser.read(10)            # read up to 10 bytes (timeout)
            if s == "fail\r\n":
                self.serin.insertPlainText("Status Adapter PASS\r\n")
            else:
                self.serin.insertPlainText("Status Adapter FAIL\r\n")
        else:
            self.statadapter.setChecked(False)
            self.serin.setPlainText("Timeout: " + s)
        
        self.progressBar.setValue(80)
        ser.write("ver\r")              # write the Init to Adapter
        ant_ver = ser.read(50)          # read up to 50 bytes (timeout)
        self.serin.insertPlainText(ant_ver + "\r\n")
        time.sleep(0.5)
        
        self.progressBar.setValue(99)
        
        time.sleep(2)
        self.progressBar.setValue(0)
        self.serout.clear()
        self.serout.setText("Adapter Initialisierung fertig")
        self.statusbar.setText(ant_ver + " Online")
        
        time.sleep(2)
        self.progressBar.setValue(0)

    def onQuit(self):
        print ser.isOpen()
        ser.close()
        print ser.isOpen()
        try:
            sys.stderr.close()
        except:
            pass
        print "Quit"
        self.close()

    def empfang(self):
        th1_id = thread.get_ident()
        print("Empfangs-thread running, th1_id={0}.".format(th1_id))
        while 1:
            try:
                werte = ser.readline()
                if werte > "":              
                    print werte #Debug
                    self.my_signal.emit(werte)   ### Versuch 
                    #self.serin.insertPlainText(werte + "\r\n"); #### Fehlermeldung Cursor
                    if werte == "Start\r\n":
                        ser.write("CENT_IBVB\r")
                    if werte == "PASS\r\n":
                        ser.write("open\r")              # open Adapter
                        
            except Exception, e:
                print("Lesefehler in Thread", th1_id,"  ", e)
            

# Hauptprogramm

# Seriellen Port Konfigurieren:
ser = serial.Serial()
ser.port = '/dev/ttyUSB0'
ser.baudrate = 38400
ser.timeout = 1
# Port oeffnen
try:
    ser.open()
    print("Port open")
except:
    print("Unable to open port")
    exit(0)

app = QtGui.QApplication(sys.argv) 
dialog = MeinDialog() 
dialog.setFixedSize(720, 540)
dialog.show()
sys.exit(app.exec_())
Benutzeravatar
Madmartigan
User
Beiträge: 200
Registriert: Donnerstag 18. Juli 2013, 07:59
Wohnort: Berlin

Hallo,

habe den Code gerade nur flüchtig lesen können, habe im Moment wenig Zeit.
Was sofort auffällt, ist die Deklaration des Signals:

Code: Alles auswählen

my_signal = pyqtSignal(Ui_Hauptdialog.serin.insertPlainText)
Das funktioniert so nicht. ;-)
Schau mal an die Stelle im Code, wo das Signal emittiert wurde. Da übergibst du doch einen Wert (string):

Code: Alles auswählen

self.my_signal.emit(werte)
Exakt diesen Typ musst du dem Signal spendieren. Korrekterweise würde die Deklaration also lauten:

Code: Alles auswählen

my_signal = pyqtSignal(str)
Btw: asdf() taugt nur bedingt als Funktionsname ;-)
opccorsa
User
Beiträge: 34
Registriert: Samstag 20. Juli 2013, 07:43

Danke, funktioniert soweit erstmal. Muss jetzt nur sehen das ich den Scrollbar immer mit aktualisiere. :roll:

asdf() ist umbenannt :wink:
Benutzeravatar
Madmartigan
User
Beiträge: 200
Registriert: Donnerstag 18. Juli 2013, 07:59
Wohnort: Berlin

opccorsa hat geschrieben:Danke, funktioniert soweit erstmal. Muss jetzt nur sehen das ich den Scrollbar immer mit aktualisiere. :roll:
Wie gesagt verfügt QPlainTextEdit über eine Funktion ensureCursorVisible(). Wenn du also Text anfügst und danach diese Funktion aufrufst, scrollt das Feld automatisch ans Ende. Sehr nützlich für Text-Output. Ich vermute das ist es, was du machen willst, richtig?
EmaNymton
User
Beiträge: 174
Registriert: Sonntag 30. Mai 2010, 14:07

Versuche doch bitte erstmal die Anregungen von pillmuncher bzgl. Threads umzusetzen und deine Logik von der Ansicht zu trennen. Imho würde ich auch einen QThread (Thread Klasse des Qt-Frameworks) verwenden und damit die Daten abgreifen. Das kannst du dann erstmal alles auf der Kommandozeile testen. Wenn das dann funktioniert, spendierst du dem QThread die passenden Signale, die die Daten in deine GUI schaufeln und die Anzeige aktualisieren.
opccorsa
User
Beiträge: 34
Registriert: Samstag 20. Juli 2013, 07:43

Hallo Leute :)

Bin kurz vor dem Abschluss. Kann mir vielleicht noch jemand einen Tipp geben wie ich den richtigen Befehl finde auf der Seite http://qt-project.org/doc/qt-4.8/qlcdnu ... value-prop ? Möchte ein qlcdNumber auslesen. Habe schon einiges versucht. Ich bekomme immer als Fehlermeldung ... kein Attribut ...

passfail ist das qlcdNumber

Code: Alles auswählen

s = self.passfail.display()
oder
s = self.passfail.value()
oder
self.passfail.display(s)
print s

usw.
BlackJack

@opccorsa: Es sollte `value()` oder `intValue()` sein, je nach dem ob Du eine Gleitkommazahl oder eine Ganzzahl haben möchtest.

Wie sieht denn der komplette Traceback aus?
opccorsa
User
Beiträge: 34
Registriert: Samstag 20. Juli 2013, 07:43

File "programm.py", line 35, in __init__
s = self.passfail.value()
AttributeError: 'QWidget' object has no attribute 'value'

File "programm.py", line 35, in __init__
self.passfail.value()
AttributeError: 'QWidget' object has no attribute 'value'

:K
Antworten