Wiki

Clone wiki

javaFMI / FmuBuilder

Export a Java application as an FMU

With JavaFMI, is very easy to create an FMU from a Java application.

  1. Download fmu-framework-<version>.jar and add as dependency to your project or add this to your pom.xml file (Maven):
    <groupId>org.siani.javafmi</groupId>
    <artifactId>fmu-framework</artifactId>
    <version>2.4.3</version>
    
  2. Extend FmiSimulation and implement the methods
  3. Generate a jar including the framework inside
  4. Build your FMU using the fmu-builder-<version>.jar

All this documentation is for framework and builder 2.4 or higher. Note that from this version, JVM is not embedded inside the built fmus and it's required to set JAVA_HOME environment variable

fmu-framework

When you add the fmu-framework-<version>.jar to your project, you are able to extend the FmiSimulation class and implement all its methods. You also can inform the result of your calculations by returning different Status codes.

package siani.javafmi; // <-- Always in this package

import org.javafmi.framework.FmiSimulation;
import org.javafmi.framework.State;

import static org.javafmi.framework.FmiContainer.Causality.input;
import static org.javafmi.framework.FmiContainer.Causality.output;
import static org.javafmi.framework.FmiContainer.Variability.discrete;


public class Car extends FmiSimulation {
    private static final String ModelName = "Car";
    private static final String SpeedVar = "car.speed";
    private static final String DistanceVar = "car.distance";
    private static final String NumberOfDoorsVar = "car.numberofdoors";
    private static final String MaxPowerVar = "car.maxpower";
    private static final String AbsVar = "car.abs";
    private static final String Mp3Var = "car.mp3";
    private static final String FuelVar = "car.fuel";

    private enum Fuel {Petrol, Diesel}

    double speed = 0, distance = 0;
    int numberOfDoors = 5, maxPower = 90;
    boolean abs = true, mp3 = true;
    Fuel fuel = Fuel.Diesel;

    @Override
    public Model define() {
        return model(ModelName)
                .canGetAndSetFMUstate(true)
                .add(variable(SpeedVar).asReal().causality(input).variability(discrete).start(0))
                .add(variable(DistanceVar).asReal().causality(output).variability(discrete))
                .add(variable(NumberOfDoorsVar).asInteger().causality(input).variability(discrete))
                .add(variable(MaxPowerVar).asInteger().causality(input).variability(discrete))
                .add(variable(AbsVar).asBoolean().causality(input).variability(discrete))
                .add(variable(Mp3Var).asBoolean().causality(input).variability(discrete))
                .add(variable(FuelVar).asString().causality(input).variability(discrete));
    }

    @Override
    public Status init() {
        logger().info("doing init");
        registerReal(SpeedVar, () -> speed, value -> speed = value);
        registerReal(DistanceVar, () -> distance);
        registerInteger(NumberOfDoorsVar, () -> numberOfDoors, value -> numberOfDoors = value);
        registerInteger(MaxPowerVar, () -> maxPower, value -> maxPower = value);
        registerBoolean(AbsVar, () -> abs, value -> abs = value);
        registerBoolean(Mp3Var, () -> mp3, value -> mp3 = value);
        registerString(FuelVar, () -> fuel.name(), value -> fuel = Fuel.valueOf(value));
        return Status.OK;
    }

    @Override
    public Status doStep(double stepSize) {
        distance += speed * (10. / 36.) * stepSize;
        return Status.OK;
    }

    @Override
    public Status reset() {
        return Status.OK;
    }

    @Override
    public Status terminate() {
        return Status.OK;
    }

}
You are able to send log messages to the FMU master using the logger protected field. It provides three logging categories: info, warning and error. The messages will be sent to the master via the fmi2CallbackLogger function.

If you want to do logging outside the class extending FmiSimulation, you can publish the logger field (i.e. via getter) to make it available everywhere in the application code. Log messages are asynchronous, and are sent to the master right after its invocation.

Please, be aware of the new way of defining the model description which has been redesigned. Now it's more light and easy to use and it's implemented through the method define

Working with states

As described in FMI Standard the FMUs can save their state in order to use it to roll-back the simulation later on. In order to have this feature available, the model description must have the flag "canGetAndSetFMUstate", as it is in CarModelDescription class.

Then, the system is ready to save and load states using the methods getState() and setState(). By default the state saves all variables that have been registered as in the example above. In case you want to modify the default behavior of these methods, you can override them like this:

@Override
public Status getState(String stateId) {
    logger().info("doing get state");
    return super.getState(stateId);
}

@Override
protected void setState(State state) {
    super.setState(state);
    distance = (double) state.get(DistanceVar);
}
In this example, instead of saving the three registered variables, only "every" will be saved and restored when get and set state are called.

Important notes on framework

Please ensure that:

  • The class extending FmiSimulation is in a package called siani.javafmi
  • This class name is passed as model name to the ModelDescriptionAttributes
  • The name of the generated jar is the same as this class

For more details about how to configure the ModelDescriptionSpec please see the tests following this link.

The register(...) method is the mechanism which allows us to handle with the values you want to read or write from an FMU wrapper. Variable<Type> is an interface that offers two methods: getValue and setValue. You can implement it, or declare it as anonymous class just in line your register call.

Note that from version 1.3.2 of the framework you can use lambda syntaxis which make lighter your code as in the example above.

In addition with the fmu-framework, you can package your application with all third party .jar dependencies inside. When it is not an option, you have to call the fmu-builder using the -i option to add files as resources to the FMU. All the files included with -i, will be in the search class path of the simulation.

Java resources and FMU resources

Due to the resource concept is overloaded in this context, it is important to distinguish between two kind of resources in order to decide how to add it to your FMU. Java offers a mechanism to handle resources and the FMI standard also has its own. You can use one, the other or both, but you will have to choose which mechanism fits on your needs. To this end, we explain here two different mechanisms to deal with the use of resources in a Java app that is going to be fmufied.

Using files located in the working directory

When a Java application is running, if you execute new File("."), you will get the folder of the working directory of the Java application. Then, all files of the working directory can be relatively accessed by doing new File("path/to/file.txt"). Once the jar is exported as an artifact, all these files should be present in the working directory in which the jar is being executed. In the case of an FMU, the files must be located in the resources folder of the FMU together with the jar file.

Via Java resources loader

As with any other Java application, you can add resources to your .jar file to be loaded as a stream via class loader. In several IDEs this is typically done using a res directory. Using this is as always, it means, the resources added to a jar will be available at simulation runtime as streams. In the general case you want to "extract" the resources and have it available as a File, you can use the loadResource(resourcePath) method, which is available when extending FmiSimulation.

FMU resources are those files that you can store in the resources directory of an FMU. In order to add resources to your FMU you will have to use the fmu-builder with the -i option, see section above. The files added in this way, will be available at simulation runtime in the "./" directory. When instantiating an FMU, it is mandatory to specify the resources directory url. This location is used as the working and user directory for your simulation when the java virtual machine is started.

Jar dependencies

If you only need to include as dependencies of the main jar other jar files, we strongly recommend to create a jar containing all dependencies (they must be extracted inside the main jar). This will make things easier. In case it's not feasible, then, they could be placed together with the main jar provided the java manifest is correct.

Testing app to fmufy

In JavaFMI, a three-steps methodology for checking the java app to be fmufied is proposed.

  1. Check your app works before creating the artifact: to this end, you can either create a test folder and make tests using junit or create a Main class that can be executed. It is advisable to have a Main class as later on we will make a test in which we will run the jar artifact and for that, it is necessary to have a Main class. To have a look on this kind of test, please go here. In case you are loading resources, please refer to the section above.
  2. Check your artifact works: once you have generated your artifact, you should be able to run it outside of your development environment. Then, your jar should be executable using the test file made before. Then, java -jar your.jar should work properly. If not, it won't work inside the fmu.
  3. Check your fmu: if all your tests have been successfully passed, you should be able to execute your fmu without any problem. In case you find a problem, be aware of: * In case of an exception within your fmufied app, a file containing the error message will be created in the working directory of the master application. * If you catch an error in your app and you make e.printTrackTrace(), this will be printed in system.err and won't be seen from in the master's console. In this case, it is strongly recommended the use of the logger to print these errors. Then, they will be seen in the master's console. * In any case, you can log at any point of your fmufied application in order to know which codes are reached and which others not as a debugging procedure.

fmu-builder

The builder is a command line application which allows you to generate an FMU from a .jar. This .jar should accomplish the requirements described in the fmu-framework section. The syntax to invoke the builder is:

    java -jar fmu-builder-<version>.jar user.jar [-n fmu_name] [-i path/to/dependency.jar ...] [-p platform]
As you can see, almost all the information is optional. By default the builder will generate a Windows 64 FMU with the same name as the .jar, in this case user.fmu_

  -n fmu_name    -> specify the resultant FMU name
  -i files...    -> Any resources you need to be available
                    to the simulation in runtime, should be
                    listed here, you can use wild card expansion
                    to include all files in a directory

                    i.e. -i dependencies/*

  -p platform   ->  currently the builder can generate FMUs
                    for Win64 and Lin64

Examples

Download this example and others listed below:

Updated