Pipeline#

The Pipeline provides a framework for integrating the Controller and Predictor together and integrating the MPMCA with other systems in your (distributed) simulator environment. The main class is the Pipeline class. Some important properties of the Pipeline class are listed below. The Pipeline:

  • can contain multiple InOutput instances, which are typically used for simple and fast functions related to communication with the outside world,
  • will contain exactly one Task, which can contain multiple Step instances, which are typically used for complex and slow computational tasks such as the prediction and control updates,
  • uses the DataBucket and its sub-components for thread-safe information exchange between InOutput and Step instances,
  • uses a Safety object to keep track of the health status of the Pipeline,
  • uses a StateMachine object to keep track of the functional state (e.g., preparing, running, stopping, etc.),
  • can be triggered periodically (with high accuracy) using the Trigger class,
  • will call the Prepare(), Tick(), MainTick(), and TaskCompleted() callbacks on the InOutput and Step instances when the health status is Safe, and
  • will (only) call the SafeTick() function on InOutput instances whenever the health status becomes Error or Critical.

Sampling#

The Tick(), MainTick(), and TaskCompleted() callbacks are commonly refered to as the update callbacks. The Pipeline will call the various update callbacks periodically at two different rates, whereas the sample time of the slower rate (the updates of the Step instances) is an integer multiple of the sample time of the faster rate (the updates of the InOutput instances).

Typically, the Pipeline calls the Tick() callback on the InOutput instances once every millisecond, and calls the MainTick() callback on the InOutput and Step instances once every 10 milliseconds. The two rates are configurable, with the only constraint that the MainTick() update rate is an integer multiple of the Tick() update rate. For example, a Tick() interval of 400 microseconds and a MainTick() interval of 1200 microseconds (multiple of 3) would be a valid configuration. The two rates can also be equal (e.g., both at 100 Hz).

The Tick() refers to the periodic update of the InOutput instances. The MainTick() refers to the periodic update of Step instances inside the Task. The MainTick() callback is called every n-th time (where n is an integer number) the Tick() callback is called after completion of the Tick() calls on the same sample. Directly following the completion of the MainTick() call on the Step instances, the TaskCompleted() callback is called on the InOutput instances. If a sequence of Tick() calls is performed on the InOutput instances when the Task completes, the calling of TaskCompleted() will be delayed until after the Tick() calls on all InOutput instances are completed.

Threading#

The update callbacks can be triggered sequentially from one thread (typically used in offline simulations, where the algorithm should run as fast as possible) or from two separate threads (typically used in realtime simulations, where the updates should be triggered periodically). When using the Pipeline in two separate threads, each thread has their own dedicated function. Note that the Pipeline class does not implement the intended trigger behavior itself. That is, it requires some external code to call the Tick() and MainTick() functions in the correct order and from the correct thread. The Trigger class implements the intended (correct) behavior, but depending on the way in which the MPMCA is integrated into an existing simulation framework, the user might want/need to omit the Trigger class and call the appropriate functions on the Pipeline directly.

The first thread (the "InOutput thread") should only call the Tick() function on the Pipeline object. The Pipeline::Tick() function will call the Tick() and MainTick() callbacks on all the InOutput instances sequentially. The other thread (the "Task thread") should only call the MainTick() function on the Pipeline object. The Pipeline::MainTick() function will call the MainTick() callbacks on the Step instances and the TaskCompleted() callback on the InOutput instances. The Pipeline::MainTick() function should be called after the Pipeline::Tick() function belonging to the same MainTick() sample has returned. That is, the Pipeline::Tick() function will call the MainTick() function on the InOutput instances, during which the InOutput is given the opportunity to fill the DataBucket with any information (contained in a Message instance) that is required by any of the Step instances. If the Pipeline::MainTick() function is called prematurely, the required information is not present and the call will fail (resulting in a Critical safety state).

Thread-safe data exchange between InOutput and Step instances is provided by the TaskMessageBus (which is a MessageBus object) located inside the DataBucket that is passed to the MainTick() and TaskCompleted() calls. During the MainTick() call, data can be written to and read from the TaskMessageBus; whereas during a TaskCompleted() call it can only be read. InOutput instances can communicate with each other during Tick() calls through messages on the InOutputMessageBus (which is also a MessageBus object) passed with the Tick() call. Be aware that messages on the InOutputMessageBus will not be passed to the Step instances, because the data might be modified inside a InOutput while a Step is executing.

Inputs and Outputs (InOutputs)#

InOutput classes are modular components that typically perform one function (or a limited set of related functions) related to communication with the outside world. A few examples for illustration purposes.

  1. Communication with the vehicle model. The InOutput would handle a UDP connection with the vehicle model and receive signals such as the position of the vehicle in the virtual world, the accelerations, and any other signal relevant for the Predictor, such as the gear, the engine RPM, the elevator trim setting, the fuel level, etc. The InOutput receives the data, converts it to the correct units or frame of reference, and writes it to a Message that provides the interface to the Predictor.
  2. Communication with the statemachine of the simulator. The simulator might go through a few states before and after spending some time in the run state, such as an initialization or stopping state. The InOutput receives information from the control station of the simulator and translates these into the appropriate commands for the StateMachineClient.
  3. Communication of position, velocity and acceleration setpoints for the simulator or robot. The InOutput might set up a connection to the simulator, perform keep-alive actions, and send new setpoints on regular intervals.

The Pipeline can contain any number of InOutput instances.

Steps and Task#

Step classes are modular components that typically perform one computationally heavy function related to the prediction or control algorithms. A few examples for illustration purposes.

  1. A Step containing a Predictor will perform the appropriate calls on a predictor to trigger an update or interchange information. Typically, the Step running the predictor does not perform the actual calculations, but is merely the 'glue' between the Pipeline and the predictor.
  2. A Step containing the Controller will pass the appropriate configuration options to the Controller object, pass the output reference signals to the controller and pass the resulting control inputs to the appropriate Message.
  3. A generic Step is provided with the MPMCA that can scale the reference output signals. It is preferable to implement the scaling (or other pre/post-processing steps) in a separate Step to promote modularity and reuse of code.
  4. Several Step classes are provided that can log data to a binary or human-readable format for later inspection.

A Task inside the Pipeline can contain any number of Step instances.