当前位置:   article > 正文

【博客8】缤果PyQt5串口调试助手V1.1(初级篇)_串口调试助手 py源码

串口调试助手 py源码

超级好用的Python QT GUI串口调试助手


目录

前言

一、软件概要:

二、软件界面:

1.App动态演示

2.其他扩展展示

三、main.py源码:

1.PyQt5_Serial_Debug_Assistant_V1.0源码

四、获取 >> 源码以及Git记录:

总结


前言

        Python串口调试助手支持常用的50bps - 10Mbps波特率,能设置校验、数据位和停止位,能以ASCII码或十六进制接收或发送任何数据或字符,可以任意设定自动发送周期,并能将接收数据实时保存成文本文件,能发送任意大小的数据或字符。

备注: V1.0为简单Demo,适合初级用户使用,V1.1可用于工程应用

高级版: 【博客9】缤果PyQt5串口调试助手V2.0(高级篇)_pyqt 串口助手_Bingo缤果的博客-CSDN博客


英文名:PyQt5_Serial_Debug_Assistant_V1.1
支持:常用的50bps ~ 10Mbps波特率
类型:串口调试助手
软件大小:18M
软件版本:V1.1
软件下载&更新:百度网盘链接_提取码 6666

一、软件概要:

        一款强大而稳定的PyQt串口调试助手,支持常用的110-921600bps波特率及自定义波特率,波特率最高可支持8000000(串口硬件有关),可适应于非标准波特率。支持动态修改串口和波特率,保存当前日志,打开当前日志以及实时保存日志(默认按每小时分包保存日志,100M自动清空接收窗口,防止UI阻塞)

二、软件界面:

1.App动态演示

2.其他扩展展示

 PyQt5_Serial_Debug_Assistant_V1.1

 PyQt5_Serial_Debug_Assistant_V1.1_串口UI布局

 PyQt5_Serial_Debug_Assistant_V1.0

三、main.py源码:

1.PyQt5_Serial_Debug_Assistant_V1.0源码

代码如下(示例):

  1. import sys
  2. import serial # 导入模块 #安装: pip3 install pyserial
  3. import serial.tools.list_ports
  4. import webbrowser
  5. import time
  6. import datetime
  7. # 导入Ui设计
  8. from PyQt5.QtWidgets import QApplication, QMainWindow # 串口Ui文件
  9. from PyQt5.QtWidgets import QMessageBox, QLabel, QFileDialog
  10. from PyQt5.QtCore import QTimer
  11. from PyQt5.QtGui import QIcon
  12. import Serial_Ui_Designer # 串口UI文件
  13. # PyQt5程序打包
  14. # 1.使用PyInstaller来打包:
  15. # 安装:pip3 install PyInstaller
  16. # 打包:pyinstaller -F -w -i=Com.ico main.py #单文件打包-优缺点:只生成exe文件,但文件大,打开软件时加载时间长
  17. # 打包:pyinstaller -D -w -i=Com.ico main.py #多文件打包-优缺点:生成exe关联包,可删除无效库,文件小,运行顺畅
  18. # 主窗口
  19. class PyQt5_Serial(QMainWindow, Serial_Ui_Designer.Ui_MainWindow):
  20. # 初始化程序
  21. def __init__(self):
  22. super(PyQt5_Serial, self).__init__()
  23. self.setupUi(self)
  24. self.Init()
  25. self.Qt5_Ui_Init()
  26. # 初始化
  27. def Init(self):
  28. self.ser = serial.Serial()
  29. self.port_check()
  30. self.baudrateBox.setCurrentIndex(5) # 921600-20 9600-5
  31. self.dataBitsBox.setCurrentIndex(3) # 8
  32. # 设置Logo和标题
  33. self.setWindowIcon(QIcon('Com.ico'))
  34. self.setWindowTitle("PyQt5_串口调试助手_V1.0")
  35. #self.setWindowTitle("PyQt5_Serial_Debug_Assistant_V1.0")
  36. # 发送数据和接收数据数目置零
  37. self.data_num_sended = 0
  38. self.label_Tx.setText(str(self.data_num_sended))
  39. self.data_num_received = 0
  40. self.label_Rx.setText(str(self.data_num_received))
  41. # 串口关闭按钮使能关闭
  42. self.sendButton.setEnabled(0)
  43. self.checkBox_autoSend.setEnabled(0)
  44. # 发送框、文本框清除
  45. self.sendTextEdit.setText("")
  46. self.recvTextEdit.setText("")
  47. # 建立控件信号与槽关系
  48. def Qt5_Ui_Init(self):
  49. # 串口检测按钮
  50. self.pushButton_Refresh.clicked.connect(self.port_check)
  51. # 串口打开按钮
  52. self.pushButton_Open_Close.clicked.connect(self.port_open_close)
  53. # 定时发送数据
  54. self.timer_send = QTimer()
  55. self.timer_send.timeout.connect(self.data_send)
  56. self.checkBox_autoSend.stateChanged.connect(self.data_send_timer)
  57. # 发送数据按钮
  58. self.sendButton.clicked.connect(self.data_send)
  59. # 保存日志
  60. self.pushButton_saveLog.clicked.connect(self.savefiles)
  61. # 加载文件
  62. self.pushButton_openLog.clicked.connect(self.openfiles)
  63. # 跳转链接
  64. self.commandLinkButton.clicked.connect(self.link)
  65. # 清除发送按钮
  66. self.pushButton_ClearSend.clicked.connect(self.send_data_clear)
  67. # 清除接收按钮
  68. self.pushButton_ClearRecive.clicked.connect(self.receive_data_clear)
  69. # RTS
  70. self.checkBox_RTS.clicked.connect(self.rts_handle)
  71. # DTR
  72. self.checkBox_DTR.clicked.connect(self.dtr_handle)
  73. # 串口检测
  74. def port_check(self):
  75. # 检测所有存在的串口,将信息存储在字典中
  76. self.Com_Dict = {}
  77. port_list = list(serial.tools.list_ports.comports())
  78. self.portNameBox.clear()
  79. for port in port_list:
  80. self.Com_Dict["%s" % port[0]] = "%s" % port[1]
  81. self.portNameBox.addItem(port[0])
  82. # 无串口判断
  83. if len(self.Com_Dict) == 0:
  84. self.portNameBox.addItem("无串口")
  85. # 打开/关闭串口
  86. def port_open_close(self):
  87. if self.pushButton_Open_Close.text() == "打开串口":
  88. self.port_open()
  89. else:
  90. self.port_close()
  91. # 打开串口
  92. def port_open(self):
  93. port = self.portNameBox.currentText()
  94. # print("port:", port)
  95. baudrate = int(self.baudrateBox.currentText())
  96. # print("baudrate:", baudrate)
  97. bytesize = int(self.dataBitsBox.currentText()) # 数据位
  98. # print("bytesize:", bytesize)
  99. parity = self.ParityBox.currentText() # 校验位
  100. # print("parity:", parity)
  101. stopbits = self.stopBitsBox.currentText() # 停止位
  102. # print("stopbits:", stopbits)
  103. flowctrl = self.flowControlBox.currentText() # 流控
  104. self.ser.port = port
  105. self.ser.baudrate = baudrate
  106. # print("bytesize:", bytesize)
  107. if bytesize == 5:
  108. self.ser.bytesize = serial.FIVEBITS
  109. elif bytesize == 6:
  110. self.ser.bytesize = serial.SIXBITS
  111. elif bytesize == 7:
  112. self.ser.bytesize = serial.SEVENBITS
  113. elif bytesize == 8:
  114. self.ser.bytesize = serial.EIGHTBITS
  115. else:
  116. self.ser.bytesize = serial.EIGHTBITS
  117. # print("parity:", parity)
  118. if parity == "None":
  119. self.ser.parity = serial.PARITY_NONE
  120. elif parity == "Even":
  121. self.ser.parity = serial.PARITY_EVEN
  122. elif parity == "Odd":
  123. self.ser.parity = serial.PARITY_ODD
  124. elif parity == "Space":
  125. self.ser.parity = serial.PARITY_SPACE
  126. elif parity == "Mark":
  127. self.ser.parity = serial.PARITY_MARK
  128. else:
  129. self.ser.parity = serial.PARITY_NONE
  130. # print("stopbits:", stopbits)
  131. if stopbits == "1":
  132. self.ser.stopbits = serial.STOPBITS_ONE
  133. elif stopbits == "1.5":
  134. self.ser.parity = serial.STOPBITS_ONE_POINT_FIVE
  135. elif stopbits == "2":
  136. self.ser.parity = serial.STOPBITS_TWO
  137. else:
  138. self.ser.stopbits = serial.STOPBITS_ONE
  139. self.ser.xonxoff = False # 软件流控
  140. self.ser.rtscts = False # 硬件流控 RTS
  141. self.ser.dsrdtr = False # 硬件流控 DTR
  142. # print("flowctrl:", flowctrl)
  143. if flowctrl == "OFF":
  144. self.ser.xonxoff = False # 软件流控
  145. self.ser.rtscts = False # 硬件流控 RTS
  146. self.ser.dsrdtr = False # 硬件流控 DTR
  147. elif flowctrl == "Hardware":
  148. if self.checkBox_DTR.isChecked():
  149. self.ser.dsrdtr = True #硬件流控 DTR
  150. if self.checkBox_RTS.isChecked():
  151. self.ser.rtscts = True #硬件流控 RTS
  152. elif flowctrl == "Software":
  153. self.ser.xonxoff = True # 软件流控
  154. self.ser.rtscts = False # 硬件流控 RTS
  155. self.ser.dsrdtr = False # 硬件流控 DTR
  156. # print(self.ser)
  157. # Serial < id = 0x4883040, open = False > (port='COM1', baudrate=9600, bytesize=8, parity='N', stopbits=1, timeout=None, xonxoff=False, rtscts=False, dsrdtr=False)
  158. try:
  159. self.ser.open()
  160. except:
  161. QMessageBox.critical(self, "串口异常", "串口打开失败! 错误: 拒绝访问(被占用).")
  162. return None
  163. # 串口打开后,切换开关串口按钮使能状态,防止失误操作
  164. if self.ser.isOpen():
  165. self.pushButton_Open_Close.setText("关闭串口")
  166. self.portNameBox.setEnabled(0)
  167. self.baudrateBox.setEnabled(0)
  168. self.dataBitsBox.setEnabled(0)
  169. self.ParityBox.setEnabled(0)
  170. self.stopBitsBox.setEnabled(0)
  171. self.flowControlBox.setEnabled(0)
  172. self.pushButton_Refresh.setEnabled(0)
  173. self.sendButton.setEnabled(1)
  174. self.checkBox_autoSend.setEnabled(1)
  175. # 定时器接收数据
  176. self.timer = QTimer()
  177. self.timer.timeout.connect(self.data_receive)
  178. # 打开串口接收定时器,周期为1ms
  179. self.timer.start(1)
  180. # 关闭串口
  181. def port_close(self):
  182. try:
  183. self.timer.stop()
  184. self.timer_send.stop()
  185. self.ser.close()
  186. self.pushButton_Open_Close.setText("打开串口")
  187. self.portNameBox.setEnabled(1)
  188. self.baudrateBox.setEnabled(1)
  189. self.dataBitsBox.setEnabled(1)
  190. self.ParityBox.setEnabled(1)
  191. self.stopBitsBox.setEnabled(1)
  192. self.flowControlBox.setEnabled(1)
  193. self.pushButton_Refresh.setEnabled(1)
  194. self.sendButton.setEnabled(0)
  195. self.checkBox_autoSend.setEnabled(0)
  196. except:
  197. QMessageBox.critical(self, '串口异常', '关闭串口失败,请重启程序!')
  198. return None
  199. # 定时发送数据
  200. def data_send_timer(self):
  201. try:
  202. if 1<= int(self.spinBox_timeDly.text()) <= 300000: # 定时时间1ms~30s内
  203. if self.checkBox_autoSend.isChecked():
  204. self.timer_send.start(int(self.spinBox_timeDly.text()))
  205. self.spinBox_timeDly.setEnabled(False)
  206. else:
  207. self.timer_send.stop()
  208. self.spinBox_timeDly.setEnabled(True)
  209. else:
  210. QMessageBox.critical(self, '定时发送数据异常', '定时发送数据周期仅可设置在300秒内!')
  211. except:
  212. QMessageBox.critical(self, '定时发送数据异常', '请设置正确的数值类型!')
  213. # 发送数据
  214. def data_send(self):
  215. if self.ser.isOpen():
  216. input_s = self.sendTextEdit.toPlainText()
  217. # 判断是否为非空字符串
  218. if input_s != "":
  219. # 时间显示
  220. if self.checkBox_displayTime.isChecked():
  221. if self.checkBox_displaySend.isChecked():
  222. self.recvTextEdit.insertPlainText(self.get_datetime())
  223. # HEX发送
  224. if self.checkBox_hexSend.isChecked():
  225. #input_s = input_s.strip() #strip() 方法用于移除字符串头尾指定的字符(默认为空格或换行符)或字符序列。注意:该方法只能删除开头或是结尾的字符,不能删除中间部分的字符。
  226. input_s = input_s.replace(" ", "")
  227. send_list = []
  228. while input_s != '':
  229. try:
  230. num = int(input_s[0:2], 16) # 没有步长的简单切片
  231. #print( hex(num) )
  232. except ValueError:
  233. QMessageBox.critical(self, '数据异常', '请输入规范的十六进制数据!')
  234. return None
  235. input_s = input_s[2:]
  236. send_list.append(num)
  237. if self.checkBox_CR_LF.isChecked():
  238. send_list.append(0x0D)
  239. send_list.append(0x0A)
  240. input_s = bytes(send_list)
  241. # ASCII发送
  242. else:
  243. if self.checkBox_CR_LF.isChecked():
  244. input_s += '\r\n'
  245. input_s = (input_s).encode('utf-8')
  246. # HEX接收显示
  247. if self.checkBox_hexReceive.isChecked():
  248. out_s = ''
  249. for i in range(0, len(input_s)):
  250. out_s = out_s + '{:02X}'.format(input_s[i]) + ' '
  251. if self.checkBox_displaySend.isChecked():
  252. self.recvTextEdit.insertPlainText(out_s)
  253. # ASCII接收显示
  254. else:
  255. if self.checkBox_displaySend.isChecked():
  256. self.recvTextEdit.insertPlainText(input_s.decode('utf-8'))
  257. # 接收换行
  258. if self.checkBox_AutoLineBreak.isChecked():
  259. if self.checkBox_displaySend.isChecked():
  260. self.recvTextEdit.insertPlainText('\r\n')
  261. # 获取到Text光标
  262. textCursor = self.recvTextEdit.textCursor()
  263. # 滚动到底部
  264. textCursor.movePosition(textCursor.End)
  265. # 设置光标到Text中去
  266. self.recvTextEdit.setTextCursor(textCursor)
  267. # 统计发送字符数量
  268. num = self.ser.write(input_s)
  269. self.data_num_sended += num
  270. self.label_Tx.setText(str(self.data_num_sended))
  271. else:
  272. pass
  273. # 接收数据
  274. def data_receive(self):
  275. try:
  276. num = self.ser.inWaiting()
  277. # if num > 0:
  278. # time.sleep(0.1) #100ms
  279. # num = self.ser.inWaiting() # 延时,再读一次数据,确保数据完整性
  280. except:
  281. # QMessageBox.critical(self, '串口异常', '串口接收数据异常,请重新连接设备!')
  282. # self.port_close()
  283. return None
  284. if num > 0:
  285. data = self.ser.read(num)
  286. num = len(data)
  287. # HEX显示数据
  288. if self.checkBox_hexReceive.checkState():
  289. # 时间显示
  290. if self.checkBox_displayTime.isChecked():
  291. self.recvTextEdit.insertPlainText(self.get_datetime())
  292. out_s = ''
  293. for i in range(0, len(data)):
  294. out_s = out_s + '{:02X}'.format(data[i]) + ' '
  295. self.recvTextEdit.insertPlainText(out_s)
  296. # 接收换行
  297. if self.checkBox_AutoLineBreak.isChecked():
  298. self.recvTextEdit.insertPlainText('\r\n')
  299. # ASCII显示数据
  300. else:
  301. try:
  302. if self.checkBox_displayTime.isChecked():
  303. displayStr = self.get_datetime()
  304. displayStr += data.decode('utf-8',"ignore")
  305. displayStr = displayStr.replace("\n", "\n" + self.get_datetime())
  306. # 接收换行
  307. if self.checkBox_AutoLineBreak.isChecked():
  308. displayStr += "\r\n" # 接收换行
  309. self.recvTextEdit.insertPlainText(displayStr)
  310. else:
  311. self.recvTextEdit.insertPlainText(data.decode('utf-8',"ignore"))
  312. # 接收换行
  313. if self.checkBox_AutoLineBreak.isChecked():
  314. self.recvTextEdit.insertPlainText('\r\n')
  315. except Exception as e:
  316. print("接收数据异常,波特率错误,请重新配置!\n", e)
  317. # 获取到text光标
  318. textCursor = self.recvTextEdit.textCursor()
  319. # 滚动到底部
  320. textCursor.movePosition(textCursor.End)
  321. # 设置光标到text中去
  322. self.recvTextEdit.setTextCursor(textCursor)
  323. # 统计接收字符的数量
  324. self.data_num_received += num
  325. self.label_Rx.setText(str(self.data_num_received))
  326. else:
  327. pass
  328. # 保存日志
  329. def savefiles(self):
  330. dlg = QFileDialog()
  331. filename = self.portNameBox.currentText() + time.strftime("_%Y-%m-%d_%H_%M_%S", time.localtime())
  332. filenames = dlg.getSaveFileName(None, "保存日志文件", filename, "Txt files(*.txt)")
  333. try:
  334. with open(file = filenames[0], mode='w', encoding='utf-8') as file:
  335. file.write(self.recvTextEdit.toPlainText())
  336. except:
  337. #QMessageBox.critical(self, '日志异常', '保存日志文件失败!')
  338. pass
  339. # 加载日志
  340. def openfiles(self):
  341. dlg = QFileDialog()
  342. filenames = dlg.getOpenFileName(None, "加载日志文件", None, "Txt files(*.txt)")
  343. try:
  344. with open(file = filenames[0], mode='r', encoding='utf-8') as file:
  345. self.sendTextEdit.setPlainText(file.read())
  346. except:
  347. # QMessageBox.critical(self, '日志异常', '加载日志文件失败!')
  348. pass
  349. # 打开博客链接
  350. def link(self):
  351. webbrowser.open('https://blog.csdn.net/santu5234?type=blog')
  352. # 清除发送数据显示
  353. def send_data_clear(self):
  354. self.sendTextEdit.setText("")
  355. self.data_num_sended = 0
  356. self.label_Tx.setText(str(self.data_num_sended))
  357. # 清除接收数据显示
  358. def receive_data_clear(self):
  359. self.recvTextEdit.setText("")
  360. self.data_num_received = 0
  361. self.label_Rx.setText(str(self.data_num_received))
  362. self.data_num_sended = 0
  363. self.label_Tx.setText(str(self.data_num_sended))
  364. # 时间格式
  365. def get_datetime(self):
  366. time_now = datetime.datetime.now()
  367. # print(str(time_now)[:-3])
  368. time_now = "[" + str(time_now)[:-3] + "] " # 转为字符串后切片
  369. return time_now
  370. # RTS
  371. def rts_handle(self):
  372. if self.ser.isOpen():
  373. if self.checkBox_RTS.isChecked():
  374. self.ser.setRTS(1)
  375. else:
  376. self.ser.setRTS(0)
  377. # DTR
  378. def dtr_handle(self):
  379. if self.ser.isOpen():
  380. if self.checkBox_DTR.isChecked():
  381. self.ser.setDTR(1)
  382. else:
  383. self.ser.setDTR(0)
  384. # 主函数
  385. def main():
  386. print("Hello, I'm PyQt5_Serial_Debug_Assistant_V1.0")
  387. # 1、创建QApplication类的实例对象
  388. app = QApplication(sys.argv)
  389. # 2、创建一个 PyQt5_Serial 实例对象
  390. myMainWindow = PyQt5_Serial()
  391. # 3、显示主窗口
  392. myMainWindow.show()
  393. # 4、进入程序的主循环、并通过exit函数确保主循环安全结束
  394. sys.exit(app.exec_())
  395. if __name__ == '__main__':
  396. main()

四、获取 >> 源码以及Git记录:

PyQt5_Serial_Debug_Assistant_V1.0&V1.1


总结

欢迎下载&更新使用。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/我家自动化/article/detail/764516
推荐阅读
相关标签
  

闽ICP备14008679号