frame buffer属性打印

本文详细介绍了如何通过自定义工具打印Framebuffer的基本信息,包括分辨率、内存地址、偏移量及BPP等,并提供了具体的编译和运行步骤。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

(一):写在前面

在这里我们主要实现了打印frame buffer的一些信息,其中包括分辨率,所占内存地址的开始地址,偏移量,BPP以及所占内存的宽度和高度等信息.主要是通过对这些参数的打印操作来实现对frame buffer的进一步深入的了解.

(二):如何编译以及运行

首先,我们看到,在我们的目录中有三个文件,fbtool.h,fbtool.c以及test.c,其中main函数位于test.c文件中.下面是我们的编译命令.

gcc fbtool.h fbtool.c test.c -o test

编译完成之后,就会生成test的可执行文件.

一般情况下,我们只要使用

./test

便能够运行该程序文件,但是由于在运行过程中,需要打开/dev/fb0文件,所以需要使用特权用户运行该程序,我们使用下面的命令来运行我们的程序文件.

sudo ./test

这样程序就能够顺利执行了.

(三):执行结果展示

下面我们来看一下我们的执行结果:

frame buffer所占内存的开始地址为:-1341710336
frame buffer 的类型为:0
frame buffer的可见清晰度为:
    x = 1366
    y = 768
frame buffer的虚拟清晰度为:
    x = 1366
    y = 768
frame buffer的虚拟分辨率到可见分辨率的偏移量为:
    x = 0
    y = 0
frame buffer的BPP为:32
这个后面再说!!
frame buffer在内存中所占的高度和宽度分别是:
    宽度 = -1
    高度 = -1

这样就能明显的看出frame buffer的一些属性了.

(四):该程序的构成

在这里我们说一下我们如果构建的该程序.

1:首先我们需要定义一个结构体,该结构体包括四个变量:

    int fb;   //framebuffer打开后的唯一标识符
    struct fb_fix_screeninfo fb_fix;  
    struct fb_var_screeninfo fb_var;
    char dev[20];  //设备名称

2:定义完成结构体之后,我们就需要定义操作frame buffer的各个方法,包括打开,关闭fb0设备,打印相关信息的函数等

//打开framebuffer
int fb_open(PFBDEV pFbdev);
//关闭framebuffer
int fb_close(PFBDEV pFbdev);
//打印frame buffer 所占内存的开始地址 
void pmem_start(PFBDEV pFbdev);
//打印FB_TYPE
void p_type(PFBDEV pFbdev);
//打印可见清晰度
void p_visible_res(PFBDEV pFbdev);
//打印虚拟分辨率
void p_virt_res(PFBDEV pFbdev);
//打印虚拟到可见的偏移量
void p_offset(PFBDEV pFbdev);
//打印每个像素的位数
void p_bpp(PFBDEV pFbdev);
//打印R,G,B和透明度
void p_rgbt(PFBDEV pFbdev);
//打印在内存中的高度和宽度
void p_hw(PFBDEV pFbdev);

3:接着就是这些函数的实现

这些函数的实现都是比较简单的,基本上是打印结构体中的一些信息.

4:编写测试程序,运行测试

最后就是编写我们的main函数,进行编译测试.

(五):后期展望

在后面一个测试中,着重讲解fb_var里面的R,G,B和透明度的值.

(六):写在后面

宝剑锋从磨砺出,梅花香自苦寒来.

注意:源代码我已经长传到github中,下面是地址:

源代码下载

转载于:https://www.cnblogs.com/bobo1223/p/7287509.html

import sys sys.path.append(‘/UI/Login’) from PyQt5 import QtCore, QtGui, QtWidgets from PyQt5.QtWidgets import QApplication, QWidget #from PyQt5.QtCore import QThread, pyqtSignal,QTimer from Login import Ui_Form as Login_ui from CanOBD import Ui_MainWindow as CanOBD_ui from PyQt5.QtWidgets import QMessageBox import time #import threading class LogInViewUI(QtWidgets.QWidget,Login_ui): def int(self): super(LogInViewUI,self).int() self.setupUi(self) def connectslot(self): #self.m_loginbuttonOn.clicked.connect(self.login_button) self.mloginButton.clicked.connect(self.login_button) def login_button(self): if self.mPassWordEdit.acceptRichText() == “”: QMessageBox.warning(self, ‘警告’, ‘密码不能为空,请输入!’) return None # 1打开新窗口 CanOBDWidget.show() # 2关闭本窗口 LoginWidget.close() class CanOBDViewUI(QtWidgets.QWidget,CanOBD_ui): def int(self): super(CanOBDViewUI,self).int() self.setupUi(self) def update_tree_widgets(self, signals): self.signals_data = signals # 更新 Speed Tree # if “CCVS1” in signals: # self.mSpeedTreeWidget.topLevelItem(0).setText(3, f"{signals[‘CCVS1’]} km/h") # # # 更新 RPM Tree # if “EEC1_rpm” in signals: # self.mRPMTreeWidget.topLevelItem(0).setText(3, f"{signals[‘EEC1_rpm’]} rpm") # if “EEC1_torque” in signals: # self.mEECTreeWidget.topLevelItem(0).setText(3, f"{signals[‘EEC1_torque’]} %“) # # # 更新 VDHR (里程) # if “VDHR” in signals: # self.mVDHRTreeWidget.topLevelItem(0).setText(3, f”{signals[‘VDHR’]} km") # # # 更新 HOURS (工作时长) # if “HOURS” in signals: # self.mHoursTreeWidget.topLevelItem(0).setText(3, f"{signals[‘HOURS’]} hours") # # # 更新 LFE (平均油耗) # if “LFE” in signals: # self.mLFETreeWidget.topLevelItem(0).setText(3, f"{signals[‘LFE’]} L/100km") # # # 更新 ET1 (冷却液温度) # if “ET1” in signals: # self.mET1TreeWidget.topLevelItem(0).setText(3, f"{signals[‘ET1’]} °C") # # # 更新 AT1T1I (燃油液面) # if “AT1T1I” in signals: # self.mAT1T1ITreeWidget.topLevelItem(0).setText(3, f"{signals[‘AT1T1I’]} %") # # # 更新 ETC2 (档位) # if “ETC2” in signals: # self.mETC2TreeWidget.topLevelItem(0).setText(3, signals[‘ETC2’]) if name == “main”: app = QtWidgets.QApplication(sys.argv) #MainWidget = QtWidgets.QDockWidget() LoginWidget = QtWidgets.QWidget() CanOBDWidget = QtWidgets.QMainWindow() loginui = LogInViewUI() CanOBDui = CanOBDViewUI() loginui.setupUi(LoginWidget) loginui.connectslot() CanOBDui.setupUi(CanOBDWidget) CanOBDui.Serialconnectslot() LoginWidget.show() sys.exit(app.exec_()) 这是main文件 – coding: utf-8 – import threading Form implementation generated from reading ui file ‘CanOBD.ui’ Created by: PyQt5 UI code generator 5.15.9 WARNING: Any manual changes made to this file will be lost when pyuic5 is run again. Do not edit this file unless you know what you are doing. from PyQt5 import QtCore, QtGui, QtWidgets from PyQt5.QtCore import QThread, pyqtSignal,QTimer from PyQt5.QtWidgets import QApplication, QTableWidget, QTableWidgetItem from SerialPro import SerialPort as SerialThread from SerialPro import CanOBDItemList from can_parser import parse_custom_can_frame import time import serial import serial.tools.list_ports class Ui_MainWindow(object): def setupUi(self, MainWindow): MainWindow.setObjectName(“MainWindow”) MainWindow.resize(1220, 940) font = QtGui.QFont() font.setPointSize(12) MainWindow.setFont(font) self.centralwidget = QtWidgets.QWidget(MainWindow) self.centralwidget.setObjectName(“centralwidget”) self.tabWidget = QtWidgets.QTabWidget(self.centralwidget) self.tabWidget.setGeometry(QtCore.QRect(0, 0, 1041, 940)) self.tabWidget.setObjectName(“tabWidget”) self.tab = QtWidgets.QWidget() self.tab.setObjectName(“tab”) self.tableWidget = QtWidgets.QTableWidget(self.tab) self.tableWidget.setGeometry(QtCore.QRect(0, 0, 1031, 940)) self.tableWidget.setObjectName(“tableWidget”) self.tableWidget.setColumnCount(5) self.tableWidget.setRowCount(150) for num in range(0,150,1): item = QtWidgets.QTableWidgetItem() self.tableWidget.setVerticalHeaderItem(num, item) item = QtWidgets.QTableWidgetItem() font = QtGui.QFont() font.setKerning(False) item.setFont(font) for line in range(0,5,1): self.tableWidget.setHorizontalHeaderItem(line, item) item = QtWidgets.QTableWidgetItem() self.tabWidget.addTab(self.tab, “”) self.tab_2 = QtWidgets.QWidget() self.tab_2.setObjectName(“CanOBD Cfg Set”) self.mSpeedTreeWidget = QtWidgets.QTreeWidget(self.tab_2) self.mSpeedTreeWidget.setGeometry(QtCore.QRect(10, 0, 1031, 101)) self.mSpeedTreeWidget.setObjectName(“mSpeedTreeWidget”) font = QtGui.QFont() font.setPointSize(12) self.mSpeedTreeWidget.headerItem().setFont(0, font) self.mSpeedTreeWidget.headerItem().setTextAlignment(1, QtCore.Qt.AlignJustify|QtCore.Qt.AlignVCenter) font = QtGui.QFont() font.setKerning(True) self.mSpeedTreeWidget.headerItem().setFont(3, font) self.mRPMTreeWidget = QtWidgets.QTreeWidget(self.tab_2) self.mRPMTreeWidget.setGeometry(QtCore.QRect(10, 100, 1031, 91)) self.mRPMTreeWidget.setObjectName(“mRPMTreeWidget”) font = QtGui.QFont() font.setPointSize(12) self.mRPMTreeWidget.headerItem().setFont(0, font) self.mRPMTreeWidget.headerItem().setTextAlignment(1, QtCore.Qt.AlignJustify|QtCore.Qt.AlignVCenter) font = QtGui.QFont() font.setKerning(True) self.mRPMTreeWidget.headerItem().setFont(3, font) self.mVDHRTreeWidget = QtWidgets.QTreeWidget(self.tab_2) self.mVDHRTreeWidget.setGeometry(QtCore.QRect(10, 190, 1031, 91)) self.mVDHRTreeWidget.setObjectName(“mVDHRTreeWidget”) font = QtGui.QFont() font.setPointSize(12) self.mVDHRTreeWidget.headerItem().setFont(0, font) self.mVDHRTreeWidget.headerItem().setTextAlignment(1, QtCore.Qt.AlignJustify|QtCore.Qt.AlignVCenter) font = QtGui.QFont() font.setKerning(True) self.mVDHRTreeWidget.headerItem().setFont(3, font) self.mHoursTreeWidget = QtWidgets.QTreeWidget(self.tab_2) self.mHoursTreeWidget.setGeometry(QtCore.QRect(10, 280, 1031, 101)) self.mHoursTreeWidget.setObjectName(“mHoursTreeWidget”) font = QtGui.QFont() font.setPointSize(12) self.mHoursTreeWidget.headerItem().setFont(0, font) self.mHoursTreeWidget.headerItem().setTextAlignment(1, QtCore.Qt.AlignJustify|QtCore.Qt.AlignVCenter) font = QtGui.QFont() font.setKerning(True) self.mHoursTreeWidget.headerItem().setFont(3, font) self.mEECTreeWidget = QtWidgets.QTreeWidget(self.tab_2) self.mEECTreeWidget.setGeometry(QtCore.QRect(10, 380, 1031, 91)) self.mEECTreeWidget.setObjectName(“mEECTreeWidget”) font = QtGui.QFont() font.setPointSize(12) self.mEECTreeWidget.headerItem().setFont(0, font) self.mEECTreeWidget.headerItem().setTextAlignment(1, QtCore.Qt.AlignJustify|QtCore.Qt.AlignVCenter) font = QtGui.QFont() font.setKerning(True) self.mEECTreeWidget.headerItem().setFont(3, font) self.mET1TreeWidget = QtWidgets.QTreeWidget(self.tab_2) self.mET1TreeWidget.setGeometry(QtCore.QRect(10, 470, 1031, 101)) self.mET1TreeWidget.setObjectName(“mET1TreeWidget”) font = QtGui.QFont() font.setPointSize(12) self.mET1TreeWidget.headerItem().setFont(0, font) self.mET1TreeWidget.headerItem().setTextAlignment(1, QtCore.Qt.AlignJustify|QtCore.Qt.AlignVCenter) font = QtGui.QFont() font.setKerning(True) self.mET1TreeWidget.headerItem().setFont(3, font) self.mAT1T1ITreeWidget = QtWidgets.QTreeWidget(self.tab_2) self.mAT1T1ITreeWidget.setGeometry(QtCore.QRect(10, 570, 1031, 91)) self.mAT1T1ITreeWidget.setObjectName(“mAT1T1ITreeWidget”) font = QtGui.QFont() font.setPointSize(12) self.mAT1T1ITreeWidget.headerItem().setFont(0, font) self.mAT1T1ITreeWidget.headerItem().setTextAlignment(1, QtCore.Qt.AlignJustify|QtCore.Qt.AlignVCenter) font = QtGui.QFont() font.setKerning(True) self.mAT1T1ITreeWidget.headerItem().setFont(3, font) self.mLFETreeWidget = QtWidgets.QTreeWidget(self.tab_2) self.mLFETreeWidget.setGeometry(QtCore.QRect(10, 660, 1031, 101)) self.mLFETreeWidget.setObjectName(“mLFETreeWidget”) font = QtGui.QFont() font.setPointSize(12) self.mLFETreeWidget.headerItem().setFont(0, font) self.mLFETreeWidget.headerItem().setTextAlignment(1, QtCore.Qt.AlignJustify|QtCore.Qt.AlignVCenter) font = QtGui.QFont() font.setKerning(True) self.mLFETreeWidget.headerItem().setFont(3, font) self.mETC2TreeWidget = QtWidgets.QTreeWidget(self.tab_2) self.mETC2TreeWidget.setGeometry(QtCore.QRect(10, 760, 1031, 101)) self.mETC2TreeWidget.setObjectName(“mETC2TreeWidget”) font = QtGui.QFont() font.setPointSize(12) self.mETC2TreeWidget.headerItem().setFont(0, font) self.mETC2TreeWidget.headerItem().setTextAlignment(1, QtCore.Qt.AlignJustify|QtCore.Qt.AlignVCenter) font = QtGui.QFont() font.setKerning(True) self.mETC2TreeWidget.headerItem().setFont(3, font) self.tabWidget.addTab(self.tab_2, “”) self.mComCfgBox = QtWidgets.QGroupBox(self.centralwidget) self.mComCfgBox.setGeometry(QtCore.QRect(1040, 10, 191, 231)) font = QtGui.QFont() font.setPointSize(14) font.setBold(True) font.setWeight(75) self.mComCfgBox.setFont(font) self.mComCfgBox.setObjectName(“mComCfgBox”) self.mPortName = QtWidgets.QLabel(self.mComCfgBox) self.mPortName.setGeometry(QtCore.QRect(20, 30, 61, 21)) self.mPortName.setObjectName(“mPortName”) self.mBpsName = QtWidgets.QLabel(self.mComCfgBox) self.mBpsName.setGeometry(QtCore.QRect(20, 60, 61, 21)) self.mBpsName.setObjectName(“mBpsName”) self.mDatabitName = QtWidgets.QLabel(self.mComCfgBox) self.mDatabitName.setGeometry(QtCore.QRect(20, 90, 61, 21)) self.mDatabitName.setObjectName(“mDatabitName”) self.mStopName = QtWidgets.QLabel(self.mComCfgBox) self.mStopName.setGeometry(QtCore.QRect(20, 120, 61, 21)) self.mStopName.setObjectName(“mStopName”) self.mOddName = QtWidgets.QLabel(self.mComCfgBox) self.mOddName.setGeometry(QtCore.QRect(20, 150, 61, 21)) self.mOddName.setObjectName(“mOddName”) self.mDatabitVal = QtWidgets.QLabel(self.mComCfgBox) self.mDatabitVal.setGeometry(QtCore.QRect(100, 90, 54, 21)) font = QtGui.QFont() font.setPointSize(14) font.setBold(True) font.setWeight(75) self.mDatabitVal.setFont(font) self.mDatabitVal.setLayoutDirection(QtCore.Qt.LeftToRight) self.mDatabitVal.setAlignment(QtCore.Qt.AlignCenter) self.mDatabitVal.setObjectName(“mDatabitVal”) self.mStopBitVal = QtWidgets.QLabel(self.mComCfgBox) self.mStopBitVal.setGeometry(QtCore.QRect(100, 120, 54, 21)) font = QtGui.QFont() font.setPointSize(14) font.setBold(True) font.setWeight(75) self.mStopBitVal.setFont(font) self.mStopBitVal.setLayoutDirection(QtCore.Qt.LeftToRight) self.mStopBitVal.setAlignment(QtCore.Qt.AlignCenter) self.mStopBitVal.setObjectName(“mStopBitVal”) self.mOddVal = QtWidgets.QLabel(self.mComCfgBox) self.mOddVal.setGeometry(QtCore.QRect(100, 150, 54, 21)) font = QtGui.QFont() font.setPointSize(14) font.setBold(True) font.setWeight(75) self.mOddVal.setFont(font) self.mOddVal.setLayoutDirection(QtCore.Qt.LeftToRight) self.mOddVal.setAlignment(QtCore.Qt.AlignCenter) self.mOddVal.setObjectName(“mOddVal”) self.mPortVal = QtWidgets.QComboBox(self.mComCfgBox) self.mPortVal.setGeometry(QtCore.QRect(90, 30, 81, 22)) self.mPortVal.setObjectName(“mPortVal”) self.mPortVal.addItem(“”) self.mPortVal.addItem(“”) self.mPortVal.addItem(“”) self.mPortVal.addItem(“”) self.mPortVal.addItem(“”) self.mPortVal.addItem(“”) self.mBPSVal = QtWidgets.QComboBox(self.mComCfgBox) self.mBPSVal.setGeometry(QtCore.QRect(90, 60, 81, 22)) self.mBPSVal.setObjectName(“mBPSVal”) self.mBPSVal.addItem(“”) self.mBPSVal.addItem(“”) self.mBPSVal.addItem(“”) self.mBPSVal.addItem(“”) self.mBPSVal.addItem(“”) self.mBPSVal.addItem(“”) self.mOpenSerial = QtWidgets.QDialogButtonBox(self.mComCfgBox) self.mOpenSerial.setGeometry(QtCore.QRect(20, 190, 156, 31)) self.mOpenSerial.setStandardButtons(QtWidgets.QDialogButtonBox.Close|QtWidgets.QDialogButtonBox.Open) self.mOpenSerial.setObjectName(“mOpenSerial”) self.mCycleCfgBox = QtWidgets.QGroupBox(self.centralwidget) self.mCycleCfgBox.setGeometry(QtCore.QRect(1040, 260, 191, 221)) font = QtGui.QFont() font.setPointSize(14) font.setBold(True) font.setWeight(75) self.mCycleCfgBox.setFont(font) self.mCycleCfgBox.setObjectName(“mCycleCfgBox”) self.mcheck1000ms = QtWidgets.QCheckBox(self.mCycleCfgBox) self.mcheck1000ms.setGeometry(QtCore.QRect(20, 180, 141, 31)) self.mcheck1000ms.setObjectName(“mcheck1000ms”) self.mcheck500ms = QtWidgets.QCheckBox(self.mCycleCfgBox) self.mcheck500ms.setGeometry(QtCore.QRect(20, 150, 141, 31)) self.mcheck500ms.setObjectName(“mcheck500ms”) self.mcheck100ms = QtWidgets.QCheckBox(self.mCycleCfgBox) self.mcheck100ms.setGeometry(QtCore.QRect(20, 90, 141, 31)) self.mcheck100ms.setObjectName(“mcheck100ms”) self.mcheck50ms = QtWidgets.QCheckBox(self.mCycleCfgBox) self.mcheck50ms.setGeometry(QtCore.QRect(20, 60, 141, 31)) self.mcheck50ms.setObjectName(“mcheck50ms”) self.mcheck20ms = QtWidgets.QCheckBox(self.mCycleCfgBox) self.mcheck20ms.setGeometry(QtCore.QRect(20, 30, 141, 31)) self.mcheck20ms.setObjectName(“mcheck20ms”) self.mcheck200ms = QtWidgets.QCheckBox(self.mCycleCfgBox) self.mcheck200ms.setGeometry(QtCore.QRect(20, 120, 141, 31)) self.mcheck200ms.setObjectName(“mcheck200ms”) self.mEventSigBox = QtWidgets.QGroupBox(self.centralwidget) self.mEventSigBox.setGeometry(QtCore.QRect(1050, 490, 191, 151)) font = QtGui.QFont() font.setPointSize(14) font.setBold(True) font.setWeight(75) self.mEventSigBox.setFont(font) self.mEventSigBox.setObjectName(“mEventSigBox”) self.radioLeftREvent = QtWidgets.QRadioButton(self.mEventSigBox) self.radioLeftREvent.setGeometry(QtCore.QRect(10, 30, 151, 16)) self.radioLeftREvent.setObjectName(“radioLeftREvent”) self.radioKiilEvent = QtWidgets.QRadioButton(self.mEventSigBox) self.radioKiilEvent.setGeometry(QtCore.QRect(10, 90, 151, 16)) self.radioKiilEvent.setObjectName(“radioKiilEvent”) self.radioPEvent = QtWidgets.QRadioButton(self.mEventSigBox) self.radioPEvent.setGeometry(QtCore.QRect(10, 120, 151, 16)) self.radioPEvent.setObjectName(“radioPEvent”) self.radioOpenCloseEvent = QtWidgets.QRadioButton(self.mEventSigBox) self.radioOpenCloseEvent.setGeometry(QtCore.QRect(10, 60, 151, 16)) self.radioOpenCloseEvent.setObjectName(“radioOpenCloseEvent”) self.mReadOBDinfBox = QtWidgets.QGroupBox(self.centralwidget) self.mReadOBDinfBox.setGeometry(QtCore.QRect(1050, 660, 191, 171)) font = QtGui.QFont() font.setPointSize(14) font.setBold(True) font.setWeight(75) self.mReadOBDinfBox.setFont(font) self.mReadOBDinfBox.setObjectName(“mReadOBDinfBox”) self.radioVinRead = QtWidgets.QRadioButton(self.mReadOBDinfBox) self.radioVinRead.setGeometry(QtCore.QRect(10, 40, 141, 21)) self.radioVinRead.setObjectName(“radioVinRead”) self.mVinInfShow = QtWidgets.QTextBrowser(self.mReadOBDinfBox) self.mVinInfShow.setGeometry(QtCore.QRect(10, 70, 171, 91)) self.mVinInfShow.setObjectName(“mVinInfShow”) MainWindow.setCentralWidget(self.centralwidget) self.statusbar = QtWidgets.QStatusBar(MainWindow) self.statusbar.setObjectName(“statusbar”) MainWindow.setStatusBar(self.statusbar) self.retranslateUi(MainWindow) self.tabWidget.setCurrentIndex(0) QtCore.QMetaObject.connectSlotsByName(MainWindow) def retranslateUi(self, MainWindow): _translate = QtCore.QCoreApplication.translate MainWindow.setWindowTitle(_translate(“MainWindow”, “MainWindow”)) for num in range(0, 150, 1): item = self.tableWidget.verticalHeaderItem(num) item.setText(_translate(“MainWindow”, str(num +1))) item = self.tableWidget.horizontalHeaderItem(0) item.setText(_translate(“MainWindow”, “时间标识”)) item = self.tableWidget.horizontalHeaderItem(1) item.setText(_translate(“MainWindow”, “帧ID”)) item = self.tableWidget.horizontalHeaderItem(2) item.setText(_translate(“MainWindow”, “帧类型”)) item = self.tableWidget.horizontalHeaderItem(3) item.setText(_translate(“MainWindow”, “长度”)) item = self.tableWidget.horizontalHeaderItem(4) item.setText(_translate(“MainWindow”, “数据 (BIT7–BIT0 大端模式)”)) self.tabWidget.setTabText(self.tabWidget.indexOf(self.tab), _translate(“MainWindow”, “Tab 1”)) self.mSpeedTreeWidget.headerItem().setText(0, _translate(“MainWindow”, “速度[CCVS1]”)) self.mSpeedTreeWidget.headerItem().setText(1, _translate(“MainWindow”, “CanID”)) self.mSpeedTreeWidget.headerItem().setText(2, _translate(“MainWindow”, “Data”)) self.mSpeedTreeWidget.headerItem().setText(3, _translate(“MainWindow”, “Signal(km/h)”)) self.mSpeedTreeWidget.setColumnWidth(0, 150) self.mSpeedTreeWidget.setColumnWidth(1, 150) self.mSpeedTreeWidget.setColumnWidth(2,550) self.mSpeedTreeWidget.setColumnWidth(3, 150) self.mRPMTreeWidget.headerItem().setText(0, _translate(“MainWindow”, “转速[EEC1]”)) self.mRPMTreeWidget.headerItem().setText(1, _translate(“MainWindow”, “CanID”)) self.mRPMTreeWidget.headerItem().setText(2, _translate(“MainWindow”, “Data”)) self.mRPMTreeWidget.headerItem().setText(3, _translate(“MainWindow”, “Signal(rpm)”)) self.mRPMTreeWidget.setColumnWidth(0, 150) self.mRPMTreeWidget.setColumnWidth(1, 150) self.mRPMTreeWidget.setColumnWidth(2,550) self.mRPMTreeWidget.setColumnWidth(3, 150) self.mVDHRTreeWidget.headerItem().setText(0, _translate(“MainWindow”, “里程[VDHR]”)) self.mVDHRTreeWidget.headerItem().setText(1, _translate(“MainWindow”, “CanID”)) self.mVDHRTreeWidget.headerItem().setText(2, _translate(“MainWindow”, “Data”)) self.mVDHRTreeWidget.headerItem().setText(3, _translate(“MainWindow”, “Signal(km)”)) self.mVDHRTreeWidget.setColumnWidth(0, 150) self.mVDHRTreeWidget.setColumnWidth(1, 150) self.mVDHRTreeWidget.setColumnWidth(2,550) self.mVDHRTreeWidget.setColumnWidth(3, 150) self.mHoursTreeWidget.headerItem().setText(0, _translate(“MainWindow”, “工作时长[HOURS]”)) self.mHoursTreeWidget.headerItem().setText(1, _translate(“MainWindow”, “CanID”)) self.mHoursTreeWidget.headerItem().setText(2, _translate(“MainWindow”, “Data”)) self.mHoursTreeWidget.headerItem().setText(3, _translate(“MainWindow”, “Signal(hours)”)) self.mHoursTreeWidget.setColumnWidth(0, 150) self.mHoursTreeWidget.setColumnWidth(1, 150) self.mHoursTreeWidget.setColumnWidth(2,550) self.mHoursTreeWidget.setColumnWidth(3, 150) self.mEECTreeWidget.headerItem().setText(0, _translate(“MainWindow”, “发动机负载[EEC1]”)) self.mEECTreeWidget.headerItem().setText(1, _translate(“MainWindow”, “CanID”)) self.mEECTreeWidget.headerItem().setText(2, _translate(“MainWindow”, “Data”)) self.mEECTreeWidget.headerItem().setText(3, _translate(“MainWindow”, “Signal(%)”)) self.mEECTreeWidget.setColumnWidth(0, 150) self.mEECTreeWidget.setColumnWidth(1, 150) self.mEECTreeWidget.setColumnWidth(2,550) self.mEECTreeWidget.setColumnWidth(3, 150) self.mET1TreeWidget.headerItem().setText(0, _translate(“MainWindow”, “冷却液温度[ET1]”)) self.mET1TreeWidget.headerItem().setText(1, _translate(“MainWindow”, “CanID”)) self.mET1TreeWidget.headerItem().setText(2, _translate(“MainWindow”, “Data”)) self.mET1TreeWidget.headerItem().setText(3, _translate(“MainWindow”, “Signal(°)”)) self.mET1TreeWidget.setColumnWidth(0, 150) self.mET1TreeWidget.setColumnWidth(1, 150) self.mET1TreeWidget.setColumnWidth(2,550) self.mET1TreeWidget.setColumnWidth(3, 150) self.mAT1T1ITreeWidget.headerItem().setText(0, _translate(“MainWindow”, “燃油液面[AT1T1I]”)) self.mAT1T1ITreeWidget.headerItem().setText(1, _translate(“MainWindow”, “CanID”)) self.mAT1T1ITreeWidget.headerItem().setText(2, _translate(“MainWindow”, “Data”)) self.mAT1T1ITreeWidget.headerItem().setText(3, _translate(“MainWindow”, “Signal(%)”)) self.mAT1T1ITreeWidget.setColumnWidth(0, 150) self.mAT1T1ITreeWidget.setColumnWidth(1, 150) self.mAT1T1ITreeWidget.setColumnWidth(2,550) self.mAT1T1ITreeWidget.setColumnWidth(3, 150) self.mLFETreeWidget.headerItem().setText(0, _translate(“MainWindow”, “平均油耗[LFE]”)) self.mLFETreeWidget.headerItem().setText(1, _translate(“MainWindow”, “CanID”)) self.mLFETreeWidget.headerItem().setText(2, _translate(“MainWindow”, “Data”)) self.mLFETreeWidget.headerItem().setText(3, _translate(“MainWindow”, “Signal(L/h)”)) self.mLFETreeWidget.setColumnWidth(0, 150) self.mLFETreeWidget.setColumnWidth(1, 150) self.mLFETreeWidget.setColumnWidth(2,550) self.mLFETreeWidget.setColumnWidth(3, 150) self.mETC2TreeWidget.headerItem().setText(0, _translate(“MainWindow”, “档位[ETC2]”)) self.mETC2TreeWidget.headerItem().setText(1, _translate(“MainWindow”, “CanID”)) self.mETC2TreeWidget.headerItem().setText(2, _translate(“MainWindow”, “Data”)) self.mETC2TreeWidget.headerItem().setText(3, _translate(“MainWindow”, “Signal”)) self.mETC2TreeWidget.setColumnWidth(0, 150) self.mETC2TreeWidget.setColumnWidth(1, 150) self.mETC2TreeWidget.setColumnWidth(2,550) self.mETC2TreeWidget.setColumnWidth(3, 150) self.tableWidget.setColumnWidth(0, 200) self.tableWidget.setColumnWidth(1, 150) self.tableWidget.setColumnWidth(4,450) self.tabWidget.setTabText(self.tabWidget.indexOf(self.tab), _translate(“MainWindow”, “CanOBD Inf Show”)) self.tabWidget.setTabText(self.tabWidget.indexOf(self.tab_2), _translate(“MainWindow”, “CanOBD J1939 Show”)) self.mComCfgBox.setTitle(_translate(“MainWindow”, “串口配置”)) self.mPortName.setText(_translate(“MainWindow”, “端口号”)) self.mBpsName.setText(_translate(“MainWindow”, “波特率”)) self.mDatabitName.setText(_translate(“MainWindow”, “数据位”)) self.mStopName.setText(_translate(“MainWindow”, “停止位”)) self.mOddName.setText(_translate(“MainWindow”, “检验位”)) self.mDatabitVal.setText(_translate(“MainWindow”, “8”)) self.mStopBitVal.setText(_translate(“MainWindow”, “1”)) self.mOddVal.setText(_translate(“MainWindow”, “无”)) self.mBPSVal.setItemText(0, _translate(“MainWindow”, “9600”)) self.mBPSVal.setItemText(1, _translate(“MainWindow”, “19200”)) self.mBPSVal.setItemText(2, _translate(“MainWindow”, “115200”)) self.mBPSVal.setItemText(3, _translate(“MainWindow”, “230400”)) self.mBPSVal.setItemText(4, _translate(“MainWindow”, “256000”)) self.mBPSVal.setItemText(5, _translate(“MainWindow”, “460800”)) port_list = list(serial.tools.list_ports.comports()) if port_list.len() is not 0: for num in range(port_list.len()): self.mPortVal.setItemText(num, _translate(“MainWindow”, str(port_list[num].device))) serialport = self.mPortVal.currentText() serialbaudrate = self.mBPSVal.currentText() self.LSerial = SerialThread(serialport, serialbaudrate) self.mCycleCfgBox.setTitle(_translate(“MainWindow”, “过滤设置(周期)”)) self.mcheck1000ms.setText(_translate(“MainWindow”, “1000ms 周期”)) self.mcheck500ms.setText(_translate(“MainWindow”, “500ms 周期”)) self.mcheck100ms.setText(_translate(“MainWindow”, “100ms 周期”)) self.mcheck50ms.setText(_translate(“MainWindow”, “50ms 周期”)) self.mcheck20ms.setText(_translate(“MainWindow”, “20ms 周期”)) self.mcheck200ms.setText(_translate(“MainWindow”, “200ms 周期”)) self.mEventSigBox.setTitle(_translate(“MainWindow”, “事件信号策略”)) self.radioLeftREvent.setText(_translate(“MainWindow”, “左右转 事件”)) self.radioKiilEvent.setText(_translate(“MainWindow”, “刹车 事件”)) self.radioPEvent.setText(_translate(“MainWindow”, “档位 事件”)) self.radioOpenCloseEvent.setText(_translate(“MainWindow”, “开关门 事件”)) self.mReadOBDinfBox.setTitle(_translate(“MainWindow”, “主动读取信息”)) self.radioVinRead.setText(translate(“MainWindow”, “VIN 信息”)) def OpenSerial(self): if self.LSerial != None: if self.LSerial.SerialIsOpen(): self.LSerial.del() port_list = list(serial.tools.list_ports.comports()) if port_list.len() != 0: serialport = self.mPortVal.currentText() serialbaudrate = self.mBPSVal.currentText() self.LSerial.init(serialport,serialbaudrate) # 开启线程 self.thread = Worker() # 创建线程对象 self.thread.update_signal.connect(self.CanOBDdatarefresh) # 连接信号和槽 self.thread.update_signal.connect(self.LSerial.Com_read_frame) # 连接信号和槽 self.thread.start() # 启动线程 #self.LSerial.Com_read_frame() def CloseSerial(self): if self.LSerial.SerialIsOpen(): self.LSerial.close() def Serialconnectslot(self): self.mOpenSerial.accepted.connect(self.OpenSerial) self.mOpenSerial.rejected.connect(self.CloseSerial) def update_j1939_widgets(self, can_id, data): “”“根据 CAN ID 和原始数据更新对应的 J1939 显示控件”“” # 解析 CAN 帧 signals = parse_custom_can_frame(can_id, data) # 定义信号组与控件的映射关系 signal_group_map = { “CCVS1”: self.mSpeedTreeWidget, “EEC1_RPM”: self.mRPMTreeWidget, “VDHR”: self.mVDHRTreeWidget, “HOURS”: self.mHoursTreeWidget, “EEC1_Torque”: self.mEECTreeWidget, “ET1”: self.mET1TreeWidget, “AT1T1I”: self.mAT1T1ITreeWidget, “LFE”: self.mLFETreeWidget, “ETC2”: self.mETC2TreeWidget } # 遍历解析出的信号 for signal_name, signal_value in signals.items(): # 获取信号组前缀(例如 “CCVS1”) signal_group = signal_name.split('‘)[0] # 找到对应的 TreeWidget tree_widget = None for key, widget in signal_group_map.items(): if key.startswith(signal_group): tree_widget = widget break if not tree_widget: continue # 清除现有内容(可选,根据需要决定是否清除) tree_widget.clear() # 创建树节点 item = QtWidgets.QTreeWidgetItem(tree_widget) # 设置各列数据 item.setText(0, signal_name) # 信号名称 item.setText(1, f"0x{can_id:08X}“) # CAN ID (十六进制格式) # 格式化原始数据为十六进制字符串 if isinstance(data, bytes): hex_data = ’ '.join(f”{b:02X}" for b in data) else: hex_data = str(data) item.setText(2, hex_data) # 原始数据 # 显示信号值 item.setText(3, str(signal_value)) # 设置列宽自适应 for i in range(tree_widget.columnCount()): tree_widget.resizeColumnToContents(i) def parse_can_data(self, raw_data): “”“增强版CAN数据解析”“” try: # 尝试直接处理字节数据 if isinstance(raw_data, (bytes, bytearray)): return raw_data # 处理整数类型 if isinstance(raw_data, int): # 将整数转换为字节数组 length = (raw_data.bit_length() + 7) // 8 return raw_data.to_bytes(length, ‘big’) # 处理字符串类型 cleaned_str = str(raw_data).strip().upper() # 移除常见前缀 for prefix in [‘0X’, ‘\X’, ‘X’, ‘HEX:’]: if cleaned_str.startswith(prefix): cleaned_str = cleaned_str[len(prefix):] # 移除所有非十六进制字符 import re cleaned_str = re.sub(r’0-9A-F’, ‘’, cleaned_str) # 处理空字符串 if not cleaned_str: return b’’ # 处理奇数长度 if len(cleaned_str) % 2 != 0: cleaned_str = ‘0’ + cleaned_str # 高位补零 return bytes.fromhex(cleaned_str) except Exception as e: print(f"解析CAN数据错误: {raw_data} -> {e}“) return b’’ def CanOBDdatarefresh(self): index = 0 for indexitem in CanOBDItemList: itemTime = QTableWidgetItem(str(indexitem[0])) itemCanID = QTableWidgetItem(str(indexitem[1])) itemType = QTableWidgetItem(str(indexitem[2])) itemLen = QTableWidgetItem(str(indexitem[3])) itemData = QTableWidgetItem(str(indexitem[4])) self.tableWidget.setItem(index,0,itemTime) self.tableWidget.setItem(index, 1, itemCanID) self.tableWidget.setItem(index, 2, itemType) self.tableWidget.setItem(index, 3, itemLen) self.tableWidget.setItem(index, 4, itemData) # 新增:更新 J1939 显示 # try: # # 确保 CAN ID 是整数 # can_id = int(indexitem[1], 16) if isinstance(indexitem[1], str) else indexitem[1] # data = self.parse_can_data(item[4]) # 使用安全解析方法 # # self.update_j1939_widgets(can_id, data) # except Exception as e: # print(f"Error updating J1939 widgets: {e}”) index += 1 #CanOBDItemList.clear() self.tableWidget.show() class Worker(QThread): update_signal = pyqtSignal(int) # 定义一个信号,用于传递更新信息到主线程 def run(self): 模拟耗时操作 while True: time.sleep(0.001) self.update_signal.emit(1) # 发射信号,传递更新信息 这是canobd文件 import serial import time import struct from datetime import datetime CanOBDItemList = [[0, 0, 0, 0, 0]] Frame_start = b’\xFF’ Frame_end = b’\x55’ Frame_data_style_len = 6 Frame_Data_Len = 0 frame_buffer = bytearray() class CanInfShow_Item: def int(self, CanID, CanFramType, Len, CanDataInf): self.SystemCycle = datetime.now().strftime(“%Y-%m-%d %H:%M:%S.%f”)[:-3], self.CanID = CanID, self.CanFrame = CanFramType, self.CanDataLen = Len, self.CanData = CanDataInf class SerialPort: def init(self, port, baudrate): 初始化串口参数 self.port = port self.baudrate = baudrate self.ser = serial.Serial( port=self.port, baudrate=self.baudrate, bytesize=serial.EIGHTBITS, parity=serial.PARITY_NONE, stopbits=serial.STOPBITS_ONE, timeout=0.1 # 添加串口读取超时 ) 添加超时检测机制 self.last_receive_time = time.time() # 记录最后接收时间 self.timeout_threshold = 1.5 # 超时阈值(秒) self.data_active = True # 初始状态为活跃 self.timeout_counter = 0 # 超时计数器 self.max_timeout_count = 3 # 连续超时3次后判定为停止 self.frame_count = 0 # 帧计数器 self.last_frame_count = 0 # 上次帧计数 等待串口连接稳定 time.sleep(0.2) if not self.ser.isOpen(): print(“串口打开失败!”) def close(self): # 关闭串口连接 if self.ser.isOpen(): self.ser.close() def send(self, data): # 发送数据 if self.ser.isOpen(): try: self.ser.write(data.encode(‘utf-8’)) except serial.SerialException as e: print(f"发送数据失败: {e}“) def recv(self): # 接收数据 - 使用非阻塞读取 if self.ser.isOpen(): try: # 读取所有可用数据,最多1024字节 data = self.ser.read(min(1024, self.ser.in_waiting)) return data except serial.SerialException as e: print(f"接收数据错误: {e}”) return b’’ def del(self): self.close() def SerialIsOpen(self): return 1 if self.ser.isOpen() else 0 def Frame_check_sum(self, data, length): “”“计算校验和”“” checksum = 0 for i in range(length): checksum += data[i] return checksum % 0x100 # 检查数据超时状态 def check_data_timeout(self): “”“检查数据接收是否超时并更新状态”“” current_time = time.time() elapsed = current_time - self.last_receive_time # 检查帧计数是否变化 if self.frame_count == self.last_frame_count: # 帧计数未变化,更新超时计数器 if elapsed > self.timeout_threshold: self.timeout_counter += 1 else: # 有新帧,重置计数器和时间 self.timeout_counter = 0 self.last_frame_count = self.frame_count self.last_receive_time = current_time # 连续超时达到阈值 if self.timeout_counter >= self.max_timeout_count: if self.data_active: print(f"数据接收超时({elapsed:.2f}s),停止界面更新") self.data_active = False return True # 数据活跃状态 if not self.data_active and self.timeout_counter == 0: print(“检测到数据流,开始更新界面”) self.data_active = True return False # 发送数据并接收响应 def Com_read_frame(self): global frame_buffer global Frame_Data_Len # 检查超时状态 if self.check_data_timeout(): # 超时状态下不处理数据 return byte_data = self.recv() if byte_data: # 更新最后接收时间 self.last_receive_time = time.time() frame_buffer.extend(byte_data) # 添加缓冲区大小限制(1MB) if len(frame_buffer) > 1024 * 1024: # 保留最后512KB frame_buffer = frame_buffer[-512 * 1024:] # 如果没有足够数据,提前返回 if len(frame_buffer) < Frame_data_style_len: return # 查找帧起始和结束位置 start_index = frame_buffer.find(Frame_start) end_index = frame_buffer.find(Frame_end) # 如果找不到起始帧或结束帧 if start_index == -1 or end_index == -1: # 清除无效数据(保留最后几个字节) if len(frame_buffer) > 10: frame_buffer = frame_buffer[-10:] return # 处理帧起始和结束位置关系 if start_index + 1 == end_index: # 计算帧长度 if start_index + 3 >= len(frame_buffer): return # 数据不足 Frame_Data_Len = (frame_buffer[start_index + 3] << 8) | frame_buffer[start_index + 2] # 调整缓冲区,使起始位置在0 frame_buffer = frame_buffer[start_index:] start_index = 0 elif end_index < start_index: # 结束标志在起始标志前,调整缓冲区 frame_buffer = frame_buffer[start_index:] start_index = 0 # 重新查找结束位置 end_index = frame_buffer.find(Frame_end) # 检查是否有完整帧 if len(frame_buffer) >= Frame_Data_Len + 5: # 计算校验和 data_start = start_index + 2 data_end = data_start + Frame_Data_Len + 2 if data_end > len(frame_buffer): return # 数据不足 checksum = self.Frame_check_sum(frame_buffer[data_start:data_end], Frame_Data_Len + 2) expected_checksum = frame_buffer[start_index + Frame_Data_Len + 4] if checksum == expected_checksum: # 提取完整帧 frame_end_pos = start_index + Frame_Data_Len + 5 frame_procee = frame_buffer[start_index:frame_end_pos] # 处理帧数据 self.Frame_analoy_process(frame_procee) self.frame_count += 1 # 增加帧计数 # 更新缓冲区 if len(frame_buffer) == frame_end_pos: frame_buffer.clear() else: frame_buffer = frame_buffer[frame_end_pos:] Frame_Data_Len = 0 else: # 校验失败,跳过这一帧 skip_pos = start_index + Frame_Data_Len + 6 if skip_pos < len(frame_buffer): frame_buffer = frame_buffer[skip_pos:] else: frame_buffer.clear() Frame_Data_Len = 0 # 报文解析 def Frame_analoy_process(self, Framedata): # 检查数据活跃状态 if not self.data_active: return # 数据超时状态下不更新界面 # 检查帧类型 (0x0C 0x98) if len(Framedata) < 8 or Framedata[4] != 0x0C or Framedata[5] != 0x98: return try: FrameNum = int(Framedata[7]) except IndexError: return # 检查是否有足够数据 if len(Framedata) < 12 * FrameNum + 8: return for index in range(FrameNum): # 时间戳 Cantime = datetime.now().strftime(“%Y-%m-%d %H:%M:%S.%f”)[:-3] # 提取ID字节 try: id_bytes = [ Framedata[12 * index + 11], # LSB Framedata[12 * index + 10], Framedata[12 * index + 9], Framedata[12 * index + 8] # MSB ] except IndexError: continue # 格式化为8位大写十六进制 CanID = ‘’.join(format(b, ‘02X’) for b in id_bytes) CanFramType = “Cycle” Len = 8 # 提取数据部分 try: CanDataSpace = ’ '.join( format(Framedata[12 * index + 12 + posindex], ‘02X’) for posindex in range(8) ) except IndexError: continue CanItemData = [Cantime, CanID, CanFramType, Len, CanDataSpace] # 更新列表 if not CanOBDItemList or CanOBDItemList[0][0] == 0: if CanOBDItemList and CanOBDItemList[0][0] == 0: CanOBDItemList.pop(0) CanOBDItemList.insert(0, CanItemData) else: Listpos = self.find_in_2d_list(CanOBDItemList, CanID) if Listpos is not None: CanOBDItemList[Listpos[0]] = CanItemData else: CanOBDItemList.append(CanItemData) def find_in_2d_list(self, matrix, target): “”“在二维列表中查找目标”“” for i, row in enumerate(matrix): if target in row: return (i, row.index(target)) return None 这是serialpro文件,现在程序存在一定问题,就是当can数据停止发送后,tab1界面仍然有较长时间(时间大概跟发送can数据的时间有正关系)在刷新数据,要求在can数据停止发送后,tab界面能够较快地(1s内)停止刷新,现在有一个思路是清除缓冲区文件,你认为应该怎么做?
最新发布
07-10
<think>我们面临的问题是:当CAN数据停止发送后,界面(tab1)仍然在刷新数据,需要实现当CAN数据停止发送后,界面在1秒内停止刷新。 根据提供的代码,在`SerialPort`类中已经有一个超时检测机制(`check_data_timeout`方法)。这个机制会在连续超时达到阈值(`max_timeout_count`)时,将`data_active`设置为False,从而停止处理数据(在`Com_read_frame`方法中,如果`check_data_timeout`返回True,则直接返回,不处理数据)。 但是,目前界面的刷新(即`CanOBDdatarefresh`方法)并没有受到`data_active`状态的影响。即使串口数据超时,`CanOBDdatarefresh`仍然会刷新界面,因为它只是从`CanOBDItemList`中读取数据并显示。 因此,我们需要做以下调整: 1. 在`SerialPort`类中,当检测到超时(即数据停止)时,除了不处理新数据外,还需要通知界面停止刷新。我们可以通过设置一个标志,然后在刷新界面的方法中检查这个标志。 2. 在`CanOBDViewUI`类中,我们需要在刷新界面的方法(`CanOBDdatarefresh`)中检查数据是否活跃。如果数据不活跃,则停止刷新。 但是,注意`CanOBDdatarefresh`方法是通过一个工作线程(`Worker`)的信号触发的。这个线程每隔1ms就发射一个信号,导致界面不断刷新。即使没有新数据,它也会刷新(因为`CanOBDItemList`中可能还有旧数据)。 为了在数据停止时停止刷新,我们可以: 方案A:停止工作线程。但这样当数据恢复时,我们需要重新启动线程。 方案B:在`CanOBDdatarefresh`方法中,检查`SerialPort`实例的`data_active`状态。如果数据不活跃,就不刷新界面。 但是,在`CanOBDViewUI`中,我们并没有直接持有`SerialPort`实例。在`Ui_MainWindow`中,我们有一个`LSerial`属性,它是`SerialPort`的实例。而`CanOBDViewUI`类(在`CanOBD`文件中)是`Ui_MainWindow`的子类?实际上,在`main`文件中,`CanOBDViewUI`是继承自`QtWidgets.QWidget`和`CanOBD_ui`(即`Ui_MainWindow`)。所以,在`CanOBDViewUI`中,我们可以通过`self.LSerial`访问串口实例。 然而,在`CanOBDdatarefresh`方法中,我们并没有使用`self.LSerial`。我们需要在`CanOBDdatarefresh`方法中添加对`self.LSerial.data_active`的检查。 但是,还有一个问题:即使数据不活跃,我们可能希望界面在停止刷新前将最后的数据显示出来,然后停止刷新。所以,我们可以在`CanOBDdatarefresh`中这样处理: - 如果`self.LSerial.data_active`为True,则正常刷新。 - 如果为False,则刷新一次(将当前`CanOBDItemList`中的数据刷新到界面),然后停止刷新(即不再响应工作线程的信号)。 但是,停止响应信号需要断开信号与槽的连接,或者停止工作线程。这样当数据恢复时,我们还需要重新连接或启动线程。 另一种做法:我们可以在`SerialPort`类中设置一个标志,然后在`CanOBDViewUI`中,每次刷新前检查这个标志。如果数据不活跃,我们就不刷新,但工作线程仍然在运行(这样当数据恢复时,我们不需要重新启动线程)。但是,这样在没有数据时,界面刷新也是浪费资源的。 考虑到性能,我们可以在数据超时时停止工作线程,然后在数据恢复时重新启动工作线程。 具体修改步骤: 1. 在`SerialPort`类中,当检测到数据恢复时(即从超时状态变为有数据),我们需要重新启动工作线程(如果之前停止了的话)。但目前,超时检测只会在`Com_read_frame`方法中调用,而该方法在数据超时时会直接返回,所以当数据恢复时,`Com_read_frame`会再次被调用,并且`check_data_timeout`会重置状态。 2. 在`CanOBDViewUI`类中,我们需要在`CanOBDdatarefresh`方法中检查串口的数据活跃状态。如果数据不活跃,我们停止工作线程(并设置一个标志,表示工作线程已停止)。然后,当数据再次活跃时,我们需要重新启动工作线程。 但是,数据活跃状态的变化是在`SerialPort`类中,而`CanOBDViewUI`并不知道。所以我们需要一个信号来通知界面数据活跃状态的变化。 由于代码结构已经比较复杂,我们可以采用一个简单的方法:在`CanOBDdatarefresh`方法中,我们检查串口的`data_active`状态。如果状态为False,并且工作线程还在运行,则停止工作线程。这样,当数据停止时,工作线程会在下一次刷新时停止。 但是,注意:工作线程(`Worker`)每隔1ms发射一个信号,而`CanOBDdatarefresh`是在主线程中执行的。我们可以在`CanOBDdatarefresh`中停止工作线程,然后当数据恢复时(在`Com_read_frame`中,当数据重新活跃时),我们重新启动工作线程。 然而,在`Com_read_frame`中重新启动工作线程可能不太合适,因为串口类不应该直接操作界面线程。 另一种思路:我们不在`Com_read_frame`中做任何界面相关的操作,而是通过信号将数据活跃状态发送给主界面。 我们可以这样做: - 在`SerialPort`类中定义一个信号,例如`data_active_changed`,当`data_active`状态改变时发射这个信号。 - 在`CanOBDViewUI`中,连接这个信号,当收到状态改变时,启动或停止工作线程。 但是,由于`SerialPort`类不是`QObject`的子类,所以不能直接定义信号。我们需要将`SerialPort`改为继承自`QObject`。 考虑到改动较大,我们可以采用另一种方式:在`CanOBDViewUI`中,我们通过定时器定期检查`SerialPort`的`data_active`状态。如果状态变为False,则停止工作线程;如果状态变为True,则启动工作线程。 我们可以在`CanOBDViewUI`中设置一个定时器,每隔1秒检查一次`self.LSerial.data_active`。但是这样会有1秒的延迟。 为了快速响应(1秒内停止刷新),我们可以在`CanOBDdatarefresh`方法中检查。因为`CanOBDdatarefresh`方法是由工作线程的信号触发的,每秒触发1000次(因为工作线程每1ms发射一次信号)。我们可以在其中检查串口的`data_active`状态,如果状态为False,则停止工作线程。 具体修改: 步骤1:在`CanOBDViewUI`的`CanOBDdatarefresh`方法中,添加以下代码: if not self.LSerial.data_active: # 数据不活跃,停止工作线程 if hasattr(self, 'thread') and self.thread.isRunning(): self.thread.quit() # 或者terminate(),但推荐quit self.thread.wait() # 等待线程结束 return # 不再刷新界面 步骤2:当数据恢复时,我们需要重新启动工作线程。但是,数据恢复是在串口类中检测的,而串口类在`Com_read_frame`方法中会重置`data_active`为True。但是,在`Com_read_frame`中,我们无法直接启动工作线程(因为串口类不知道界面类的工作线程)。 所以,我们还需要在`CanOBDdatarefresh`方法中处理数据恢复的情况。但是,当工作线程停止后,`CanOBDdatarefresh`方法就不会被调用了,因此无法检测到数据恢复。 为了解决这个问题,我们可以采用: - 在串口类中,当数据从超时状态恢复为活跃时,我们设置一个标志`data_reactivated`(或者直接通过一个方法通知界面)。 - 在界面类中,我们通过一个定时器定期(比如每秒)检查串口类的`data_active`状态,如果发现从非活跃变为活跃,则重新启动工作线程。 但是,为了简化,我们可以这样:当工作线程停止后,我们启动一个定时器,定期检查串口类的`data_active`状态,如果为True,则重新启动工作线程。 修改步骤: 1. 在`CanOBDViewUI`类中,添加一个定时器成员,用于在停止工作线程后定期检查数据活跃状态。 2. 在`CanOBDdatarefresh`方法中,如果检测到`self.LSerial.data_active`为False,则停止工作线程,并启动定时器(每隔200ms检查一次串口的`data_active`状态)。 3. 在定时器的槽函数中,如果检测到`self.LSerial.data_active`为True,则重新启动工作线程,并停止定时器。 这样,当数据恢复时,定时器会检测到,并重新启动工作线程,从而恢复界面刷新。 但是,这样修改涉及较多代码,而且需要确保线程安全。 考虑到时间,我们可以采用一个更简单的方法:不停止工作线程,而是让`CanOBDdatarefresh`方法在数据不活跃时直接返回,不进行刷新。这样,工作线程还在运行,但刷新操作被跳过。当数据恢复时,刷新操作自然就会恢复。 这样修改最小: 在`CanOBDdatarefresh`方法开始添加: if not self.LSerial.data_active: return 这样,当数据不活跃时,刷新方法直接返回,不更新界面。同时,工作线程还在运行,所以当数据恢复时,刷新会自动继续。 但是,这样工作线程还在不断发射信号,导致`CanOBDdatarefresh`被调用,虽然里面直接返回了,但函数调用本身也有开销。如果长时间没有数据,这可能会浪费一些资源(每秒1000次调用)。但考虑到主频较高的计算机,这可能可以接受。 如果希望减少资源占用,我们可以在串口类中记录数据活跃状态,并在`CanOBDdatarefresh`中根据这个状态来决定是否刷新。同时,我们可以调整工作线程的信号发射频率,例如在没有数据时降低频率。但这样会增加复杂度。 我们先采用简单的方法:在`CanOBDdatarefresh`开始检查`data_active`状态。 修改`CanOBDViewUI`的`CanOBDdatarefresh`方法: def CanOBDdatarefresh(self): # 检查串口数据是否活跃 if not self.LSerial.data_active: return # 数据不活跃,不刷新 index = 0 ... # 原有的刷新逻辑 这样,当数据停止(超时)时,`self.LSerial.data_active`变为False,刷新方法直接返回,不再更新界面。 但是,我们还需要注意:在超时之前接收到的数据,在超时后可能还没有刷新到界面上。所以,我们可以在超时后立即刷新一次(将最后的数据显示出来)。这可以在串口类中设置`data_active`为False时,主动发射一个信号通知界面刷新一次。 由于串口类不是`QObject`,我们可以通过另一种方式:在`Com_read_frame`方法中,当检测到超时(即`check_data_timeout`返回True)时,我们设置`data_active`为False,然后这里可以调用一个回调函数通知界面刷新一次。但是,串口类并不知道界面。 因此,我们可以在`CanOBDViewUI`中,在连接串口时,将串口实例的`data_active`状态与界面的刷新关联。具体来说,在`CanOBDViewUI`中,我们设置一个定时器,每隔一段时间(比如100ms)检查一次`self.LSerial.data_active`。如果发现状态变为False,则主动调用一次`CanOBDdatarefresh`(这样可以将最后的数据显示出来),然后停止后续的刷新(因为工作线程还在发射信号,但`CanOBDdatarefresh`中会检查`data_active`,所以会直接返回)。 但是,这样我们又要添加定时器,增加复杂度。 考虑到简单性,我们可以在`CanOBDdatarefresh`中,在检测到`data_active`为False之前,界面已经刷新了超时前接收到的数据。因为超时后,串口类不再处理新数据,所以`CanOBDItemList`中的内容就是最后的数据。而工作线程还在运行,所以`CanOBDdatarefresh`会被调用,在第一次检测到`data_active`为False时,就不会刷新,但上一次刷新已经是最后的数据了。所以,界面上显示的就是最后的数据。 因此,我们只需要在`CanOBDdatarefresh`开始添加两行代码即可。 但是,我们还需要确保在`SerialPort`类中,`check_data_timeout`方法被正确调用。在`Com_read_frame`中,每次调用都会先检查超时。 总结修改步骤: 1. 在`CanOBDViewUI`的`CanOBDdatarefresh`方法开始,添加对`self.LSerial.data_active`的检查。 2. 确保`SerialPort`类中的超时检测机制正常工作(即`check_data_timeout`方法)。 注意:在`main`文件中,`CanOBDViewUI`类继承自`QtWidgets.QWidget`和`CanOBD_ui`(即`Ui_MainWindow`)。在`Ui_MainWindow`中,我们初始化了`self.LSerial = SerialThread(...)`,所以在`CanOBDViewUI`中可以通过`self.LSerial`访问串口实例。 但是,在`CanOBD`文件中,`Ui_MainWindow`的`setupUi`方法中,初始化`self.LSerial`的代码被注释掉了?实际代码: # 在`Ui_MainWindow`的`retranslateUi`方法中,有以下代码: # port_list = list(serial.tools.list_ports.comports()) # if port_list.len() is not 0: # for num in range(port_list.len()): # self.mPortVal.setItemText(num, _translate("MainWindow", str(port_list[num].device))) # serialport = self.mPortVal.currentText() # serialbaudrate = self.mBPSVal.currentText() # self.LSerial = SerialThread(serialport, serialbaudrate) 但是,在`retranslateUi`方法中初始化串口可能不合适,因为`retranslateUi`是用于翻译界面的。而且,在界面初始化时,串口参数可能还没有设置好。 实际上,串口的初始化是在点击“打开串口”按钮时,在`OpenSerial`方法中进行的。所以,在`CanOBDViewUI`中,`self.LSerial`可能还没有被初始化,或者在初始化之前访问会导致错误。 因此,在`CanOBDdatarefresh`中,我们需要先判断`self.LSerial`是否存在,并且是否已经初始化。 修改后的`CanOBDdatarefresh`方法开头: if not hasattr(self, 'LSerial') or not self.LSerial or not self.LSerial.data_active: return 但是,`self.LSerial`在串口打开时才会被初始化(在`OpenSerial`方法中)。如果串口没有打开,那么`self.LSerial`可能不存在或者为None。所以,我们还需要在串口没有打开时就不刷新。 另外,在`SerialPort`类中,`data_active`的初始值为True。所以,在串口打开后,`data_active`为True,直到检测到超时。 所以,我们修改`CanOBDdatarefresh`方法如下: def CanOBDdatarefresh(self): # 如果串口实例不存在,或者串口没有打开,或者数据不活跃,则不刷新 if not hasattr(self, 'LSerial') or self.LSerial is None or not self.LSerial.SerialIsOpen() or not self.LSerial.data_active: return ... # 原有刷新逻辑 这样,当串口没有打开,或者数据超时,都会停止刷新。 另外,在串口关闭时,我们也要停止刷新。在`CloseSerial`方法中,我们关闭了串口,并且设置`self.LSerial`为None?并没有,我们只是关闭了串口连接,但`self.LSerial`实例还在。在`SerialPort`类中,`SerialIsOpen`方法返回串口是否打开。所以,在`CloseSerial`中,我们调用了`self.LSerial.close()`,那么`self.LSerial.SerialIsOpen()`就会返回0(即False)。所以,上面的条件也会满足,停止刷新。 因此,我们只需要在`CanOBDdatarefresh`方法开始添加上述条件判断即可。 但是,我们还需要确保在串口关闭后,`data_active`状态被重置。在`SerialPort`的`close`方法中,我们关闭了串口,但没有重置`data_active`。所以,在重新打开串口后,`data_active`可能还是之前的状态。因此,在`SerialPort`的`init`方法中,我们应该初始化`data_active`为True。 另外,在`SerialPort`的`close`方法中,我们可以设置`data_active`为False,因为串口关闭了,数据自然就停止了。 修改`SerialPort`的`init`方法: def __init__(self, port, baudrate): # ... 其他初始化 self.data_active = True # 初始为活跃状态 修改`SerialPort`的`close`方法: def close(self): if self.ser.isOpen(): self.ser.close() self.data_active = False # 关闭串口后,数据不活跃 同时,在`OpenSerial`方法中,我们重新初始化了串口,所以`data_active`会被重置为True(在`SerialPort`的`__init__`中)。 这样,当串口关闭时,`data_active`变为False,刷新停止。 最后,我们还需要考虑线程安全。因为`data_active`可能在多个线程中被访问(串口读取线程和主界面线程)。在`SerialPort`类中,`Com_read_frame`是在主线程中调用的(因为它是通过工作线程的信号触发,但工作线程的信号是在主线程中处理的)。所以,实际上`Com_read_frame`和`CanOBDdatarefresh`都是在主线程中执行的。因此,不需要额外的锁。 总结: 1. 修改`SerialPort`类的`__init__`方法,初始化`self.data_active = True`。 2. 修改`SerialPort`类的`close`方法,在关闭串口后设置`self.data_active = False`。 3. 在`CanOBDViewUI`的`CanOBDdatarefresh`方法开始,添加条件判断,如果串口未打开或数据不活跃,则返回。 代码修改: 在`SerialPort`类中: class SerialPort: def __init__(self, port, baudrate): # ... 其他代码 self.data_active = True # 新增 def close(self): if self.ser.isOpen(): self.ser.close() self.data_active = False # 新增 在`CanOBDViewUI`类中,修改`CanOBDdatarefresh`方法: def CanOBDdatarefresh(self): # 检查串口是否打开且数据活跃 if not hasattr(self, 'LSerial') or self.LSerial is None or not self.LSerial.SerialIsOpen() or not self.LSerial.data_active: return index = 0 # ... 原有代码 注意:在`SerialPort`的`Com_read_frame`方法中,当检测到超时时,会设置`self.data_active = False`(在`check_data_timeout`方法中)。所以,当数据停止发送时,`data_active`会变为False,从而停止刷新。 另外,在`SerialPort`的`check_data_timeout`方法中,当数据恢复时,会设置`self.data_active = True`(当超时计数器重置时,并且之前为不活跃状态,会打印消息并设置为活跃)。所以,当数据恢复时,刷新也会恢复。 这样修改后,应该能达到要求:当CAN数据停止发送后,界面在1秒内停止刷新。 但是,注意超时阈值是1.5秒,连续超时3次(即4.5秒)才会判定为超时。这个时间可能超过了1秒的要求。我们可以调整超时参数。 在`SerialPort`的`__init__`中: self.timeout_threshold = 0.5 # 改为0.5秒超时 self.max_timeout_count = 2 # 连续2次超时(即1秒)就判定为停止 这样,从数据停止到判定为超时,最多需要0.5 * 2 = 1秒(加上第一次检测到超时的时间,最多1.5秒)。但这样可能会因为短暂的停顿就判定为停止。根据实际需求调整。 我们可以在`SerialPort`的`__init__`中调整这两个参数,以满足1秒内停止刷新的要求。 修改`SerialPort`的`__init__`: self.timeout_threshold = 0.5 # 超时阈值设为0.5秒 self.max_timeout_count = 2 # 连续2次超时后判定为停止(即1秒) 这样,从数据停止到界面停止刷新,最多需要1秒(加上刷新间隔,应该可以满足)。 因此,最终修改包括: 1. 调整超时参数(可选,根据实际需要)。 2. 在`SerialPort`的`__init__`中初始化`data_active`为True,在`close`中设置为False。 3. 在`CanOBDdatarefresh`方法开始添加条件判断。 请根据实际代码进行调整。 注意:由于代码较长,以上修改需要仔细核对。 如果按照上述修改后,问题仍然存在,请检查`check_data_timeout`方法的逻辑是否正确,以及`Com_read_frame`中是否调用了`check_data_timeout`。 由于无法实际运行,以上代码可能需要调试。 最后,我们提供修改后的`CanOBDdatarefresh`方法代码: def CanOBDdatarefresh(self): # 检查串口是否打开且数据活跃 if not hasattr(self, 'LSerial') or self.LSerial is None: return if not self.LSerial.SerialIsOpen() or not self.LSerial.data_active: return # 原有的刷新逻辑 index = 0 ... 这样,当串口未打开或数据不活跃时,刷新方法直接返回,不更新界面。</think>根据你的描述,问题在于当CAN数据停止发送后,界面刷新未能及时停止。核心原因是串口缓冲区中仍有未处理的数据,以及工作线程持续触发刷新。以下是解决方案: ### 解决方案 1. **在串口类中增加缓冲区清除功能** 2. **优化超时检测机制** 3. **在数据超时时主动清除界面数据** 修改`SerialPort`类如下: ```python class SerialPort: def __init__(self, port, baudrate): # ... 其他初始化代码 ... self.timeout_threshold = 1.0 # 缩短超时阈值到1秒 self.max_timeout_count = 1 # 只需一次超时即判定停止 def clear_buffers(self): """清除所有缓冲区数据""" self.ser.reset_input_buffer() self.ser.reset_output_buffer() global frame_buffer frame_buffer = bytearray() # 清空全局帧缓冲区 global CanOBDItemList CanOBDItemList = [] # 清空数据列表 def check_data_timeout(self): """增强版超时检测""" current_time = time.time() elapsed = current_time - self.last_receive_time # 当超过阈值且之前是活跃状态 if elapsed > self.timeout_threshold and self.data_active: print(f"数据接收超时({elapsed:.2f}s),停止处理并清除缓冲区") self.data_active = False self.clear_buffers() # 超时立即清除缓冲区 return True # 当有新数据时重置状态 if self.frame_count != self.last_frame_count: self.last_frame_count = self.frame_count self.last_receive_time = current_time if not self.data_active: print("检测到数据流,恢复处理") self.data_active = True return False def close(self): if self.ser.isOpen(): self.clear_buffers() # 关闭时清除缓冲区 self.ser.close() ``` ### 在界面类中添加停止刷新逻辑 修改`CanOBDViewUI`类: ```python class CanOBDViewUI(QtWidgets.QWidget, CanOBD_ui): # ... 其他代码 ... def stop_refresh(self): """停止界面刷新""" if hasattr(self, 'thread') and self.thread.isRunning(): self.thread.quit() # 停止工作线程 self.thread.wait() # 清空表格数据 self.tableWidget.setRowCount(0) # 清空所有J1939控件 widgets = [ self.mSpeedTreeWidget, self.mRPMTreeWidget, self.mVDHRTreeWidget, self.mHoursTreeWidget, self.mEECTreeWidget, self.mET1TreeWidget, self.mAT1T1ITreeWidget, self.mLFETreeWidget, self.mETC2TreeWidget ] for widget in widgets: widget.clear() def update_status(self): """定期检查数据状态""" if hasattr(self, 'LSerial') and self.LSerial and not self.LSerial.data_active: self.stop_refresh() def setupUi(self, MainWindow): # ... 原有代码 ... # 添加状态检查定时器 self.status_timer = QTimer() self.status_timer.timeout.connect(self.update_status) self.status_timer.start(500) # 每500ms检查一次 def OpenSerial(self): # ... 打开串口代码 ... # 启动工作线程前确保停止之前的 if hasattr(self, 'thread') and self.thread.isRunning(): self.thread.quit() self.thread.wait() # ... 其他初始化代码 ... ``` ### 修改工作线程逻辑 ```python class Worker(QThread): update_signal = pyqtSignal(int) def __init__(self, serial_instance): super().__init__() self.serial = serial_instance self.active = True def run(self): while self.active: # 只有数据活跃时才处理 if self.serial.data_active: time.sleep(0.001) self.update_signal.emit(1) else: time.sleep(0.1) # 非活跃时降低CPU占用 def stop(self): self.active = False ``` ### 修改串口打开逻辑 ```python def OpenSerial(self): if hasattr(self, 'LSerial') and self.LSerial and self.LSerial.SerialIsOpen(): self.LSerial.close() # 初始化串口实例 serialport = self.mPortVal.currentText() serialbaudrate = self.mBPSVal.currentText() self.LSerial = SerialPort(serialport, serialbaudrate) # 创建工作线程并传递串口实例 self.thread = Worker(self.LSerial) self.thread.update_signal.connect(self.CanOBDdatarefresh) self.thread.start() ``` ### 问题原因解释 1. **缓冲区残留数据**: - 当CAN停止发送后,缓冲区中仍有未处理数据 - 这些数据会被持续解析并刷新到界面 2. **工作线程未停止**: - 工作线程持续发射信号触发刷新 - 即使没有新数据,界面仍会尝试刷新旧数据 3. **超时检测不够灵敏**: - 原始超时阈值(1.5秒)和检测次数(3次)导致响应延迟 - 改为1秒单次超时检测提高响应速度 ### 优化效果 1. **快速响应**:数据停止后1秒内停止刷新 2. **资源释放**:清空缓冲区并停止工作线程 3. **界面清理**:超时后自动清空界面显示 4. **降低CPU**:非活跃状态降低线程轮询频率 ### 注意事项 1. 确保所有全局变量(`frame_buffer`, `CanOBDItemList`)在清除后被正确重置 2. 工作线程需要接收串口实例以检查数据活跃状态 3. 界面刷新方法应处理空数据情况
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值