-1

I am working on a Qt-C++ based front-end app for a Raspberry Pi powered robot. I am using Qt version 5.9 along with libraries QSerialPort and Pigpio. In my app, when I give the run command for a command sequence to the robot, my Raspberry Pi starts a serial communication with a microcontroller in which it sends some message and then waits to receive a response. This sending and waiting causes the Mainwindow thread to freeze up. I am trying to build in a emergency stop functionality, which would stop the command execution in the middle of the run process.

Towards that effort, I tried to push my serial communication part to a separate thread(QThread). It didn't work out. Now I am trying to build the emergency stop part into a QDialog box that opens up when I give the run command, which contains a emergency stop QPushbutton. The Dialog box is being run in non-modal form. But in my current code, when I give the run command, a dialog box does open up, but the dialog box is completely blank and then closes up when the run command ends(which is intentional). I'll share some screenshots of the appearance.
enter image description here

Can you suggest where I might be going wrong? Or is there a better approach to this issue? Any criticism and suggestions are welcome!

Thanks!

Aditya
  • 65
  • 1
  • 11
  • 2
    Your program waiting for a response could be the issue. Instead of waiting for a response, you could react to a message that you receive (`readyRead` signal). Hard to say without seeing any code. – thuga Aug 11 '20 at 08:51
  • @thuga I thought of that initially too. The reason why I can't use that is because I want to run 1 command, wait for a OK response from lower-level controller, and then execute the next command. My emergency stop needs to work during the wait time. I need to put the execution of next command on hold. Is there something you can suggest in this situation? – Aditya Aug 11 '20 at 10:05

1 Answers1

0

One shouldn't block the main thread in the Qt. Everytime you call the blocking function, your GUI freezes, as well as Dialog boxes.

One solution is to use signal/slots. They blend really well into Qt. But doing a complicated request/response logic would require a huge state machine usually prone to errors.

Sometimes it is better to leave this code blocking, create a plain chain of request/response code, and put it in another non-GUI thread. Then use the signal to notify the main thread about the job result.

In order to stop the execution it is possible to use an atomic and check it between blocking steps. The biggest time delay before exiting the working function is the biggest delay of the single blocking function. You should carefully tune the timeouts. Or you can write your own function, which emulates timeout and a stop condition. It should check if incoming data is available in an infinite loop and check fro stop condition on each iteration, which must be a timeout AND a stop condition variable.

// pseudocode here
while (true) {
    if (stopCondition) return; // check for emergency condition
    it (currentTime - startTime > timeout) return;
    if (serial->dataReady()) break;
}
auto data = serial->getData();

If a step can block forever, then this method can't be used.

There is an example with QtConcurrent framework, which demonstrates the use of QFuture and the work of a function in a separate thread without blocking the main thread. You can put all your communication logic inside it. The code is example only!

#ifndef WORKERCLASS_H
#define WORKERCLASS_H

#include <QObject>
#include <QtConcurrent/QtConcurrent>
#include <QFuture>

class WorkerClass : public QObject
{
    Q_OBJECT
public:
    explicit WorkerClass(QObject *parent = nullptr) : QObject(parent) {
        connect(&futureWatcher, &QFutureWatcher<void>::finished, [this] () {
            emit workFinsihed();
        });
    }

    void startWork(int value) {
        atomic = 0;
        future = QtConcurrent::run(this, &WorkerClass::workFunction, value);
        futureWatcher.setFuture(future);
    }

    void stopWork() {
        atomic = 1;
    }

private:
    QFuture<void> future;
    QFutureWatcher<void> futureWatcher;

    void workFunction(int value) {
        for (int i = 0; i < value; ++i) {
            if (atomic) return;
        }
        return;
    };

    QAtomicInt atomic{0};

signals:
    void workFinsihed();
};

#endif // WORKERCLASS_H
Vasilij
  • 1,861
  • 1
  • 5
  • 9
  • so are you suggesting me to finally put my serial communication code in a separate thread then? I actually tried that before by puting my QSerialPort code in a QThread. But eventually, my code section with the iteration loop for the command sequence(which is in run button's clicked() slot) had to wait for the response. Am I wrong to presume that in any way that I try to wait for a response, my stop button will remain unresponsive? Also I don't think the while loop will give a different outcome. If needed I will share my code. It has lot of crappy math. – Aditya Aug 11 '20 at 11:05
  • I suggest several things. 1. To use another thread, but with QFuture. 2. Write communication code step after step, synchronously 3. Add a stop condition variable for emergency stop. 4. Set small timeouts for each blocking function and check stop condition between calls or write custom function which checks for data ready and stop condition. Then your GUI code will remain fast, buttons will work; communcation code will be synchronous and simple; you will have a mechanism to stop communication cycle and send emergency stop. – Vasilij Aug 11 '20 at 11:18