PyQtGraph串口实时绘图类封装及Qt Designer集成问题求助
故障排查结论
封装后失效90%以上是以下几个低级错误导致,和pyqtgraph本身无关:
- 子类构造时未调用
pg.GraphicsWindow(或嵌入用的pg.GraphicsLayoutWidget)的父类__init__方法,或调用时未传入parent参数,Qt对象树断裂,组件生命周期不受Qt管控,嵌入UI后会出现不刷新、闪退、无响应问题 - 全局代码中定义的QTimer、串口实例、数据缓存数组、曲线对象,封装后写成了构造函数内的局部变量,出了构造作用域就被Python垃圾回收,串口读数据、定时刷新绘图的逻辑直接失效
- 原全局代码中绑定的定时器timeout槽函数、串口数据解析函数,封装后未正确绑定为类实例方法,调用时作用域不匹配,触发AttributeError但未被捕获
- 直接使用顶层
pg.GraphicsWindow做嵌入组件:该类自带独立窗口框架,嵌入Qt Designer生成的布局时会出现窗口嵌套、焦点冲突、大小无法自适应的问题,嵌入场景应替换为pg.GraphicsLayoutWidget作为父类。
可直接复用的正确实现
组件代码
import serial import pyqtgraph as pg from PyQt5.QtCore import QTimer from PyQt5.QtWidgets import QWidget import numpy as np # 注意:嵌入Qt Designer请继承pg.GraphicsLayoutWidget,不要用pg.GraphicsWindow class CustomPressurePlotWidget(pg.GraphicsLayoutWidget): def __init__(self, parent=None): # 必须调用父类构造,传入parent super().__init__(parent=parent) self.setBackground('w') # 固定配置参数 self.PORT = '/dev/ttyUSB0' self.BAUD = 230400 self.PACKET_LEN = 22 self.SCROLL_WINDOW = 200 # 滚动窗口显示最近200个采样点 self.CURVE_NUM_PER_SIDE = 5 # 每侧5条压力曲线 # 所有资源必须绑定为self属性,避免被GC回收 # 初始化滚动数据缓存 self.left_data = np.zeros((self.CURVE_NUM_PER_SIDE, self.SCROLL_WINDOW)) self.right_data = np.zeros((self.CURVE_NUM_PER_SIDE, self.SCROLL_WINDOW)) self.plot_x = np.arange(self.SCROLL_WINDOW) # 初始化左足绘图区 self.left_plot = self.addPlot(title="左鞋垫压力") self.left_plot.setYRange(0, 4096) # 按实际传感器量程调整 self.left_plot.setLabel('bottom', '采样点') self.left_plot.setLabel('left', '压力值') self.left_curves = [] for i in range(self.CURVE_NUM_PER_SIDE): curve = self.left_plot.plot( self.plot_x, self.left_data[i], pen=pg.mkPen(color=pg.intColor(i, self.CURVE_NUM_PER_SIDE), width=2) ) self.left_curves.append(curve) # 换行添加右足绘图区 self.nextRow() self.right_plot = self.addPlot(title="右鞋垫压力") self.right_plot.setYRange(0, 4096) self.right_plot.setLabel('bottom', '采样点') self.right_plot.setLabel('left', '压力值') self.right_curves = [] for i in range(self.CURVE_NUM_PER_SIDE): curve = self.right_plot.plot( self.plot_x, self.right_data[i], pen=pg.mkPen(color=pg.intColor(i, self.CURVE_NUM_PER_SIDE), width=2) ) self.right_curves.append(curve) # 初始化串口 self.ser = None self.init_serial() # 初始化刷新定时器,必须绑定为self属性 self.refresh_timer = QTimer() self.refresh_timer.timeout.connect(self.read_and_update) self.refresh_timer.start(10) # 10ms刷新一次,可按实际采样率调整 def init_serial(self): try: self.ser = serial.Serial( port=self.PORT, baudrate=self.BAUD, parity=serial.PARITY_NONE, stopbits=serial.STOPBITS_1, bytesize=serial.EIGHTBITS, timeout=0.01 ) except Exception as e: print(f"串口初始化失败: {str(e)}") def parse_packet(self, raw_bytes): """22字节数据包解析,可按实际传感器协议调整偏移量 通用协议结构:帧头(1B) + 左足5个uint16(10B) + 右足5个uint16(10B) + 校验(1B) """ if len(raw_bytes) != self.PACKET_LEN: return None, None # 跳过帧头,从第1位开始读10字节转5个uint16(小端序) left_vals = np.frombuffer(raw_bytes[1:11], dtype=np.uint16) right_vals = np.frombuffer(raw_bytes[11:21], dtype=np.uint16) return left_vals, right_vals def read_and_update(self): if not self.ser or not self.ser.is_open: return # 非阻塞读,凑够完整数据包就解析 while self.ser.in_waiting >= self.PACKET_LEN: # 匹配帧头,默认帧头为0xAA,可按实际协议修改 head_byte = self.ser.read(1) if head_byte != b'\xaa': continue raw_packet = head_byte + self.ser.read(self.PACKET_LEN - 1) left_vals, right_vals = self.parse_packet(raw_packet) if left_vals is None: continue # 滚动更新缓存 self.left_data = np.roll(self.left_data, -1, axis=1) self.left_data[:, -1] = left_vals self.right_data = np.roll(self.right_data, -1, axis=1) self.right_data[:, -1] = right_vals # 批量更新曲线 for i in range(self.CURVE_NUM_PER_SIDE): self.left_curves[i].setData(self.plot_x, self.left_data[i]) self.right_curves[i].setData(self.plot_x, self.right_data[i])
Qt Designer集成步骤
- 打开Qt Designer,在目标界面位置拖入一个普通
QWidget控件,调整好大小并加入布局自适应 - 右键点击该QWidget,选择「提升为...」
- 在弹出窗口中,「提升的类名称」填
CustomPressurePlotWidget,「头文件」填上述组件代码保存的py文件名(比如代码存为pressure_plot.py就填pressure_plot,不需要加.py后缀) - 点击「添加」后选中该提升类,点击「提升」即可
- 编译生成ui对应的py文件后,确保组件代码文件和ui文件在同一目录,运行时会自动加载绘图组件,不需要额外手动调用addWidget
注意:不要在组件内部写while True死循环读串口,会阻塞Qt主线程导致UI无响应;所有串口读取、数据解析逻辑放在定时器槽函数中,超时时间设为10ms以内即可保证实时性。
内容的提问来源于stack exchange,提问作者Aloi Leseigneur
相关产品推荐
相关产品推荐

