Implementing QTimer to execute a function for a given time - c++

I have a function that should send data to a raspberry pi for a given period of time depending on the parameter.
//headerfiles
namespace Ui {
class MainWindow;
}
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
explicit MainWindow(QWidget *parent = 0);
~MainWindow();
private:
Ui::MainWindow *ui;
QUdpSocket udpSocket;
// movement Timer
QTimer* movementTimer;
private slots:
void sendDatagram(); // Sends to the RaspberryPi
void processFrameAndUpdateGUI();
void turnLeft(double time);
void turnRight(double time);
void goStraight(double time);
void MovRobot();
};
// MainWindow.cpp
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow)
{
movementTimer = new QTimer(this);
//send datagram sends data to tehe PI.
connect(movementTimer, SIGNAL(timeout()), this, SLOT(sendDatagram()));
MovRobot(); // MovRobot() function
}
// controls robot to turn left for specified time
void TurnLeft(double time)
{
s = 1; // sets the value to be sent to the PI.
movementTimer->setInterval(time);
movementTimer->setSingleShot(true);
movementTimer->start();
}
//sendDatagram() slot
void MainWindow::sendDatagram() {
QString datagramOutput = "start," +
QString::number(w) + ',' + QString::number(a) + ',' +
QString::number(s) + ',' + QString::number(d) + ',' +
QString::number(ui->motorSpeedSlider->value()) + ',' +
QString::number(dispenserSignal);
datagramOutput += ",end";
QByteArray datagram;
QDataStream out(&datagram,QIODevice::WriteOnly);
out << datagramOutput;
udpSocket.writeDatagram(datagram,QHostAddress("192.168.0.104"),12345);
}
//MovRobot Function;
void MainWindow :: MovRobot() {
if (ui->pushButton_3->isChecked()) {
MapArea(); // This function maps the area....
// final_plan is a vector<Point2i> that stores positions on the map for
// the robot to move to
for (int i = 0; i < final_plan.size(); i++) {
for (int j = final_plan.size() - 1; j>=0; j--) {
do {
Mat src;
bool bsuccess = cap.read(src);
if (!bsuccess) {
ui->label_34->setText("Status: Can't read frame.");
ui->pushButton_3->setChecked(0);
}
GetRobotPosition(src);
// AngleToGoal calculates the angle of the robot relative
// to the final goal.
double tempAngle = AngletoGoal(final_plan[i][j]);
if (tempAngle>=0) {
turnLeft(TimeToTurn(tempAngle));
}
else {
turnRight(TimeToTurn(tempAngle));
}
double tempDistance = DistancetoGoal(final_plan[i][j]);
goStraight(tempDistance);
} while (DistancetoGoal(final_plan[i][j])<20);
}
}
}
}
The timer should only send the data over to the PI for the duration it in time sendDatagram is the function that sends the data over to the Pi. Is there anything I'm missing here. The timer doesn't start inside the TurnLeft() function and doesn't run at all. Am I going about this wrong?
EDIT: 13/03/2016:
My apologies for the late reply. I've been quite sick the past few days. I've added the relevant parts of the code. MovRobot() is the main function responsible for the movement and this is called in the constructor for MainWindow. I have debugged and stepped through the program and yes, TurnLeft() is called. However, the sendDatagram() slot doesn't actually send anything in the function. To confirm sendDatagram() was actually working, I used another timer to continuously send information to the PI on the robot to control the arm.
// Header File
QTimer* tmrTimer;
private slot:
void processFrameAndUpdateGUI();
// MainWindow Constructor:
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow)
{
connect(tmrTimer, SIGNAL(timeout()), this, SLOT(processFrameAndUpdateGUI()));
tmrTimer->start(10);
}
void MainWindow::processFrameAndUpdateGUI() {
sendDatagram();
}
The sendDatagram() slot is pretty much the same with the exception of me changing what values are being sent to the PI and this seems to work perfectly.
However, my original problem is, I would like to send the data to the robot with for a specified amount of time as that makes the robot turn x degrees. This is why I've made movementTimer() single shot.
Stepping through my program, I know this line is called within my TurnLeft function.
movementTimer->start();
but the sendDatagram() slot itself doesn't actually send anything to the PI.

Based on your code. The program never called TurnLeft(). So the timer never started. It will be better to start the timer in constrator instead.

timer = new QTimer(this);
connect(timer, SIGNAL(timeout()), this, SLOT(MySlot()));
timer->start(1000);
I would try setting the timer to start in the constructor.

Related

QProgressBar updates as function progress

How to initializa the operation of QProgressBar, I already declare her maximum, minimum, range and values.
I want to assimilate the progress of QProgressBar with the "sleep_for" function.
Current code:
void MainPrograma::on_pushCorre_clicked()
{
QPlainTextEdit *printNaTela = ui->plainTextEdit;
printNaTela->moveCursor(QTextCursor::End);
printNaTela->insertPlainText("corrida iniciada\n");
QProgressBar *progresso = ui->progressBar;
progresso->setMaximum(100);
progresso->setMinimum(0);
progresso->setRange(0, 100);
progresso->setValue(0);
progresso->show();
WORD wEndereco = 53606;
WORD wValor = 01;
WORD ifValor = 0;
EscreveVariavel(wEndereco, wValor);
//How to assimilate QProgressBar to this function:
std::this_thread::sleep_for(std::chrono::milliseconds(15000));
//StackOverFlow help me please
EscreveVariavel(wEndereco, ifValor);
use a QTimer
and in the slot update the value of the progressbar
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow)
{
ui->setupUi(this);
t = new QTimer(this);
t->setSingleShot(false);
c = 0;
connect(t, &QTimer::timeout, [this]()
{ c++;
if (c==100) {
c=0;
}
qDebug() << "T...";
ui->progressBar->setValue(c);
});
}
MainWindow::~MainWindow()
{
delete ui;
}
void MainWindow::on_pushButton_clicked()
{
t->start(100);
}
I'm not sure about your intentions with such sleep: are you simulating long wait? do you have feedback about progress during such process? Is it a blocking task (as in the example) or it will be asynchronous?
As a direct answer (fixed waiting time, blocking) I think it is enough to make a loop with smaller sleeps, like:
EscreveVariavel(wEndereco, wValor);
for (int ii = 0; ii < 100; ++ii) {
progresso->setValue(ii);
qApp->processEvents(); // necessary to update the UI
std::this_thread::sleep_for(std::chrono::milliseconds(150));
}
EscreveVariavel(wEndereco, ifValor);
Note that you may end waiting a bit more time due to thread scheduling and UI refresh.
For an async task you should pass the progress bar to be updated, or some kind of callback that does such update. Keep in mind that UI can only be refreshed from main thread.

Qt double spin box autofills when processing value changed

I have a time-consuming computation that depends on a double value. For this, I've created a GUI where I can set the value for the computation using a QDoubleSpinBox. The double spin box QDoubleSpinBox::valueChanged(double) signal is connected to a slot that starts the heavy computation in a new thread using QtConcurrent::run. The problem is that when I add a progress bar, the double spin box automatically fills its contents (zero-padding until the number of decimals) when the progress bar appears. My feeling is that this is because the double spin box loses the focus (i.e. the progress bar is the selected widget).
My question is:
How can I show the progress bar and make the double spin box
not fill the rest of the decimals with zeros?
This video shows how, when the progress bar is not shown, I can keep editing the double spin box while, when the progress bar is shown, the double spin box fills its precision with zeros. This is the current behavior, not the desired one. The desired one is that after the computation is done the double spin box is has not automatically filled its empty decimal places with zeros. Here is the code used for the video (fully available in GitHub):
Header
class DoubleSpinboxHeavyComputation : public QWidget
{
Q_OBJECT
public:
explicit DoubleSpinboxHeavyComputation(QWidget *parent = nullptr);
~DoubleSpinboxHeavyComputation();
signals:
void computationDone();
void progressSignal(int progress_state);
private slots:
void startHeavyComputationInThread();
void heavyComputation();
private:
Ui::DoubleSpinboxHeavyComputation *ui;
int n_ = 0;
};
Implementation
DoubleSpinboxHeavyComputation::DoubleSpinboxHeavyComputation(QWidget *parent)
: QWidget(parent), ui(new Ui::DoubleSpinboxHeavyComputation)
{
ui->setupUi(this);
connect(ui->doubleSpinBox, QOverload<double>::of(&QDoubleSpinBox::valueChanged), this,
&DoubleSpinboxHeavyComputation::startHeavyComputationInThread);
}
DoubleSpinboxHeavyComputation::~DoubleSpinboxHeavyComputation()
{
delete ui;
}
void DoubleSpinboxHeavyComputation::startHeavyComputationInThread()
{
if (ui->checkBox->isChecked())
{
QProgressDialog *progress = new QProgressDialog("Computing", "", 0, 0, this);
progress->setWindowTitle(windowTitle());
progress->setWindowFlags((progress->windowFlags() | Qt::CustomizeWindowHint) &
~Qt::WindowCloseButtonHint); // Hide close button
progress->setWindowModality(Qt::WindowModal);
progress->setCancelButton(nullptr);
progress->setMaximum(1000);
progress->show();
connect(this, &DoubleSpinboxHeavyComputation::progressSignal, progress, &QProgressDialog::setValue);
connect(this, &DoubleSpinboxHeavyComputation::computationDone, progress, &QProgressDialog::close);
connect(this, &DoubleSpinboxHeavyComputation::computationDone, progress, &QProgressDialog::deleteLater);
}
QtConcurrent::run(this, &DoubleSpinboxHeavyComputation::heavyComputation);
}
void DoubleSpinboxHeavyComputation::heavyComputation()
{
int current_n = n_;
++n_;
qDebug() << "Start computation " << current_n;
for (int i = 0; i < 1000; i++)
{
emit progressSignal(i);
usleep(1000);
}
qDebug() << "End computation" << current_n;
emit computationDone();
}
Looks like you need to subclass your QDoubleSpinbox and reimplement textFromValue method
class NumericEdit : public QDoubleSpinBox
{
Q_OBJECT
public:
NumericEdit(QWidget *p_parent = nullptr);
QString textFromValue(double val) const;
};
QString NumericEdit::textFromValue(double val) const
{
//default converting
// return QString::number(val);
//converting with a local representation
QLocale locale;
return locale.toString(val);
}
But this will break default prefix and suffix functional of spinbox

SIGNAL and SLOT parameters issue in QT

Could someone please advise how to correctly implement SLOT execution?
my code:
prog::prog(QWidget *parent): QMainWindow(parent) //constructor (VisualStudio):
{
ui.setupUi(this);
QCustomPlot * customPlot = new QCustomPlot(this);
setupRealtimeDataDemo(customPlot);
// + more code
}
void prog::setupRealtimeDataDemo(QCustomPlot * customPlot)
{
customPlot->addGraph(); //
// + more related with graph methods
// setup a timer that repeatedly calls realtimeDataSlot:
connect(&dataTimer, SIGNAL(timeout()), this, SLOT(realtimeDataSlot(QCustomPlot)));
dataTimer.start(0); // Interval 0 means to refresh as fast as possible
}
void prog::realtimeDataSlot(QCustomPlot *customPlot)
{
// calculate two new data points:
#if QT_VERSION < QT_VERSION_CHECK(4, 7, 0)
double key = 0;
#else
double key = QDateTime::currentDateTime().toMSecsSinceEpoch()/1000.0;
#endif
static double lastPointKey = 0;
if (key-lastPointKey > 0.01) // at most add point every 10 ms
{
double value0 = qSin(key); //sin(key*1.6+cos(key*1.7)*2)*10 + sin(key*1.2+0.56)*20 + 26;
double value1 = qCos(key); //sin(key*1.3+cos(key*1.2)*1.2)*7 + sin(key*0.9+0.26)*24 + 26
// add data to lines:
customPlot->graph(0)->addData(key, value0);
customPlot->graph(1)->addData(key, value1);
// + more code related with graph
}
}
Here are my findings:
SIGNAL and SLOT need the same signature, build program won't run
SLOT ( because SLOT become undefined).
Possible solution: remove QCustomPlot argument form SLOT, but how
then should I send to realtimeDataSlot pointer to QCustomPlot? Maybe
it possible to overload timeout() ? Maybe other solution?
I discovered when I use #include "winsock2.h" and try to "Promote to..." option
like in http://www.qcustomplot.com/index.php/tutorials/settingup errors
appears about parameters redefinition , so this workaround I cannot
use. I also don't won't to use qwt
Thanks is advance for help!
There is a multitude of solutions. Two come to mind:
Make the QCustomPlot* a member of the prog class:
class prog : public QWidget {
Q_OBJECT
QScopedPointer<QCustomPlot> m_plot;
...
}
prog::prog(QWidget *parent): QMainWindow(parent) :
m_plot(new QCustomPlot)
{
ui.setupUi(this);
setupRealtimeDataDemo(m_plot.data());
}
Use C++11 and Qt 5 features:
connect(&dataTimer, &QTimer::timeout, this, [=]{
realtimeDataSlot(customPlot); // doesn't need to be a slot anymore
});

Progress bar function not looping

Almost done with that application I've been working on BUT, now I have one more problem. I created a QProgressBar and connected it to a QTimer. It goes up one percent per second but surpasses the actual progress. I have yet to program in the former however I set up the timer to go up one every second. Here is my problem the progress bar goes up to one percent then stops. It hits the if statement every second I know that, but it doesn’t go any higher then 1%.
Edit:
Sorry meant to add the code.
#include "thiwindow.h"
#include "ui_thiwindow.h"
#include <QProcess>
#include <fstream>
#include <sstream>
int ModeI;
ThiWindow::ThiWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::ThiWindow)
{
ui->setupUi(this);
std::ifstream ModeF;
ModeF.open ("/tmp/Mode.txt");
getline (ModeF,ModeS);
std::stringstream ss(ModeS);
ss >> ModeI;
ModeF.close();
SecCount = new QTimer(this);
Aproc = new QProcess;
proc = new QProcess;
connect(SecCount, SIGNAL(timeout()), this, SLOT(UpdateProcess()));
connect(Aproc, SIGNAL(readyRead()), this, SLOT(updateText()));
connect(proc, SIGNAL(readyRead()), this, SLOT(updateText()));
SecCount->start(1000);
if (ModeI==1)
Aproc->start("gksudo /home/brooks/Documents/Programming/AutoClean/LPB.pyc");
else
proc->start("/home/brooks/Documents/Programming/AutoClean/LPB.pyc");
ui->progressBar->setValue(0);
}
ThiWindow::~ThiWindow()
{
delete ui;
}
void ThiWindow::updateText()
{
if (ModeI==1){
QString appendText(Aproc->readAll());
ui->textEdit->append(appendText);}
else{
QString appendText(proc->readAll());
ui->textEdit->append(appendText);}
}
void ThiWindow::UpdateProcess()
{
SecCount->start(1000);
int Count=0;
float Increments;
int Percent_limit;
if (ModeI==1){
Increments = 100/5;
Percent_limit = Increments;
if (Count<Percent_limit) {
Count += 1;
ui->progressBar->setValue(Count);
}
}
}
If you need more let me know.
Thanks,
Brooks Rady
You are always incrementing zero. int Count=0; This have to be removed from this function and moved for example to constructor where timer is started and declare it in header file ( shown in last two code snipets )
void ThiWindow::UpdateProcess()
{
SecCount->start(1000);
int Count=0; // Count is 0
float Increments;
int Percent_limit;
if (ModeI==1){
Increments = 100/5;
Percent_limit = Increments;
if (Count<Percent_limit) {
Count += 1; // Count is 0 + 1
ui->progressBar->setValue(Count); // progressBar is 1
}
}
}
You have to declare Count in header file. Count will be stored as long as ThiWindows exists. Not only for a few miliseconds in your example ( Count was destroyed when your UpdateProccess functions finish and then recreated again when it was called again )
class ThiWindow : public QMainWindow {
Q_OBJECT
public:
// whatever you have
private:
int Count;
}
Count should be initialized before Timer starts
SecCount = new QTimer(this);
Aproc = new QProcess;
proc = new QProcess;
connect(SecCount, SIGNAL(timeout()), this, SLOT(UpdateProcess()));
connect(Aproc, SIGNAL(readyRead()), this, SLOT(updateText()));
connect(proc, SIGNAL(readyRead()), this, SLOT(updateText()));
Count = 0; // << move Count variable here
SecCount->start(1000);

Refreshing with repaint() stops working

Today I encountered a problem with repaint() function from QT libraries. Long story short, I got a slot where I train my neural network using BP algorithm. I had tested the whole algorithm in console and then wanted to move it into GUI Application. Everything works fine except refreshing. Training of neural networks is a process containing a lot of computations, which are made in bp_alg function (training) and licz_mse function (counting a current error). Variable ilosc_epok can be set up to 1e10. Therefore the whole process may last even several hours. Thats why I wanted to display a current progress after each 100000 epochs (the last if contition). wyniki is an object of QTextEdit class used for displaying the progress. Unfortunately, repaint() doesnt work as intended. At the beginning it refreshes wyniki in GUI, but after some random time it stops working. When the external loop is finished, it refreshes once again showing all changes.
I tried to change frequency of refreshing, but sooner or later it always stops (unless the whole training process stops early enough because of satisfying the break condition). It looks like at some moment of time the application decides to stop refreshing because of too many computations. Imo it shouldnt happen. I was looking for a solution among older questions and managed to solve the problem when I used qApp->processEvents(QEventLoop::ExcludeUserInputEvents); instead of wyniki->repaint();. However, Im still curious why repaint() stops working just like that.
Below I paste a part of the code with the problematic part. Im using QT Creator 2.4.1 and QT Libraries 4.8.1 if it helps.
unsigned long int ile_epok;
double mse_w_epoce;
for (ile_epok=0; ile_epok<ilosc_epok; ile_epok++) { //external loop of training
mse_w_epoce = 0;
for (int i=0; i<zbior_uczacy_rozmiary[0]; i++) { //internal loop of training
alg_bp(zbior_uczacy[i], &zbior_uczacy[i][zbior_uczacy_rozmiary[1]]);
mse_w_epoce += licz_mse(&zbior_uczacy[i][zbior_uczacy_rozmiary[1]]);
}
//checking break condition
if (mse_w_epoce < warunek_stopu) {
wyniki->append("Zakończono uczenie po " + QString::number(ile_epok) + " epokach, osiągając MSE: " + QString::number(mse_w_epoce));
break;
}
//problematic part
if ((ile_epok+1)%(100000) == 0) {
wyniki->append("Uczenie w toku, po " + QString::number(ile_epok+1) + " epokach MSE wynosi: " + QString::number(mse_w_epoce));
wyniki->repaint();
}
}
You're blocking your GUI thread, so repaints will not work, it's just plainly bad design. You're never supposed to block the GUI thread.
If you insist on doing the work in the GUI thread, you must forcibly chop the work into small chunks and return to the main event loop after each chunk. Nested event loops are evil, so don't even think you'd want one. All this has a bad code smell, so stay away.
Alternatively, simply move your computation QObject to a worker thread and do the work there.
The code below demonstrates both techniques. It's easy to notice that the chopping-up-of-work requires to maintain loop state inside of the worker object, not merely locally in the loop. It's messier, the code smells bad, again - avoid it.
The code works under both Qt 4.8 and 5.1.
//main.cpp
#include <QApplication>
#include <QThread>
#include <QWidget>
#include <QBasicTimer>
#include <QElapsedTimer>
#include <QGridLayout>
#include <QPlainTextEdit>
#include <QPushButton>
class Helper : private QThread {
public:
using QThread::usleep;
};
class Trainer : public QObject {
Q_OBJECT
Q_PROPERTY(float stopMSE READ stopMSE WRITE setStopMSE)
float m_stopMSE;
int m_epochCounter;
QBasicTimer m_timer;
void timerEvent(QTimerEvent * ev);
public:
Trainer(QObject *parent = 0) : QObject(parent), m_stopMSE(1.0) {}
Q_SLOT void startTraining() {
m_epochCounter = 0;
m_timer.start(0, this);
}
Q_SLOT void moveToGUIThread() { moveToThread(qApp->thread()); }
Q_SIGNAL void hasNews(const QString &);
float stopMSE() const { return m_stopMSE; }
void setStopMSE(float m) { m_stopMSE = m; }
};
void Trainer::timerEvent(QTimerEvent * ev)
{
const int updateTime = 50; //ms
const int maxEpochs = 5000000;
if (ev->timerId() != m_timer.timerId()) return;
QElapsedTimer t;
t.start();
while (1) {
// do the work here
float currentMSE;
#if 0
for (int i=0; i<zbior_uczacy_rozmiary[0]; i++) { //internal loop of training
alg_bp(zbior_uczacy[i], &zbior_uczacy[i][zbior_uczacy_rozmiary[1]]);
currentMSE += licz_mse(&zbior_uczacy[i][zbior_uczacy_rozmiary[1]]);
}
#else
Helper::usleep(100); // pretend we're busy doing some work
currentMSE = 2E4/m_epochCounter;
#endif
// bail out if we're done
if (currentMSE <= m_stopMSE || m_epochCounter >= maxEpochs) {
QString s = QString::fromUtf8("Zakończono uczenie po %1 epokach, osiągając MSE: %2")
.arg(m_epochCounter).arg(currentMSE);
emit hasNews(s);
m_timer.stop();
break;
}
// send out periodic updates
// Note: QElapsedTimer::elapsed() may be expensive, so we don't call it all the time
if ((m_epochCounter % 128) == 1 && t.elapsed() > updateTime) {
QString s = QString::fromUtf8("Uczenie w toku, po %1 epokach MSE wynosi: %2")
.arg(m_epochCounter).arg(currentMSE);
emit hasNews(s);
// return to the event loop if we're in the GUI thread
if (QThread::currentThread() == qApp->thread()) break; else t.restart();
}
m_epochCounter++;
}
}
class Window : public QWidget {
Q_OBJECT
QPlainTextEdit *m_log;
QThread *m_worker;
Trainer *m_trainer;
Q_SIGNAL void startTraining();
Q_SLOT void showNews(const QString & s) { m_log->appendPlainText(s); }
Q_SLOT void on_startGUI_clicked() {
QMetaObject::invokeMethod(m_trainer, "moveToGUIThread");
emit startTraining();
}
Q_SLOT void on_startWorker_clicked() {
m_trainer->moveToThread(m_worker);
emit startTraining();
}
public:
Window(QWidget *parent = 0, Qt::WindowFlags f = 0) :
QWidget(parent, f), m_log(new QPlainTextEdit), m_worker(new QThread(this)), m_trainer(new Trainer)
{
QGridLayout * l = new QGridLayout(this);
QPushButton * btn;
btn = new QPushButton("Start in GUI Thread");
btn->setObjectName("startGUI");
l->addWidget(btn, 0, 0, 1, 1);
btn = new QPushButton("Start in Worker Thread");
btn->setObjectName("startWorker");
l->addWidget(btn, 0, 1, 1, 1);
l->addWidget(m_log, 1, 0, 1, 2);
connect(m_trainer, SIGNAL(hasNews(QString)), SLOT(showNews(QString)));
m_trainer->connect(this, SIGNAL(startTraining()), SLOT(startTraining()));
m_worker->start();
QMetaObject::connectSlotsByName(this);
}
~Window() {
m_worker->quit();
m_worker->wait();
delete m_trainer;
}
};
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
Window w;
w.show();
return a.exec();
}
#include "main.moc"