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 multipleStep
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 betweenInOutput
andStep
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()
, andTaskCompleted()
callbacks on theInOutput
andStep
instances when the health status is Safe, and - will (only) call the
SafeTick()
function onInOutput
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.
- 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. TheInOutput
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. - 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 theStateMachineClient
. - 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.
- A
Step
containing aPredictor
will perform the appropriate calls on a predictor to trigger an update or interchange information. Typically, theStep
running the predictor does not perform the actual calculations, but is merely the 'glue' between the Pipeline and the predictor. - A
Step
containing theController
will pass the appropriate configuration options to theController
object, pass the output reference signals to the controller and pass the resulting control inputs to the appropriate Message. - 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 separateStep
to promote modularity and reuse of code. - 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.