pythonpyqtpyqt5qthreadqtserialport

doesn't update serial data using button click


I have a problem with the gui data, my gui doesn't update a real-time value when I click the button. the first time i click my connect button it shows the correct value, but when i change the sensor position, it doesn't update the value. Where i miss the code, i try to solve the problem from the other similar question to this question, but still does not solve my problem

here is my code

class SerialReadThread(QThread):
received_data = pyqtSignal(QByteArray, name="receivedData")

def __init__(self, serial):
    QThread.__init__(self)
    self.cond = QWaitCondition()
    self._status = False
    self.mutex = QMutex()
    self.serial = serial

def __del__(self):
    self.wait()

def run(self):
    while True:
        self.mutex.lock()
        if not self._status:
            self.cond.wait(self.mutex)

        buf = self.serial.read(14)
        if buf:
            self.received_data.emit(buf)
        self.sleep(1)
        self.mutex.unlock()

def toggle_status(self):
    self._status = not self._status
    if self._status:
        self.cond.wakeAll()

@pyqtSlot(bool, name='setStatus')
def set_status(self, status):
    self._status = status
    if self._status:
        self.cond.wakeAll()

class Form(QDialog):
   received_data = pyqtSignal(QByteArray, name="receivedData")

def __init__(self):
    super().__init__()
    self.ui = Ui_Dialog()
    self.ui.setupUi(self)
    self.show()

    self.serial = QSerialPort()
    self.serial_info = QSerialPortInfo()
    self._fill_serial_info()
    self.ui.btnConnect.clicked.connect(self.slot_clicked_connect_button)
    self.serial_read_thread = SerialReadThread(self.serial)
    self.serial_read_thread.received_data.connect(lambda v: self.received_data.emit(v))

@staticmethod
def get_port_path():
    return {"linux": '/dev/ttyS', "win32": 'COM'}[__platform__]

def _get_available_port(self):
    available_port = list()
    port_path = self.get_port_path()

    for number in range(255):
        port_name = port_path + str(number)
        if not self._open(port_name):
            continue
        available_port.append(port_name)
        self.serial.close()
    return available_port

def _fill_serial_info(self):
    self.ui.cmbPort.insertItems(0, self._get_available_port())

def _open(self, port_name, baudrate=QSerialPort.Baud9600):
    info = QSerialPortInfo(port_name)
    self.serial.setPort(info)
    self.serial.setBaudRate(baudrate)
    return self.serial.open(QIODevice.ReadWrite)

def connect_serial(self):
    serial_info = {"port_name": self.ui.cmbPort.currentText()}
    status = self._open(**serial_info)
    self.received_data.connect(self.read_data)
    self.serial_read_thread.start()
    self.serial_read_thread.setStatus(status)
    return status

def disconnect_serial(self):
    return self.serial.close()

@pyqtSlot(QByteArray, name="readData")
def read_data(self, rd):
    rd = str(binascii.hexlify(rd), 'ascii', 'replace')
    if rd.startswith("68"):
        val = rd[10:14]
        self.ui.txtRaw.insertPlainText(val)
        self.ui.txtRaw.insertPlainText("\n")

@pyqtSlot(name="clickedConnectButton")
def slot_clicked_connect_button(self):
    if self.serial.isOpen():
        self.disconnect_serial()
    else:
        self.connect_serial()
    self.ui.btnConnect.setText({False: 'Connect', True: 'Stop'}  [self.serial.isOpen()])

and here is my gui

enter image description here


Solution

  • It is not necessary to use a thread for this case besides that your implementation has at least one problem: QSerialPort is a QObject that should only be used in a single thread since it is not thread-safe, this belongs to the GUI thread since there It was created but you use it in another thread.

    In this case you must use the readyRead signal from QSerialPort:

    import binascii
    from PyQt5 import QtCore, QtGui, QtWidgets, QtSerialPort
    
    from dialog_ui import Ui_Dialog
    
    
    class Dialog(QtWidgets.QDialog):
        def __init__(self, parent=None):
            super(Dialog, self).__init__(parent)
            self.ui = Ui_Dialog()
            self.ui.setupUi(self)
            self.ui.btnConnect.clicked.connect(self.slot_clicked_connect_button)
            self.serial = QtSerialPort.QSerialPort(self)
            self.serial.readyRead.connect(self.onReadyRead)
            self._fill_serial_info()
    
        def onReadyRead(self):
            while self.serial.bytesAvailable() >= 14:
                buff = self.serial.read(14)
                rd = str(binascii.hexlify(buff), "ascii", "replace")
                if rd.startswith("68"):
                    val = rd[10:14]
                    self.ui.txtRaw.insertPlainText(val)
                    self.ui.txtRaw.insertPlainText("\n")
    
        def _fill_serial_info(self):
            self.ui.cmbPort.clear()
            for info in QtSerialPort.QSerialPortInfo.availablePorts():
                self.ui.cmbPort.addItem(info.portName())
    
        def _open(self, port_name, baudrate=QtSerialPort.QSerialPort.Baud9600):
            info = QtSerialPort.QSerialPortInfo(port_name)
            self.serial.setPort(info)
            self.serial.setBaudRate(baudrate)
            return self.serial.open(QtCore.QIODevice.ReadWrite)
    
        def connect_serial(self):
            serial_info = {"port_name": self.ui.cmbPort.currentText()}
            status = self._open(**serial_info)
            return status
    
        def disconnect_serial(self):
            return self.serial.close()
    
        @QtCore.pyqtSlot(name="clickedConnectButton")
        def slot_clicked_connect_button(self):
            if self.serial.isOpen():
                self.disconnect_serial()
            else:
                self.connect_serial()
            self.ui.btnConnect.setText(
                "Stop" if self.serial.isOpen() else "Connect"
            )
    
    
    if __name__ == "__main__":
        import sys
    
        app = QtWidgets.QApplication(sys.argv)
        w = Dialog()
        w.resize(640, 480)
        w.show()
        sys.exit(app.exec_())