QtSerialPort in a separate thread?
-
Hello,
Im using QtSerialPort along with QCustomPlot to plot in real time a stream of values I receive from an Arduino.
Im using a QElapsedTimer to control the time axis of each plot, so for example if I have a time window of 10sec the timer will count up to that value and then it is reset.The problem is: when I move or resize the main window the data I receive from Arduino stops being processed (proper processing slot() is not called). Data isn't lost since after releasing the mouse button the processing slot is called and data is processed but this means all that buffered data will have (almost) the same "time" value.
How can I avoid this? By using the QtSerialPort in a separate thread or is there another (easier/less complex) approach?
-
At present, the single decision (workaround) - to use a separate thread.
In principle, the solution of this problem is known. Maybe it will fixed in Qt5.2.1, I hope.
Please watch for: https://bugreports.qt-project.org/browse/QTBUG-34946
-
Thanks!
Can you elaborate a little bit on how to solve this with a separate thread?
Im trying to use .moveToThread() as follows:@ QSerialPort serialPort = new QSerialPort();
m_device = (QIODevice)m_sp;QThread *thread = new QThread(); serialPort->moveToThread(thread); thread->start();@
But when I try to establish the serial connection I get the following error:
bq. QObject: Cannot create children for a parent that is in a different thread.
(Parent is QSerialPort(0x4366a58), parent's thread is QThread(0x435a910), current thread is QThread(0x42a6728)
QObject: Cannot create children for a parent that is in a different thread. -
Sorry if this is a bit late, maybe you resolved this already.
This is also referenced here, so maybe this may help.
http://qt-project.org/forums/viewthread/21369In short, the open() call needs to happen on the worker-thread where you moved the serial port object.
-
I had no issues running QSerialPort in worker threads. Just create the thread first and then, already within the context of the new thread, create a QSerialPort object.
-
Your thread code is fine, but there is one important thing to note here:
@
QSerialPort serialPort = new QSerialPort();
m_device = (QIODevice)m_sp;QThread *thread = new QThread();
serialPort->moveToThread(thread);
thread->start();/*
Any further direct function calls to serialPort should be done via slots/signals, a direct function call will result in objects created within serialPort being attached to the main thread and not the new thread.
*/
@Also, as a good design principal, I believe it is always a good idea to separate your I/O and other non-user tasks away from the GUI where possible, this is a good example of why :) (event though it may be fixed in future versions).
-
@code_fodder
So in this case you would use a signal to be handled on the worker thread's slot (in the context of that worker thread), and then emit a signal back to notify of success/error? -
Almost...
calling functions directly on the serialPort object object is now "bad/unsafe" (as mentioned). You can pass data/messages between the worker OBJECT and serialPort OBJECT (note: I am not shouting, just highlighting, the word object because you connect objects, not threads :) )
Without more of your code to work with its hard to generate an example, so I produce a theoretical one here:
Lets say we have object workerObj which lives in the worker thread and serialPort which lives in its own thread (as setup in your code) then:
@
// Connect I/O from serial port to worker object
connect(serialPort, SIGNAL(dataReceived(QByteArray)), workerObj, SLOT(displaySerialData(QByteArray)));// Send data to the serial Port
connect(workerObj, SIGNAL(userDataToSend(QByteArray)), serialPort, SLOT(sendSerialData(QByteArray)));
@Now you can send data from the serial port to the worker object by emitting dataReceived(<data>) within the serialPort object. This will use a queued connecion by default which will mean your data will arrive in order.
Also you can send data to the serial port from worker object by "emit userDataToSend(<data>);"You have to implement the slot functions displaySerialData(QByteArray) and sendSerialData(QByteArray) in their respective objects...
-
Thanks code_fodder. And thanks for clarifying that you're not shouting : D
I'm asking about handling opening / closing the port. So would that be the same concept?
For example, would it be something like this :
This connection in the worker object code:
@
// Open port
connect(workerObj, SIGNAL(openPort(QString)), serialPort, SLOT(openPortRequested(QString)));
@This one in the serial port object:
@
// Result of trying to open the port
connect(serialPort, SIGNAL(openPortFinished(QString)), workerObj, SLOT(openPortFinished(QString)));
@ -
Perfect, you have got it :)
I think you should do these connections just after you move serialPort into the new thread. I am not sure if there is any issue connecting it before that, but I have just never bothered to find out. Conceptually (in my head only) it seems wrong to do that because you connect them and then move one of the objects, I am sure Qt's "black magic" can handle it, but I just not 100% sure :o
-
Also my earlier comment was not 100% accurate.
You CAN connect to the thread as well, but I meant you connect directly to the objects in question.
But, for example, if you wanted your serialPort object to do something (like start processing) as soon as the thread starts then you can do this:
@connect(thread, SIGNAL(started()), serialPort, SLOT(startProcessing()));@
Implement startProcessing() in serialPort, and then as soon as the thread starts (after you call thread->start()) it will emit a signal to your serialPort and it can safely begin its operations... :) -
I have another question (I am really kidnapping this thread I see).
The thread where I'm doing all of the actual thread operations - let's call it SerialPortThread, it would now work based on signals and slots. But this means that the QSerialPort cannot just be a data member of that class since it would be created on the stack in the Worker thread, and not under SerialPortThread.
Which is, I guess, why I get this error:
@
QObject: Cannot create children for a parent that is in a different thread.
@How should the actual QSerialPort object be defined then, considering I don't have a "while" code to create it within a run() method, but really only need it in my slots?
I could create a (QSerialPort *) member and to initialize it within the relevant slot but I'm not sure that's the correct way.
-
Ohh... hangon, I thought you where the original poster :) .. oh, well, they are not as picky here as they are on stackoverflow.... we would definatley get told off there! :o
Yes you are right, the objects should not have an owner already assigned. So you can make them member pointers as you mentioned:
@
QSerialPort *mp_serialPort; //(for example)
@Then create it WITHOUT a parent
@
mp_serialPort = new QSerialPort(0); // 0 = no parent pointer
@
Then you can do the rest:
@
QThread *aThreadToStickSerialPortIn = new QThread(this); // ok to use this a parent thread.
mp_serialPort.moveToThread(aThreadToStickSerialPortIn);
etc...
@ -
But I don't understand that last part.
(bah writing this in words is tough)
Let's reintroduce it:
I have a Worker object which wants to use a QSerialPort on another thread. So I introduce a SerialPortWrapper class, and in my Worker I create a QThread, move the SerialPortWrapper to that thread and start() it. Implicitly it runs exec(), SerialPortWrapper doesn't subclass QThread and doesn't do any run() logic.
So now the question. In the context of that thread that I started, SerialPortWrapper has only the slots. So where would I initialize the actual QSerialPort? I could store a QPointer<QSerialPort> as a datamember and initialize it in the beginOpenPort() slot but I'd still be talking with a pointer that is declared as a data member of the class (Even if the object it points to lives in the newly running thread).
Is this clear? Or must I throw in code parts?
-
Ok, so I am not 100% clear, lets get it sorted :)
Start with the workerObject:
@
workerObj::workerObj(...blah...) :
mp_serialPortWrapper(0) // null pointer to your serialPortWrapper class
{
// Create thread and serialWrapper
QThread *p_thread = new QThread(this); // make a new thread
mp_serialPortWrapper = new serialPortWrapper(0, params...); // 0 = pointer to parent, set this to 0 (null)// Now move the serialWrapper into the thread: mp_serialPortWrapper.moveToThread(p_thread); // Now connect a slot in the wrapper to the the started() signal of the thread so it can do initialisations once the thread starts: connect(p_thread, SIGNAL(started()), mp_serialPortWrapper, SLOT(doStartupStuff()); // Now just start the thread, and the doStartupStuff() will be called in the serialWrapper p_thread->start(); // Done (omitted other connections for this example)
}
@Now in the serialWrapper doStartupStuff slot:
@
serialPortWrapper::doStartupStuff()
{
// initiate your serial port. It will be created in this thread
mp_serialPort = new QSerialPort(this, params...);// Safe to call function directly on the serialPort here, because it is a member of this class and is in the same thread: mp_serialPort->someFunc();
}
@Now all you need is to pass data between serialWrapper and worker via slots and signals. The serialPort itself is created within the serialWrapper.
Note: It's probably easier if you just inherit QSerialPort to your object:
@
class franklefranksSerialPort : public QSerialPort
{
Q_OBJECT
//etc...
}
@And then you can do what you want with it and it also has full QSerialPort capabilities. You just call the serial port function calls as a result of recieving certain signals in certain slots...
-
Thank you very much for your help!
Believe it or not, I now encounter some issues which are more about signals and slots across and less about the QSerialPort part. But I think I took advantage of this post enough, so now it's all about here: http://qt-project.org/forums/viewthread/39345/
-
haha, no problem. Yes, they can be a little fiddly at first, but they suddenly become easy to use once you know what to do!