diff --git a/.gitignore b/.gitignore
index ca6b1b7..cb8c953 100644
--- a/.gitignore
+++ b/.gitignore
@@ -11,6 +11,7 @@ extra/tools/*/bin
!extra/tools/*/build
extra/tools/*/build/stage
.task/
+.vscode
# Prerequisites
*.d
diff --git a/README.md b/README.md
index 8937966..17df9d8 100644
--- a/README.md
+++ b/README.md
@@ -94,6 +94,14 @@ task: Available tasks for this project:
* generate-fmimcl: Generate an FMI MCL from an existing FMU.
* generate-fmimodelc: Generate a FMI ModelC FMU from an existing (DSE/ModelC) Simer simulation.
+# FMI Gateway with the generate-fmigateway command:
+$ task generate-fmigateway \
+ FMI_VERSION=2 \
+ SIGNAL_GROUPS="extra/tools/fmi/test/testdata/fmigateway/SG1.yaml,extra/tools/fmi/test/testdata/fmigateway/SG2.yaml"
+Running FMI Toolset command: gen-gateway
+Adding SignalGroup Model_1 to out/fmu.yaml
+Adding SignalGroup Model_2 to out/fmu.yaml
+Creating Model YAML: gateway (out/model.yaml)
# FMI MCL with the generate-fmimcl command:
$ task generate-fmimcl \
diff --git a/Taskfile.yml b/Taskfile.yml
index 0f360d8..f61cc7d 100644
--- a/Taskfile.yml
+++ b/Taskfile.yml
@@ -44,6 +44,14 @@ tasks:
cmds:
- task: gen-fmu
+ generate-fmigateway:
+ desc: Generate a FMI Gateway FMU from existing Signalgroups.
+ run: always
+ dir: '{{.USER_WORKING_DIR}}'
+ label: dse:fmi:generate-fmigateway
+ cmds:
+ - task: gen-gateway
+
gen-mcl:
internal: true
run: always
@@ -112,6 +120,32 @@ tasks:
requires:
vars: [SIM, FMU_NAME]
+ gen-gateway:
+ internal: true
+ run: always
+ dir: '{{.USER_WORKING_DIR}}'
+ label: dse:fmi:gen-gateway
+ vars:
+ SIGNAL_GROUPS: '{{.SIGNAL_GROUPS | default "SIGNAL_GROUPS_NOT_SPECIFIED"}}'
+ OUT_DIR: '{{.OUT_DIR | default "out"}}'
+ SESSION: "{{if .SESSION}}--session '{{.SESSION}}'{{else}}{{end}}"
+ VERSION: "{{if .VERSION}}--version '{{.VERSION}}'{{else}}{{end}}"
+ FMI_VERSION: "{{if .FMIVERSION}}--fmiVersion '{{.FMIVERSION}}'{{else}}{{end}}"
+ UUID: "{{if .UUID}}--uuid '{{.UUID}}'{{else}}{{end}}"
+ cmds:
+ - docker run --rm
+ -v {{.ENTRYDIR}}:/sim
+ {{.FMI_IMAGE}}:{{.FMI_TAG}}
+ gen-gateway
+ --signalgroups {{.SIGNAL_GROUPS}}
+ --outdir {{.OUT_DIR}}
+ {{.SESSION}}
+ {{.VERSION}}
+ {{.FMI_VERSION}}
+ {{.UUID}}
+ requires:
+ vars: [SIGNAL_GROUPS]
+
patch-signalgroup:
run: always
dir: '{{.USER_WORKING_DIR}}'
diff --git a/doc/Makefile b/doc/Makefile
index 1ad1eb0..36d1ee5 100644
--- a/doc/Makefile
+++ b/doc/Makefile
@@ -45,8 +45,15 @@ DOC_OUTPUT_fmu := doc/content/apis/fmi/fmu/index.md
DOC_LINKTITLE_fmu := "FMU"
DOC_TITLE_fmu := "FMU API Reference"
+# Module "fmu"
+DOC_INPUT_fmigateway := dse/fmigateway/fmigateway.h
+DOC_CDIR_fmigateway := dse/fmigateway/fmigateway.c,dse/fmigateway/parser.c,dse/fmigateway/index.c,dse/fmigateway/session.c,dse/fmigateway/session_win32.c
+DOC_OUTPUT_fmigateway := doc/content/apis/fmi/fmigateway/index.md
+DOC_LINKTITLE_fmigateway := "Gateway FMU"
+DOC_TITLE_fmigateway := "FMI Gateway FMU API Reference"
+
# Targets
-DOC_C_MODULES := fmimcl fmimodelc fmu
+DOC_C_MODULES := fmimcl fmimodelc fmu fmigateway
.PHONY: examples
diff --git a/doc/content/apis/fmi/fmigateway/fmigateway-component.png b/doc/content/apis/fmi/fmigateway/fmigateway-component.png
new file mode 100644
index 0000000..299838e
Binary files /dev/null and b/doc/content/apis/fmi/fmigateway/fmigateway-component.png differ
diff --git a/doc/content/apis/fmi/fmigateway/index.md b/doc/content/apis/fmi/fmigateway/index.md
new file mode 100644
index 0000000..42402f5
--- /dev/null
+++ b/doc/content/apis/fmi/fmigateway/index.md
@@ -0,0 +1,262 @@
+---
+title: FMI Gateway FMU API Reference
+linkTitle: Gateway FMU
+---
+## fmu_signals_reset
+
+
+Resets the binary signals of the gateway to a length of 0, if the signals have
+not been reseted yet.
+
+> Required by FMU.
+
+### Parameters
+
+fmu (FmuInstanceData*)
+: The FMU Descriptor object representing an instance of the FMU Model.
+
+
+
+## fmu_signals_setup
+
+
+Placeholder to signal the FMU to not use the default signal allocation.
+
+### Parameters
+
+fmu (FmuInstanceData*)
+: The FMU Descriptor object representing an instance of the FMU Model.
+
+
+
+## fmu_signals_remove
+
+
+This method frees the allocated binary signal indexes.
+
+### Parameters
+
+fmu (FmuInstanceData*)
+: The FMU Descriptor object representing an instance of the FMU Model.
+
+
+
+## FMI ModelC Gateway
+
+
+
+### Component Diagram
+
+
+
+```
+@startuml fmigateway-component
+
+title FMI Gateway FMU
+
+center footer Dynamic Simulation Environment
+
+@enduml
+```
+
+
+
+![](fmigateway-component.png)
+
+
+### Example
+
+
+
+
+## fmu_create
+
+
+This method allocates the necessary gateway models. The location of the required
+yaml files is set and allocated.
+
+> Required by FMU.
+
+### Parameters
+
+fmu (FmuInstanceData*)
+: The FMU Descriptor object representing an instance of the FMU Model.
+
+### Returns
+
+0 (int32_t)
+: The FMU was created correctly.
+
+
+
+## fmu_init
+
+
+In this method the required yaml files are parsed and the session is configured,
+if required. The gateway is set up and connected to the simbus. After a
+sucessfull connection has been established, the fmu variables are indexed to
+their corresponding simbus signals.
+
+> Required by FMU.
+
+### Parameters
+
+fmu (FmuInstanceData*)
+: The FMU Descriptor object representing an instance of the FMU Model.
+
+### Returns
+
+0 (int32_t)
+: The FMU was created correctly.
+
+
+
+## fmu_step
+
+
+This method executes one step of the gateway model and signals are exchanged
+with the other simulation participants.
+
+> Required by FMU.
+
+### Parameters
+
+fmu (FmuInstanceData*)
+: The FMU Descriptor object representing an instance of the FMU Model.
+communication_point (double)
+: The current model time of the FMU in seconds.
+step_size (double)
+: The step size of the FMU in seconds.
+
+### Returns
+
+0 (int32_t)
+: The FMU step was performed correctly.
+
+
+
+
+## fmu_destroy
+
+
+Releases memory and system resources allocated by gateway.
+
+> Required by FMU.
+
+### Parameters
+
+fmu (FmuInstanceData*)
+: The FMU Descriptor object representing an instance of the FMU Model.
+
+### Returns
+
+0 (int32_t)
+: The FMU data was released correctly.
+
+
+
+## Typedefs
+
+### FmiGateway
+
+```c
+typedef struct FmiGateway {
+ int * model;
+ struct (anonymous struct at dse/fmigateway/fmigateway.h:75:5) settings;
+ int binary_signals_reset;
+}
+```
+
+### FmiGatewaySession
+
+```c
+typedef struct FmiGatewaySession {
+ WindowsModel * w_models;
+ struct (anonymous struct at dse/fmigateway/fmigateway.h:61:5) init;
+ struct (anonymous struct at dse/fmigateway/fmigateway.h:65:5) shutdown;
+ double last_step;
+}
+```
+
+### WindowsModel
+
+```c
+typedef struct WindowsModel {
+ const char * path;
+ const char * file;
+ int show_process;
+ const char * name;
+ double step_size;
+ double end_time;
+ int log_level;
+ const char * yaml;
+ double current_step;
+ double timeout;
+ void * w_process;
+}
+```
+
+## Functions
+
+### fmigateway_parse
+
+This method loads the required yaml files from the resource location of the fmu.
+The loaded yaml files are parsed into the fmu descriptor object.
+
+#### Parameters
+
+fmu (FmuInstanceData*)
+: The FMU Descriptor object representing an instance of the FMU Model.
+
+
+
+### fmigateway_session_configure
+
+If session parameters were parsed from the model description, this method
+configures and starts the additional models, or executes the given command.
+
+#### Parameters
+
+fmu (FmuInstanceData*)
+: The FMU Descriptor object representing an instance of the FMU Model.
+
+
+
+### fmigateway_session_end
+
+If session parameters were parsed from the model description, this method
+shuts down the additional models, or executes the given command.
+
+#### Parameters
+
+fmu (FmuInstanceData*)
+: The FMU Descriptor object representing an instance of the FMU Model.
+
+
+
+### fmigateway_session_windows_end
+
+Termiantes all previously started windows processes.
+After sending the termination signals, one additionally
+step is made by the gateway to close the simulation.
+
+#### Parameters
+
+fmu (FmuInstanceData*)
+: The FMU Descriptor object representing an instance of the FMU Model.
+
+
+
+### fmigateway_session_windows_start
+
+Creates windows processes based on the parameters
+configured in a yaml file. Process informations are
+stored for later termination.
+
+#### Parameters
+
+fmu (FmuInstanceData*)
+: The FMU Descriptor object representing an instance of the FMU Model.
+
+
+
diff --git a/doc/content/docs/user/fmi/fmigateway/index.md b/doc/content/docs/user/fmi/fmigateway/index.md
index 52b8a86..ce49ddc 100644
--- a/doc/content/docs/user/fmi/fmigateway/index.md
+++ b/doc/content/docs/user/fmi/fmigateway/index.md
@@ -32,4 +32,86 @@ FMI Gateway FMU
## Operation
-> DOC: Operation.
+### Session
+
+A session handling can be defined for the gatway using the following yaml file in the toolchain.
+It is then automatically placed as part of the annoations in the model.yaml.
+
+|Name|Type|Required|Description|
+|---|---|---|---|
+|session|object|true|none|
+|» initialization|object|false|Script/ File that is run in the initialization phase of the FMU|
+|»» path|string|true|Path to the file|
+|»» file|string|true|File to be executed|
+|» shutdown|object|false|Script/ File that is run in the shutdown phase of the FMU|
+|»» path|string|true|Path to the file|
+|»» file|string|true|File to be executed|
+|» windows|object|false|List of ModelC models to be started/ stopped on windows|
+|»» model|string|true|Name of the model|
+|»» annotation|object|true||
+|»»» stepsize|float|true|Step size of this model|
+|»»» endtime|float|false|End time for this model. If not set, model runs until gateway shutdown|
+|»»» loglevel|int|true|Log level between 0 and 6 (0 for most logs)|
+|»»» timeout|false|true|Model timeout|
+|»»» path|string|true|Path to the model executable|
+|»»» file|string|true|Model file|
+|»»» yaml|string|true|List of yaml files, separated by ";"|
+|»»» show|boolean|true|Terminal toggle for on/ off|
+
+```text
+---
+annotations:
+ session:
+ initialization:
+ path: 'foo'
+ file: 'bar'
+ shutdown:
+ path: 'foo_shutdown'
+ file: 'bar_shutdown'
+ windows:
+ - model: 'Model_1'
+ annotations:
+ stepsize: 0.1
+ endtime: 0.2
+ loglevel: 6
+ timeout: 60.0
+ path: 'foo'
+ file: 'bar'
+ yaml: 'foo;bar'
+ show: true
+ - model: 'Model_2'
+ annotations:
+ stepsize: 0.15
+ endtime: 0.25
+ loglevel: 5
+ timeout: 61.0
+ path: 'foo_2'
+ file: 'bar_2'
+ yaml: 'foo_2;bar_2'
+ show: false
+```
+
+### Toolchains
+
+The required files for the FMI gateway can be generated using a toolchain included
+in the FMI GO tools. This operation with generate the required modelDescription.xml,
+model.yaml and fmu.yaml.
+
+|Name|Required|Description|
+|---|---|---|
+|VERSION|false|Version of the Gateway FMU|
+|FMI_VERSION|false|FMI Version of the Gateway FMU (Default 2)|
+|SESSION|false|Yaml file containing the session handling|
+|SIGNAL_GROUPS|true|List of yaml files to generate the required files for the Gateway FMU|
+|OUT_DIR|false|Path to the generated files|
+|UUID|false|UUID of the Gateway FMU|
+
+```bash
+$ task generate-fmigateway \
+ VERSION=0.01 \
+ FMI_VERSION=2 \
+ SESSION=session.yaml \
+ SIGNAL_GROUPS="extra/tools/fmi/test/testdata/fmigateway/SG1.yaml,extra/tools/fmi/test/testdata/fmigateway/SG2.yaml" \
+ OUT_DIR=./out \
+ UUID=2f9f2b62-0718-4e66-8f40-6735e03d4c08
+```
diff --git a/dse/CMakeLists.txt b/dse/CMakeLists.txt
index 28a6a77..5b74fff 100644
--- a/dse/CMakeLists.txt
+++ b/dse/CMakeLists.txt
@@ -142,6 +142,7 @@ endif()
# ===========
add_subdirectory(fmimcl)
add_subdirectory(fmimodelc)
+add_subdirectory(fmigateway)
add_subdirectory(fmu)
add_subdirectory(importer)
add_subdirectory(examples)
diff --git a/dse/examples/CMakeLists.txt b/dse/examples/CMakeLists.txt
index 6bbd5e2..476ec9f 100644
--- a/dse/examples/CMakeLists.txt
+++ b/dse/examples/CMakeLists.txt
@@ -5,4 +5,5 @@
if($ENV{PACKAGE_ARCH} STREQUAL "linux-amd64")
add_subdirectory(fmu/counter)
add_subdirectory(fmu/linear)
+add_subdirectory(gateway)
endif()
diff --git a/dse/examples/gateway/CMakeLists.txt b/dse/examples/gateway/CMakeLists.txt
new file mode 100644
index 0000000..4c3e5e4
--- /dev/null
+++ b/dse/examples/gateway/CMakeLists.txt
@@ -0,0 +1,224 @@
+# Copyright 2024 Robert Bosch GmbH
+#
+# SPDX-License-Identifier: Apache-2.0
+
+cmake_minimum_required(VERSION 3.21)
+
+# set(CMAKE_VERBOSE_MAKEFILE ON)
+
+set(VERSION "$ENV{PACKAGE_VERSION}")
+set(MODULE "Gateway")
+set(MODULE_LC "gateway")
+
+project(fmigateway VERSION ${VERSION})
+
+include(FetchContent)
+
+# set(CMAKE_VERBOSE_MAKEFILE ON)
+set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
+set(CMAKE_C_STANDARD 99)
+set(CMAKE_C_STANDARD_REQUIRED TRUE)
+set(CMAKE_POSITION_INDEPENDENT_CODE ON)
+set(CMAKE_BUILD_TYPE Debug)
+set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -O3 -ggdb -DLIBXML_STATIC")
+list(APPEND C_CXX_WARNING_FLAGS
+ -Wall
+ -W
+ -Wwrite-strings
+ -Wno-missing-field-initializers
+ -Wno-misleading-indentation
+)
+add_compile_options(${C_CXX_WARNING_FLAGS})
+
+
+# External Project - DSE C Lib
+# ----------------------------
+FetchContent_Declare(dse_clib
+ URL https://github.com/boschglobal/dse.clib/archive/refs/tags/v$ENV{DSE_CLIB_VERSION}.zip
+)
+FetchContent_MakeAvailable(dse_clib)
+set(DSE_CLIB_SOURCE_DIR ${dse_clib_SOURCE_DIR}/dse)
+set(DSE_CLIB_INCLUDE_DIR "${DSE_CLIB_SOURCE_DIR}/..")
+set(FMI2_INCLUDE_DIR "${DSE_CLIB_SOURCE_DIR}/clib/fmi/fmi2/headers")
+set(FMI3_INCLUDE_DIR "${DSE_CLIB_SOURCE_DIR}/clib/fmi/fmi3/headers")
+
+
+# External Project - DSE ModelC LIB
+# ---------------------------------
+FetchContent_Declare(dse_modelc_lib
+ URL https://github.com/boschglobal/dse.modelc/releases/download/v$ENV{DSE_MODELC_VERSION}/ModelC-$ENV{DSE_MODELC_VERSION}-$ENV{PACKAGE_ARCH}.zip
+)
+FetchContent_MakeAvailable(dse_modelc_lib)
+set(DSE_MODELC_INCLUDE_DIR "${dse_modelc_lib_SOURCE_DIR}/include")
+
+
+set(FMU_SOURCE_DIR "../../fmu")
+set(FMU_INCLUDE_DIR "../../..")
+set(FMI_2_COMMON_FILES
+ ${FMU_SOURCE_DIR}/ascii85.c
+ ${FMU_SOURCE_DIR}/fmi2fmu.c
+ ${FMU_SOURCE_DIR}/signal.c
+)
+set(FMI_3_COMMON_FILES
+ ${FMU_SOURCE_DIR}/ascii85.c
+ ${FMU_SOURCE_DIR}/fmi3fmu.c
+ ${FMU_SOURCE_DIR}/signal.c
+)
+set(FMIGATEWAY_FILES "../../fmigateway")
+set(FMIGATEWAY_FILES
+ ${FMIGATEWAY_FILES}/fmigateway.c
+ ${FMIGATEWAY_FILES}/index.c
+ ${FMIGATEWAY_FILES}/session.c
+ ${FMIGATEWAY_FILES}/parser.c
+ $<$:${FMIGATEWAY_FILES}/session_win32.c>
+)
+
+# Targets
+# =======
+
+# Simulation (single Model)
+# ----------
+add_library(gatewaytarget SHARED
+ model.c
+)
+target_include_directories(gatewaytarget
+ PRIVATE
+ ${DSE_MODELC_INCLUDE_DIR}
+ ${FMU_INCLUDE_DIR}
+)
+target_link_directories(gatewaytarget
+ PRIVATE
+ $<$:${dse_modelc_lib_SOURCE_DIR}/bin>
+)
+target_link_libraries(gatewaytarget
+ PRIVATE
+ $<$:modelc>
+)
+install(TARGETS gatewaytarget
+ LIBRARY DESTINATION
+ examples/gateway/model
+)
+
+# FMI2 Gateway FMU
+# ---------------
+add_library(gatewayfmi2fmu SHARED
+ ${FMI_2_COMMON_FILES}
+ ${FMIGATEWAY_FILES}
+)
+target_include_directories(gatewayfmi2fmu
+ PRIVATE
+ ${DSE_CLIB_INCLUDE_DIR}
+ ${DSE_MODELC_INCLUDE_DIR}
+ ${FMI2_INCLUDE_DIR}
+ ${FMU_INCLUDE_DIR}
+)
+target_link_directories(gatewayfmi2fmu
+ PRIVATE
+ $<$:${dse_modelc_lib_SOURCE_DIR}/lib>
+ $<$:${dse_modelc_lib_SOURCE_DIR}/bin>
+)
+target_compile_definitions(gatewayfmi2fmu
+ PRIVATE
+ PLATFORM_OS="${CDEF_PLATFORM_OS}"
+ PLATFORM_ARCH="${CDEF_PLATFORM_ARCH}"
+)
+target_link_libraries(gatewayfmi2fmu
+ PRIVATE
+ yaml
+ xml
+ $<$:bcrypt>
+ $<$:-Wl,--whole-archive,--export-dynamic>
+ $<$:modelc_bundled>
+ $<$:-Wl,--no-whole-archive>
+ $<$:rt>
+ $<$:modelc>
+ $<$:dl>
+
+)
+set_target_properties(gatewayfmi2fmu
+ PROPERTIES
+ OUTPUT_NAME
+ fmi2gateway
+)
+install(
+ TARGETS
+ gatewayfmi2fmu
+ LIBRARY DESTINATION
+ examples/gateway/fmi2/binaries/linux64
+)
+install(
+ FILES
+ fmi2/modelDescription.xml
+ DESTINATION
+ examples/gateway/fmi2
+)
+install(
+ FILES
+ data/fmu.yaml
+ data/model.yaml
+ data/stack.yaml
+ DESTINATION
+ examples/gateway/fmi2/resources
+)
+
+# FMI3 Gateway FMU
+# ---------------
+add_library(gatewayfmi3fmu SHARED
+ ${FMI_3_COMMON_FILES}
+ ${FMIGATEWAY_FILES}
+)
+target_include_directories(gatewayfmi3fmu
+ PRIVATE
+ ${DSE_CLIB_INCLUDE_DIR}
+ ${DSE_MODELC_INCLUDE_DIR}
+ ${FMI3_INCLUDE_DIR}
+ ${FMU_INCLUDE_DIR}
+)
+target_link_directories(gatewayfmi3fmu
+ PRIVATE
+ $<$:${dse_modelc_lib_SOURCE_DIR}/lib>
+ $<$:${dse_modelc_lib_SOURCE_DIR}/bin>
+)
+target_compile_definitions(gatewayfmi3fmu
+ PRIVATE
+ PLATFORM_OS="${CDEF_PLATFORM_OS}"
+ PLATFORM_ARCH="${CDEF_PLATFORM_ARCH}"
+)
+target_link_libraries(gatewayfmi3fmu
+ PRIVATE
+ yaml
+ xml
+ $<$:bcrypt>
+ $<$:-Wl,--whole-archive,--export-dynamic>
+ $<$:modelc_bundled>
+ $<$:-Wl,--no-whole-archive>
+ $<$:rt>
+ $<$:modelc>
+ $<$:dl>
+
+)
+set_target_properties(gatewayfmi3fmu
+ PROPERTIES
+ OUTPUT_NAME
+ fmi3gateway
+)
+install(
+ TARGETS
+ gatewayfmi3fmu
+ LIBRARY DESTINATION
+ examples/gateway/fmi3/binaries/x86_64-linux
+)
+install(
+ FILES
+ fmi3/modelDescription.xml
+ DESTINATION
+ examples/gateway/fmi3
+)
+install(
+ FILES
+ data/fmu.yaml
+ data/model.yaml
+ data/stack.yaml
+ DESTINATION
+ examples/gateway/fmi3/resources
+)
diff --git a/dse/examples/gateway/data/fmu.yaml b/dse/examples/gateway/data/fmu.yaml
new file mode 100644
index 0000000..64848d7
--- /dev/null
+++ b/dse/examples/gateway/data/fmu.yaml
@@ -0,0 +1,90 @@
+---
+kind: SignalGroup
+metadata:
+ name: scalar_vector
+ labels:
+ channel: scalar
+ annotations:
+ vector_type: scalar
+spec:
+ signals:
+ - signal: scalar_in
+ annotations:
+ fmi_variable_vref: 1001
+ fmi_variable_name: scalar_in
+ fmi_variable_type: Real
+ fmi_variable_causality: input
+ - signal: scalar_out
+ annotations:
+ fmi_variable_vref: 1002
+ fmi_variable_name: scalar_out
+ fmi_variable_type: Real
+ fmi_variable_causality: output
+# - signal: scalar_param
+# annotations:
+# fmi_variable_vref: 1003
+# fmi_variable_name: scalar_param
+# fmi_variable_type: Real
+# fmi_variable_causality: parameter
+---
+kind: SignalGroup
+metadata:
+ name: scalar_vector2
+ labels:
+ channel: scalar
+ annotations:
+ vector_type: scalar
+spec:
+ signals:
+ - signal: scalar_in2
+ annotations:
+ fmi_variable_vref: 1004
+ fmi_variable_name: scalar_in2
+ fmi_variable_type: Real
+ fmi_variable_causality: input
+ - signal: scalar_out2
+ annotations:
+ fmi_variable_vref: 1005
+ fmi_variable_name: scalar_out2
+ fmi_variable_type: Real
+ fmi_variable_causality: output
+# - signal: scalar_param
+# annotations:
+# fmi_variable_vref: 1003
+# fmi_variable_name: scalar_param
+# fmi_variable_type: Real
+# fmi_variable_causality: parameter
+---
+kind: SignalGroup
+metadata:
+ name: binary_vector
+ labels:
+ channel: binary
+ type: binary
+ annotations:
+ vector_type: binary
+ vector_name: network_vector
+spec:
+ signals:
+ - signal: can
+ annotations:
+ mime_type: 'application/x-automotive-bus; interface=stream; type=frame; bus=can; schema=fbs; bus_id=1; node_id=2; interface_id=0'
+ #
+ # FMI Value Reference
+ # -------------------
+ fmi_variable_vref: 4
+ fmi_variable_vref_input: 4
+ fmi_variable_vref_output: 5
+ #
+ # FMI LS Text Encoding (applied to FMI String Variables):
+ # --------------------
+ dse.standards.fmi-ls-binary-to-text.encoding: ascii85
+ # (supporting annotations to avoid XML parsing)
+ dse.standards.fmi-ls-binary-to-text.vref: [2, 3, 4, 5, 6, 7, 8, 9]
+ #
+ # FMI LS Bus Topology (applied to FMI String/Binary Variables):
+ # -------------------
+ dse.standards.fmi-ls-bus-topology.bus_id: 1
+ # (supporting annotations to avoid XML parsing)
+ dse.standards.fmi-ls-bus-topology.rx_vref: [2, 4, 6, 8]
+ dse.standards.fmi-ls-bus-topology.tx_vref: [3, 5, 7, 9]
diff --git a/dse/examples/gateway/data/model.yaml b/dse/examples/gateway/data/model.yaml
new file mode 100644
index 0000000..d3c1111
--- /dev/null
+++ b/dse/examples/gateway/data/model.yaml
@@ -0,0 +1,52 @@
+---
+kind: Model
+metadata:
+ name: Gateway
+ annotations:
+ adapter: 'fmi'
+ fmi_version: '2.0'
+ fmi_model_cosim: true
+ fmi_model_version: '1.48'
+ fmi_stepsize: '0.0005'
+ fmi_endtime: '0.002'
+ fmi_guid: '{11111111-2222-3333-4444-555555555555}'
+ fmi_resource_dir: 'data'
+ set_init_values: true
+ loglevel: 4
+spec:
+ runtime:
+ gateway: {}
+ channels:
+ - alias: scalar_vector
+ selectors:
+ channel: scalar
+ - alias: binary_vector
+ selectors:
+ channel: binary
+---
+kind: Model
+metadata:
+ name: Target
+spec:
+ runtime:
+ dynlib:
+ - os: linux
+ arch: amd64
+ path: model/libgatewaytarget.so
+ - os: linux
+ arch: x86
+ path: model/libgatewaytarget.so
+ - os: windows
+ arch: x64
+ path: model/libgatewaytarget.dll
+ - os: windows
+ arch: x86
+ path: model/libgatewaytarget.dll
+ channels:
+ - alias: scalar_vector
+ selectors:
+ side: scalar_vector
+ - alias: binary_vector
+ selectors:
+ channel: binary
+---
diff --git a/dse/examples/gateway/data/stack.yaml b/dse/examples/gateway/data/stack.yaml
new file mode 100644
index 0000000..f2736f1
--- /dev/null
+++ b/dse/examples/gateway/data/stack.yaml
@@ -0,0 +1,48 @@
+---
+kind: Stack
+metadata:
+ name: stack
+spec:
+ connection:
+ transport:
+ redispubsub:
+ uri: redis://localhost:6379
+ timeout: 60
+ models:
+ - name: simbus
+ model:
+ name: simbus
+ channels:
+ - name: scalar
+ expectedModelCount: 2
+ - name: binary
+ expectedModelCount: 2
+ - name: gateway
+ uid: 42
+ model:
+ name: Gateway
+ channels:
+ - name: scalar
+ alias: scalar_vector
+ selectors:
+ channel: scalar
+ - name: binary
+ alias: binary_vector
+ selectors:
+ channel: binary
+ - name: target_inst
+ uid: 43
+ model:
+ name: Target
+ runtime:
+ files:
+ - fmu/resources/fmu.yaml
+ channels:
+ - name: scalar
+ alias: scalar_vector
+ selectors:
+ channel: scalar
+ - name: binary
+ alias: binary_vector
+ selectors:
+ channel: binary
diff --git a/dse/examples/gateway/fmi2/modelDescription.xml b/dse/examples/gateway/fmi2/modelDescription.xml
new file mode 100644
index 0000000..4f9e89b
--- /dev/null
+++ b/dse/examples/gateway/fmi2/modelDescription.xml
@@ -0,0 +1,65 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ application/x-automotive-bus; interface=stream; type=frame; bus=can; schema=fbs; bus_id=1; interface_id=3
+
+
+ ascii85
+
+
+ 1
+
+
+
+
+
+
+
+ ascii85
+
+
+ 1
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/dse/examples/gateway/fmi3/modelDescription.xml b/dse/examples/gateway/fmi3/modelDescription.xml
new file mode 100644
index 0000000..46910a8
--- /dev/null
+++ b/dse/examples/gateway/fmi3/modelDescription.xml
@@ -0,0 +1,50 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ application/x-automotive-bus; interface=stream; type=frame; bus=can; schema=fbs; bus_id=1; interface_id=3
+
+
+ ascii85
+
+
+ 1
+
+
+
+
+
+
+
+ ascii85
+
+
+ 1
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/dse/examples/gateway/model.c b/dse/examples/gateway/model.c
new file mode 100644
index 0000000..8607c3a
--- /dev/null
+++ b/dse/examples/gateway/model.c
@@ -0,0 +1,110 @@
+// Copyright 2024 Robert Bosch GmbH
+//
+// SPDX-License-Identifier: Apache-2.0
+
+#include
+#include
+#include
+#include
+#include
+#include
+
+
+typedef struct {
+ ModelDesc model;
+ /* Network Codec reference. */
+ NCODEC* nc;
+ /* Model Instance values. */
+ char* node_id;
+ uint8_t counter;
+} ExtendedModelDesc;
+
+
+static inline NCODEC* _index(ExtendedModelDesc* m, const char* v, const char* s)
+{
+ ModelSignalIndex idx = signal_index((ModelDesc*)m, v, s);
+ if (idx.binary == NULL) log_fatal("Signal not found (%s:%s)", v, s);
+
+ NCODEC* nc = signal_codec(idx.sv, idx.signal);
+ if (nc == NULL) log_fatal("NCodec object not available (%s:%s)", v, s);
+
+ return nc;
+}
+
+
+static void _adjust_node_id(NCODEC* nc, const char* node_id)
+{
+ ncodec_config(nc, (struct NCodecConfigItem){
+ .name = "node_id",
+ .value = node_id,
+ });
+}
+
+
+ModelDesc* model_create(ModelDesc* model)
+{
+ /* Extend the ModelDesc object (using a shallow copy). */
+ ExtendedModelDesc* m = calloc(1, sizeof(ExtendedModelDesc));
+ memcpy(m, model, sizeof(ModelDesc));
+
+ /* Index the Network Codec. */
+ m->nc = _index(m, "binary_vector", "can");
+
+ /* Configure the Network Codec. */
+ for (int i = 0; i >= 0; i++) {
+ NCodecConfigItem nci = ncodec_stat(m->nc, &i);
+ if (strcmp(nci.name, "node_id") == 0) {
+ m->node_id = strdup(nci.value);
+ break;
+ }
+ }
+ if (m->node_id == NULL) log_fatal("NCodec node_id not configured!");
+
+ /* Return the extended object. */
+ return (ModelDesc*)m;
+}
+
+
+int model_step(ModelDesc* model, double* model_time, double stop_time)
+{
+ ExtendedModelDesc* m = (ExtendedModelDesc*)model;
+
+ /* Scalar signals. */
+ ModelSignalIndex counter =
+ m->model.index((ModelDesc*)m, "scalar_vector", "scalar_out");
+ if (counter.scalar == NULL) return -EINVAL;
+ *(counter.scalar) += 1;
+
+ /* Message RX - spoof the node_id to avoid RX filtering. */
+ _adjust_node_id(m->nc, "49");
+ while (1) {
+ NCodecCanMessage msg = {};
+ int len = ncodec_read(m->nc, &msg);
+ if (len < 0) break;
+ log_notice("Model: RX[%04x] %s", msg.frame_id, msg.buffer);
+ }
+ _adjust_node_id(m->nc, m->node_id);
+
+ /* Message TX. */
+ char msg[100] = "Hello World!";
+ snprintf(msg + strlen(msg), sizeof(msg), " from node_id=%s", m->node_id);
+ struct NCodecCanMessage tx_msg = {
+ .frame_id = ++m->counter + 1000, /* Simple frame_id: 1001, 1002 ... */
+ .frame_type = CAN_EXTENDED_FRAME,
+ .buffer = (uint8_t*)msg,
+ .len = strlen(msg) + 1, /* Capture the NULL terminator. */
+ };
+ ncodec_truncate(m->nc);
+ ncodec_write(m->nc, &tx_msg);
+ ncodec_flush(m->nc);
+
+ *model_time = stop_time;
+ return 0;
+}
+
+
+void model_destroy(ModelDesc* model)
+{
+ ExtendedModelDesc* m = (ExtendedModelDesc*)model;
+ free(m->node_id);
+}
diff --git a/dse/fmigateway/CMakeLists.txt b/dse/fmigateway/CMakeLists.txt
new file mode 100644
index 0000000..bc40870
--- /dev/null
+++ b/dse/fmigateway/CMakeLists.txt
@@ -0,0 +1,170 @@
+# Copyright 2024 Robert Bosch GmbH
+#
+# SPDX-License-Identifier: Apache-2.0
+
+cmake_minimum_required(VERSION 3.21)
+
+# set(CMAKE_VERBOSE_MAKEFILE ON)
+
+set(VERSION "$ENV{PACKAGE_VERSION}")
+set(MODULE "FmiGateway")
+set(MODULE_LC "fmigateway")
+
+project(${MODULE}
+ VERSION ${VERSION}
+ DESCRIPTION "DSE FMI ModelC Gateway"
+ HOMEPAGE_URL "$ENV{PROJECT_URL}"
+)
+
+set(CMAKE_C_STANDARD 99)
+set(CMAKE_C_STANDARD_REQUIRED TRUE)
+set(CMAKE_POSITION_INDEPENDENT_CODE ON)
+set(CMAKE_BUILD_TYPE Debug)
+set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -O3 -ggdb -DLIBXML_STATIC")
+list(APPEND C_CXX_WARNING_FLAGS
+ -Wall
+ -W
+ -Wwrite-strings
+ -Wno-missing-field-initializers
+ -Wno-misleading-indentation
+)
+add_compile_options(${C_CXX_WARNING_FLAGS})
+
+
+set(FMI2_INCLUDE_DIR "${DSE_CLIB_SOURCE_DIR}/clib/fmi/fmi2/headers")
+set(FMI3_INCLUDE_DIR "${DSE_CLIB_SOURCE_DIR}/clib/fmi/fmi3/headers")
+
+
+set(FMU_SOURCE_DIR "../fmu")
+set(FMU_INCLUDE_DIR "../..")
+set(FMI_2_COMMON_FILES
+ ${FMU_SOURCE_DIR}/ascii85.c
+ ${FMU_SOURCE_DIR}/fmi2fmu.c
+ ${FMU_SOURCE_DIR}/signal.c
+)
+set(FMI_3_COMMON_FILES
+ ${FMU_SOURCE_DIR}/ascii85.c
+ ${FMU_SOURCE_DIR}/fmi3fmu.c
+ ${FMU_SOURCE_DIR}/signal.c
+)
+
+
+# Targets
+# =======
+
+# FMI2 Gateway FMU
+# ---------------
+add_library(fmi2gateway SHARED
+ ${FMI_2_COMMON_FILES}
+ fmigateway.c
+ index.c
+ session.c
+ parser.c
+ $<$:session_win32.c>
+)
+target_include_directories(fmi2gateway
+ PRIVATE
+ ${DSE_CLIB_INCLUDE_DIR}
+ ${DSE_MODELC_INCLUDE_DIR}
+ ${DSE_MODELC_SOURCE_INCLUDE_DIR}
+ ${FMI2_INCLUDE_DIR}
+ ${FMU_INCLUDE_DIR}
+)
+target_link_directories(fmi2gateway
+ PRIVATE
+ $<$:${dse_modelc_lib_SOURCE_DIR}/lib>
+ $<$:${dse_modelc_lib_SOURCE_DIR}/bin>
+)
+target_compile_definitions(fmi2gateway
+ PRIVATE
+ PLATFORM_OS="${CDEF_PLATFORM_OS}"
+ PLATFORM_ARCH="${CDEF_PLATFORM_ARCH}"
+)
+target_link_libraries(fmi2gateway
+ PRIVATE
+ yaml
+ xml
+ $<$:bcrypt>
+ $<$:-Wl,--whole-archive,--export-dynamic>
+ $<$:modelc_bundled>
+ $<$:-Wl,--no-whole-archive>
+ $<$:rt>
+ $<$:modelc>
+ $<$:dl>
+
+)
+install(
+ TARGETS
+ fmi2gateway
+ LIBRARY DESTINATION
+ ${MODULE_LC}/lib
+)
+
+# FMI3 Gateway FMU
+# ---------------
+add_library(fmi3gateway SHARED
+ ${FMI_3_COMMON_FILES}
+ fmigateway.c
+ index.c
+ session.c
+ parser.c
+ $<$:session_win32.c>
+)
+target_include_directories(fmi3gateway
+ PRIVATE
+ ${DSE_CLIB_INCLUDE_DIR}
+ ${DSE_MODELC_INCLUDE_DIR}
+ ${DSE_MODELC_SOURCE_INCLUDE_DIR}
+ ${FMI3_INCLUDE_DIR}
+ ${FMU_INCLUDE_DIR}
+)
+target_link_directories(fmi3gateway
+ PRIVATE
+ $<$:${dse_modelc_lib_SOURCE_DIR}/lib>
+ $<$:${dse_modelc_lib_SOURCE_DIR}/bin>
+)
+target_compile_definitions(fmi3gateway
+ PRIVATE
+ PLATFORM_OS="${CDEF_PLATFORM_OS}"
+ PLATFORM_ARCH="${CDEF_PLATFORM_ARCH}"
+)
+target_link_libraries(fmi3gateway
+ PRIVATE
+ yaml
+ xml
+ $<$:bcrypt>
+ $<$:-Wl,--whole-archive,--export-dynamic>
+ $<$:modelc_bundled>
+ $<$:-Wl,--no-whole-archive>
+ $<$:rt>
+ $<$:modelc>
+ $<$:dl>
+
+)
+install(
+ TARGETS
+ fmi3gateway
+ LIBRARY DESTINATION
+ ${MODULE_LC}/lib
+)
+
+# ModelC DLL (windows only)
+# -------------------------
+install(
+ FILES
+ $<$:${dse_modelc_lib_SOURCE_DIR}/bin/libmodelc.dll>
+ DESTINATION
+ ${MODULE_LC}/bin
+)
+
+
+# Package
+# =======
+set(CPACK_SYSTEM_NAME $ENV{PACKAGE_ARCH})
+set(CPACK_PACKAGE_VENDOR "Robert Bosch GmbH")
+set(CPACK_PACKAGE_DESCRIPTION "DSE FMI Gateway")
+set(CPACK_OUTPUT_FILE_PREFIX _dist)
+set(CPACK_GENERATOR "ZIP")
+set(CPACK_PACKAGE_CHECKSUM MD5)
+set(CPACK_MONOLITHIC_INSTALL TRUE)
+include(CPack)
diff --git a/dse/fmigateway/fmigateway.c b/dse/fmigateway/fmigateway.c
new file mode 100644
index 0000000..04c13a0
--- /dev/null
+++ b/dse/fmigateway/fmigateway.c
@@ -0,0 +1,277 @@
+// Copyright 2024 Robert Bosch GmbH
+//
+// SPDX-License-Identifier: Apache-2.0
+
+#include
+#include
+#include
+#include
+
+
+#define UNUSED(x) ((void)x)
+
+
+/**
+fmu_create
+==========
+
+This method allocates the necessary gateway models. The location of the required
+yaml files is set and allocated.
+
+> Required by FMU.
+
+Parameters
+----------
+fmu (FmuInstanceData*)
+: The FMU Descriptor object representing an instance of the FMU Model.
+
+Returns
+-------
+0 (int32_t)
+: The FMU was created correctly.
+*/
+int32_t fmu_create(FmuInstanceData* fmu)
+{
+ assert(fmu);
+ /* Allocate the ModelGatewayDesc object. */
+ FmiGateway* fmi_gw = calloc(1, sizeof(FmiGateway));
+ *fmi_gw = (FmiGateway){
+ .model = calloc(1, sizeof(ModelGatewayDesc)),
+ .settings.yaml_files = calloc(4, sizeof(char*)),
+ };
+
+ /* Allocate a NTL for the required files. */
+ fmi_gw->settings.yaml_files[0] =
+ dse_path_cat(fmu->instance.resource_location, "model.yaml");
+ fmi_gw->settings.yaml_files[1] =
+ dse_path_cat(fmu->instance.resource_location, "fmu.yaml");
+ fmi_gw->settings.yaml_files[2] =
+ dse_path_cat(fmu->instance.resource_location, "stack.yaml");
+
+ fmu->data = (void*)fmi_gw;
+
+ return 0;
+}
+
+
+/**
+fmu_init
+========
+
+In this method the required yaml files are parsed and the session is configured,
+if required. The gateway is set up and connected to the simbus. After a
+sucessfull connection has been established, the fmu variables are indexed to
+their corresponding simbus signals.
+
+> Required by FMU.
+
+Parameters
+----------
+fmu (FmuInstanceData*)
+: The FMU Descriptor object representing an instance of the FMU Model.
+
+Returns
+-------
+0 (int32_t)
+: The FMU was created correctly.
+*/
+int32_t fmu_init(FmuInstanceData* fmu)
+{
+ assert(fmu);
+ FmiGateway* fmi_gw = fmu->data;
+ assert(fmi_gw);
+ ModelGatewayDesc* gw = fmi_gw->model;
+ assert(gw);
+
+ /* Parse the yaml files. */
+ fmigateway_parse(fmu);
+
+ fmigateway_session_configure(fmu);
+
+ /* Setup the Model Gateway object. */
+ fmu_log(fmu, 0, "Debug", "Setting up the Simbus connection...");
+ model_gw_setup(gw, "gateway", fmi_gw->settings.yaml_files,
+ fmi_gw->settings.log_level, fmi_gw->settings.step_size,
+ fmi_gw->settings.end_time);
+ fmu_log(fmu, 0, "Debug", "Connected to the Simbus...");
+
+ fmigateway_index_scalar_signals(
+ fmu, gw, &fmu->variables.scalar.input, &fmu->variables.scalar.output);
+ fmigateway_index_binary_signals(
+ fmu, gw, &fmu->variables.binary.rx, &fmu->variables.binary.tx);
+ fmigateway_index_text_encoding(fmu, gw, &fmu->variables.binary.encode_func,
+ &fmu->variables.binary.decode_func);
+
+ return 0;
+}
+
+
+/**
+fmu_step
+========
+
+This method executes one step of the gateway model and signals are exchanged
+with the other simulation participants.
+
+> Required by FMU.
+
+Parameters
+----------
+fmu (FmuInstanceData*)
+: The FMU Descriptor object representing an instance of the FMU Model.
+communication_point (double)
+: The current model time of the FMU in seconds.
+step_size (double)
+: The step size of the FMU in seconds.
+
+Returns
+-------
+0 (int32_t)
+: The FMU step was performed correctly.
+
+*/
+int32_t fmu_step(
+ FmuInstanceData* fmu, double communication_point, double step_size)
+{
+ assert(fmu);
+ FmiGateway* fmi_gw = fmu->data;
+ assert(fmi_gw);
+ ModelGatewayDesc* gw = fmi_gw->model;
+ assert(gw);
+
+ /* Step the model. */
+ int rc = model_gw_sync(gw, communication_point);
+ if (rc == E_GATEWAYBEHIND) {
+ return 0;
+ }
+
+ /* Save current step for shutdown process. */
+ if (fmi_gw->settings.session) {
+ fmi_gw->settings.session->last_step = communication_point;
+ fmi_gw->settings.step_size = step_size;
+ }
+
+ return 0;
+}
+
+
+/**
+fmu_destroy
+===========
+
+Releases memory and system resources allocated by gateway.
+
+> Required by FMU.
+
+Parameters
+----------
+fmu (FmuInstanceData*)
+: The FMU Descriptor object representing an instance of the FMU Model.
+
+Returns
+-------
+0 (int32_t)
+: The FMU data was released correctly.
+*/
+int32_t fmu_destroy(FmuInstanceData* fmu)
+{
+ assert(fmu);
+ FmiGateway* fmi_gw = fmu->data;
+ assert(fmi_gw);
+ ModelGatewayDesc* gw = fmi_gw->model;
+ assert(gw);
+
+ fmigateway_session_end(fmu);
+
+ /* Disconnect from the simbus. */
+ model_gw_exit(gw);
+
+ /* Cleanup */
+ dse_yaml_destroy_doc_list(fmi_gw->settings.doc_list);
+ for (size_t i = 0; fmi_gw->settings.yaml_files[i]; i++) {
+ free((char*)fmi_gw->settings.yaml_files[i]);
+ }
+ free(fmi_gw->settings.yaml_files);
+ free(gw);
+ free(fmi_gw);
+
+ return 0;
+}
+
+
+/**
+fmu_signals_reset
+=================
+
+Resets the binary signals of the gateway to a length of 0, if the signals have
+not been reseted yet.
+
+> Required by FMU.
+
+Parameters
+----------
+fmu (FmuInstanceData*)
+: The FMU Descriptor object representing an instance of the FMU Model.
+*/
+void fmu_signals_reset(FmuInstanceData* fmu)
+{
+ assert(fmu);
+ FmiGateway* fmi_gw = fmu->data;
+ assert(fmi_gw);
+
+ if (fmi_gw->binary_signals_reset) return;
+
+ for (SignalVector* sv = fmi_gw->model->sv; sv && sv->name; sv++) {
+ if (sv->is_binary == false) continue;
+ sv->length[0] = 0;
+ }
+ fmi_gw->binary_signals_reset = true;
+}
+
+
+/**
+fmu_signals_setup
+=================
+
+Placeholder to signal the FMU to not use the default signal allocation.
+
+Parameters
+----------
+fmu (FmuInstanceData*)
+: The FMU Descriptor object representing an instance of the FMU Model.
+*/
+void fmu_signals_setup(FmuInstanceData* fmu)
+{
+ UNUSED(fmu);
+}
+
+
+static inline int _free_fmu_idx(void* map_item, void* additional_data)
+{
+ UNUSED(additional_data);
+ FmuSignalVectorIndex* fmu_idx = map_item;
+ if (fmu_idx) {
+ free(fmu_idx->sv);
+ }
+ return 0;
+}
+
+
+/**
+fmu_signals_remove
+==================
+
+This method frees the allocated binary signal indexes.
+
+Parameters
+----------
+fmu (FmuInstanceData*)
+: The FMU Descriptor object representing an instance of the FMU Model.
+*/
+void fmu_signals_remove(FmuInstanceData* fmu)
+{
+ assert(fmu);
+ fmu_log(fmu, 0, "Debug", "Removing additional signal data...");
+ hashmap_iterator(&fmu->variables.binary.rx, _free_fmu_idx, false, NULL);
+ hashmap_iterator(&fmu->variables.binary.tx, _free_fmu_idx, false, NULL);
+}
diff --git a/dse/fmigateway/fmigateway.h b/dse/fmigateway/fmigateway.h
new file mode 100644
index 0000000..b13c513
--- /dev/null
+++ b/dse/fmigateway/fmigateway.h
@@ -0,0 +1,106 @@
+// Copyright 2024 Robert Bosch GmbH
+//
+// SPDX-License-Identifier: Apache-2.0
+
+#ifndef DSE_FMIGATEWAY_FMIGATEWAY_H_
+#define DSE_FMIGATEWAY_FMIGATEWAY_H_
+
+#include
+#include
+#include
+
+
+/**
+FMI ModelC Gateway
+==================
+
+
+Component Diagram
+-----------------
+
+
+```
+@startuml fmigateway-component
+
+title FMI Gateway FMU
+
+center footer Dynamic Simulation Environment
+
+@enduml
+```
+
+
+
+![](fmigateway-component.png)
+
+
+Example
+-------
+*/
+
+typedef struct WindowsModel {
+ /* process Information */
+ const char* path;
+ const char* file;
+ bool show_process;
+ /* Model information. */
+ const char* name;
+ double step_size;
+ double end_time;
+ int log_level;
+ const char* yaml;
+ double current_step;
+ double timeout;
+ /* Windows Information. */
+ void* w_process;
+} WindowsModel;
+
+typedef struct FmiGatewaySession {
+ WindowsModel* w_models;
+ struct {
+ const char* path;
+ const char* file;
+ } init;
+ struct {
+ const char* path;
+ const char* file;
+ } shutdown;
+ /* Additional information. */
+ double last_step;
+} FmiGatewaySession;
+
+typedef struct FmiGateway {
+ ModelGatewayDesc* model;
+ struct {
+ YamlDocList* doc_list;
+ const char** yaml_files;
+ double step_size;
+ double end_time;
+ int log_level;
+ const char* log_location;
+ FmiGatewaySession* session;
+ } settings;
+ bool binary_signals_reset;
+} FmiGateway;
+
+/* index.c */
+DLL_PRIVATE void fmigateway_index_scalar_signals(
+ FmuInstanceData* fmu, ModelGatewayDesc* m, HashMap* input, HashMap* output);
+DLL_PRIVATE void fmigateway_index_binary_signals(
+ FmuInstanceData* fmu, ModelGatewayDesc* m, HashMap* rx, HashMap* tx);
+DLL_PRIVATE void fmigateway_index_text_encoding(FmuInstanceData* fmu,
+ ModelGatewayDesc* m, HashMap* encode_func, HashMap* decode_func);
+
+/* parser.c */
+DLL_PRIVATE void fmigateway_parse(FmuInstanceData* fmu);
+
+/* session.c */
+DLL_PRIVATE void fmigateway_session_configure(FmuInstanceData* fmu);
+DLL_PRIVATE void fmigateway_session_end(FmuInstanceData* fmu);
+
+/* session_win32.c */
+DLL_PRIVATE void fmigateway_session_windows_start(FmuInstanceData* fmu);
+DLL_PRIVATE void fmigateway_session_windows_end(FmuInstanceData* fmu);
+
+
+#endif // DSE_FMIGATEWAY_FMIGATEWAY_H_
diff --git a/dse/fmigateway/index.c b/dse/fmigateway/index.c
new file mode 100644
index 0000000..f49e2ea
--- /dev/null
+++ b/dse/fmigateway/index.c
@@ -0,0 +1,244 @@
+// Copyright 2024 Robert Bosch GmbH
+//
+// SPDX-License-Identifier: Apache-2.0
+
+#include
+#include
+#include
+#include
+#include
+
+
+static SchemaSignalObject* __signal_match;
+static const char* __signal_match_name;
+
+
+ChannelSpec* _model_build_channel_spec(
+ ModelInstanceSpec* model_instance, const char* channel_name)
+{
+ const char* selectors[] = { "name" };
+ const char* values[] = { channel_name };
+ YamlNode* c_node = dse_yaml_find_node_in_seq(
+ model_instance->spec, "channels", selectors, values, 1);
+ /* No channel was found with matching name. */
+ if (c_node == NULL) {
+ const char* selectors[] = { "alias" };
+ const char* values[] = { channel_name };
+ c_node = dse_yaml_find_node_in_seq(
+ model_instance->spec, "channels", selectors, values, 1);
+ }
+ /* No channel was found with matching alias. */
+ if (c_node == NULL) {
+ return NULL;
+ }
+
+ ChannelSpec* channel_spec = calloc(1, sizeof(ChannelSpec));
+ channel_spec->name = channel_name;
+ channel_spec->private = c_node;
+ YamlNode* n_node = dse_yaml_find_node(c_node, "name");
+ if (n_node && n_node->scalar) channel_spec->name = n_node->scalar;
+ YamlNode* a_node = dse_yaml_find_node(c_node, "alias");
+ if (a_node && a_node->scalar) channel_spec->alias = a_node->scalar;
+
+ return channel_spec; /* Caller to free. */
+}
+
+
+static int _signal_group_match_handler(
+ ModelInstanceSpec* model_instance, SchemaObject* object)
+{
+ uint32_t index = 0;
+
+ /* Enumerate over the signals. */
+ SchemaSignalObject* so;
+ do {
+ so = schema_object_enumerator(model_instance, object, "spec/signals",
+ &index, schema_signal_object_generator);
+ if (so == NULL) break;
+ if (strcmp(so->signal, __signal_match_name) == 0) {
+ __signal_match = so; /* Caller to free. */
+ return 0;
+ }
+ free(so);
+ } while (1);
+
+ return 0;
+}
+
+
+static const char** _signal_annotation_list(ModelInstanceSpec* mi,
+ SignalVector* sv, const char* signal, const char* name)
+{
+ /* Set the search vars. */
+ __signal_match = NULL;
+ __signal_match_name = signal;
+
+ /* Select and handle the schema objects. */
+ ChannelSpec* cs = _model_build_channel_spec(mi, sv->name);
+ SchemaObjectSelector* selector;
+ selector = schema_build_channel_selector(mi, cs, "SignalGroup");
+ if (selector) {
+ schema_object_search(mi, selector, _signal_group_match_handler);
+ }
+ schema_release_selector(selector);
+ free(cs);
+ /* Look for the annotation. */
+ if (__signal_match) {
+ const char** v = NULL;
+ YamlNode* a_node =
+ dse_yaml_find_node(__signal_match->data, "annotations");
+ if (a_node) {
+ v = dse_yaml_get_array(a_node, name, NULL);
+ }
+ free(__signal_match);
+ __signal_match = NULL;
+ return v;
+ }
+
+ return NULL;
+}
+
+
+void fmigateway_index_scalar_signals(
+ FmuInstanceData* fmu, ModelGatewayDesc* m, HashMap* input, HashMap* output)
+{
+ for (SignalVector* sv = m->sv; sv && sv->name; sv++) {
+ if (sv->is_binary) continue;
+
+ for (uint32_t i = 0; i < sv->count; i++) {
+ /* Value Reference. */
+ const char* vref =
+ signal_annotation(sv, i, "fmi_variable_vref", NULL);
+ if (vref == NULL) continue;
+
+ /* Locate the variable. */
+ ModelSignalIndex idx = signal_index(
+ (ModelDesc*)m->mi->model_desc, sv->alias, sv->signal[i]);
+ if (idx.scalar == NULL) continue;
+
+ /* Index based on causality. */
+ const char* causality =
+ signal_annotation(sv, i, "fmi_variable_causality", NULL);
+ if (strcmp(causality, "output") == 0) {
+ hashmap_set(output, vref, idx.scalar);
+ } else if (strcmp(causality, "input") == 0) {
+ hashmap_set(input, vref, idx.scalar);
+ }
+ }
+ }
+
+ fmu_log(fmu, 0, "Debug", " Scalar: input=%u, output=%u", input->used_nodes,
+ output->used_nodes);
+}
+
+
+static inline void _set_binary_variable(ModelDesc* m, SignalVector* sv,
+ uint32_t index, HashMap* map, const char* vref)
+{
+ /* Locate the variable. */
+ ModelSignalIndex idx = signal_index(m, sv->alias, sv->signal[index]);
+ if (idx.binary == NULL) return;
+
+ FmuSignalVectorIndex* fmu_idx = calloc(1, sizeof(FmuSignalVectorIndex));
+ fmu_idx->sv = calloc(1, sizeof(FmuSignalVector));
+ fmu_idx->sv->binary = idx.binary;
+ fmu_idx->sv->signal = (char**)idx.sv->signal;
+ fmu_idx->sv->length = idx.sv->length;
+ fmu_idx->sv->buffer_size = idx.sv->buffer_size;
+ fmu_idx->vi = idx.signal;
+ hashmap_set_alt(map, vref, fmu_idx);
+}
+
+
+void fmigateway_index_binary_signals(
+ FmuInstanceData* fmu, ModelGatewayDesc* m, HashMap* rx, HashMap* tx)
+{
+ for (ModelInstanceSpec* mi = m->sim->instance_list; mi && mi->name; mi++) {
+ for (SignalVector* sv = mi->model_desc->sv; sv && sv->name; sv++) {
+ if (sv->is_binary == false) continue;
+
+ for (uint32_t i = 0; i < sv->count; i++) {
+ /* Value Reference. */
+ const char* vref =
+ signal_annotation(sv, i, "fmi_variable_vref", NULL);
+ if (vref == NULL) continue;
+
+ /* FMU binary input variables. */
+ /* Index according to bus topology. */
+ const char** rx_list = _signal_annotation_list(sv->mi, sv,
+ sv->signal[i], "dse.standards.fmi-ls-bus-topology.rx_vref");
+ if (rx_list) {
+ for (size_t j = 0; rx_list[j]; j++) {
+ /* Value Reference for the RX variable. */
+ const char* rx_vref = rx_list[j];
+
+ /* Create a FmuSignalVector and add it to the map. */
+ _set_binary_variable(
+ m->mi->model_desc, sv, i, rx, rx_vref);
+ }
+ free(rx_list);
+ }
+
+ /* FMU binary output variables. */
+ /* Index according to bus topology. */
+ const char** tx_list = _signal_annotation_list(sv->mi, sv,
+ sv->signal[i], "dse.standards.fmi-ls-bus-topology.tx_vref");
+ if (tx_list) {
+ for (size_t j = 0; tx_list[j]; j++) {
+ /* Value Reference for the TX variable. */
+ const char* tx_vref = tx_list[j];
+
+ /* Create a FmuSignalVector and add it to the map. */
+ _set_binary_variable(
+ m->mi->model_desc, sv, i, tx, tx_vref);
+ }
+ free(tx_list);
+ }
+ }
+ }
+ }
+
+ fmu_log(fmu, 0, "Debug", " Binary: rx=%u, tx=%u", rx->used_nodes,
+ tx->used_nodes);
+}
+
+
+void fmigateway_index_text_encoding(FmuInstanceData* fmu, ModelGatewayDesc* m,
+ HashMap* encode_func, HashMap* decode_func)
+{
+ for (ModelInstanceSpec* mi = m->sim->instance_list; mi && mi->name; mi++) {
+ for (SignalVector* sv = mi->model_desc->sv; sv && sv->name; sv++) {
+ if (sv->is_binary == false) continue;
+
+ for (uint32_t i = 0; i < sv->count; i++) {
+ /* Value Reference. */
+ const char* _ =
+ signal_annotation(sv, i, "fmi_variable_vref", NULL);
+ if (_ == NULL) continue;
+
+ /* Encoding. */
+ const char* encoding = signal_annotation(sv, i,
+ "dse.standards.fmi-ls-binary-to-text.encoding", NULL);
+ if (strcmp(encoding, "ascii85") != 0) continue;
+
+ /* Index, all with same encoding (for now). */
+ // dse.standards.fmi-ls-binary-to-text.vref: [[2,3,4,5,6,7,8,9]
+ const char** vref_list = _signal_annotation_list(sv->mi, sv,
+ sv->signal[i], "dse.standards.fmi-ls-binary-to-text.vref");
+ if (vref_list) {
+ for (size_t j = 0; vref_list[j]; j++) {
+ /* Value Reference for the RX variable. */
+ const char* vref = vref_list[j];
+
+ /* Encoding. */
+ hashmap_set(encode_func, vref, ascii85_encode);
+ hashmap_set(decode_func, vref, ascii85_decode);
+ }
+ free(vref_list);
+ }
+ }
+ }
+ }
+ fmu_log(fmu, 0, "Debug", " Encoding: enc=%u, dec=%u",
+ encode_func->used_nodes, decode_func->used_nodes);
+}
diff --git a/dse/fmigateway/parser.c b/dse/fmigateway/parser.c
new file mode 100644
index 0000000..770e7b1
--- /dev/null
+++ b/dse/fmigateway/parser.c
@@ -0,0 +1,183 @@
+// Copyright 2024 Robert Bosch GmbH
+//
+// SPDX-License-Identifier: Apache-2.0
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+
+#define UNUSED(x) ((void)x)
+#define ARRAY_SIZE(x) (sizeof(x) / sizeof(x[0]))
+#define MODEL_MAX_TIME 60 * 60 * 10 // 10 minutes
+#define STEP_SIZE 0.0005
+
+
+static inline void* _gwfmu_model_generator(ModelInstanceSpec* mi, void* data)
+{
+ UNUSED(mi);
+ YamlNode* n = dse_yaml_find_node((YamlNode*)data, "model");
+ if (n && n->scalar) {
+ WindowsModel* w_model = calloc(1, sizeof(WindowsModel));
+
+ w_model->name = n->scalar;
+ if (dse_yaml_get_double(
+ data, "annotations/stepsize", &w_model->step_size)) {
+ w_model->step_size = STEP_SIZE;
+ };
+ if (dse_yaml_get_double(
+ data, "annotations/endtime", &w_model->end_time)) {
+ w_model->end_time = MODEL_MAX_TIME;
+ };
+ if (dse_yaml_get_int(
+ data, "annotations/loglevel", &w_model->log_level)) {
+ w_model->log_level = -1;
+ };
+ if (dse_yaml_get_double(
+ data, "annotations/timeout", &w_model->timeout)) {
+ w_model->timeout = 60.0;
+ };
+ dse_yaml_get_string(data, "annotations/path", &w_model->path);
+ dse_yaml_get_string(data, "annotations/file", &w_model->file);
+ dse_yaml_get_string(data, "annotations/yaml", &w_model->yaml);
+ dse_yaml_get_bool(data, "annotations/show", &w_model->show_process);
+
+ log_notice("%s", w_model->name);
+ log_notice(" Path: %s", w_model->path);
+ log_notice(" File: %s", w_model->file);
+ log_notice(" Yaml: %s", w_model->yaml);
+ log_notice(" Stepsize: %lf", w_model->step_size);
+ log_notice(" Endtime: %lf", w_model->end_time);
+ log_notice(" Timout: %lf", w_model->timeout);
+ log_notice(" Loglevel: %d", w_model->log_level);
+ log_notice(" Show: %d", w_model->show_process);
+ return w_model;
+ }
+ return NULL;
+}
+
+
+static inline void _parse_session_script(FmuInstanceData* fmu, YamlNode* node,
+ const char* node_path, const char** path, const char** file)
+{
+ YamlNode* _node = dse_yaml_find_node(node, node_path);
+ if (_node) {
+ if (dse_yaml_get_string(_node, "path", path)) {
+ *path = fmu->instance.resource_location;
+ }
+ dse_yaml_get_string(_node, "file", file);
+ }
+}
+
+
+static inline void _gwfmu_parse_session(ModelInstanceSpec* mi, SchemaObject* o,
+ FmuInstanceData* fmu, YamlNode* s_node)
+{
+ FmiGateway* fmi_gw = fmu->data;
+ assert(fmi_gw);
+
+ fmi_gw->settings.session = calloc(1, sizeof(FmiGatewaySession));
+ FmiGatewaySession* session = fmi_gw->settings.session;
+
+ _parse_session_script(fmu, s_node, "initialization", &session->init.path,
+ &session->init.file);
+ _parse_session_script(fmu, s_node, "shutdown", &session->shutdown.path,
+ &session->shutdown.file);
+
+ YamlNode* w_node = dse_yaml_find_node(s_node, "windows");
+ if (w_node) {
+ /* Enumerate over the models. */
+ uint32_t index = 0;
+ WindowsModel* w_model;
+ HashList m_list;
+ hashlist_init(&m_list, 100);
+ do {
+ w_model = schema_object_enumerator(mi, o,
+ "metadata/annotations/session/windows", &index,
+ _gwfmu_model_generator);
+ if (w_model == NULL) break;
+ if (w_model->name) {
+ hashlist_append(&m_list, w_model);
+ } else {
+ free(w_model);
+ }
+ } while (1);
+ /* Convert to a NTL. */
+ session->w_models = hashlist_ntl(&m_list, sizeof(WindowsModel), true);
+ }
+}
+
+
+static inline int _model_match_handler(ModelInstanceSpec* mi, SchemaObject* o)
+{
+ FmuInstanceData* fmu = o->data;
+ FmiGateway* fmi_gw = fmu->data;
+ assert(fmi_gw);
+
+ /* Get and set runtime parameters. */
+ if (dse_yaml_get_double(o->doc, "metadata/annotations/fmi_stepsize",
+ &(fmi_gw->settings.step_size))) {
+ fmi_gw->settings.step_size = STEP_SIZE;
+ }
+ if (dse_yaml_get_double(o->doc, "metadata/annotations/fmi_endtime",
+ &(fmi_gw->settings.end_time))) {
+ fmi_gw->settings.end_time = MODEL_MAX_TIME;
+ }
+ if (dse_yaml_get_int(o->doc, "metadata/annotations/loglevel",
+ &(fmi_gw->settings.log_level))) {
+ fmi_gw->settings.log_level = 6;
+ }
+ if (dse_yaml_get_string(o->doc, "metadata/annotations/loglocation",
+ &(fmi_gw->settings.log_location))) {
+ fmi_gw->settings.log_location = ".";
+ }
+
+
+ /* Create Session if specified in the doc. */
+ YamlNode* session_node =
+ dse_yaml_find_node(o->doc, "metadata/annotations/session");
+ if (session_node) {
+ _gwfmu_parse_session(mi, o, fmu, session_node);
+ }
+
+ return 0;
+}
+
+
+/**
+fmigateway_parse
+================
+
+This method loads the required yaml files from the resource location of the fmu.
+The loaded yaml files are parsed into the fmu descriptor object.
+
+Parameters
+----------
+fmu (FmuInstanceData*)
+: The FMU Descriptor object representing an instance of the FMU Model.
+*/
+void fmigateway_parse(FmuInstanceData* fmu)
+{
+ FmiGateway* fmi_gw = fmu->data;
+ assert(fmi_gw);
+
+ /* Load yaml files. */
+ for (size_t i = 0; fmi_gw->settings.yaml_files[i]; i++) {
+ fmi_gw->settings.doc_list = dse_yaml_load_file(
+ fmi_gw->settings.yaml_files[i], fmi_gw->settings.doc_list);
+ }
+
+ /* Parse the FMU Model. */
+ SchemaObjectSelector m_sel = {
+ .kind = "Model",
+ .name = "Gateway",
+ .data = fmu,
+ };
+ ModelInstanceSpec mi = { .yaml_doc_list = fmi_gw->settings.doc_list };
+ schema_object_search(&mi, &m_sel, _model_match_handler);
+}
diff --git a/dse/fmigateway/session.c b/dse/fmigateway/session.c
new file mode 100644
index 0000000..a6d127b
--- /dev/null
+++ b/dse/fmigateway/session.c
@@ -0,0 +1,86 @@
+// Copyright 2024 Robert Bosch GmbH
+//
+// SPDX-License-Identifier: Apache-2.0
+
+#include
+#include
+
+
+#define UNUSED(x) ((void)x)
+
+
+/**
+fmigateway_session_configure
+============================
+
+If session parameters were parsed from the model description, this method
+configures and starts the additional models, or executes the given command.
+
+Parameters
+----------
+fmu (FmuInstanceData*)
+: The FMU Descriptor object representing an instance of the FMU Model.
+*/
+void fmigateway_session_configure(FmuInstanceData* fmu)
+{
+ FmiGateway* fmi_gw = fmu->data;
+
+ FmiGatewaySession* session = fmi_gw->settings.session;
+ if (session == NULL) return;
+
+ if (session->init.file) {
+ char buf[256];
+ snprintf(
+ buf, sizeof(buf), "%s%s", session->init.path, session->init.file);
+ system(buf);
+ }
+
+ if (session->w_models && strcmp(PLATFORM_OS, "windows") == 0) {
+ fmigateway_session_windows_start(fmu);
+ }
+}
+
+
+/**
+fmigateway_session_end
+======================
+
+If session parameters were parsed from the model description, this method
+shuts down the additional models, or executes the given command.
+
+Parameters
+----------
+fmu (FmuInstanceData*)
+: The FMU Descriptor object representing an instance of the FMU Model.
+*/
+void fmigateway_session_end(FmuInstanceData* fmu)
+{
+ FmiGateway* fmi_gw = fmu->data;
+
+ FmiGatewaySession* session = fmi_gw->settings.session;
+ if (session == NULL) return;
+
+ if (session->shutdown.file) {
+ char buf[256];
+ snprintf(buf, sizeof(buf), "%s%s", session->shutdown.path,
+ session->shutdown.file);
+ system(buf);
+ }
+
+ if (session->w_models && strcmp(PLATFORM_OS, "windows") == 0) {
+ fmigateway_session_windows_end(fmu);
+ }
+}
+
+
+__attribute__((weak)) void fmigateway_session_windows_start(
+ FmuInstanceData* fmu)
+{
+ UNUSED(fmu);
+}
+
+
+__attribute__((weak)) void fmigateway_session_windows_end(FmuInstanceData* fmu)
+{
+ UNUSED(fmu);
+}
diff --git a/dse/fmigateway/session_win32.c b/dse/fmigateway/session_win32.c
new file mode 100644
index 0000000..1df906e
--- /dev/null
+++ b/dse/fmigateway/session_win32.c
@@ -0,0 +1,392 @@
+// Copyright 2024 Robert Bosch GmbH
+//
+// SPDX-License-Identifier: Apache-2.0
+
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+
+#define MODEL_MAX_TIME 60 * 60 * 10 // 10 minutes
+
+
+typedef struct WindowsProcess {
+ STARTUPINFO s_info;
+ PROCESS_INFORMATION p_info;
+} WindowsProcess;
+
+
+/*
+_build_cmd
+==========
+
+Build the command for the modelC process from the yaml parameters.
+Endtime and stepsize are optional.
+
+Parameters
+----------
+w_model (WindowsModel)
+: Model Descriptor containing parameter information.
+
+Returns
+-------
+string
+: string containing the cmd to start a windows model
+*/
+static inline char* _build_cmd(WindowsModel* w_model, const char* path)
+{
+ char cmd[1024];
+ size_t max_len = sizeof cmd;
+ int offset =
+ snprintf(cmd, max_len, "cmd /C cd %s && %s", path, w_model->file);
+ offset += snprintf(cmd + offset, max_len, " --name %s", w_model->name);
+ offset +=
+ snprintf(cmd + offset, max_len, " --endtime %lf", w_model->end_time);
+ offset +=
+ snprintf(cmd + offset, max_len, " --stepsize %lf", w_model->step_size);
+ offset +=
+ snprintf(cmd + offset, max_len, " --logger %d", w_model->log_level);
+ offset +=
+ snprintf(cmd + offset, max_len, " --timeout %lf", w_model->timeout);
+ if (w_model->yaml) {
+ offset += snprintf(cmd + offset, max_len, " %s", w_model->yaml);
+ }
+ return strdup(cmd);
+}
+
+
+/*
+_create_file
+============
+
+Creates a file on a windows operating system.
+
+Parameters
+----------
+name (char*)
+: name of the file to be created.
+
+Returns
+-------
+HANDLE
+: open handle to the specified file
+*/
+static inline HANDLE _create_file(char* name)
+{
+ SECURITY_ATTRIBUTES sa;
+ sa.nLength = sizeof(sa);
+ sa.lpSecurityDescriptor = NULL;
+ sa.bInheritHandle = TRUE;
+
+ return CreateFile(name, FILE_WRITE_DATA, FILE_SHARE_WRITE | FILE_SHARE_READ,
+ &sa, // default security
+ CREATE_ALWAYS, // create new file always
+ FILE_ATTRIBUTE_NORMAL, // normal file
+ NULL // attribute template
+ );
+}
+
+
+/*
+_terminate_process
+==================
+
+Terminate a windows process.
+
+Parameters
+----------
+w_model (WindowsModel)
+: Model Descriptor containing parameter information.
+*/
+static inline void _terminate_process(WindowsModel* w_model)
+{
+ WindowsProcess* w_process = w_model->w_process;
+
+ TerminateProcess(w_process->p_info.hProcess, 0);
+ CloseHandle(w_process->p_info.hProcess);
+ CloseHandle(w_process->p_info.hThread);
+ free(w_process);
+}
+
+
+/*
+_gracefully_termiante_process
+=============================
+
+Gracefully terminate a windows process by sending a Ctrl C, Sigint
+Signal to the process.
+
+Parameters
+----------
+w_model (WindowsModel)
+: Model Descriptor containing parameter information.
+*/
+static inline void _gracefully_termiante_process(WindowsModel* w_model)
+{
+ WindowsProcess* w_process = w_model->w_process;
+ if (w_model->end_time != MODEL_MAX_TIME) return;
+
+ /* Sequence of functions to attach to the running models and sent
+ a SIGINT */
+ FreeConsole();
+ AttachConsole(w_process->p_info.dwProcessId);
+ SetConsoleCtrlHandler(NULL, true);
+ GenerateConsoleCtrlEvent(CTRL_BREAK_EVENT, w_process->p_info.dwProcessId);
+ FreeConsole();
+
+ /* wait for the processes to process the SIGINT and reattach to
+ host. */
+ Sleep(1000);
+ SetConsoleCtrlHandler(NULL, false);
+ AttachConsole(-1);
+}
+
+
+/*
+_start_redis
+============
+
+Create and start a new redis process.
+
+Parameters
+----------
+w_model (WindowsModel)
+: Model Descriptor containing parameter information.
+
+w_process (WindowsProcess)
+: Process Descriptor, references various data.
+
+fmu (FmuInstanceData*)
+: The FMU Descriptor object representing an instance of the FMU Model.
+*/
+static inline void _start_redis(
+ WindowsModel* w_model, WindowsProcess* w_process, FmuInstanceData* fmu)
+{
+ /* Redis Server. */
+ char* file_path =
+ dse_path_cat(fmu->instance.resource_location, w_model->file);
+
+ if (!CreateProcess(file_path, NULL, NULL, NULL, FALSE,
+ CREATE_NEW_CONSOLE | CREATE_NEW_PROCESS_GROUP, NULL, NULL,
+ &(w_process->s_info), &(w_process->p_info))) {
+ log_fatal("Could not start Redis-Server.exe");
+ }
+ free(file_path);
+}
+
+
+/*
+_start_model
+============
+
+Create and start a new modelC process.
+
+Parameters
+----------
+w_model (WindowsModel)
+: Model Descriptor containing parameter information.
+
+w_process (WindowsProcess)
+: Process Descriptor, references various data.
+
+fmu (FmuInstanceData*)
+: The FMU Descriptor object representing an instance of the FMU Model.
+*/
+static inline void _start_model(
+ WindowsModel* w_model, WindowsProcess* w_process, FmuInstanceData* fmu)
+{
+ FmiGateway* fmi_gw = fmu->data;
+ char* cmd = _build_cmd(w_model, fmu->instance.resource_location);
+
+ HANDLE _log;
+ if (w_model->log_level >= 0) {
+ /* Create logfile for modelC models. */
+ char log[128];
+ snprintf(log, sizeof(log), "%s/%s_log.txt",
+ fmi_gw->settings.log_location, w_model->name);
+ _log = _create_file(log);
+ if (_log) {
+ w_process->s_info.hStdInput = NULL;
+ w_process->s_info.hStdError = _log;
+ w_process->s_info.hStdOutput = _log;
+ }
+ }
+
+ /* ModelC models. */
+ if (!CreateProcess(NULL, cmd, NULL, NULL, ((_log) ? TRUE : FALSE),
+ CREATE_NEW_CONSOLE | CREATE_NEW_PROCESS_GROUP, NULL, NULL,
+ &(w_process->s_info), &(w_process->p_info))) {
+ log_fatal(
+ "Could not start %s (error %d)", w_model->name, GetLastError());
+ }
+ free(cmd);
+}
+
+
+/*
+_start_process
+==============
+
+Start a modelC windows process based on information from
+a yaml parameter configuration.
+
+Parameters
+----------
+w_model (WindowsModel)
+: Model Descriptor containing parameter information.
+
+fmu (FmuInstanceData*)
+: The FMU Descriptor object representing an instance of the FMU Model.
+*/
+static inline void _start_process(WindowsModel* w_model, FmuInstanceData* fmu)
+{
+ WindowsProcess* w_process = w_model->w_process;
+
+ log_notice("Starting process: %s", w_model->name);
+
+ /* Initialize the process handles and information for
+ the windows processes. */
+ ZeroMemory(&(w_process->s_info), sizeof(STARTUPINFO));
+ ZeroMemory(&(w_process->p_info), sizeof(PROCESS_INFORMATION));
+
+ w_process->s_info.cb = sizeof(STARTUPINFO);
+ w_process->s_info.dwFlags = (STARTF_USESTDHANDLES);
+
+ /* Display a terminal window. */
+ if (w_model->show_process) {
+ w_process->s_info.lpTitle = (char*)w_model->name;
+ } else {
+ w_process->s_info.dwFlags =
+ (STARTF_USESTDHANDLES | STARTF_USESHOWWINDOW);
+ w_process->s_info.wShowWindow = SW_HIDE;
+ }
+
+ /* Redis requires a different startup. */
+ if (strcasecmp(w_model->name, "redis") == 0) {
+ _start_redis(w_model, w_process, fmu);
+ return;
+ }
+ _start_model(w_model, w_process, fmu);
+}
+
+
+/*
+_check_shutdown
+===============
+
+Observe a process and check if it is terminated.
+
+Parameters
+----------
+w_model (WindowsModel)
+: Model Descriptor containing parameter information.
+
+sec (integer)
+: Time in seconds.
+*/
+inline static int32_t _check_shutdown(WindowsModel* w_model, int sec)
+{
+ WindowsProcess* w_process = w_model->w_process;
+ DWORD result = WaitForSingleObject(w_process->p_info.hProcess, sec * 1000);
+ if (result == WAIT_OBJECT_0) {
+ log_notice("%s is shut down.", w_model->name);
+ } else {
+ log_error("%s is still active.", w_model->name);
+ return -1;
+ }
+ /* Close the model handle. */
+ CloseHandle(w_process->p_info.hProcess);
+ CloseHandle(w_process->p_info.hThread);
+ free(w_model->w_process);
+ return 0;
+}
+
+
+/**
+fmigateway_session_windows_start
+================================
+
+Creates windows processes based on the parameters
+configured in a yaml file. Process informations are
+stored for later termination.
+
+Parameters
+----------
+fmu (FmuInstanceData*)
+: The FMU Descriptor object representing an instance of the FMU Model.
+ */
+void fmigateway_session_windows_start(FmuInstanceData* fmu)
+{
+ FmiGateway* fmi_gw = fmu->data;
+ FmiGatewaySession* session = fmi_gw->settings.session;
+
+ for (WindowsModel* w_m = session->w_models; w_m && w_m->name; w_m++) {
+ w_m->w_process = calloc(1, sizeof(WindowsProcess));
+ _start_process(w_m, fmu);
+ }
+}
+
+
+/**
+fmigateway_session_windows_end
+==============================
+
+Termiantes all previously started windows processes.
+After sending the termination signals, one additionally
+step is made by the gateway to close the simulation.
+
+Parameters
+----------
+fmu (FmuInstanceData*)
+: The FMU Descriptor object representing an instance of the FMU Model.
+ */
+void fmigateway_session_windows_end(FmuInstanceData* fmu)
+{
+ FmiGateway* fmi_gw = fmu->data;
+ FmiGatewaySession* session = fmi_gw->settings.session;
+ ModelGatewayDesc* gw = fmi_gw->model;
+
+ WindowsModel* redis = NULL;
+ WindowsModel* simbus = NULL;
+
+ for (WindowsModel* w_m = session->w_models; w_m && w_m->name; w_m++) {
+ if (strcasecmp(w_m->name, "simbus") == 0) {
+ simbus = w_m;
+ continue;
+ } else if (strcasecmp(w_m->name, "redis") == 0) {
+ redis = w_m;
+ continue;
+ }
+ _gracefully_termiante_process(w_m);
+ }
+
+ model_gw_sync(
+ gw, session->last_step + (fmi_gw->settings.step_size * 1.001));
+ fmu_log(fmu, 0, "Debug", "Extra step for shutting down models finished...");
+
+ model_gw_exit(gw);
+ fmu_log(fmu, 0, "Debug", "Gateway exited...");
+
+ /* Loop through processes and confirm that all are closed. */
+ for (WindowsModel* w_m = session->w_models; w_m && w_m->name; w_m++) {
+ if (strcasecmp(w_m->name, "redis") == 0 ||
+ strcasecmp(w_m->name, "simbus") == 0) {
+ continue;
+ }
+ _check_shutdown(w_m, 10);
+ }
+
+ if (simbus) {
+ if (_check_shutdown(simbus, 10) < 0) {
+ _gracefully_termiante_process(simbus);
+ }
+ }
+ if (redis) _terminate_process(redis);
+}
diff --git a/dse/fmimodelc/examples/network_fmu/fmi2importer.c b/dse/fmimodelc/examples/network_fmu/fmi2importer.c
index 9b75968..78a1572 100644
--- a/dse/fmimodelc/examples/network_fmu/fmi2importer.c
+++ b/dse/fmimodelc/examples/network_fmu/fmi2importer.c
@@ -93,7 +93,7 @@ int main(int argc, char** argv)
fmi2Instantiate instantiate = dlsym(handle, "fmi2Instantiate");
if (instantiate == NULL) return EINVAL;
fmu = instantiate("network_fmu", fmi2CoSimulation, "guid", "resources",
- NULL, true, false);
+ NULL, true, true);
if (fmu == NULL) return EINVAL;
/* fmi2ExitInitializationMode */
diff --git a/dse/fmimodelc/examples/network_fmu/modelDescription.xml b/dse/fmimodelc/examples/network_fmu/modelDescription.xml
index 74d2a44..5404c21 100644
--- a/dse/fmimodelc/examples/network_fmu/modelDescription.xml
+++ b/dse/fmimodelc/examples/network_fmu/modelDescription.xml
@@ -1,36 +1,54 @@
-
-
-
-
- ...
- ...
-
-
-
-
- application/x-automotive-bus; interface=stream; type=frame; bus=can; schema=fbs; bus_id=1; interface_id=3
-
-
- ascii85
-
-
- 1
-
-
-
-
-
-
-
- ascii85
-
-
- 1
-
-
-
- ...
- ...
- ...
- ...
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ application/x-automotive-bus; interface=stream; type=frame; bus=can; schema=fbs; bus_id=1; interface_id=3
+
+
+ ascii85
+
+
+ 1
+
+
+
+
+
+
+
+ ascii85
+
+
+ 1
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/dse/fmu/fmi2fmu.c b/dse/fmu/fmi2fmu.c
index 155152e..2c61825 100644
--- a/dse/fmu/fmi2fmu.c
+++ b/dse/fmu/fmi2fmu.c
@@ -20,45 +20,60 @@
#define VREF_KEY_LEN (10 + 1)
+/**
+default_log
+===========
+
+Default logging function in case the FMU caller does not provide any logger.
+*/
void default_log(fmi2ComponentEnvironment componentEnvironment,
fmi2String instanceName, fmi2Status status, fmi2String category,
fmi2String message, ...)
{
UNUSED(componentEnvironment);
+ UNUSED(instanceName);
static const char* statusString[] = { "OK", "Warning", "Discard", "Error",
"Fatal", "Pending" };
- printf("[%s] %s: ", category, instanceName);
- va_list args;
- va_start(args, message);
- vprintf(message, args);
- va_end(args);
- printf(" (%s)\n", statusString[status]);
+ printf("[%s:%s] %s\n", category, statusString[status], message);
fflush(stdout);
}
-static void _log(const char* format, ...)
+
+void fmu_log(FmuInstanceData* fmu, const int status, const char* category,
+ const char* message, ...)
{
- printf("FMU: ");
+ if (fmu->instance.log_enabled == fmi2False) return;
+
va_list args;
- va_start(args, format);
- vprintf(format, args);
+ va_start(args, message);
+ char format[1024];
+ vsnprintf(format, sizeof(format), message, args);
va_end(args);
- printf("\n");
- fflush(stdout);
+
+ ((void (*)())fmu->instance.logger)(
+ NULL, fmu->instance.name, status, category, format);
}
-static void _log_binary_signal(FmuSignalVectorIndex* idx)
+
+
+static void _log_binary_signal(FmuInstanceData* fmu, FmuSignalVectorIndex* idx)
{
if (idx == NULL || idx->sv->binary == NULL) return;
uint32_t index = idx->vi;
- _log(" - name : %s", idx->sv->signal[index]);
- _log(" length : %d", idx->sv->length[index]);
- _log(" buffer len : %d", idx->sv->buffer_size[index]);
+
+ fmu_log(fmu, fmi2OK, "Debug",
+ "\n - name : %s"
+ "\n length : %d"
+ "\n buffer len : %d",
+ idx->sv->signal[index], idx->sv->length[index],
+ idx->sv->buffer_size[index]);
+
uint8_t* buffer = *idx->sv->binary;
for (uint32_t j = 0; j + 16 < idx->sv->length[index]; j += 16) {
- _log(" %02x %02x %02x %02x %02x %02x %02x %02x "
- "%02x %02x %02x %02x %02x %02x %02x %02x",
+ fmu_log(fmu, fmi2OK, "Debug",
+ "\n %02x %02x %02x %02x %02x %02x %02x %02x "
+ "%02x %02x %02x %02x %02x %02x %02x %02x",
buffer[j + 0], buffer[j + 1], buffer[j + 2], buffer[j + 3],
buffer[j + 4], buffer[j + 5], buffer[j + 6], buffer[j + 7],
buffer[j + 8], buffer[j + 9], buffer[j + 10], buffer[j + 11],
@@ -91,7 +106,6 @@ fmi2Component fmi2Instantiate(fmi2String instance_name, fmi2Type fmu_type,
UNUSED(visible);
/* Create the FMU Model Instance Data. */
- _log("Create the FMU Model Instance Data");
FmuInstanceData* fmu = calloc(1, sizeof(FmuInstanceData));
fmu->instance.name = strdup(instance_name);
fmu->instance.type = fmu_type;
@@ -111,8 +125,7 @@ fmi2Component fmi2Instantiate(fmi2String instance_name, fmi2Type fmu_type,
} else {
fmu->instance.logger = default_log;
}
- ((void (*)())fmu->instance.logger)(
- NULL, fmu->instance.name, fmi2OK, "Debug", "FMU Model instantiated");
+ fmu_log(fmu, fmi2OK, "Debug", "FMU Model instantiated");
/**
* Calculate the offset needed to trim/correct the resource location.
@@ -130,11 +143,11 @@ fmi2Component fmi2Instantiate(fmi2String instance_name, fmi2Type fmu_type,
resource_path_offset = strlen(FILE_URI_SHORT_SCHEME);
}
fmu->instance.resource_location += resource_path_offset;
- ((void (*)())fmu->instance.logger)(NULL, fmu->instance.name, fmi2OK,
- "Debug", "Resource location: %s", fmu->instance.resource_location);
- ((void (*)())fmu->instance.logger)(
- NULL, fmu->instance.name, fmi2OK, "Debug", "Build indexes...");
+ fmu_log(fmu, fmi2OK, "Debug", "Resource location: %s",
+ fmu->instance.resource_location);
+
+ fmu_log(fmu, fmi2OK, "Debug", "Build indexes...");
hashmap_init(&fmu->variables.scalar.input);
hashmap_init(&fmu->variables.scalar.output);
hashmap_init(&fmu->variables.binary.rx);
@@ -150,8 +163,9 @@ fmi2Component fmi2Instantiate(fmi2String instance_name, fmi2Type fmu_type,
hashlist_init(&fmu->variables.binary.free_list, 1024);
/* Specialised Model. */
- fmu_create(fmu);
-
+ if (fmu_create(fmu) != fmi2OK) {
+ fmu_log(fmu, fmi2Error, "Error", "The FMU was not created correctly!");
+ }
/* Return the created instance object. */
return (fmi2Component)fmu;
}
@@ -182,7 +196,7 @@ fmi2Status fmi2ExitInitializationMode(fmi2Component c)
assert(c);
FmuInstanceData* fmu = (FmuInstanceData*)c;
- int rc = fmu_init(fmu);
+ int32_t rc = fmu_init(fmu);
return (rc == 0 ? fmi2OK : fmi2Error);
}
@@ -282,7 +296,7 @@ fmi2Status fmi2GetString(fmi2Component c, const fmi2ValueReference vr[],
if (data == NULL || data_len == 0) continue;
/* Write the requested string, encode if configured. */
- _log_binary_signal(idx);
+ _log_binary_signal(fmu, idx);
EncodeFunc ef = hashmap_get(&fmu->variables.binary.encode_func, vr_idx);
if (ef) {
value[i] = ef((char*)data, data_len);
@@ -406,7 +420,7 @@ fmi2Status fmi2SetString(fmi2Component c, const fmi2ValueReference vr[],
/* Append the binary string to the Binary Signal. */
dse_buffer_append(&idx->sv->binary[idx->vi], &idx->sv->length[idx->vi],
&idx->sv->buffer_size[idx->vi], (void*)data, data_len);
- _log_binary_signal(idx);
+ _log_binary_signal(fmu, idx);
/* Release the decode string/memory. Caller owns value[]. */
if (data != value[i]) free(data);
@@ -461,7 +475,8 @@ fmi2Status fmi2DoStep(fmi2Component c, fmi2Real currentCommunicationPoint,
if (fmu->variables.vtable.reset) fmu->variables.vtable.reset(fmu);
/* Step the model. */
- int rc = fmu_step(fmu, currentCommunicationPoint, communicationStepSize);
+ int32_t rc =
+ fmu_step(fmu, currentCommunicationPoint, communicationStepSize);
/* Reset the binary signal reset mechanism. */
fmu->variables.signals_reset = false;
@@ -487,15 +502,13 @@ void fmi2FreeInstance(fmi2Component c)
assert(c);
FmuInstanceData* fmu = (FmuInstanceData*)c;
- if (fmu_destroy(fmu) < 0) {
- ((void (*)())fmu->instance.logger)(NULL, fmu->instance.name, fmi2OK,
- "Error", "Could not release model");
+ if (fmu_destroy(fmu) < fmi2OK) {
+ fmu_log(fmu, fmi2Error, "Error", "Could not release model");
}
if (fmu->variables.vtable.remove) fmu->variables.vtable.remove(fmu);
- ((void (*)())fmu->instance.logger)(
- NULL, fmu->instance.name, fmi2OK, "Debug", "Destroy the index");
+ fmu_log(fmu, fmi2OK, "Debug", "Destroy the index");
hashmap_destroy(&fmu->variables.scalar.input);
hashmap_destroy(&fmu->variables.scalar.output);
hashmap_destroy(&fmu->variables.binary.rx);
@@ -504,8 +517,7 @@ void fmi2FreeInstance(fmi2Component c)
hashmap_destroy(&fmu->variables.binary.decode_func);
hashlist_destroy(&fmu->variables.binary.free_list);
- ((void (*)())fmu->instance.logger)(NULL, fmu->instance.name, fmi2OK,
- "Debug", "Release FMI instance resources");
+ fmu_log(fmu, fmi2OK, "Debug", "Release FMI instance resources");
free(fmu->instance.name);
free(fmu->instance.guid);
free(fmu->instance.save_resource_location);
diff --git a/dse/fmu/fmi3fmu.c b/dse/fmu/fmi3fmu.c
index 6952d81..9385056 100644
--- a/dse/fmu/fmi3fmu.c
+++ b/dse/fmu/fmi3fmu.c
@@ -1,4 +1,4 @@
-// Copyright 2023 Robert Bosch GmbH
+// Copyright 2024 Robert Bosch GmbH
//
// SPDX-License-Identifier: Apache-2.0
@@ -7,10 +7,10 @@
#include
#include
#include
-#include
#include
#include
#include
+#include
#include
#include
#include
@@ -20,17 +20,12 @@
#define VREF_KEY_LEN (10 + 1)
-static void _log(const char* format, ...)
-{
- printf("FmiGateway: ");
- va_list args;
- va_start(args, format);
- vprintf(format, args);
- va_end(args);
- printf("\n");
- fflush(stdout);
-}
+/**
+default_log
+===========
+Default logging function in case the FMU caller does not provide any logger.
+*/
void default_log(fmi3InstanceEnvironment instanceEnvironment, fmi3Status status,
fmi3String category, fmi3String message)
{
@@ -44,6 +39,45 @@ void default_log(fmi3InstanceEnvironment instanceEnvironment, fmi3Status status,
}
+void fmu_log(FmuInstanceData* fmu, const int status, const char* category,
+ const char* message, ...)
+{
+ if (fmu->instance.log_enabled == fmi3False) return;
+
+ va_list args;
+ va_start(args, message);
+ char format[1024];
+ vsnprintf(format, sizeof(format), message, args);
+ va_end(args);
+
+ ((void (*)())fmu->instance.logger)(NULL, status, category, format);
+}
+
+
+static void _log_binary_signal(FmuInstanceData* fmu, FmuSignalVectorIndex* idx)
+{
+ if (idx == NULL || idx->sv->binary == NULL) return;
+ uint32_t index = idx->vi;
+
+ fmu_log(fmu, fmi3OK, "Debug",
+ "\n - name : %s"
+ "\n length : %d"
+ "\n buffer len : %d",
+ idx->sv->signal[index], idx->sv->length[index],
+ idx->sv->buffer_size[index]);
+
+ uint8_t* buffer = *idx->sv->binary;
+ for (uint32_t j = 0; j + 16 < idx->sv->length[index]; j += 16) {
+ fmu_log(fmu, fmi3OK, "Debug",
+ "\n %02x %02x %02x %02x %02x %02x %02x %02x "
+ "%02x %02x %02x %02x %02x %02x %02x %02x",
+ buffer[j + 0], buffer[j + 1], buffer[j + 2], buffer[j + 3],
+ buffer[j + 4], buffer[j + 5], buffer[j + 6], buffer[j + 7],
+ buffer[j + 8], buffer[j + 9], buffer[j + 10], buffer[j + 11],
+ buffer[j + 12], buffer[j + 13], buffer[j + 14], buffer[j + 15]);
+ }
+}
+
/* Inquire version numbers and setting logging status */
const char* fmi3GetVersion()
@@ -111,8 +145,7 @@ fmi3Instance fmi3InstantiateCoSimulation(fmi3String instanceName,
} else {
fmu->instance.logger = default_log;
}
- ((void (*)())fmu->instance.logger)(
- NULL, fmi3OK, "fmi3Instantiate", "FMU Model instantiated");
+ fmu_log(fmu, fmi3OK, "Debug", "FMU Model instantiated");
/**
* Calculate the offset needed to trim/correct the resource location.
@@ -130,8 +163,11 @@ fmi3Instance fmi3InstantiateCoSimulation(fmi3String instanceName,
resource_path_offset = strlen(FILE_URI_SHORT_SCHEME);
}
fmu->instance.resource_location += resource_path_offset;
- _log("Resource location: %s", fmu->instance.resource_location);
+ fmu_log(fmu, fmi3OK, "Debug", "Resource location: %s",
+ fmu->instance.resource_location);
+
+ fmu_log(fmu, fmi3OK, "Debug", "Build indexes...");
hashmap_init(&fmu->variables.scalar.input);
hashmap_init(&fmu->variables.scalar.output);
hashmap_init(&fmu->variables.binary.rx);
@@ -147,7 +183,9 @@ fmi3Instance fmi3InstantiateCoSimulation(fmi3String instanceName,
hashlist_init(&fmu->variables.binary.free_list, 1024);
/* Specialised Model. */
- fmu_create(fmu);
+ if (fmu_create(fmu) != fmi3OK) {
+ fmu_log(fmu, fmi3Error, "Error", "The FMU was not created correctly!");
+ }
return (fmi3Instance)fmu;
}
@@ -178,13 +216,14 @@ void fmi3FreeInstance(fmi3Instance instance)
assert(instance);
FmuInstanceData* fmu = (FmuInstanceData*)instance;
- if (fmu_destroy(fmu) < 0) {
- _log("Error while releasing the allocated specialised model.");
+ if (fmu_destroy(fmu) < fmi3OK) {
+ fmu_log(fmu, fmi3Error, "Error",
+ "Error while releasing the allocated specialised model.");
}
if (fmu->variables.vtable.remove) fmu->variables.vtable.remove(fmu);
- _log("Destroy the index");
+ fmu_log(fmu, fmi3OK, "Ok", "Destroy the index");
hashmap_destroy(&fmu->variables.scalar.input);
hashmap_destroy(&fmu->variables.scalar.output);
hashmap_destroy(&fmu->variables.binary.rx);
@@ -193,7 +232,7 @@ void fmi3FreeInstance(fmi3Instance instance)
hashmap_destroy(&fmu->variables.binary.decode_func);
hashlist_destroy(&fmu->variables.binary.free_list);
- _log("Release FMI instance resources");
+ fmu_log(fmu, fmi3OK, "Ok", "Release FMI instance resources");
free(fmu->instance.name);
free(fmu->instance.guid);
free(fmu->instance.save_resource_location);
@@ -221,7 +260,7 @@ fmi3Status fmi3ExitInitializationMode(fmi3Instance instance)
assert(instance);
FmuInstanceData* fmu = (FmuInstanceData*)instance;
- int rc = fmu_init(fmu);
+ int32_t rc = fmu_init(fmu);
return (rc == 0 ? fmi3OK : fmi3Error);
}
@@ -449,6 +488,7 @@ fmi3Status fmi3GetBinary(fmi3Instance instance,
if (data == NULL || data_len == 0) continue;
/* Write the requested string, encode if configured. */
+ _log_binary_signal(fmu, idx);
EncodeFunc ef = hashmap_get(&fmu->variables.binary.encode_func, vr_idx);
if (ef) {
values[i] = (fmi3Binary)ef((char*)data, data_len);
@@ -673,6 +713,7 @@ fmi3Status fmi3SetBinary(fmi3Instance instance,
/* Append the binary string to the Binary Signal. */
dse_buffer_append(&idx->sv->binary[idx->vi], &idx->sv->length[idx->vi],
&idx->sv->buffer_size[idx->vi], (void*)data, valueSizes[i]);
+ _log_binary_signal(fmu, idx);
/* Release the decode string/memory. Caller owns value[]. */
if (data != values[i]) free((uint8_t*)data);
@@ -1103,7 +1144,8 @@ fmi3Status fmi3DoStep(fmi3Instance instance,
if (fmu->variables.vtable.reset) fmu->variables.vtable.reset(fmu);
/* Step the model. */
- int rc = fmu_step(fmu, currentCommunicationPoint, communicationStepSize);
+ int32_t rc =
+ fmu_step(fmu, currentCommunicationPoint, communicationStepSize);
/* Reset the binary signal reset mechanism. */
fmu->variables.signals_reset = false;
diff --git a/dse/fmu/fmu.c b/dse/fmu/fmu.c
index 1ba446a..1880287 100644
--- a/dse/fmu/fmu.c
+++ b/dse/fmu/fmu.c
@@ -104,8 +104,8 @@ It is called in the `FreeInstance()` Method of the FMU.
Parameters
----------
-model (ModelDesc*)
-: Model descriptor object.
+fmu (FmuInstanceData*)
+: The FMU Descriptor object representing an instance of the FMU Model.
Returns
-------
@@ -117,3 +117,27 @@ int32_t fmu_destroy(FmuInstanceData* fmu)
UNUSED(fmu);
return 0;
}
+
+
+/**
+fmu_log
+=======
+
+Write a log message to the logger defined by the FMU.
+
+Parameters
+----------
+fmu (FmuInstanceData*)
+: The FMU Descriptor object representing an instance of the FMU Model.
+
+status (const int)
+: The status of the message to be logged.
+
+category (const char*)
+: The category the message belongs to.
+
+message (const char*)
+: The message to be logged by the FMU.
+*/
+extern void fmu_log(FmuInstanceData* fmu, const int status,
+ const char* category, const char* message, ...);
diff --git a/dse/fmu/fmu.h b/dse/fmu/fmu.h
index a370a90..58d9eeb 100644
--- a/dse/fmu/fmu.h
+++ b/dse/fmu/fmu.h
@@ -223,6 +223,8 @@ DLL_PRIVATE int32_t fmu_init(FmuInstanceData* fmu);
DLL_PRIVATE int32_t fmu_step(
FmuInstanceData* fmu, double communication_point, double step_size);
DLL_PRIVATE int32_t fmu_destroy(FmuInstanceData* fmu);
+DLL_PRIVATE void fmu_log(FmuInstanceData* fmu, const int status,
+ const char* category, const char* message, ...);
/* FMU Signal Interface (optional) */
DLL_PUBLIC void fmu_signals_reset(FmuInstanceData* fmu);
diff --git a/dse/importer/importer.c b/dse/importer/importer.c
index 3092708..ec84416 100644
--- a/dse/importer/importer.c
+++ b/dse/importer/importer.c
@@ -61,7 +61,7 @@ static struct option long_options[] = {
{ "help", no_argument, NULL, 'h' },
{ "version", required_argument, NULL, 'V' },
{ "fmu", required_argument, NULL, 'F' },
- { "stepSize", required_argument, NULL, 'S' },
+ { "step_size", required_argument, NULL, 'S' },
{ "steps", required_argument, NULL, 'N' },
};
@@ -97,7 +97,7 @@ static int _run_fmu2_cosim(
return EINVAL;
}
fmu = instantiate(
- "fmu", fmi2CoSimulation, "guid", "resources", NULL, true, false);
+ "fmu", fmi2CoSimulation, "guid", "resources", NULL, true, true);
if (fmu == NULL) return EINVAL;
/* fmi2ExitInitializationMode */
@@ -391,8 +391,10 @@ int main(int argc, char** argv)
}
if (desc->binary.vr_tx_binary) free(desc->binary.vr_tx_binary);
if (desc->binary.val_tx_binary) free(desc->binary.val_tx_binary);
+ if (desc->binary.val_size_tx_binary) free(desc->binary.val_size_tx_binary);
if (desc->binary.vr_rx_binary) free(desc->binary.vr_rx_binary);
if (desc->binary.val_rx_binary) free(desc->binary.val_rx_binary);
+ if (desc->binary.val_size_rx_binary) free(desc->binary.val_size_rx_binary);
if (desc->real.vr_tx_real) free(desc->real.vr_tx_real);
if (desc->real.val_tx_real) free(desc->real.val_tx_real);
if (desc->real.vr_rx_real) free(desc->real.vr_rx_real);
diff --git a/dse/importer/xml.c b/dse/importer/xml.c
index bb072ad..a1b1d86 100644
--- a/dse/importer/xml.c
+++ b/dse/importer/xml.c
@@ -188,12 +188,12 @@ void _parse_fmi2_model_desc(HashMap* vr_rx_real, HashMap* vr_tx_real,
static inline void _parse_fmi3_scalar(
- xmlNodePtr child, xmlChar* vr, xmlChar* causality, xmlChar* start,
- HashMap* vr_rx_real, HashMap* vr_tx_real)
+ xmlNodePtr child, xmlChar* vr, xmlChar* causality, HashMap* vr_rx_real,
+ HashMap* vr_tx_real)
{
if (xmlStrcmp(child->name, (xmlChar*)"Float64")) return;
- start = xmlGetProp(child, (xmlChar*)"start");
+ xmlChar* start = xmlGetProp(child, (xmlChar*)"start");
double _start = 0.0;
if (start) _start = atof((char*)start);
@@ -202,15 +202,19 @@ static inline void _parse_fmi3_scalar(
} else if (xmlStrcmp(causality, (xmlChar*)"output") == 0) {
hashmap_set_double(vr_tx_real, (char*)vr, _start);
}
+
+ /* Cleanup. */
+ if (start) xmlFree(start);
}
static inline void _parse_fmi3_binary(
- xmlNodePtr child, xmlChar* vr, xmlChar* causality, xmlChar* start,
- HashMap* vr_rx_binary, HashMap* vr_tx_binary)
+ xmlNodePtr child, xmlChar* vr, xmlChar* causality, HashMap* vr_rx_binary,
+ HashMap* vr_tx_binary)
{
if (xmlStrcmp(child->name, (xmlChar*)"Binary")) return;
+ xmlChar* start = NULL;
for (xmlNodePtr _child = child->children; _child;
_child = _child->next) {
if (_child->type != XML_ELEMENT_NODE) continue;
@@ -220,10 +224,14 @@ static inline void _parse_fmi3_binary(
}
if (strcmp((char*)causality, "input") == 0) {
- hashmap_set_string(vr_rx_binary, (char*)vr, (char*)start);
+ hashmap_set_string(
+ vr_rx_binary, (char*)vr, start ? (char*)start : (char*)"");
} else if (strcmp((char*)causality, "output") == 0) {
hashmap_set_string(vr_tx_binary, (char*)vr, (char*)"");
}
+
+ /* Cleanup. */
+ if (start) xmlFree(start);
}
@@ -240,20 +248,18 @@ static inline void _parse_fmi3_model_desc(HashMap* vr_rx_real, HashMap* vr_tx_re
xmlChar* vr = xmlGetProp(child, (xmlChar*)"valueReference");
xmlChar* causality = xmlGetProp(child, (xmlChar*)"causality");
- xmlChar* start = NULL;
if (vr == NULL || causality == NULL) goto next;
_parse_fmi3_scalar(
- child, vr, causality, start, vr_rx_real, vr_tx_real);
+ child, vr, causality, vr_rx_real, vr_tx_real);
_parse_fmi3_binary(
- child, vr, causality, start, vr_rx_binary, vr_tx_binary);
+ child, vr, causality, vr_rx_binary, vr_tx_binary);
/* Cleanup. */
next:
if (vr) xmlFree(vr);
if (causality) xmlFree(causality);
- if (start) xmlFree(start);
}
}
xmlXPathFreeObject(xml_sv_obj);
diff --git a/extra/external/CMakeLists.txt b/extra/external/CMakeLists.txt
index f7aaae8..3024ff1 100644
--- a/extra/external/CMakeLists.txt
+++ b/extra/external/CMakeLists.txt
@@ -116,6 +116,20 @@ set(XML_INSTALL_DIR "${INSTALL_DIR}")
file(MAKE_DIRECTORY "${XML_SOURCE_DIR}/include")
file(MAKE_DIRECTORY "${XML_INSTALL_DIR}/include/libxml2")
+# External Project - DSE Network Codec (unit test only)
+# -----------------
+ExternalProject_Add(dse_ncodec
+ URL ${ExternalProject__DSE_NCODEC__URL}
+ HTTP_USERNAME ${ExternalProject__DSE_NCODEC__USERNAME}
+ HTTP_PASSWORD ${ExternalProject__DSE_NCODEC__PASSWORD}
+ SOURCE_DIR "$ENV{EXTERNAL_BUILD_DIR}/dse_ncodec"
+ SOURCE_SUBDIR "dse/ncodec/libs/automotive-bus"
+ BINARY_DIR "$ENV{EXTERNAL_BUILD_DIR}/dse_ncodec"
+ CMAKE_ARGS
+ -DCMAKE_C_FLAGS=${CMAKE_C_FLAGS}
+ INSTALL_COMMAND ""
+)
+
# dlfcn-win32
# -----------
if(NOT WIN32)
diff --git a/extra/external/oss_repos.cmake b/extra/external/oss_repos.cmake
index 57e9e9b..6ca4b3e 100644
--- a/extra/external/oss_repos.cmake
+++ b/extra/external/oss_repos.cmake
@@ -10,7 +10,7 @@ set(ExternalProject__MODELC__URL https://github.com/boschglobal/dse.model
set(ExternalProject__MODELC_LIB__URL https://github.com/boschglobal/dse.modelc/releases/download/v$ENV{DSE_MODELC_VERSION}/ModelC-$ENV{DSE_MODELC_VERSION}-$ENV{PACKAGE_ARCH}.zip)
set(ExternalProject__DLFCNWIN32__URL https://github.com/dlfcn-win32/dlfcn-win32/archive/refs/tags/v1.3.0.tar.gz)
set(ExternalProject__XML2__URL https://github.com/GNOME/libxml2/archive/refs/tags/v2.13.5.zip)
-
+set(ExternalProject__DSE_NCODEC__URL https://github.com/boschglobal/dse.standards/archive/refs/tags/v1.1.0.tar.gz)
# Used by unit testing only (provided by ModelC for released targets).
set(ExternalProject__YAML__URL https://github.com/yaml/libyaml/archive/0.2.5.tar.gz)
diff --git a/extra/tools/fmi/README.md b/extra/tools/fmi/README.md
index 78095c7..bed0772 100644
--- a/extra/tools/fmi/README.md
+++ b/extra/tools/fmi/README.md
@@ -85,6 +85,25 @@ Appending file: out/model/signalgroup.yaml
$ ls out/model
model.yaml* signalgroup.yaml*
+
+$ task generate-fmigateway \
+ SIGNAL_GROUPS="extra/tools/fmi/test/testdata/fmigateway/SG1.yaml,extra/tools/fmi/test/testdata/fmigateway/SG2.yaml"
+Running FMI Toolset command: gen-gateway
+Options:
+ fmiVersion : 2
+ libroot : /usr/local
+ log : 4
+ outdir : out
+ session :
+ signalgroups : extra/tools/fmi/test/testdata/fmigateway/SG1.yaml,extra/tools/fmi/test/testdata/fmigateway/SG2.yaml
+ uuid :
+ version : 0.0.1
+Adding SignalGroup Model_1 to out/fmu.yaml
+Adding SignalGroup Model_2 to out/fmu.yaml
+Creating Model YAML: gateway (out/model.yaml)
+
+$ ls out/
+fmu.yaml modelDescription.xml model.yaml
```
diff --git a/extra/tools/fmi/cmd/fmi/main.go b/extra/tools/fmi/cmd/fmi/main.go
index 9be5e9b..d899c48 100644
--- a/extra/tools/fmi/cmd/fmi/main.go
+++ b/extra/tools/fmi/cmd/fmi/main.go
@@ -20,6 +20,7 @@ var (
generate.NewFmiMclCommand("gen-mcl"),
generate.NewGenSignalGroupCommand("gen-signalgroup"),
generate.NewGenFmuAnnotationCommand("gen-annotations"),
+ generate.NewFmiGatewayCommand("gen-gateway"),
patch.NewPatchSignalGroupCommand("patch-signalgroup"),
}
)
diff --git a/extra/tools/fmi/internal/app/generate/fmigateway.go b/extra/tools/fmi/internal/app/generate/fmigateway.go
new file mode 100644
index 0000000..51c938f
--- /dev/null
+++ b/extra/tools/fmi/internal/app/generate/fmigateway.go
@@ -0,0 +1,327 @@
+// Copyright 2024 Robert Bosch GmbH
+//
+// SPDX-License-Identifier: Apache-2.0
+
+package generate
+
+import (
+ "flag"
+ "fmt"
+ "log/slog"
+ "os"
+ "path/filepath"
+ "slices"
+ "strings"
+ "time"
+
+ "github.com/boschdevcloud.com/dse.fmi/extra/tools/fmi/pkg/file/handler"
+ "github.com/boschdevcloud.com/dse.fmi/extra/tools/fmi/pkg/file/handler/kind"
+ "github.com/boschdevcloud.com/dse.fmi/extra/tools/fmi/pkg/file/index"
+ "github.com/boschdevcloud.com/dse.fmi/extra/tools/fmi/pkg/fmi/fmi2"
+ "github.com/boschdevcloud.com/dse.fmi/extra/tools/fmi/pkg/log"
+ schema_kind "github.com/boschglobal/dse.schemas/code/go/dse/kind"
+ "github.com/google/uuid"
+ "gopkg.in/yaml.v3"
+)
+
+type GenFmiGatewayCommand struct {
+ commandName string
+ fs *flag.FlagSet
+
+ logLevel int
+ signalGroups string
+ uuid string
+ version string
+ fmiVersion string
+ outdir string
+ session string
+
+ libRootPath string
+}
+
+func NewFmiGatewayCommand(name string) *GenFmiGatewayCommand {
+ c := &GenFmiGatewayCommand{commandName: name, fs: flag.NewFlagSet(name, flag.ExitOnError)}
+
+ c.fs.IntVar(&c.logLevel, "log", 4, "Loglevel")
+ c.fs.StringVar(&c.signalGroups, "signalgroups", "", "A list of signalgroups separated by comma")
+ c.fs.StringVar(&c.uuid, "uuid", "", "UUID to assign to the FMU, set to '' to generate a new UUID")
+ c.fs.StringVar(&c.version, "version", "0.0.1", "Version to assign to the FMU")
+ c.fs.StringVar(&c.fmiVersion, "fmiVersion", "2", "Modelica FMI Version")
+ c.fs.StringVar(&c.outdir, "outdir", "out", "Output directory for the FMU file")
+ c.fs.StringVar(&c.session, "session", "", "Yaml file containing the session annotation")
+
+ // Supports unit testing.
+ c.fs.StringVar(&c.libRootPath, "libroot", "/usr/local", "System lib root path (where lib & lib32 directory are found)")
+
+ return c
+}
+
+func (c GenFmiGatewayCommand) Name() string {
+ return c.commandName
+}
+
+func (c GenFmiGatewayCommand) FlagSet() *flag.FlagSet {
+ return c.fs
+}
+
+func (c *GenFmiGatewayCommand) Parse(args []string) error {
+ return c.fs.Parse(args)
+}
+
+func (c *GenFmiGatewayCommand) Run() error {
+ slog.SetDefault(log.NewLogger(c.logLevel))
+ if len(c.uuid) == 0 {
+ c.uuid = uuid.New().String()
+ }
+
+ if err := os.MkdirAll(c.outdir, os.ModePerm); err != nil {
+ return err
+ }
+ if err := c.mergeSignalGroups(c.signalGroups, c.outdir); err != nil {
+ return err
+ }
+ fmuXml, channels, err := c.createFmuXml()
+ if err != nil {
+ return err
+ }
+ if err := c.generateModel(*fmuXml, channels); err != nil {
+ return err
+ }
+ fmuModelDescriptionFilename := filepath.Join(c.outdir, "modelDescription.xml")
+ if err := writeXml(fmuXml, fmuModelDescriptionFilename); err != nil {
+ return fmt.Errorf("could not generate the FMU Model Description (%v)", err)
+ }
+ return nil
+}
+
+func (c *GenFmiGatewayCommand) patchSignal(vr *int, sg_type string, signals []schema_kind.Signal, used_signals *[]string) ([]schema_kind.Signal, error) {
+ var signal_list []schema_kind.Signal
+ if sg_type == "string" {
+ return signals, nil
+ }
+ for _, s := range signals {
+ if slices.Contains(*used_signals, s.Signal) {
+ continue
+ }
+ *used_signals = append(*used_signals, s.Signal)
+ slog.Info(fmt.Sprintf("Patch Signals: %s", s.Signal))
+ causality := ""
+ if (*s.Annotations)["softecu_direction"] != nil {
+ switch (*s.Annotations)["softecu_direction"] {
+ case "M2E":
+ {
+ causality = "input"
+ break
+ }
+ case "E2M":
+ {
+ causality = "output"
+ break
+ }
+ default:
+ break
+ }
+ }
+ if (*s.Annotations)["direction"] != nil {
+ causality = ((*s.Annotations)["direction"]).(string)
+ }
+ if (*s.Annotations)["fmi_variable_causality"] != nil {
+ causality = ((*s.Annotations)["fmi_variable_causality"]).(string)
+ }
+
+ annotations := schema_kind.Annotations{
+ "fmi_variable_causality": causality,
+ "fmi_variable_vref": *vr,
+ "fmi_variable_type": sg_type,
+ "fmi_variable_name": s.Signal,
+ }
+ *s.Annotations = annotations
+ signal_list = append(signal_list, s)
+ *vr++
+ }
+ return signal_list, nil
+}
+
+func (c *GenFmiGatewayCommand) mergeSignalGroups(signalGroups string, outDir string) error {
+ signalGroupList := []string{}
+ if len(signalGroups) > 0 {
+ signalGroupList = strings.Split(signalGroups, ",")
+ }
+ vr := 0
+ var signal_list []string
+ for _, signalGroup := range signalGroupList {
+
+ // Load the Signal Group.
+ inputYaml, err := os.ReadFile(signalGroup)
+ if err != nil {
+ return fmt.Errorf("unable to read input file: %s (%w)", signalGroup, err)
+ }
+ sg := schema_kind.SignalGroup{}
+ if err := yaml.Unmarshal(inputYaml, &sg); err != nil {
+ return fmt.Errorf("unable to unmarshal signalgroup yaml: %s (%w)", signalGroup, err)
+ }
+ sg_type := "real"
+ if sg.Metadata.Annotations != nil {
+ if (*sg.Metadata.Annotations)["vector_type"] != nil {
+ if (*sg.Metadata.Annotations)["vector_type"] == "binary" {
+ sg_type = "string"
+ }
+ }
+ }
+
+ signals := sg.Spec.Signals
+
+ sg.Spec.Signals, err = c.patchSignal(&vr, sg_type, signals, &signal_list)
+ if err != nil {
+ return err
+ }
+
+ if err := writeYaml(&sg, filepath.Join(outDir, "fmu.yaml"), true); err != nil {
+ return err
+ }
+ }
+ return nil
+}
+
+func (c *GenFmiGatewayCommand) setGeneralXmlFields(fmuXml *fmi2.FmiModelDescription) {
+ // Basic FMU Information.
+ fmuXml.ModelName = "gateway"
+ fmuXml.FmiVersion = c.fmiVersion
+ fmuXml.Guid = fmt.Sprintf("{%s}", c.uuid)
+ fmuXml.GenerationTool = "DSE FMI - FMU Gateway"
+ fmuXml.GenerationDateAndTime = time.Now().String()
+ fmuXml.Author = "Robert Bosch GmbH"
+ fmuXml.Version = c.version
+ fmuXml.Description = "Gateway to connect to the DSE Simbus"
+
+ // Runtime Information.
+ fmuXml.DefaultExperiment.StartTime = "0.0"
+ fmuXml.DefaultExperiment.StopTime = "9999"
+ fmuXml.DefaultExperiment.StepSize = "0.0005"
+
+ // Model Information.
+ fmuXml.CoSimulation.ModelIdentifier = fmt.Sprintf("libfmi%sgateway", c.fmiVersion) // This is the packaged SO/DLL file.
+ fmuXml.CoSimulation.CanHandleVariableCommunicationStepSize = "true"
+ fmuXml.CoSimulation.CanInterpolateInputs = "true"
+}
+
+func (c *GenFmiGatewayCommand) buildXmlSignals(fmuXml *fmi2.FmiModelDescription, index *index.YamlFileIndex) ([]string, error) {
+ var channels []string
+ if signalGroupDocs, ok := index.DocMap["SignalGroup"]; ok {
+ for _, doc := range signalGroupDocs {
+ fmt.Fprintf(flag.CommandLine.Output(), "Adding SignalGroup %s to %s\n", doc.Metadata.Name, doc.File)
+ signalGroupSpec := doc.Spec.(*schema_kind.SignalGroupSpec)
+ if doc.Metadata.Annotations["vector_type"] == "binary" {
+ if err := fmi2.BinarySignal(*signalGroupSpec, fmuXml); err != nil {
+ slog.Warn(fmt.Sprintf("Skipped BinarySignal (Error: %s)", err.Error()))
+ }
+ } else {
+ if err := fmi2.ScalarSignal(*signalGroupSpec, fmuXml); err != nil {
+ slog.Debug(fmt.Sprintf("Skipped Scalarsignal (Error: %s)", err.Error()))
+ }
+ }
+ if !slices.Contains(channels, doc.Metadata.Labels["channel"]) {
+ channels = append(channels, doc.Metadata.Labels["channel"])
+ slog.Debug(fmt.Sprintf("added %s", doc.Metadata.Labels["channel"]))
+ }
+ }
+ }
+ return channels, nil
+}
+
+func (c *GenFmiGatewayCommand) createFmuXml() (*fmi2.FmiModelDescription, []string, error) {
+ fmuXml := fmi2.FmiModelDescription{}
+ c.setGeneralXmlFields(&fmuXml)
+
+ index := index.NewYamlFileIndex()
+ _, docs, err := handler.ParseFile(filepath.Join(c.outdir, "fmu.yaml"))
+ if err != nil {
+ return nil, nil, fmt.Errorf("parse failed (%s) on file: %s", err.Error(), "fmu.yaml")
+ }
+ for _, doc := range docs.([]kind.KindDoc) {
+ slog.Info(fmt.Sprintf("kind: %s; name=%s (%s)", doc.Kind, doc.Metadata.Name, doc.File))
+ if _, ok := index.DocMap[doc.Kind]; !ok {
+ index.DocMap[doc.Kind] = []kind.KindDoc{}
+ }
+ index.DocMap[doc.Kind] = append(index.DocMap[doc.Kind], doc)
+ }
+ channels, err := c.buildXmlSignals(&fmuXml, index)
+ if err != nil {
+ return nil, nil, err
+ }
+ return &fmuXml, channels, nil
+}
+
+func (c *GenFmiGatewayCommand) generateChannels(channel_map []string) ([]schema_kind.Channel, error) {
+ channels := []schema_kind.Channel{}
+ for i := range channel_map {
+ channel := schema_kind.Channel{
+ Alias: stringPtr(channel_map[i]),
+ Selectors: &schema_kind.Labels{
+ "channel": channel_map[i],
+ },
+ }
+ channels = append(channels, channel)
+ }
+ return channels, nil
+}
+
+func (c *GenFmiGatewayCommand) generateModel(fmiMD fmi2.FmiModelDescription, channel_map []string) error {
+
+ // Build the model.xml
+ nodeExists := func(Node any) string {
+ if Node != nil {
+ return "true"
+ }
+ return "false"
+ }
+
+ model := schema_kind.Model{
+ Kind: "Model",
+ Metadata: &schema_kind.ObjectMetadata{
+ Name: &fmiMD.ModelName,
+ Annotations: &schema_kind.Annotations{
+ "fmi_version": fmiMD.FmiVersion,
+ "fmi_model_cosim": nodeExists(fmiMD.CoSimulation),
+ "fmi_stepsize": fmiMD.DefaultExperiment.StepSize,
+ "fmi_endtime": fmiMD.DefaultExperiment.StopTime,
+ "fmi_guid": fmiMD.Guid,
+ "fmi_resource_dir": "resources",
+ "loglevel": 4,
+ },
+ },
+ }
+
+ if len(c.session) != 0 {
+ inputYaml, err := os.ReadFile(c.session)
+ if err != nil {
+ return fmt.Errorf("unable to read input file: %s (%w)", "session.yaml", err)
+ }
+ ann := schema_kind.ObjectMetadata{}
+ if err := yaml.Unmarshal(inputYaml, &ann); err != nil {
+ return fmt.Errorf("unable to unmarshal signalgroup yaml: %s (%w)", "session.yaml", err)
+ }
+ (*model.Metadata.Annotations)["session"] = (*ann.Annotations)["session"]
+ }
+
+ channels, err := c.generateChannels(channel_map)
+ if err != nil {
+ return fmt.Errorf("could not generate channels: (%v)", err)
+ }
+ spec := schema_kind.ModelSpec{
+ Channels: &channels,
+ }
+ model.Spec = spec
+
+ // Write the Model
+ if err := os.MkdirAll(c.outdir, 0755); err != nil {
+ return fmt.Errorf("error Creating Directory for Yaml output: %v", err)
+ }
+ modelYamlPath := filepath.Join(c.outdir, "model.yaml")
+ fmt.Fprintf(flag.CommandLine.Output(), "Creating Model YAML: %s (%s)\n", *model.Metadata.Name, modelYamlPath)
+ if err := writeYaml(&model, modelYamlPath, false); err != nil {
+ return fmt.Errorf("error writing YAML: %v", err)
+ }
+
+ return nil
+}
diff --git a/extra/tools/fmi/internal/app/generate/fmimcl.go b/extra/tools/fmi/internal/app/generate/fmimcl.go
index f5437f1..185737b 100644
--- a/extra/tools/fmi/internal/app/generate/fmimcl.go
+++ b/extra/tools/fmi/internal/app/generate/fmimcl.go
@@ -26,12 +26,6 @@ type FmiMclCommand struct {
logLevel int
}
-type channel []struct {
- Alias string `json:"alias"`
- Name string `json:"name"`
- Selector map[string]string `json:"selector"`
-}
-
func NewFmiMclCommand(name string) *FmiMclCommand {
c := &FmiMclCommand{commandName: name, fs: flag.NewFlagSet(name, flag.ExitOnError)}
c.fs.StringVar(&c.fmupath, "fmu", "", "Path to FMU")
diff --git a/extra/tools/fmi/internal/app/generate/fmimodelc_test.go b/extra/tools/fmi/internal/app/generate/fmimodelc_test.go
index 8546280..4b0f929 100644
--- a/extra/tools/fmi/internal/app/generate/fmimodelc_test.go
+++ b/extra/tools/fmi/internal/app/generate/fmimodelc_test.go
@@ -24,26 +24,24 @@ func TestFmiModelc_ModelDescription(t *testing.T) {
assert.Equal(t, "{11111111-2222-3333-4444-555555555555}", fmiMD.Guid, "Guid should match")
assert.Equal(t, "fmi2modelcfmu", fmiMD.CoSimulation.ModelIdentifier, "ModelIdentifier should match")
- ann := &[]fmi2.Annotations{
- {
- Tool: []fmi2.Tool{
- {
- Name: "dse.standards.fmi-ls-binary-codec",
- Annotation: &[]fmi2.Annotation{
- {Name: "mimetype", Text: "application/x-automotive-bus; interface=stream; type=frame; bus=can; schema=fbs; bus_id=1; node_id=2; interface_id=0"},
- },
+ ann := &fmi2.Annotations{
+ Tool: []fmi2.Tool{
+ {
+ Name: "dse.standards.fmi-ls-binary-codec",
+ Annotation: []fmi2.Annotation{
+ {Name: "mimetype", Text: "application/x-automotive-bus; interface=stream; type=frame; bus=can; schema=fbs; bus_id=1; node_id=2; interface_id=0"},
},
- {
- Name: "dse.standards.fmi-ls-binary-to-text",
- Annotation: &[]fmi2.Annotation{
- {Name: "encoding", Text: "ascii85"},
- },
+ },
+ {
+ Name: "dse.standards.fmi-ls-binary-to-text",
+ Annotation: []fmi2.Annotation{
+ {Name: "encoding", Text: "ascii85"},
},
- {
- Name: "dse.standards.fmi-ls-bus-topology",
- Annotation: &[]fmi2.Annotation{
- {Name: "bus_id", Text: "1"},
- },
+ },
+ {
+ Name: "dse.standards.fmi-ls-bus-topology",
+ Annotation: []fmi2.Annotation{
+ {Name: "bus_id", Text: "1"},
},
},
},
diff --git a/extra/tools/fmi/internal/app/generate/signalgroup.go b/extra/tools/fmi/internal/app/generate/signalgroup.go
index 5473cb0..7197dda 100644
--- a/extra/tools/fmi/internal/app/generate/signalgroup.go
+++ b/extra/tools/fmi/internal/app/generate/signalgroup.go
@@ -127,7 +127,10 @@ func (c *GenSignalGroupCommand) generateSignalVector(fmiMD fmi2.FmiModelDescript
},
}
signalVector.Spec.Signals = scalarSignals
- writeYaml(&signalVector, c.outputFile, true)
+ if err := writeYaml(&signalVector, c.outputFile, true); err != nil {
+ return err
+ }
+
if len(binarySignals) > 0 {
networkVector := kind.SignalGroup{
Kind: "SignalGroup",
@@ -143,7 +146,9 @@ func (c *GenSignalGroupCommand) generateSignalVector(fmiMD fmi2.FmiModelDescript
},
}
networkVector.Spec.Signals = binarySignals
- writeYaml(&networkVector, c.outputFile, true)
+ if err := writeYaml(&networkVector, c.outputFile, true); err != nil {
+ return err
+ }
}
return nil
diff --git a/extra/tools/fmi/pkg/fmi/fmi2/xml.go b/extra/tools/fmi/pkg/fmi/fmi2/xml.go
index 5bd9970..754a2c0 100644
--- a/extra/tools/fmi/pkg/fmi/fmi2/xml.go
+++ b/extra/tools/fmi/pkg/fmi/fmi2/xml.go
@@ -230,10 +230,10 @@ func BinarySignal(signalGroupSpec schema_kind.SignalGroupSpec, FmiXml *FmiModelD
for _, signal := range signalGroupSpec.Signals {
var rx_vref []interface{}
var tx_vref []interface{}
- if rx_vref := (*signal.Annotations)["dse.standards.fmi-ls-bus-topology.rx_vref"]; rx_vref == nil {
+ if rx_vref = (*signal.Annotations)["dse.standards.fmi-ls-bus-topology.rx_vref"].([]interface{}); rx_vref == nil {
return fmt.Errorf("Could not get rx_vref for signal %s", signal.Signal)
}
- if tx_vref := (*signal.Annotations)["dse.standards.fmi-ls-bus-topology.tx_vref"]; tx_vref == nil {
+ if tx_vref = (*signal.Annotations)["dse.standards.fmi-ls-bus-topology.tx_vref"].([]interface{}); tx_vref == nil {
return fmt.Errorf("Could not get tx_vref for signal %s", signal.Signal)
}
if len(rx_vref) != len(tx_vref) {
diff --git a/extra/tools/fmi/pkg/patch/signalgroup.go b/extra/tools/fmi/pkg/patch/signalgroup.go
index e5fcdd9..ee6144e 100644
--- a/extra/tools/fmi/pkg/patch/signalgroup.go
+++ b/extra/tools/fmi/pkg/patch/signalgroup.go
@@ -20,9 +20,9 @@ type PatchSignalGroupCommand struct {
name string
fs *flag.FlagSet
- signalGroupFile string
- signalPatchFile string
- removeUnknown bool
+ signalGroupFile string
+ signalPatchFile string
+ removeUnknown bool
}
func NewPatchSignalGroupCommand(name string) *PatchSignalGroupCommand {
@@ -45,7 +45,6 @@ func (c *PatchSignalGroupCommand) Parse(args []string) error {
return c.fs.Parse(args)
}
-
func (c *PatchSignalGroupCommand) Run() error {
inputYaml, err := os.ReadFile(c.signalGroupFile)
if err != nil {
@@ -74,7 +73,6 @@ func (c *PatchSignalGroupCommand) Run() error {
return nil
}
-
func patchSignals(sigGroup *kind.SignalGroup, patchFile string, removeUnknown bool) error {
// Load the CSV patch file.
f, err := os.Open(patchFile)
diff --git a/extra/tools/fmi/test/testdata/fmigateway/SG1.yaml b/extra/tools/fmi/test/testdata/fmigateway/SG1.yaml
new file mode 100644
index 0000000..21d1c1c
--- /dev/null
+++ b/extra/tools/fmi/test/testdata/fmigateway/SG1.yaml
@@ -0,0 +1,29 @@
+---
+kind: SignalGroup
+metadata:
+ labels:
+ channel: E2M_M2E
+ model: Model_1
+ name: Model_1
+spec:
+ signals:
+ - annotations:
+ softecu_access_function: E2M+M2E
+ softecu_direction: M2E
+ softecu_name: softecu_name_1
+ signal: softecu_name_1_changed
+ - annotations:
+ softecu_access_function: E2M+M2E
+ softecu_direction: E2M
+ softecu_name: softecu_name_2
+ signal: softecu_name_2_changed
+ - annotations:
+ softecu_access_function: E2M+M2E
+ softecu_direction: M2E
+ softecu_name: softecu_name_3
+ signal: softecu_name_3_changed
+ - annotations:
+ softecu_access_function: E2M+M2E
+ softecu_direction: E2M
+ softecu_name: softecu_name_4
+ signal: softecu_name_4_changed
diff --git a/extra/tools/fmi/test/testdata/fmigateway/SG2.yaml b/extra/tools/fmi/test/testdata/fmigateway/SG2.yaml
new file mode 100644
index 0000000..4cd9ede
--- /dev/null
+++ b/extra/tools/fmi/test/testdata/fmigateway/SG2.yaml
@@ -0,0 +1,29 @@
+---
+kind: SignalGroup
+metadata:
+ labels:
+ channel: E2M_M2E
+ model: Model_2
+ name: Model_2
+spec:
+ signals:
+ - annotations:
+ softecu_access_function: E2M+M2E
+ softecu_direction: M2E
+ softecu_name: softecu_name_5
+ signal: softecu_name_5_changed
+ - annotations:
+ softecu_access_function: E2M+M2E
+ softecu_direction: E2M
+ softecu_name: softecu_name_6
+ signal: softecu_name_6_changed
+ - annotations:
+ softecu_access_function: E2M+M2E
+ softecu_direction: M2E
+ softecu_name: softecu_name_1
+ signal: softecu_name_1_changed
+ - annotations:
+ softecu_access_function: E2M+M2E
+ softecu_direction: E2M
+ softecu_name: softecu_name_2
+ signal: softecu_name_2_changed
diff --git a/extra/tools/fmi/test/testdata/fmigateway/SG3.yaml b/extra/tools/fmi/test/testdata/fmigateway/SG3.yaml
new file mode 100644
index 0000000..4ffee9c
--- /dev/null
+++ b/extra/tools/fmi/test/testdata/fmigateway/SG3.yaml
@@ -0,0 +1,25 @@
+---
+kind: SignalGroup
+metadata:
+ labels:
+ channel: E2M_M2E
+ model: Model_3
+ name: Model_3
+spec:
+ signals:
+ - annotations:
+ direction: input
+ mcl_fmi_variable_id: '1342177280'
+ mcl_fmi_variable_name: fmu_signal_1
+ mcl_fmi_variable_start: '0.0'
+ mcl_fmi_variable_type: Real
+ org-fmu-name: fmu_signal_1
+ signal: fmu_signal_1_changed
+ - annotations:
+ direction: output
+ mcl_fmi_variable_id: '1342177280'
+ mcl_fmi_variable_name: fmu_signal_2
+ mcl_fmi_variable_start: '0.0'
+ mcl_fmi_variable_type: Real
+ org-fmu-name: fmu_signal_2
+ signal: fmu_signal_2_changed
diff --git a/extra/tools/fmi/test/testdata/fmigateway/SG4.yaml b/extra/tools/fmi/test/testdata/fmigateway/SG4.yaml
new file mode 100644
index 0000000..fbe1b6d
--- /dev/null
+++ b/extra/tools/fmi/test/testdata/fmigateway/SG4.yaml
@@ -0,0 +1,15 @@
+---
+kind: SignalGroup
+metadata:
+ labels:
+ channel: E2M_M2E
+ model: Model_4
+ name: Model_4
+spec:
+ signals:
+ - signal: signal_1
+ annotations:
+ direction: input
+ - signal: signal_2
+ annotations:
+ direction: output
diff --git a/extra/tools/fmi/test/testdata/fmigateway/SG5.yaml b/extra/tools/fmi/test/testdata/fmigateway/SG5.yaml
new file mode 100644
index 0000000..b061223
--- /dev/null
+++ b/extra/tools/fmi/test/testdata/fmigateway/SG5.yaml
@@ -0,0 +1,20 @@
+---
+kind: SignalGroup
+metadata:
+ labels:
+ channel: com_phys
+ name: Model_5
+spec:
+ signals:
+ - annotations:
+ direction: input
+ signal: signal_1
+ - annotations:
+ direction: output
+ signal: signal_2
+ - annotations:
+ direction: input
+ signal: signal_3
+ - annotations:
+ direction: output
+ signal: signal_4
diff --git a/extra/tools/fmi/test/testdata/fmigateway/SG6.yaml b/extra/tools/fmi/test/testdata/fmigateway/SG6.yaml
new file mode 100644
index 0000000..d91333d
--- /dev/null
+++ b/extra/tools/fmi/test/testdata/fmigateway/SG6.yaml
@@ -0,0 +1,32 @@
+---
+kind: SignalGroup
+metadata:
+ name: network_vector
+ labels:
+ channel: network_vector
+ annotations:
+ vector_type: binary
+spec:
+ signals:
+ - signal: can
+ annotations:
+ mime_type: 'application/x-automotive-bus; interface=stream; type=frame; bus=can; schema=fbs; bus_id=1; node_id=2; interface_id=0'
+ #
+ # FMI Value Reference
+ # -------------------
+ fmi_variable_vref: 4
+ fmi_variable_vref_input: 4
+ fmi_variable_vref_output: 5
+ #
+ # FMI LS Text Encoding (applied to FMI String Variables):
+ # --------------------
+ dse.standards.fmi-ls-binary-to-text.encoding: ascii85
+ # (supporting annotations to avoid XML parsing)
+ dse.standards.fmi-ls-binary-to-text.vref: [2, 3, 4, 5, 6, 7, 8, 9]
+ #
+ # FMI LS Bus Topology (applied to FMI String/Binary Variables):
+ # -------------------
+ dse.standards.fmi-ls-bus-topology.bus_id: 1
+ # (supporting annotations to avoid XML parsing)
+ dse.standards.fmi-ls-bus-topology.rx_vref: [2, 4, 6, 8]
+ dse.standards.fmi-ls-bus-topology.tx_vref: [3, 5, 7, 9]
diff --git a/extra/tools/fmi/test/testdata/fmigateway/session.yaml b/extra/tools/fmi/test/testdata/fmigateway/session.yaml
new file mode 100644
index 0000000..2952464
--- /dev/null
+++ b/extra/tools/fmi/test/testdata/fmigateway/session.yaml
@@ -0,0 +1,30 @@
+---
+annotations:
+ session:
+ initialization:
+ path: 'foo'
+ file: 'bar'
+ shutdown:
+ path: 'foo_shutdown'
+ file: 'bar_shutdown'
+ windows:
+ - model: 'Model_1'
+ annotations:
+ stepsize: 0.1
+ endtime: 0.2
+ loglevel: 6
+ timeout: 60.0
+ path: 'foo'
+ file: 'bar'
+ yaml: 'foo;bar'
+ show: true
+ - model: 'Model_2'
+ annotations:
+ stepsize: 0.15
+ endtime: 0.25
+ loglevel: 5
+ timeout: 61.0
+ path: 'foo_2'
+ file: 'bar_2'
+ yaml: 'foo_2;bar_2'
+ show: false
diff --git a/tests/cmocka/CMakeLists.txt b/tests/cmocka/CMakeLists.txt
index 504f4c7..02f5128 100644
--- a/tests/cmocka/CMakeLists.txt
+++ b/tests/cmocka/CMakeLists.txt
@@ -61,6 +61,7 @@ FetchContent_MakeAvailable(dse_clib)
set(DSE_CLIB_SOURCE_DIR ${dse_clib_SOURCE_DIR}/dse/clib)
set(DSE_CLIB_SOURCE_FILES
${DSE_CLIB_SOURCE_DIR}/util/binary.c
+ ${DSE_CLIB_SOURCE_DIR}/util/strings.c
${DSE_CLIB_SOURCE_DIR}/util/yaml.c
${DSE_CLIB_SOURCE_DIR}/collections/hashmap.c
${DSE_CLIB_SOURCE_DIR}/collections/set.c
@@ -225,3 +226,4 @@ install(
add_subdirectory(fmimodelc)
add_subdirectory(fmu)
+add_subdirectory(fmigateway)
diff --git a/tests/cmocka/Makefile b/tests/cmocka/Makefile
index 03e63f9..56ec729 100644
--- a/tests/cmocka/Makefile
+++ b/tests/cmocka/Makefile
@@ -27,6 +27,7 @@ run:
cd build/_out; $(GDB_CMD) bin/test_mstep
cd build/_out; $(GDB_CMD) bin/test_fmimodelc
cd build/_out; $(GDB_CMD) bin/test_fmu
+ cd build/_out; $(GDB_CMD) bin/test_fmigateway
clean:
rm -rf build
diff --git a/tests/cmocka/fmigateway/CMakeLists.txt b/tests/cmocka/fmigateway/CMakeLists.txt
new file mode 100644
index 0000000..f1db1cc
--- /dev/null
+++ b/tests/cmocka/fmigateway/CMakeLists.txt
@@ -0,0 +1,121 @@
+# Copyright 2024 Robert Bosch GmbH
+#
+# SPDX-License-Identifier: Apache-2.0
+
+cmake_minimum_required(VERSION 3.21)
+
+# set(CMAKE_VERBOSE_MAKEFILE ON)
+
+project(test_fmigateway)
+
+
+
+# FMI ModelC Code
+# ===============
+set(DSE_FMIGATEWAY_SOURCE_DIR ../../../dse/fmigateway)
+set(DSE_FMI2FMU_SOURCE_DIR ../../../dse/fmu)
+set(DSE_FMIGATEWAY_SOURCE_FILES
+ ${DSE_FMI2FMU_SOURCE_DIR}/fmi2fmu.c
+ ${DSE_FMI2FMU_SOURCE_DIR}/ascii85.c
+ ${DSE_FMI2FMU_SOURCE_DIR}/signal.c
+ ${DSE_FMIGATEWAY_SOURCE_DIR}/fmigateway.c
+ ${DSE_FMIGATEWAY_SOURCE_DIR}/index.c
+ ${DSE_FMIGATEWAY_SOURCE_DIR}/session.c
+ ${DSE_FMIGATEWAY_SOURCE_DIR}/parser.c
+)
+set(DSE_FMIGATEWAY_INCLUDE_DIR "${DSE_FMIGATEWAY_SOURCE_DIR}/../..")
+
+set(FMI2_INCLUDE_DIR "${DSE_CLIB_SOURCE_DIR}/fmi/fmi2/headers")
+
+# External Project - DSE Network Codec
+# ------------------------------------
+set(DSE_NCODEC_SOURCE_DIR "$ENV{EXTERNAL_BUILD_DIR}/dse_ncodec")
+set(DSE_NCODEC_BINARY_DIR "$ENV{EXTERNAL_BUILD_DIR}/dse_ncodec")
+find_library(DSE_NCODEC_LIB
+ NAMES
+ libautomotive-bus-codec.a
+ PATHS
+ ${DSE_NCODEC_BINARY_DIR}
+ REQUIRED
+ NO_DEFAULT_PATH
+)
+add_library(dse_ncodec STATIC IMPORTED GLOBAL)
+set_target_properties(dse_ncodec
+ PROPERTIES
+ IMPORTED_LOCATION "${DSE_NCODEC_LIB}"
+ INTERFACE_INCLUDE_DIRECTORIES "${DSE_NCODEC_SOURCE_DIR}"
+)
+set(DSE_NCODEC_SOURCE_FILES
+ ${DSE_NCODEC_SOURCE_DIR}/dse/ncodec/codec.c
+)
+set(DSE_NCODEC_INCLUDE_DIR "${DSE_NCODEC_SOURCE_DIR}")
+set(DSE_MODELC_SOURCE_FILES
+ ${DSE_MODELC_SOURCE_DIR}/model/gateway.c
+ ${DSE_MODELC_SOURCE_DIR}/model/model.c
+ ${DSE_MODELC_SOURCE_DIR}/model/ncodec.c
+ ${DSE_MODELC_SOURCE_DIR}/model/schema.c
+ ${DSE_MODELC_SOURCE_DIR}/model/signal.c
+ ${DSE_MODELC_SOURCE_DIR}/model/trace.c
+
+ ${DSE_MODELC_SOURCE_DIR}/controller/controller_stub.c
+ ${DSE_MODELC_SOURCE_DIR}/controller/model_function.c
+ ${DSE_MODELC_SOURCE_DIR}/controller/loader.c
+ ${DSE_MODELC_SOURCE_DIR}/controller/modelc.c
+ ${DSE_MODELC_SOURCE_DIR}/controller/modelc_debug.c
+ ${DSE_MODELC_SOURCE_DIR}/controller/modelc_args.c
+ ${DSE_MODELC_SOURCE_DIR}/controller/step.c
+ ${DSE_MODELC_SOURCE_DIR}/controller/model_runtime.c
+
+ ${DSE_MODELC_SOURCE_DIR}/adapter/adapter_loopb.c
+ ${DSE_MODELC_SOURCE_DIR}/adapter/index.c
+)
+
+# Targets
+# =======
+
+# Target - FMI Gateway
+# -------------------
+add_executable(test_fmigateway
+ __test__.c
+ test_parser.c
+ test_fmi2.c
+ ${DSE_NCODEC_SOURCE_FILES}
+ ${DSE_FMIGATEWAY_SOURCE_FILES}
+ ${DSE_MODELC_SOURCE_FILES}
+ ${DSE_CLIB_SOURCE_FILES}
+)
+target_include_directories(test_fmigateway
+ PRIVATE
+ ${DSE_NCODEC_INCLUDE_DIR}
+ ${DSE_FMIGATEWAY_INCLUDE_DIR}
+ ${DSE_MODELC_INCLUDE_DIR}
+ ${DSE_CLIB_INCLUDE_DIR}
+ ${FMI2_INCLUDE_DIR}
+ ${YAML_SOURCE_DIR}/include
+ ./
+)
+target_compile_definitions(test_fmigateway
+ PUBLIC
+ CMOCKA_TESTING
+ PRIVATE
+ PLATFORM_OS="${CDEF_PLATFORM_OS}"
+ PLATFORM_ARCH="${CDEF_PLATFORM_ARCH}"
+ MODELC_VERSION="${DSE_MODELC_VERSION}"
+)
+target_link_libraries(test_fmigateway
+ PUBLIC
+ PRIVATE
+ xml
+ dse_ncodec
+ yaml
+ cmocka
+ dl
+ m
+)
+install(TARGETS test_fmigateway)
+install(
+ FILES
+ DESTINATION
+ data/fmigateway
+)
+
diff --git a/tests/cmocka/fmigateway/__test__.c b/tests/cmocka/fmigateway/__test__.c
new file mode 100644
index 0000000..6f2485d
--- /dev/null
+++ b/tests/cmocka/fmigateway/__test__.c
@@ -0,0 +1,24 @@
+// Copyright 2024 Robert Bosch GmbH
+//
+// SPDX-License-Identifier: Apache-2.0
+
+#include
+#include
+
+
+uint8_t __log_level__; /* LOG_ERROR LOG_INFO LOG_DEBUG LOG_TRACE */
+
+
+extern int run_fmigateway__parser_tests(void);
+extern int run_fmigateway__fmi2_tests(void);
+
+
+int main()
+{
+ __log_level__ = LOG_QUIET; // LOG_DEBUG;//LOG_QUIET;
+
+ int rc = 0;
+ rc |= run_fmigateway__parser_tests();
+ rc |= run_fmigateway__fmi2_tests();
+ return rc;
+}
diff --git a/tests/cmocka/fmigateway/data/fmu.yaml b/tests/cmocka/fmigateway/data/fmu.yaml
new file mode 100644
index 0000000..64848d7
--- /dev/null
+++ b/tests/cmocka/fmigateway/data/fmu.yaml
@@ -0,0 +1,90 @@
+---
+kind: SignalGroup
+metadata:
+ name: scalar_vector
+ labels:
+ channel: scalar
+ annotations:
+ vector_type: scalar
+spec:
+ signals:
+ - signal: scalar_in
+ annotations:
+ fmi_variable_vref: 1001
+ fmi_variable_name: scalar_in
+ fmi_variable_type: Real
+ fmi_variable_causality: input
+ - signal: scalar_out
+ annotations:
+ fmi_variable_vref: 1002
+ fmi_variable_name: scalar_out
+ fmi_variable_type: Real
+ fmi_variable_causality: output
+# - signal: scalar_param
+# annotations:
+# fmi_variable_vref: 1003
+# fmi_variable_name: scalar_param
+# fmi_variable_type: Real
+# fmi_variable_causality: parameter
+---
+kind: SignalGroup
+metadata:
+ name: scalar_vector2
+ labels:
+ channel: scalar
+ annotations:
+ vector_type: scalar
+spec:
+ signals:
+ - signal: scalar_in2
+ annotations:
+ fmi_variable_vref: 1004
+ fmi_variable_name: scalar_in2
+ fmi_variable_type: Real
+ fmi_variable_causality: input
+ - signal: scalar_out2
+ annotations:
+ fmi_variable_vref: 1005
+ fmi_variable_name: scalar_out2
+ fmi_variable_type: Real
+ fmi_variable_causality: output
+# - signal: scalar_param
+# annotations:
+# fmi_variable_vref: 1003
+# fmi_variable_name: scalar_param
+# fmi_variable_type: Real
+# fmi_variable_causality: parameter
+---
+kind: SignalGroup
+metadata:
+ name: binary_vector
+ labels:
+ channel: binary
+ type: binary
+ annotations:
+ vector_type: binary
+ vector_name: network_vector
+spec:
+ signals:
+ - signal: can
+ annotations:
+ mime_type: 'application/x-automotive-bus; interface=stream; type=frame; bus=can; schema=fbs; bus_id=1; node_id=2; interface_id=0'
+ #
+ # FMI Value Reference
+ # -------------------
+ fmi_variable_vref: 4
+ fmi_variable_vref_input: 4
+ fmi_variable_vref_output: 5
+ #
+ # FMI LS Text Encoding (applied to FMI String Variables):
+ # --------------------
+ dse.standards.fmi-ls-binary-to-text.encoding: ascii85
+ # (supporting annotations to avoid XML parsing)
+ dse.standards.fmi-ls-binary-to-text.vref: [2, 3, 4, 5, 6, 7, 8, 9]
+ #
+ # FMI LS Bus Topology (applied to FMI String/Binary Variables):
+ # -------------------
+ dse.standards.fmi-ls-bus-topology.bus_id: 1
+ # (supporting annotations to avoid XML parsing)
+ dse.standards.fmi-ls-bus-topology.rx_vref: [2, 4, 6, 8]
+ dse.standards.fmi-ls-bus-topology.tx_vref: [3, 5, 7, 9]
diff --git a/tests/cmocka/fmigateway/data/model.yaml b/tests/cmocka/fmigateway/data/model.yaml
new file mode 100644
index 0000000..6643be5
--- /dev/null
+++ b/tests/cmocka/fmigateway/data/model.yaml
@@ -0,0 +1,52 @@
+---
+kind: Model
+metadata:
+ name: Gateway
+ annotations:
+ adapter: 'fmi'
+ fmi_version: '2.0'
+ fmi_model_cosim: true
+ fmi_model_version: '1.48'
+ fmi_stepsize: '0.0005'
+ fmi_endtime: '0.002'
+ fmi_guid: '{11111111-2222-3333-4444-555555555555}'
+ fmi_resource_dir: 'data'
+ set_init_values: true
+ loglevel: 6
+spec:
+ runtime:
+ gateway: {}
+ channels:
+ - alias: scalar_vector
+ selectors:
+ channel: scalar
+ - alias: binary_vector
+ selectors:
+ channel: binary
+---
+kind: Model
+metadata:
+ name: Target
+spec:
+ runtime:
+ dynlib:
+ - os: linux
+ arch: amd64
+ path: model/libgatewaytarget.so
+ - os: linux
+ arch: x86
+ path: model/libgatewaytarget.so
+ - os: windows
+ arch: x64
+ path: model/libgatewaytarget.dll
+ - os: windows
+ arch: x86
+ path: model/libgatewaytarget.dll
+ channels:
+ - alias: scalar_vector
+ selectors:
+ side: scalar_vector
+ - alias: binary_vector
+ selectors:
+ channel: binary
+---
diff --git a/tests/cmocka/fmigateway/data/model_parser.yaml b/tests/cmocka/fmigateway/data/model_parser.yaml
new file mode 100644
index 0000000..ba44239
--- /dev/null
+++ b/tests/cmocka/fmigateway/data/model_parser.yaml
@@ -0,0 +1,58 @@
+---
+kind: Model
+metadata:
+ name: Gateway
+ annotations:
+ adapter: 'fmi'
+ fmi_version: '2.0'
+ fmi_model_cosim: true
+ fmi_model_version: '1.48'
+ fmi_stepsize: '0.005'
+ fmi_endtime: '0.02'
+ fmi_guid: '{11111111-2222-3333-4444-555555555555}'
+ fmi_resource_dir: 'data'
+ set_init_values: true
+ loglevel: 6
+ loglocation: 'foo/bar'
+ session:
+ initialization:
+ path: 'foo'
+ file: 'bar'
+ shutdown:
+ path: 'foo_shutdown'
+ file: 'bar_shutdown'
+ windows:
+ - model: 'Model_1'
+ annotations:
+ stepsize: 0.1
+ endtime: 0.2
+ loglevel: 6
+ timeout: 60.0
+ path: 'foo'
+ file: 'bar'
+ yaml: 'foo;bar'
+ show: true
+ - model: 'Model_2'
+ annotations:
+ stepsize: 0.15
+ endtime: 0.25
+ loglevel: 5
+ timeout: 61.0
+ path: 'foo_2'
+ file: 'bar_2'
+ yaml: 'foo_2;bar_2'
+ show: false
+ linux:
+ local:
+ path: 'foo'
+ file: 'bar'
+spec:
+ runtime:
+ gateway: {}
+ channels:
+ - alias: scalar_vector
+ selectors:
+ channel: scalar
+ - alias: binary_vector
+ selectors:
+ channel: binary
diff --git a/tests/cmocka/fmigateway/data/stack.yaml b/tests/cmocka/fmigateway/data/stack.yaml
new file mode 100644
index 0000000..f2736f1
--- /dev/null
+++ b/tests/cmocka/fmigateway/data/stack.yaml
@@ -0,0 +1,48 @@
+---
+kind: Stack
+metadata:
+ name: stack
+spec:
+ connection:
+ transport:
+ redispubsub:
+ uri: redis://localhost:6379
+ timeout: 60
+ models:
+ - name: simbus
+ model:
+ name: simbus
+ channels:
+ - name: scalar
+ expectedModelCount: 2
+ - name: binary
+ expectedModelCount: 2
+ - name: gateway
+ uid: 42
+ model:
+ name: Gateway
+ channels:
+ - name: scalar
+ alias: scalar_vector
+ selectors:
+ channel: scalar
+ - name: binary
+ alias: binary_vector
+ selectors:
+ channel: binary
+ - name: target_inst
+ uid: 43
+ model:
+ name: Target
+ runtime:
+ files:
+ - fmu/resources/fmu.yaml
+ channels:
+ - name: scalar
+ alias: scalar_vector
+ selectors:
+ channel: scalar
+ - name: binary
+ alias: binary_vector
+ selectors:
+ channel: binary
diff --git a/tests/cmocka/fmigateway/test_fmi2.c b/tests/cmocka/fmigateway/test_fmi2.c
new file mode 100644
index 0000000..e70cb00
--- /dev/null
+++ b/tests/cmocka/fmigateway/test_fmi2.c
@@ -0,0 +1,279 @@
+// Copyright 2024 Robert Bosch GmbH
+//
+// SPDX-License-Identifier: Apache-2.0
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+
+#define UNUSED(x) ((void)x)
+#define ARRAY_SIZE(x) (sizeof(x) / sizeof(x[0]))
+
+
+extern uint8_t __log_level__;
+
+
+typedef struct fmi2_setup {
+ fmi2String instance_name;
+ fmi2Type fmu_type;
+ fmi2String fmu_guid;
+ fmi2String fmu_resource_location;
+ fmi2CallbackFunctions* functions;
+ fmi2Boolean visible;
+ fmi2Boolean logging_on;
+ Controller* controller;
+ Endpoint* endpoint;
+} fmi2_setup;
+
+void stub_setup_objects(Controller* c, Endpoint* e);
+void stub_release_objects(Controller* c, Endpoint* e);
+
+
+void _fmi2_unit_test_logger(fmi2ComponentEnvironment componentEnvironment,
+ fmi2String instanceName, fmi2Status status, fmi2String category,
+ fmi2String message, ...)
+{
+ UNUSED(componentEnvironment);
+ UNUSED(instanceName);
+
+ static const char* statusString[] = { "OK", "Warning", "Discard", "Error",
+ "Fatal", "Pending" };
+
+ if ((strcmp(category, "Debug") == 0) && ( __log_level__ > LOG_DEBUG)) {
+ return;
+ }
+ if ((strcmp(category, "Simbus") == 0) && ( __log_level__ > LOG_SIMBUS)) {
+ return;
+ }
+ if ((strcmp(category, "Info") == 0) && ( __log_level__ > LOG_INFO)) {
+ return;
+ }
+ if ((strcmp(category, "Notice") == 0) && ( __log_level__ > LOG_NOTICE)) {
+ return;
+ }
+ if ((strcmp(category, "Quiet") == 0) && ( __log_level__ > LOG_QUIET)) {
+ return;
+ }
+
+ printf("[%s:%s] %s\n", category, statusString[status], message);
+ fflush(stdout);
+}
+
+
+int test_fmigateway__fmi2_setup(void** state)
+{
+ fmi2_setup* setup = calloc(1, sizeof(fmi2_setup));
+ /* commit with log level in model.yaml set to 6. */
+ setup->logging_on = fmi2True;
+ setup->fmu_guid = "{1-22-333-4444-55555-666666-7777777}";
+ setup->fmu_type = fmi2CoSimulation;
+ setup->instance_name = "test_inst";
+ setup->visible = fmi2True;
+ setup->fmu_resource_location = "../../../../tests/cmocka/fmigateway/data";
+ setup->functions = calloc(1, sizeof(fmi2CallbackFunctions));
+ setup->functions->logger = _fmi2_unit_test_logger;
+
+ setup->controller = calloc(1, sizeof(Controller));
+ setup->controller->adapter = calloc(1, sizeof(Adapter));
+ setup->endpoint = calloc(1, sizeof(Endpoint));
+ stub_setup_objects(setup->controller, setup->endpoint);
+
+ *state = setup;
+
+ return 0;
+}
+
+
+int test_fmigateway__fmi2_teardown(void** state)
+{
+ fmi2_setup* setup = *state;
+ free(setup->functions);
+
+ if (setup && setup->controller) {
+ if (setup->controller->adapter) free(setup->controller->adapter);
+ free(setup->controller);
+ }
+ if (setup && setup->endpoint) free(setup->endpoint);
+
+ stub_release_objects(setup->controller, setup->endpoint);
+ free(setup);
+
+ return 0;
+}
+
+void test_fmigateway__fmi2_instantiate(void** state)
+{
+ fmi2_setup* setup = *state;
+
+ FmuInstanceData* inst = fmi2Instantiate(setup->instance_name,
+ setup->fmu_type, setup->fmu_guid, setup->fmu_resource_location,
+ setup->functions, setup->visible, setup->logging_on);
+
+ /* Check instance data. */
+ assert_non_null(inst->instance.guid);
+ assert_string_equal(inst->instance.guid, setup->fmu_guid);
+ assert_non_null(inst->instance.name);
+ assert_string_equal(inst->instance.name, setup->instance_name);
+ assert_non_null(inst->instance.resource_location);
+ assert_string_equal(
+ inst->instance.resource_location, setup->fmu_resource_location);
+ assert_non_null(inst->instance.type);
+ assert_int_equal(inst->instance.type, setup->fmu_type);
+
+ /* Check gateway data. */
+ assert_non_null(inst->data);
+
+ fmi2FreeInstance(inst);
+}
+
+void test_fmigateway__fmi2_ExitInitializationMode(void** state)
+{
+ fmi2_setup* setup = *state;
+
+ FmuInstanceData* inst = fmi2Instantiate(setup->instance_name,
+ setup->fmu_type, setup->fmu_guid, setup->fmu_resource_location,
+ setup->functions, setup->visible, setup->logging_on);
+
+ fmi2ExitInitializationMode(inst);
+ FmiGateway* fmi_gw = inst->data;
+
+ assert_string_equal(fmi_gw->model->mi->name, "gateway");
+ assert_double_equal(fmi_gw->settings.step_size, 0.0005, 0.0);
+ assert_double_equal(fmi_gw->settings.end_time, 0.002, 0.0);
+
+ assert_int_equal(inst->variables.scalar.input.used_nodes, 2);
+ assert_int_equal(inst->variables.scalar.output.used_nodes, 2);
+ for (SignalVector* sv = fmi_gw->model->sv; sv && sv->name; sv++) {
+ if (sv->is_binary) continue;
+
+ /* Check if SignalVector <-> Hashmap is set up correctly. */
+ assert_memory_equal(
+ (hashmap_get(&inst->variables.scalar.input, "1001")),
+ &sv->scalar[0], sizeof(double));
+ assert_memory_equal(
+ (hashmap_get(&inst->variables.scalar.input, "1004")),
+ &sv->scalar[2], sizeof(double));
+
+ assert_memory_equal(
+ (hashmap_get(&inst->variables.scalar.output, "1002")),
+ &sv->scalar[1], sizeof(double));
+ assert_memory_equal(
+ (hashmap_get(&inst->variables.scalar.output, "1005")),
+ &sv->scalar[3], sizeof(double));
+ }
+
+ assert_int_equal(inst->variables.binary.rx.used_nodes, 4);
+ assert_int_equal(inst->variables.binary.tx.used_nodes, 4);
+
+ fmi2FreeInstance(inst);
+}
+
+void test_fmigateway__fmi2_DOUBLE(void** state)
+{
+ fmi2_setup* setup = *state;
+
+ FmuInstanceData* inst = fmi2Instantiate(setup->instance_name,
+ setup->fmu_type, setup->fmu_guid, setup->fmu_resource_location,
+ setup->functions, setup->visible, setup->logging_on);
+
+ fmi2ExitInitializationMode(inst);
+ FmiGateway* fmi_gw = inst->data;
+
+ const unsigned int VR_DBL_IN[] = { 1001, 1004 };
+ const unsigned int VR_DBL_OUT[] = { 1002, 1005 };
+ double VALUE[] = { 1.0, 2.0 };
+ fmi2SetReal(inst, VR_DBL_IN, 2, VALUE);
+
+ for (SignalVector* sv = fmi_gw->model->sv; sv && sv->name; sv++) {
+ if (sv->is_binary) continue;
+ assert_double_equal(VALUE[0], sv->scalar[0], 0.0);
+ assert_double_equal(VALUE[1], sv->scalar[2], 0.0);
+
+ sv->scalar[1] = 3.0;
+ sv->scalar[3] = 4.0;
+ }
+
+ fmi2DoStep(inst, 0.0, 0.0005, 0);
+
+ fmi2GetReal(inst, VR_DBL_OUT, 2, VALUE);
+ for (SignalVector* sv = fmi_gw->model->sv; sv && sv->name; sv++) {
+ if (sv->is_binary) continue;
+ assert_double_equal(VALUE[0], sv->scalar[1], 0.0);
+ assert_double_equal(VALUE[1], sv->scalar[3], 0.0);
+ }
+ fmi2FreeInstance(inst);
+}
+
+void test_fmigateway__fmi2_BINARY(void** state)
+{
+ fmi2_setup* setup = *state;
+
+ FmuInstanceData* inst = fmi2Instantiate(setup->instance_name,
+ setup->fmu_type, setup->fmu_guid, setup->fmu_resource_location,
+ setup->functions, setup->visible, setup->logging_on);
+
+ fmi2ExitInitializationMode(inst);
+ FmiGateway* fmi_gw = inst->data;
+
+ const unsigned int VR_BIN_IN[] = { 2, 4, 6, 8 };
+ const unsigned int VR_BIN_OUT[] = { 3, 5, 7, 9 };
+ const char* VALUE[] = { "6:jpZ0`", "6:jpZ1&", "6:jpZ1B", "6:jpZ1]" };
+
+ fmi2SetString(inst, VR_BIN_IN, 4, VALUE);
+
+ for (SignalVector* sv = fmi_gw->model->sv; sv && sv->name; sv++) {
+ if (sv->is_binary == false) continue;
+ int len = strlen("BIN_1BIN_2BIN_3BIN_4");
+ assert_memory_equal("BIN_1BIN_2BIN_3BIN_4", sv->binary[0], len);
+ assert_int_equal(sv->length[0], len);
+ assert_int_equal(sv->buffer_size[0], len);
+ }
+
+ fmi2DoStep(inst, 0.0, 0.0005, 0);
+
+ for (SignalVector* sv = fmi_gw->model->sv; sv && sv->name; sv++) {
+ if (sv->is_binary == false) continue;
+ sv->length[0] = 0;
+
+ dse_buffer_append(&sv->binary[0], &sv->length[0], &sv->buffer_size[0],
+ (void*)"REMOTE_1", strlen("REMOTE_1"));
+ }
+
+ fmi2GetString(inst, VR_BIN_OUT, 4, VALUE);
+ for (SignalVector* sv = fmi_gw->model->sv; sv && sv->name; sv++) {
+ if (sv->is_binary == false) continue;
+ assert_memory_equal(";FO;U<(1.K", VALUE[0], 8);
+ assert_memory_equal(";FO;U<(1.K", VALUE[1], 8);
+ assert_memory_equal(";FO;U<(1.K", VALUE[2], 8);
+ assert_memory_equal(";FO;U<(1.K", VALUE[3], 8);
+ }
+
+ fmi2FreeInstance(inst);
+}
+
+
+int run_fmigateway__fmi2_tests(void)
+{
+ void* s = test_fmigateway__fmi2_setup;
+ void* t = test_fmigateway__fmi2_teardown;
+
+ const struct CMUnitTest tests[] = {
+ cmocka_unit_test_setup_teardown(
+ test_fmigateway__fmi2_instantiate, s, t),
+ cmocka_unit_test_setup_teardown(
+ test_fmigateway__fmi2_ExitInitializationMode, s, t),
+ cmocka_unit_test_setup_teardown(test_fmigateway__fmi2_DOUBLE, s, t),
+ cmocka_unit_test_setup_teardown(test_fmigateway__fmi2_BINARY, s, t),
+ };
+
+ return cmocka_run_group_tests_name("engine", tests, NULL, NULL);
+}
diff --git a/tests/cmocka/fmigateway/test_parser.c b/tests/cmocka/fmigateway/test_parser.c
new file mode 100644
index 0000000..f7b469f
--- /dev/null
+++ b/tests/cmocka/fmigateway/test_parser.c
@@ -0,0 +1,140 @@
+// Copyright 2024 Robert Bosch GmbH
+//
+// SPDX-License-Identifier: Apache-2.0
+
+#include
+#include
+#include
+#include
+#include
+#include
+
+
+#define UNUSED(x) ((void)x)
+#define ARRAY_SIZE(x) (sizeof(x) / sizeof(x[0]))
+
+
+int test_fmigateway__parser_setup(void** state)
+{
+ FmuInstanceData* fmu = calloc(1, sizeof(FmuInstanceData));
+
+ FmiGateway* fmi_gw = calloc(1, sizeof(FmiGateway));
+
+ fmi_gw->model = calloc(1, sizeof(ModelGatewayDesc));
+
+ fmu->data = (void*)fmi_gw;
+
+ fmu->instance.resource_location =
+ strdup("../../../../tests/cmocka/fmigateway/data");
+
+ fmi_gw->settings.yaml_files = calloc(2, sizeof(char*));
+ fmi_gw->settings.yaml_files[0] =
+ dse_path_cat(fmu->instance.resource_location, "model_parser.yaml");
+
+ *state = fmu;
+ return 0;
+}
+
+int test_fmigateway__parser_teardown(void** state)
+{
+ FmuInstanceData* fmu = *state;
+ FmiGateway* fmi_gw = fmu->data;
+
+ if (fmu) {
+ dse_yaml_destroy_doc_list(fmi_gw->settings.doc_list);
+ /* cleanup. */
+ for (const char** _ = fmi_gw->settings.yaml_files; *_; _++) {
+ free((char*)*_);
+ };
+ free(fmu->instance.resource_location);
+
+ if (fmu->data) {
+ free(fmi_gw->settings.yaml_files);
+ free(fmi_gw->settings.session->w_models);
+ free(fmi_gw->settings.session);
+ free(fmi_gw->model);
+ free(fmi_gw);
+ }
+ }
+ free(fmu);
+
+ return 0;
+}
+
+void test_fmigateway__parser(void** state)
+{
+ FmuInstanceData* fmu = *state;
+ FmiGateway* fmi_gw = fmu->data;
+
+ // Test conditions.
+ WindowsModel pi[] = {
+ {
+ .name = "Model_1",
+ .step_size = 0.1,
+ .end_time = 0.2,
+ .log_level = 6,
+ .timeout = 60.0,
+ .path = "foo",
+ .file = "bar",
+ .yaml = "foo;bar",
+ .show_process = true,
+ },
+ {
+ .name = "Model_2",
+ .step_size = 0.15,
+ .end_time = 0.25,
+ .log_level = 5,
+ .timeout = 61.0,
+ .path = "foo_2",
+ .file = "bar_2",
+ .yaml = "foo_2;bar_2",
+ .show_process = false,
+ },
+ };
+
+ assert_null(fmi_gw->settings.end_time);
+ assert_null(fmi_gw->settings.log_level);
+ assert_null(fmi_gw->settings.session);
+
+ fmigateway_parse(fmu);
+
+ assert_non_null(fmi_gw->settings.doc_list);
+ assert_double_equal(fmi_gw->settings.end_time, 0.02, 0.0);
+ assert_double_equal(fmi_gw->settings.step_size, 0.005, 0.0);
+ assert_int_equal(fmi_gw->settings.log_level, 6);
+ assert_string_equal(fmi_gw->settings.log_location, "foo/bar");
+ assert_non_null(fmi_gw->settings.session);
+ assert_string_equal(fmi_gw->settings.session->init.path, "foo");
+ assert_string_equal(fmi_gw->settings.session->init.file, "bar");
+ assert_string_equal(
+ fmi_gw->settings.session->shutdown.path, "foo_shutdown");
+ assert_string_equal(
+ fmi_gw->settings.session->shutdown.file, "bar_shutdown");
+
+ assert_non_null(fmi_gw->settings.session->w_models);
+ for (size_t i = 0; i < ARRAY_SIZE(pi); i++) {
+ WindowsModel* w_model = &fmi_gw->settings.session->w_models[i];
+ WindowsModel* TC_model = &pi[i];
+ assert_string_equal(TC_model->name, w_model->name);
+ assert_string_equal(TC_model->path, w_model->path);
+ assert_string_equal(TC_model->file, w_model->file);
+ assert_string_equal(TC_model->yaml, w_model->yaml);
+ assert_double_equal(TC_model->step_size, w_model->step_size, 0.0);
+ assert_double_equal(TC_model->end_time, w_model->end_time, 0.0);
+ assert_double_equal(TC_model->timeout, w_model->timeout, 0.0);
+ assert_int_equal(TC_model->log_level, w_model->log_level);
+ assert_int_equal(TC_model->show_process, w_model->show_process);
+ }
+}
+
+
+int run_fmigateway__parser_tests(void)
+{
+ void* s = test_fmigateway__parser_setup;
+ void* t = test_fmigateway__parser_teardown;
+
+ const struct CMUnitTest tests[] = { cmocka_unit_test_setup_teardown(
+ test_fmigateway__parser, s, t) };
+
+ return cmocka_run_group_tests_name("PARSER", tests, NULL, NULL);
+}
diff --git a/tests/testscript/e2e/fmigateway_fmi2.txtar b/tests/testscript/e2e/fmigateway_fmi2.txtar
new file mode 100644
index 0000000..45c9317
--- /dev/null
+++ b/tests/testscript/e2e/fmigateway_fmi2.txtar
@@ -0,0 +1,62 @@
+env NAME=fmigateway
+
+# Operate from the WORKDIR (allocated by caller, i.e. docker command).
+cd $WORKDIR
+env SIM=sim
+
+env EXAMPLE=$REPODIR/dse/build/_out/examples/gateway
+env FMU_IMPORTER=$REPODIR/dse/build/_out/importer/fmuImporter
+
+
+# Create the simulation folder (Simer layout)
+exec ls -R $WORK
+mkdir $SIM/bin
+cp $FMU_IMPORTER $SIM/bin/fmuImporter
+
+exec cp -R $EXAMPLE/fmi2 $SIM/fmu
+exec cp -R $EXAMPLE/model $SIM/model
+
+# TEST: FMI Gateway with FMI2
+exec sh -e $WORK/test.sh
+
+stdout 'Importer: Cwd: /sim/fmu'
+stdout 'Importer: Loading FMU: binaries/linux64/libfmi2gateway.so'
+stdout 'FMU Model instantiated'
+stdout 'Resource location: resources'
+stdout 'Setting up the Simbus connection...'
+stdout 'Connected to the Simbus...'
+stdout 'Configure Channel: scalar_vector'
+stdout 'Configure Channel: binary_vector'
+stdout 'Build indexes...'
+stdout 'Scalar: input=2, output=2'
+stdout 'Binary: rx=4, tx=4'
+stdout 'Encoding: enc=8, dec=8'
+
+stdout 'name : can'
+stdout 'length : 480'
+stdout 'buffer len : 480'
+
+stdout 'Importer: \[1002\] 9'
+stdout 'Importer: \[3\] F8u:'
+stdout 'Importer: \[5\] F8u:'
+stdout 'Importer: \[7\] F8u:'
+stdout 'Importer: \[9\] F8u:'
+
+stdout 'Model: RX\[03f1\] Hello World! from node_id=2'
+
+-- test.sh --
+SIMER="${SIMER:-ghcr.io/boschglobal/dse-simer:latest}"
+
+docker run --name gateway -i --rm -v $ENTRYWORKDIR/$SIM:/sim \
+ --env SIMBUS_LOGLEVEL=4 \
+ --net="host" \
+ --entrypoint="" $SIMER \
+ bash -c "cd fmu; valgrind -q --leak-check=yes --error-exitcode=808 \
+ ../bin/fmuImporter -F binaries/linux64/libfmi2gateway.so" &
+
+docker run --name simer -i --rm -v $ENTRYWORKDIR/$SIM:/sim \
+ --env SIMBUS_LOGLEVEL=4 \
+ --env target_inst:SIMBUS_LOGGER=4 \
+ --net="host" \
+ $SIMER \
+ -stepsize 0.0005 -endtime 0.005
diff --git a/tests/testscript/e2e/fmigateway_fmi3.txtar b/tests/testscript/e2e/fmigateway_fmi3.txtar
new file mode 100644
index 0000000..c1f696d
--- /dev/null
+++ b/tests/testscript/e2e/fmigateway_fmi3.txtar
@@ -0,0 +1,57 @@
+env NAME=fmigateway
+
+# Operate from the WORKDIR (allocated by caller, i.e. docker command).
+cd $WORKDIR
+env SIM=sim
+
+env EXAMPLE=$REPODIR/dse/build/_out/examples/gateway
+env FMU_IMPORTER=$REPODIR/dse/build/_out/importer/fmuImporter
+
+
+# Create the simulation folder (Simer layout)
+exec ls -R $WORK
+mkdir $SIM/bin
+cp $FMU_IMPORTER $SIM/bin/fmuImporter
+
+exec cp -R $EXAMPLE/fmi3 $SIM/fmu
+exec cp -R $EXAMPLE/model $SIM/model
+
+# TEST: FMI Gateway with FMI3
+exec sh -e $WORK/test.sh
+
+stdout 'Importer: Cwd: /sim/fmu'
+stdout 'Importer: Loading FMU: binaries/x86_64-linux/libfmi3gateway.so'
+stdout 'FMU Model instantiated'
+stdout 'Resource location: resources'
+stdout 'Setting up the Simbus connection...'
+stdout 'Connected to the Simbus...'
+stdout 'Configure Channel: scalar_vector'
+stdout 'Configure Channel: binary_vector'
+stdout 'Scalar: input=2, output=2'
+stdout 'Binary: rx=4, tx=4'
+stdout 'Encoding: enc=8, dec=8'
+
+stdout 'Importer: \[1002\] 9'
+stdout 'Importer: \[3\] F8u:'
+stdout 'Importer: \[5\] F8u:'
+stdout 'Importer: \[7\] F8u:'
+stdout 'Importer: \[9\] F8u:'
+
+stdout 'Model: RX\[03f1\] Hello World! from node_id=2'
+
+-- test.sh --
+SIMER="${SIMER:-ghcr.io/boschglobal/dse-simer:latest}"
+
+docker run --name gateway -i --rm -v $ENTRYWORKDIR/$SIM:/sim \
+ --env SIMBUS_LOGLEVEL=4 \
+ --net="host" \
+ --entrypoint="" $SIMER \
+ bash -c "cd fmu; valgrind -q --leak-check=yes --error-exitcode=808 \
+ ../bin/fmuImporter -V 3 -F binaries/x86_64-linux/libfmi3gateway.so" &
+
+docker run --name simer -i --rm -v $ENTRYWORKDIR/$SIM:/sim \
+ --env SIMBUS_LOGLEVEL=4 \
+ --env target_inst:SIMBUS_LOGGER=4 \
+ --net="host" \
+ $SIMER \
+ -stepsize 0.0005 -endtime 0.005