1

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.

dtech
  • 47,916
  • 17
  • 112
  • 190
  • 1
    Running time of function depends on what it is doing. Please, explain, why you need this stuff? Looks like you are talking about real-time OS. – Dmitry Sazonov Apr 21 '16 at 12:41
  • @SaZ, I'm driving the wheels of a robot over wifi. I know how many seconds both wheels need to run to get the robot to turn a certain angle. That's what I'm trying to implement here. –  Apr 21 '16 at 12:47
  • 1
    I think you mean *interupt* your function after 5s. You'd do it in the function itself by polling, with threads by stopping the thread, or with a system level signal interrupt. It's not a Qt thing. – spinkus Apr 21 '16 at 13:06
  • @S.Pinkus, you're right, my wording perhaps wasn't that great. –  Apr 21 '16 at 13:18
  • 1
    I doubt that you need to send the data repeatedly. Send (4096,0) *once* to get the wheels turning, then send (0,0) *once* to get them to stop. – Kuba hasn't forgotten Monica Apr 21 '16 at 13:58
  • It is completely unnecessary, unless he doesn't have access to the robot's microcontroller code and that's the way it is programmed to work. Sending PWM values directly is rather rudimentary IMO. – dtech Apr 21 '16 at 14:05
  • @ddriver, the microcontroller is actually a PI with a servo HAT. There's a simple TCP server running on the PI receiving a stream of data. Efficiency isn't probably what I'm searching for here as I have may other things going on (Inverse Kinematics, Image Processing) etc. I'm chasing simplicity and quick implementation. –  Apr 21 '16 at 14:10
  • @MaskedAfrican the Pi is definitely an overkill, just get a 7$ esp8266 and go wireless, spend an hour writing a decent firmware for your robot and you will get exponential growth in functionality and a solid foundation for future projects. As for simplicity, see my latest edit. – dtech Apr 21 '16 at 14:16

2 Answers2

1

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.

Community
  • 1
  • 1
dtech
  • 47,916
  • 17
  • 112
  • 190
  • 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. –  Apr 21 '16 at 13:23
  • I ended up using your suggestion. _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_ –  Apr 21 '16 at 16:13
1

You haven't specified how you command your robot. There are two approaches:

  1. Send an ON command at the start of the interval, then an OFF command at the end of the interval;

  2. 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"
Kuba hasn't forgotten Monica
  • 95,931
  • 16
  • 151
  • 313