Python3+PyQt5+pySerial实现串口助手
环境搭建
本项目在windows10下开发,用到的开发软件是Pycharm
- 电脑安装python3以上版本,具体安装过程此处略过。
- 安装相关库pip install PyQt5,pip install pyserial。
- Pycharm中配置QtDesigner以及PyUIC。此处有详细的配置教程
通过QtDesigner设计界面
-
在pycharm中新建好项目后即可打开QtDesigner设计我们的程序界面了。
-
选中项目文件夹后右击,选择“Tools”–“External Tools”,即可打开我们关联的QtDesigner。
-
附上我的界面设计样板。
-
设计完后保存当前设计好的*.ui文件到我们的项目路径下。选中*.ui文件右击选择“Tools”–“External Tools”–“PyUIC”,即可将*.ui文件转化为*.py文件。
-
至此你肯定迫不及待想要运行显示你的界面查看效果了。同级目录下创建Main_Com.py文件。添加以下代码即可运行。
import sys # 导入程序运行必须模块
from PyQt5.QtWidgets import QApplication, QMainWindow # PyQt5中使用的基本控件都在PyQt5.QtWidgets模块中
from myCom import Ui_Form # 导入designer工具生成的*.py模块中的类
from PyQt5.QtWidgets import QApplication, QWidget, QInputDialog, QLineEdit
from PyQt5 import QtGui
from PyQt5.QtCore import *
import serial # 导入串口模块
import os
import time
class MyMainForm(QMainWindow, Ui_Form):
def __init__(self, parent=None):
super(MyMainForm, self).__init__(parent)
self.setupUi(self)
if __name__ == "__main__" :
app = QApplication(sys.argv)
# 初始化
myWin = MyMainForm()
# 将窗口控件显示在屏幕上
myWin.show()
# 程序运行,sys.exit方法确保程序完整退出。
sys.exit(app.exec_())
定义信号与槽函数
这里我就不打算详细说了,直接上代码,注意这是应用型程序,大家在我程序基础上增删改即可。
# Author:Guo_zq
# Date:2020-09-29
# UseFor:检查烧录版本是否正确
import sys # 导入程序运行必须模块
from PyQt5.QtWidgets import QApplication, QMainWindow # PyQt5中使用的基本控件都在PyQt5.QtWidgets模块中
from myCom import Ui_Form # 导入designer工具生成的myCom模块
from PyQt5.QtWidgets import QApplication, QWidget, QInputDialog, QLineEdit
from PyQt5 import QtGui
from PyQt5.QtCore import *
import serial # 导入串口模块
import os
import time
pass_Num = 0
fail_Num = 0
enable = 0 # 标识串口开闭
class MyMainForm(QMainWindow, Ui_Form):
def __init__(self, parent=None):
super(MyMainForm, self).__init__(parent)
self.setupUi(self)
self.com_Name.setText("COM8") # 设置默认串口
self.send_Content1.setText("F1") # 设置默认发送字符一
self.send_Content2.setText("81") # 设置默认发送字符二
'''
初始化获取LCD显示值,用于计数
---start---
'''
global fail_Num
global pass_Num
if os.path.exists("result") == False: # 先确保存在目录result
os.mkdir("result")
open("result/" + time.strftime("%Y_%m_%d", time.localtime()) + ".txt", 'a') # 再确保存在文件
if (os.path.getsize("result/" + time.strftime("%Y_%m_%d", time.localtime()) + ".txt") == 0): # 再判断文件是否有内容,若无则初始写入
with open("result/"+time.strftime("%Y_%m_%d", time.localtime())+".txt",'w') as f:
f.write("PASS:"+str(pass_Num)+"\t"+"FAIL:"+str(fail_Num))
else: # 若有则提取数值显示,同时全局变量中的pass_Num与fail_Num也被同步
with open("result/"+time.strftime("%Y_%m_%d", time.localtime())+".txt",'r') as f:
res_line = f.readline()
pass_Num = int(res_line.split("\t")[0].split(":")[1])
fail_Num = int(res_line.split("\t")[1].split(":")[1])
self.pass_lcd.display(pass_Num)
self.fail_lcd.display(fail_Num)
'''
---end---
'''
'''
添加按钮信号和槽函数
'''
self.open_button.clicked.connect(lambda: self.openSerial(self.com_Name.text(), enable))
self.send_button1.clicked.connect(lambda: self.sendData(self.send_Content1.text(), "1"))
self.send_button2.clicked.connect(lambda: self.sendData(self.send_Content2.text(), "2"))
self.clean_button.clicked.connect(self.show_dialog)
self.cancle_button.clicked.connect(self.close)
'''
设置初始化灰色图片
'''
def setInitImg(self):
self.light.setPixmap(QtGui.QPixmap("img/init.png"))
def openSerial(self, com, flag):
global t
global enable
flag = enable
if flag == 0:
try:
t = serial.Serial(com, 9600, timeout=5)
t.bytesize = 8 # 8位数据位
t.parity = serial.PARITY_EVEN # 此处设置为偶校验(无校验:serial.PARITY_NONE奇校验serial.PARITY_ODD)
t.stopbits = 1 # 停止位为1
t.rtscts # 硬件流控 ser.dsrdtr 软件流控 ser.xonxoff
t.flushInput() # 清空缓冲区
if t:
self.showContext.append("%s串口已打开" % (self.com_Name.text()))
self.open_button.setText("关闭串口")
enable = 1
except Exception as exc:
self.showContext.append("检查串口软硬件设置是否正确!!!error:%s"%exc)
else:
try:
serial.Serial.close(t)
enable=0
self.open_button.setText("打开串口")
self.showContext.append("%s串口已关闭"% (self.com_Name.text()))
except Exception as exc:
self.showContext.append("检查串口状态是否正确!!!error:%s"%exc)
def sendData(self, command, flag):
global pass_Num
global fail_Num
if flag == "1":
try:
self.send_button2.setEnabled(True) # 当第一个按钮被按下,将第二个按钮置为可用
t.write(bytes.fromhex(command)) # 以hex写入串口
time.sleep(0.5) # time.sleep() 与 inWaiting() 最好配对使用
t.inWaiting()
data = t.read_all().hex() # 将接收缓冲区中数据读取到data中
if data != "" and int(data) > 0: # 判空以及去除接收到00字符的干扰字符
now = str(time.strftime("%Y-%m-%d %H:%M:%S", time.localtime()))
self.showContext.append(now+"::"+data) # 显示当前时间+获取到的串口数据
if data[-2:] == "83": # 去除收到形如000083的字符串,直接取倒数两位值做判断
self.send_button1.setEnabled(False) # 获取到83即刻将按钮置为不可用,防止持续发送串口命令
self.light.setPixmap(QtGui.QPixmap("img/testing.png")) # 接收到83字符则显示黄色图片
else:
self.send_button2.setEnabled(False) # 先将第二个按钮置为不可用,防止检测继续进行下去
self.light.setPixmap(QtGui.QPixmap("img/fail.png")) # 接收到除83以外其他字符则显示红色图片
fail_Num = fail_Num+1 # 更新全局变量fail_Num的值
with open("result/" + time.strftime("%Y_%m_%d", time.localtime()) + ".txt", 'w') as f: # 对记录PASS数量及FAIL数量的文件进行更新
f.write("PASS:" + str(pass_Num) + "\t" + "FAIL:" + str(fail_Num))
with open("result/" + time.strftime("%Y_%m_%d", time.localtime()) + "_fail.txt",'a') as fr:# 对记录Fail详细数据的文件进行写入
fr.write(now+"::"+data+"\n")
self.fail_lcd.display(fail_Num) # 计数LCD显示值刷新
self.show_dialog() # 弹出提示框锁存当前错误状态,直至输入密码正确后方可复位清屏
QTimer.singleShot(5000, self.setInitImg) # 定时器,指定5秒后将红色图片替换为灰色图片,恢复初始状态
except Exception as exc:
self.showContext.append("请先确认串口连接成功:%s" % exc)
elif flag == "2":
self.send_button1.setEnabled(True) # 当第二个按钮被按下,解禁第一个按钮
try:
t.write(bytes.fromhex(command)) # 以hex写入串口
time.sleep(0.5) # sleep() 与 inWaiting() 最好配对使用
n = t.inWaiting()
if n:
data_hex = t.read(n).hex() # 读取缓冲区的数据,将bytes字符串转16进制
now = str(time.strftime("%Y-%m-%d %H:%M:%S", time.localtime()))
self.showContext.append(now+"::"+data_hex) # 将接收到的字符放入内容显示框
if data_hex <= bytes.fromhex("80").hex(): # 将转16进制后的结果与16进制的80进行比较大小
self.light.setPixmap(QtGui.QPixmap("img/fail.png")) # 接收到16进制数小于等于80则将图片置为红色
fail_Num = fail_Num+1 # 更新全局变量中fail_Num的值
with open("result/" + time.strftime("%Y_%m_%d", time.localtime()) + ".txt", 'w') as f: # 对记录PASS以及FAIL数量的文件进行更新
f.write("PASS:" + str(pass_Num) + "\t" + "FAIL:" + str(fail_Num))
with open("result/" + time.strftime("%Y_%m_%d", time.localtime()) + "_fail.txt",'a') as fr:# 对记录Fail数据的文件进行写入
fr.write(now+"::"+data+"\n")
self.fail_lcd.display(fail_Num) # 计数LCD显示值刷新
self.show_dialog() # 弹出提示框锁存当前错误状态,直至输入密码正确后方可复位清屏
QTimer.singleShot(5000, self.setInitImg) # 定时器,指定5秒后将红色图片替换为灰色图片,恢复初始状态
else:
self.send_button2.setEnabled(False) # 接收到大于80的十六进制数据将第二个按钮置为不可用
self.light.setPixmap(QtGui.QPixmap("img/success.png")) # 否则置于绿色图片 该测试机通过
pass_Num = pass_Num+1 # 更新全局变量pass_Num的值
with open("result/" + time.strftime("%Y_%m_%d", time.localtime()) + ".txt", 'w') as f:
f.write("PASS:" + str(pass_Num) + "\t" + "FAIL:" + str(fail_Num))
self.pass_lcd.display(pass_Num) # 计数LCD显示值刷新
QTimer.singleShot(5000, self.clean) # 清理显示框,PASS测试机文本框内值保存5秒
QTimer.singleShot(5000, self.setInitImg) # 定时器,指定5秒后将绿色图片替换为灰色图片,恢复初始状态
except Exception as exc:
self.showContext.append("请先确认串口连接成功:%s" % exc)
'''
显示提示框
'''
def show_dialog(self):
text, okPressed = QInputDialog.getText(self, "Dialog", "清屏密码:", QLineEdit.Password) # 密码提示框
if okPressed and text == "112233":
self.clean()
self.send_button1.setEnabled(True)
self.send_button2.setEnabled(True)
else:
self.show_dialog()
'''
清除显示文本框内的文本
'''
def clean(self):
self.showContext.clear()
if __name__ == "__main__" :
app = QApplication(sys.argv)
# 初始化
myWin = MyMainForm()
# 将窗口控件显示在屏幕上
myWin.show()
# 程序运行,sys.exit方法确保程序完整退出。
sys.exit(app.exec_())