AeroSim FMU reference
A Functional Mock-up Unit or FMU is an abstract representation of a dynamic system based on a standardised interface called the Functional Mock-up Interface (FMI), used in simulations to model the behaviour of physical systems. FMUs can represent any kind of dynamical system and can be interconnected with other FMUs or other simulation components such as input devices (e.g. a joystick), renderers or displays.
FMUs can be generated by numerous tools, including Simulink and Modelica. AeroSim makes use of an open source Python library called PythonFMU3 to create FMUs and also FMPy to run and use them. Since these tools are open source, we will consider how to create and use FMUs using these tools.
AeroSim supports FMI versions 2.0 and 3.0. Information about the standard can be found on the FMI-standard website.
Creating a simple FMU with PythonFMU3
The PythonFMU3 library provides an intuitive interface for the FMI standard and a tool to build FMUs. The following code demonstrates a simple FMU example with a single input, a single output and a procedure for each simulation step. The FMU inherits functionality from the Fmi3Slave class provided by PythonFMU3 to expose variables as input or outputs. Any variable intended to receive input data or broadcast output data must be registered through the register_variable(...) method of FmiSlave. Several methods and objects imported from PythonFMU3 are needed to define variables registered as FMU inputs or outputs:
Float64(...)- this method sets the data type of the variable as Float64causality=Fmi3Causality.input/output- this argument defines the variable as an input or output variablevariability=Fmi3Variability.continuous- this argument defines the variable as continiousinitial=Fmi3Initial.exact- this argument specifies that an exact value must be provided for initialization (that it is not calculated or approximated in the FMU)
from pythonfmu3 import Fmi3Slave, Fmi3Variability, Fmi3Causality, Fmi3Initial, Float64
class simple_fmu(Fmi3Slave):
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.time = 0.0
self.register_variable(Float64("time", causality=Fmi3Causality.independent, variability=Fmi3Variability.continuous))
# Define an input variable
self.input_value = 0.0
self.register_variable(Float64("input_value", causality=Fmi3Causality.input, start=1, variability=Fmi3Variability.continuous, initial=Fmi3Initial.exact))
# Define an output variable
self.output_value = 0.0
self.register_variable(Float64("output_value", causality=Fmi3Causality.output, start=1, variability=Fmi3Variability.continuous, initial=Fmi3Initial.exact))
def do_step(self, current_time: float, step_size: float) -> bool:
"""Simulation step execution"""
self.time += step_size
self.output_value = self.input_value * 2
return True
The do_step(..) method of the FMU defines the logic to be executed on each step of the simulation, in this case the output is a simple multiple of the input variable. The method should return True for successful operation or False otherwise.
An FMU written with PythonFMU3 can be built using the provided build tool. Save the above code in a file named simple_fmu.py then run the build tool from the command line:
pythonfmu3 build -f simple_fmu.py
This will create a file named simple_fmu.fmu in the same directory. Note that the name of the .fmu file is governed by the name of the FMU class, not the name of the Python file.
AeroSim FMU utilities
To streamline the process of producing FMUs for use in AeroSim, some FMU utils are provided as AeroSim Python libraries.
AeroSim provides utilities to utilize native AeroSim types and to declare variables, which are recommended to ensure compliance between the FMU interface and AeroSim message structures.
To streamline the process of declaring variables and parameters, import the following methods:
from aerosim_core import register_fmu3_var, register_fmu3_param
For access to AeroSim types, import the following:
from aerosim_data import types as aerosim_types
from aerosim_data import dict_to_namespace
When declaring a variable, use register_fmu3_var:
self.time = 0.0
register_fmu3_var(self, "time", causality="independent")
When declaring a variable to match an AeroSim type, use the types library:
class example_fmu(Fmi3Slave):
def __init__(self, **kwargs):
super().__init__(**kwargs)
...
self.vehicle_state = dict_to_namespace(
aerosim_types.VehicleState.with_timestamp(
aerosim_types.TimeStamp(0, 0)
).to_dict()
)
register_fmu3_var(self, "vehicle_state", causality="output")
...
This ensures that the output variable vehicle_state of this example will conform to the expected message structure for the vehicle state. This will ensure any AeroSim components subscribing to the FMU driver output will consume the information correctly.
FMU driver
FMUs used in AeroSim are executed by the FMU driver component. This component loads the FMU and publishes/subscribes to the appropriate topics for the FMU to communicate with other components, including other FMUs.
To load an FMU in an AeroSim driver, the FMU must be included as a list item the fmu_models field of the sim config file:
...
"fmu_models": [
{
"id": "example_fmu",
"fmu_model_path": "fmu/example_fmu.fmu",
"component_input_topics": [],
"component_output_topics": [
{
"msg_type": "vehicle_state",
"topic": "aerosim.actor1.vehicle_state"
}
],
"fmu_aux_input_mapping": {},
"fmu_aux_output_mapping": {},
"fmu_initial_vals": {
"init_pos_north": 0.0,
"init_pos_east": 0.0,
"init_pos_down": -10.0
}
}
...
Simulink FMUs
FMUs can also be exported from Simulink. To export Simulink models as FMUs install the FMU Builder for Simulink and then follow the instructions to export an FMU from Mathworks.