Vishal R
Vishal R

Reputation: 66

PyQt4: GUI stuck during long-running loops

I have been looking for solutions in the stackoverflow and other pyqt tutorials on how to overcome the GUI freeze problem in pyqt4. There are similar topics that suggest the following methods to rectify it:

I have tried the above methods but still my GUI is stuck. I have given below the structure of code that is causing the problem.

# a lot of headers
from PyQt4 import QtCore, QtGui
import time
import serial
from time import sleep
from PyQt4.QtCore import QThread, SIGNAL

getcontext().prec = 6
getcontext().rounding = ROUND_CEILING

adbPacNo = 0
sdbPacNo =0
tmPacNo = 0

try:
    _fromUtf8 = QtCore.QString.fromUtf8
except AttributeError:
    def _fromUtf8(s):
        return s

try:
    _encoding = QtGui.QApplication.UnicodeUTF8
    def _translate(context, text, disambig):
        return QtGui.QApplication.translate(context, text, disambig, _encoding)
except AttributeError:
    def _translate(context, text, disambig):
        return QtGui.QApplication.translate(context, text, disambig)

#ADB Widget

class Ui_ADB(object):

    def setupUi(self, ADB):
        ADB.setObjectName(_fromUtf8("ADB"))
        ADB.resize(1080, 212)
        self.gridLayout_2 = QtGui.QGridLayout(ADB)
        self.gridLayout_2.setObjectName(_fromUtf8("gridLayout_2"))
        self.verticalLayout = QtGui.QVBoxLayout()
        self.verticalLayout.setObjectName(_fromUtf8("verticalLayout"))
        self.label_20 = QtGui.QLabel(ADB)
        font = QtGui.QFont()
        font.setBold(True)
        font.setUnderline(True)
        font.setWeight(75)
        self.label_20.setFont(font)
        self.label_20.setAlignment(QtCore.Qt.AlignCenter)
        self.label_20.setObjectName(_fromUtf8("label_20"))
        .
        # Rate X
        self.rateX = QtGui.QLineEdit(ADB)
        self.rateX.setReadOnly(True)
        self.rateX.setObjectName(_fromUtf8("rateX"))
        self.gridLayout.addWidget(self.rateX, 1, 6, 1, 1)
        # Rate Z
        self.rateZ = QtGui.QLineEdit(ADB)
        self.rateZ.setReadOnly(True)
        self.rateZ.setObjectName(_fromUtf8("rateZ"))
        self.gridLayout.addWidget(self.rateZ, 1, 10, 1, 1)

        # Rate Y
        self.rateY = QtGui.QLineEdit(ADB)
        self.rateY.setReadOnly(True)
        self.rateY.setObjectName(_fromUtf8("rateY"))
        self.gridLayout.addWidget(self.rateY, 1, 8, 1, 1)
        # qv2

        # qv1

        # rateValid

        # qv3

        # qs

        # and a lot more....

    def retranslateUi(self, ADB):
        # this contains the label definintions

# SDB Widget
class Ui_SDB(object):
    def setupUi(self, SDB):
        # again lot of fields to be displayed

    def retranslateUi(self, SDB):
        # this contains the label definintions

    def sdbReader(self, sdbData):
    #--- CRC Checking -------------------------------------------------#
        global sdbPacNo
        sdbPacNo+=1
        tmCRC = sdbData[0:4];
        data = sdbData[4:];
        tmCRCResult = TM_CRCChecker(data,tmCRC)
        if (tmCRCResult == 1):
            print 'SDB Packet verification : SUCCESS!'
        else:
            print 'SDB packet verification : FAILED!'
            quit()

    #--- Type ID and Length -------------------------------------------#

        # code to check the ID and length of the packet

    #--- Reading out SDB into its respective variables ----------------#
    # the code that performs the calculations and updates the parameters for GUI



## make thread for displaying ADB and SDB separately

# ADB Thread
class adbThread(QThread):
    def __init__(self,Ui_ADB, adbData):
        QThread.__init__(self)
        self.adbData = adbData
        self.Ui_ADB = Ui_ADB

    def adbReader(self,adbData):
        global adbPacNo
        adbPacNo+=1;
#--- CRC Checking -------------------------------------------------#
        tmCRC = self.adbData[0:4];
        data = self.adbData[4:];
        tmCRCResult = TM_CRCChecker(data,tmCRC)
        if (tmCRCResult == 1):
            print 'ADB Packet verification : SUCCESS!'
        else:
            print 'ADB packet verification : FAILED!'

#--- Type ID and Length -------------------------------------------#
    # code to check the ID and length

#--- Reading out ADB into respective variables --------------------#
        qvUnit = decimal.Decimal(pow(2,-30))
        qv1 = qvUnit*decimal.Decimal(int(ADBlock[0:8],16))
        qv1 = qv1.to_eng_string()
        print 'qv1 = '+ qv1
        self.Ui_ADB.qv1.setText(qv1)

        # similar to above code there are many such variables that have to
        # be calculated and printed on the respective fields.

    def __del__(self):
        self.wait()

    def run(self):
        self.adbReader(self.adbData)
        myMessage = "ITS F** DONE!"
        self.emit(SIGNAL('done(QString)'), myMessage)
        print "I am in ADB RUN"


# SDB Thread
class sdbThread(QThread):
#similar type as of adbThread

# Global Variable to set the number of packets
packets=0

class mainwindow(QtGui.QMainWindow):

    def __init__(self):
        super(self.__class__, self).__init__()
        self.setupUi(self)

    def setupUi(self, MainWindow):
        MainWindow.setObjectName(_fromUtf8("MainWindow"))
        MainWindow.resize(1153, 125)
        self.centralwidget = QtGui.QWidget(MainWindow)
        self.centralwidget.setObjectName(_fromUtf8("centralwidget"))
        self.formLayout = QtGui.QFormLayout(self.centralwidget)
        self.formLayout.setObjectName(_fromUtf8("formLayout"))
        self.label = QtGui.QLabel(self.centralwidget)
        self.label.setObjectName(_fromUtf8("label"))
        self.formLayout.setWidget(0, QtGui.QFormLayout.LabelRole, self.label)
        self.serialStatus = QtGui.QLineEdit(self.centralwidget)
        self.serialStatus.setReadOnly(True)
        self.serialStatus.setObjectName(_fromUtf8("serialStatus"))
        self.formLayout.setWidget(0, QtGui.QFormLayout.FieldRole, self.serialStatus)
        self.label_2 = QtGui.QLabel(self.centralwidget)
        self.label_2.setObjectName(_fromUtf8("label_2"))
        self.formLayout.setWidget(1, QtGui.QFormLayout.LabelRole, self.label_2)
        self.lineEdit = QtGui.QLineEdit(self.centralwidget)
        self.lineEdit.setReadOnly(True)
        self.lineEdit.setObjectName(_fromUtf8("lineEdit"))
        self.formLayout.setWidget(1, QtGui.QFormLayout.FieldRole, self.lineEdit)
        MainWindow.setCentralWidget(self.centralwidget)
        self.menubar = QtGui.QMenuBar(MainWindow)
        self.menubar.setGeometry(QtCore.QRect(0, 0, 1153, 25))
        self.menubar.setObjectName(_fromUtf8("menubar"))
        MainWindow.setMenuBar(self.menubar)
        self.statusbar = QtGui.QStatusBar(MainWindow)
        self.statusbar.setObjectName(_fromUtf8("statusbar"))
        MainWindow.setStatusBar(self.statusbar)
        self.retranslateUi(MainWindow)
        QtCore.QMetaObject.connectSlotsByName(MainWindow)

        ################################################################

        #Setting up ADB
        self.Ui_ADB = Ui_ADB()
        self.myADB = QtGui.QWidget()
        self.Ui_ADB.setupUi(self.myADB)
        self.myADB.show()

        # Setting up SDB
        self.Ui_SDB = Ui_SDB()
        self.mySDB = QtGui.QWidget()
        self.Ui_SDB.setupUi(self.mySDB)

        # Setting up the serial communication
        self.tmSerial = serial.Serial('/dev/ttyACM0',9600)

        self.sdb_Thread = sdbThread(self.Ui_SDB, self.mySDB)        

        buff = ''
        tempByte= ''

        counter =1

        while counter<10:
            # this reads the header of the SP 

            # Simulating the RTT signal trigger
            self.tmSerial.write('y')
            print "serial opened to read header"
            tmSerialData = self.tmSerial.read(8*8)
            print "tmSerialData="+str(tmSerialData)
            littleEndian = tmSerialData[0:8*8]

            # Converts the bitstream of SP header after converting to bigEndian 
            bufferData = bitstream_to_hex(littleEndian)
            print "bufferData="+str(bufferData)

            # Reads the header info : First 8 bytes
            headerINFO = readHeader(bufferData)

            # checking the packets in the headerINFO
            # ADB & SDB present
            global tmPacNo
            if (headerINFO['adbINFO'] == 1 and headerINFO['sdbINFO'] == 1):
                print 'Both ADB and SDB info are present'
                tmPacNo+=1;

                # Need to call both ADB and SDB 
                # Statements for reading the ADB
                bufferData = tmSerial.read(42*8) # ADB packet bitstream
                self.adbPacket = bitstream_to_hex(bufferData)

                # Calling ADB thread
                self.adb_Thread = adbThread(self.Ui_ADB, self.adbPacket)
                self.adb_Thread.start()
                #self.connect(self.adb_Thread, SIGNAL("finished()"),self.done)
                self.connect(self.adb_Thread, SIGNAL("done(QString)"), self.done)
                QtGui.QApplication.processEvents()

                # IGNORED FOR NOW...
                ## Statements for reading the SDB 
                #bufferData = self.tmSerial.read(46*8) # SDB packet bitstream
                #self.sdbPacket = bitstream_to_hex(bufferData)

                ## Calling SDB thread

                #self.sdb_Thread.run(self.sdbPacket)


            elif (headerINFO['adbINFO'] == 1 and headerINFO['sdbINFO'] == 0):
                print 'ADB INFO only present'
                tmPacNo+=1;

                # Statements for reading the ADB
                bufferData = self.tmSerial.read(42*8) # ADB packet bitstream
                self.adbPacket = bitstream_to_hex(bufferData)
                # Calling ADB thread
                self.adb_Thread = adbThread(self.Ui_ADB, self.adbPacket)
                self.adb_Thread.start()
                #self.connect(self.adb_Thread, SIGNAL("finished()"),self.done)
                self.connect(self.adb_Thread, SIGNAL("done(QString)"), self.done)
                QtGui.QApplication.processEvents()

            # IGNORED FOR NOW...
            #elif (headerINFO['adbINFO'] == 0 and headerINFO['sdbINFO'] == 1):
                #print 'SDB INFO only present'
                #tmPacNo+=1;
                ## Statements for reading the SDB
                #bufferData = self.tmSerial.read(46*8) # SDB packet bitstream
                #self.sdbPacket = bitstream_to_hex(bufferData)
                ## Calling SDB thread

                #self.sdb_Thread.run(sdbPacket)

            #while (self.adb_Thread.isFinished() or self.sdb_Thread.isFinished() is False):
                #print "waiting to complete adb Thread"

            counter+=1

        ################################################################

    def retranslateUi(self, MainWindow):
        MainWindow.setWindowTitle(_translate("MainWindow", "MainWindow", None))
        self.label.setText(_translate("MainWindow", "Serial Communication Status", None))
        self.label_2.setText(_translate("MainWindow", "No. of SP_Packets Received", None))

    ####################################################################
    def done(self,someText):
        print someText + "the value has been updated"
        self.myADB.show()



# This program converts the little endian bitstream -> BigEndian -> hex
def bitstream_to_hex(bitStream):
    #global littleEndian
    # small code for conversion


if __name__== "__main__":
    import sys

    # setting up the GUI
    app = QtGui.QApplication(sys.argv)
    main = mainwindow()
    main.show() 
    sys.exit(app.exec_())

In the above code it can be noticed that threads have been implemented but I am not sure what am I doing wrong? I have put the long running loop adbreader() in the thread but the values are not updated in GUI responsively. I could only view the output only after the while loop has run 10 times.

Also, I have tried using QtGui.QApplication.processEvents() and this somehow manages to print the values in GUI, but I am not happy with that approach.(Not happy because, it sometimes skips printing while on iteration 5 and it prints the values in iteration 7 next) Some guidance on how to use threads in this purpose would be greatly appreciated.

Upvotes: 1

Views: 1351

Answers (1)

Vishal R
Vishal R

Reputation: 66

As suggested by three_pinapples , I tried to offload the program to by creating more thread. Further I was calling the thread that performed the whole serial writing and reading in while loop. This caused the problem of calling the thread only once no matter the loop. I am not sure why, but I guess it could be because of the same object has been called again and again in the loop? Not sure.

I figured out a way around this issue by using a signal/slot mechanism acting as recursive function that keeps the thread in infinite running mode irrespective of the while loop. I have posted the modified structure of the code below:

# a lot of headers
from PyQt4 import QtCore, QtGui
import time
import serial
from time import sleep
from PyQt4.QtCore import QThread, SIGNAL

getcontext().prec = 6
getcontext().rounding = ROUND_CEILING

adbPacNo = 0
sdbPacNo =0
tmPacNo = 0

try:
    _fromUtf8 = QtCore.QString.fromUtf8
except AttributeError:
    def _fromUtf8(s):
        return s

try:
    _encoding = QtGui.QApplication.UnicodeUTF8
    def _translate(context, text, disambig):
        return QtGui.QApplication.translate(context, text, disambig, _encoding)
except AttributeError:
    def _translate(context, text, disambig):
        return QtGui.QApplication.translate(context, text, disambig)

#ADB Widget

class Ui_ADB(object):

    def setupUi(self, ADB):


        # Rate X

        # Rate Z


        # Rate Y


        # qv2

        # qv1

        # rateValid

        # qv3

        # qs

        # and a lot more....

    def retranslateUi(self, ADB):
        # this contains the label definintions


## make thread for displaying ADB and SDB separately

# ADB Thread
class adbThread(QThread):
    def __init__(self,Ui_ADB, adbData):


    def adbReader(self,adbData):
        global adbPacNo
        adbPacNo+=1;
#--- CRC Checking -------------------------------------------------#


#--- Type ID and Length -------------------------------------------#
    # code to check the ID and length

#--- Reading out ADB into respective variables --------------------#


        # similar to above code there are many such variables that have to
        # be calculated and printed on the respective fields.

    def __del__(self):
        self.wait()

    def run(self):
        self.adbReader(self.adbData)
        myMessage = "ITS F** DONE!"
        self.emit(SIGNAL('done(QString)'), myMessage)
        print "I am in ADB RUN"


# SDB Thread
class sdbThread(QThread):
#similar type as of adbThread

# Global Variable to set the number of packets
packets=0

# WorkerThread : This runs individually in the loop & call the respective threads to print.
class workerThread(QThread):

    readComplete = QtCore.pyqtSignal(object)

    def __init__(self, tmSerial, Ui_ADB, myADB, Ui_SDB, mySDB):
        QThread.__init__(self)
        self.tmSerial = tmSerial
        self.Ui_ADB = Ui_ADB
        self.myADB = myADB
        self.Ui_SDB = Ui_SDB
        self.mySDB = mySDB

    def __del__(self):
        self.wait()

    def run(self):
        print "worker = "+str(self.temp)
        buff = ''
        tempByte= ''

        # Simulating the RTT signal trigger

        self.tmSerial.write('y')

        # Reading SP Header
        tmSerialData = self.tmSerial.read(8*8)

        # Converts the bitstream of SP header after converting to bigEndian 
        bufferData = bitstream_to_hex(littleEndian)

        # Reads the header info : First 8 bytes
        headerINFO = readHeader(bufferData)

        # checking the packets in the headerINFO

        global tmPacNo
        if (headerINFO['adbINFO'] == 1 and headerINFO['sdbINFO'] == 1):
            print 'Both ADB and SDB info are present'
            tmPacNo+=1;

            # Need to call both ADB and SDB 
            # Statements for reading the ADB
            bufferData = tmSerial.read(42*8) # ADB packet bitstream
            self.adbPacket = bitstream_to_hex(bufferData)

            # Calling ADB thread
            self.adb_Thread = adbThread(self.Ui_ADB, self.myADB, self.adbPacket)
            self.adb_Thread.start()
            self.adb_Thread.adbReadComplete.connect(self.adbdone)

            # IGNORED -- Statements for reading the SDB


            # Calling SDB thread

            #self.sdb_Thread.run(self.sdbPacket)


        elif (headerINFO['adbINFO'] == 1 and headerINFO['sdbINFO'] == 0):
            print 'ADB INFO only present'
            tmPacNo+=1;

            # Statements for reading the ADB
            bufferData = self.tmSerial.read(42*8) # ADB packet bitstream
            self.adbPacket = bitstream_to_hex(bufferData)
            # Calling ADB thread
            self.adb_Thread = adbReadThread(self.Ui_ADB, self.myADB , self.adbPacket)
            self.adb_Thread.start()
            self.adb_Thread.adbReadComplete.connect(self.adbDone)

        # IGNORED FOR NOW
        #elif (headerINFO['adbINFO'] == 0 and headerINFO['sdbINFO'] == 1):
            #print 'SDB INFO only present'
            #tmPacNo+=1;
            ## Statements for reading the SDB
            #bufferData = self.tmSerial.read(46*8) # SDB packet bitstream
            #self.sdbPacket = bitstream_to_hex(bufferData)
            ## Calling SDB thread

            #self.sdb_Thread.run(sdbPacket)
        mess = "Worker Reading complete"

        self.readComplete.emit(mess)


    def adbDone(self,text):
        print text
        #self.myADB.show()



# Global Variable to set the number of packets
packets=0

class mainwindow(QtGui.QMainWindow):

    def __init__(self):
        super(self.__class__, self).__init__()
        self.setupUi(self)

    def setupUi(self, MainWindow):
        MainWindow.setObjectName(_fromUtf8("MainWindow"))
        MainWindow.resize(1153, 125)
        # ..... codes for main window GUI

        ################################################################

        #Setting up ADB
        self.Ui_ADB = Ui_ADB()
        self.myADB = QtGui.QWidget()
        self.Ui_ADB.setupUi(self.myADB)
        #self.myADB.show()

        # IGONRED FOR NOW -- Setting up SDB
        self.Ui_SDB = Ui_SDB()
        self.mySDB = QtGui.QWidget()
        self.Ui_SDB.setupUi(self.mySDB)

        # Setting up the serial communication
        self.tmSerial = serial.Serial('/dev/ttyACM0',9600)

        #  IGONRED FOR NOW --  setting up the SDB read thread 
        #self.sdb_Thread = sdbReadThread(self.Ui_SDB, self.SDBPacket)

        # *** MODIFIED ***
        # Setting up the Worker thread 
        self.tmWorker = workerThread(self.tmSerial, self.Ui_ADB, self.myADB, Ui_SDB, self.mySDB)

        # Code to call the thread that checks the serial data and print accordingly

        self.tmWorker.start()
        self.tmWorker.readComplete.connect(self.done) # This will act as a recursive function


    def retranslateUi(self, MainWindow):
        MainWindow.setWindowTitle(_translate("MainWindow", "MainWindow", None))
        self.label.setText(_translate("MainWindow", "Serial Communication Status", None))
        self.label_2.setText(_translate("MainWindow", "No. of SP_Packets Received", None))

    ####################################################################
    def done(self):
        print "worker reading done" 
        self.myADB.show()
        self.tmWorker.start() #Modified
        #sleep(01)

# This program converts the little endian bitstream -> BigEndian -> hex
def bitstream_to_hex(bitStream):
    # Code for conversion

if __name__== "__main__":
    import sys

    # setting up the GUI
    app = QtGui.QApplication(sys.argv)
    main = mainwindow()
    main.show()
    sys.exit(app.exec_())

This program now works fine and the GUI seems responsive. But I find a glitch in GUI as am not sure whether it is because the program runs much faster than the time required to refresh the frames. I find it so because the counter placed in the GUI skips one or two counts while updating the value. But the GUI is responsive and there is no force-close during execution of the program.

Hope this helps someone who is in search of similar problem. More insight on the glitches and good programming techniques are welcome. Thank you.

Upvotes: 1

Related Questions