Module fmi-export enables the FMI-compliant simulation coupling with ns-3 scripts.
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:
sudo apt install build-essential
sudo apt install cmake
sudo apt install unzip
sudo apt install libboost1.71-all-dev
git clone https://github.com/fmipp/fmipp.git
cd fmipp
git checkout 10b4dbe
cd ..
git clone https://github.com/ERIGrid/ns3-fmi-export.git
git clone https://gitlab.com/nsnam/ns-3-dev.git
cd ns-3-dev
git checkout ns-3.33
cp -R /path/to/cloned/ns3-fmi-export/fmi-export/ src/
cp -R /path/to/cloned/ns3-fmi-export/fmu-examples/ src/
waf
with the –with-fmi-export flag set to the previously cloned FMI++ library:
./waf configure --with-fmi-export=/path/to/cloned/fmipp
waf
:
./waf
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 […].
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:
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 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
==========================================