Important: Please read the Qt Code of Conduct - https://forum.qt.io/topic/113070/qt-code-of-conduct

QSerialPort::readyRead() is not emitted, but QTCPSocket::readyRead() works fine.



  • Hello,
    I try to make a shared library for communication with a device via TCP or Serial communication. So I have created Channel class with QIODevice* member and two derived classes that instantiate this QIODevice* to SerialPort* or to TCPSocket* and use it for communication.

    The problem is that when I test the library in console application without QApplication::exec() it works very well with TCPSocket, but doesn't work with SerialPort. I found out that the QSerialPort::readyRead() is not emitted.

    I asked for help during the Qt Summit 2020 and they told me that I need to run an EventLoop. The thing is that the QEventLoop can't be ran without QApplication, but the QApplication can be ran only on the main thread and the exec() method blocks it. I tested the library with Qt UI project and it worked fine. However I want my library to be usable for all type of users, not only Qt ones. So now I am looking for a decision. If someone knows how to fix this, please tell me?

    The strangest thing for me is that QTcpSocket and all other connections between signals and slots that I have created work just fine, only QSerialPort::readyRead() doesn't. Why? Why only the SerialPort::readyRead() can't work without QApplication::exec()?


  • Lifetime Qt Champion

    Please provide a minimal, working example. You need to call the initial Qt event loop with QCoreApplication::exec() then all works fine. Please also take a look at the various examples.



  • @Christian-Ehrlicher said in QSerialPort::readyRead() is not emitted, but QTCPSocket::readyRead() works fine.:

    Please provide a minimal, working example. You need to call the initial Qt event loop with QCoreApplication::exec() then all works fine. Please also take a look at the various examples.

    Hello and thanks for your answer!
    Firstly for calling Qt event loop with QCoreApplication::exec() - I have tried it but it also blocks the thread and the client application. I have tried to move it to background thread, but then I got another warnings and errors (for example "QCoreApplication::exec: Must be called from the main thread"). I also get error: "QEventDispatcherWin32::registerTimer: timers cannot be started from another thread".

    Secondly, as you asked, a simplified code example. The implementations are included only for the necessary functions.

    
    class MLXCOMChannel : public QObject
    {
        Q_OBJECT
    
    protected:
        QSharedPointer<QIODevice> ch_communicationDevice;
    
        ErrorHandler ch_errorHandler;
        QMutex ch_mutex;
        bool ch_open;
        uint64_t ch_sentDataSize;
        QByteArray ch_dataBuffer;
    
    private:
    
    
    private slots:
        void                    handleMLXCOMError(MLXCOMErrorType errorType);
    
    protected slots:
        void                    readData() {
                                        if (ch_communicationDevice.data()) {
                                    
                                            QMutexLocker locker(&ch_mutex);
                                    
                                            ch_dataBuffer.append(ch_communicationDevice->readAll());
                                    
                                        } else {
                                    
                                            emit error(MLXCOMErrorType::FunctionalityNotImplemented);
                                    
                                        }                        };
        void                    dataWritten(qint64 bytes);
    
    public:
    
                                MLXCOMChannel();
        virtual                 ~MLXCOMChannel();
    
        virtual void            open();
        virtual void            close();
        virtual void            sendData(const QByteArray &data);
        virtual QByteArray      receiveData(size_t dataSize);
        virtual bool            hasData();
        virtual qint32          availableDataSize();
    
    
        // Channels settings interaction
        t_ChannelType getChannelType();
        std::string getChannelTypeName();
    
        static qint32           createChannel(MLXCOMChannel **channel, const t_ChannelType channelType);
        static qint32           destroyChannel(MLXCOMChannel **channel);
    
    signals:
        void                    error(MLXCOMChannel::MLXCOMErrorType errorType) const;
    
    };
    
    
    // The one that works - TCP Channel
    class TCPChannel : public MLXCOMChannel
    {
        Q_OBJECT
    
    public:
        TCPChannel() {
                ch_communicationDevice.reset(new QTcpSocket);
            
                m_pSocket = reinterpret_cast<QTcpSocket*>(ch_communicationDevice.get());
            
            
            
                connect(m_pSocket, &QTcpSocket::disconnected, this, [=](){ch_open = false;});
            
                connect(m_pSocket, &QTcpSocket::connected, this, [=](){ch_open = true;});        
            
                connect(m_pSocket, &QIODevice::readyRead, this, &TCPChannel::readData);
            
                connect(m_pSocket, &QIODevice::bytesWritten, this, &TCPChannel::dataWritten);
            
                connect(m_pSocket, QOverload<QAbstractSocket::SocketError>::of(&QAbstractSocket::error), this, &TCPChannel::handleSocketError);   ~TCPChannel() override;
    
        void open() override;
        void close() override;
        void sendData(const QByteArray &data) override;
    
    private:
    
        QTcpSocket *m_pSocket;
    
    
    private slots:
        void handleSocketError();
    };
    
    
    // The one that does not work - Serial Channel
    class SerialChannel : public MLXCOMChannel
    {
        Q_OBJECT
    
    public:
        SerialChannel() {
            ch_communicationDevice.reset(new QSerialPort);
        
            m_pSerialPort = reinterpret_cast<QSerialPort*>(ch_communicationDevice.get());
            
            connect(this, &SerialChannel::debugSignal, this, &SerialChannel::debugSlot);
        
            connect(m_pSerialPort, &QSerialPort::readyRead, this, &SerialChannel::readData);
        
            connect(m_pSerialPort, &QIODevice::bytesWritten, this, &SerialChannel::dataWritten);
        
            connect(m_pSerialPort, &QSerialPort::errorOccurred, this, &SerialChannel::handlePortError);;
        ~SerialChannel() override;
    
        // Overridden methods
        void open() override;
        void close() override;                              // To be moved to MLXCOMChannel
        void sendData(const QByteArray &data) override;     // To be moved to MLXCOMChannel
    
    
    private:
        QSerialPort *m_pSerialPort;
    
    
    private slots:
        void handlePortError();
        void debugSlot(QString &msg);
    signals:
        void debugSignal(QString &msg);
    
    };
    

    So, the TCPChannel works just fine. The debugSignal and debugSlot in SerialChannel work fine. Error handling signals work fine. Only the QSerialPort::readyRead doesn't.


  • Lifetime Qt Champion

    @Rufledore said in QSerialPort::readyRead() is not emitted, but QTCPSocket::readyRead() works fine.:

    I have tried it but it also blocks the thread and the client application

    Please show how you did it. I hope you did it in your main.cpp



  • @jsulm said in QSerialPort::readyRead() is not emitted, but QTCPSocket::readyRead() works fine.:

    As I mentioned in the first post it is a part of a shared library that I develop. So, this library has export functions like these:

    MLXCOM_API int MLXCOM_CALL_CONV mlxcom_send_data(MLXCOM_CHANNEL mlxcomChannel, char* data, uint32_t dataSize) {
        MLXCOMChannel* channel = static_cast<MLXCOMChannel*>(mlxcomChannel);
        QByteArray dataForSend(data, dataSize);
    
        channel->prepareErrorHandler();
        channel->sendData(dataForSend);
        if (channel->hasNewError()) {
            return channel->lastError().first;
        }
    
        return 0;
    }
    
    // add parameter - number of bytes.
    MLXCOM_API int MLXCOM_CALL_CONV mlxcom_receive_data(MLXCOM_CHANNEL mlxcomChannel, char* data, uint32_t data_size) {
        MLXCOMChannel* channel = static_cast<MLXCOMChannel*>(mlxcomChannel);
    
        channel->prepareErrorHandler();
        QByteArray msg = channel->receiveData(data_size);
        if (msg.size() > 0) {
            std::memcpy(data, msg.data(), static_cast<size_t>(msg.size() + 1));
        }
        if (channel->hasNewError()) {
            return channel->lastError().first;
        }
    
    
        return 0;
    }
    
    

    So I added a function init() where I did QCoreApplication::exec().

    I use the library in a test console application that is only a main(), but doesn't have QApplication or QCoreApplication in it. There I faced the issue. When I use the library in a test UI app that is simply a MainWindow started from main() and QApplication.exec() it works okay. However I am willing do make cross platform C-style library that could be used not only in Qt. TCP communication class works well also when I use it in visual studio.



  • Okay, now I tried it and the static way QCoreApplication::exec() doesn't block but throw : QApplication::exec: Please instantiate the QApplication object first



  • I didn't mentioned also that it something actually turns the event loop sometimes and the signal is emitted, but not regularly. I think that it is waitForBytesWritten(). If I don't use waitForBytesWritten while writing to device nothing happens.


  • Lifetime Qt Champion

    @Rufledore said in QSerialPort::readyRead() is not emitted, but QTCPSocket::readyRead() works fine.:

    QApplication::exec: Please instantiate the QApplication object first

    Then you should do this in our main() in the first line.



  • @Christian-Ehrlicher said in QSerialPort::readyRead() is not emitted, but QTCPSocket::readyRead() works fine.:

    @Rufledore said in QSerialPort::readyRead() is not emitted, but QTCPSocket::readyRead() works fine.:

    QApplication::exec: Please instantiate the QApplication object first

    Then you should do this in our main() in the first line.

    In principle yes, but I don't want the user of the library to bother with Qt staff as QApplication. And the main question for me is how it works okay for TCPSocket but not for SerialPort?


  • Lifetime Qt Champion

    You need a working QCoreApplication - everything else simply works by accident and may not work suddenly.



  • Then how to use Qt specific features as signals and slots in a library? QCoreApplication could be started only in main thread... If I start it on the background it doesn't work well. Could you give me an idea, how to proceed?


  • Qt Champions 2020

    @Rufledore said in QSerialPort::readyRead() is not emitted, but QTCPSocket::readyRead() works fine.:

    Could you give me an idea, how to proceed?

    Quick answer - nohow, it makes not sense to use Qt stuff from the non-qt libraries.


  • Lifetime Qt Champion

    @Rufledore said in QSerialPort::readyRead() is not emitted, but QTCPSocket::readyRead() works fine.:

    could be started only in main thread

    Q(Core|Gui)Application can also be started in another thread but then all gui operations must be done inside this thread too.



  • @Rufledore said in QSerialPort::readyRead() is not emitted, but QTCPSocket::readyRead() works fine.:

    Then how to use Qt specific features as signals and slots in a library? QCoreApplication could be started only in main thread... If I start it on the background it doesn't work well. Could you give me an idea, how to proceed?

    It is a Qt library but not for Qt application :)



  • @Rufledore
    I don't think you can use Qt library calls for TCP/serial port without being in a Qt application. As in, they don't work properly, as you have discovered. They need parts of the Q(Core|Gui)Application-type architecture.,


Log in to reply