1

I'm trying to get an output from an arduino to be updated in two pyqt windows, one of which is a matplotlib plot. If I comment out the non-plot thread window, the plot works. But when I try to run both at the same time, I get the message

3 Device is already open
3 Device is already open

but nothing shows up in the plot, though the window is there. Whereas the non-plot window is updated with values. I'm not sure if I should use QThreadpool instead of QThread. On a side note, if I try to rerun the code, I get the message

2 Access is denied.
2 Access is denied.
2 Access is denied.

so I have to unplug and replug in the arduino, uploading its code again, before running the python script again, and neither window is updated with values. The arduino code I use, to just give signals over 1 second intervals, to test it out, is

int PinOutput = 13;
int PinInput = A0;
int inph;
int inpl;

void setup() {
  // put your setup code here, to run once:
Serial.begin(9600);
pinMode(PinInput, INPUT);
pinMode(PinOutput, OUTPUT);

}

void loop() {
  // put your main code here, to run repeatedly:
inpl = analogRead(PinInput)/4;
Serial.println(inpl);
analogWrite(PinOutput,255);
delay(1000);
inph = analogRead(PinInput)/4;
Serial.println(inph);
analogWrite(PinOutput,0);
delay(1000);
}

Where pin 13 is connected to A0 on the arduino. The python code is

import sys

from PyQt5 import QtCore,  QtWidgets, QtSerialPort

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

seril_port_connection = QtSerialPort.QSerialPort("COM3")
seril_port_connection.setBaudRate(QtSerialPort.QSerialPort.Baud9600)

class MyThread2(QtCore.QThread):
    ard_signal = QtCore.pyqtSignal(str)

    
    def __init__(self):
        QtCore.QThread.__init__(self)
        self.serial_port = seril_port_connection
        self.serial_port.errorOccurred.connect(self.handle_error)
        self.serial_port.readyRead.connect(self.run)
        self.serial_port.open(QtCore.QIODevice.ReadWrite)
        
    def run(self):

        while self.serial_port.canReadLine():
            line = self.serial_port.readLine().data().decode().strip().strip('\x00')

            try:
                self.ard_signal.emit(str(line))

            except ValueError as e:
                print("error", e)
        
    def handle_error(self, error):
        if error == QtSerialPort.QSerialPort.NoError:
            return
        print(error, self.serial_port.errorString())

class Ui_MainWindow(object):
    def setupUi(self, MainWindow):
        MainWindow.setObjectName("MainWindow")
        MainWindow.resize(325, 237)
        self.centralwidget = QtWidgets.QWidget(MainWindow)
        self.centralwidget.setObjectName("centralwidget")
        self.label = QtWidgets.QLabel(self.centralwidget)
        self.label.setGeometry(QtCore.QRect(110, 20, 61, 16))
        self.label.setObjectName("label")
        self.textEdit = QtWidgets.QTextEdit(self.centralwidget)
        self.textEdit.setGeometry(QtCore.QRect(90, 60, 104, 71))
        self.textEdit.setObjectName("textEdit")
        self.pushButton = QtWidgets.QPushButton(self.centralwidget)
        self.pushButton.setGeometry(QtCore.QRect(100, 150, 75, 23))
        self.pushButton.setObjectName("pushButton")
        MainWindow.setCentralWidget(self.centralwidget)
        self.menubar = QtWidgets.QMenuBar(MainWindow)
        self.menubar.setGeometry(QtCore.QRect(0, 0, 325, 21))
        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", "test window"))
        self.label.setText(_translate("MainWindow", "pyqt5 tests"))
        self.pushButton.setText(_translate("MainWindow", "test button"))
        self.pushButton.clicked.connect(self.label_change)  
        self.thread_start = MyThread()
        self.thread_start.ard_signal.connect(self.label.setText)        
        self.thread_start.start()
    
  
    def label_change(self):
        self.pushButton.setText('Button Clicked!')
        self.textEdit.setText('taco')

class MainWindowm(QtWidgets.QMainWindow):
    def __init__(self, *args, **kwargs):
        super(MainWindowm, self).__init__(*args, **kwargs)

        self.canvas = FigureCanvasQTAgg(Figure(figsize=(5, 4), dpi=100))
        self.setCentralWidget(self.canvas)

        self.axes = self.canvas.figure.subplots()

        n_data = 10
        self.xdata = list(range(n_data))
        self.ydata = [0 for i in range(n_data)]

        self.thread_start = MyThread2()
        self.thread_start.ard_signal.connect(self.update_plot)
        self.thread_start.start()
        
    def handle_error(self, error):
        if error == QtSerialPort.QSerialPort.NoError:
            return
        print(error, self.serial_port.errorString())

    def update_plot(self, value):
        self.ydata = self.ydata[1:] + [float(value)]
        self.axes.cla()
        self.axes.plot(self.xdata, self.ydata, "r")
        self.canvas.draw()

class MyThread(QtCore.QThread):
    ard_signal = QtCore.pyqtSignal(str)

    
    def __init__(self):
        QtCore.QThread.__init__(self)
        self.serial_port = seril_port_connection
        self.serial_port.errorOccurred.connect(self.handle_error)
        self.serial_port.readyRead.connect(self.run)
        self.serial_port.open(QtCore.QIODevice.ReadWrite)
        
    def run(self):

        while self.serial_port.canReadLine():
            line = self.serial_port.readLine().data().decode().strip().strip('\x00')
            try:
                self.ard_signal.emit(str(line))
            except ValueError as e:
                print("error", e)
        
    def handle_error(self, error):
        if error == QtSerialPort.QSerialPort.NoError:
            return
        print(error, self.serial_port.errorString())


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

    sys.exit(app.exec_())

So I'm trying to make the arduino output available globally, to then be used by different threads at the same time, being updated in two windows, one with a plot. Any info on how to do this would help, thanks.

0

1 Answer 1

3

I assume that the OP is using my old answer, and in that solution indicate that the use of QSerialPort avoids the unnecessary use of threads, and this also applies in this case. On the other hand, a serial port cannot be handled by several classes. In this case, you just have to create a class that manages the serial port and distributes the information to the other elements.

import sys

from PyQt5 import QtCore, QtWidgets, QtSerialPort

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


class Ui_MainWindow(object):
    def setupUi(self, MainWindow):
        MainWindow.setObjectName("MainWindow")
        MainWindow.resize(325, 237)
        self.centralwidget = QtWidgets.QWidget(MainWindow)
        self.centralwidget.setObjectName("centralwidget")
        self.label = QtWidgets.QLabel(self.centralwidget)
        self.label.setGeometry(QtCore.QRect(110, 20, 61, 16))
        self.label.setObjectName("label")
        self.textEdit = QtWidgets.QTextEdit(self.centralwidget)
        self.textEdit.setGeometry(QtCore.QRect(90, 60, 104, 71))
        self.textEdit.setObjectName("textEdit")
        self.pushButton = QtWidgets.QPushButton(self.centralwidget)
        self.pushButton.setGeometry(QtCore.QRect(100, 150, 75, 23))
        self.pushButton.setObjectName("pushButton")
        MainWindow.setCentralWidget(self.centralwidget)
        self.menubar = QtWidgets.QMenuBar(MainWindow)
        self.menubar.setGeometry(QtCore.QRect(0, 0, 325, 21))
        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", "test window"))
        self.label.setText(_translate("MainWindow", "pyqt5 tests"))
        self.pushButton.setText(_translate("MainWindow", "test button"))


class MainWindowm(QtWidgets.QMainWindow):
    def __init__(self, *args, **kwargs):
        super(MainWindowm, self).__init__(*args, **kwargs)

        self.canvas = FigureCanvasQTAgg(Figure(figsize=(5, 4), dpi=100))
        self.setCentralWidget(self.canvas)

        self.axes = self.canvas.figure.subplots()

        n_data = 10
        self.xdata = list(range(n_data))
        self.ydata = [0 for i in range(n_data)]

    def update_plot(self, value):
        self.ydata = self.ydata[1:] + [value]
        self.axes.cla()
        self.axes.plot(self.xdata, self.ydata, "r")
        self.canvas.draw()


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

    def update_text(self, value):
        self.label.setNum(value)
        self.label.adjustSize()


class SerialManager(QtCore.QObject):
    valueChanged = QtCore.pyqtSignal(float)

    def __init__(self, parent=None):
        super().__init__(parent)
        self.serial_port = QtSerialPort.QSerialPort("COM3")
        self.serial_port.setBaudRate(QtSerialPort.QSerialPort.Baud9600)
        self.serial_port.errorOccurred.connect(self.handle_error)
        self.serial_port.readyRead.connect(self.handle_ready_read)
        self.serial_port.open(QtCore.QIODevice.ReadWrite)

    def handle_ready_read(self):
        while self.serial_port.canReadLine():
            codec = QtCore.QTextCodec.codecForName("UTF-8")
            line = codec.toUnicode(self.serial_port.readLine()).strip().strip("\x00")
            try:
                print(line)
                value = float(line)
            except ValueError as e:
                print("error", e)
            else:
                self.valueChanged.emit(value)

    def handle_error(self, error):
        if error == QtSerialPort.QSerialPort.NoError:
            return
        print(error, self.serial_port.errorString())


if __name__ == "__main__":
    app = QtWidgets.QApplication(sys.argv)
    w1 = MainWindow()
    w1.show()

    w = MainWindowm()
    w.show()

    manager = SerialManager()
    manager.valueChanged.connect(w1.update_text)
    manager.valueChanged.connect(w.update_plot)

    sys.exit(app.exec_())
Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.