Mock Robot Example

This tutorial demonstrates an EEROS application complete with a control system, a safety system, and a sequencer. You can find the code under MockRobotExample.cpp.

Start the program with

$ sudo ./examples/system/mockRobotExample

The Full Picture

This example does not need any hardware. The moving of a robot is pretended by using integrators summing up. This represents the position on an axis. Before a robot can be moved it has usually to be homed.

The main application defines a control system and a safety system. The safety system is assigned as main task and passed to the executor. Further, a main sequence is defined and added to the sequencer and started. After this the executor starts running.

int main(int argc, char **argv) {
  ...
  double period = 0.01;
  MockRobotControlSystem cs(period);
  MockRobotSafetyProperties sp(cs, period);
  SafetySystem ss(sp, period);
 
  auto& executor = Executor::instance();
  executor.setMainTask(safetySys);
 
  auto& sequencer = Sequencer::instance();
  MainSequence mainSeq("Main Sequence", sequencer, cs, ss, sp);
  sequencer.addSequence(mainSeq);
  mainSeq();
 
  ...
  executor.run();
  sequencer.wait();
}

The executor only stops after the system is stopped by pressing Ctrl-C. For this purpose a signal handler has to be defined which itself stops the executor and the sequencer.

void signalHandler(int signum) {
  SafetySystem::exitHandler();
  Sequencer::instance().abort();;
}

The main program must wait for the sequencer to stop until it can finish, in order to terminate gracefully without any running thread.

Control System

The control system is very simple. Each of the two axis x and y, it has a constant block acting as a set point and an integrator block pretending the position to move.
In the constructor of the control system the blocks are created, connected, and the integrator are enabled. All of the blocks are added to a time domain which itself is passed to the executor. This time domain runs in parallel to the safety system task.

Safety System

The safety system has four levels and a couple of safety events to switch between these levels.

Safety system with levels and events.

The event abort can be triggered in all levels except slOff. The safety system starts in the level slHoming. As soon as the homing is complete the safety level switches to slReady. The sequencer can then cause the safety system to change to level slMoving.

Sequencer

The main sequence comprises of two subsequences and a step. The first of the two sequences is responsible for homing while the latter moves the robot up and down. The main sequence does the following action:

  int action() {
    while(Sequencer::running) {
      if (ss.getCurrentLevel() == sp.slHoming) {
        homing();
      } else if(ss.getCurrentLevel() == sp.slReady) {
        wait(2);
        cs.sw.switchToInput(1);
        ss.triggerEvent(sp.startMoving);
      } else if(ss.getCurrentLevel() == sp.slMoving) {
        upDown();
      }
      wait(0.1);
    }
    return 0;
  }

Depending on the safety level the homing or the upDown sequence is started. In slReady the robot pauses for 2 seconds. The action is stopped as soon as the sequencer stops running. The wait(0.1) is necessary to wait for the safety system to actually switch levels. Without waiting the situation could arise that a safety event is triggered which takes effect in the next cycles when the executor runs the safety system.

class HomingSequence : public Sequence {
public:
  HomingSequence(std::string name, Sequence* caller, MockRobotControlSystem& cs, SafetySystem& ss, MockRobotSafetyProperties& sp) : 
    Sequence(name, caller, true), 
    cs(cs), ss(ss), sp(sp) { }
 
  int action() {
    cs.setpoint.setValue({0.1, 0.1});
  }
  bool checkExitCondition() {
    Matrix<2,1,double> val = cs.i.getOut().getSignal().getValue();
    bool done = val[0] >= 1.0 && val[1] >= 1.0;
    if (val[0] >= 1.0) cs.setpoint.setValue({0, cs.setpoint.getValue()[1]});
    if (val[1] >= 1.0) cs.setpoint.setValue({cs.setpoint.getValue()[0], 0});
    if (done) ss.triggerEvent(sp.homingDone);
    return done;
  }
  MockRobotControlSystem& cs;
  SafetySystem& ss;
  MockRobotSafetyProperties& sp;
};

What does the sequence do? It's action method simply adjusts the set points of the two axis to a small positive value. That causes the position of the robot (represented by the two integrators) to slowly move upwards. The sequence, which in this case is a simple step, terminates as soon as its exit condition is met. That is when both axis reach the position of 1.0. As soon as one of the axis reaches the position of 1.0 its setpoint is zeroed in order to stop it moving further. On termination a safety event is triggered.

The second sequence “UpAndDown Sequence” is as follows:

class MoveUp : public Step {
public:
  MoveUp(std::string name, Sequence* caller, MockRobotControlSystem& cs) : Step(name, caller), cs(cs) { }
  int action() {
    Matrix<2,1,double> dest{8.2, 27.0};
    cs.pp.move(dest);
    return 0;
  }
  bool checkExitCondition() {return cs.pp.endReached();}
  MockRobotControlSystem& cs;
};
 
class MoveDown : public Step {
public:
  MoveDown(std::string name, Sequence* caller, MockRobotControlSystem& cs) : Step(name, caller), cs(cs) { }
  int action() {
    Matrix<2,1,double> dest{-5, 3};
    cs.pp.move(dest);
    return 0;
  }
  bool checkExitCondition() {return cs.pp.endReached();}
  MockRobotControlSystem& cs;
};
 
class UpAndDownSequence : public Sequence {
public:
  UpAndDownSequence(std::string name, Sequence* caller, MockRobotControlSystem& cs) : 
    Sequence(name, caller, true), 
    moveUp("move up", this, cs), 
    moveDown("move down", this, cs) { }
 
    int action() {
      while (Sequencer::running) {
        moveUp();
        moveDown();
      }
      return 0;
    }
  MoveUp moveUp;
  MoveDown moveDown;
};

Its action method is an endless loop running the steps “move up” and “move down” repetitively. Here again, the sequence is nonblocking while the single steps block when called. Both steps simply set the set point of the two integrators which causes them to run up- or downwards. Both steps stop when their exit condition is met. The sequence itself needs no exit condition because of its endless loop.

Neither of the two sequences has an associated monitor. In reality this could be very useful to check for a blocked robot arm for instance.