pythonpyqt5serial-portsignals-slotsqiodevice

PyQt readyRead: set text from serial to multiple labels


In PyQt5, I want to read my serial port after writing (requesting a value) to it. I've got it working using readyRead.connect(self.readingReady), but then I'm limited to outputting to only one text field.

The code for requesting parameters sends a string to the serial port. After that, I'm reading the serial port using the readingReady function and printing the result to a plainTextEdit form.

def read_configuration(self):
    if self.serial.isOpen():
        self.serial.write(f"?request1\n".encode())
        self.label_massGainOutput.setText(f"{self.serial.readAll().data().decode()}"[:-2])
        self.serial.write(f"?request2\n".encode())
        self.serial.readyRead.connect(self.readingReady)
        self.serial.write(f"?request3\n".encode())
        self.serial.readyRead.connect(self.readingReady)

def readingReady(self):
    data = self.serial.readAll()
    if len(data) > 0:
        self.plainTextEdit_commandOutput.appendPlainText(f"{data.data().decode()}"[:-2])
    else: self.serial.flush()

The problem I have, is that I want every answer from the serial port to go to a different plainTextEdit form. The only solution I see now is to write a separate readingReady function for every request (and I have a lot! Only three are shown now). This must be possible in a better way. Maybe using arguments in the readingReady function? Or returning a value from the function that I can redirect to the correct form?

Without using the readyRead signal, all my values are one behind. So the first request prints nothing, the second prints the first etc. and the last is not printed out.

Does someone have a better way to implement this functionality?


Solution

  • If reqests are independent of each other you can use request queue, provide callback for each request and switch this callbacks inside readyRead slot.

    class RequestQueue:
        def __init__(self, serial):
            self._queue = []
            self._started = False
            self._handler = self._defaultHandler
            self._serial = serial
            serial.readyRead.connect(self._onReadyRead)
            self._message = b''
            
        def _isCompleteMessage(self, data):
            return True
    
        def _onReadyRead(self):
            data = self._serial.readAll()
            self._message += data.data()
            if not self._isCompleteMessage(self._message):
                return
            self._handler(self._message)
            self._message = b''
            self._handler = self._defaultHandler
            self._sendNextRequest()
    
        def _defaultHandler(self, data):
            pass
    
        def _sendNextRequest(self):
            if len(self._queue) < 1:
                self._started = False
                self._handler = self._defaultHandler
                return
            self._started = True
            (data, handler) = self._queue[0]
            self._queue = self._queue[1:]
            self._handler = handler
            self._serial.write(data)
    
        def enqueue(self, data, handler):
            self._queue.append((data, handler))
            if not self._started:
                self._sendNextRequest()
    
    if __name__ == "__main__":
        ...
        queue = RequestQueue(serial)
        queue.enqueue(f"?request1\n".encode(), lambda data: ui.label1.setText(data.decode()))
        queue.enqueue(f"?request2\n".encode(), lambda data: ui.label2.setText(data.decode()))
        queue.enqueue(f"?request3\n".encode(), lambda data: ui.label3.setText(data.decode()))
    

    Notice that this queue lives and operates in the same thread, since it uses asyncronous api.