Solved QSerialPort::waitForReadyRead exits with timeout
-
@J-Hilk
I would be happy to do so, just let me clean up a bit first... -
The calling method (every 250msec) , just to set the context:
PAMS_Point PAMSApp::ReadPosition(bool init) { PAMS_Point pos; PAMS_Point mark; PAMS_Point errmark(0,0,false); QString PosString; char str[512]=""; int len = 0; SerialPort *port; port = fSettings->GetSerialPort(); if(!port) return errmark; if(!port->Status()) return errmark; if(init) {port->ZeroSCD();} len = port->GetSCDPos(ALL,str); if(!len) return errmark; PosString = str; mark = ParseSCDString(PosString); return mark; }
serialport.cpp
/* * Serial port code adapted to SCD PROFILER instruction set. */ #include <stdio.h> #include <stdlib.h> #include <string.h> #include "serialport.h" #include "PAMS_Types.h" SerialPort::SerialPort(QString portname) { PortName = ""; status = OpenComPort(portname); if(status) PortName = portname; lasterror = QSerialPort::NoError; reads = 0; } SerialPort::~SerialPort() { if(status) close(); } bool SerialPort::OpenComPort(QString sPortText) { bool success = false; setPortName(sPortText); setBaudRate(Baud9600); setFlowControl(QSerialPort::NoFlowControl); setParity(QSerialPort::NoParity); setDataBits(QSerialPort::Data8); setStopBits(QSerialPort::OneStop); success = open(QIODevice::ReadWrite); return success; } int SerialPort::GetSCDPos(int axis,char *str) { bool ok=true; int err=0; int len=0; QByteArray reply; char X_axis[10] = "?pos x\r"; char Y_axis[10] = "?pos y\r"; char Z_axis[10] = "?pos z\r"; char A_axis[10] = "?pos\r"; Buffer[0] = Buffer[1] = (char)NULL; switch(axis) { case XAXIS: // X-Axis PortWrite(X_axis); break; case YAXIS: // Y-Axis PortWrite(Y_axis); break; case ZAXIS: // Z-Axis PortWrite(Z_axis); break; case ALL: // All-Axes PortWrite(A_axis); break; default:return 0; break; } err = error(); lasterror = err; ok = waitForBytesWritten(500); if(!ok){ err = error(); lasterror = err; } ok = waitForReadyRead(100); if(!ok){ err = error(); lasterror = err; } while(bytesAvailable()) { reply.append(readAll()); } strcpy_s(str,512,reply.data()); len = (int)strlen(str); if (!lasterror) reads += 1; return len; } bool SerialPort::PortClear(void) { bool rc; rc = clear(); clearError(); lasterror = error(); return rc; } void SerialPort::PortWrite(char *request) { if(isOpen()) { write(request,strlen(request)); } } void SerialPort::ZeroSCD(void) { char command[10]="!pos 0 0"; PortClear(); command[8] = 0x0d; command[9] = 0; PortWrite(command); }
serialport.h
#ifndef _H_SerialPort #define _H_SerialPort #include <stdio.h> #include <QtCore> #include <QIODevice> #include <QFile> #include <QSerialPort> enum AXIS {XAXIS,YAXIS,ZAXIS,ALL}; class SerialPort : public QSerialPort { Q_OBJECT public: SerialPort(QString inPortname); virtual ~SerialPort(); bool status; //======================================================================== // Platform specific routines //======================================================================== public: ulong lasterror, reads; bool Status() { return status; }; int GetSCDPos(int axis, char *PosString); QString name() {return PortName;}; void ZeroSCD(void); ulong GetLastError() {return lasterror;}; ulong GetReads() {return reads;}; bool PortClear(); protected: bool OpenComPort(QString sPortText); QString PortName; void PortWrite(char *); }; #endif // _H_SerialPort
-
@Bengt-0
ok, here ya go:#ifndef SERIALPORT_H #define SERIALPORT_H #include <stdio.h> #include <QtCore> #include <QIODevice> #include <QFile> #include <QSerialPort> enum AXIS {XAXIS,YAXIS,ZAXIS,ALL}; class SerialPort : public QSerialPort { Q_OBJECT public: SerialPort(QString inPortname); virtual ~SerialPort(); //======================================================================== // Platform specific routines //======================================================================== enum OperationMode { NoOperation = 0, GetScdPos, WriteNoResponse }m_Mode{NoOperation}; struct RequestQueue{ AXIS axis; QByteArray data; RequestQueue(AXIS a, QByteArray b) : axis(a), data(b){} RequestQueue(AXIS a) : axis(a){data.reserve(512);} }; public: void requestSCDPos(AXIS axis); QString getResponseAsString(){return QString(getOldestResponse());} QByteArray getOldestResponse(); QByteArray getLatestResponse(); bool Status() { return isOpen() && error() == QSerialPort::NoError; }; QString name() {return PortName;}; void ZeroSCD(void); ulong GetLastError() {return lasterror;}; ulong GetReads() {return reads;}; bool PortClear(); protected: bool OpenComPort(QString sPortText); QString PortName; void PortWrite(const char *); signals: void responseReceived(); private slots: void requestNextData(); void onSerialPortError(QSerialPort::SerialPortError error); void onReadyRead(); void onBytesWritten(quint64 bytes); private: ulong lasterror, reads; QQueue<RequestQueue> m_requestQueue; QQueue<RequestQueue> m_resultQueue; quint64 bytesToWrite = 0; }; #endif // _H_SerialPort
/* * Serial port code adapted to SCD PROFILER instruction set. */ #include <stdio.h> #include <stdlib.h> #include <string.h> #include "serialport.h" //#include "PAMS_Types.h" //Console output for test cases #include <QDebug> SerialPort::SerialPort(QString portname) { PortName = ""; if(OpenComPort(portname)) PortName = portname; lasterror = QSerialPort::NoError; reads = 0; //Sets up the connection from the base class QSerialPort to our own slot onSerialPortError connect(this, &QSerialPort::errorOccurred, this, &SerialPort::onSerialPortError); connect(this, &QSerialPort::readyRead, this, &SerialPort::onReadyRead); connect(this, &QSerialPort::bytesWritten, this, &SerialPort::onBytesWritten); //Automatically try to call next data package, when a response was succesfully extracted connect(this, &SerialPort::responseReceived, this, &SerialPort::requestNextData); } SerialPort::~SerialPort() { if(isOpen()) close(); } bool SerialPort::OpenComPort(QString sPortText) { bool success = false; setPortName(sPortText); setBaudRate(Baud9600); setFlowControl(QSerialPort::NoFlowControl); setParity(QSerialPort::NoParity); setDataBits(QSerialPort::Data8); setStopBits(QSerialPort::OneStop); success = open(QIODevice::ReadWrite); return success; } void SerialPort::requestSCDPos(AXIS axis) { m_requestQueue.enqueue(RequestQueue(axis)); if(m_Mode == NoOperation) requestNextData(); } QByteArray SerialPort::getOldestResponse() { if(m_resultQueue.isEmpty()) return QByteArray(); return m_resultQueue.dequeue().data; } QByteArray SerialPort::getLatestResponse() { //This kind of goes behind the idea of a queue, but it's here just in case if(m_resultQueue.isEmpty()) return QByteArray(); return m_resultQueue.takeLast().data; } bool SerialPort::PortClear(void) { bool rc; rc = clear(); clearError(); lasterror = error(); return rc; } void SerialPort::PortWrite(const char *request) { if(isOpen()) { write(request,strlen(request)); } } void SerialPort::requestNextData() { if(m_resultQueue.isEmpty()){ m_Mode = NoOperation; return; } m_Mode = GetScdPos; AXIS axis = m_requestQueue.first().axis; constexpr char X_axis[10] = "?pos x\r"; constexpr char Y_axis[10] = "?pos y\r"; constexpr char Z_axis[10] = "?pos z\r"; constexpr char A_axis[10] = "?pos\r"; switch(axis) { case XAXIS: // X-Axis PortWrite(X_axis); break; case YAXIS: // Y-Axis PortWrite(Y_axis); break; case ZAXIS: // Z-Axis PortWrite(Z_axis); break; case ALL: // All-Axes PortWrite(A_axis); break; } } //If the Serialport has an error, the signal that the SerialPort emits will call this function void SerialPort::onSerialPortError(QSerialPort::SerialPortError error) { lasterror = error; qDebug() << Q_FUNC_INFO << error; } void SerialPort::onReadyRead() { if(bytesAvailable() < 512) //Not Enough bytes in the buffer, 512 should be small enough so that we don't have to empty the buffer on each readyRead signal //And we can let it be in the internal buffer return; RequestQueue queue = m_resultQueue.dequeue(); quint64 read = readData(queue.data.data(), 512); if(read != 512) qDebug() << Q_FUNC_INFO << read << "of" << 512; m_resultQueue.enqueue(queue); reads++; emit responseReceived(); } void SerialPort::onBytesWritten(quint64 bytes) { if(m_Mode == WriteNoResponse) { bytesToWrite -= bytes; if(bytesToWrite == 0) m_Mode = NoOperation; } } //No Idea what this is supposed to do (If it will trigger a readyRead or not) I assume not and implemented the bytesWritten signal/slot logic for no response void SerialPort::ZeroSCD(void) { char command[10]="!pos 0 0"; PortClear(); command[8] = 0x0d; command[9] = 0; bytesToWrite = 10; m_Mode = WriteNoResponse; PortWrite(command); }
The idea here is you call the
requestSCDPos(AXIS axis)
function with your 250 ms second timer.Once the serialport class has received all data for one response, the signal
void responseReceived();
is emitted. In your other class you simply connect to that signal and request the oldest response withgetOldestResponse
orQString getResponseAsString
I implemented a queue system, to make the whole system more robust, just in case.
-
@J-Hilk
Thanks a lot, this will be an exciting exercise!Comments:
ZeroSCD is sending a command to zero the XY-pos (0.000 0.000) of the stage measurement device at the current position, no response expected.Actually, I would prefer the newest position rather than the oldest.
If I cannot figure it out myself, I will ask again. -
Hi,
Designing and debugging a thing like this is difficult or impossible without a real device that answers the requests. Your code is impressive in this respect.
A few things..
In requestNewData() I changed from
void SerialPort::requestNextData() { if(m_resultQueue.isEmpty()){ m_Mode = NoOperation; return; } m_Mode = GetScdPos; AXIS axis = m_requestQueue.first().axis;
to
void SerialPort::requestNextData() { if(m_requestQueue.isEmpty()){ m_Mode = NoOperation; return; } m_Mode = GetScdPos; AXIS axis = m_requestQueue.first().axis;
because there are no results initially, so it will not proceed. First requests, then results. Would you agree this was a misprint?
Then I can see that the 512 byte buffer is never filled, so the initial condition bytesAvailable>512 in onReadyRead makes it return immediately. This may be because the OperationMode is never changed from "GetScdPos" after requestNextData() so requestNextData() is never entered again in requestSCDPos(). I am not sure where it should be changed.Finally, I changed
RequestQueue queue = m_resultQueue.dequeue(); quint64 read = readData(queue.data.data(), 512);
to
RequestQueue queue = m_requestQueue.dequeue(); qint64 rd = read(b, 512); queue.data = b;
It simply did not read, I have no idea why. Anyway this is no functional difference.
So it is 90% working, but some things remains.
Comments are welcome. -
@Bengt-0 said in QSerialPort::waitForReadyRead exits with timeout:
A few things..
In requestNewData() I changed from
...
to
...
because there are no results initially, so it will not proceed. First requests, then results. Would you agree this was a misprint?Yes, absolutely, that was an error on my part 😱
It simply did not read, I have no idea why. Anyway this is no functional difference.
there is, may be due to badly chosen names on my part! Read, same as readData expects a char* as the first argument. Seems like b is a QByteArray, so I'm surprised it worked 🤔
try:
RequestQueue queue = m_resultQueue.dequeue(); QByteArray b = read(512); if(b.size() != 512) qDebug() << Q_FUNC_INFO << b.size() << "of" << 512; queue.data = b;
This may be because the OperationMode is never changed from "GetScdPos" after requestNextData() so requestNextData() is never entered again in requestSCDPos().
acutually, the idea was, this connect
//Automatically try to call next data package, when a response was succesfully extracted connect(this, &SerialPort::responseReceived, this, &SerialPort::requestNextData);
should automatically call
requestNextData
, once it received content of a full package, the queue would be empty -> the mode would be set toNoOperation
and requestNextData exited early, setting the Serialport in "Idel" mode until next call ofrequestSCDPos
-
Hi,all ,I have a prolbem with waitForReadyRead, in qthread.run():
void run() override{ QByteArray ba; QSerialPort sp; sp.setPortName(p()->m_comPort); sp.setBaudRate(p()->m_bd); sp.setParity(QSerialPort::NoParity); sp.setDataBits(QSerialPort::Data8); sp.setStopBits(QSerialPort::OneStop); sp.setFlowControl(QSerialPort::NoFlowControl); if(!sp.open(QIODevice::ReadWrite)){ emit p()->sigHardwareError(-1); goto fail; } do{ /* Method1: not work when mess data received , it always return false if(sp.waitForReadyRead(50)){ ba.clear(); while(sp.bytesAvailable()) ba += sp.readAll(); }*/ // Method2: need to change like this: sp.waitForReadyRead(50); if(sp.bytesAvailable()){ ba.clear(); while(sp.bytesAvailable()) ba += sp.readAll(); } }while(!isInterruptionRequested()); fail: if(sp.isOpen()) sp.close(); }
why waitForReadyRead() always return false with Method1 ?