GAMs

The development of GAM components is discussed in the GAM development section.

The most important methods in GAM development are:

  • Initialise, which is called once at the beginning of the application execution, is used to load the static parameters.

  • Setup is called after the application is set up, when the characteristics of all input and output signals are known. It can be used to validate the signal characteristics and allocate any private memory that depends on them.

  • Execute is called at each cycle of the application execution and is used to implement the actual functionality of the GAM. It is important to note that this method is called in a real-time thread and thus it should not contain any blocking calls or any calls to the operating system that might cause the thread to be preempted. It should also not contain any calls to the standard library that might cause the thread to be preempted (e.g. memory allocation, file I/O, etc.). The use of the standard library is not forbidden, but it should be used with caution and only for operations that are known to be safe in a real-time context (e.g. mathematical operations, string manipulation, etc.).

Static parameters definition.
1private:
2    ///AUTO-GENERATED: PARAMETERS. DO NOT EDIT!
3
4    /**
5     * The time difference will be divided by this value before being sent to the output signal
6     */
7    MARTe::uint32 deltaTDiv;
GAM Initialise method. Note the use of StructuredDataIHelper to validate the parameter.
 1bool DeltaTSol::Initialise(MARTe::StructuredDataI &data) {
 2    using namespace MARTe;
 3    bool ok = GAM::Initialise(data);
 4    StructuredDataIHelper helper(data, this);
 5
 6    ///AUTO-GENERATED: INITIALISE PARAMETERS. DO NOT EDIT!
 7
 8    if (ok) {
 9        ok = helper.ReadValidated("DeltaTDiv", deltaTDiv, "(DeltaTDiv > (uint32)(0))");
10    }
11    ///AUTO-GENERATED: END OF INITIALISE PARAMETERS. DO NOT EDIT!
12
13    return ok;

GAMs are divided into two broad categories:

  • GAMs that are generic and adapt to the application requirements (e.g. the number of input and output signals, their data types, etc.). These GAMs are typically transversal to several applications and are therefore good candidates to be included in the MARTe2-components library. They usually require more extensive development, as they demand careful design and testing to cover edge cases.

  • GAMs that are specific to a particular application.

For application-specific GAMs, the use of the marte2_manager to create the boilerplate code is recommended, as it allows developers to quickly create a new GAM and focus on implementing the functionality.

Note

For generic GAMs, the use of the marte2_manager is not recommended, as it might not provide all the necessary flexibility for the design. In this case, it is recommended to start from an existing GAM with similar characteristics and modify it accordingly.

In this example, a GAM will be developed to generate a relative time from an absolute time signal. The user shall be able to specify a divider value named DeltaTDiv, which will be used to divide the absolute time signal and generate the relative time signal.

In order to use the marte2_manager to create the boilerplate code for the GAM, the following command can be used:

python -m marte2_manager.cli --project_name MARTe2-training-proj --project_path $MARTE_TRAINING_PARENT_DIR -l DEBUG add --cpt_type gams --cpt_name DeltaT --cpt_namespace Tutorial # Modify the project name and path if needed

This will create a new GAM component named DeltaT in the namespace Tutorial, together with all the required Makefiles and tests.

To facilitate the generation of boilerplate code, a JSON template can be used to specify the characteristics of the GAM, including the number of input and output signals, their data types, and the static parameters. An example of such a template is shown in the following listings:

JSON definition for the generation of boilerplate code.
  1{
  2    "parameters": {
  3        "Parameter1": {
  4            "type": "uint32",
  5            "description": "This is a required uint32 parameter",
  6            "number_of_elements": 1,
  7            "required": true
  8        },
  9        "Parameter2": {
 10            "type": "uint32",
 11            "description": "This is a uint32 array parameter with a fixed number of elements",
 12            "number_of_elements": 3,
 13            "number_of_elements_fixed": true
 14        },
 15        "Parameter3": {
 16            "type": "uint32",
 17            "description": "This is a uint32 matrix parameter with a variable number of elements",
 18            "number_of_elements": [2, 2],
 19            "number_of_elements_fixed": false
 20        },
 21        "Parameter4": {
 22            "type": "uint32",
 23            "description": "This is an optional uint32 parameter",
 24            "number_of_elements": 1,
 25            "required": false,
 26            "default_value": 5
 27        },
 28        "Parameter5": {
 29            "type": "uint32",
 30            "description": "This is a uint32 array parameter with a variable number of elements",
 31            "number_of_elements": 3,
 32            "number_of_elements_fixed": false
 33        },
 34        "Parameter6": {
 35            "type": "uint32",
 36            "description": "This is a uint32 matrix parameter with a fixed number of elements",
 37            "number_of_elements": [3, 2],
 38            "number_of_elements_fixed": true
 39        },
 40        "Parameter7": {
 41            "type": "enum",
 42            "enum_type": "uint32",
 43            "description": "This is a a compulsory (pseudo) enum parameter",
 44            "number_of_elements": 1,
 45            "required": true,
 46            "enum_values": {
 47                "option1": 0,
 48                "option2": 1,
 49                "option3": 2
 50             }  
 51        },
 52        "Parameter8": {
 53            "type": "enum",
 54            "enum_type": "uint32",
 55            "description": "This is an optional (pseudo) enum parameter",
 56            "number_of_elements": 1,
 57            "required": false,
 58            "default_value": 2,
 59            "enum_values": {
 60                "Option1": 0,
 61                "Option2": 1,
 62                "Option3": 2
 63             }
 64        },
 65        "Parameter9": {
 66            "type": "float64",
 67            "description": "This is a required float64 parameter with validation",
 68            "number_of_elements": 1,
 69            "required": true,
 70            "validation": "(Parameter9 > (float32)(-3.0)) && (Parameter9 <= (float32)(0.0))"
 71        },
 72        "Parameter10": {
 73            "type": "StreamString",
 74            "description": "This is a required String parameter",
 75            "number_of_elements": 1,
 76            "required": true
 77        },
 78        "Parameter11": {
 79            "type": "StreamString",
 80            "description": "This is a an optional String parameter",
 81            "number_of_elements": 1,
 82            "required": false,
 83            "default_value": "Default"
 84        },
 85        "Parameter12": {
 86            "type": "StreamString",
 87            "description": "This is a an optional String parameter",
 88            "number_of_elements": 3,
 89            "required": false,
 90            "default_value": "Default"
 91        }
 92
 93    },
 94    "input_signals": {
 95        "InputSignal1": {
 96            "type": "uint32",
 97            "description": "This is a uint32 input signal",
 98            "number_of_elements": 1,
 99            "samples": 1,
100            "number_of_elements_fixed": true
101        },
102        "InputSignal2": {
103            "type": "float32",
104            "description": "This is a float32 array input signal",
105            "number_of_elements": 3,
106            "samples": 10,
107            "number_of_elements_fixed": true,
108            "number_of_samples_fixed": true
109        }
110    },
111    "output_signals": {
112        "OutputSignal1": {
113            "type": "float32",
114            "description": "This is a float32 output signal",
115            "number_of_elements": 1,
116            "number_of_elements_fixed": true
117        },
118        "OutputSignal2": {
119            "type": "uint32",
120            "description": "This is a uint32 output array signal",
121            "number_of_elements": 5,
122            "samples": 2,
123            "number_of_elements_fixed": false,
124            "number_of_samples_fixed": false
125        },
126        "OutputSignal3": {
127            "type": "uint32",
128            "description": "This is a uint32 output array signal",
129            "number_of_elements": 5,
130            "samples": 2,
131            "number_of_elements_fixed": false,
132            "number_of_samples_fixed": false
133        }
134
135    }
136}
JSON definition for the generation of boilerplate code for the DeltaT GAM example.
 1{
 2    "parameters": {
 3        "DeltaTDiv": {
 4            "type": "uint32",
 5            "description": "The time difference will be divided by this value before being sent to the output signal",
 6            "validation": "(DeltaTDiv > (uint32)(0))",
 7            "number_of_elements": 1,
 8            "required": true,
 9            "default_value": 1.0
10        }
11    },
12    "input_signals": {
13        "AbsoluteTime": {
14            "type": "uint64",
15            "description": "Absolute time in user defined units (e.g. microseconds) used to calculate the time difference. After division by DeltaTDiv, the output signal will be in micro-seconds.",
16            "number_of_elements": 1,
17            "samples": 1
18        }
19    },
20    "output_signals": {
21        "DeltaT": {
22            "type": "uint32",
23            "description": "Time difference between the current and previous AbsoluteTime input signal",
24            "number_of_elements": 1
25        }
26   }
27}

To update the GAM boilerplate code based on the JSON template, the following command can be used:

python -m marte2_manager.cli --project_name MARTe2-training-proj --project_path $MARTE_TRAINING_PARENT_DIR -l DEBUG modify --cpt_type gams --cpt_name DeltaT --cpt_template $MARTE_TRAINING_PARENT_DIR/MARTe2-training-proj/Resources/delta_t_gam_template.json # Modify the project name and path if needed

The code can be immediately compiled and tested using the generated Makefiles.

export TARGET=x86-linux
make -C $MARTE_TRAINING_PARENT_DIR/MARTe2-training-proj/ -f Makefile.gcc core

The code must then be modified to add the missing variables and implement the Execute method. The implementation can be copied from the DeltaTSol GAM.

Add the lastAbsoluteTime variable to the GAM header file.
1    /**
2     * Last absolute time
3     */
4    MARTe::uint64 lastAbsoluteTime;
Initialise the lastAbsoluteTime variable.
 1DeltaTSol::DeltaTSol() :
 2        GAM() {
 3    using namespace MARTe;
 4    ///AUTO-GENERATED: CTOR PARAMETERS. DO NOT EDIT!
 5
 6    deltaTDiv = 0;
 7    ///AUTO-GENERATED: END OF CTOR PARAMETERS. DO NOT EDIT!
 8    ///AUTO-GENERATED: CTOR SIGNALS. DO NOT EDIT!
 9
10    absoluteTime = NULL_PTR(uint64 *);
11    deltaT = NULL_PTR(uint32 *);
12    ///AUTO-GENERATED: END OF CTOR SIGNALS. DO NOT EDIT!
13    lastAbsoluteTime = 0;
14}
Implement the Execute method.
 1bool DeltaTSol::Execute() {
 2    using namespace MARTe;
 3    bool ok = true;
 4    if (lastAbsoluteTime > 0) {
 5        uint64 timeDiff = (*absoluteTime - lastAbsoluteTime);
 6        timeDiff /= static_cast<uint64>(deltaTDiv);
 7        *deltaT += static_cast<uint32>(timeDiff);
 8    }
 9    else {
10        *deltaT = 0;
11    }
12    lastAbsoluteTime = *absoluteTime;
13    return ok;
14}

Compile the GAM:

export TARGET=x86-linux
make -C $MARTE_TRAINING_PARENT_DIR/MARTe2-training-proj/ -f Makefile.gcc core

Note that the GAM path was automatically added to the MARTeApp.sh script.

Running the application

The GAM is already added to the configuration file ../Configurations/MassSpring/RTApp-MassSpring-57.cfg, so the application can be run directly after compiling the GAM.

./MARTeApp.sh -f ../Configurations/MassSpring/RTApp-MassSpring-57.cfg -l RealTimeLoader -m StateMachine::START

Once the application is running, inspect the screen output and verify that the application is running without issues. The log should show entries similar to the following:

$ [Information - LoggerBroker.cpp:152]: Time [0:0]:3830000
$ [Information - LoggerBroker.cpp:152]: AbsoluteTime [0:0]:7437436439108
$ [Information - LoggerBroker.cpp:152]: DeltaTime [0:0]:3820000
...

The marte2_manager also allows to print a snippet of the configuration file that corresponds to the GAM being developed.

python -m marte2_manager.cli --project_name MARTe2-training-proj --project_path $MARTE_TRAINING_PARENT_DIR -l DEBUG print --cpt_type gams --cpt_name DeltaT --cpt_template $MARTE_TRAINING_PARENT_DIR/MARTe2-training-proj/Resources/delta_t_gam_template.json # Modify the project name and path if needed

Exercise

The objective of this exercise is to implement the MassSpring model in a GAM.

The configurable parameters are:

  • Mass (m) [kg] – must be set and greater than zero

  • Spring constant (k) [N/m] – default value: 10.0

  • Damping coefficient (c) [Ns/m] – default value: 0.5

  • Initial position (x0) [m] – default value: 0.0

  • Initial velocity (v0) [m/s] – default value: 0.0

The input signals are:

  • Time (t) [microseconds]

  • Force (F) [N]

The output signals are:

  • Position (x) [m]

  • Velocity (v) [m/s]

Based on the file Resources/any_gam_template.json, create a new JSON template for the MassSpring GAM that implements the requirements above.

  1. Run the manager command to generate the boilerplate code for the MassSpring GAM based on the JSON template created in the previous step.

python -m marte2_manager.cli --project_name MARTe2-training-proj --project_path $MARTE_TRAINING_PARENT_DIR -l DEBUG add --cpt_type gams --cpt_name MassSpringModel --cpt_namespace Tutorial --cpt_template $MARTE_TRAINING_PARENT_DIR/MARTe2-training-proj/Resources/mass_spring_gam_template.json # Modify the project name and path if needed
  1. Modify the generated code to implement the functionality of the MassSpring model based on the equations of motion of the system. The equations of motion are described in the MathExpressionGAM section of the tutorial.

  2. Compile the GAM using the generated Makefiles.

export TARGET=x86-linux
make -C $MARTE_TRAINING_PARENT_DIR/MARTe2-training-proj/ -f Makefile.gcc core
  1. Execute the application:

./MARTeApp.sh -f ../Configurations/MassSpring/RTApp-MassSpring-58.cfg -l RealTimeLoader -m StateMachine::START
  1. Inspect the screen output and verify that the application is running without issues. The log should show entries similar to the following:

...
$ [Information - LoggerBroker.cpp:152]: Time [0:0]:7010000
$ [Information - LoggerBroker.cpp:152]: Position [0:0]:2.000131
$ [Information - LoggerBroker.cpp:152]: PositionM [0:0]:2.010475
$ [Information - LoggerBroker.cpp:152]: PositionSPM [0:0]:2.002166
$ [Information - LoggerBroker.cpp:152]: Velocity [0:0]:-0.000294
$ [Information - LoggerBroker.cpp:152]: VelocityM [0:0]:-0.012081
$ [Information - LoggerBroker.cpp:152]: VelocitySPM [0:0]:0.048822

Where PositionSPM and VelocitySPM are the position and velocity calculated by the MassSpringModel GAM, while Position, PositionM, Velocity and VelocityM are calculated by the MathExpressionGAM and the SSMGAM. The values should be similar across the three GAMs, with minor differences due to numerical errors and differences in the integration methods used.

Solution

The solution is to create a new JSON template for the MassSpring GAM based on the requirements specified above, and then use the manager to generate the boilerplate code.

JSON definition for the generation of boilerplate code for the MassSpringModel GAM example.
 1{
 2    "parameters": {
 3        "Mass": {
 4            "type": "float64",
 5            "description": "The mass in kg",
 6            "number_of_elements": 1,
 7            "required": false,
 8            "validation": "(Mass > (float64)(0))",
 9            "default_value": 1.0
10        },
11        "SpringConstant": {
12            "type": "float64",
13            "description": "The spring constant in N/m",
14            "required": false,
15            "default_value": 10.0
16        },
17        "DampingCoefficient": {
18            "type": "float64",
19            "description": "The damping coefficient in Ns/m",
20            "required": false,
21            "default_value": 0.5
22        },
23        "InitialPosition": {
24            "type": "float64",
25            "description": "The initial position in m",
26            "required": false,
27            "default_value": 0.0
28        },
29        "InitialVelocity": {
30            "type": "float64",
31            "description": "The initial velocity in m/s",
32            "required": false,
33            "default_value": 0.0
34        }
35    },
36    "input_signals": {
37        "Time": {
38            "type": "uint32",
39            "description": "Input time in microseconds",
40            "number_of_elements": 1,
41            "samples": 1,
42            "number_of_elements_fixed": true
43        },
44        "Force": {
45            "type": "float64",
46            "description": "Input force in N",
47            "number_of_elements": 1,
48            "samples": 1,
49            "number_of_elements_fixed": true
50        }
51    },
52    "output_signals": {
53        "Position": {
54            "type": "float64",
55            "description": "Position in m",
56            "number_of_elements": 1,
57            "number_of_elements_fixed": true
58        },
59        "Velocity": {
60            "type": "float64",
61            "description": "Velocity in m/s",
62            "number_of_elements": 1,
63            "number_of_elements_fixed": true
64        }
65
66    }
67}

Then implement the functionality of the MassSpring model based on the equations of motion.

Execute method definition.
 1bool MassSpringModelSol::Execute() {
 2    using namespace MARTe;
 3    bool ok = true;
 4    if (lastTime == 0u) {
 5        *position = initialPosition;
 6        *velocity = initialVelocity;
 7    }
 8    else {
 9        float64 dt = static_cast<uint32>(*time - lastTime);
10        dt /= 1e6;
11        *velocity += dt * ((-springConstant * (*position)) - (dampingCoefficient * (*velocity)) + (*force / mass));
12        *position += dt * (*velocity); //Use the already updated velocity even if it is not exact Forward Euler
13    }
14    lastTime = *time;
15
16    return ok;
17}

Note that the damping coefficient and the spring constant are divided by the mass in the Setup method.

Setup method definition (snippet).
 1bool MassSpringModelSol::Setup() {
 2    using namespace MARTe;
 3    bool ok = true;
 4    ///AUTO-GENERATED: SETUP SIGNALS. DO NOT EDIT!
 5
 6    if (ok) {
 7        springConstant /= mass;
 8        dampingCoefficient /= mass;
 9    }
10    return ok;