We will show two examples on how to run a SUMO simulation and process the results in JAVA. The examples will use the SUMO24 development and the JAVA SDK + SUMO24 runtime Docker images, provided on Dynamita's Docker HUB.
A2O plant.sumo
example in SUMOLoad the A2O plant.sumo
example in SUMO. We change the influent flow from the default 24000 m3/day to something else, let's say 22500. We want to see this parameter in the initial script generated by the Python script which compiles the example for Linux.
The following picture shows the input parameter change:
Figure 1 - change the influent flow
We will follow the nitrate/nitrite and the total ammonia mass flows before and after the simulation and we will reproduce it in a JAVA application.
Before starting the simulation the values are like on the following picture:
Figure 2 - The starting values are NOx=109, NHx=22
We can change the Name
column to show the symbols of the variables instead of the description by pressing the Advanced/Variable indentifier/Symbol
menu item. The following picture shows the symbols and the values after the simulation was finished:
Figure 3 - Results after the simulation is finished, NOx=99, NHx=8.7
A2O plant.sumo
exampleWe will reproduce the simulation from a JAVA application in a Docker container. First, we need to compile the .sumo
project to produce a binary runnable library in a container, and to create a script which will initialize the parameters of the simulation (the ones with non-default values).
Download the SUMO image containing the development environment to compile the .sumo
project. We expect that you are familiar with Docker, and it is installed on a Linux computer or VM (Virtual Machine). In a terminal window download the SUMO24 image:
docker pull dynamita/sumo24:u24.04
which is a prepared environment containing the SUMO24 runtime, a C++ compiler and a Python installation. Run an instance of the image with:
docker run -it -v <host shared dir>:/home/shared --rm dynamita/sumo24:u24.04
In the previous command we mapped a directory of the host to the /home/shared
directory in the container. You may need to add a --user root
argument after --rm
if you want root privileges in the container.
Copy your license file and rename it to sumo.dynlic
, then the A2O plant.sumo
project file into the /home/sumo-runtime
directory. You can use the shared directory which was set up in the docker run
command.
We will use a Python script to compile the example and execute a test run as well. the script expects the sumo.dynlic
license file in the same directory.
python3 sim.py A2O\ plant
Please note, that the .sumo
extansion was not specified. After compilation, we will have libA2Oplant.so
and init.scs
in the A2Oplant/
directory. We need to copy these files into the shared location:
cp A2Oplant/libA2Oplant.so /home/shared
cp A2Oplant/init.scs /home/shared
In the init.scs
we can check the influent flow parameter. Its symbol or variable name is generated using a fixed prefix (Sumo__Plant__
), the unit name (Influent__
) and the parameter symbol (param__Q
):
Figure 4 - The init.scs script extracted from the SUMO project file
There are other parameters that were changed during plant setup in the SUMO application, to have different values than the default. We will execute this script in the JAVA application to have the same conditions as in the SUMO application.
This step is not necessary, but it is useful to understand how we can utilize the command line interpreter in external programs written in any possible language.
After the A2O plant.sumo
is compiled, start the SUMO command line interface or interpreter and type in some commands. The following listing contains the commands and the responses sent by the numerical engine:
root@f9bc2de4e346:/home/sumo-runtime# ./scli .
scli-use-license sumo.dynlic
scli-result scli-use-license 0
scli-load-model A2Oplant/libA2Oplant.so
scli-cb-message 530049 Core loop started.
scli-result scli-load-model 0
scli-send-command execute init.scs
scli-cb-message 230017 Script file "init.scs" could not be loaded.
scli-send-command execute A2Oplant/init.scs
scli-cb-message 530036 Script file A2Oplant/init.scs loaded.
scli-cb-message 530021 Set: Sumo__Plant__Influent__param__Q to 22500
scli-cb-message 530021 Set: Sumo__Plant__CSTR__param__L_Vtrain to 1000
scli-cb-message 530021 Set: Sumo__Plant__CSTR__param__Qair_NTP to 0
scli-cb-message 530021 Set: Sumo__Plant__CSTR2__param__L_Vtrain to 1500
scli-cb-message 530021 Set: Sumo__Plant__CSTR2__param__Qair_NTP to 0
scli-cb-message 530021 Set: Sumo__Plant__CSTR3__param__L_Vtrain to 4500
scli-cb-message 530021 Set: Sumo__Plant__Sideflowdivider__param__Qpumped_target to 72000
scli-cb-message 530021 Set: Sumo__Plant__Clarifier__param__Ntrain to 3
scli-cb-message 530021 Set: Sumo__Plant__Clarifier__param__L_VTV to 1800
scli-cb-message 530021 Set: Sumo__Plant__Clarifier__param__L_VFV to 150
scli-cb-message 530021 Set: Sumo__Plant__Clarifier__param__L_VSBV to 150
scli-cb-message 530021 Set: Sumo__Plant__Clarifier__param__Atank_train to 300
scli-send-command follow Sumo__Plant__Effluent__F_SNOx
scli-cb-message 530024 Following variable Sumo__Plant__Effluent__F_SNOx
scli-send-command follow Sumo__Plant__Effluent__F_SNHx
scli-cb-message 530024 Following variable Sumo__Plant__Effluent__F_SNHx
scli-send-command set Sumo__StopTime 86400000; set Sumo__DataComm 3600000
scli-cb-message 530021 Set: Sumo__StopTime to 86400000
scli-cb-message 530021 Set: Sumo__DataComm to 3600000
scli-send-command start
scli-cb-message 530002 Simulation started.
scli-cb-datacomm 0ms
scli-cb-message 530044 DATA Sumo__Plant__Effluent__F_SNOx Sumo__Plant__Effluent__F_SNHx
scli-cb-message 530039 DATA 108529.7839709552 21705.95679419105
scli-cb-dataend 0ms
scli-cb-datacomm 3600000ms
scli-cb-message 530039 DATA 107590.9205199261 18813.95246346241
scli-cb-dataend 3600000ms
scli-cb-datacomm 7200000ms
scli-cb-message 530039 DATA 103948.34944977 16644.90465366757
scli-cb-dataend 7200000ms
scli-cb-datacomm 10800000ms
scli-cb-message 530039 DATA 100267.2753786787 15281.16812007555
scli-cb-dataend 10800000ms
scli-cb-datacomm 14400000ms
scli-cb-message 530039 DATA 97694.08598540505 14233.05113869519
scli-cb-dataend 14400000ms
scli-cb-datacomm 18000000ms
scli-cb-message 530039 DATA 96182.16509771612 13345.06720295519
scli-cb-dataend 18000000ms
scli-cb-datacomm 21600000ms
scli-cb-message 530039 DATA 95423.17553305085 12570.92902520759
scli-cb-dataend 21600000ms
scli-cb-datacomm 25200000ms
scli-cb-message 530039 DATA 95148.93739716226 11893.40731938099
scli-cb-dataend 25200000ms
scli-cb-datacomm 28800000ms
scli-cb-message 530039 DATA 95171.37625431237 11303.38145496303
scli-cb-dataend 28800000ms
scli-cb-datacomm 32400000ms
scli-cb-message 530039 DATA 95367.00015704671 10793.93891379179
scli-cb-dataend 32400000ms
scli-cb-datacomm 36000000ms
scli-cb-message 530039 DATA 95656.73851449792 10358.50621896827
scli-cb-dataend 36000000ms
scli-cb-datacomm 39600000ms
scli-cb-message 530039 DATA 95991.47275443099 9990.524903815167
scli-cb-dataend 39600000ms
scli-cb-datacomm 43200000ms
scli-cb-message 530039 DATA 96339.48795699392 9683.191334031444
scli-cb-dataend 43200000ms
scli-cb-datacomm 46800000ms
scli-cb-message 530039 DATA 96681.41271389837 9429.754590599476
scli-cb-dataend 46800000ms
scli-cb-datacomm 50400000ms
scli-cb-message 530039 DATA 97005.70765893999 9223.780791249301
scli-cb-dataend 50400000ms
scli-cb-datacomm 54000000ms
scli-cb-message 530039 DATA 97305.47988579808 9059.334083825866
scli-cb-dataend 54000000ms
scli-cb-datacomm 57600000ms
scli-cb-message 530039 DATA 97576.64798971594 8930.624195258431
scli-cb-dataend 57600000ms
scli-cb-datacomm 61200000ms
scli-cb-message 530039 DATA 97817.50804168372 8832.571616354535
scli-cb-dataend 61200000ms
scli-cb-datacomm 64800000ms
scli-cb-message 530039 DATA 98027.69917353804 8760.63341502848
scli-cb-dataend 64800000ms
scli-cb-datacomm 68400000ms
scli-cb-message 530039 DATA 98207.73878278516 8710.762065041834
scli-cb-dataend 68400000ms
scli-cb-datacomm 72000000ms
scli-cb-message 530039 DATA 98358.88387465401 8679.487599246007
scli-cb-dataend 72000000ms
scli-cb-datacomm 75600000ms
scli-cb-message 530039 DATA 98482.72374892159 8663.783709045498
scli-cb-dataend 75600000ms
scli-cb-datacomm 79200000ms
scli-cb-message 530039 DATA 98581.1076244767 8661.034722529383
scli-cb-dataend 79200000ms
scli-cb-datacomm 82800000ms
scli-cb-message 530039 DATA 98656.03287801579 8669.033902067235
scli-cb-dataend 82800000ms
scli-cb-datacomm 86400000ms
scli-cb-message 530039 DATA 98709.53976629477 8685.904729981432
scli-cb-dataend 86400000ms
scli-cb-message 530004 Simulation ended.
scli-exit
root@f9bc2de4e346:/home/sumo-runtime#
Listing 1 - Output of the SUMO command line interface using the follow symbol method.
The first command is scli-use-license sumo.dynlic
. The answer is scli-result scli-use-license 0
which has three parts scli-result
tells that it is a result sent back from the numerical engine, the second is the command used to generate this result. In our case it was scli-use-license
. Finally the result itself, 0
, which means that the license was accepted.
Then we load the model library with scli-load-model A2Oplant/libA2Oplant.so
. The numerical engine loads the model and notifies us that it's ready to go: scli-cb-message 530049 Core loop started.
. Messages from the numerical engine start with scli-cb-message
then the message type ID, in this case 530049
, then the message itself Core loop started.
. The result of loading the model is scli-result scli-load-model 0
, where 0
means success.
Next we execute the initialization script with scli-send-command execute init.scs
. As you can see, the numerical engine couldn't find the file, and notifies us with scli-cb-message 230017 Script file "init.scs" could not be loaded.
. This is because the script is in the A2Oplant/
directory. After fixing the path we get the notification that the script was loaded, and we get the messages about setting the parameters, e.g.: scli-cb-message 530021 Set: Sumo__Plant__Influent__param__Q to 22500
, etc.
Next we specify the symbols of the variables that we are interested in using the follow command: scli-send-command follow Sumo__Plant__Effluent__F_SNOx
, which gives us the response: scli-cb-message 530024 Following variable Sumo__Plant__Effluent__F_SNOx
. We do the same for Sumo__Plant__Effluent__F_SNHx
.
Then we set the stop time and data comm interval in ms: scli-send-command set Sumo__StopTime 86400000; set Sumo__DataComm 3600000
. Finally we start the simulation with: scli-send-command start
. We get the notification scli-cb-message 530002 Simulation started.
after which the interesting part comes. At every data comm interval we get three outputs, but at the first time we get four:
scli-cb-datacomm 0ms
scli-cb-message 530044 DATA Sumo__Plant__Effluent__F_SNOx Sumo__Plant__Effluent__F_SNHx
scli-cb-message 530039 DATA 108529.7839709552 21705.95679419105
scli-cb-dataend 0ms
The first output is scli-cb-datacomm 0ms
which tells us that we are at 0ms of the simulation. The second output is a numerical engine message listing the followed symbols. We can identify this message by the messgae type ID 530044
. This message comes only at 0ms. Then we get the values of the followed symbols in the same order. In our case 108529.7839709552 21705.95679419105
. The symbols and their values are placed after the DATA
string, and they are separated by whitespaces. This will be important when we process the results.
The closing part of a data comm is the scli-cb-dataend 0ms
.
The triplet will repeat until the simulation is ended, which is represented by the scli-cb-message 530004 Simulation ended.
message. We can use the message type ID 530004
to identify that the simulation was ended.
We close the command line interpreter by typing in scli-exit
.
.tsv
fileWe can instruct the numerical engine to write the simulation results in a file at the end of the simulation. The commands are simpler and we postpone result processing after the simulation is finished. Before we run the simulation we need to prepare a file containing the followed variables. Create an empty variables.tsv
with the command nano variables.tsv
and add the following lines:
Sumo__Plant__Effluent__F_SNOx
Sumo__Plant__Effluent__F_SNHx
After save type the following commands in our scli
(please note, that most of the lines are responses to the typed in commands):
root@f9bc2de4e346:/home/sumo-runtime# ./scli .
scli-use-license sumo.dynlic
scli-result scli-use-license 0
scli-load-model A2Oplant/libA2Oplant.so
scli-cb-message 530049 Core loop started.
scli-result scli-load-model 0
scli-send-command execute A2Oplant/init.scs
scli-cb-message 530036 Script file A2Oplant/init.scs loaded.
scli-cb-message 530021 Set: Sumo__Plant__Influent__param__Q to 22500
scli-cb-message 530021 Set: Sumo__Plant__CSTR__param__L_Vtrain to 1000
scli-cb-message 530021 Set: Sumo__Plant__CSTR__param__Qair_NTP to 0
scli-cb-message 530021 Set: Sumo__Plant__CSTR2__param__L_Vtrain to 1500
scli-cb-message 530021 Set: Sumo__Plant__CSTR2__param__Qair_NTP to 0
scli-cb-message 530021 Set: Sumo__Plant__CSTR3__param__L_Vtrain to 4500
scli-cb-message 530021 Set: Sumo__Plant__Sideflowdivider__param__Qpumped_target to 72000
scli-cb-message 530021 Set: Sumo__Plant__Clarifier__param__Ntrain to 3
scli-cb-message 530021 Set: Sumo__Plant__Clarifier__param__L_VTV to 1800
scli-cb-message 530021 Set: Sumo__Plant__Clarifier__param__L_VFV to 150
scli-cb-message 530021 Set: Sumo__Plant__Clarifier__param__L_VSBV to 150
scli-cb-message 530021 Set: Sumo__Plant__Clarifier__param__Atank_train to 300
scli-send-command writetsv results.tsv variables.tsv
scli-cb-message 530038 TSV writer: "results.tsv" variables from file "variables.tsv".
scli-send-command set Sumo__StopTime 86400000; set Sumo__DataComm 3600000; start
scli-cb-message 530021 Set: Sumo__StopTime to 86400000
scli-cb-message 530021 Set: Sumo__DataComm to 3600000
scli-cb-message 530002 Simulation started.
scli-cb-datacomm 0ms
scli-cb-dataend 0ms
scli-cb-datacomm 3600000ms
scli-cb-dataend 3600000ms
scli-cb-datacomm 7200000ms
scli-cb-dataend 7200000ms
scli-cb-datacomm 10800000ms
scli-cb-dataend 10800000ms
scli-cb-datacomm 14400000ms
scli-cb-dataend 14400000ms
scli-cb-datacomm 18000000ms
scli-cb-dataend 18000000ms
scli-cb-datacomm 21600000ms
scli-cb-dataend 21600000ms
scli-cb-datacomm 25200000ms
scli-cb-dataend 25200000ms
scli-cb-datacomm 28800000ms
scli-cb-dataend 28800000ms
scli-cb-datacomm 32400000ms
scli-cb-dataend 32400000ms
scli-cb-datacomm 36000000ms
scli-cb-dataend 36000000ms
scli-cb-datacomm 39600000ms
scli-cb-dataend 39600000ms
scli-cb-datacomm 43200000ms
scli-cb-dataend 43200000ms
scli-cb-datacomm 46800000ms
scli-cb-dataend 46800000ms
scli-cb-datacomm 50400000ms
scli-cb-dataend 50400000ms
scli-cb-datacomm 54000000ms
scli-cb-dataend 54000000ms
scli-cb-datacomm 57600000ms
scli-cb-dataend 57600000ms
scli-cb-datacomm 61200000ms
scli-cb-dataend 61200000ms
scli-cb-datacomm 64800000ms
scli-cb-dataend 64800000ms
scli-cb-datacomm 68400000ms
scli-cb-dataend 68400000ms
scli-cb-datacomm 72000000ms
scli-cb-dataend 72000000ms
scli-cb-datacomm 75600000ms
scli-cb-dataend 75600000ms
scli-cb-datacomm 79200000ms
scli-cb-dataend 79200000ms
scli-cb-datacomm 82800000ms
scli-cb-dataend 82800000ms
scli-cb-datacomm 86400000ms
scli-cb-dataend 86400000ms
scli-cb-message 530004 Simulation ended.
scli-exit
root@f9bc2de4e346:/home/sumo-runtime#
Listing 2 - Output of the SUMO command line interface using the writetsv method
The difference is in row 21, where we send the writetsv results.tsv variables.tsv
command to the numerical engine. It will acknowledge with the message: scli-cb-message 530038 TSV writer: "results.tsv" variables from file "variables.tsv".
. After the simulation started we have empty datacomm messages. We still can use these to follow the progress of the simulation.
After the simulation ends, we leave the command line interpreter with scli-exit
. The current directory should contain now the results.tsv
file, which we can open with nano results.tsv
.
Figure 5 - The simulation results contained in results.tsv.
We have the same results as in the previous case. Please note, that results may be different from those in SUMO. This is because the numerical engine always uses the measurment units specified in the process code, while the SUMO application may perform unit transformations. The following picture shows the process code unit of SNOx and SNHx:
Figure 6 - The unit of SNOx and SNHx is g N.m-3
If we change the unit in SUMO we get the same results:
Figure 7 - Results in SUMO after changing the unit to g N/d.
SUMO reduced the unit after some simplifications to g N/d
.
To run the JAVA examples we will need to download the image containing the SUMO runtime and the JAVA SDK:
docker pull dynamita/sumo24-jdk21:u24.04
docker run -it <host shared dir>:/home/shared --rm dynamita/sumo24-jdk21:u24.04
The image is based on Ubuntu 24.04, SUMO24 and JAVA JDK21.
In the first JAVA example we will use the follow
method. We need to start the scli
command line interpreter and read/write text from/to the standard output. Create a file named RunSumoSim.java
which will contain a class with the same name.
import java.io.*;
import java.util.HashMap;
import java.util.Map;
import java.util.ArrayList;
import java.util.List;
public class RunSumoSim {
public static void main(String[] args) {
String command = "./scli";
ProcessBuilder processBuilder = new ProcessBuilder(command, ".");
processBuilder.redirectErrorStream(true);
try {
// Start the command line interpreter.
Process process = processBuilder.start();
// Preparing the standard I/O streams. We write commands to the stdin
OutputStream stdin = process.getOutputStream();
InputStream stdout = process.getInputStream();
BufferedReader reader = new BufferedReader(new InputStreamReader(stdout));
BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(stdin));
// The simulation runner gets the stdio streams.
runSim(reader, writer);
writer.close();
reader.close();
int exitCode = process.waitFor();
System.out.println("Process exited with code: " + exitCode);
} catch (IOException | InterruptedException e) {
e.printStackTrace();
}
}
// ...
}
The function runSim(...)
will send commands through the standard input and read responses from the standard output. In the previous section we explained how these commands work.
public static void runSim(BufferedReader reader, BufferedWriter writer) throws IOException {
// Commands to be sent to the SUMO numerical engine on stdin.
String coreCommands = """
scli-use-license sumo.dynlic
scli-load-model libA2Oplant.so
scli-send-command follow Sumo__Plant__Effluent__F_SNHx
scli-send-command follow Sumo__Plant__Effluent__F_SNOx
scli-send-command execute init.scs
scli-send-command set Sumo__StopTime 86400000; set Sumo__DataComm 3600000
scli-send-command start
""";
// Writing the commands to the standard input (as if we would type them in).
writer.write(coreCommands);
writer.flush();
Map<String, List<String>> simResult = new HashMap<String, List<String>>();
String line;
String[] header = null;
while ((line = reader.readLine()) != null) {
// We process one line of text sent to the standard output.
header = processLine(line, simResult, header);
// Checking exit conditions:
// 1. The license could not be used.
// 2. The model could not be loaded.
// 3. The simulation ended.
boolean scliExit = false;
if (line.startsWith("scli-result scli-use-license") && !line.equals("scli-result scli-use-license 0")) {
scliExit = true;
}
if (line.startsWith("scli-result scli-load-model") && !line.equals("scli-result scli-load-model 0")) {
scliExit = true;
}
if (line.startsWith("scli-cb-message 530004")) {
scliExit = true;
}
if (scliExit) {
// Write the scli-exit command to the standard input.
writer.write("scli-exit\n");
writer.flush();
}
}
}
The function processLine(...)
handles one line of text. We can decide if it's something useful or just a message we don't care about. You can check the output of the scli
on Listing 1 to identify the data header line coming at the first data comm and value lines coming at every data comm interval.
private static String[] processLine(String line, Map<String, List<String>> simResult, String[] header) {
String dataHeaderLine = "scli-cb-message 530044 DATA";
String dataValuesLine = "scli-cb-message 530039 DATA";
System.out.println(line);
if (line.startsWith(dataHeaderLine)) {
String headerLine = line.substring(dataHeaderLine.length()).trim();
return headerLine.split("[\\s|\\t]+");
} else if (line.startsWith(dataValuesLine)) {
String dataValues = line.substring(dataValuesLine.length()).trim();
String[] values = dataValues.split("[\\s|\\t]+");
for (int i = 0; i < header.length; i++) {
String symbol = header[i];
if (!simResult.containsKey(symbol)) {
simResult.put(symbol, new ArrayList<String>());
} else {
simResult.get(symbol).add(values[i]);
}
saveData(symbol, values[i]);
}
System.out.println();
}
return header;
}
private static void saveData(String symbol, String value) {
// This could be a database or file save.
System.out.println(symbol + ": " + value);
}
Please note, that the saveData(...)
function can be time consuming, and it can slow down data processing. No data is lost, but it could take a long time to go through the lines coming on the standard output. Also, it can be something else, e.g. instead of saving, we could pass the values to some algorithm and do other calculations.
You can also choose to store your data in memory, and save to the database at the end of the simulation. In the previous example we prepared for this with the simResult
variable in the runSim(...)
function. It was not needed in processLine(...)
, but we passed it anyway in preparation for the next example:
private static String[] processLine(String line, Map<String, List<String>> simResult, String[] header) {
String dataHeaderLine = "scli-cb-message 530044 DATA";
String dataValuesLine = "scli-cb-message 530039 DATA";
if (line.startsWith(dataHeaderLine)) {
String headerLine = line.substring(dataHeaderLine.length()).trim();
return headerLine.split("[\\s|\\t]+");
} else if (line.startsWith(dataValuesLine)) {
String dataValues = line.substring(dataValuesLine.length()).trim();
String[] values = dataValues.split("[\\s|\\t]+");
for (int i = 0; i < header.length; i++) {
String symbol = header[i];
if (!simResult.containsKey(symbol)) {
simResult.put(symbol, new ArrayList<String>());
} else {
// Add the symbol and value to the Map we set up in the caller.
simResult.get(symbol).add(values[i]);
}
// We postpone saving to the caller.
}
}
return header;
}
In the caller runSim(...)
function we can process the collected data and save to a database or just print out as in the following case:
public static void runSim(BufferedReader reader, BufferedWriter writer) throws IOException {
...
// Reading output from the process. This is how we process the simulation results.
Map<String, List<String>> simResult = new HashMap<String, List<String>>();
String line;
String[] header = null;
while ((line = reader.readLine()) != null) {
header = processLine(line, simResult, header);
...
}
System.out.println("Sim result count: " + simResult.size());
for (Map.Entry<String, List<String>> entry : simResult.entrySet()) {
String symbol = entry.getKey();
List<String> values = entry.getValue();
// Here we pass all values of the symbol at once.
saveData(symbol, values);
}
}
private static void saveData(String symbol, Map<String, List<String>> values) {
System.out.println(symbol + ": [" + String.join(", ", values) + "]");
}
Please note, that the saveData(...)
function gets the values all at once, and not one by one, as in the first version. Also, the values are strings, you may need to convert them to numbers before use.
We can skip all standard output processing using the SUMO numerical engine command writetsv
. It depends on your use case whether you need to process the variable values at every data comm, or is it enough to process them at the end of the simulation?
We will use the file named variables.tsv
created in the scli
usage example. The JAVA application will be much smaller in this case:
public static void main(String[] args) {
// The SUMO command line interpreter is a tool to communicate with the SUMO core.
String command = "./scli";
// We start the program with a ProcessBuilder instance to send and receive messages through standard I/O.
ProcessBuilder processBuilder = new ProcessBuilder(command, ".");
processBuilder.redirectErrorStream(true);
try {
Process process = processBuilder.start();
// Set up the input, output streams.
OutputStream stdin = process.getOutputStream();
InputStream stdout = process.getInputStream();
BufferedReader reader = new BufferedReader(new InputStreamReader(stdout));
BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(stdin));
runSim(reader, writer);
// Ensure the writer and reader are closed
writer.close();
reader.close();
// Wait for the process to finish and get the exit code. The process was finished from inside the interpreter
// after it received the scli-exit command.
int exitCode = process.waitFor();
System.out.println("Process exited with code: " + exitCode);
// Insert code here to process the file results.tsv, after the simulation has finished.
} catch (IOException | InterruptedException e) {
e.printStackTrace();
}
}
public static void runSim(BufferedReader reader, BufferedWriter writer) throws IOException {
// The SUMO core commands specify the license, the model to load, the variables to follow,
// the stop time (1d=86400000ms) and the Data comm interval (1h=3600000ms).
String coreCommands = """
scli-use-license sumo.dynlic
scli-load-model libA2Oplant.so
scli-send-command execute init.scs
scli-send-command set Sumo__StopTime 86400000; set Sumo__DataComm 3600000
scli-send-command writetsv results.tsv variables.tsv
scli-send-command start
""";
// Write the commands to the standard input (like we would type them in from the keyboard). Note,
// that the lines should end with a new line character. The multi-line string provides them.
writer.write(coreCommands);
writer.flush(); // Ensure the input is sent
// Reading output from the process. This is how we process the simulation results.
String line;
while ((line = reader.readLine()) != null) {
// Exit in case of the simulation ended message.
if (line.startsWith("scli-cb-message 530004")) {
// ... sending the exit command to the SUMO command line interpreter.
writer.write("scli-exit\n");
writer.flush();
}
}
}
Here the important command is in row 43: scli-send-command writetsv results.tsv variables.tsv
. If you need to save the data of every run, you can time-stamp the results.tsv
file. In that case it won't be a fixed file name, you need to calculate it, then insert it into the coreCommands
string.