Как добавить динамический (обновляющий) график в мое окно PyQt5 с помощью последовательного порта? - PullRequest
0 голосов
/ 26 мая 2020

Я пишу программу, которая должна получать 4 случайных значения от моего Arduino каждую секунду и создавать с ними гистограмму обновления. Затем я хочу создать окно 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)

Вот как должно выглядеть мое окно, но замените диаграмму stati c на динамическую c (не обращайте внимания на кнопки, они не предназначены для чего-либо прямо сейчас):


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 Ответ

1 голос
/ 27 мая 2020

Если вы собираетесь использовать PyQt5, забудьте о pyplot, так как вам нужно использовать соответствующий холст. С другой стороны, не используйте pyserial, так как лучше использовать QSerialPort, поскольку он использует событие Qt l oop.

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_())
...