How to Organize Sequences and Steps

Sometimes its not obvious what sould go into a separate step and what to pack directly into a sequence. Let's study the following example.
A robot finished a homing sequence. It should move to a ready position before further action can happen. For this purpose a sequence readying is implemented as follows:

class Readying : public Sequence {
public:
  Readying(std::string name, Sequencer& seq) : Sequence(name, seq) {setNonBlocking();}
 
  int action() {
    cs.pathPlanner.move(readyPos);  // we assume that the control system comprises of a path planner
    return 0;
  }
 
  bool checkExitCondition() {
    bool end = cs->pathPlanner.endReached();
    if (end) safetySystem->triggerEvent(safetyProperties->readyDone);
    return end;
  }
};

The sequence consists of a single action - set the final destination of the path planner. It will terminate as soon as the path planner has reached this position. Before returning it will trigger a safety event which causes the safety system to switch to the next level.

Alternative Solution

The same goal as above could be achieved as follows:

class Move : public Step {
  Move(std::string name, Sequence* caller) : Step(name, caller) { }
 
  int action() {
    cs.pathPlanner.move(readyPos);  // we assume that the control system comprises of a path planner
    return 0;
  }
 
  bool checkExitCondition() {
    return cs->pathPlanner.endReached();
  }
}
 
class Readying : public Sequence {
public:
  Readying(std::string name, Sequencer& seq) : Sequence(name, seq), move("move", this) {setNonBlocking();}
 
  int action() {
    move(readyPos);
    if (Sequencer::running) safetySystem->triggerEvent(safetyProperties->readyDone);
    return 0;
  }
private:
  Move move;
};

This time Readying calls Move, which itself is a separate step. This step terminates as soon as the ready position is reached. Readying then triggers the aforementioned safety event and terminates itself. No exit condition must be testet for Readying. Please note that the safety event should only be triggered if the sequencer is still running. If the sequencer is requested to stop (e.g. by aborting all sequences by pressing Ctrl-C) the current action method must run to completion and would hence trigger the event, which is not what a user might want.