PyQT实时显示串口数据在QtCore.QTimer.singleShot()上抛出最大递归深度超出异常

Posted

技术标签:

【中文标题】PyQT实时显示串口数据在QtCore.QTimer.singleShot()上抛出最大递归深度超出异常【英文标题】:PyQT real time display of serial port data throwing maximum recursion depth exceeded exception on QtCore.QTimer.singleShot() 【发布时间】:2018-12-16 11:21:17 【问题描述】:

我的应用场景:

从串口读取连续数据(实时)并显示在 PyQt GUI 上,并将数据存储在 csv 文件中。

我曾尝试使用 While 循环。

所以,我在二手方面尝试过 QtCore.QTimer.singleShot(1, methodname)。哪个抛出递归异常。

我的代码:

# -*- coding: utf-8 -*-

# Form implementation generated from reading ui file 'D:\TUK\Master Thesis\ML\Lable_Data.ui'
#
# Created by: PyQt5 UI code generator 5.11.2
#
# WARNING! All changes made in this file will be lost!

from PyQt5 import QtCore, QtGui, QtWidgets
import traceback
import os
import sys
import serial
import atexit
import time

write_path = "recording_folder1/"

port = "COM6"  # COM for windows, it changes when we use unix system
#ser = serial.Serial(port, 115200, timeout=None)

class Ui_MainWindow(object):
    def setupUi(self, MainWindow):
        MainWindow.setObjectName("MainWindow")
        MainWindow.resize(850, 568)
        icon = QtGui.QIcon()
        icon.addPixmap(QtGui.QPixmap("C:/Users/HackoMan/Google Drive/MasterThesis/Code/QtDesigner/Icon/research4-1.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off)
        MainWindow.setWindowIcon(icon)
        self.centralwidget = QtWidgets.QWidget(MainWindow)
        self.centralwidget.setObjectName("centralwidget")
        self.gridLayout = QtWidgets.QGridLayout(self.centralwidget)
        self.gridLayout.setObjectName("gridLayout")
        self.DataLabelingTabObj = QtWidgets.QTabWidget(self.centralwidget)
        self.DataLabelingTabObj.setObjectName("DataLabelingTabObj")
        self.tab = QtWidgets.QWidget()
        self.tab.setObjectName("tab")
        self.formLayoutWidget = QtWidgets.QWidget(self.tab)
        self.formLayoutWidget.setGeometry(QtCore.QRect(10, 10, 811, 481))
        self.formLayoutWidget.setObjectName("formLayoutWidget")
        self.gridLayout_2 = QtWidgets.QGridLayout(self.formLayoutWidget)
        self.gridLayout_2.setSizeConstraint(QtWidgets.QLayout.SetMaximumSize)
        self.gridLayout_2.setContentsMargins(0, 0, 0, 0)
        self.gridLayout_2.setObjectName("gridLayout_2")
        self.horizontalLayout = QtWidgets.QHBoxLayout()
        self.horizontalLayout.setObjectName("horizontalLayout")
        self.startCaptureBtn = QtWidgets.QPushButton(self.formLayoutWidget)
        self.startCaptureBtn.setObjectName("startCaptureBtn")
        self.horizontalLayout.addWidget(self.startCaptureBtn)
        self.stopCaptureBtn = QtWidgets.QPushButton(self.formLayoutWidget)
        self.stopCaptureBtn.setEnabled(False)
        self.stopCaptureBtn.setObjectName("stopCaptureBtn")
        self.horizontalLayout.addWidget(self.stopCaptureBtn)
        self.gridLayout_2.addLayout(self.horizontalLayout, 3, 1, 1, 1)
        self.fileNameLabel = QtWidgets.QLabel(self.formLayoutWidget)
        self.fileNameLabel.setObjectName("fileNameLabel")
        self.gridLayout_2.addWidget(self.fileNameLabel, 0, 0, 1, 1)
        self.fileNameLineEdit = QtWidgets.QLineEdit(self.formLayoutWidget)
        self.fileNameLineEdit.setObjectName("fileNameLineEdit")
        self.gridLayout_2.addWidget(self.fileNameLineEdit, 0, 1, 1, 1)
        self.label = QtWidgets.QLabel(self.formLayoutWidget)
        self.label.setObjectName("label")
        self.gridLayout_2.addWidget(self.label, 2, 0, 1, 1)
        self.classLabelLabel = QtWidgets.QLabel(self.formLayoutWidget)
        self.classLabelLabel.setObjectName("classLabelLabel")
        self.gridLayout_2.addWidget(self.classLabelLabel, 1, 0, 1, 1)
        self.classLabelLineEdit = QtWidgets.QLineEdit(self.formLayoutWidget)
        self.classLabelLineEdit.setObjectName("classLabelLineEdit")
        self.gridLayout_2.addWidget(self.classLabelLineEdit, 1, 1, 1, 1)
        self.sensorTextEdit = QtWidgets.QTextEdit(self.formLayoutWidget)
        self.sensorTextEdit.setReadOnly(True)
        self.sensorTextEdit.setObjectName("sensorTextEdit")
        self.gridLayout_2.addWidget(self.sensorTextEdit, 2, 1, 1, 1)
        self.label_2 = QtWidgets.QLabel(self.formLayoutWidget)
        self.label_2.setObjectName("label_2")
        self.gridLayout_2.addWidget(self.label_2, 3, 0, 1, 1)
        self.horizontalLayout_8 = QtWidgets.QHBoxLayout()
        self.horizontalLayout_8.setObjectName("horizontalLayout_8")
        self.resetBtn = QtWidgets.QPushButton(self.formLayoutWidget)
        self.resetBtn.setObjectName("resetBtn")
        self.horizontalLayout_8.addWidget(self.resetBtn)
        self.closeBtn = QtWidgets.QPushButton(self.formLayoutWidget)
        self.closeBtn.setObjectName("closeBtn")
        self.horizontalLayout_8.addWidget(self.closeBtn)
        self.gridLayout_2.addLayout(self.horizontalLayout_8, 4, 1, 1, 1)
        self.DataLabelingTabObj.addTab(self.tab, "")
        self.tab_2 = QtWidgets.QWidget()
        self.tab_2.setObjectName("tab_2")
        self.DataLabelingTabObj.addTab(self.tab_2, "")
        self.gridLayout.addWidget(self.DataLabelingTabObj, 0, 1, 1, 1)
        MainWindow.setCentralWidget(self.centralwidget)
        self.statusbar = QtWidgets.QStatusBar(MainWindow)
        self.statusbar.setObjectName("statusbar")
        MainWindow.setStatusBar(self.statusbar)

        self.retranslateUi(MainWindow)
        self.DataLabelingTabObj.setCurrentIndex(0)
        QtCore.QMetaObject.connectSlotsByName(MainWindow)

        self.addButtonOperations()
        self.addFiledsValidators()

    def retranslateUi(self, MainWindow):
        _translate = QtCore.QCoreApplication.translate
        MainWindow.setWindowTitle(_translate("MainWindow", "Data Collection"))
        self.startCaptureBtn.setToolTip(_translate("MainWindow", "click to capture the sensor data"))
        self.startCaptureBtn.setWhatsThis(_translate("MainWindow", "click to capture the sensor data"))
        self.startCaptureBtn.setText(_translate("MainWindow", "Start"))
        self.stopCaptureBtn.setText(_translate("MainWindow", "Stop"))
        self.fileNameLabel.setText(_translate("MainWindow", "FileName"))
        self.label.setText(_translate("MainWindow", "Sensor Data"))
        self.classLabelLabel.setText(_translate("MainWindow", "ClassLabel"))
        self.label_2.setText(_translate("MainWindow", "Capture Sensor Data"))
        self.resetBtn.setText(_translate("MainWindow", "Reset"))
        self.closeBtn.setText(_translate("MainWindow", "Close"))
        self.DataLabelingTabObj.setTabText(self.DataLabelingTabObj.indexOf(self.tab), _translate("MainWindow", "Data labeling"))
        self.DataLabelingTabObj.setTabText(self.DataLabelingTabObj.indexOf(self.tab_2), _translate("MainWindow", "Classifier"))

    def addButtonOperations(self):
        self.closeBtn.clicked.connect(self.closeWindow)
        self.resetBtn.clicked.connect(self.resetFields)
        self.startCaptureBtn.clicked.connect(self.startCaptureBtnPressedEvent)
        self.stopCaptureBtn.clicked.connect(self.stopCaptureSensorData)

    def addFiledsValidators(self):

        regexp = QtCore.QRegExp('[a-zA-Z0-9_ -]+')
        validator = QtGui.QRegExpValidator(regexp)

        self.fileNameLineEdit.setValidator(validator)
        self.classLabelLineEdit.setValidator(validator)


    def captureSensorData(self, file):

        current_label = self.classLabelLineEdit.text()

        # new_line = ser.readline().decode('utf-8').rstrip()
        new_line = "test1, Test2, Test3"
        new_line = new_line + ',' + current_label + '\n'

        file.write(new_line)
        file.flush()

        self.sensorTextEdit.append(new_line)
        self.sensorTextEdit.repaint()

        if not self.startCaptureBtn.isEnabled():
            QtCore.QTimer.singleShot(1, self.captureSensorData(file))
        else:
            file.close()

    def startCaptureBtnPressedEvent(self):
        try:
            if(self.isValidFields()):
                # capture the sensor data
                print("#capturing the sensor data")
                self.startCaptureBtn.setEnabled(False)
                self.stopCaptureBtn.setEnabled(True)

                self.startCaptureBtn.repaint()
                self.stopCaptureBtn.repaint()

                file = self.openFileForWriting()  # Create/Open file for saving sensor data

                if not self.startCaptureBtn.isEnabled():
                    QtCore.QTimer.singleShot(1, self.captureSensorData(file))


            else:
                # throw error message
                print("#throw error message")
                msgBox = QtWidgets.QMessageBox()
                msgBox.setText("Please fill the required fields ")
                msgBox.setWindowTitle("Warning !! ")
                msgBox.setIcon(QtWidgets.QMessageBox.Warning)
                msgBox.exec()
        except:
            traceback.print_exc()


    def openFileForWriting(self):
        os.makedirs(os.path.dirname(write_path), exist_ok=True)
        file_name = self.fileNameLineEdit.text()
        file_name  = write_path + file_name + ".csv"
        file = open(file_name, 'a')
        return file

    def stopCaptureSensorData(self):
        # stop capture the sensor data
        print("# stop capturing the sensor data")
        self.startCaptureBtn.setEnabled(True)
        self.stopCaptureBtn.setEnabled(False)

        self.startCaptureBtn.repaint()
        self.stopCaptureBtn.repaint()



    def isValidFields(self):
        if(self.fileNameLineEdit.text().__len__()<=0 or self.classLabelLineEdit.text().__len__()<=0):
            return False
        else:
            return True

    def resetFields(self):
        self.fileNameLineEdit.clear()
        self.classLabelLineEdit.clear()
        self.sensorTextEdit.clear()

    def closeWindow(self):
        QtCore.QCoreApplication.instance().quit()


    def releaseResource(self):
        print("Release Resources !!! ")
        #if ser.isOpen():
        #    ser.close()

if __name__ == "__main__":
    app = QtWidgets.QApplication(sys.argv)
    MainWindow = QtWidgets.QMainWindow()
    ui = Ui_MainWindow()
    ui.setupUi(MainWindow)
    MainWindow.show()
    atexit.register(ui.releaseResource)
    sys.exit(app.exec_())

如果你做了类似的操作,比如读取数据库数据和在 GUI 上显示...等,或者是否有人在 pyqt 中找到了解决方案,请告诉我。

错误信息:

【问题讨论】:

在您对QTimer.singleShot 的调用中,您实际上将调用captureSensorData(file) 的结果作为第二个参数传递,而不仅仅是对方法本身的引用。因此递归。 顺便说一句,如果您的串行端口是QSerialPort,那么您最好连接到其QIODevice::readyRead 信号并在必要时读取数据,而不是使用计时器轮询。跨度> @G.M.你能建议代码更改吗?这对我有很大帮助。 @G.M.: hv warning @HovercraftFullOfEels 我觉得他在帮助我。请稍等,不要下结论说每个人都是假的。 【参考方案1】:

根据我在上面尝试过的没有任何参数的评论之一,问题已解决。使用 captureSensorData 而不是 captureSensorData(file)

解决方案:

# -*- coding: utf-8 -*-

# Form implementation generated from reading ui file 'D:\TUK\Master Thesis\ML\Lable_Data.ui'
#
# Created by: PyQt5 UI code generator 5.11.2
#
# WARNING! All changes made in this file will be lost!

from PyQt5 import QtCore, QtGui, QtWidgets
import traceback
import os
import sys
import serial
import atexit


write_path = "recording_folder1/"

port = "COM6"  # COM for windows, it changes when we use unix system
ser = serial.Serial(port, 115200, timeout=None)
file = None

class Ui_MainWindow(object):
    def setupUi(self, MainWindow):
        MainWindow.setObjectName("MainWindow")
        MainWindow.resize(850, 568)
        icon = QtGui.QIcon()
        icon.addPixmap(QtGui.QPixmap("C:/Users/HackoMan/Google Drive/MasterThesis/Code/QtDesigner/Icon/research4-1.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off)
        MainWindow.setWindowIcon(icon)
        self.centralwidget = QtWidgets.QWidget(MainWindow)
        self.centralwidget.setObjectName("centralwidget")
        self.gridLayout = QtWidgets.QGridLayout(self.centralwidget)
        self.gridLayout.setObjectName("gridLayout")
        self.DataLabelingTabObj = QtWidgets.QTabWidget(self.centralwidget)
        self.DataLabelingTabObj.setObjectName("DataLabelingTabObj")
        self.tab = QtWidgets.QWidget()
        self.tab.setObjectName("tab")
        self.formLayoutWidget = QtWidgets.QWidget(self.tab)
        self.formLayoutWidget.setGeometry(QtCore.QRect(10, 10, 811, 481))
        self.formLayoutWidget.setObjectName("formLayoutWidget")
        self.gridLayout_2 = QtWidgets.QGridLayout(self.formLayoutWidget)
        self.gridLayout_2.setSizeConstraint(QtWidgets.QLayout.SetMaximumSize)
        self.gridLayout_2.setContentsMargins(0, 0, 0, 0)
        self.gridLayout_2.setObjectName("gridLayout_2")
        self.horizontalLayout = QtWidgets.QHBoxLayout()
        self.horizontalLayout.setObjectName("horizontalLayout")
        self.startCaptureBtn = QtWidgets.QPushButton(self.formLayoutWidget)
        self.startCaptureBtn.setObjectName("startCaptureBtn")
        self.horizontalLayout.addWidget(self.startCaptureBtn)
        self.stopCaptureBtn = QtWidgets.QPushButton(self.formLayoutWidget)
        self.stopCaptureBtn.setEnabled(False)
        self.stopCaptureBtn.setObjectName("stopCaptureBtn")
        self.horizontalLayout.addWidget(self.stopCaptureBtn)
        self.gridLayout_2.addLayout(self.horizontalLayout, 3, 1, 1, 1)
        self.fileNameLabel = QtWidgets.QLabel(self.formLayoutWidget)
        self.fileNameLabel.setObjectName("fileNameLabel")
        self.gridLayout_2.addWidget(self.fileNameLabel, 0, 0, 1, 1)
        self.fileNameLineEdit = QtWidgets.QLineEdit(self.formLayoutWidget)
        self.fileNameLineEdit.setObjectName("fileNameLineEdit")
        self.gridLayout_2.addWidget(self.fileNameLineEdit, 0, 1, 1, 1)
        self.label = QtWidgets.QLabel(self.formLayoutWidget)
        self.label.setObjectName("label")
        self.gridLayout_2.addWidget(self.label, 2, 0, 1, 1)
        self.classLabelLabel = QtWidgets.QLabel(self.formLayoutWidget)
        self.classLabelLabel.setObjectName("classLabelLabel")
        self.gridLayout_2.addWidget(self.classLabelLabel, 1, 0, 1, 1)
        self.classLabelLineEdit = QtWidgets.QLineEdit(self.formLayoutWidget)
        self.classLabelLineEdit.setObjectName("classLabelLineEdit")
        self.gridLayout_2.addWidget(self.classLabelLineEdit, 1, 1, 1, 1)
        self.sensorTextEdit = QtWidgets.QTextEdit(self.formLayoutWidget)
        self.sensorTextEdit.setReadOnly(True)
        self.sensorTextEdit.setObjectName("sensorTextEdit")
        self.gridLayout_2.addWidget(self.sensorTextEdit, 2, 1, 1, 1)
        self.label_2 = QtWidgets.QLabel(self.formLayoutWidget)
        self.label_2.setObjectName("label_2")
        self.gridLayout_2.addWidget(self.label_2, 3, 0, 1, 1)
        self.horizontalLayout_8 = QtWidgets.QHBoxLayout()
        self.horizontalLayout_8.setObjectName("horizontalLayout_8")
        self.resetBtn = QtWidgets.QPushButton(self.formLayoutWidget)
        self.resetBtn.setObjectName("resetBtn")
        self.horizontalLayout_8.addWidget(self.resetBtn)
        self.closeBtn = QtWidgets.QPushButton(self.formLayoutWidget)
        self.closeBtn.setObjectName("closeBtn")
        self.horizontalLayout_8.addWidget(self.closeBtn)
        self.gridLayout_2.addLayout(self.horizontalLayout_8, 4, 1, 1, 1)
        self.DataLabelingTabObj.addTab(self.tab, "")
        self.tab_2 = QtWidgets.QWidget()
        self.tab_2.setObjectName("tab_2")
        self.DataLabelingTabObj.addTab(self.tab_2, "")
        self.gridLayout.addWidget(self.DataLabelingTabObj, 0, 1, 1, 1)
        MainWindow.setCentralWidget(self.centralwidget)
        self.statusbar = QtWidgets.QStatusBar(MainWindow)
        self.statusbar.setObjectName("statusbar")
        MainWindow.setStatusBar(self.statusbar)

        self.retranslateUi(MainWindow)
        self.DataLabelingTabObj.setCurrentIndex(0)
        QtCore.QMetaObject.connectSlotsByName(MainWindow)

        self.addButtonOperations()
        self.addFiledsValidators()

    def retranslateUi(self, MainWindow):
        _translate = QtCore.QCoreApplication.translate
        MainWindow.setWindowTitle(_translate("MainWindow", "Data Collection"))
        self.startCaptureBtn.setToolTip(_translate("MainWindow", "click to capture the sensor data"))
        self.startCaptureBtn.setWhatsThis(_translate("MainWindow", "click to capture the sensor data"))
        self.startCaptureBtn.setText(_translate("MainWindow", "Start"))
        self.stopCaptureBtn.setText(_translate("MainWindow", "Stop"))
        self.fileNameLabel.setText(_translate("MainWindow", "FileName"))
        self.label.setText(_translate("MainWindow", "Sensor Data"))
        self.classLabelLabel.setText(_translate("MainWindow", "ClassLabel"))
        self.label_2.setText(_translate("MainWindow", "Capture Sensor Data"))
        self.resetBtn.setText(_translate("MainWindow", "Reset"))
        self.closeBtn.setText(_translate("MainWindow", "Close"))
        self.DataLabelingTabObj.setTabText(self.DataLabelingTabObj.indexOf(self.tab), _translate("MainWindow", "Data labeling"))
        self.DataLabelingTabObj.setTabText(self.DataLabelingTabObj.indexOf(self.tab_2), _translate("MainWindow", "Classifier"))

    def addButtonOperations(self):
        self.closeBtn.clicked.connect(self.closeWindow)
        self.resetBtn.clicked.connect(self.resetFields)
        self.startCaptureBtn.clicked.connect(self.startCaptureBtnPressedEvent)
        self.stopCaptureBtn.clicked.connect(self.stopCaptureSensorData)

    def addFiledsValidators(self):

        regexp = QtCore.QRegExp('[a-zA-Z0-9_ -]+')
        validator = QtGui.QRegExpValidator(regexp)

        self.fileNameLineEdit.setValidator(validator)
        self.classLabelLineEdit.setValidator(validator)


    def captureSensorData(self):
        try:

            current_label = self.classLabelLineEdit.text()

            #new_line = ser.readline().decode('utf-8').rstrip()
            new_line = "test1, Test2, Test3"
            new_line = new_line + ',' + current_label + '\n'

            self.file.write(new_line)
            self.file.flush()

            self.sensorTextEdit.append(new_line)
            self.sensorTextEdit.repaint()

            if not self.startCaptureBtn.isEnabled():
                QtCore.QTimer.singleShot(1, self.captureSensorData)
            else:
                self.file.close()
        except:
            traceback.print_exc()

    def startCaptureBtnPressedEvent(self):
        try:
            if(self.isValidFields()):
                # capture the sensor data
                print("#capturing the sensor data")
                self.startCaptureBtn.setEnabled(False)
                self.stopCaptureBtn.setEnabled(True)

                self.startCaptureBtn.repaint()
                self.stopCaptureBtn.repaint()

                self.file = self.openFileForWriting()  # Create/Open file for saving sensor data

                if not self.startCaptureBtn.isEnabled():
                    QtCore.QTimer.singleShot(1, self.captureSensorData)


            else:
                # throw error message
                print("#throw error message")
                msgBox = QtWidgets.QMessageBox()
                msgBox.setText("Please fill the required fields ")
                msgBox.setWindowTitle("Warning !! ")
                msgBox.setIcon(QtWidgets.QMessageBox.Warning)
                msgBox.exec()
        except:
            traceback.print_exc()


    def openFileForWriting(self):
        os.makedirs(os.path.dirname(write_path), exist_ok=True)
        file_name = self.fileNameLineEdit.text()
        file_name  = write_path + file_name + ".csv"
        file = open(file_name, 'a')
        return file

    def stopCaptureSensorData(self):
        # stop capture the sensor data
        print("# stop capturing the sensor data")
        self.startCaptureBtn.setEnabled(True)
        self.stopCaptureBtn.setEnabled(False)

        self.startCaptureBtn.repaint()
        self.stopCaptureBtn.repaint()



    def isValidFields(self):
        if(self.fileNameLineEdit.text().__len__()<=0 or self.classLabelLineEdit.text().__len__()<=0):
            return False
        else:
            return True

    def resetFields(self):
        self.fileNameLineEdit.clear()
        self.classLabelLineEdit.clear()
        self.sensorTextEdit.clear()

    def closeWindow(self):
        QtCore.QCoreApplication.instance().quit()


    def releaseResource(self):
        print("Release Resources !!! ")
        if ser.isOpen():
            ser.close()

if __name__ == "__main__":
    app = QtWidgets.QApplication(sys.argv)
    MainWindow = QtWidgets.QMainWindow()
    ui = Ui_MainWindow()
    ui.setupUi(MainWindow)
    MainWindow.show()
    atexit.register(ui.releaseResource)
    sys.exit(app.exec_())

【讨论】:

以上是关于PyQT实时显示串口数据在QtCore.QTimer.singleShot()上抛出最大递归深度超出异常的主要内容,如果未能解决你的问题,请参考以下文章

用Python串口实时显示数据并绘图pyqtgraph(详细教程)

Qt中关于QTime类中QTime(int h, int m, int s=0, int ms = 0)

串口屏开发之曲线控件的使用总结——如何将实时采集的数据连成曲线显示在屏幕上

串口屏开发之曲线控件的使用总结——如何将实时采集的数据连成曲线显示在屏幕上

Android(Linux)实时监测串口数据

如何在从串口python pyqt接收的textbrowser pyqt5上附加串行数据[重复]