Python Qt GUI Design: be a serial port debugging assistant (practical Part-1)

catalogue

1. UI design

2. Convert UI file to Py file

3. Logic function realization

3.1 initialization procedure

3.2 serial port detection procedure

3.3. Set and open serial port program

3.4 program for sending data regularly

3.5 data sending procedure

3.6 data receiving procedure

3.7 log saving procedure

3.8. Loading log program

3.9, open blog, official account program

3.10 clear the sending and receiving data display program

3.11. Close the serial port program

The Python Qt GUI design series blog has finally reached the practical chapter. This blog will run through the previous basic knowledge points to realize a serial port debugging assistant.

Attention to the official account: the man plays programming, replies the key word: serial debugging assistant, gets the source code of the project.

1. UI design

The UI design is implemented by Qt Creator, and the component layout is as follows:

2. Convert UI file to Py file

Here, a Python script is used to convert the UI file into a Python file. The code is as follows:

import os
import os.path
 
dir ='./' #The path where the file is located
 
#Find all under the path ui file
def listUiFile():
    list = []
    files = os.listdir(dir)
    for filename in files:
        #print(filename)
        if os.path.splitext(filename)[1] == '.ui':
            list.append(filename)
    
    return list
 
#The extension is not ui conversion to py file
def transPyFile(filename):
    return os.path.splitext(filename)[0] + '.py'
 
#By order Convert ui files to py file
def runMain():
    list = listUiFile()
    for uifile in list:
        pyfile = transPyFile(uifile)
        cmd = 'pyuic5 -o {pyfile} {uifile}'.format(pyfile=pyfile, uifile=uifile)
        os.system(cmd)
        
if __name__ =="__main__":
    runMain()

3. Logic function realization

3.1 initialization procedure

Firstly, initialize the status of some components and flag bits, and set the relationship between signal and slot. The implementation code is as follows:

    # Initialization program
    def __init__(self):
        super(Pyqt5_Serial, self).__init__()
        
        self.setupUi(self)
        
        self.init()
        
        self.ser = serial.Serial()
        self.port_check()
        
        # Set Logo and title
        self.setWindowIcon(QIcon('Com.png'))
        self.setWindowTitle("Serial debugging assistant [official account] beauty playing programming")
        # Sets the window size to prevent stretching
        self.setFixedSize(self.width(), self.height())
        
        # Set the number of transmitted data and received data to zero
        self.data_num_sended = 0
        self.Lineedit2.setText(str(self.data_num_sended))
        self.data_num_received = 0
        self.Lineedit3.setText(str(self.data_num_received))

        # Serial port close button enable
        self.Pushbuttom3.setEnabled(False)

        # Clear sending box and text box
        self.Text1.setText("")
        self.Text2.setText("")
        
    # Establish the relationship between control signal and slot
    def init(self):
        # Serial port detection button
        self.Pushbuttom2.clicked.connect(self.port_check)
        # Serial port open button
        self.Pushbuttom1.clicked.connect(self.port_open)
        # Serial port close button
        self.Pushbuttom3.clicked.connect(self.port_close)

        # Send data regularly
        self.timer_send = QTimer()
        self.timer_send.timeout.connect(self.data_send)
        self.Checkbox7.stateChanged.connect(self.data_send_timer)
        
        # Send data button
        self.Pushbuttom6.clicked.connect(self.data_send)

        # Load log
        self.Pushbuttom4.clicked.connect(self.savefiles)
        # Load log
        self.Pushbuttom5.clicked.connect(self.openfiles)
        
        # Jump link
        self.commandLinkButton1.clicked.connect(self.link)

        # Clear send button
        self.Pushbuttom7.clicked.connect(self.send_data_clear)

        # Clear receive button
        self.Pushbuttom8.clicked.connect(self.receive_data_clear)

3.2 serial port detection procedure

Detect all serial ports on the computer, and the implementation code is as follows:

    # Serial port detection
    def port_check(self):
        # Detect all existing serial ports and store the information in the dictionary
        self.Com_Dict = {}
        port_list = list(serial.tools.list_ports.comports())
        
        self.Combobox1.clear()
        for port in port_list:
            self.Com_Dict["%s" % port[0]] = "%s" % port[1]
            self.Combobox1.addItem(port[0])
            
        # No serial port judgment
        if len(self.Com_Dict) == 0:
            self.Combobox1.addItem("No serial port")

3.3. Set and open serial port program

After detecting the serial port, configure it, open the serial port, and start the timer to always receive user input. The implementation code is as follows:

    # Open serial port
    def port_open(self):
        self.ser.port        = self.Combobox1.currentText()      # Serial port number
        self.ser.baudrate    = int(self.Combobox2.currentText()) # Baud rate

        flag_data = int(self.Combobox3.currentText())  # Data bit
        if flag_data == 5:
            self.ser.bytesize = serial.FIVEBITS
        elif flag_data == 6:
            self.ser.bytesize = serial.SIXBITS
        elif flag_data == 7:
            self.ser.bytesize = serial.SEVENBITS
        else:
            self.ser.bytesize = serial.EIGHTBITS

        flag_data = self.Combobox4.currentText()  # Check bit
        if flag_data == "None":
            self.ser.parity = serial.PARITY_NONE
        elif flag_data == "Odd":
            self.ser.parity = serial.PARITY_ODD
        else:
            self.ser.parity = serial.PARITY_EVEN

        flag_data = int(self.Combobox5.currentText()) # Stop bit
        if flag_data == 1:
            self.ser.stopbits = serial.STOPBITS_ONE
        else:
            self.ser.stopbits = serial.STOPBITS_TWO

        flag_data = self.Combobox6.currentText()  # Flow control
        if flag_data == "No Ctrl Flow":
            self.ser.xonxoff = False  #Software flow control
            self.ser.dsrdtr  = False  #Hardware flow control DTR
            self.ser.rtscts  = False  #Hardware flow control RTS
        elif flag_data == "SW Ctrl Flow":
            self.ser.xonxoff = True  #Software flow control
        else:         
            if self.Checkbox3.isChecked():
                self.ser.dsrdtr = True  #Hardware flow control DTR
            if self.Checkbox4.isChecked():
                self.ser.rtscts = True  #Hardware flow control RTS
        try:
            time.sleep(0.1)
            self.ser.open()
        except:
            QMessageBox.critical(self, "Serial port exception", "This serial port cannot be opened!")
            return None

        # After the serial port is opened, switch the enable state of the serial port button to prevent misoperation        
        if self.ser.isOpen():
            self.Pushbuttom1.setEnabled(False)
            self.Pushbuttom3.setEnabled(True)
            self.formGroupBox1.setTitle("Serial port status (on)")

        # Timer receiving data
        self.timer = QTimer()
        self.timer.timeout.connect(self.data_receive)
        # Open the serial port receiving timer with a cycle of 1ms
        self.timer.start(1)

3.4 program for sending data regularly

The timer can support data timing between 1ms and 30s, and the implementation code is as follows:

    # Send data regularly
    def data_send_timer(self):
        try:
            if 1<= int(self.Lineedit1.text()) <= 30000:  # Timing time within 1ms~30s
                if self.Checkbox7.isChecked():
                    self.timer_send.start(int(self.Lineedit1.text()))
                    self.Lineedit1.setEnabled(False)
                else:
                    self.timer_send.stop()
                    self.Lineedit1.setEnabled(True)
            else:
                QMessageBox.critical(self, 'Abnormal timing sending data', 'The periodic data transmission can only be set within 30 seconds!')
        except:
            QMessageBox.critical(self, 'Abnormal timing sending data', 'Please set the correct value type!')

3.5 data sending procedure

ASCII character and hexadecimal type data can be sent, and the sending time can be displayed in front of the data. Line feed is carried out after the data, and a byte is sent. The TX flag will be automatically accumulated. The implementation code is as follows:

    # send data
    def data_send(self):
        if self.ser.isOpen():
            input_s = self.Text2.toPlainText()

            # Determine whether it is a non empty string
            if input_s != "":
                # Time display
                if self.Checkbox5.isChecked():
                    self.Text1.insertPlainText((time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())) + " ")
                    
                # HEX send
                if self.Checkbox1.isChecked():  
                    input_s = input_s.strip()
                    send_list = []
                    while input_s != '':
                        try:
                            num = int(input_s[0:2], 16)
                        except ValueError:
                            QMessageBox.critical(self, 'Serial port exception', 'Please enter standard hexadecimal data, separated by spaces!')
                            return None
                        
                        input_s = input_s[2:].strip()
                        send_list.append(num)
                        
                    input_s = bytes(send_list)
                # ASCII send
                else:  
                    input_s = (input_s).encode('utf-8')
                    
                # HEX receive display
                if self.Checkbox2.isChecked():  
                    out_s = ''
                    for i in range(0, len(input_s)):
                        out_s = out_s + '{:02X}'.format(input_s[i]) + ' '
                        
                    self.Text1.insertPlainText(out_s)
                # ASCII receive display
                else:  
                    self.Text1.insertPlainText(input_s.decode('utf-8')) 

                # Receive newline              
                if self.Checkbox6.isChecked():
                    self.Text1.insertPlainText('\r\n')

                # Get Text cursor
                textCursor = self.Text1.textCursor()
                # Scroll to bottom
                textCursor.movePosition(textCursor.End)
                # Set the cursor to Text
                self.Text1.setTextCursor(textCursor)
            
                # Count the number of characters sent
                num = self.ser.write(input_s)
                self.data_num_sended += num
                self.Lineedit2.setText(str(self.data_num_sended))
        else:
            pass

3.6 data receiving procedure

ASCII character and hexadecimal data can be received, and the sending time can be displayed in front of the data. Line feed is carried out after the data, one byte is received, and the RX flag will be automatically accumulated. The implementation code is as follows:

    # receive data 
    def data_receive(self):
        try:
            num = self.ser.inWaiting()
            
            if num > 0:
                time.sleep(0.1)
                num = self.ser.inWaiting()  #Delay and read the data again to ensure data integrity
        except:
            QMessageBox.critical(self, 'Serial port exception', 'Serial port receiving data is abnormal, please reconnect the device!')
            self.port_close()
            return None
        
        if num > 0:
            data = self.ser.read(num)
            num = len(data)
            
            # Time display
            if self.Checkbox5.isChecked():
                self.Text1.insertPlainText((time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())) + " ")
                
            # HEX display data
            if self.Checkbox2.checkState():
                out_s = ''
                for i in range(0, len(data)):
                    out_s = out_s + '{:02X}'.format(data[i]) + ' '
                    
                self.Text1.insertPlainText(out_s)
            # ASCII display data
            else:
                self.Text1.insertPlainText(data.decode('utf-8'))

            # Receive newline              
            if self.Checkbox6.isChecked():
                self.Text1.insertPlainText('\r\n')
                    
            # Get text cursor
            textCursor = self.Text1.textCursor()
            # Scroll to bottom
            textCursor.movePosition(textCursor.End)
            # Set the cursor to text
            self.Text1.setTextCursor(textCursor)

            # Count the number of characters received
            self.data_num_received += num
            self.Lineedit3.setText(str(self.data_num_received))
        else:
            pass

3.7 log saving procedure

Save the data sent and received in the receiving box to TXT text, and the implementation code is as follows:

    # Save log
    def savefiles(self):
        dlg = QFileDialog()
        filenames = dlg.getSaveFileName(None, "Save log file", None, "Txt files(*.txt)")

        try:
            with open(file = filenames[0], mode='w', encoding='utf-8') as file:
                file.write(self.Text1.toPlainText())
        except:
            QMessageBox.critical(self, 'Log exception', 'Failed to save log file!')

3.8. Loading log program

Load the data information saved in TXT text into the send box, and the implementation code is as follows:

    # Load log
    def openfiles(self):
        dlg = QFileDialog()
        filenames = dlg.getOpenFileName(None, "Load log file", None, "Txt files(*.txt)")

        try:
            with open(file = filenames[0], mode='r', encoding='utf-8') as file:
                self.Text2.setPlainText(file.read())
        except:
            QMessageBox.critical(self, 'Log exception', 'Failed to load log file!')

3.9, open blog, official account program

Click the button to open my official account number two dimensional code and blog homepage.

    # Open blog links and official account number two dimensional code.
    def link(self):
        dialog = QDialog()
        label_img = QLabel()
            
        label_img.setAlignment(Qt.AlignCenter)    
        label_img.setPixmap(QPixmap("./img.jpg"))

        vbox = QVBoxLayout()
        vbox.addWidget(label_img)
        dialog.setLayout(vbox)
        
        dialog.setWindowTitle("Fast scan code, pay attention to the official account.~")
        dialog.setWindowModality(Qt.ApplicationModal)
        dialog.exec_()
		
        webbrowser.open('https://blog.csdn.net/m0_38106923')

3.10 clear the sending and receiving data display program

Clear the contents and count times of sending data frame and receiving data frame. The implementation code is as follows:

    # Clear send data display
    def send_data_clear(self):
        self.Text2.setText("")

        self.data_num_sended = 0
        self.Lineedit2.setText(str(self.data_num_sended))

    # Clear received data display
    def receive_data_clear(self):
        self.Text1.setText("")

        self.data_num_received = 0
        self.Lineedit3.setText(str(self.data_num_received))

3.11. Close the serial port program

Close the serial port, stop the timer, reset the component and flag status, and the implementation code is as follows:

    # Close the serial port
    def port_close(self):
        try:
            self.timer.stop()
            self.timer_send.stop()
            
            self.ser.close()
        except:
            QMessageBox.critical(self, 'Serial port exception', 'Failed to close the serial port, please restart the program!')
            return None

        # Switch the serial port button enable status and timing transmission enable status
        self.Pushbuttom1.setEnabled(True)
        self.Pushbuttom3.setEnabled(False)
        self.Lineedit1.setEnabled(True)
        
        # Set the number of transmitted data and received data to zero
        self.data_num_sended = 0
        self.Lineedit2.setText(str(self.data_num_sended))
        self.data_num_received = 0
        self.Lineedit3.setText(str(self.data_num_received))
        
        self.formGroupBox1.setTitle("Serial port status (off)")

Keywords: Python Qt pyqt

Added by sebastiaandraaisma on Wed, 05 Jan 2022 06:18:01 +0200