Reputation:
I'm trying to run a function for about 5 seconds using the QTimer
function. After studying the documentation and testing, I can't seem to find a function that does this in Qt.
I've tried the following approaches:
QTimer *timer = new QTimer(this);
connect(timer, SIGNAL(timeout()), this, SLOT(myFunction());
timer->start(5000);
This approach runs the function after every 5 seconds. This is not what I'm trying to achieve. I also attempted to use the singleShot()
property using the following code:
QTimer::singleShot(5000,this,SLOT(myFunction()));
This function simply fires my function once. Is there any property of QTimer
that can run my function for a given time. Something like:
run function and timeout function after 5 seconds.
EDIT: More Info:
This is for a robot competition in my University. What I'm driving to do is drive the wheels of a robot over TCP. I know how many seconds both wheels need to run to get the robot to turn a certain angle. The SLOT in myFunction
will be something like senddatatoRobot()
The function basically sends data over TCP to my robot, turning it a certain angle based on the orientation of the robot from a desired endpoint. For example, I know my robot will make a 360 degree turn if I send a PWM value of 4096 and 0 to the left and right wheels for 5 seconds. So to make a 45 degree turn for example, I would send data for a specified number of seconds.
Upvotes: 1
Views: 5778
Reputation: 49289
You could try something like this:
QElapsedTimer t;
t.start();
while (t.elapsed() < 5000) { ...do stuff... } // this will block the thread
You could also do something like this:
startDoingStuff(); // must not block the thread
QTimer::singleShot(5000, this, "stopDoingStuff");
But from your comments it sounds like you need to share what that function actually does, and most definitely learn more about event driven programming, then you will be able to come up with a better design.
The function basically sends data over TCP to my robot, turning it a certain angle based on the orientation of the robot from a desired endpoint. For example, I know my robot will make a 360 degree turn if I send a PWM value of 4096 and 0 to the left and right wheels for 5 seconds. So to make a 45 degree turn for example, I would enter send data for a specified number of seconds.
To me it looks like the more efficient solution would be to only send value changes rather than repeatedly sending the same data.
So on your robot's microcontroller code, you will cache and use those values continuously, and from your controller application, you will only be "setting" the remote values:
public slots:
void reset() { send(0, 0); }
...
// begin turning
send(4096, 0);
QTimer::singleShot(5000, this, "reset"); // end turning in 5 secs
An even better solution would be to instead of sending PWM values, you send your robot specific commands, and leave the robot's microcontroller keep the timing, as it will most likely be better at it than QTimer
. You can even get more specific, for example a command that turns X degrees in T seconds. You definitely have a lot of room for improvement on the side of the robot microcontroller code.
If you must use the approach you are currently attempting, you can use the QElapsedTimer
approach to implement a worker with a function void send(v1, v2, time)
as long as you put the worker in another thread, so you don't block the main thread, which will cause the OS to report your application as "not responding". But this way commands cannot be interrupted, and new commands cannot be issued until the current command is completed. You can improve that by implementing a non-blocking worker, as outlined in this example. This way you will be able to issue new commands before the current one is completed by interrupting in.
Upvotes: 1
Reputation: 98425
You haven't specified how you command your robot. There are two approaches:
Send an ON command at the start of the interval, then an OFF command at the end of the interval;
Repeatedly send an ON command during the interval, with the motor turning off "shortly" after the last command was received, with no explicit OFF command.
Either approach is easy to implement using state machines. Let's see how to implement the first approach.
A controller can encapsulate the behaviors. The isIdle
and isActive
signals would be connected to actions that send commands to your robot.
class Controller : public QObject {
Q_OBJECT
QStateMachine m_machine{this};
QState
m_idle {&m_machine},
m_active {&m_machine};
Transition
m_go{&m_idle, &m_active};
Delay
m_activeTime{&m_active, &m_idle, 0};
public:
Controller(QObject * parent = 0) : QObject(parent) {
connect(&m_idle, &QState::entered, this, &Controller::isIdle);
connect(&m_active, &QState::entered, this, &Controller::isActive);
m_machine.setInitialState(&m_idle);
m_machine.start();
}
Q_SLOT void moveFor(int ms) {
m_activeTime.setDuration(ms);
m_go();
}
Q_SIGNAL void isIdle();
Q_SIGNAL void isActive();
};
The Transition
and Delay
classes implement behaviors linked to state transitions:
// https://github.com/KubaO/stackoverflown/tree/master/questions/robot-state-timer-36769933
#include <QtWidgets>
struct Transition : public QObject {
Q_OBJECT
public:
Transition(QState * source, QState * destination) : QObject(source->machine()) {
source->addTransition(this, &Transition::trigger, destination);
}
Q_SIGNAL void trigger();
void operator()() { trigger(); }
};
class Delay : public Transition {
Q_OBJECT
int m_duration;
QBasicTimer m_timer;
void timerEvent(QTimerEvent * ev) {
if (m_timer.timerId() != ev->timerId()) return;
m_timer.stop();
trigger();
}
public:
Delay(QState * src, QState * dst, int ms) : Transition(src, dst), m_duration(ms) {
connect(src, &QState::entered, this, [this]{ m_timer.start(m_duration, this);});
}
Q_SLOT void setDuration(int duration) { m_duration = duration; }
};
A test harness can make the example complete.
int main(int argc, char ** argv) {
QApplication app{argc, argv};
Controller ctl;
QWidget w;
QFormLayout layout{&w};
QPushButton start{"Go"};
QLineEdit duration{"5000"};
QPlainTextEdit log;
log.setReadOnly(true);
layout.addRow("Duration", &duration);
layout.addRow(&start);
layout.addRow(&log);
QObject::connect(&ctl, &Controller::isIdle, &log, [&]{ log.appendPlainText("Idle"); });
QObject::connect(&ctl, &Controller::isActive, &log, [&]{ log.appendPlainText("Active"); });
QObject::connect(&start, &QPushButton::clicked, &ctl, [&]{
ctl.moveFor(duration.text().toInt());
});
w.show();
return app.exec();
}
#include "main.moc"
Upvotes: 1