[Solved] QtSerialPort and Threads



  • Hi.

    I want to ask something which i don't know if it's possible.

    Is it possible to create a single qserialport object in guithread and then implement one separate thread for sending data and a different one to receive data?
    To receive data i need blocking approach because i need it to only send a signal when i receive a complete and valid data frame.

    Thanks


  • Lifetime Qt Champion

    Hi,

    You don't need any additional threads for that. Just add a layer that handles your read/write operation and in that layer handle your special reading purpose and send a signal once you have what you want.

    Hope it helps



  • [quote author="SGaist" date="1370891902"]Hi,

    You don't need any additional threads for that. Just add a layer that handles your read/write operation and in that layer handle your special reading purpose and send a signal once you have what you want.

    Hope it helps[/quote]

    Hi, Thanks for your input.

    I'm new to Qt so i don't understand what you mean by add a layer.

    Can you try to explain or give me an example?


  • Lifetime Qt Champion

    Warning it's just some quick coding to give you the basic idea, it has not been compiled.

    @
    class SerialPortHandlingLayer : public QObject
    {
    Q_OBJECT
    public:
    SerialPortHandlingLayer(QtSerialPort *serialPort) :
    _serialPort(serialPort)
    {
    connect(_serialPort, SIGNAL(readyRead()), SLOT(onReadyRead()));
    }

    signals:
    void mySignalForWhenAllDataHaveArrived(ParameterType parameterName);

    public slots:
    void write(const QByteArray& data) // Not mandatory but it will keep all serial port related
    // reading and writing in the same place
    {
    _serialPort->write(data);
    }

    private slots:
    void onReadyRead()
    {
    // read data from _serialPort
    // check if i have everything
    // emit mySignalForWhenAllDataHaveArrived(parameter);
    // if needed some cleanup
    }

    private:
    QtSerialPort *_serialPort;
    };
    @



  • Thanks.
    Let´s see if i understood what you meant.

    In my:

    • mainwindow.h

    i declare:
    @QSerialPort *serial;@

    in:

    • mainwindow.cpp

    in the constructor i have:
    @ ui->setupUi(this);

    serial = new QSerialPort(this);
    
    openSerialPort("COM3");@
    
    • openSerialPort()

    @void MainWindow::openSerialPort(const QString &portName)
    {

    serial->setPortName(portName);
    if (serial->open(QIODevice::ReadWrite)) {
        if (serial->setBaudRate(QSerialPort::Baud19200)
                && serial->setDataBits(QSerialPort::Data8)
                && serial->setParity(QSerialPort::NoParity)
                && serial->setStopBits(QSerialPort::OneStop)
                && serial->setFlowControl(QSerialPort::NoFlowControl)) {
    
    
            ui->statusBar->showMessage(tr("Connected to %1 : %2, %3, %4, %5, %6")
                                       .arg(serial->portName()).arg(serial->baudRate()).arg(serial->dataBits())
                                       .arg(serial->parity()).arg(serial->stopBits()).arg(serial->flowControl()));
    
        } else {
            serial->close();
            QMessageBox::critical(this, tr("Error"), serial->errorString());
    
            ui->statusBar->showMessage(tr("Open error"));
        }
    } else {
        QMessageBox::critical(this, tr("Error"), serial->errorString());
    
        ui->statusBar->showMessage(tr("Configure error"));
    }
    

    }@

    and now i should create a new class like you told me to. Is this correct?

    Why do you have the implementation of write and onreadyread in the .h or this should be implemented in serialporthandlyinglayer.cpp?

    Also, i didn't understand what this does "SerialPortHandlingLayer(QtSerialPort *serialPort) :
    _serialPort(serialPort)"


  • Lifetime Qt Champion

    You can create the new class or do the handling in your MainWindow, personally I prefer to restrict the responsibilities of my classes to do one thing when possible.

    I've put all in the header because it was a quick way to show you the idea of the intermediate layer. Normally I would have put all the implementation in a cpp file.

    Then I strongly advise you to get a good C++ book. This is a constructor and the following line is initializing _serialPort with the content of serialPort.



  • Hi,

    Now i understood your approach on the subject. What you are suggesting me is to use the asynchronous approach. I already modified the terminal example to do it. But because of my gui i can't use it, because using it, everytime i receive a byte i will get a signal. Thats why i decided to use threads, so that only when i get the complete valid message the signal is emmitted.
    I also experimented this, modifying the blocking slave example.
    But because i need to send data after doing some calculations, i was trying to create a thread for sending data.
    But if i have a thread for receiving and another to send (the send will be in a wait condition) i will need to initialize the qserialport on the guithread. But i don't know how to use it inside the worker threads. Is this possible? Same qserialport object will be created in guithread and used on receive and send thread. Can this be done?

    I need to receive a data array, from a PIC18F, for example: 0 | 1 | 3 | 121 | 50 | 99 other is 0 | 2 | 2 | 10 | 150

    to detect the start i use 0, then message identifier (1 or 2), next the number of data bytes and finally the data bytes (data will change). I receive a complete data message every 10ms. After receiving it i make some calculations and send back to pic the result.
    I thought of using separate threads for receiving and send because for receiving i'm using the blocking method. And also because Serial com is full duplex, it should be possible to read and write at the same time.



  • bq. And also because Serial com is full duplex, it should be possible to read and write at the same time.

    Nobody forbids to do it to you in non-blocking (async) approach.

    bq. But i don’t know how to use it inside the worker threads. Is this possible?

    In your context of a question - No. It can't be done because QtSerialPort (as well as QAbstractSocket) to isn't thread safe.

    bq. I also experimented this, modifying the blocking slave example.

    Also available and simple Slave (non-blocking) example with async approach. You can look at it if your device has a slave role.

    PS: To you already above Mr. SGaist responded that - is better to use asynchronous approach. In my opinion his explanation is exhaustive.



  • Hi, Thanks kuzulis.

    I thought of using threads and blocking approach because if I'm processing received data i can't do anything else in my gui.

    As i mentioned i need to receive data, update gui, do some calculations and write back to pic. And i think that if i use async approach in guithread my app will be most of the time processing reception. Also i will receive data from another serial device.



  • UPD:

    bq. I need to receive a data array, from a PIC18F, for example: 0 | 1 | 3 | 121 | 50 | 99 other is 0 | 2 | 2 | 10 | 150

    bq. to detect the start i use 0, then message identifier (1 or 2), next the number of data bytes and finally the data bytes (data will change).

    I don't understand that to you is unclear?

    @

    ...
    enum ParserState { WaitForStart, WaitForMsgId, WaitForData, Done };

    static ParserState currentParserState = WaitForStart; // start synchronization
    ...

    ...
    connect(port, SIGNAL(readyRead()), this, SLOT(onParseInputStream()));
    ...

    SerialPortHandlingLayer::onParseInputStream()
    {
    while (port->bytesAvailable() > 0) {
    switch (currentParserState) {
    case WaitForStart:
    char c = 0;
    if (port->peek(&c, 1) > 0 && (c == 0)) {
    currentParserState = WaitForMsgId;
    } else {
    // read wrong char to dummy, skip it
    port->read(&c, 1);
    // stay on WaitForStart
    }
    break;

        case WaitForMsgId: 
            QByteArray ba = port->peek(2);
            if (ba.at(1) == 1 ||ba.at(1) == 2) {
                currentParserState = WaitForData;
            } else {
                // read wrong char to dummy, skip it
                port->read(&c, 1);
                // transition to WaitForStart
                currentParserState = WaitForStart;
            }
            break;
    
        case WaitForData:
            // parse next length field from data
            QByteArray ba = port->peek(3);
            int msgLength = ba.at(2);
            if (msgLength <= port->bytesAvailable() - 2) {
                // all complete data is available
                // skip start, id, length field
                QByteArray dummy = port->read(3);
                // read complete data
                QByteArray data =  port->read(msgLength);
    
                emit port->completeData(data);
            } else {
                // not all data is available, wait for next readyRead()
                return;
            }
             break;
        }
    }
    

    }

    @



  • This part i already have working.
    The problem is that while i'm doing reception i can't do anything else.



  • You can do the calculations (parse or something else) in a separate thread (e.g. using QtConcurrent) and pass the result of the calculations through the signal to the QtSerialPort back. So, I not see a problem.


  • Lifetime Qt Champion

    Then don't block: read what is available on the port in a QByteArray and once the QByteArray has the correct size, do your computation.

    Between two readyRead, your GUI will be responsive.



  • Hi, I will try your approach, but i will need to receive data from another serialport. How can i do this? Because if i'm processing the readyRead() of the first, my app can't process the readyRead() of the second...
    That's why i first thought about threads..



  • Sorry? You give us all new and new surprises? :)

    bq. Hi, I will try your approach, but i will need to receive data from another serialport. How can i do this?

    This:
    @
    QSerialPort *port1 = new QSerialPort;
    QSerialPort *port2 = new QSerialPort;

    connect(port1, SIGNAL(readyRead()),
    this, SLOT(onReadyReadFromPort1()));

    connect(port2, SIGNAL(readyRead()),
    this, SLOT(onReadyReadFromPort2()));

    port1->setPortName("COM1");
    port2->setPortName("COM125");

    ...
    ...
    ...
    @

    does not work? :)



  • I already had mentioned it in a previous post.

    I thought of doing like you say, but my question is: if both signals happen at same time, won't my program freeze? It will only process one or the other, or so i think... Or can 2 slots be processed at the same time?



  • bq. if both signals happen at same time, won’t my program freeze?

    It depends on the code that you implement in the handler of each slot.

    bq. Or can 2 slots be processed at the same time?

    Any slot/signal is executed in turn in a context of the current thread. Only one slot it is possible to process at the same time. Signals can't come at the same time, all (signals and slots) is processing from Qt event-loop queue (as a whole).

    Please, read documentation on Qt as a whole...



  • Ok, like i thought.
    That's why i first thought on using Threads...
    So that each serialport could only deal with one port.


Log in to reply
 

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