2. Getting Started

This section takes you through a the entire process of conducting a co-simulation with Maestro2.

This guide is based on the command line interface (CLI) of Maestro2 and consists of two parts:
The first part Part 1: First MaBL Specification concerns writing a MaBL Specification by hand and executing it.
The second part Part 2: Specification Generation and Expansion concerns using the specification generation feature of Maestro for generating the specification.

Note that Maestro2 also features a web API presented in Web Interface (API).

2.1. Setup

The first step is to install the dependencies along with the tools and resources that are to be used

  • Download and install Java 11
  • Download maestro-2.x.x-jar-with-dependencies.jar from the newest Maestro2 2.x.x release on the Maestro release page https://github.com/INTO-CPS-Association/maestro/releases. To easier follow the tutorials below it is recommended to rename the jar file to maestro2.jar, such that the terminal commands can be copy-pasted.
  • Optional: Python 3 for plotting capabilities

2.2. Example

The example throughout this getting started guide concerns a water tank. The tank (Continuous Time component) has a constant inflow of water and a valve that is controlled by a controller (Discrete-Event component). When the valve is open, the water level within the tank decreases, and whe the valve is closed, the water level increases. The controller determines when to open and close the valve based on a maximum and a minimum water level.

../_images/wt-example-graphic.png

Water Tank Example

This is modelled as two FMUs with external connections, internal connections and parameters as presented in the figure below.

../_images/wt-example.png

Water Tank FMUs and ports

2.3. Part 1: First MaBL Specification

This part presents a MaBL specification written by hand with descriptive comments.

To follow this part download the getting started part1 resources and unzip into a directory. To easier follow this guide the downloaded jar file can be placed in the same directory as the resources has been unzipped into.

The corresponding MaBL code, including descriptive comments, to execute this co-simulation is presented below:

simulation
{
    real START_TIME = 0.0;
    real END_TIME = 10.0;
    real STEP_SIZE = 0.1;

    // #############################
    // DataWriter for results
    // #############################
    DataWriter dataWriter = load("DataWriter");
    string[] data_headers[1] = {"crtlValveState","wtLevel"};
    DataWriterConfig dataWriterConfiguration = dataWriter.writeHeader(data_headers);
    string[] data_values[2];

    // #############################
    // Load the FMUs and instantiate instances
    // #############################
    FMI2 tankController = load("FMI2", "{8c4e810f-3df3-4a00-8276-176fa3c9f000}", "watertankcontroller-c.fmu");
    FMI2 singleWatertank = load("FMI2", "{cfc65592-9ece-4563-9705-1581b6e7071c}",  "singlewatertank-20sim.fmu");
    FMI2Component crtlInstance = tankController.instantiate("crtlInstance", false, false);
    FMI2Component wtInstance = singleWatertank.instantiate("wtInstance", false, false);

    wtInstance.setupExperiment(false, 0.0, START_TIME, true, END_TIME);
    crtlInstance.setupExperiment(false, 0.0, START_TIME, true, END_TIME);

    // #############################
    // Set maximum level and minimum level parameters
    // #############################
    real[] crtlParameters[2] = { 2.0 , 1.0 };
    uInt[] crtlParameterValRefs[2] = { 0 , 1 };
    crtlInstance.setReal(crtlParameterValRefs, 2, crtlParameters);

    wtInstance.enterInitializationMode();
    crtlInstance.enterInitializationMode();


    uInt[] crtlValveStateValRef[1] = { 4 };
    bool[] crtlValveStateOutput[1];

    uInt[] crtlLevelInputValRef[1] = { 3 };
    real[] crtlLevelInputValue[1];

    uInt[] wtValveStateValRef[1] = { 16 };
    real[] wtValveStateInputs[1];

    uInt[] wtLevelValRef[1] = { 17 };
    real[] wtLevelOutputValue[1];

    // #############################
    // Perform the Initialization:
    // 1. Extract valve state from controller
    // 2. Convert the valve state from boolean to real
    // 3. Set the valve state on the tank
    // 4. Extract the level from the tank
    // 5. Set the level on the controller
    // #############################
    crtlInstance.getBoolean(crtlValveStateValRef, 1, crtlValveStateOutput);
    real wtValveStateInput;
    if( crtlValveStateOutput[0] )
    {
        wtValveStateInput = 1.0;
    }
    else
    {
        wtValveStateInput = 0.0;
    }


    wtValveStateInputs[0] = wtValveStateInput;
    wtInstance.setReal(wtValveStateValRef, 1, wtValveStateInputs);

    wtInstance.getReal(wtLevelValRef, 1, wtLevelOutputValue);
    crtlInstance.setReal(crtlLevelInputValRef, 1, wtLevelOutputValue);

    wtInstance.exitInitializationMode();
    crtlInstance.exitInitializationMode();

    // #############################
    // Get the valve state from the controller
    // Get the level from the tank 
    // Write the valve state and the water level to the results
    // #############################
    crtlInstance.getBoolean(crtlValveStateValRef, 1, crtlValveStateOutput);
    wtInstance.getReal(wtLevelValRef, 1, wtLevelOutputValue);
    data_values[0] = crtlValveStateOutput[0];
    data_values[1] = wtLevelOutputValue[0];
    dataWriter.writeDataPoint(dataWriterConfiguration, START_TIME, data_values);


    real time = START_TIME;
    // #############################
    // Simulation Loop until END_TIME
    // #############################
    while(time + STEP_SIZE <= END_TIME)
    {
        // #############################
        // Convert valve state from boolean to real
        // #############################
        if( crtlValveStateOutput[0] )
        {
            wtValveStateInput = 1.0;
        }
        else
        {
            wtValveStateInput = 0.0;
        }
        // #############################
        // Set the valve state on the tank
        // Set the level on the controller
        // #############################
        wtValveStateInputs[0] = wtValveStateInput;
        wtInstance.setReal(wtValveStateValRef, 1, wtValveStateInputs);
        crtlInstance.setReal(crtlLevelInputValRef, 1, wtLevelOutputValue);

        // #############################
        // Step the tank
        // Step the controller
        // #############################
        wtInstance.doStep(time, STEP_SIZE, true);
        crtlInstance.doStep(time, STEP_SIZE, true);

        // #############################
        // Get the valve state from the controller
        // Get the level from the tank
        // #############################
        crtlInstance.getBoolean(crtlValveStateValRef, 1, crtlValveStateOutput);
        wtInstance.getReal(wtLevelValRef, 1, wtLevelOutputValue);

        // #############################
        // Increase the time with STEP_SIZE
        // Write the valve state and the water level to the results
        // #############################
        time = time + STEP_SIZE;
        data_values[0] = crtlValveStateOutput[0];
        data_values[1] = wtLevelOutputValue[0];
        dataWriter.writeDataPoint(dataWriterConfiguration, time, data_values);

    }
    
    // #############################
    // Clean up
    // #############################
    dataWriter.close();
    wtInstance.terminate();
    crtlInstance.terminate();
    tankController.freeInstance(crtlInstance);
    singleWatertank.freeInstance(wtInstance);

    unload(tankController);
    unload(singleWatertank);
    unload(dataWriter);
}

To execute this (one can use the file corresponding wt-example.mabl from the unzipped example resouces) run the following command from the terminal:

java -jar maestro2.jar --interpret wt-example.mabl

Where --interpret is to interpret a specification

The result is available in outputs.csv. This can be plotted with python part1pythoncsvplotter.py, and the result should look like the figure below.

../_images/wt-example-result.png

Result from co-simulation

2.4. Part 2: Specification Generation and Expansion

This part also concerns the water tank but uses the capabilities of specification generation and expansion to create the specification. Thus, the user does not have to write MaBL by hand. Specification generation and Expansion are not treated in detail in this guide, but more information is available in Specification Generator and Expansion.

To follow this part download the getting started part2 resources and unzip into a directory. To easier follow this guide the downloaded jar file can be placed in the same directory as the resources has been unzipped into.

The specification generator is based on JSON configuration files with the same structure as the ones used in Maestro1. For this reason, it is also possible to use the INTO-CPS Application <https://into-cps-association.readthedocs.io/projects/desktop-application/en/latest/> to create the specification files. These files are available in the folder of the given Multi-Model. This is also mentioned below where the actual terminal command is issued.

2.4.1. Configuration file

The content of the configuration file is briefly described after the example below. A more detailed description is available at Legacy Configuration File Format. The configuration for this example is:

{
    "fmus": {
        "{controller}": "watertankcontroller-c.fmu",
        "{tank}": "singlewatertank-20sim.fmu"
    },
    "connections": {
        "{controller}.crtlInstance.valve": [
            "{tank}.wtInstance.valvecontrol"
        ],
        "{tank}.wtInstance.level": [
            "{controller}.crtlInstance.level"
        ]
    },
    "parameters": {
        "{controller}.crtlInstance.maxlevel": 2,
        "{controller}.crtlInstance.minlevel": 1
    },

    "algorithm": {
        "type": "fixed-step",
        "size": 0.1
    },
    "endTime": 10.0
}
  • fmus contains mappings between a key, enclosed in {} and the corresponding FMU.
  • connections contains mappings from a single FMU output port to one or more FMU input ports. The format of a port is {fmuKey}.instance.scalarVariableName
  • parameters contains mappings from a single FMU parameter port and a value.
  • algorithm describes that the fixed-step algorithm should be used with the size 0.001. Currently only fixed-step is supported.
  • end_time is the end-time of the simulation

2.4.2. Specification Generation

The command below generates the specification based on the configuration file above:

java -jar maestro2.jar --spec-generate1 wt-example-config.json --dump "./"

Where --spec-generate1 instructs Maestro2 to use specification generator for legacy configuration files (i.e. configuration files for maestro1) and --dump describes to dump the final specification at the current directory.

Note

The INTO-CPS Application creates two configuration files in JSON format: a multimodel configuration file and a simulate configuration file. These files can both be added to the terminal command, i.e. --spec-generate1 multimodel.json simulate.json, and will be merged by Maestro. Thus, one does not have to merge these files manually.

2.4.3. Final specification and Execution

The final specification is in the file spec.mabl and is not presented here but can be downloaded: spec.mabl.

Furthermore, a spec.runtime.json file has been produced with information for the runtime DataWriter CSV plugin with information on where to store the csv result:

{
  "DataWriter" : [ {
    "filename" : "output.csv",
    "type" : "CSV"
  } ]
}

2.4.4. Execution

To execute the specification execute the following command:

> java -jar maestro2.jar --interpret spec.mabl

Where --interpret instructs Maestro2 to interpret the subsequent specifications, in this case spec.mabl.

The interpreter will automatically look for *.runtime.json files within the same directory as the jar file.

Similar to above in Part 1: First MaBL Specification, the results can be viewed and plotted with the accompanying python script: python part2pythoncsvplotter.py

2.4.5. Extra: Expansion

This section gives a small insight into the expansion feature of Maestro. It is possible to use expansion plugins within in a MaBL specification. When Maestro encounters a MaBL expand statement during processing, it will replace the statement with MaBL code generated by the related expansion plugin. This happens automatically in the part2 example, but it can be made obvious.

Run the command below:

> java -jar maestro2.jar --spec-generate1 wt-example-config.json --dump-intermediate "./part2mabl-intermediate"
where
--dump-intermediate describes to dump the specification after every expansion iteration at “./part2mabl-intermediate”

The directory part2mabl-intermediate now contains files with different numbers in increasing order due to the flag --dump-intermediate "./part2mabl-intermediate". The initial specification, spec00000.mabl, refers to the specification generated by the specification generator without any expansions carried out. spec00001.mabl is the specification after 1 expansion has been carried out and so forth. The larget number referes to the specification after all expansions has been carried out.

spec00000.mabl is the specification generated by the specification generator which contains several annotations (marked with @) and two expand statements (begins with: expand): initialize and fixedStep. The reason for these annotations are that the file is self-contained. The relevant information to generate the final specification is added to the specifications via these annotations. See the example below and the description afterwards:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
simulation 
import FixedStep;
import TypeConverter;
import Initializer;
import DebugLogging;
@Framework( "FMI2");
@FrameworkConfig( "FMI2", "{\"fmus\":{\"{controller}\":\"watertankcontroller-c.fmu\",\"{tank}\":\"singlewatertank-20sim.fmu\"},\"connections\":{\"{controller}.crtlInstance.valve\":[\"{tank}.wtInstance.valvecontrol\"],\"{tank}.wtInstance.level\":[\"{controller}.crtlInstance.level\"]},\"parameters\":{\"{controller}.crtlInstance.maxlevel\":2,\"{controller}.crtlInstance.minlevel\":1},\"startTime\":0.0,\"endTime\":10.0,\"loggingOn\":false,\"visible\":false,\"algorithm\":{\"type\":\"fixed-step\",\"size\":0.1}}");
{
 	bool global_execution_continue = true;
	Math math = load("Math");
	Logger logger = load("Logger");
	DataWriter dataWriter = load("DataWriter");
	FMI2 tank = load("FMI2", "{cfc65592-9ece-4563-9705-1581b6e7071c}", "singlewatertank-20sim.fmu");
	if( (tank == null) )
	{
		global_execution_continue = false;
	}
	FMI2 controller = load("FMI2", "{8c4e810f-3df3-4a00-8276-176fa3c9f000}", "watertankcontroller-c.fmu");
	if( (controller == null) )
	{
		global_execution_continue = false;
	}
	@map wtInstance -> "wtInstance";
	FMI2Component wtInstance = null;
	if( (global_execution_continue) )
	{
			wtInstance = tank.instantiate("wtInstance", false, false);
			if( (wtInstance == null) )
			{
				global_execution_continue = false;
			}
	}
	@map crtlInstance -> "crtlInstance";
	FMI2Component crtlInstance = null;
	if( (global_execution_continue) )
	{
			crtlInstance = controller.instantiate("crtlInstance", false, false);
			if( (crtlInstance == null) )
			{
				global_execution_continue = false;
			}
	}
	FMI2Component[] components[2] = { wtInstance , crtlInstance };
	real START_TIME = 0.0;
	real STEP_SIZE = 0.1;
	real END_TIME = 10.0;
	@Config("{\"parameters\":{\"{controller}.crtlInstance.maxlevel\":2,\"{controller}.crtlInstance.minlevel\":1}}");
	expand initialize(components, START_TIME, END_TIME);
	expand fixedStep(components, STEP_SIZE, START_TIME, END_TIME);
	tank.freeInstance(wtInstance);
	controller.freeInstance(crtlInstance);
	unload(tank);
	unload(controller);
	unload(math);
	unload(logger);
	unload(dataWriter);
}
  • Line 5-6 describes the framework and the JSON necessary to generate the configuration of the framework context, which is needed by the expansion plugins.
  • Line 47 describes a configuration used specifically for the initialize expansion plugin on the subsequent line
  • Line 48 specifies that the initialize expansion plugin should be expanded.
  • Line 49 specifies that the fixedStep expansion plugin should be expanded.

The next example presents an extract of the new specification generated after the expansion of line 47 and 48 containing @Config(...) and expand initialize(...). The full specification at this stage of expansion is available at here. Thus the @Config(...) and expand initialize(...) has been replaced with the MaBL generated by the corresponding initialize plugin. As it can be seen on line 127, fixedStep has not been expanded yet.

 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
	real END_TIME = 10.0;
	while( global_execution_continue )
	{
		int status = 0;
		status = wtInstance.setupExperiment(false, 0.0, START_TIME, true, END_TIME);
		if( status == 3 || status == 4 )
		{
				logger.log(4, "Setup Experiment Failed:  %d", status);
				global_execution_continue = false;
				break;
		}
		status = crtlInstance.setupExperiment(false, 0.0, START_TIME, true, END_TIME);
		if( status == 3 || status == 4 )
		{
				logger.log(4, "Setup Experiment Failed:  %d", status);
				global_execution_continue = false;
				break;
		}
		real[] realValueSize7[7] = { 9.0 , 1.0 , 1.0 , 9.81 , 1.0 , 0.0 , 0.0 };
		uInt[] valRefsSize7[7] = { 0 , 1 , 2 , 3 , 4 , 5 , 6 };
		status = wtInstance.setReal(valRefsSize7, 7, realValueSize7);
		if( status == 3 || status == 4 )
		{
				logger.log(4, "set failed %d", status);
				global_execution_continue = false;
				break;
		}
		real[] realValueSize2[2] = { 2.0 , 1.0 };
		uInt[] valRefsSize2[2] = { 0 , 1 };
		status = crtlInstance.setReal(valRefsSize2, 2, realValueSize2);
		if( status == 3 || status == 4 )
		{
				logger.log(4, "set failed %d", status);
				global_execution_continue = false;
				break;
		}
		status = wtInstance.enterInitializationMode();
		status = crtlInstance.enterInitializationMode();
		bool[] BooleanValueSize1[1];
		uInt[] valRefsSize1[1] = { 4 };
		status = crtlInstance.getBoolean(valRefsSize1, 1, BooleanValueSize1);
		if( status == 3 || status == 4 )
		{
				logger.log(4, "get failed %d", status);
				global_execution_continue = false;
				break;
		}
		bool crtlInstanceSvValRef4 = BooleanValueSize1[0];
		real wtInstanceSvValRef16Real;
		expand convertBoolean2Real(crtlInstanceSvValRef4, wtInstanceSvValRef16Real);
		real[] realValueSize1[1] = { wtInstanceSvValRef16Real };
		valRefsSize1[0] = 16;
		status = wtInstance.setReal(valRefsSize1, 1, realValueSize1);
		if( status == 3 || status == 4 )
		{
				logger.log(4, "set failed %d", status);
				global_execution_continue = false;
				break;
		}
		valRefsSize1[0] = 17;
		status = wtInstance.getReal(valRefsSize1, 1, realValueSize1);
		if( status == 3 || status == 4 )
		{
				logger.log(4, "get failed %d", status);
				global_execution_continue = false;
				break;
		}
		real wtInstanceSvValRef17 = realValueSize1[0];
		realValueSize1[0] = wtInstanceSvValRef17;
		valRefsSize1[0] = 3;
		status = crtlInstance.setReal(valRefsSize1, 1, realValueSize1);
		if( status == 3 || status == 4 )
		{
				logger.log(4, "set failed %d", status);
				global_execution_continue = false;
				break;
		}
		status = wtInstance.exitInitializationMode();
		status = crtlInstance.exitInitializationMode();
		break;
	}
	expand fixedStep(components, STEP_SIZE, START_TIME, END_TIME);