QSerialPort and threads



  • Hello all
    I am trying to create an application that will plot some data from a serial plot. I can get the serial port and the plot working so far (using QCustomPlot), but the application gets a little slow, depending on the data exchanged. I read that I should move the serial port to separate thread in this topic: http://qt-project.org/forums/viewthread/36805
    I have been trying to understand how to do it for a while; I do not have any background in PC programming, the only time I had to use threads was when doing an Android app (it was pretty easy). Now I have really difficult time understanding Workers and Threads in Qt. I am also reading this article: http://mayaposch.wordpress.com/2011/11/01/how-to-really-truly-use-qthreads-the-full-explanation/

    As far as I understand the topic, I should create a Worker class that wraps a QSerialPort object, and my main object will have a pointer variable to this class. Then in my button clicked slot() I will initialize this pointer and use its moveToThread() method. The Worker class needs to open the serial port, which will be a pointer in that class.
    What I do not understand is how will I close the serial port, and how to exchange data between my main class and the Worker class, that has the QSerialPort pointer. The other thing that I don't know is how to delete the pointers after the port is closed.

    I am sure that it looks that I have no idea what is going on. It might be true; just started with QThreads this morning. I would really appreciate some clarification about the purpose of the Worker object, and a general method for implementing this task. Not looking for a complete solution, just directions.

    Thanks



  • Please, have a look at this:
    http://kde-apps.org/content/show.php/?content=142378
    to have an idea on how to manage serial ports with Qt.



  • Hi, I downloaded the code, but it's way too advanced for me. I am really a beginner.

    Here is what I try to do:

    The worker object:
    @SerialWorker::SerialWorker(QObject *parent, QString portName, int baudRate, QSerialPort::DataBits dataBits, QSerialPort::Parity parity, QSerialPort::StopBits stopBits) :
    QObject(parent)
    {
    this->portName = portName;
    this->baudRate = baudRate;
    this->dataBits = dataBits;
    this->parity = parity;
    this->stopBits = stopBits;

    port = new QSerialPort(QSerialPortInfo(portName));
    port->setBaudRate(baudRate);
    port->setDataBits(dataBits);
    port->setParity(parity);
    port->setStopBits(stopBits);
    

    }

    SerialWorker::~SerialWorker()
    {
    qDebug() << "Destructor running!";
    }

    void SerialWorker::openPort()
    {
    qDebug() << "Open Port Runs!";

    // Try to open port
    if(port->open(QIODevice::ReadOnly)) {
        emit portOpen();
        qDebug() << "Port open!";
    } else {
        emit portOpenFail();
        qDebug() << "Port cannot open!";
        emit finished();
    }
    

    }

    void SerialWorker::closePort()
    {
    port->close();
    qDebug() << "Port closed!";
    emit portClosed();
    emit finished();
    }@

    Using it:
    @void MainWindow::on_connectButton_clicked()
    {
    if(connected) {
    port->closePort();
    connected = false;
    ui->connectButton->setText("Connect");
    } else {
    QThread *thread = new QThread;
    port = new SerialWorker(0, "COM6", 9600, QSerialPort::Data8, QSerialPort::NoParity, QSerialPort::OneStop);
    port->moveToThread(thread);
    connect(thread, SIGNAL(started()), port, SLOT(openPort()));
    connect(port, SIGNAL(finished()), thread, SLOT(quit()));
    // connect(port, SIGNAL(finished()), port, SLOT(deleteLater()));
    // connect(port, SIGNAL(finished()), thread, SLOT(deleteLater()));

        thread->start();
        connected = true;
    
        ui->connectButton->setText("Disconnect");
    }
    

    }
    @

    I get
    QWinEventNotifier: event notifiers cannot be enabled from another thread
    QWinEventNotifier: event notifiers cannot be enabled from another thread

    But If I use the serial port this way I will need to provide way for communicating data to the class that is using it. It looks like a great overcomplication, so I think I am on the wrong way.



  • In your case the QSerialPort belongs to main thread, but worker object to second thread.

    Just re-write to:

    @
    port = new QSerialPort(QSerialPortInfo(portName), this);
    @

    because QSerialPort should has the worker as parent object!



  • I managed to fix it to a certain point, thanks to the input from kuzulis. My code now is:
    The wrapper
    @#include "serialportw.hpp"

    SerialPortW::SerialPortW(QSerialPortInfo portInfo, int baudRate, QSerialPort::DataBits dataBits, QSerialPort::Parity parity, QSerialPort::StopBits stopBits, QObject *parent) :
    QObject(parent), portInfo(portInfo), baudRate(baudRate), dataBits(dataBits), parity(parity), stopBits(stopBits), running(false)
    {
    qDebug() << "Constructing serial port for " << portInfo.portName();
    }

    SerialPortW::~SerialPortW()
    {
    qDebug() << "Destructing serial port for " << portInfo.portName();
    if(port) delete port;
    }

    void SerialPortW::process() {

    port = new QSerialPort(portInfo, this);
    port->setBaudRate(baudRate);
    port->setParity(parity);
    port->setDataBits(dataBits);
    port->setStopBits(stopBits);
    connect(port, SIGNAL(readyRead()), this, SLOT(readyRead()));
    
    if(port->open(QIODevice::ReadWrite)) {
        emit portOpenOK(); 
        running = true;
    } else {
        emit portOpenFail();
        running = false;
    }
    
    while(running) {
        thread()->msleep(1000);
        qDebug() << "Calling from inside serial port thread.";
    }
    
    if(port) {
        port->close();
    }
    
    emit finished();
    

    }

    void SerialPortW::closePort()
    {
    emit portClosed();
    running = false;
    }

    void SerialPortW::readyRead()
    {
    QByteArray data = port->readAll();
    qDebug() << data;
    }
    @

    How it is called from the GUI thread:
    @QThread *thread = new QThread();
    port = new SerialPortW(portInfo, baudRate, datab, paritySelection, stopBitsSelection);

        connect(port, SIGNAL(portOpenOK()), this, SLOT(portOpenedSuccess()));
        connect(port, SIGNAL(portOpenFail()), this, SLOT(portOpenedFail()));
        connect(port, SIGNAL(portClosed()), this, SLOT(portClosed()));
    
        port->moveToThread(thread);
        connect(thread, SIGNAL(started()), port, SLOT(process()));
        connect(port, SIGNAL(finished()), thread, SLOT(quit()));
        connect(port, SIGNAL(finished()), port, SLOT(deleteLater()));
        connect(thread, SIGNAL(finished()), thread, SLOT(deleteLater()));
        thread->start();@
    

    The pointer *port is a member of my View class. So far the application opens and closes the serial ports fine; however, it cannot read from them. The signal readyRead() is never emitted. I actually find a post with the same problem:
    http://www.wenda.io/questions/2938793/using-qserialport-on-a-separate-thread.html
    but it does not have answers. Can someone please take a look at my code? I guess it would be simple mistake, but it's giving me real hard time.



  • Please, have a look at the file qthcomport.h and qthcomport.cpp


  • Moderators

    Hi,

    The key to your problem is "Signal-Slot Connections":http://doc.qt.io/qt-5/signalsandslots.html. This is a core feature of Qt that doesn't exist in "raw" C++.

    [quote author="mojo_risin" date="1419348978"]I would really appreciate some clarification about the purpose of the Worker object[/quote]The purpose of creating a Worker is to have QObject that lives in the other thread. A QObject's slots are invoked in the thread that it lives in. This characteristic facilitates inter-thread communication.

    See "Thread Affinity":http://doc.qt.io/qt-5/qobject.html#thread-affinity for details.

    [quote author="mojo_risin" date="1419348978"]What I do not understand is how will I close the serial port, and how to exchange data between my main class and the Worker class, that has the QSerialPort pointer. The other thing that I don't know is how to delete the pointers after the port is closed.[/quote]Make the Worker responsible for creating and destroying the QSerialPort.

    The Worker communicates with the GUI thread using signals and slots. For example, your Worker could have a signal that carries plottable data:

    @
    class SerialPortW : public QObject {
    Q_OBJECT

    signals:
    void dataReceived(const QVector<double>& data);
    };
    @
    When your worker reads some data from the serial port, it interprets the data and put them in a QVector. Then, it emits the dataReceived signal along with the data vector. Another QObject in your GUI thread receives the signal (in other words, the dataReceived signal invokes a slot in the GUI thread object). The receiving slot then plots the data.

    Communication works the other way too -- QObjects in the GUI thread can emit signals that are connected to the worker's slots.

    [quote author="mojo_risin" date="1419369784"]
    @
    while(running) {
    thread()->msleep(1000);
    qDebug() << "Calling from inside serial port thread.";
    }
    @
    [/quote]This while-loop is effectively an infinite loop. The infinite loop blocks the event loop that QObjects use for communication -- you must not create infinite loops in a Worker QObject.

    To use a Worker QObject, you need to use event-driven programming. Every function in the Worker should be short. Public functions should not be called directly by other classes. Instead, the Worker's public functions should be converted into slots, and be triggered by signals from other classes.

    (Alternatively, the other classes could use "invokeMethod()":http://doc.qt.io/qt-5/qmetaobject.html#invokeMethod instead of emitting a signal, but this is a bit more advanced)



  • Thanks to the above post, I have the serial port working. I removed the infinite loop, and my code is:
    @void SerialPortW::process() {

    port = new QSerialPort(portInfo, this);
    
    if(port->open(QIODevice::ReadOnly)) {
        if(! port->setBaudRate(baudRate)) {
            qDebug() << "Baud Rate Error::" << port->errorString();
        }
        if(!port->setParity(parity)) {
            qDebug() << "Parity Error:" << port->errorString();
        }
        if(!port->setDataBits(dataBits)) {
            qDebug() << "Data Bits Error::" << port->errorString();
        }
        if(!port->setStopBits(stopBits)) {
            qDebug() << "Stop Bits Error:" << port->errorString();
        }
    
        connect(port, SIGNAL(readyRead()), this, SLOT(readyRead()));
        emit portOpenOK(); 
    
        qDebug() << "Port: " << port->portName() << "Mode: " << port->openMode();
    
    } else {
        emit portOpenFail();
    }
    

    }

    void SerialPortW::closePort()
    {
    if(port->isOpen()) port->close();
    emit portClosed();
    emit finished();
    }

    void SerialPortW::readyRead()
    {
    QByteArray data = port->readAll();
    qDebug() << data;
    }@

    I am not including constructor/destructor to avoid cluttering. In my main program, I have this code:
    @ QThread *thread = new QThread();
    port = new SerialPortW(portInfo, baudRate, datab, paritySelection, stopBitsSelection);
    port->moveToThread(thread);

        connect(port, SIGNAL(portOpenOK()), this, SLOT(portOpenedSuccess()), Qt::QueuedConnection);
        connect(port, SIGNAL(portOpenFail()), this, SLOT(portOpenedFail()), Qt::QueuedConnection);
        connect(port, SIGNAL(portClosed()), this, SLOT(portClosed()), Qt::QueuedConnection);
    
        connect(thread, SIGNAL(started()), port, SLOT(process()), Qt::QueuedConnection);
        connect(port, SIGNAL(finished()), thread, SLOT(quit()), Qt::QueuedConnection);
        connect(port, SIGNAL(finished()), port, SLOT(deleteLater()), Qt::QueuedConnection);
        connect(thread, SIGNAL(finished()), thread, SLOT(deleteLater()), Qt::QueuedConnection);
    
        thread->start();@
    

    Now I receive data from the serial port and I am able to drag the GUI across the screen without disrupting the communication. However, when I close the serial port from the main GUI, I get these messages:
    @QWinEventNotifier: event notifiers cannot be disabled from another thread
    QWinEventNotifier: event notifiers cannot be disabled from another thread@
    I read about this class at http://doc.qt.io/qt-5/qwineventnotifier.html#details but it does not say how to fix this issue. The message clearly says that the notifier's thread is not the current thread; so what can I do in this case?



  • Update: Everything works fine when I moved deleting the QSerialPort pointer to the destructor of my wrapper class; no more warnings.


Log in to reply
 

Looks like your connection to Qt Forum was lost, please wait while we try to reconnect.