Search code examples
pythonmultithreadinguser-interfacepyqt5sleep

GUI freezes on sleep from a worker


I have created a big project involving communication with a controller via serial port. My project is divided to 3 files:

File A - a pyqt5 designer file - including all my buttons, line edits, etc.

File B - a serial communication file - basically ask questions and receive answers from the controller.

File C - the main file; in this file I am importing file A and file B and using them.

In file C, I have created a worker, so all the communications with the controller are being made by this thread. (using the class and functions of file B..)

Now, I am trying to make a create a new function within the worker. The functions is using time.sleep every x seconds for y times. From what I understand, because I am using a thread, the GUI should not get stuck, but for some reason, it does.

Since the code is very long, I have only attaching what I think is relevant to the question. I tried to copy as little of the code, yet still keep it understandable, I hope it's ok.

File A - pyqt5 designer code:

from PyQt5 import QtCore, QtGui, QtWidgets

class Ui_MainWindow(object):
    def setupUi(self, MainWindow):

################creating general veriables#####################
        MainWindow.setObjectName("MainWindow")
        MainWindow.setFixedSize(780, 585)
        font = QtGui.QFont()
        font.setBold(False)
        font.setWeight(50)
        MainWindow.setFont(font)
        self.centralwidget = QtWidgets.QWidget(MainWindow)
        self.centralwidget.setObjectName("centralwidget")
        self.frame_Holder = QtWidgets.QFrame(self.centralwidget)
        self.frame_Holder.setGeometry(QtCore.QRect(0, 85, 780, 500))
        self.frame_Holder.setFrameShape(QtWidgets.QFrame.StyledPanel)
        self.frame_Holder.setFrameShadow(QtWidgets.QFrame.Raised)
        self.frame_Holder.setObjectName("frame_Holder")


#########################################################################        
################creating veriables for the Top Frame#####################

        self.frame_Top = QtWidgets.QFrame(self.centralwidget)
        self.frame_Top.setEnabled(True)
        self.frame_Top.setGeometry(QtCore.QRect(10, 5, 760, 80))
        font = QtGui.QFont()
        font.setStrikeOut(False)
        self.frame_Top.setFont(font)
        self.frame_Top.setFrameShape(QtWidgets.QFrame.WinPanel)
        self.frame_Top.setFrameShadow(QtWidgets.QFrame.Raised)
        self.frame_Top.setLineWidth(1)
        self.frame_Top.setObjectName("frame_Top")        

################creating Buttons#####################

        self.btn_Connect = QtWidgets.QPushButton(self.frame_Top)
        self.btn_Connect.setGeometry(QtCore.QRect(85, 50, 75, 23))
        self.btn_Connect.setObjectName("btn_Connect")
        self.btn_Connect.setEnabled(False)

        self.btn_PortList = QtWidgets.QPushButton(self.frame_Top)
        self.btn_PortList.setGeometry(QtCore.QRect(10, 50, 65, 23))
        self.btn_PortList.setObjectName("btn_PortList")
        self.productMenu=QtWidgets.QMenu(self.frame_Top)
        self.btn_PortList.setMenu(self.productMenu) 
        self.btn_Init = QtWidgets.QPushButton(self.frame_Top)
        self.btn_Init.setEnabled(False)
        self.btn_Init.setGeometry(QtCore.QRect(300, 50, 75, 23))
        self.btn_Init.setObjectName("btn_Init")

################creating text edits#####################
        self.edit_version = QtWidgets.QLineEdit(self.frame_Top)
        self.edit_version.setGeometry(QtCore.QRect(170, 50, 120, 23))
        self.edit_version.setObjectName("edit_version")
        self.edit_version.setEnabled(False)
################creating labels#####################

        self.lbl_Version = QtWidgets.QLabel(self.frame_Top)
        self.lbl_Version.setGeometry(QtCore.QRect(170, 30, 71, 21))
        font = QtGui.QFont()
        font.setPointSize(10)
        font.setBold(True)
        font.setWeight(75)
        self.lbl_Version.setFont(font)
        self.lbl_Version.setObjectName("lbl_Version")             

        self.lbl_FrameTop = QtWidgets.QLabel(self.frame_Top)
        self.lbl_FrameTop.setEnabled(True)
        self.lbl_FrameTop.setGeometry(QtCore.QRect(5, 5, 90, 25))
        font = QtGui.QFont()
        font.setPointSize(12)
        font.setBold(True)
        font.setItalic(True)
        font.setWeight(75)
        font.setStrikeOut(False)
        font.setKerning(True)       
        self.lbl_FrameTop.setFont(font)
        self.lbl_FrameTop.setObjectName("lbl_FrameTop")

        self.lbl_On = QtWidgets.QLabel(self.frame_Top)
        self.lbl_On.setGeometry(QtCore.QRect(528, 30, 41, 16))
        self.lbl_On.setObjectName("lbl_On")

        self.lbl_Enable = QtWidgets.QLabel(self.frame_Top)
        self.lbl_Enable.setGeometry(QtCore.QRect(560, 30, 41, 16))
        self.lbl_Enable.setObjectName("lbl_Enable")

        self.lbl_Rls = QtWidgets.QLabel(self.frame_Top)
        self.lbl_Rls.setGeometry(QtCore.QRect(608, 30, 41, 16))
        self.lbl_Rls.setObjectName("lbl_Rls")

        self.lbl_Chk = QtWidgets.QLabel(self.frame_Top)
        self.lbl_Chk.setGeometry(QtCore.QRect(645, 30, 41, 16))
        self.lbl_Chk.setObjectName("lbl_Chk")

        self.lbl_Wafer = QtWidgets.QLabel(self.frame_Top)
        self.lbl_Wafer.setGeometry(QtCore.QRect(683, 30, 41, 16))
        self.lbl_Wafer.setObjectName("lbl_Wafer")

        self.lbl_Err = QtWidgets.QLabel(self.frame_Top)
        self.lbl_Err.setGeometry(QtCore.QRect(725, 30, 20, 16))
        self.lbl_Err.setObjectName("lbl_Err")

        self.lbl_Port = QtWidgets.QLabel(self.frame_Top)
        self.lbl_Port.setGeometry(QtCore.QRect(10, 30, 51, 21))
        font = QtGui.QFont()
        font.setPointSize(10)
        font.setBold(True)
        font.setItalic(True)
        font.setWeight(75)
        self.lbl_Port.setFont(font)
        self.lbl_Port.setObjectName("lbl_Port")              



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


        self.frame_AutoRun = QtWidgets.QFrame(self.frame_Holder)
        self.frame_AutoRun.setEnabled(False)
        self.frame_AutoRun.setGeometry(QtCore.QRect(10, 90, 760, 320))
        font = QtGui.QFont()
        self.frame_AutoRun.setFont(font)
        self.frame_AutoRun.setFrameShape(QtWidgets.QFrame.WinPanel)
        self.frame_AutoRun.setFrameShadow(QtWidgets.QFrame.Raised)
        self.frame_AutoRun.setLineWidth(1)
        self.frame_AutoRun.setObjectName("frame_AutoRun")

        self.lbl_AutoRun = QtWidgets.QLabel(self.frame_AutoRun)
        self.lbl_AutoRun.setGeometry(QtCore.QRect(5, 5, 110, 25))
        font = QtGui.QFont()
        font.setPointSize(12)
        font.setBold(True)
        font.setItalic(True)
        font.setWeight(75)
        font.setStrikeOut(False)
        font.setKerning(True)
        self.lbl_AutoRun.setFont(font)
        self.lbl_AutoRun.setObjectName("lbl_AutoRun")

        self.btn_RunStop = QtWidgets.QPushButton(self.frame_AutoRun)
        self.btn_RunStop.setGeometry(QtCore.QRect(500, 40, 60, 23))
        font = QtGui.QFont()
        font.setPointSize(8)
        font.setBold(False)
        font.setWeight(50)
        font.setKerning(True)
        self.btn_RunStop.setFont(font)
        self.btn_RunStop.setObjectName("btn_RunStop")



        ########################4##########################



        MainWindow.setCentralWidget(self.centralwidget)
        self.retranslateUi(MainWindow)
        QtCore.QMetaObject.connectSlotsByName(MainWindow)





    def retranslateUi(self, MainWindow):
        _translate = QtCore.QCoreApplication.translate
        MainWindow.setWindowTitle(_translate("MainWindow", "ECC GUI"))
        self.btn_Connect.setText(_translate("MainWindow", "Connect"))
        self.btn_PortList.setText(_translate("MainWindow", "Ports"))
        self.lbl_Version.setText(_translate("MainWindow", "Version"))
        self.btn_Init.setText(_translate("MainWindow", "Initiate"))
        self.lbl_FrameTop.setText(_translate("MainWindow", "Connect"))##########
        self.lbl_Port.setText(_translate("MainWindow", "Ports"))
        self.lbl_On.setText(_translate("MainWindow", "ON"))
        self.lbl_Enable.setText(_translate("MainWindow", "Enable"))
        self.lbl_Rls.setText(_translate("MainWindow", "RLS"))
        self.lbl_Chk.setText(_translate("MainWindow", "CHK"))
        self.lbl_Wafer.setText(_translate("MainWindow", "Wafer"))
        self.lbl_Err.setText(_translate("MainWindow", "ERR"))
        self.lbl_AutoRun.setText(_translate("MainWindow", "Auto Run"))
        self.btn_RunStop.setText(_translate("MainWindow", "Run"))

File B - the serial communication file

import serial 
from string import hexdigits
from re import search
class ECC():
    def __init__(self,Port ):
        self.ser = serial.Serial(port ='COM'+str(Port), baudrate= 38400,timeout=1)
    def cmd(self,command):
        return ('00'+command+chr(13)).encode('utf-8')

    def disconnect(self):
        self.ser.close()

    def init(self):
        self.ser.write(self.cmd('INI'))
        return(self.ser.readline().decode('utf-8')[:3])        

    def spr(self,address,value):
        self.ser.write(self.cmd('SPR'+str(address)+str(value)))
        return (self.ser.readline().decode('utf-8')[:3])


    def rpr(self,address):
        self.ser.write(self.cmd('RPR'+str(address)))
        return(self.ser.readline().decode('utf-8')[3:7])


    def chuck(self):
        self.ser.write(self.cmd('CHK'))
        return(self.ser.readline().decode('utf-8')[:3])        

    def rls(self):
        self.ser.write(self.cmd('RLS'))
        return(self.ser.readline().decode('utf-8')[:3])          

    def cap(self):
        self.ser.write(self.cmd('cap'))
        cap=self.ser.readline()[3:].decode('utf-8')
        cap=cap.rstrip()
        if (all(c in hexdigits for c in cap) and cap!=""):
            return int(cap,16)
        else:
            return("Busy...")

    def version(self):
        self.ser.write(self.cmd('VER'))
        return(self.ser.readline().decode('utf-8')[:9])

    def gst(self):
        self.ser.flushInput()
        self.ser.flushOutput()
        self.ser.write(self.cmd('GST'))
        gst=self.ser.readline()
        gst=gst.decode('utf-8')
        is_STV=search('^STV',str(gst))
        if (is_STV):
            stat_hex=gst[3:]
            stat_bin=(bin(int(stat_hex,16))[2:].zfill(16))
            stat_bool=[bool(int(bit)) for bit in stat_bin] 
        else:
            stat_hex="ERR"
            stat_bool="ERR"

        return stat_hex, stat_bool

    def statLights(self):
        [stat_hex, stat_bool]=self.gst()
        if(not stat_hex=="ERR"):
            lights=[not(stat_bool[3]),not(stat_bool[5]),not(stat_bool[10]),stat_bool[10],stat_bool[11],stat_bool[0]]
        else:
            lights="ERR"
        return lights




    def msrphys(self):
        self.ser.flushInput()
        self.ser.flushOutput()
        self.ser.write(self.cmd('msr'))
        A=list()
        A.append(self.ser.readline())
        A[0]=A[0][3:7]
        exists=search('[0-9A-Fa-f]{4}',str(A[0]))

        if(exists):
            A[0]=int(A[0],16)*-0.08398628+2500
            for ind in range(1,32):

                A.append(self.ser.readline())
                exists=search('[0-9A-Fa-f]{4}',str(A[ind]))
                if (exists):
                    A[ind]=int(A[ind].decode('utf-8'),16)*-0.08398628+2500
                else:
                    A[0]="ERR"
                    break
            self.ser.readline()

            A[12]=A[12]*5.75
            A[13]=A[13]*5.75
            A[14]=A[14]*0.1
        else:
            A[0]="ERR"
        return A

File C - the operative file

from test_des import Ui_MainWindow
from PyQt5 import QtWidgets, QtCore, QtGui
from test_serialCom import ECC
import serial.tools.list_ports
import sys
from re import findall
import time
from functools import partial
try:
    import queue
except ImportError:
    import Queue as queue

class eccWorker(QtCore.QRunnable):
    def __init__(self,port,q):
        super(eccWorker, self).__init__()
        self.finished = QtCore.pyqtSignal() 
        self.port=port
        self.Q=q
        self.threadactive=True
    def run(self):
        if(self.threadactive==True):
            try:
                self.ecc=ECC(self.port)
            except:
                self.threadactive=False
                self.Q.put("ERROR-Not connected")
                self.Q.put("ERROR-Not connected")
            else:
                self.Q.put(self.ecc.version())
                self.Q.put(self.ecc.rpr('04'))
    def init(self):
        if(self.threadactive):
            self.ecc.init()  

    def disconnect(self):
        self.ecc.disconnect()
        self.threadactive=False

    def readCap(self):
        if(self.threadactive==True):
            self.Q.put(self.ecc.cap())

    def chk(self):
        if(self.threadactive==True):
            self.ecc.chuck()

    def rls(self):
        if(self.threadactive==True):
            self.ecc.rls()

    def rprWorker(self,par):
        if(self.threadactive==True):
            self.Q.put(self.ecc.rpr(par))

    def sprWorker(self,par,val):
        if(self.threadactive==True):
#            par=par.zfill(2)
            isOk=self.ecc.spr(par,val)
            if (isOk!="NAK"):
                self.Q.put(self.ecc.rpr(par))

    def getStat(self):
        if(self.threadactive==True):
            statLight=self.ecc.statLights()
            if(statLight=="ERR"):
                self.Q.put("ERR")
            else:
                for i in statLight:
                    self.Q.put(i)

    def sprWorkerNoRpr(self,par,val):
        if(self.threadactive==True):
            isOk=self.ecc.spr(par,val)
            if (isOk!="NAK"):
                return True

    def test1(self,Vchk,N,dt,Dt,threshold):
        Vchk=str(Vchk).zfill(4)
        if(self.sprWorkerNoRpr('04',Vchk)):
            cap1=list()
            cap2=list()
            for i in range(N):
                self.rls()
                time.sleep(dt)
                self.readCap()
                res=self.Q.get()
                if (res!="Busy..."):
                    cap1.append(int(str(res)))
                time.sleep(Dt)
                self.chk()
                time.sleep(dt)
                self.readCap()
                res2=self.Q.get()
                if (res2!="Busy..."):
                    cap2.append(int(str(res2)))
                time.sleep(Dt)
            self.rls()
            cap_Rls=sum(cap1)/len(cap1)     
            cap_Chk=sum(cap2)/len(cap2)
            if((max(cap2)-min(cap1))>threshold):
               isPass=True
            else:
                isPass=False
            self.Q.put(cap_Rls)
            self.Q.put(cap_Chk)
            self.Q.put(isPass)
            print("Done!")


class ApplicationWindow(QtWidgets.QMainWindow):
    def __init__(self):
        super(ApplicationWindow, self).__init__()
        self.ui = Ui_MainWindow()
        self.ui.setupUi(self)
        self.threadpool=QtCore.QThreadPool()        
        self.Q=queue.Queue()


        self.ui.productMenu.aboutToShow.connect(self.findPort)
        self.ui.btn_Connect.clicked.connect(self.connectClicked)        
        self.ui.btn_RunStop.clicked.connect(self.run)
        self.ui.btn_Init.clicked.connect(self.initClicked)

        self.redOff=QtGui.QColor(120,0,0)
        self.redOn=QtGui.QColor(255,0,0)
        self.grnOff=QtGui.QColor(7,100,0)
        self.grnOn=QtGui.QColor(16,243,0)
        self.yelOff=QtGui.QColor(155,80,0)
        self.yelOn=QtGui.QColor(255,127,0)

        self.onPen=QtGui.QPen(self.grnOff, 8, QtCore.Qt.SolidLine)
        self.enablePen=QtGui.QPen(self.grnOff, 8, QtCore.Qt.SolidLine)
        self.rlsPen=QtGui.QPen(self.grnOff, 8, QtCore.Qt.SolidLine)
        self.chkPen=QtGui.QPen(self.grnOff, 8, QtCore.Qt.SolidLine)
        self.waferPen=QtGui.QPen(self.yelOff, 8, QtCore.Qt.SolidLine)
        self.errPen=QtGui.QPen(self.redOff, 8, QtCore.Qt.SolidLine)

    def paintEvent(self, e):

       onLedPainter = QtGui.QPainter(self)
       onLedPainter.setPen(self.onPen)
       onLedPainter.setBrush(self.grnOff)
       onLedPainter.drawEllipse(540,55,10,10)

       enabledLedPainter = QtGui.QPainter(self)
       enabledLedPainter.setPen(self.enablePen)
       enabledLedPainter.setBrush(self.grnOff)
       enabledLedPainter.drawEllipse(580,55,10,10)

       rlsLedPainter = QtGui.QPainter(self)
       rlsLedPainter.setPen(self.rlsPen)
       rlsLedPainter.setBrush(self.grnOff)
       rlsLedPainter.drawEllipse(620,55,10,10)

       chkLedPainter = QtGui.QPainter(self)
       chkLedPainter.setPen(self.chkPen)
       chkLedPainter.setBrush(self.grnOff)
       chkLedPainter.drawEllipse(660,55,10,10)

       waferLedPainter = QtGui.QPainter(self)
       waferLedPainter.setPen(self.waferPen)
       waferLedPainter.setBrush(self.yelOff)
       waferLedPainter.drawEllipse(700,55,10,10)

       errLedPainter = QtGui.QPainter(self)
       errLedPainter.setPen(self.errPen)
       errLedPainter.setBrush(self.redOff)
       errLedPainter.drawEllipse(740,55,10,10)


    def updateStat(self):
        #####updating LEDs####
            self.worker.getStat()
            self.ledStat=list()
            self.ledStat.append(self.worker.Q.get())
            if(not self.ledStat[0]=="ERR"):
                for i in range(5):
                    self.ledStat.append(self.worker.Q.get())
                if(self.ledStat[0]):
                    self.onPen=QtGui.QPen(self.grnOn, 8, QtCore.Qt.SolidLine)
                else:
                    self.onPen=QtGui.QPen(self.grnOff, 8, QtCore.Qt.SolidLine)

                if(self.ledStat[1]):
                    self.enablePen=QtGui.QPen(self.grnOn, 8, QtCore.Qt.SolidLine)
                else:
                    self.enablePen=QtGui.QPen(self.grnOff, 8, QtCore.Qt.SolidLine) 

                if(self.ledStat[2]):
                    self.rlsPen=QtGui.QPen(self.grnOn, 8, QtCore.Qt.SolidLine)
                    self.chkPen=QtGui.QPen(self.grnOff, 8, QtCore.Qt.SolidLine)
                else:
                    self.chkPen=QtGui.QPen(self.grnOn, 8, QtCore.Qt.SolidLine)
                    self.rlsPen=QtGui.QPen(self.grnOff, 8, QtCore.Qt.SolidLine)

                if(self.ledStat[4]):
                        self.waferPen=QtGui.QPen(self.yelOn, 8, QtCore.Qt.SolidLine)
                else:
                        self.waferPen=QtGui.QPen(self.yelOff, 8, QtCore.Qt.SolidLine) 

                if(self.ledStat[5]):
                    self.errPen=QtGui.QPen(self.redOn, 8, QtCore.Qt.SolidLine)
                else:
                    self.errPen=QtGui.QPen(self.redOff, 8, QtCore.Qt.SolidLine)
                self.update()
            else:                
                self.connectClicked()
                self.ui.edit_version.setText("GST Disconnected!")

    def findPort(self):
           self.ui.productMenu.clear()
           comPorts = list(serial.tools.list_ports.comports())    # get a list of all devices connected through serial port
           comPorts=sorted(comPorts)
           for port in comPorts:
                   self.ui.productMenu.addAction(str(port),self.selectedPort)

    def selectedPort(self):
        self.portNum=self.sender()
        self.portNum=self.portNum.text()
        self.portNum=findall("COM\d+",self.portNum)
        self.ui.btn_PortList.setText(str(self.portNum[0]))
        self.portNum=self.portNum[0][3:]
        self.ui.btn_Connect.setEnabled(True)

    def connectClicked(self):
        if (self.ui.btn_Connect.text()=="Connect"):
            self.worker=eccWorker(self.portNum,self.Q)
            self.threadpool.start(self.worker)
            ver=self.worker.Q.get()
            volHex=self.worker.Q.get()

            if(ver[:3]=="VER"):
                self.ui.btn_Connect.setText("Disconnect")
                self.ui.btn_PortList.setEnabled(False)
                self.ui.edit_version.setText(ver)
                self.ui.btn_Init.setEnabled(True)
                self.ui.frame_AutoRun.setEnabled(True)


                self.timer_updateStat = QtCore.QTimer()
                self.timer_updateStat.setInterval(500)
                self.timer_updateStat.timeout.connect(self.updateStat)
                self.timer_updateStat.start()                


            else:
                self.ui.edit_version.setText("Error!")
        else:        
            self.timer_updateStat.stop()
            self.worker.disconnect()
            self.ui.btn_Connect.setText("Connect")
            self.ui.btn_Connect.setEnabled(False)
            self.ui.btn_Init.setEnabled(False)
            self.ui.frame_AutoRun.setEnabled(False)
            self.ui.btn_PortList.setEnabled(True)
            self.ui.edit_version.clear()
            self.setFixedSize(780,585)
            self.ui.btn_PortList.setText("Ports")
            self.onPen=QtGui.QPen(self.grnOff, 8, QtCore.Qt.SolidLine)
            self.enablePen=QtGui.QPen(self.grnOff, 8, QtCore.Qt.SolidLine) 
            self.chkPen=QtGui.QPen(self.grnOff, 8, QtCore.Qt.SolidLine)
            self.rlsPen=QtGui.QPen(self.grnOff, 8, QtCore.Qt.SolidLine)
            self.waferPen=QtGui.QPen(self.yelOff, 8, QtCore.Qt.SolidLine) 
            self.errPen=QtGui.QPen(self.redOff, 8, QtCore.Qt.SolidLine)
            self.update()

    def initClicked(self):
        self.worker.init()

    def run(self):
        self.worker.test1(200,5,0.2,0.3,100)
        cap_Rls=self.Q.get()
        cap_Chk=self.Q.get()
        ispass=self.Q.get()
        print("cap_Rls:\n")
        print(cap_Rls)
        print("cap_Chk:\n")
        print(cap_Chk)
        print(ispass)


    def closeEvent(self,event):
        try:
            self.worker.disconnect()
        finally:
            sys.exit()

if __name__ == "__main__":
    app = QtWidgets.QApplication(sys.argv)
    application = ApplicationWindow()
    application.show()
    sys.exit(app.exec_())

So what does the GUI gets stuck for the time of the function even though I am using a thread? What can I do to fix this?

Also, ideally, I would want to have another file, file D, that contains a main class called TESTS,and it has like 5 functions within it, each one represents a test. The problem I faced doing the above, is failing to calling/getting information from the thread active in file C, so if you have an idea of how to make this one as well, it would be much appreciated. (if not creating another file, so store all the tests in a different class. Here, as well, I faced some issues and could not make it work).


Solution

  • After many many hours of searching online, I found that using QtTest.QTest.qWait(msecs) instead of sleep is giving me the wanted result - it holds the commands but does not make the GUI freeze. In order to use it I had to use from PyQt5 import QtTest.

    If anyone has yet a better suggestions (to any of the questions above) I would love to see them.