Python Qt GUI设计:做一款串口调试助手(实战篇—1)

目录

1、UI设计

2、将UI文件转换为Py文件

3、逻辑功能实现

3.1、初始化程序

3.2、串口检测程序

3.3、 设置及打开串口程序

3.4、定时发送数据程序

3.5、发送数据程序

3.6、接收数据程序

3.7、保存日志程序

3.8、加载日志程序

3.9、打开博客、公众号程序

3.10、清除发送和接收数据显示程序

3.11、关闭串口程序


Python Qt GUI设计系列博文终于到了实战篇,本篇博文将贯穿之前的基础知识点实现一款串口调试助手。

关注【公众号】 美男子玩编程,回复关键字:串口调试助手,获取项目源码~

1、UI设计

UI设计使用Qt Creator实现,组件布局如下所示:

2、将UI文件转换为Py文件

这里使用Python脚本的方式将UI文件转换为Python文件,代码如下所示:

  1. import os
  2. import os.path
  3. dir ='./' #文件所在的路径
  4. #找出路径下所有的.ui文件
  5. def listUiFile():
  6. list = []
  7. files = os.listdir(dir)
  8. for filename in files:
  9. #print(filename)
  10. if os.path.splitext(filename)[1] == '.ui':
  11. list.append(filename)
  12. return list
  13. #把扩展名未.ui的转换成.py的文件
  14. def transPyFile(filename):
  15. return os.path.splitext(filename)[0] + '.py'
  16. #通过命令把.ui文件转换成.py文件
  17. def runMain():
  18. list = listUiFile()
  19. for uifile in list:
  20. pyfile = transPyFile(uifile)
  21. cmd = 'pyuic5 -o {pyfile} {uifile}'.format(pyfile=pyfile, uifile=uifile)
  22. os.system(cmd)
  23. if __name__ =="__main__":
  24. runMain()

3、逻辑功能实现

3.1、初始化程序

首先初始化一些组件和标志位的状态,设置信号与槽的关系,实现代码如下所示:

  1. # 初始化程序
  2. def __init__(self):
  3. super(Pyqt5_Serial, self).__init__()
  4. self.setupUi(self)
  5. self.init()
  6. self.ser = serial.Serial()
  7. self.port_check()
  8. # 设置Logo和标题
  9. self.setWindowIcon(QIcon('Com.png'))
  10. self.setWindowTitle("串口调试助手 【公众号】美男子玩编程")
  11. # 设置禁止拉伸窗口大小
  12. self.setFixedSize(self.width(), self.height())
  13. # 发送数据和接收数据数目置零
  14. self.data_num_sended = 0
  15. self.Lineedit2.setText(str(self.data_num_sended))
  16. self.data_num_received = 0
  17. self.Lineedit3.setText(str(self.data_num_received))
  18. # 串口关闭按钮使能关闭
  19. self.Pushbuttom3.setEnabled(False)
  20. # 发送框、文本框清除
  21. self.Text1.setText("")
  22. self.Text2.setText("")
  23. # 建立控件信号与槽关系
  24. def init(self):
  25. # 串口检测按钮
  26. self.Pushbuttom2.clicked.connect(self.port_check)
  27. # 串口打开按钮
  28. self.Pushbuttom1.clicked.connect(self.port_open)
  29. # 串口关闭按钮
  30. self.Pushbuttom3.clicked.connect(self.port_close)
  31. # 定时发送数据
  32. self.timer_send = QTimer()
  33. self.timer_send.timeout.connect(self.data_send)
  34. self.Checkbox7.stateChanged.connect(self.data_send_timer)
  35. # 发送数据按钮
  36. self.Pushbuttom6.clicked.connect(self.data_send)
  37. # 加载日志
  38. self.Pushbuttom4.clicked.connect(self.savefiles)
  39. # 加载日志
  40. self.Pushbuttom5.clicked.connect(self.openfiles)
  41. # 跳转链接
  42. self.commandLinkButton1.clicked.connect(self.link)
  43. # 清除发送按钮
  44. self.Pushbuttom7.clicked.connect(self.send_data_clear)
  45. # 清除接收按钮
  46. self.Pushbuttom8.clicked.connect(self.receive_data_clear)

3.2、串口检测程序

检测电脑上所有串口,实现代码如下所示:

  1. # 串口检测
  2. def port_check(self):
  3. # 检测所有存在的串口,将信息存储在字典中
  4. self.Com_Dict = {}
  5. port_list = list(serial.tools.list_ports.comports())
  6. self.Combobox1.clear()
  7. for port in port_list:
  8. self.Com_Dict["%s" % port[0]] = "%s" % port[1]
  9. self.Combobox1.addItem(port[0])
  10. # 无串口判断
  11. if len(self.Com_Dict) == 0:
  12. self.Combobox1.addItem("无串口")

3.3、 设置及打开串口程序

检测到串口后进行配置,打开串口,并且启动定时器一直接收用户输入,实现代码如下所示:

  1. # 打开串口
  2. def port_open(self):
  3. self.ser.port = self.Combobox1.currentText() # 串口号
  4. self.ser.baudrate = int(self.Combobox2.currentText()) # 波特率
  5. flag_data = int(self.Combobox3.currentText()) # 数据位
  6. if flag_data == 5:
  7. self.ser.bytesize = serial.FIVEBITS
  8. elif flag_data == 6:
  9. self.ser.bytesize = serial.SIXBITS
  10. elif flag_data == 7:
  11. self.ser.bytesize = serial.SEVENBITS
  12. else:
  13. self.ser.bytesize = serial.EIGHTBITS
  14. flag_data = self.Combobox4.currentText() # 校验位
  15. if flag_data == "None":
  16. self.ser.parity = serial.PARITY_NONE
  17. elif flag_data == "Odd":
  18. self.ser.parity = serial.PARITY_ODD
  19. else:
  20. self.ser.parity = serial.PARITY_EVEN
  21. flag_data = int(self.Combobox5.currentText()) # 停止位
  22. if flag_data == 1:
  23. self.ser.stopbits = serial.STOPBITS_ONE
  24. else:
  25. self.ser.stopbits = serial.STOPBITS_TWO
  26. flag_data = self.Combobox6.currentText() # 流控
  27. if flag_data == "No Ctrl Flow":
  28. self.ser.xonxoff = False #软件流控
  29. self.ser.dsrdtr = False #硬件流控 DTR
  30. self.ser.rtscts = False #硬件流控 RTS
  31. elif flag_data == "SW Ctrl Flow":
  32. self.ser.xonxoff = True #软件流控
  33. else:
  34. if self.Checkbox3.isChecked():
  35. self.ser.dsrdtr = True #硬件流控 DTR
  36. if self.Checkbox4.isChecked():
  37. self.ser.rtscts = True #硬件流控 RTS
  38. try:
  39. time.sleep(0.1)
  40. self.ser.open()
  41. except:
  42. QMessageBox.critical(self, "串口异常", "此串口不能被打开!")
  43. return None
  44. # 串口打开后,切换开关串口按钮使能状态,防止失误操作
  45. if self.ser.isOpen():
  46. self.Pushbuttom1.setEnabled(False)
  47. self.Pushbuttom3.setEnabled(True)
  48. self.formGroupBox1.setTitle("串口状态(开启)")
  49. # 定时器接收数据
  50. self.timer = QTimer()
  51. self.timer.timeout.connect(self.data_receive)
  52. # 打开串口接收定时器,周期为1ms
  53. self.timer.start(1)

3.4、定时发送数据程序

通过定时器,可支持1ms至30s之间数据定时,实现代码如下所示:

  1. # 定时发送数据
  2. def data_send_timer(self):
  3. try:
  4. if 1<= int(self.Lineedit1.text()) <= 30000: # 定时时间1ms~30s内
  5. if self.Checkbox7.isChecked():
  6. self.timer_send.start(int(self.Lineedit1.text()))
  7. self.Lineedit1.setEnabled(False)
  8. else:
  9. self.timer_send.stop()
  10. self.Lineedit1.setEnabled(True)
  11. else:
  12. QMessageBox.critical(self, '定时发送数据异常', '定时发送数据周期仅可设置在30秒内!')
  13. except:
  14. QMessageBox.critical(self, '定时发送数据异常', '请设置正确的数值类型!')

3.5、发送数据程序

可以发送ASCII字符和十六进制类型数据,并且可以在数据前显示发送的时间,在数据后进行换行,发送一个字节,TX标志会自动累加,实现代码如下所示:

  1. # 发送数据
  2. def data_send(self):
  3. if self.ser.isOpen():
  4. input_s = self.Text2.toPlainText()
  5. # 判断是否为非空字符串
  6. if input_s != "":
  7. # 时间显示
  8. if self.Checkbox5.isChecked():
  9. self.Text1.insertPlainText((time.strftime("%Y-%m-%d %H:%M:%S",
  10. time.localtime())) + " ")
  11. # HEX发送
  12. if self.Checkbox1.isChecked():
  13. input_s = input_s.strip()
  14. send_list = []
  15. while input_s != '':
  16. try:
  17. num = int(input_s[0:2], 16)
  18. except ValueError:
  19. QMessageBox.critical(self, '串口异常', '请输入规范十六进制
  20. 数据,以空格分开!')
  21. return None
  22. input_s = input_s[2:].strip()
  23. send_list.append(num)
  24. input_s = bytes(send_list)
  25. # ASCII发送
  26. else:
  27. input_s = (input_s).encode('utf-8')
  28. # HEX接收显示
  29. if self.Checkbox2.isChecked():
  30. out_s = ''
  31. for i in range(0, len(input_s)):
  32. out_s = out_s + '{:02X}'.format(input_s[i]) + ' '
  33. self.Text1.insertPlainText(out_s)
  34. # ASCII接收显示
  35. else:
  36. self.Text1.insertPlainText(input_s.decode('utf-8'))
  37. # 接收换行
  38. if self.Checkbox6.isChecked():
  39. self.Text1.insertPlainText('\r\n')
  40. # 获取到Text光标
  41. textCursor = self.Text1.textCursor()
  42. # 滚动到底部
  43. textCursor.movePosition(textCursor.End)
  44. # 设置光标到Text中去
  45. self.Text1.setTextCursor(textCursor)
  46. # 统计发送字符数量
  47. num = self.ser.write(input_s)
  48. self.data_num_sended += num
  49. self.Lineedit2.setText(str(self.data_num_sended))
  50. else:
  51. pass

3.6、接收数据程序

可以接收ASCII字符和十六进制类型数据,并且可以在数据前显示发送的时间,在数据后进行换行,接收一个字节,RX标志会自动累加,实现代码如下所示:

  1. # 接收数据
  2. def data_receive(self):
  3. try:
  4. num = self.ser.inWaiting()
  5. if num > 0:
  6. time.sleep(0.1)
  7. num = self.ser.inWaiting() #延时,再读一次数据,确保数据完整性
  8. except:
  9. QMessageBox.critical(self, '串口异常', '串口接收数据异常,请重新连接设备!')
  10. self.port_close()
  11. return None
  12. if num > 0:
  13. data = self.ser.read(num)
  14. num = len(data)
  15. # 时间显示
  16. if self.Checkbox5.isChecked():
  17. self.Text1.insertPlainText((time.strftime("%Y-%m-%d %H:%M:%S",
  18. time.localtime())) + " ")
  19. # HEX显示数据
  20. if self.Checkbox2.checkState():
  21. out_s = ''
  22. for i in range(0, len(data)):
  23. out_s = out_s + '{:02X}'.format(data[i]) + ' '
  24. self.Text1.insertPlainText(out_s)
  25. # ASCII显示数据
  26. else:
  27. self.Text1.insertPlainText(data.decode('utf-8'))
  28. # 接收换行
  29. if self.Checkbox6.isChecked():
  30. self.Text1.insertPlainText('\r\n')
  31. # 获取到text光标
  32. textCursor = self.Text1.textCursor()
  33. # 滚动到底部
  34. textCursor.movePosition(textCursor.End)
  35. # 设置光标到text中去
  36. self.Text1.setTextCursor(textCursor)
  37. # 统计接收字符的数量
  38. self.data_num_received += num
  39. self.Lineedit3.setText(str(self.data_num_received))
  40. else:
  41. pass

3.7、保存日志程序

将接收框中收发的数据保存到TXT文本中,实现代码如下所示:

  1. # 保存日志
  2. def savefiles(self):
  3. dlg = QFileDialog()
  4. filenames = dlg.getSaveFileName(None, "保存日志文件", None, "Txt files(*.txt)")
  5. try:
  6. with open(file = filenames[0], mode='w', encoding='utf-8') as file:
  7. file.write(self.Text1.toPlainText())
  8. except:
  9. QMessageBox.critical(self, '日志异常', '保存日志文件失败!')

3.8、加载日志程序

加载保存到TXT文本中的数据信息到发送框中,实现代码如下所示:

  1. # 加载日志
  2. def openfiles(self):
  3. dlg = QFileDialog()
  4. filenames = dlg.getOpenFileName(None, "加载日志文件", None, "Txt files(*.txt)")
  5. try:
  6. with open(file = filenames[0], mode='r', encoding='utf-8') as file:
  7. self.Text2.setPlainText(file.read())
  8. except:
  9. QMessageBox.critical(self, '日志异常', '加载日志文件失败!')

3.9、打开博客、公众号程序

点击按钮,打开我的公众号二维码和博客主页,实现代码如下所示:

  1. # 打开博客链接和公众号二维码
  2. def link(self):
  3. dialog = QDialog()
  4. label_img = QLabel()
  5. label_img.setAlignment(Qt.AlignCenter)
  6. label_img.setPixmap(QPixmap("./img.jpg"))
  7. vbox = QVBoxLayout()
  8. vbox.addWidget(label_img)
  9. dialog.setLayout(vbox)
  10. dialog.setWindowTitle("快扫码关注公众号吧~")
  11. dialog.setWindowModality(Qt.ApplicationModal)
  12. dialog.exec_()
  13. webbrowser.open('https://blog.csdn.net/m0_38106923')

3.10、清除发送和接收数据显示程序

清除发送数据框和接收数据框的内容和计数次数,实现代码如下所示:

  1. # 清除发送数据显示
  2. def send_data_clear(self):
  3. self.Text2.setText("")
  4. self.data_num_sended = 0
  5. self.Lineedit2.setText(str(self.data_num_sended))
  6. # 清除接收数据显示
  7. def receive_data_clear(self):
  8. self.Text1.setText("")
  9. self.data_num_received = 0
  10. self.Lineedit3.setText(str(self.data_num_received))

3.11、关闭串口程序

关闭串口,停止定时器,重置组件和标志状态,实现代码如下所示:

  1. # 关闭串口
  2. def port_close(self):
  3. try:
  4. self.timer.stop()
  5. self.timer_send.stop()
  6. self.ser.close()
  7. except:
  8. QMessageBox.critical(self, '串口异常', '关闭串口失败,请重启程序!')
  9. return None
  10. # 切换开关串口按钮使能状态和定时发送使能状态
  11. self.Pushbuttom1.setEnabled(True)
  12. self.Pushbuttom3.setEnabled(False)
  13. self.Lineedit1.setEnabled(True)
  14. # 发送数据和接收数据数目置零
  15. self.data_num_sended = 0
  16. self.Lineedit2.setText(str(self.data_num_sended))
  17. self.data_num_received = 0
  18. self.Lineedit3.setText(str(self.data_num_received))
  19. self.formGroupBox1.setTitle("串口状态(关闭)")

关注公众号,发送关键字:Java车牌识别,获取项目源码。