如何使用串行向我的 PyQt5 窗口添加实时(更新)图?

Posted

技术标签:

【中文标题】如何使用串行向我的 PyQt5 窗口添加实时(更新)图?【英文标题】:How to add a live (updating) graph to my PyQt5 Window using Serial? 【发布时间】:2020-05-26 20:52:42 【问题描述】:

我正在编写一个程序,它应该每秒从我的 Arduino 接收 4 个随机值,并用它们创建一个更新的条形图。然后我想创建一个 PyQt5 窗口来显示该条形图。我成功地读取了来自我的 Arduino 的值并使用它们创建条形图。但是,由于我是 GUI/Arduino 的初学者,所以我不知道如何将该条形图添加到我的 PyQt 窗口中。如果您能帮我解决这个问题,我将不胜感激。

这是我的 Arduino 代码:

void setup() 
  Serial.begin(115200);


void loop() 
  float nb1 = random(0, 100);
  float nb2 = random(0, 100);
  float nb3 = random(0, 100);
  float nb4 = random(0, 100);

  Serial.print(nb1);
  Serial.print(" , ");
  Serial.print(nb2);
  Serial.print(" , ");
  Serial.print(nb3);
  Serial.print(" , ");
  Serial.println(nb4);

  delay(1000);    


这是图表的外观(标题和标签是法语,但不要介意):

import serial
import numpy
import matplotlib.pyplot as plt
from drawnow import *

plt.ion()

def make_figure():
    plt.ylim(0, 100)
    plt.title("Titre que tu veux")
    plt.tick_params(axis = 'x', which = 'both', bottom = False, top = False, labelbottom = False)
    plt.hlines(16, -0.5, 0.5, color = 'g')
    plt.ylabel("Nom de la variable")
    plt.bar(0, nb1, color = 'b')

    plt.bar(2, nb2, color = 'b')
    plt.hlines(42, 1.5, 2.5, color = 'g')

    plt.bar(4, nb3, color = 'b')
    plt.hlines(32, 3.5, 4.5, color = 'g')

    plt.bar(6, nb4, color = 'b')
    plt.hlines(80, 5.5, 6.5, color = 'g')



with serial.Serial('COM3', baudrate = 115200) as ser:
    while True:
        while (ser.inWaiting() == 0):
            pass
        string_received = ser.readline().decode().strip('\r\n')
        dataArray = string_received.split(" , ")
        nb1 = float(dataArray[0])
        nb2 = float(dataArray[1])
        nb3 = float(dataArray[2])
        nb4 = float(dataArray[3])

        drawnow(make_figure)
        plt.pause(0.000001)

现在,我的窗口应该是这样的,但是用动态图表替换静态图表(不要介意按钮,它们现在不打算做任何事情):


import sys
from PyQt5 import QtCore, QtGui, QtWidgets
from PyQt5.QtWidgets import QApplication, QWidget, QHBoxLayout
import matplotlib.pyplot as plt
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg
from matplotlib.figure import Figure


class MplCanvas(FigureCanvasQTAgg):
    def __init__(self, parent=None, width=5, height=4, dpi=100):
        fig = Figure(figsize=(width, height), dpi=dpi)
        self.axes = fig.add_subplot(111)
        super(MplCanvas, self).__init__(fig)

class Ui_MainWindow(object):
    def setupUi(self, MainWindow):
        MainWindow.setObjectName("Titre que tu veux")
        MainWindow.resize(1510, 600)
        self.centralwidget = QtWidgets.QWidget(MainWindow)
        self.centralwidget.setObjectName("centralwidget")
        self.pushgetMax = QtWidgets.QPushButton(self.centralwidget)
        self.pushgetMax.setGeometry(QtCore.QRect(1240, 30, 93, 28))
        self.pushgetMax.setObjectName("pushgetMax")
        self.pushAll = QtWidgets.QPushButton(self.centralwidget)
        self.pushAll.setGeometry(QtCore.QRect(1350, 30, 93, 28))
        self.pushAll.setObjectName("pushAll")
        self.pushButton1 = QtWidgets.QPushButton(self.centralwidget)
        self.pushButton1.setGeometry(QtCore.QRect(1240, 70, 93, 28))
        self.pushButton1.setObjectName("pushButton1")
        self.pushButton2 = QtWidgets.QPushButton(self.centralwidget)
        self.pushButton2.setGeometry(QtCore.QRect(1350, 70, 93, 28))
        self.pushButton2.setObjectName("pushButton2")
        self.pushButton3 = QtWidgets.QPushButton(self.centralwidget)
        self.pushButton3.setGeometry(QtCore.QRect(1240, 110, 93, 28))
        self.pushButton3.setObjectName("pushButton3")
        self.pushButton_6 = QtWidgets.QPushButton(self.centralwidget)
        self.pushButton_6.setGeometry(QtCore.QRect(1350, 110, 93, 28))
        self.pushButton_6.setObjectName("pushButton_6")
        self.horizontalLayoutWidget = QtWidgets.QWidget(self.centralwidget)
        self.horizontalLayoutWidget.setGeometry(QtCore.QRect(20, 10, 1191, 531))
        self.horizontalLayoutWidget.setObjectName("horizontalLayoutWidget")
        self.horizontalLayout = QtWidgets.QHBoxLayout(self.horizontalLayoutWidget)
        self.horizontalLayout.setContentsMargins(0, 0, 0, 0)
        self.horizontalLayout.setObjectName("horizontalLayout")


        barre1 = MplCanvas(self, width=5, height=4, dpi=100)
        barre1.axes.set_ylim([0, 100])
        barre1.axes.set_title('Titre 1')
        barre1.axes.tick_params(axis = 'x', which = 'both', bottom = False, top = False, labelbottom = False)
        barre1.axes.bar(0, 22, color = 'b')
        barre1.axes.hlines(25, -0.5, 0.5, color = 'g')

        barre1.axes.bar(2, 50, color = 'b')
        barre1.axes.hlines(60, 1.5, 2.5, color = 'g')

        barre1.axes.bar(4, 32, color = 'b')
        barre1.axes.hlines(50, 3.5, 4.5, color = 'g')

        barre1.axes.bar(6, 81, color = 'b')
        barre1.axes.hlines(70, 5.5, 6.5, color = 'g')


        self.horizontalLayout.addWidget(barre1)


        MainWindow.setCentralWidget(self.centralwidget)
        self.menubar = QtWidgets.QMenuBar(MainWindow)
        self.menubar.setGeometry(QtCore.QRect(0, 0, 1510, 26))
        self.menubar.setObjectName("menubar")
        MainWindow.setMenuBar(self.menubar)
        self.statusbar = QtWidgets.QStatusBar(MainWindow)
        self.statusbar.setObjectName("statusbar")
        MainWindow.setStatusBar(self.statusbar)

        self.retranslateUi(MainWindow)
        QtCore.QMetaObject.connectSlotsByName(MainWindow)



    def retranslateUi(self, MainWindow):
        _translate = QtCore.QCoreApplication.translate
        MainWindow.setWindowTitle(_translate("MainWindow", "MainWindow"))
        self.pushgetMax.setText(_translate("MainWindow", "GetMax"))
        self.pushAll.setText(_translate("MainWindow", "All"))
        self.pushButton1.setText(_translate("MainWindow", "1"))
        self.pushButton2.setText(_translate("MainWindow", "2"))
        self.pushButton3.setText(_translate("MainWindow", "3"))
        self.pushButton_6.setText(_translate("MainWindow", "4"))


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

【问题讨论】:

【参考方案1】:

如果您打算使用 PyQt5,那么请忘记 pyplot,因为您必须使用相应的画布。另一方面,不要使用 pyserial,因为使用 QSerialPort 会更好,因为它使用 Qt 事件循环。

import sys

from PyQt5 import QtCore, QtGui, QtWidgets, QtSerialPort

from matplotlib.backends.backend_qt5agg import FigureCanvas
from matplotlib.figure import Figure


class SerialPortManager(QtCore.QObject):
    dataChanged = QtCore.pyqtSignal(list)

    def __init__(self, parent=None):
        super().__init__(parent)

        self._serial = QtSerialPort.QSerialPort(baudRate=115200)
        self.serial.setPortName("COM3")
        self.serial.readyRead.connect(self.on_ready_read)

    @property
    def serial(self):
        return self._serial

    def start(self):
        self.serial.open(QtCore.QIODevice.ReadOnly)

    @QtCore.pyqtSlot()
    def on_ready_read(self):
        if self.serial.canReadLine():
            line = self.serial.readLine().data().decode()
            values = line.strip().split(",")
            try:
                data = list(map(float, values))
            except ValueError as e:
                print("error", e)
            else:
                self.dataChanged.emit(data)


class MainWindow(QtWidgets.QMainWindow):
    def __init__(self, parent=None):
        super().__init__(parent)

        fig = Figure(figsize=(5, 4), dpi=100)
        self.canvas = FigureCanvas(fig)

        self.max_button = QtWidgets.QPushButton(self.tr("GetMax"))
        self.all_button = QtWidgets.QPushButton(self.tr("All"))
        self.one_button = QtWidgets.QPushButton(self.tr("1"))
        self.two_button = QtWidgets.QPushButton(self.tr("2"))
        self.three_button = QtWidgets.QPushButton(self.tr("3"))
        self.four_button = QtWidgets.QPushButton(self.tr("4"))

        central_widget = QtWidgets.QWidget()
        self.setCentralWidget(central_widget)
        hlay = QtWidgets.QHBoxLayout(central_widget)
        hlay.addWidget(self.canvas, stretch=1)

        grid_layout = QtWidgets.QGridLayout()
        grid_layout.addWidget(self.max_button, 0, 0)
        grid_layout.addWidget(self.all_button, 0, 1)
        grid_layout.addWidget(self.one_button, 1, 0)
        grid_layout.addWidget(self.two_button, 1, 1)
        grid_layout.addWidget(self.three_button, 2, 0)
        grid_layout.addWidget(self.four_button, 2, 1)

        vlay = QtWidgets.QVBoxLayout()
        vlay.addLayout(grid_layout)
        vlay.addStretch()

        hlay.addLayout(vlay)

        self.axes = self.canvas.figure.add_subplot(111)
        self.axes.set_ylim([0, 100])
        self.axes.set_title("Titre 1")
        self.axes.tick_params(
            axis="x", which="both", bottom=False, top=False, labelbottom=False
        )

        self.axes.hlines(25, -0.5, 0.5, color="g")
        self.axes.hlines(60, 1.5, 2.5, color="g")
        self.axes.hlines(50, 3.5, 4.5, color="g")
        self.axes.hlines(70, 5.5, 6.5, color="g")

        self.containers = []

        self.update_bars([0, 0, 0, 0])

        self.resize(640, 480)

    @QtCore.pyqtSlot(list)
    def update_bars(self, values):
        if len(values) == 4:
            [c.remove() for c in self.containers]
            self.containers = []
            for index, value in zip((0, 2, 4, 6), values):
                c = self.axes.bar(index, value, color="b")
                self.containers.append(c)
            self.canvas.draw()


if __name__ == "__main__":
    import sys

    app = QtWidgets.QApplication(sys.argv)

    w = MainWindow()
    w.show()

    manager = SerialPortManager()
    manager.dataChanged.connect(w.update_bars)
    manager.start()

    sys.exit(app.exec_())

【讨论】:

非常感谢!我会调查的!

以上是关于如何使用串行向我的 PyQt5 窗口添加实时(更新)图?的主要内容,如果未能解决你的问题,请参考以下文章

PyQT5 实时更新图

如何实时更新 PyQt5 标签?

如何在python中使用pyqt5将QSlider添加到主窗口的工具栏

在 Python 中使用 Qt Designer 接口实时读取串行数据

PyQt5如何在一个窗口中显示不同的页面?

解析来自串行窗口的 json 响应