3

I'm writing a software in Qt/c++ that communicate through serial with arduino and other electronic devices.

I need to start a sequence of events that call different slot with different timing like this:

  • Start Motor 1 For 20 seconds
  • After 10 seconds Start Motor 2
  • Stop Motor 1
  • Change Motor 1 speed
  • Start Motor 1 For 30 second

I've tried with QTimer::singleShot but it works only with slot with no parameters and I need to set parameters like motor speed different from time to time.

Right now I'm using a delay function that confront currentTime do dieTime but it is complicated to keep track of timing to all the devices.

What is the best solution in doing this? Suggestions?

nicodio
  • 55
  • 1
  • 6
  • If the static function QTimer::singleShot doesn't work for you, what about using instances of QTimer, set to single shot usage ( http://doc.qt.io/qt-5/qtimer.html#singleShot-prop ) – TheDarkKnight May 20 '15 at 13:37
  • 2
    `QTimer` is indeed the best solution. And you can pass paraneters through class's fields. – Amartel May 20 '15 at 13:38
  • @Amartel, I'm trying to pass parameters through class's fields but my problem is how to pass parameters according to time. If, for example, I want to set a motor to x speed, start motor for 30 seconds, stop motor, set it to y speed and then start again how can I update parameters according with timing? I'm thinking about queueing all parameters but I don't think it's a good solution. – nicodio May 20 '15 at 14:01

2 Answers2

4

You can use the static QTimer single shot function if you use the overloaded method, which takes a Functor (function object). This would allow you to capture the variables required; which motor, speed, action etc. If you're not familiar with Functors, you can read about them here.

Alternatively, since the question provides no code examples, let's assume you've already written functions for starting and stopping motors. For a more direct method, with C++11, you could do something like this:

StartMotor(1);

// Stop in 20 seconds
QTimer* p1 = new QTimer;
connect(p1, &QTimer::timeout, [=]{
    StopMotor(1);
    p1->deleteLater(); // clean up
});
p1->start(1000 * 20); // trigger in 20 seconds

// After 10 seconds, start motor 2

QTimer* p2 = new QTimer;
connect(p2, &QTimer::timeout, [=]{
    StartMotor(2);

    // And Stop Motor 1
    StopMotor(1);

    p2->deleteLater(); // clean up
});
p2->start(1000 * 10); // trigger in 10 seconds

...And so on for each timed action.

Community
  • 1
  • 1
TheDarkKnight
  • 27,181
  • 6
  • 55
  • 85
  • Using lambda as a slot/callback looks very elegant. There is no need actually to define each timer. QTimer *p; and p = new QTimer; each time would also do. – Valentin H May 21 '15 at 09:44
  • @ValentinHeinitz, absolutely, though I defined separate timers for clarity here. – TheDarkKnight May 21 '15 at 10:20
  • I accepted this answer because is what I needed to do right now. The only thing is that with a lot of instruction the code could be a little difficult to read but it's my job to write it the best I can. Thank you. – nicodio May 22 '15 at 09:35
  • If readability becomes a problem, I suggest looking at using a Functor to encapsulate the actions for each timer. Alternatively, you could create functions such as Action1, Action2, Action3 etc., then write a function which takes a pointer to an action function and the time delay. This function then creates the timer and calls start, with the lamda calling the given function passed via the function pointer. – TheDarkKnight May 22 '15 at 09:48
3

Implement a class for a motor e.g.:

class Motor:public QObject
{
  Q_OBJECT
  public slots:
    void start();
    void start(int ms); //this one starts QTimer::singleShot and calls stop
    void stop();
};

I would recommend to check QStateMachine Framework. When tasks will get more complicated, you'd be better off using FSM than spaghetti of signal-slot calls.

In my current project I've built a FSM based on QStateMachine where the definition of FSMs is done in a DSL (domain specific language).

Valentin H
  • 7,240
  • 12
  • 61
  • 111
  • In this solution if from the main class I do something like that: motor1->start(10); motor1->setSpeed(100); motor1->start(20); It take just the first command because when I call the second and third one the first is still executing. Do I have to create some sort of queue to keep all the commands sent? – nicodio May 20 '15 at 14:48
  • 1
    depends on what you want. If commands should be executed one after another asynchronously, yes queuing is the only one solution. Queue either the strings as commands and interpret them as far as the previous command is finished, or use command pattern - encapsulate a command in a class. – Valentin H May 20 '15 at 15:11
  • Thank you. I accepted the other answer 'cause it's more specific for my question. But I will use your suggestions too to write a better and more usable code. – nicodio May 22 '15 at 09:37