Module fmi-export enables the FMI-compliant simulation coupling with ns-3 scripts, i.e., ns-3 script are launched and executed through an FMI-compliant co-simulation interface. In terms of FMI terminology, ns-3 is the slave application, and generated FMUs launch ns-3 and synchronize its execution during runtime (tool coupling).
Module fmu-examples provides examples for using the fmi-export module. The module comprises dedicated models (clients and servers), helpers and simulation scripts implementing example applications, whose functionality is then exported as FMU for Co-Simulation. Furthermore, test applications (written in Python) show how the resulting FMUs can be used in a simulation.
Follow these instructions to install the fmi-export module:
-
Install required dependencies:
sudo apt install build-essential sudo apt install cmake sudo apt install unzip sudo apt install libboost1.71-all-dev
-
This module relies on a lot of functionality provided by the FMI++ library. Hence, in order to install this module, the latest version of the FMI++ library (commit 10b4dbe) should be cloned from its repository:
git clone https://github.com/fmipp/fmipp.git cd fmipp git checkout 10b4dbe cd ..
-
Get the source code from GitHub.
git clone https://github.com/ERIGrid/ns3-fmi-export.git
-
Get the ns-3 code (tested with release version ns-3.33).
git clone https://gitlab.com/nsnam/ns-3-dev.git cd ns-3-dev git checkout ns-3.33
-
From the source code, copy the fmi-export directory (and the fmu-examples directory if you want to include examples) to the src subdirectory of ns-3, i.e., the directory with all the other ns-3 modules.
cp -R /path/to/cloned/ns3-fmi-export/fmi-export/ src/ cp -R /path/to/cloned/ns3-fmi-export/fmu-examples/ src/
-
Configure
waf
with the --with-fmi-export flag set to the previously cloned FMI++ library:./waf configure --with-fmi-export=/path/to/cloned/fmipp
-
Build the module using
waf
:./waf
-
If you want to run all the examples (see below for more information), you can run script
run-tests.sh
:cd src/fmu-examples/examples chmod +x run-tests.sh ./run-tests.sh
ns-3 is mainly developed for Linux, but it can also be installed on Windows in a 32-bit Cygwin environment. Please refer to commit 122190b for details.
ns-3 scripts have to implement the abstract class SimpleEventQueueFMUBase. This class provides a simple event queue that can be synchronized via an FMI-compliant interface. Incoming/outgoing messages are associated with input/output variables. Whenever a message is sent from a network node, the associated input variable is set to a non-zero integer value, referred to as message ID. When the same message is received at another network node, the associated output variable is set to the same message ID. The sending/receiving of messages is simulated with individual ns-3 simulation runs, whose results are stored as events in the queue.
To define an FMI-compliant ns-3 script, a new class inheriting from SimpleEventQueueFMUBase has to be implemented, which provides the following two methods:
-
Method initializeSimulation(): This method allows to define simulation variables (typically member variables of the inheriting class) as inputs/outputs/parameters of the ns-3 simulation.
-
Method runSimulation( const double& sync_time ): This method is called whenever a new ns-3 simulation has to be run. Most of the code it contains is what you would typically find in an ns-3 script. Results of such a simulation can be added to the event queue via method addNewEventForMessage( evt_time, msg_id, output_var ).
After the definition of the class, the macro CREATE_NS3_FMU_BACKEND has to be used. This macro replaces the typical main methods of ns-3 scripts.
FMUs can be generated using the Python script ns3_fmu_create.py
.
The FMU is created by executing the Python script from the command line:
ns3_fmu_create.py [-h] [-v] -m <model_id> -s <ns3_script> \
[-f <fmi_version>] [<additional_file_1> ... <additional_file_N>] \
[var1=start_val1 ... varN=start_valN]
Optional arguments are enclosed by squared brackets [...].
- -m, --model-id: Specify the FMU model identifier. Attention: The FMU model identifier must fulfill the restrictions for C function names!
- -s, --script: Path to ns-3 script (absolute or relative).
- -h, --help: Display the help screen.
- -v, --verbose: Turn on log messages.
- -l, --litter: Do not clean-up intermediate files (e.g., log file with debug messages from compilation).
- -f, --fmi-version: Specify FMI version (1 or 2, default is 2)
Additional files may be specified (e.g., CSV input lists) that will be automatically copied to the FMU. The specified files paths may be absolute or relative.
Start values for variables and parameters may be defined. For instance, to set variable with name var1 to value 12.34, specify var1=12.34 in the command line as optional argument.
During simulation, interaction with the FMU is basically limited to the use of three (types of) functions:
-
Setter functions: Set the value (message ID) of input variables. This corresponds to sending a message. 0 means no input.
-
Getter functions: Retrieve the value (message ID) of output variables. This corresponds to receiving a message. 0 means no output.
-
Synchronization: An FMU for co-simulation is synchronized from one synchronization point to the next by calling the doStep( ..., com_point, step_size, ...) function, where com_point is the time of the last successful FMU synchronization and step_size is the length of the next simulation step. In the case of FMUs for ns-3, which internally implement an event queue, there are three distinct ways of calling this function:
-
Time advance: When doStep(...) is called with step_size > 0, then the FMU tries to advance its internal simulation time accordingly.
-
Receiving messages: When calling doStep(...) with step_step = 0 (an FMU iteration) at a time corresponding to an internal event, the value(s) of the associated output variable(s) will be set to the according message ID. To retrieve the actual message ID, call the getter function after the FMU iteration.
-
Sending messages: When calling doStep(...) with step_size = 0 (an FMU iteration) even though there is no internal event scheduled at this time, then the FMU assumes that one or more new messages have been sent and a new ns-3 simulation should be run. To trigger an ns-3 simulation, provide new message IDs via the setter functions directly before the FMU iteration (but after a time advance).
-
The example applications are test cases from the ERIGrid project:
-
SimpleFMU: A very simple test case where a client sends data to a server.
-
TC3: This test case comprises two smart meters sending data to a voltage controller, which sends data to actuate the tap position of an OLTC transformer. This test case is described in detail in ERIGrid deliverable D-JRA2.2.
-
LSS2: This test case also looks on the data transmission of smart meters to a controller, focusing on the effect of co-channel interference of Wi-Fi networks. This test case is described in detail in ERIGrid deliverable D-JRA2.3.
Module fmu-examples provides the models for these test cases. These models implement dedicated clients and servers, which provide the functionality to extract the end-to-end delay of message transmissions. Based on these end-to-end delays, the ns-3 simulation scripts add events to the event queue of the FMU.
The classes ClientBase and ServerBase are the bases classes for all the clients and servers implemented for the example applications. The implemented clients and servers are examples of how callback functions can be used to calculate end-to-end delays. Helpers for including the clients and servers into simulations scripts are provided. All helpers are specializations of the template base classes ClientHelperBase and ServerHelperBase.
The examples are implemented in dedicated ns-3 scripts, which can be found in the module's subdirectory examples/scratch.
The scripts can be translated to FMUs for Co-Simulations using Python script ns3_fmu_create.py
(from module fmi-export).
Test applications (written in Python) using these FMUs can be found in module's subdirectory examples/test.
To build the examples, copy directory fmu-examples to ns-3's src directory (i.e., the directory with all the other ns-3 modules).
Then change into the ns-3 root directory and build the module using waf
.
The ns-3 script of the simple example can be found (SimpleFMU.cc
).
It implements a simple simulation in which one node (A) send messages to another node (B).
The script defines class SimpleFMU, which inherits from class SimpleEventQueueFMUBase:
-
The class defines three class member variables:
- Variable nodeA_send is of type fmippInteger and will be used as input variable for the final FMU
- Variable nodeB_receive is of type fmippInteger and will be used as output variable for the final FMU
- Variable channel_delay is of type fmippReal and will be used as parameter for the final FMU
-
Function initializeSimulation(): This function uses the macros addIntegerInput(...), addIntegerOutput(...) and addRealParameter(...) to define the class member variables as input, output and parameter, respectively. This definition is sufficient to create later on the FMU with an input, output and parameter with exactly the same names as the corresponding variables in the script.
-
Function runSimulation( const double& sync_time ): This function runs an ns-3 simulation every time new inputs are set to the FMU (and the FMU is iterated, see above). Most of the code is just like for normal ns-3 scripts, there are a few differences however:
- At the beginning of the function, variable nodeA_send is checked to be equal to zero, in order to know if indeed a message has been sent (i.e., a message ID is available as input).
- At the end of the simulation, the end-to-end delay of the message is retrieved.
- Finally, the event queue is updated with a new event using function addNewEventForMessage(...). The inputs are the time at which the message is received (i.e., the time of the FMU synchronization plus the end-to-end delay), the message ID and the pointer to the corresponding output variable.
-
At the very end of the script, macro CREATE_NS3_FMU_BACKEND is used with the name of the new class (SimpleFMU). This macro is basically a replacement for a main function.
Create the FMU with the help of Python script ns3_fmu_create.py
.
In the command line, go to the example directory src/fmu-examples/examples and issue the following command:
$ ./../../fmi-export/ns3_fmu_create.py -v -m SimpleFMU -s scratch/SimpleFMU.cc -f 1 channel_delay=0.2
This command does the following:
-
It defines the FMU's model identifier as SimpleFMU. This means that the resulting FMU will be called SimpleFMU.fmu.
-
It defines scratch/SimpleFMU.cc as the ns-3 script to be used for the simulation.
-
The parameter channel_delay is set to 0.3.
The output of the script in the command line should be something along the following lines. (Note that waf
is called twice during the process.)
[DEBUG] Using FMI version 1
[DEBUG] Found start value: channel_delay = 0.2
Waf: Entering directory `/cygdrive/c/Development/erigrid/ns-3-allinone/ns-3-dev/build'
Waf: Leaving directory `/cygdrive/c/Development/erigrid/ns-3-allinone/ns-3-dev/build'
Build commands will be stored in build/compile_commands.json
'build' finished successfully (1.363s)
Modules built:
antenna aodv applications
bridge buildings config-store
core csma csma-layout
dsdv dsr energy
flow-monitor fmi-export (no Python) fmu-examples (no Python)
internet internet-apps lr-wpan
lte mesh mobility
mpi netanim (no Python) network
nix-vector-routing olsr point-to-point
point-to-point-layout propagation sixlowpan
spectrum stats test (no Python)
topology-read traffic-control uan
virtual-net-device wave wifi
wimax
Modules not built (see ns-3 tutorial for explanation):
brite click fd-net-device
openflow tap-bridge visualizer
[DEBUG] successfully compiled ns-3 script
Waf: Entering directory `/cygdrive/c/Development/erigrid/ns-3-allinone/ns-3-dev/build'
Waf: Leaving directory `/cygdrive/c/Development/erigrid/ns-3-allinone/ns-3-dev/build'
Build commands will be stored in build/compile_commands.json
'build' finished successfully (1.349s)
[DEBUG] successfully created JSON script
[DEBUG] FMI model identifier: SimpleFMU
[DEBUG] ns-3 script: scratch/SimpleFMU.cc
[DEBUG] ns-3 install directory: /cygdrive/c/Development/erigrid/ns-3-allinone/ns-3-dev
[DEBUG] Aditional files:
[DEBUG] Added start value to model description: channel_delay = 0.2
[DEBUG] FMU created successfully: SimpleFMU.fmu
Python script testSimpleFMU.py
uses the generated FMU in a simulation.
It can be found in the module's subdirectory examples/test.
When running the simulation script, the output should be similar to the following:
[test_sim_ict] WARNING: The path specified for the FMU's entry point does not exist: ""
Use directory of main application as working directory instead.
[test_sim_ict] MIME-TYPE: Wrong MIME type: application/x-waf --- expected:
Waf: Entering directory `/cygdrive/c/Development/erigrid/ns-3-allinone/ns-3-dev/build'
Waf: Leaving directory `/cygdrive/c/Development/erigrid/ns-3-allinone/ns-3-dev/build'
Build commands will be stored in build/compile_commands.json
'build' finished successfully (1.406s)
================================================
simulation time : 0.0
next event time : 0.0
next send time : 1.0
================================================
simulation time : 1.0
next event time : no next event specified
next send time : 1.0
At time 1.00000: SEND message with ID = 1
================================================
simulation time : 1.301686399
next event time : 1.301686399
next send time : 2.0
At time 1.30169: RECEIVE message with ID = 1
================================================
simulation time : 2.0
next event time : no next event specified
next send time : 2.0
At time 2.00000: SEND message with ID = 2
================================================
simulation time : 2.301686399
next event time : 2.301686399
next send time : 3.0
At time 2.30169: RECEIVE message with ID = 2
================================================
simulation time : 3.0
next event time : no next event specified
next send time : 3.0
At time 3.00000: SEND message with ID = 3
================================================
simulation time : 3.301686399
next event time : 3.301686399
next send time : 4.0
At time 3.30169: RECEIVE message with ID = 3
================================================
Create the FMU with the help of Python script ns3_fmu_create.py
.
In the command line, go to the example directory src/fmu-examples/examples and issue the following command:
$ ./../../fmi-export/ns3_fmu_create.py -v -m TC3 -s scratch/TC3.cc -f 1
Python script testTC3.py
uses the generated FMU in a simulation.
It can be found in the module's subdirectory examples/test.
When running the simulation script, the output should be similar to the following:
[test_sim_ict] WARNING: The path specified for the FMU's entry point does not exist: ""
Use directory of main application as working directory instead.
[test_sim_ict] MIME-TYPE: Wrong MIME type: application/x-waf --- expected:
Waf: Entering directory `/cygdrive/c/Development/erigrid/ns-3-allinone/ns-3-dev/build'
Waf: Leaving directory `/cygdrive/c/Development/erigrid/ns-3-allinone/ns-3-dev/build'
Build commands will be stored in build/compile_commands.json
'build' finished successfully (1.415s)
==========================================
simulation time : 0.0
next event time : 0.0
next send time : 1.0
ctrl_msg_id = 0
==========================================
simulation time : 1.0
next event time : 1.0
next send time : 1.0
At time 1.00000: SEND messages to controller with ID = 1 and ID = -1
ctrl_msg_id = 0
==========================================
simulation time : 1.039132019
next event time : 1.039132019
next send time : 2.0
ctrl_msg_id = 1
At time 1.03913: RECEIVE message at controller with ID = 1
At time 1.03913: SEND message from controller with ID = 10
==========================================
simulation time : 1.049157658
next event time : 1.049157658
next send time : 2.0
ctrl_msg_id = -1
At time 1.04916: RECEIVE message at controller with ID = -1
At time 1.04916: SEND message from controller with ID = -10
==========================================
simulation time : 1.079941038
next event time : 1.079941038
next send time : 2.0
ctrl_msg_id = 0
At time 1.07994: RECEIVE message at transformer with ID = 10
==========================================
simulation time : 1.089867677
next event time : 1.089867677
next send time : 2.0
ctrl_msg_id = 0
At time 1.08987: RECEIVE message at transformer with ID = -10
==========================================
simulation time : 2.0
next event time : 2.0
next send time : 2.0
At time 2.00000: SEND messages to controller with ID = 2 and ID = -2
ctrl_msg_id = 0
==========================================
simulation time : 2.039015019
next event time : 2.039015019
next send time : 3.0
ctrl_msg_id = 2
At time 2.03902: RECEIVE message at controller with ID = 2
At time 2.03902: SEND message from controller with ID = 20
==========================================
simulation time : 2.049040658
next event time : 2.049040658
next send time : 3.0
ctrl_msg_id = -2
At time 2.04904: RECEIVE message at controller with ID = -2
At time 2.04904: SEND message from controller with ID = -20
==========================================
simulation time : 2.076761038
next event time : 2.076761038
next send time : 3.0
ctrl_msg_id = 0
At time 2.07676: RECEIVE message at transformer with ID = 20
==========================================
simulation time : 2.090786677
next event time : 2.090786677
next send time : 3.0
ctrl_msg_id = 0
At time 2.09079: RECEIVE message at transformer with ID = -20
==========================================
simulation time : 3.0
next event time : 3.0
next send time : 3.0
At time 3.00000: SEND messages to controller with ID = 3 and ID = -3
ctrl_msg_id = 0
==========================================
simulation time : 3.041898019
next event time : 3.041898019
next send time : 4.0
ctrl_msg_id = -3
At time 3.04190: RECEIVE message at controller with ID = -3
At time 3.04190: SEND message from controller with ID = -30
==========================================
simulation time : 3.052203658
next event time : 3.052203658
next send time : 4.0
ctrl_msg_id = 3
At time 3.05220: RECEIVE message at controller with ID = 3
At time 3.05220: SEND message from controller with ID = 30
==========================================
simulation time : 3.079644038
next event time : 3.079644038
next send time : 4.0
ctrl_msg_id = 0
At time 3.07964: RECEIVE message at transformer with ID = -30
==========================================
simulation time : 3.096913677
next event time : 3.096913677
next send time : 4.0
ctrl_msg_id = 0
At time 3.09691: RECEIVE message at transformer with ID = 30
==========================================