1、软件安装
需要用到的软件包括:
Python:本人使用的版本是3.8.2
PyCharm:本人使用的版本是PyCharm 2021.2.3(Community Edition)
PyQt5相关插件:PyQt5、PyQt5Designer、PyQt5-tools;
绘制曲线相关插件:pyqtgraph;
串口工具相关插件:pyqt5-Uart-Tool;
打包工具插件:pyinstaller
安装方法可参考:Python:命令安装pyQt5相关插件
配置PyQt5工具
菜单栏中打开:File——>Settings,在对话框中选择:Tools——>External Tools
如下图添加Qt设计师工具:
Program项:填写Qt设计师安装路径
$ProjectFileDir$:表示文件所在项目路径
如下图添加.ui文件转.py文件的工具:(下图中命名为PyUIC)
Program项:填写python.exe安装路径
Arguments项:-m PyQt5.uic.pyuic $FileName$ -o $FileNameWithoutExtension$.py
Working directory项:$FileDir$
$FileName$:表示文件名
$FileNameWithoutExtension$:表示没有扩展名的文件名
$FileDir$:表示文件所在路径
2、制作UI界面
打开Tools——>External Tools——>Qt Designer
其中这里的Qt Designer就是上一节配置的PyQt5工具
界面制作省略……
3、将UI文件转换为.py
选中需要转换的.ui文件(下图中选中的是mainUI.ui)
打开Tools——>External Tools——>PyUIC
其中这里的PyUIC就是第1节配置的PyQt5工具
如果转换成功将会有如下图提示
4、代码实现
基本界面1:串口配置对话框
其中windowModality项选择ApplicationModal,目的是将该对话框设置为模式对话框,即该对话框不关闭就不能操作打开该对话框的主页面;
基本界面2:主页面
通过菜单栏:设置——> 串口设置,打开串口配置对话框;
采样数据界面显示接收到的数据,采样曲线界面显示采样曲线。
4.1整体框架
其中只有serial_page.py、main_page.py、myUI.py三个文件需要写代码:
serial_page.py文件是serialUI.py界面的逻辑部分代码,该文件定义了类:
class SerialPage(QtWidgets.QDialog, Ui_DialogSerial)
main_page.py文件是mainPage.py界面的逻辑部分代码,该文件中定义了类:
class MainPage(QtWidgets.QMainWindow, Ui_MainWindow)
myUI.py文件是main函数文件:
import sys
from PyQt5 import QtWidgets
from mainUI.main_page import MainPage
if __name__ == '__main__':
app = QtWidgets.QApplication(sys.argv)
main_ui = MainPage()
main_ui.show()
sys.exit(app.exec_())
4.2串口配置逻辑
from PyQt5 import QtWidgets
from PyQt5.QtWidgets import QMessageBox
import serial
import serial.tools.list_ports
from .serialUI import Ui_DialogSerial
class SerialPage(QtWidgets.QDialog, Ui_DialogSerial):
def __init__(self):
super(SerialPage, self).__init__()
self.setupUi(self)
self.this_serial = serial.Serial()
self.port_check()
self.open_button.clicked.connect(self.port_open)
self.close_button.clicked.connect(self.port_close)
def port_check(self):
port_list = list(serial.tools.list_ports.comports())
self.box_serial_port.clear()
for port in port_list:
self.box_serial_port.addItem(port[0])
def port_open(self):
self.this_serial.port = self.box_serial_port.currentText()
self.this_serial.baudrate = int(self.box_baudrate.currentText())
self.this_serial.bytesize = 8
self.this_serial.stopbits = 1
self.this_serial.parity = 'N'
try:
self.this_serial.open()
except:
QMessageBox.critical(self, "Port Error", "此串口不能被打开!")
return None
if self.this_serial.isOpen():
self.open_button.setEnabled(False)
self.close_button.setEnabled(True)
self.close()
def port_close(self):
try:
self.this_serial.close()
except:
pass
self.open_button.setEnabled(True)
self.close_button.setEnabled(False)
4.3主页面逻辑
import array
from PyQt5 import QtWidgets, QtCore
from PyQt5.QtWidgets import QMessageBox
from PyQt5.QtCore import QTimer, QDateTime
import pyqtgraph as pg
from .mainUI import Ui_MainWindow
from .serialUI.serial_page import SerialPage
class MainPage(QtWidgets.QMainWindow, Ui_MainWindow):
def __init__(self):
super(MainPage, self).__init__()
self.setupUi(self)
self.serial_page = SerialPage()
self.timer_tx = QTimer()
self.timer_rx = QTimer()
self.timer_sys = QTimer()
self.data_num_rx = 0
self.data_num_tx = 0
self.start_flag = 0
self.pre_str = ""
self.init()
self.init_curve()
self.tmp_time = 0
self.data = array.array("f")
self.time = array.array("L")
def init_curve(self):
win = pg.PlotWidget()
self.gridLayoutCurve.addWidget(win)
win.showGrid(x=True, y=True)
win.setLabel(axis="left", text="重量", units="g")
win.setLabel(axis="bottom", text="时间", units="s")
self.curve = win.plot()
self.curve.setPen((255, 0, 0))
def init(self):
self.actionSerialSet.triggered.connect(self.serial_config)
self.clear_button.clicked.connect(self.clear_rx)
self.start_button.clicked.connect(self.start_sample)
self.timer_tx.timeout.connect(self.data_send)
self.timer_rx.timeout.connect(self.data_receive)
self.timer_sys.timeout.connect(self.show_time)
self.timer_sys.start(200)
def serial_config(self):
self.serial_page.show()
def clear_rx(self):
self.receive_text.setText("")
def show_time(self):
datetime = QtCore.QDateTime.currentDateTime()
text = datetime.toString("yyyy-MM-dd hh:mm:ss")
self.statusbar.showMessage(text)
def start_sample(self):
if self.serial_page.this_serial.isOpen():
if self.start_flag == 0:
self.start_flag = 1
self.timer_rx.start(30)
self.timer_tx.start(int(self.lineEdit_period.text()))
self.lineEdit_period.setEnabled(False)
self.radioButtonDaoJin.setEnabled(False)
self.radioButtonMeiTeLe.setEnabled(False)
self.start_button.setText("停止采样")
else:
self.start_flag = 0
self.timer_rx.stop()
self.timer_tx.stop()
self.lineEdit_period.setEnabled(True)
self.radioButtonDaoJin.setEnabled(True)
self.radioButtonMeiTeLe.setEnabled(True)
self.start_button.setText("启动采样")
else:
QMessageBox.critical(self, "Error!!!", "请先打开串口:设置——>串口设置")
return None
def data_send(self):
if self.serial_page.this_serial.isOpen():
# input_s = 'S S 1.04664 g\r\n'
input_s = 'SI\r\n'
if self.radioButtonDaoJin.isChecked():
input_s = 'D05\n'
input_s = input_s.encode('utf-8')
num = self.serial_page.this_serial.write(input_s)
self.data_num_tx += num
self.lineEdit_tx_num.setText(str(self.data_num_tx))
else:
pass
def data_receive(self):
try:
num = self.serial_page.this_serial.inWaiting()
except:
self.serial_page.port_close()
return None
if num > 0:
data = self.serial_page.this_serial.read(num)
self.show_balance_text(data)
self.show_balance_curve(data)
else:
pass
def show_balance_text(self, str_data):
num = len(str_data)
# 串口接收到的字符串为b'123',要转化成unicode字符串才能输出到窗口中去
self.receive_text.insertPlainText(str_data.decode('iso-8859-1'))
# 统计接收字符的数量
self.data_num_rx += num
self.lineEdit_rx_num.setText(str(self.data_num_rx))
# 获取到text光标
text_cursor = self.receive_text.textCursor()
# 滚动到底部
text_cursor.movePosition(text_cursor.End)
# 设置光标到text中去
self.receive_text.setTextCursor(text_cursor)
def show_balance_curve(self, str_data):
self.pre_str = self.pre_str+str_data.decode()
print(self.pre_str)
while len(self.pre_str) >= 16:
if self.pre_str.startswith('S ') and self.pre_str.find('.') == 8 and self.pre_str.find('g') == 15:
break
else:
self.pre_str = self.pre_str.replace(self.pre_str[0], '', 1)
print(self.pre_str)
if self.pre_str.startswith('S ') and self.pre_str.find('.') == 8 and self.pre_str.find('g') == 15:
tmp_str = self.pre_str.split()
print(tmp_str)
tmp_data = float(tmp_str[2])
print(tmp_data)
self.tmp_time += 1
self.pre_str = self.pre_str.replace(self.pre_str[0:14], '', 1)
self.data.append(tmp_data)
self.time.append(self.tmp_time)
self.curve.setData(self.data)
else:
pass
5、打包成.exe文件
在工程路径中打开cmd窗口(就是main函数所在文件的文件夹下打开cmd窗口):
输入指令:pyinstaller -F -w myUI.py
指令执行完毕后生成的.exe文件在该路径下的dist文件夹里面:
6、遇到的问题
问题1、串口接收数据不完整
现象:天平上传字符串格式为:'S S 1.04664 g\r\n'
但实际串口接收时发现很多时候一次接收不完整,需要分两次或三次接收才能接收完整;
解决办法:使用缓冲区,将每次接收到数据先放到缓冲区,在解析数据是否完整;
问题2、接收到的数据是char类型不是string类型
解决办法:将每次接收到的串口字符串先做解码,再放到缓冲区中:
self.pre_str = self.pre_str+str_data.decode()