Unsolved 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()?
-
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.
-
@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.
-
@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?
-
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?
-
@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.
-
@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 theQ(Core|Gui)Application
-type architecture.,