Search code examples
c++asynchronousserial-portqt5terminal-emulator

Why Qt serial read data never arrives? (Qt 5.15.2, C++, Win64, MSVC2019_64)


To develop my program first without connecting two physical machines on serial port, I downloaded and used this program to simulate COM ports: https://sourceforge.net/projects/com0com/ I connected virtual COM4 to virtual COM5. It works fine.

Using Br@y's Terminal program, I tested if I connect to COM4 in one Terminal instance, and to COM5 in another instance on the same computer, the data that I send on one terminal arrives in the other terminal, and vice versa. Terminal program: https://sites.google.com/site/terminalbpp/

Now let's see the problem: I used SerialPortReader class from this official Qt sample code for async serial read: https://code.qt.io/cgit/qt/qtserialport.git/tree/examples/serialport/creaderasync It connects to COM5 and sets baud rate to 9600 successfully, but no data arrives if I send something via Terminal to COM4, so: SerialPortReader runs through with no error, but after then, no matter what message I send on my Terminal instance, handleReadyRead, handleError, and handleTimeout never get called.

(If I have already a terminal emulator connected to the virtual COM5 port, then connection in my C++ program fails, so indeed the open() check works fine. Also, if I try to send more than one messages to my program via the virtual COM4 port, Terminal freezes, which is a clear sign of that the previous message has not yet been read on the other side(COM5).)

I have googled a lot, but have not yet found any solutions. Someone here said that it is/was a bug Qt Serial Port Errors - Data not getting read and that the problem is in qserialport_win.cpp, but even if I change that and compile my program again, nothing happens. I use the following code to create the class, but the class' content is unchanged, I use it as I found in the sample program:

    // Serial comm init
    QSerialPort serialPort;
    QString serialPortName = "COM5";
    serialPort.setPortName(serialPortName);

    int serialPortBaudRate = 9600;
    

    if (serialPort.open(QIODevice::ReadOnly)) {
        if(serialPort.setBaudRate(serialPortBaudRate) &&
            serialPort.setDataBits(QSerialPort::Data8) &&
            serialPort.setParity(QSerialPort::NoParity) &&
            serialPort.setStopBits(QSerialPort::OneStop) &&
            serialPort.setFlowControl(QSerialPort::NoFlowControl)) {
            //SerialPortReader serialPortReader(&serialPort);
            SerialPortReader serialPortReader(&serialPort, this);
        } else {
            std::cout << "Failed to set COM connection properties " << serialPortName.toStdString() << serialPort.errorString().toStdString() << std::endl;
        }
    } else {
        std::cout << "Failed to open port " << serialPortName.toStdString() << serialPort.errorString().toStdString() << std::endl;
    }

I would appreciate any help. Thanks!


Solution

  • Today I figured out a sketchy but working version:

    SerialPortReader.h

    #pragma once
    #include <QtCore/QObject>
    #include <QByteArray>
    #include <QSerialPort>
    #include <QTextStream>
    #include <QTimer>
    
    class SerialPortReader : public QObject {
        Q_OBJECT
    
    public:
        explicit SerialPortReader(QObject *parent = 0);
        ~SerialPortReader() override;
        void close();
    
    private:
        QSerialPort *serialPort = nullptr;
        QByteArray m_readData;
        QTimer m_timer;
    
    public slots:
        void handleReadyRead();
        //void handleTimeout();
        //void handleError(QSerialPort::SerialPortError error);
    
    };
    

    SerialPortReader.cpp

    #include <iostream>
    #include "SerialPortReader.h"
    
    SerialPortReader::SerialPortReader(QObject *parent) : QObject(parent)
    {
    
        serialPort = new QSerialPort(this);
        const QString serialPortName = "COM4"; //argumentList.at(1);
        serialPort->setPortName(serialPortName);
    
        const int serialPortBaudRate = QSerialPort::Baud9600;
        serialPort->setBaudRate(serialPortBaudRate);
    
        if (!serialPort->open(QIODevice::ReadOnly)) {
            std::cout << "Failed to open port" << std::endl;
            //return 1;
        }
    
    
        std::cout << "SerialPortReader(QSerialPort *serialPort, QObject *parent)" << std::endl;
    
        connect(serialPort, SIGNAL(readyRead()), this, SLOT(handleReadyRead()), Qt::QueuedConnection);
    
       // connect(serialPort, &QSerialPort::readyRead, this, &SerialPortReader::handleReadyRead);
        //connect(serialPort, &QSerialPort::errorOccurred, this, &SerialPortReader::handleError);
        //connect(&m_timer, &QTimer::timeout, this, &SerialPortReader::handleTimeout);
    
        //m_timer.start(5000);
    }
    
    void SerialPortReader::handleReadyRead()
    {
        std::cout << "handleReadyRead()" << std::endl;
    
        m_readData.append(serialPort->readAll());
    
        if (!m_timer.isActive())
            m_timer.start(5000);
    }
    
    /*
    void SerialPortReader::handleTimeout()
    {
        std::cout << "handleTimeout()" << std::endl;
    
        if (m_readData.isEmpty()) {
            std::cout << "No data was currently available for reading" << std::endl;
        } else {
            std::cout << "Data successfully received" << std::endl;
            //m_standardOutput << m_readData << Qt::endl;
        }
    
        //QCoreApplication::quit();
    }
    
    void SerialPortReader::handleError(QSerialPort::SerialPortError serialPortError)
    {
        std::cout << "handleError()" << std::endl;
    
        if (serialPortError == QSerialPort::ReadError) {
            std::cout << "An I/O error occurred while reading" << std::endl;
            //QCoreApplication::exit(1);
        }
    }
    */
    
    SerialPortReader::~SerialPortReader() {
        close();
    }
    
    // Close the files, filestreams, etc
    void SerialPortReader::close() {
        // ...
    }
    

    ... and in my QApplication code you just need to include the .h and write this to instantiate the serial listener:

    SerialPortReader *serialPortReader = new SerialPortReader(this);