diff --git a/README.md b/README.md index bbaaaa4..bf6fa21 100644 --- a/README.md +++ b/README.md @@ -3,28 +3,31 @@ # UniFMU - Universal Functional Mock-Up Units The [_Functional Mock-Up Interface_](https://fmi-standard.org/) _(FMI)_ defines an exchange format that allows models, referred to as _Functional Mock-Up Unit (FMU)_, to be shared between tools supporting the standard. +In general, an FMU must be implemented in a programming language that can produce binaries that can be called from C, such as C itself or C++. +While this allows efficient execution of a simulation, it is a significant limitation when prototyping models. -Traditionally, an FMU must be implemented in a programming language that is compatible with C's binary interface such as C itself or C++. +UniFMU is a command line tool that facilitates the implementation of FMUs in other popular languages that would otherwise not be able to produce C-compatible binaries. +It does this by providing a precompiled binary that is C-compatible, which then dispatches calls to the implementation of the model in the target language. -UniFMU is a command line tool that facilitates the implementation of FMUs in languages in several languages, such as: +| Specification Version | FMU Types | Languages | +| --------------------- | ------------ | ---------- | +| FMI3 | | | +| FMI2 | cosimulation | Python, C# | +| FMI1 | | | -- Python -- C# +Examples of generated FMUs can be found in the [unifmu_examples](https://github.com/INTO-CPS-Association/unifmu_examples) repo. -This is made possible by providing a generic binary that dispatches calls to the users implementation using _remote procedure call_ _(RPC)_. +## Getting the tool -## Installing the tool - -The current and revious versions of the tool can be downloaded from the releases tab of the repository. - -For convenience the tool can be copied to a directory that is in the systems path such as `/usr/bin/` for most Linux distributions. +The tool can be downloaded from [releases](https://github.com/INTO-CPS-Association/unifmu/releases) tab of the repository. +It is a single executable that bundles all assets used during FMU generation as part of the binary. ## How to use the command line interface? To display the synopsis use the `--help` flag. ``` -UniFMU 0.0.4 +UniFMU 0.0.6 Implement 'Functional Mock-up units' (FMUs) in various source languages. USAGE: @@ -93,74 +96,6 @@ Like the file structure, the workflow for modifying FMUs varies depending on the Depending on the language a `README.md` is placed in the root of the generated FMU, which serves as documentation for the particular language. For reference the `README.md` copied into Python FMUs looks like [README.md](tool/unifmu/resources/backends/python/README.md). -## Building and Testing - -Build the cross compilation image from the dockerfile stored in `docker-build` folder: - -``` -docker build -t unifmu-build docker-build -``` - -**Note: This process may take a long time 10-30 minutes, but must only be done once.** - -Start a container with the name `builder` from the cross-compilation image `unifmu-build`: - -```bash -docker run --name builder -it -v $(pwd):/workdir unifmu-build # bash -``` - -```powershell -$pwd = (pwd).Path -docker run --name builder -it -v ${pwd}:/workdir unifmu-build # powershell -``` - -**Note: On windows you may have to enable the use of shared folders through the dockers interface, otherwise the container fails to start.** - -To build the code invoke the script `docker-build/build_all.sh` in the `workdir` of the container: - -``` bash -bash ./docker-build/build_all.sh -``` - -This generates and copies all relevant build artifacts into the `assets/auto_generated` directory: - -``` -📦auto_generated - ┣ 📜.gitkeep - ┣ 📜unifmu.dll - ┣ 📜unifmu.dylib - ┣ 📜unifmu.so - ┣ 📜UnifmuFmi2.cs - ┗ 📜unifmu_fmi2_pb2.py -``` - -**Note: On windows Git may be configured to replace LF line-endings with CRLF, which are not compatible with bash.** - -Following this the cli is compiled for each platform, including the assets that were just compiled. -The final standalone executables can be found in the target folder, under the host tripple: - -- linux: unifmu-x86_64-unknown-linux-gnu-0.0.4.zip -- windows: unifmu-x86_64-pc-windows-gnu-0.0.4.zip -- macOS: unifmu-x86_64-apple-darwin-0.0.4.zip - -## Environment Variables - -In addition to the systems environment variables, UniFMU defines the following variables in the process created during instantiation of a slave. -These can be accessed during execution by the model implementation or the backend. - -| Variable | Description | Example | -| ------------------------------- | ----------------------------------------------------------------------------------------------------------------------------- | ------------------------------------- | -| UNIFMU_GUID | The global unique identifier, passed as an argument to fmi2Instantiate | 77236337-210e-4e9c-8f2c-c1a0677db21b | -| UNIFMU_INSTANCE_NAME | Name of the slave instance, passed as an argument to fmi2Instantiate | left_wheel_motor | -| UNIFMU_VISIBLE | Flag used to indicating if the instance should run in visible mode, passed as an argument to fmi2Instantiate | {true, false} | -| UNIFMU_LOGGING_ON | Flag used to indicating if the instance should run with logging, passed as an argument to fmi2Instantiate | {true, false} | -| UNIFMU_FMU_TYPE | Flag used to indicating if the instance is running in co-sim or model exchange mode, passed as an argument to fmi2Instantiate | {fmi2ModelExchange, fmi2CoSimulation} | -| UNIFMU_DISPATCHER_ENDPOINT | Endpoint bound by the zmq socket of the binary | tcp://127.0.0.1/5000 | -| UNIFMU_DISPATCHER_ENDPOINT_PORT | Port component of UNIFMU_DISPATCHER_ENDPOINT | 5000 | -| UNIFMU_REFS_TO_ATTRS | Mapping from value references to variable names encoded as a JSON dictionary\*. | {"0": "my_input", "1" : "my_output"} | - -\* This variable is only defined if the modelDescription.xml is present in the parent directory of the resources folder passed to fmi2Instantiate. - ## Citing the tool When citing the tool, please cite the following paper: diff --git a/assets/csharp/model.cs b/assets/csharp/model.cs index 2a3cbea..bf4034f 100644 --- a/assets/csharp/model.cs +++ b/assets/csharp/model.cs @@ -3,8 +3,6 @@ using System.IO; using System.Reflection; - -using Newtonsoft.Json; using System.Linq; using Fmi2Proto; @@ -28,18 +26,21 @@ public class Model public Model() { - // Populate map from value reference to attributes of the model. - string references_to_values = System.Environment.GetEnvironmentVariable("UNIFMU_REFS_TO_ATTRS"); - if (references_to_values == null) - { - Console.WriteLine("the environment variable 'UNIFMU_REFS_TO_ATTRS' was not set"); - Environment.Exit(-1); - } - var dict = JsonConvert.DeserializeObject>(references_to_values); - foreach (var (vref, variable) in dict) + this.reference_to_attributes = new Dictionary { - this.reference_to_attributes.Add(vref, this.GetType().GetProperty(variable)); - } + { 0, this.GetType().GetProperty("real_a") }, + { 1, this.GetType().GetProperty("real_b") }, + { 2, this.GetType().GetProperty("real_c") }, + { 3, this.GetType().GetProperty("integer_a") }, + { 4, this.GetType().GetProperty("integer_b") }, + { 5, this.GetType().GetProperty("integer_c") }, + { 6, this.GetType().GetProperty("boolean_a") }, + { 7, this.GetType().GetProperty("boolean_b") }, + { 8, this.GetType().GetProperty("boolean_c") }, + { 9, this.GetType().GetProperty("string_a") }, + { 10, this.GetType().GetProperty("string_b") }, + { 11, this.GetType().GetProperty("string_c") }, + }; } public Fmi2Status Fmi2DoStep(double currentTime, double stepSize, bool noStepPrior) diff --git a/assets/python/README.md b/assets/python/README.md index 12a37b5..b54f5a3 100644 --- a/assets/python/README.md +++ b/assets/python/README.md @@ -1,358 +1,115 @@ **This FMU was generated using UniFMU. For general instructions on how to use the tool access the repository https://github.com/INTO-CPS-Association/unifmu** -An overview of the role of each file is provided in the tree below: - -```python -📦model - ┣ 📂binaries # binaries for linux, windows and macOS - ┃ ┣ 📂darwin64 - ┃ ┃ ┗ 📜unifmu.dylib - ┃ ┣ 📂linux64 - ┃ ┃ ┗ 📜unifmu.so - ┃ ┗ 📂win64 - ┃ ┃ ┗ 📜unifmu.dll - ┣ 📂resources - ┃ ┣ 📂schemas # schemas for grpc backend - ┃ ┃ ┣ 📜unifmu_fmi2_pb2.py - ┃ ┃ ┗ 📜unifmu_fmi2_pb2_grpc.py - ┃ ┣ 📜backend_grpc.py # grpc based backend - ┃ ┣ 📜socket_slave.py # zmq based backend - ┃ ┣ 📜fmi2.py # FMI related class definitions - ┃ ┣ 📜launch.toml* "OS specific commands to bootstrap backend" - ┃ ┗ 📜model.py* "implementation of FMU" - ┗ 📜modelDescription.xml* "definition of inputs and outputs" -``` - -\* denotes files that should be modified by the user. - -# Selecting Python interpreter +# Implementing the model -The `launch.toml` specifies a command that is used to start the selected backend using a specific python interpreter. - -```toml -backend = "grpc" - -[grpc] -linux = ["python3", "backend_grpc.py"] -macos = ["python3", "backend_grpc.py"] -windows = ["python", "backend_grpc.py"] - -[zmq] -linux = ["python3", "socket_slave.py"] -macos = ["python3", "socket_slave.py"] -serialization_format = "Pickle" -windows = ["python", "socket_slave.py"] -``` - -By default the interpreter used on windows is `python` and `python3` on Linux and macOS. -**The executable must be in the systems path, otherwise the FMU can not be instantiated.** - -Note that the command is invoked by the system without the use of a shell. -On some operating systems an name like `python3` may actually be an alias defined by the shell and not an executable that is in the system's path. -There are several solutions to this: - -1. Add the python interpreter to the system's path. -2. Replace the alias, `python3`, with the absolute path to the interpreter. - ```toml - linux = ["somedir/python3","backend_grpc.py"] - macos = ["somedir/python3","backend_grpc.py"] - windows = ["C:\Program Files\Python39\python.exe", "backend_grpc.py"] - ``` -3. Launch the shell, then launch the interpreter using the alias. - ```toml - linux = ["sh", "python3", "backend_grpc.py"] - macos = ["zsh", "python3", "backend_grpc.py"] - windows = ["powershell", "python3", "backend_grpc.py"] - ``` - -# How to modify the model? - -The behavior of the model is defined by the `model.py` script, which provides an object oriented API for defining FMUs. -Specifically, to implement an FMU the methods such as the `do_step` or `enter_initialization_model` functions. -The python methods serve as the implementation of the FMU and are invoked whenever a call is made to the corresponding method in FMI's C-API. - -```python -def do_step(self, current_time, step_size, no_step_prior): - ... - return Fmi2Status.ok - -def enter_initialization_mode(self) -> int: - ... - return Fmi2Status.ok - -``` +The `resources/model.py` file defines the functional relationship between inputs and outputs of the FMU. -For a complete list of function see the `Fmi2FMU` class defined in `fmi2.py` +## Declaring inputs and outputs -# How to set and get variables? - -By default the variables defined in the `modelDescription.xml` are bound to the attributes of the instance of the model in Python. - -For instance, consider the a variable `real_a` defined in the `modelDescription.xml` file as shown below: +By default, each input, output or parameter declared in the `modelDescription.xml` file is represented as attributes on the instance of the `Model` class. +For instance if a variable `a` is declared in the `modelDescription.xml` file, it an attribute of the same name should be declared in the `Model` class: ```xml - + ``` -To set the value of the variable from within the model, the variable can be assigned to just like any other python attribute. -For example the following code sets `real_a` to zero: - ```python -self.real_a = 0.0 -``` - -It is also possible to define _getters_ and _setters_ for a variable in the model. -Using python's `property` decorator the `real_a` can be turned into a property as follows: +def __init__(self) -> None: + self.a = 0.0 -```python -@property -def real_a(self): - return self._real_a - -@real_a.setter -def real_a(self, value): - self._real_a = value + self.reference_to_attribute = { + 0: "a", + } ``` -The use of properties shown above, does not provide much advantage compared to representing the variable as a plain python attribute. -However, the use of _getter_ and _setters_ is useful for things such as: - -- Validating values when setting variables -- Modelling cases of feedthrough, where a change in input variable causes an update to an output, despite a `fmi2DoStep` is invoked. +The FMI C-API uses numerical indices rather than names to which variables to read or write to. +As such a mapping between the indices declared by the `valueReference` attribute of the xml and the attributes must be defined. +By default the mapping between a value reference and its corresponding Python attribute is defined by adding an entry to the `reference_to_attributes` variable of the `Model` class. -An alternative way to customize the mapping between the variables of the `modelDescription.xml` and the attributes of the python object, is to override the `get_xxx` and `set_xxx` defined in the `Fmi2FMU` class. -The default implementation of the two methods is what establishes the direct mapping between the names of variables declared in the `modelDescription.xml` file and the names of the python object. -Below is a _pseudo-code_ representation of the two methods is shown: +## Defining the behavior -```python -def get_xxx(self, references): - # convert to value references to attribute names, then call `getattr` - ... - return Fmi2Status.ok, values - -def set_xxx(self, references, values): - # convert to value references to attribute names, then call `setattr` - ... - return Fmi2Status.ok -``` +The `Model` class declares several methods that can be used to define the behavior of the FMU. +Methods prefixed with `fmi2` mirror the methods declared in the C-API defined by the FMI specification. -Note that this translation requires that the `modelDescription.xml` is available during runtime for parsing. -Most FMU importing tools unzip the entire FMU archive to the same directory; resulting in the `modelDescription.xml` ending up in the parent folder of the `resources` directory. -However, the FMI specification does not require this to be the case. - -If this is a problem for you, it can be addressed in several ways, two of which are: - -- The `modelDescription.xml` could be copied to the resources directory. -- The parsing of `modelDescription.xml` could be replaced with manually defined map implemented in `set_xxx` and `get_xxx` - -# Error handling - -FMI uses error codes to indicate the success or failure of an operation, whereas Python typically handles errors using try-catch blocks. -Python FMUs generated by UniFMU use status codes to report the state of each operation performed. -Concretely, the FMI functions defined by the `Model` class, must return a status code indicating if the operation went well. -For example, consider the example of the `do_step` implementation shown below: +For instance, to update an output `b` to be twice the value of `a` the `fmi2DoStep` method could be defined as: ```python -# return is error -> error -def do_step(self, current_time, step_size, no_step_prior): - - return Fmi2Status.error - -# return is error -> error -def do_step(self, current_time, step_size, no_step_prior): - - return Fmi2Status.error - -``` - -Unrecognized or lack-of returns result in status code error: - -```python -def do_step(self, current_time, step_size, no_step_prior): - ... - # no return - -def do_step(self, current_time, step_size, no_step_prior): - return "" -``` - -Additionally, any uncaught exception results in status code error: - -```python -# uncaught exception -> error -def do_step(self, current_time, step_size, no_step_prior): - - raise Exception() - +def fmi2DoStep(self, current_time, step_size, no_step_prior): + self.b = self.a * 2 return Fmi2Status.ok ``` -In addition to the _ok_ and _error_ several other status codes exists. -The permitted status codes are defined in the `Fmi2Status` class: +# Testing and debugging the model -```python -class Fmi2Status: - """Represents the status of the FMU or the results of function calls. - - Values: - * ok: all well - * warning: an issue has arisen, but the computation can continue. - * discard: an operation has resulted in invalid output, which must be discarded - * error: an error has ocurred for this specific FMU instance. - * fatal: an fatal error has ocurred which has corrupted ALL FMU instances. - * pending: indicates that the FMu is doing work asynchronously, which can be retrived later. - - Notes: - FMI section 2.1.3 - - """ - ok = 0 - warning = 1 - discard = 2 - error = 3 - fatal = 4 - pending = 5 -``` - -# Serialization and deserialization - -FMUs generated by UniFMU provides a simplified API for serializing and deserializing an FMU's state. -A pair of methods `serialize` and `deserialize` are used to define how to convert the FMU's current state into a array of bytes and how to subsequently turn to restore the state of the FMU using the stream of bytes. - -In python this can be achieved using the built in library `pickle`, which allows the vast majority of types in python to be serialized and deserialized with ease. -For instance, an example of how to implement the two methods is shown below: +The `model.py` is _plain_ Python code, which means we can test the model using test cases and debugging tools. +A small test program can be written and placed in the `model.py` the slave as seen below: ```python -def serialize(self): - bytes = pickle.dumps((self.real_a, self.real_b)) - return Fmi2Status.ok, bytes +if __name__ == "__main__": + m = Model() -def deserialize(self, bytes): - (real_a, real_b) = pickle.loads(bytes) - self.real_a = real_a - self.real_b = real_b + assert m.a == 0.0 + assert m.b == 0.0 - return Fmi2Status.ok -``` + m.a = 1.0 + m.fmiDoStep(0.0, 1.0, False) -Here the example only shows two variables being serialized. However serialization mechanism will work any number of variables. -It is up to the implementer of the FMU to ensure that the variables being serialized are sufficient to restore the FMU. -Similarly, it is also the implementers decision on how to handle shared resources like files opened by an FMU. + assert m.b == 2.0 +``` -# Testing the model +The program can be executed in your IDE with or from the command line by running the `resources/model.py` script. -Remember the `model.py` is _plain_ python code, so not why test it in python, where you have good debugging tools? +# Runtime dependencies -A small test program can be written and placed in the `model.py` the slave as seen below: +The environment that invokes the Python code must provide all the dependencies, otherwise the simulation will fail when instantiating or simulation the model. +For instance, if the `resources/model.py` imports a third-party package such as `numpy` ```python -if __name__ == "__main__": # <--- ensures that test-code is not run if module is imported - import numpy as np - import matplotlib.pyplot as plt - - # create time stamps - n_steps = 100 - ts, step_size = np.linspace(0, 10, n_steps, retstep=True) - - # create FMU - generator = SineGenerator() - generator.amplitude = 10 - generator.std = 1 - generator.setup_experiment(0) - - # outputs - ys = np.zeros(n_steps) - - for idx, t in enumerate(ts): - ys[idx] = generator.y - generator.do_step(t, step_size, False) - - plt.plot(ts, ys) - plt.ylabel("y(t)") - plt.xlabel("t") - plt.show() +import numpy as np ``` -To test the FMU simply run the script in the python interpreter: +this must also be available to the Python interpreter specified by the `launch.toml` file, in this case the system's `python3` interpreter: -```bash -python model.py +```toml +linux = ["python3", "backend.py"] ``` -A more complex FMU may warrant multiple test cases, each testing a distinct piece of functionality. For these cases a proper testing framework like [pytest](https://docs.pytest.org/en/stable/) is recommended. - -# How to manage runtime dependencies? +One way to address a missing dependency is to install using package manager such as `pip` -Python FMU generated by UniFMU requires the following python packages during runtime: - -- grpcio -- protobuf -- zmq - -These can be installed in the current environment using `pip`: - -```bash -python -m pip install unifmu[python-backend] ``` - -Additionally, any library imported by the model will also need to be present during runtime. -For instance if the model uses `numpy` and `scipy`, these will also need to be installed in the environment running the FMU. - -## Virtual Enviornments - -There several options for managing python environments, such as: - -- [venv](https://docs.python.org/3/library/venv.html) (part of standard library since 3.3) -- [conda](https://docs.conda.io/en/latest/) (third-party) - -Bundling dependencies is not the core goal of UniFMU, rather we aim to provide a mechanism to easily integrate FMUs with other technologies such as virtual environments. - -To illustrate this, consider the process of bootstrapping a virtual environment using `venv`. -First, create a new python environment: - -```bash -python -m venv fmu_env +python3 -m pip install numpy ``` -Next we activate the environment: +**Any Python FMU generated UniFMU requires the `protobuf` package. +The easiest way to install this is using pip:** -```bash -source fmu_env/bin/activate ``` - -Install UniFMU's python FMU runtime dependencies and dependencies used by the model: - -```bash -python -m pip install unifmu[python-backend] numpy scipy +python3 -m pip install protobuf ``` -We store all requirements in a text file, conventionally named `requirements.txt`: +# File structure -```bash -python -m pip freeze > requirements.txt -``` +An overview of the role of each file is provided in the tree below: -The contents of the `requirements.txt` lists all the dependencies installed in the environment. -It should look like something along the lines of: - -```txt -grpcio==1.38.0 -lxml==4.6.3 -numpy==1.20.3 -protobuf==3.17.3 -pyzmq==22.1.0 -scipy==1.6.3 -six==1.16.0 -toml==0.10.2 -unifmu==0.0.2 +```python +📦model + ┣ 📂binaries + ┃ ┣ 📂darwin64 + ┃ ┃ ┗ 📜unifmu.dylib # binary for macOS + ┃ ┣ 📂linux64 + ┃ ┃ ┗ 📜unifmu.so # binary for Linux + ┃ ┗ 📂win64 + ┃ ┃ ┗ 📜unifmu.dll # binary For Windows + ┣ 📂resources + ┃ ┣ 📂schemas + ┃ ┃ ┗ 📜unifmu_fmi2_pb2.py # schema defining structure of messages sent over RPC + ┃ ┣ 📜backend.py # receives messages and dispatched function calls to "model.py" + ┃ ┣ 📜launch.toml* # specifies command used to start FMU + ┃ ┗ 📜model.py* # implementation of FMU + ┗ 📜modelDescription.xml* # definition of inputs and outputs ``` -Next, we want to modify the `launch.toml` to do the following: - -1. Create a new virtual environment named _fmu_env_ if it does not already exist. -2. Activate the environment. -3. Install all missing dependencies from the `requirements.txt` in the environment. -4. Launch the model from the environment. +\* denotes files that would typically be modified by the implementor of the FMU diff --git a/assets/python/model.py b/assets/python/model.py index f708033..1baf18e 100644 --- a/assets/python/model.py +++ b/assets/python/model.py @@ -1,5 +1,4 @@ -import os, json, pickle -from schemas.unifmu_fmi2_pb2 import Ok +import pickle class Model: @@ -13,15 +12,19 @@ def __init__(self) -> None: self.string_a = "" self.string_b = "" - refs_to_attr = os.environ["UNIFMU_REFS_TO_ATTRS"] - - if refs_to_attr is None: - raise NotImplementedError( - "the environment variable 'UNIFMU_REFS_TO_ATTRS' was not set" - ) - self.reference_to_attribute = { - int(k): v for k, v in json.loads(refs_to_attr).items() + 0: "real_a", + 1: "real_b", + 2: "real_c", + 3: "integer_a", + 4: "integer_b", + 5: "integer_c", + 6: "boolean_a", + 7: "boolean_b", + 8: "boolean_c", + 9: "string_a", + 10: "string_b", + 11: "string_c", } self._update_outputs() @@ -34,6 +37,7 @@ def fmi2EnterInitializationMode(self): return Fmi2Status.ok def fmi2ExitInitializationMode(self): + self._update_outputs() return Fmi2Status.ok def fmi2SetupExperiment(self, start_time, stop_time, tolerance): @@ -153,3 +157,36 @@ class Fmi2Status: error = 3 fatal = 4 pending = 5 + + +if __name__ == "__main__": + m = Model() + + assert m.real_a == 0.0 + assert m.real_b == 0.0 + assert m.real_c == 0.0 + assert m.integer_a == 0 + assert m.integer_b == 0 + assert m.integer_c == 0 + assert m.boolean_a == False + assert m.boolean_b == False + assert m.boolean_c == False + assert m.string_a == "" + assert m.string_b == "" + assert m.string_c == "" + + m.real_a = 1.0 + m.real_b = 2.0 + m.integer_a = 1 + m.integer_b = 2 + m.boolean_a = True + m.boolean_b = False + m.string_a = "Hello " + m.string_b = "World!" + + assert m.fmi2DoStep(0.0, 1.0, False) == Fmi2Status.ok + + assert m.real_c == 3.0 + assert m.integer_c == 3 + assert m.boolean_c == True + assert m.string_c == "Hello World!" diff --git a/docs/notes.md b/docs/notes.md index 65906c8..70b396f 100644 --- a/docs/notes.md +++ b/docs/notes.md @@ -89,3 +89,68 @@ def deserialize(self, bytes) -> int: return Fmi2Status.ok ``` + +## Building and Testing + +Build the cross compilation image from the dockerfile stored in `docker-build` folder: + +``` +docker build -t unifmu-build docker-build +``` + +**Note: This process may take a long time 10-30 minutes, but must only be done once.** + +Start a container with the name `builder` from the cross-compilation image `unifmu-build`: + +```bash +docker run --name builder -it -v $(pwd):/workdir unifmu-build # bash +``` + +```powershell +$pwd = (pwd).Path +docker run --name builder -it -v ${pwd}:/workdir unifmu-build # powershell +``` + +**Note: On windows you may have to enable the use of shared folders through the dockers interface, otherwise the container fails to start.** + +To build the code invoke the script `docker-build/build_all.sh` in the `workdir` of the container: + +```bash +bash ./docker-build/build_all.sh +``` + +This generates and copies all relevant build artifacts into the `assets/auto_generated` directory: + +``` +📦auto_generated + ┣ 📜.gitkeep + ┣ 📜unifmu.dll + ┣ 📜unifmu.dylib + ┣ 📜unifmu.so + ┣ 📜UnifmuFmi2.cs + ┗ 📜unifmu_fmi2_pb2.py +``` + +**Note: On windows Git may be configured to replace LF line-endings with CRLF, which are not compatible with bash.** + +Following this the cli is compiled for each platform, including the assets that were just compiled. +The final standalone executables can be found in the target folder, under the host tripple: + +- linux: unifmu-x86_64-unknown-linux-gnu-0.0.4.zip +- windows: unifmu-x86_64-pc-windows-gnu-0.0.4.zip +- macOS: unifmu-x86_64-apple-darwin-0.0.4.zip + +## Environment Variables + +In addition to the systems environment variables, UniFMU defines the following variables in the process created during instantiation of a slave. +These can be accessed during execution by the model implementation or the backend. + +| Variable | Description | Example | +| ------------------------------- | ----------------------------------------------------------------------------------------------------------------------------- | ------------------------------------- | +| UNIFMU_GUID | The global unique identifier, passed as an argument to fmi2Instantiate | 77236337-210e-4e9c-8f2c-c1a0677db21b | +| UNIFMU_INSTANCE_NAME | Name of the slave instance, passed as an argument to fmi2Instantiate | left_wheel_motor | +| UNIFMU_VISIBLE | Flag used to indicating if the instance should run in visible mode, passed as an argument to fmi2Instantiate | {true, false} | +| UNIFMU_LOGGING_ON | Flag used to indicating if the instance should run with logging, passed as an argument to fmi2Instantiate | {true, false} | +| UNIFMU_FMU_TYPE | Flag used to indicating if the instance is running in co-sim or model exchange mode, passed as an argument to fmi2Instantiate | {fmi2ModelExchange, fmi2CoSimulation} | +| UNIFMU_DISPATCHER_ENDPOINT | Endpoint bound by the zmq socket of the binary | tcp://127.0.0.1/5000 | +| UNIFMU_DISPATCHER_ENDPOINT_PORT | Port component of UNIFMU_DISPATCHER_ENDPOINT | 5000 | diff --git a/fmi2api/Cargo.toml b/fmi2api/Cargo.toml index 739dbfc..01e2c44 100644 --- a/fmi2api/Cargo.toml +++ b/fmi2api/Cargo.toml @@ -27,7 +27,5 @@ zeromq = {git = "https://github.com/zeromq/zmq.rs.git", default-features = false "tcp-transport", ]} -common = {path = "../common"} - [build-dependencies] prost-build = "0.8.0" diff --git a/fmi2api/src/lib.rs b/fmi2api/src/lib.rs index 7c63d3e..9e8dc3b 100644 --- a/fmi2api/src/lib.rs +++ b/fmi2api/src/lib.rs @@ -8,7 +8,6 @@ pub mod dispatcher; pub mod fmi2_proto; pub mod socket_dispatcher; -use common::md; use dispatcher::{Fmi2CommandDispatcher, Fmi2CommandDispatcherError}; use libc::c_double; use libc::size_t; @@ -18,12 +17,11 @@ use safer_ffi::{ c, char_p::{char_p_raw, char_p_ref}, }; -use serde_json; + use subprocess::Popen; use subprocess::PopenConfig; use url::Url; -use std::collections::HashMap; use std::ffi::CStr; use std::ffi::CString; use std::ffi::OsString; @@ -40,7 +38,6 @@ use std::slice::from_raw_parts_mut; use crate::config::LaunchConfig; use crate::socket_dispatcher::Fmi2SocketDispatcher; -use common::md::parse_model_description; /// /// Represents the function signature of the logging callback function passsed @@ -271,33 +268,6 @@ pub fn fmi2Instantiate( OsString::from(endpoint_port), )); - match resources_dir.parent() { - Some(p) => match parse_model_description(&p.join("modelDescription.xml")) { - Ok(md) => { - let ref_to_attr: HashMap = md - .model_variables - .variables - .iter() - .map(|v| (v.value_reference, v.name.to_owned())) - .collect(); - - env_vars.push(( - OsString::from("UNIFMU_REFS_TO_ATTRS"), - OsString::from(serde_json::to_string(&ref_to_attr).unwrap()), - )); - } - Err(e) => match e { - md::Fmi2ModelDescriptionError::UnableToRead => { - println!("the 'modelDescription.xml' file was not found") - } - md::Fmi2ModelDescriptionError::UnableToParse => { - println!("the 'modelDescription.xml' file was found but could not be parsed") - } - }, - }, - None => println!("resources directory has no parent"), - } - // grab launch command for host os let launch_command = match std::env::consts::OS { "windows" => match config.windows {