FCAPI - Features¶
Gateway Features¶
API Documentation
Connection Handling¶
To interact with the gateway, client applications need to establish a connection with the gateway. For this they need to establish connection to one or more worker ports on a gateway instance.
Upon creation, the gateway opens a listening port on the specified protocol to accept incoming client connections (see interface of GatewayFERALComponent constructors. Supported protocols are:
- TCP: clients connect here to the given port, also over network.
- UDP: same as TCP
- SHM: Client connection is established via shared memory. This is restricted to machine-local connections, and any input regarding clientAddress or serverAddress is ignored.
For TCP and UDP the port can be provided as 0, in this case the implementation choses an arbitrary free port. The port number chosen can be obtained via the function Gateway::getListeningPort()
and then used in connection setup at client side. This is especially useful for automated environments on local machines where the port number is insignificant and can easily be forwarded to the client applications (e.g. in systematic testing scenarios).
When the client requests a connection, it must do so using provided client libraries and provide protocol, server address, server port and own address and port where the server shall contact back.
- For the C client implementation this is done via the fcapi_connect(FCAPIHandle_t id, const char *name, const Connection_t *conn) or fcapi_connectRaw(FCAPIHandle_t *id, const char *name, const Connection_t *conn, const char mimeType) call
- For the Java client implementation this is done by invocation of connect(String serverAddress, int serverPort, String clientAddress, String portName) or connect(String serverAddress, int serverPort, String clientAddress, String portName, String mimeType)
If a correctly configured gateway is reached with that request and has an unconnected worker port with the requested name it will connect that worker port with the client and create a unique handle which will be returned to the client. From that moment on the client libraries only use this handle to identify the connection. As on gateway side, the port number of 0 can be used to let the library choose an arbitrary free port number. This is very useful as the port number is automatically communicated to the gateway and typically is irrelevant to the functionality.
Note that the connection setup is supporting 2 different types, one common type and another raw one supporting a mimeType. The differences of that will be elaborated in the RAW-Interface chapter.
The client API allows the definition of a port that the client should be reached. It is possible and advised that multiple connections from the same client share the same port. But it is also possible to open a new port for each and every client connection, leading to greater overhead and resource consumption. If the client port is given as 0 the client library tries to reuse an existing open client port.
Established connections can be terminated with the disconnect() or diconnectAll() calls.
Data exchange between clients and the FERAL simulation¶
A worker port can be viewed as a bundle of a port that is visible to the clients (green squares on the Gateway component in FERAL Gateway Concept figure) and a FERAL port that is visible to the FERAL scenario to be linked with other FERAL workers (grey square on the Gateway component in FERAL Gateway Concept figure). Worker ports can only be instantiated as related pairs of these ports, and they share the same name.
The client libraries offer functions to transmit and receive data, this data is just piped through the gateway. The gateway does not interpret that data, it treats all as an array of bytes. On the FERAL side, the gateway always emits ValueMessage<byte[]>
and also can only work on these messages. If the data needs to be interpreted in the FERAL simulation the FERAL worker connected with that port must appropriately serialize/deserialize that data.
Data written into the FERAL port (as the value of a ValueMessage<byte[]>
) is forwarded to the client application and can be consumed there.
Synchronization of simulation time¶
To achieve proper co-simulation of client and server side, the client not only needs to read the simulation time of the server, it must also be capable of controlling the advancement of simulation time on the server. This is achieved by a three-way API that client libraries offer to synchronize with server gateways:
- Registering of Synchronization Points: The client can instruct the server gateway to be notified when a specific point in simulation time is reached. The server stops advancing of simulation time at this point.
- Stalling Client Operation: The client library implementation offers instructions to wait (block) client execution until the server has reached a previously registered synchronization point.
- Releasing Server Simulation Time Progression: The client library also offers functionality to release the server from its waiting state it has entered after reaching a synchronization point.
TBD: Figure of Synchronization between client and server and further explanation
Gateway port/simulation properties¶
The gateway exposes some properties (identified by their name as a string) to connected clients. These are depicted in Figure FERAL Gateway Concept as property1 and property2. Generally these properties can be understood as named parameter enpoints, e.g. the gateway offers a generic interface to allow client libraries to manipulate simulation settings. A property has a name by which it is registered in the gateway and which is used by the client to set and get values. Property names can be queried using the client library.
At gateway side the concept is based on the PropertyProcessorIF
interface, an implementation of this interface is for example the DynamicPropertyProcessor. A gateway does not need to have an instance of this, there exist getter and setter interfaces in GatewayComponent
to manipulate this.
Network/Worker port specific properties¶
Properties can be assigned to a specific gateway worker port, in the example in FERAL Gateway Concept this is property1. In the client libraries these properties are referred to as NetworkProperties as they typically target manipulation of network-specific properties of a network connected to this specific worker port. In the java client library, for example, the available properties can be queried from the gateway using IClientControl::getNetworkProperties(int id)
. Note that these properties are bound to one port alone, so issuing that query on another connection handle will yield different results. On the gateway side, those properties can be registered using the interface PropertyProcessorIF::registerNetworkDeviceProperty(String networkDeviceName, String propertyName, Supplier<?> getterFun, Consumer<Object> setterFun)
. Note that the parameter networkDeviceName
references explicitely the worker port name.
A special case for this is a property for setting/getting a connected network's bitrate, there are special setters and getters for this property. But under the hood, it is just a plain network property by the name of BitRate which does expect numerical values for the bit rate in the unit Bits/second.
Simulation properties¶
Properties can also refer to simulation-wide settings. In Figure FERAL Gateway Concept this is represented by property2. The names of such properties can be queried using IClientControl::getSimulationProperties(int id)
(example for java client library). On the gateway side such properties are configured using PropertyProcessorIF::registerSimulationProperty(String propertyName, Supplier<?> getterFun, Consumer<Object> setterFun)
. Note that there is no worker port name provided when setting these up, so simulation properties are same for all connections.
The following code snipplet illustrates how properties can be registered with a gateway. In this example, gw is an instance of GatewayComponent with 2 worker ports, Apple and Banana.
// instantiate and register a property processor for the gateway
DynamicPropertyProcessor dpp = new DynamicPropertyProcessor();
gw.setPropertyProcessor(dpp);
// register two numeric network properties, one for each worker port
final long[] nwProperties = {23, 42};
dpp.registerNetworkDeviceProperty("Apple", "AppleProperty",
() -> nwProperties[0], (i) -> nwProperties[0] = Long.parseLong((String) i));
dpp.registerNetworkDeviceProperty("Banana", "BananaProperty",
() -> nwProperties[1], (i) -> nwProperties[1] = Long.parseLong((String) i));
// register two simulation properties
final String[] simProperties = {"HalloWelt", "KleineWelt"};
dpp.registerSimulationProperty("SimProperty",
() -> simProperties[0], (s) -> simProperties[0] = (String) s);
dpp.registerSimulationProperty("SimProperty2",
() -> simProperties[1], (s) -> simProperties[1] = (String) s);
// register a bitrate accessor for the Apple worker port
final long[] bitRate = {100};
dpp.registerNetworkDeviceBitrateAccessors("Apple",
() -> bitRate[0], (br) -> bitRate[0] = br);
Implementation details¶
The basic concept of how the gateway interacts with the FERAL simulation to synchronize its progress with the client instances is depicted in figure 3. A semaphore is used to block the simulation execution thread in a synchronization point until the client has done its work.
This has these major implications with respect of how to use a gateway:
- There must never be more than one gateway instance in a simulation. Failure to do so will obviously result in deadlocks.
- The client library must never be executed within the FERAL simulation thread. Though the client library is not intended to be used within a FERAL component, it is technically possible to instantiate the client library inside of FERAL workers. These workers must never be put in the same simulation as the gateway, as that constellation as well will lead to deadlocks.
- The simulation must always start immediately. Don't use the property feral.start = deferred with the new gateway!
Client Libraries Features¶
Active vs Passive Client Mode¶
Originally, the gateway client is designed to operate in "active" mode, which means that it is intended that the client controls simulation time by using the sync, wait and continue API and declares readiness to commence the simulation with startSim. However, there are some scenarios where this kind of behavior is not applicable:
- The simulation runs under wall clock time (sometimes referred to as real-time): The progression of simulation time is not under the control of any simulation participant.
- The simulation time is intended to be controlled by one client alone, e.g. in a scenario where multiple clients are connected and shall execute time steps of fixed sizes which are controlled by one time master client.
For these cases the concept of the "passive" client mode is introduced. From client perspective, it is just one new function added to the API, (fcapi_)setPassiveMode()
. When invoking it the client will switch to passive mode changing the following features:
- The client can only toggle from "active" to "passive" mode once. The mode can't be changed back later.
- The client can now send and receive at any point in time (in active mode it can only send while it has paused the gateway)
- The client can no longer invoke startSim(). This has to be respected in the numExpectedClients property of the gateway.
- The client can no longer use any of the API calls for simulation time control
To summarize, after that call the client can only connect or disconnect from gateway ports and exchange data with the gateway.
There are restrictions to the usage of the setPassiveMode()
call: It may only be invoked before setting up conections or immediately after it. Any invocation after a call to sync or startSim will result in an ERR_SEQUENCE
error. Also it must be done before the first invocation to tx (this has technical reasons, as the message format for data from passive and active clients differs so the decision if passive mode shall be used has to be made before the first transmission). Adhering to that restrictions, the call setPassiveMode()
can be done multiple times with no error.
C Client Library¶
The C client library API documentation can be downloaded here. It is written in C++ (with a plain C header for better interoperability). With each release, the following variants are delivered:
- 32 and 64 bit version
- Windows MSVC (VS2013), Windows MinGW GCC and Linux GCC
- statically and dynamically linked
On source code side, you need to link the feralcapi.h
header file.
To link dynamically: choose correct DLL and make sure that the DLL is in the executable path. To link statically: choose correct static DLL (has file name extension _static) and define the symbol STATIC_LINK_FERALCAPI_DLL
before including the feralcapi.h header file.
SiLVI Client Library¶
The SiLVI interface is developed by Bosch for coupling of networtk simulations and is in many parts similar to the C client library. It is basically a (C) wrapper around the C client library to provide a different interface. To use SilVI, the header SiLVI.h
needs to be included and the appropriate DLL (e.g. silvi64.dll) needs to be loaded. There exists no statically linked version of SilVI.