'PyQt5 QTimer in not working after moving it to a thread

I am currently trying to separate my PyQT5-GUI from my serial communication to prevent a freezing GUI.

Therefore, I tried to implement threading. So when I am pressing a button "Open Port", a new thread starts which handles this incoming and outgoing data of the Port.

Now, this works fine, but I am having problems with a timer not fulfilling its function. Essentially, I want to close the port after a 'no valid' message has been received for x seconds.

I tried to create a minimum example:

Upon starting, the GUI and the thread are created. When pressing "Open", the port is opened and the timer in the thread should start. After 3000 milliseconds, the port should be closed by the timer overflow.

from PyQt5 import QtGui, QtWidgets
from PyQt5.QtCore import *
from PyQt5.QtWidgets import QApplication, QMainWindow
from PyQt5 import QtSerialPort

import sys


class AECRobotControl(QMainWindow):
    signal_open_port = pyqtSignal(str)
    signal_close_port = pyqtSignal(int)

    def __init__(self):
        super().__init__()
        # main-window setup
        self.ui = Ui_RobotControls_Main()
        self.ui.setupUi(self)

        # Port
        self.port_gcode_timer_sending = QTimer()  # Timer for sending Gcode to prevent robot queue overflow
        # END Port

        # Threads
        self.thread_port = QThread()
        self.thread_port_worker = None
        # END Threads

        # END Variables

        # Functions
        # Init the ui with standard values
        # self.init_ui()
        # Init COM-worker
        self.init_worker()
        # Init signal callbacks
        self.init_signals()

        # show GUI
        self.show()
        # END Functions

    def init_ui(self):
        self.layout.addWidget(self.button_open)
        self.layout.addWidget(self.button_close)
        self.setLayout(self.layout)

    def init_signals(self):
        # Button Open Port
        self.ui.pushButton_open_port.clicked.connect(self.button_open_comport)
        # END Button Open Port

        # Button Close Port
        self.ui.pushButton_close_port.clicked.connect(self.button_close_comport)
        # END Button Close Port

    def init_worker(self):
        self.thread_port_worker = RobotMessageThread()
        self.thread_port_worker.moveToThread(self.thread_port)
        self.thread_port.started.connect(self.thread_port_worker.start_thread)
        self.thread_port.finished.connect(self.thread_port.deleteLater)

        self.thread_port_worker.finished.connect(self.thread_port.quit)
        self.thread_port_worker.finished.connect(self.thread_port_worker.deleteLater)

        self.signal_open_port.connect(self.thread_port_worker.open_port_slot)
        self.signal_close_port.connect(self.thread_port_worker.close_comport)

        self.thread_port.start()

    def button_open_comport(self):
        self.signal_open_port.emit("COM4")

    def button_close_comport(self):
        if (self.thread_port.isRunning() == True):
            self.signal_close_port.emit(0)

    def parse_com_message(self, message):
        try:
            print(message)
        except Exception as e:
            print(e)


class RobotMessageThread(QObject):
    finished = pyqtSignal()
    progress = pyqtSignal(int)
    com_message_parsed = pyqtSignal(QByteArray)
    com_ascii_message_parsed = pyqtSignal(str)

    def __init__(self):
        super().__init__()
        self.port_com_port = QtSerialPort.QSerialPort()
        self.port_name = None
        self.port_is_alive_timer = QTimer()  # Interprets valid received messages as alive-sign from robot.

    """ Functions to be called upon start of thread"""
    def start_thread(self):
        print("Thread started")
        self.port_is_alive_timer.timeout.connect(lambda: self.close_comport(1))

    """ Inits the port"""
    def _init_port(self):
        self.port_com_port.setPortName(self.port_name)
        self.port_com_port.setBaudRate(QtSerialPort.QSerialPort.BaudRate.Baud115200)
        self.port_com_port.setParity(QtSerialPort.QSerialPort.Parity.NoParity)
        self.port_com_port.setDataBits(QtSerialPort.QSerialPort.DataBits.Data8)
        self.port_com_port.setStopBits(QtSerialPort.QSerialPort.StopBits.OneStop)

    @pyqtSlot(bytearray)
    def message_slot(self, message: bytearray):
        self._write_to_port(message)

    @pyqtSlot(str)
    def open_port_slot(self, com_name: str):
        self.port_name = com_name
        self._init_port()
        self._open_comport()

    @pyqtSlot()
    def close_port_slot(self, message: bytearray):
        self.close_comport(0)

    """ Tries to open the selected comport"""
    def _open_comport(self):
        # Check whether port is already open
        if self.port_com_port.open(QIODevice.ReadWrite) == True:
            self.port_com_port.setDataTerminalReady(True)
            print("COM Opened")

            # Reset message-buffer
            self.port_is_alive_timer.start(3000)
        else:
            print("opening failed")

    """ Closes the selected comport"""
    def close_comport(self, source):
        if self.port_com_port.isOpen() == True:
            # Close port and delete queue
            self.port_com_port.clear()
            self.port_com_port.close()

            # Stop timers
            self.port_is_alive_timer.stop()
            print("Closed by " + str(source))
        else:
            print("Closing failed")

# GUI
class Ui_RobotControls_Main(object):
    def setupUi(self, RobotControls_Main):
        RobotControls_Main.setObjectName("RobotControls_Main")
        RobotControls_Main.resize(1024, 900)
        RobotControls_Main.setMinimumSize(QSize(1024, 900))
        RobotControls_Main.setMaximumSize(QSize(1600, 1200))
        self.centralwidget = QtWidgets.QWidget(RobotControls_Main)
        self.centralwidget.setObjectName("centralwidget")
        self.gridLayout_12 = QtWidgets.QGridLayout(self.centralwidget)
        self.gridLayout_12.setObjectName("gridLayout_12")
        self.QGroupBox_port_settings = QtWidgets.QGroupBox(self.centralwidget)
        self.QGroupBox_port_settings.setObjectName("QGroupBox_port_settings")
        self.gridLayout_15 = QtWidgets.QGridLayout(self.QGroupBox_port_settings)
        self.gridLayout_15.setObjectName("gridLayout_15")
        self.horizontalLayout_21 = QtWidgets.QHBoxLayout()
        self.horizontalLayout_21.setObjectName("horizontalLayout_21")
        self.pushButton_open_port = QtWidgets.QPushButton(self.QGroupBox_port_settings)
        self.pushButton_open_port.setMaximumSize(QSize(100, 50))
        self.pushButton_open_port.setObjectName("pushButton_open_port")
        self.horizontalLayout_21.addWidget(self.pushButton_open_port)
        self.gridLayout_15.addLayout(self.horizontalLayout_21, 0, 0, 1, 1)
        self.horizontalLayout_4 = QtWidgets.QHBoxLayout()
        self.horizontalLayout_4.setObjectName("horizontalLayout_4")
        self.pushButton_close_port = QtWidgets.QPushButton(self.QGroupBox_port_settings)
        self.pushButton_close_port.setObjectName("pushButton_close_port")
        self.horizontalLayout_4.addWidget(self.pushButton_close_port)
        self.gridLayout_15.addLayout(self.horizontalLayout_4, 0, 1, 1, 1)
        self.gridLayout_12.addWidget(self.QGroupBox_port_settings, 0, 0, 1, 1)
        RobotControls_Main.setCentralWidget(self.centralwidget)
        self.statusbar = QtWidgets.QStatusBar(RobotControls_Main)
        self.statusbar.setObjectName("statusbar")
        RobotControls_Main.setStatusBar(self.statusbar)

        self.retranslateUi(RobotControls_Main)
        QMetaObject.connectSlotsByName(RobotControls_Main)

    def retranslateUi(self, RobotControls_Main):
        _translate = QCoreApplication.translate
        RobotControls_Main.setWindowTitle(_translate("RobotControls_Main", "RobotControls"))
        self.QGroupBox_port_settings.setTitle(_translate("RobotControls_Main", "Port settings"))
        self.pushButton_open_port.setText(_translate("RobotControls_Main", "Open Port"))
        self.pushButton_close_port.setText(_translate("RobotControls_Main", "Close Port"))



if __name__ == '__main__':
    app = QApplication(sys.argv)

    main_window = AECRobotControl()
    app.exec()  # QT main-threadtimer.start(TIME)


Solution 1:[1]

I found a solution. In the above code, just one line needs to be added: after moving the worker to the separate thread, the QTimer also has to be moved to the same thread. So, after self.thread_port_worker.moveToThread(self.thread_port), the line self.thread_port_worker.port_is_alive_timer.moveToThread(self.thread_port) needs to be added

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 user44791