QSerialPort with no GUI, no thread: QObject::startTimer: Timers can only be used with threads started with QThread

345 views Asked by At

I'm doing a DLL with no GUI (TEMPLATE = lib), using QSerialPort. I don't create threads and I don't need any: I have no GUI and having a blocking serial port operation is no problem, it is what I want.

When doing:

while (!serial_uart->isWritable());
while (!serial_uart->write(frame));

I get:

QObject::startTimer: Timers can only be used with threads started with QThread

Question: how to use QSerialPort in a library without GUI without triggering this error?

Note: I first thought the problem was coming from serial_uart->waitForReadyRead(timeout) but even without this and only serial_uart->write() I already have this problem.


Minimal reproducible DLL example:

test.cpp

#include "test.h"
extern "C" {
    __declspec(dllexport) Test* new_Test() { return new Test(); }
    __declspec(dllexport) void DoTest(Test *t) { t->DoTest(); }
}
Test::Test() :QObject()
{
    qDebug("Hello");
}
void Test::DoTest()
{
    this->serialport = new QSerialPort();
    this->serialport ->setPortName("COM12");
    this->serialport->setBaudRate(QSerialPort::Baud19200);
    this->serialport->open(QIODevice::ReadWrite);
    while (!this->serialport->isWritable());
    while (!this->serialport->write("hello"));
}

test.h

#include <QSerialPort>
class Test : public QObject
{
    Q_OBJECT
public:
    Test();
    void DoTest();
    QSerialPort *serialport;
};

test.pro

TEMPLATE = lib
TARGET = test
QT += serialport
INCLUDEPATH += .
HEADERS += test.h
SOURCES += test.cpp

When I call the release/test.dll from Python I have this:

from ctypes import *
dll = CDLL(r"release\test.dll")
dll.new_Test.restype = c_void_p
dll.new_Test.argtypes = []
dll.DoTest.restype = None
dll.DoTest.argtypes = [c_void_p]
t = dll.new_Test()
dll.DoTest(t)

Hello

QObject::startTimer: Timers can only be used with threads started with QThread

1

There are 1 answers

4
parti82 On

Most of the QIODevice based classes (like Qt sockets or serial port) want to live in a Qt based thread and also their functions needs to be called from the same thread where the object was created.

For that reason I've usually solved this by:

  1. Create wrapper class (QObject based with Q_OBJECT macro for signal/slot functionality) for the QIODevice based class you are about to use. For each function you are planning on using create a slot function on your wrapper class which then calls the equivalent funtion in the QIODevice:

     qint64 MySerialPort::write(const QByteArray &data)
     {
         // m_serialPort is created with new QSerialPort in constructor of MySerialPort.
         return m_serialPort->write(data);
     }
    
  2. Create a QThread class that in its run function creates an instance of MySerialPort (with new MySerialPort) and just calls exec() after that. Now MySerialPort lives in an event loop and is able to send and receive signals/slots.

     void MySerialPortThread::run()
     {
         m_serialPort = new MySerialPort();
         exec();
         delete m_serialPort; // Automatic deletion after thread is stopped.
     }
    

The thread could also return a pointer to the instance for easier access from outside to connect signals and slots.

MySerialPort* MySerialPortThread::serialPort()
{
    return m_serialPort; // Instance of  MySerialPort class
}
  1. In your main code create signals that match the slots of the MySerialPort and connect them.

     signals:
         qint64 writeSerial(const QByteArray& data);
    
     void MyMainClass::connectSignalsAndSlots()
     {
         MySerialPort* serialPort = m_serialThread->serialPort();
         connect(this, &MyMainClass::writeSerial, serialPort, &MySerialPort::write, Qt::BlockingQueuedConnection); // Use either QueuedConnection or BlockingQueuedConnection to force the execution of the slot to the MySerialThread. 
     }
    
  2. Emit the signals to access the QSerialPort.

     emit writeSerial(dataByteArray);