Skip to content
  • Categories
  • Recent
  • Tags
  • Popular
  • Users
  • Groups
  • Search
  • Get Qt Extensions
  • Unsolved
Collapse
Brand Logo
  1. Home
  2. Qt Development
  3. General and Desktop
  4. QSerialPort and threads

QSerialPort and threads

Scheduled Pinned Locked Moved General and Desktop
9 Posts 4 Posters 7.8k Views
  • Oldest to Newest
  • Newest to Oldest
  • Most Votes
Reply
  • Reply as topic
Log in to reply
This topic has been deleted. Only users with topic management privileges can see it.
  • M Offline
    M Offline
    mojo_risin
    wrote on last edited by
    #1

    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

    1 Reply Last reply
    0
    • mrdebugM Offline
      mrdebugM Offline
      mrdebug
      wrote on last edited by
      #2

      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.

      Need programmers to hire?
      www.labcsp.com
      www.denisgottardello.it
      GMT+1
      Skype: mrdebug

      1 Reply Last reply
      0
      • M Offline
        M Offline
        mojo_risin
        wrote on last edited by
        #3

        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.

        1 Reply Last reply
        0
        • K Offline
          K Offline
          kuzulis
          Qt Champions 2020
          wrote on last edited by
          #4

          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!

          1 Reply Last reply
          0
          • M Offline
            M Offline
            mojo_risin
            wrote on last edited by
            #5

            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.

            1 Reply Last reply
            0
            • mrdebugM Offline
              mrdebugM Offline
              mrdebug
              wrote on last edited by
              #6

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

              Need programmers to hire?
              www.labcsp.com
              www.denisgottardello.it
              GMT+1
              Skype: mrdebug

              1 Reply Last reply
              0
              • JKSHJ Offline
                JKSHJ Offline
                JKSH
                Moderators
                wrote on last edited by
                #7

                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)

                Qt Doc Search for browsers: forum.qt.io/topic/35616/web-browser-extension-for-improved-doc-searches

                1 Reply Last reply
                0
                • M Offline
                  M Offline
                  mojo_risin
                  wrote on last edited by
                  #8

                  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?

                  1 Reply Last reply
                  0
                  • M Offline
                    M Offline
                    mojo_risin
                    wrote on last edited by
                    #9

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

                    1 Reply Last reply
                    0

                    • Login

                    • Login or register to search.
                    • First post
                      Last post
                    0
                    • Categories
                    • Recent
                    • Tags
                    • Popular
                    • Users
                    • Groups
                    • Search
                    • Get Qt Extensions
                    • Unsolved