/******************************************************************************** * Copyright (c) 2025-2026 ZF Friedrichshafen AG * * This program and the accompanying materials are made available under the * terms of the Apache License Version 2.0 which is available at * https://www.apache.org/licenses/LICENSE-2.0 * * SPDX-License-Identifier: Apache-2.0 * * Contributors: * Thomas Pfleiderer - initial API and implementation ********************************************************************************/ /** * @brief Build description template. File 'buildDescription.xml'. Code chunks are inserted at the keywords surrounded by %%. */ const char szBuildDescriptionTemplate[] = R"code( %buildDescription_xml% )code"; /** * @brief CMake build project template. File 'CMakeLists.txt'. Code chunks are inserted at the keywords surrounded by %%. */ const char szMappingCMakeFileListsTemplate[] = R"code( # @file %cmakefilelists_path% # This file is cmake project file. # This file was generated by the DBC utility from: # %dbc_sources% # %dbc_version% # Based on CMakeLists.txt from https://github.com/modelica/Reference-FMUs # only FMI 2.0, only CoSimulation # without fumsim # Only valid for Windows if ( WIN32 ) # Enforce CMake version 3.20 or newer needed for path function cmake_minimum_required (VERSION 3.20) # Use new policy for project version settings and default warning level cmake_policy(SET CMP0048 NEW) # requires CMake 3.14 cmake_policy(SET CMP0092 NEW) # requires CMake 3.15 project (%model_Identifier%Project) # Use C++17 support set(CMAKE_CXX_STANDARD 17) # Library symbols are hidden by default set(CMAKE_CXX_VISIBILITY_PRESET hidden) # Set the SDV_FRAMEWORK_DEV_INCLUDE if not defined yet if (NOT DEFINED SDV_FRAMEWORK_DEV_INCLUDE) if (NOT DEFINED ENV{SDV_FRAMEWORK_DEV_INCLUDE}) message( FATAL_ERROR "The environment variable SDV_FRAMEWORK_DEV_INCLUDE needs to be pointing to the SDV V-API development include files location!") endif() set (SDV_FRAMEWORK_DEV_INCLUDE "$ENV{SDV_FRAMEWORK_DEV_INCLUDE}") endif() # Include link to export directory of SDV V-API development include files location include_directories(${SDV_FRAMEWORK_DEV_INCLUDE}) set(VAPI_CORE_SDV_BINARY_DIR ${CMAKE_BINARY_DIR}/bin) set(MODEL_NAME %model_Identifier%) set(TARGET_NAME ${MODEL_NAME}) set(FMU_FULL_FILE_NAME "${CMAKE_CURRENT_BINARY_DIR}/fmus/${MODEL_NAME}.fmu") FUNCTION(cat IN_FILE OUT_FILE) file(READ ${IN_FILE} CONTENTS) file(APPEND ${OUT_FILE} "${CONTENTS}") ENDFUNCTION() set(FMI_VERSION 2 CACHE STRING "FMI Version") set_property(CACHE FMI_VERSION PROPERTY STRINGS 2) set(FMI_TYPE CS CACHE STRING "FMI Version") set_property(CACHE FMI_TYPE PROPERTY STRINGS CS) set(FMI_TYPE "") set (FMI_PLATFORM win32) if ("${CMAKE_SIZEOF_VOID_P}" STREQUAL "8") set (FMI_PLATFORM win64) endif () SET(HEADERS ${MODEL_NAME}/config.h include/cosimulation.h include/model.h ) SET(HEADERS ${HEADERS} include/fmi2Functions.h include/fmi2FunctionTypes.h include/fmi2TypesPlatform.h ) SET(SOURCES ${MODEL_NAME}/model.cpp src/fmi${FMI_VERSION}Functions.c src/cosimulation.c ) add_library(${TARGET_NAME} SHARED ${HEADERS} ${SOURCES} ${MODEL_NAME}/FMI${FMI_VERSION}${FMI_TYPE}.xml ${MODEL_NAME}/buildDescription.xml ) file(MAKE_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/fmus) set(FMU_BUILD_DIR temp/${MODEL_NAME}) target_compile_definitions(${TARGET_NAME} PRIVATE FMI_VERSION=${FMI_VERSION} DISABLE_PREFIX ) #[[ if (MSVC) set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreaded$<$:Debug>DLL") else() set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -static-libstdc++ -static-libgcc") set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -static-libstdc++ -static-libgcc") endif() ]] target_compile_definitions(${TARGET_NAME} PRIVATE FMI_COSIMULATION) target_include_directories(${TARGET_NAME} PRIVATE include ${MODEL_NAME}) target_link_libraries(${TARGET_NAME} Winmm Ws2_32 Rpcrt4.lib) set_target_properties(${TARGET_NAME} PROPERTIES RUNTIME_OUTPUT_DIRECTORY "${FMU_BUILD_DIR}/binaries/${FMI_PLATFORM}" RUNTIME_OUTPUT_DIRECTORY_DEBUG "${FMU_BUILD_DIR}/binaries/${FMI_PLATFORM}" RUNTIME_OUTPUT_DIRECTORY_RELEASE "${FMU_BUILD_DIR}/binaries/${FMI_PLATFORM}" LIBRARY_OUTPUT_DIRECTORY "${FMU_BUILD_DIR}/binaries/${FMI_PLATFORM}" LIBRARY_OUTPUT_DIRECTORY_DEBUG "${FMU_BUILD_DIR}/binaries/${FMI_PLATFORM}" LIBRARY_OUTPUT_DIRECTORY_RELEASE "${FMU_BUILD_DIR}/binaries/${FMI_PLATFORM}" ARCHIVE_OUTPUT_DIRECTORY "${FMU_BUILD_DIR}/binaries/${FMI_PLATFORM}" ARCHIVE_OUTPUT_DIRECTORY_DEBUG "${FMU_BUILD_DIR}/binaries/${FMI_PLATFORM}" ARCHIVE_OUTPUT_DIRECTORY_RELEASE "${FMU_BUILD_DIR}/binaries/${FMI_PLATFORM}" ) set_target_properties(${TARGET_NAME} PROPERTIES PREFIX "") set_target_properties(${TARGET_NAME} PROPERTIES OUTPUT_NAME ${MODEL_NAME}) # modelDescription.xml add_custom_command(TARGET ${TARGET_NAME} POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_CURRENT_SOURCE_DIR}/${MODEL_NAME}/FMI${FMI_VERSION}${FMI_TYPE}.xml "${FMU_BUILD_DIR}/modelDescription.xml" ) set(ARCHIVE_FILES "modelDescription.xml" "binaries") if (EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/${MODEL_NAME}/resources") add_custom_command(TARGET ${TARGET_NAME} POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy_directory "${CMAKE_CURRENT_SOURCE_DIR}/${MODEL_NAME}/resources" "${FMU_BUILD_DIR}/resources/" ) set(ARCHIVE_FILES ${ARCHIVE_FILES} "resources") endif() # When windows robocopy command (using cmd.exe) is used to copy files its important to set the dependencies # to assure that the copy command is finished before the next custom action to avoid copy/file access failures # Copy sdv binaries of this FMU set(DEST_DIR "${FMU_BUILD_DIR}/resources") set(SOURCE_DIR_EXAMPLES_BIN "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}") add_custom_target(copy_function_sdv_files_${TARGET_NAME} DEPENDS ${TARGET_NAME}) add_custom_command(TARGET copy_function_sdv_files_${TARGET_NAME} COMMAND cmd /C "robocopy \"${SOURCE_DIR_EXAMPLES_BIN}\" \"${DEST_DIR}\" *.pdb *.sdv /NP /R:3 /W:5 || exit /b 0" COMMENT "Copying contents from ${SOURCE_DIR_EXAMPLES_BIN} to ${DEST_DIR}, include only *.pdb *.sdv files" ) add_dependencies(copy_function_sdv_files_${TARGET_NAME} ${TARGET_NAME}) # Copy framework sdv binaries set(SOURCE_DIR_CORE_BIN "${SDV_FRAMEWORK_RUNTIME}") add_custom_target(copy_framework_sdv_files_${TARGET_NAME} DEPENDS copy_function_sdv_files_${TARGET_NAME}) add_custom_command(TARGET copy_framework_sdv_files_${TARGET_NAME} COMMAND cmd /C "robocopy \"${SOURCE_DIR_CORE_BIN}\" \"${DEST_DIR}\" *.pdb *.sdv /NP /R:3 /W:5 || exit /b 0" COMMENT "Copying contents from ${SOURCE_DIR_CORE_BIN} to ${DEST_DIR}, include only *.pdb *.sdv files" ) add_dependencies(copy_framework_sdv_files_${TARGET_NAME} copy_function_sdv_files_${TARGET_NAME}) # FMU content created, all files copied # to zip the files create a new target 'create_zip' which is build after all files have been copied add_custom_target(create_zip_${TARGET_NAME} ALL DEPENDS copy_framework_sdv_files_${TARGET_NAME} ) add_custom_command(TARGET create_zip_${TARGET_NAME} POST_BUILD COMMAND ${CMAKE_COMMAND} -E tar "cfv" ${FMU_FULL_FILE_NAME} --format=zip ${ARCHIVE_FILES} WORKING_DIRECTORY ${FMU_BUILD_DIR} COMMENT "Creating ZIP ${FMU_FULL_FILE_NAME}" ) add_dependencies(create_zip_${TARGET_NAME} copy_framework_sdv_files_${TARGET_NAME}) #TODO #add_dependencies(${TARGET_NAME} ) endif () )code"; /** *@brief Config Header template. File 'config.h'. Code chunks are inserted at the keywords surrounded by %%. */ const char szConfigHTemplate[] = R"code(/** * @file %configH_path% * @date %date% * This file defines the data link object between CAN and the V-API devices. * This file was generated by the DBC utility from: * %dbc_sources% * %dbc_version% */ #ifndef %safeguardconfig% #define %safeguardconfig% #include // define class name and unique id #define MODEL_IDENTIFIER %model_Identifier% #define INSTANTIATION_TOKEN "%model_guid%" #define FMI_VERSION 2 #define CO_SIMULATION // define model size #define NX 0 #define NZ 0 #define GET_INT32 #define SET_INT32 #define GET_FLAOT64 #define SET_FLOAT64 #define EVENT_UPDATE #define CLEAN_UP #define FIXED_SOLVER_STEP 0.04 #define DEFAULT_STOP_TIME 10 typedef enum { %value_reference% } ValueReference; typedef struct { %model_data% } ModelData; #endif // !defined %safeguardconfig% )code"; /** *@brief FMI2 xml template. File 'FMI2.XML'. Code chunks are inserted at the keywords surrounded by %%. */ const char szFMI2XMLTemplate[] = R"code( %model_variables% )code"; /** *@brief Model cpp template. File 'model.cpp'. Code chunks are inserted at the keywords surrounded by %%. */ const char szModelCPPTemplate[] = R"code(/** * @file %modelcpp_path% * @date %date% * This file defines the data link object between CAN and the V-API devices. * This file was generated by the DBC utility from: * %dbc_sources% * %dbc_version% */ #include #include #include #include #include #include "signal_identifier.h" #include #include %global_signals% // in case the simulation timer should be used sdv::core::ITimerSimulationStep* g_pTimerSimulationStep; std::unique_ptr g_appcontrol; bool InitializeAppControl(const std::string& resource, const std::string& configFileName) { auto bResult = g_appcontrol->AddModuleSearchDir( resource ); bResult &= g_appcontrol->Startup(""); g_appcontrol->SetConfigMode(); bResult &= g_appcontrol->AddConfigSearchDir( resource ); if (!configFileName.empty()) { bResult &= g_appcontrol->LoadConfig(configFileName.c_str()) == sdv::core::EConfigProcessResult::successful; } return bResult; } sdv::core::EConfigProcessResult RegisterAllSignals() { std::string msg = "register all signals: "; sdv::core::CDispatchService dispatch; %global_signals_register% %global_signals_register_check% return sdv::core::EConfigProcessResult::failed; } bool ResetAllSignals() { sdv::core::CDispatchService dispatch; %global_signals_reset% SDV_LOG_INFO("Reset signals"); return true; } bool CreateCoreServiceTomlFile(const std::string& resources) { std::ofstream tomlFile("sdv_core_reloc.toml"); if (tomlFile.is_open()) { tomlFile << "# Location of the SDV binaries and configuration files\ndirectory = \""; tomlFile << resources; tomlFile << "\"\n"; tomlFile.close(); return true; } return false; } bool OpenAPILoad(const std::string& resources) { bool success = CreateCoreServiceTomlFile(resources); g_appcontrol = std::make_unique (); // // TODO: Dispatch service must be loaded first, adjust the correct toml file // success &= InitializeAppControl(resources, "data_dispatch_config_file.toml"); if (!success) { std::cout << "Error: InitializeAppControl() failed" << std::endl; SDV_LOG_ERROR("Failed InitializeAppControl"); return false; } success &= RegisterAllSignals() == sdv::core::EConfigProcessResult::successful; if (!success) { SDV_LOG_ERROR("Signals could not be registered"); } %vapi_load_config_files% g_appcontrol->SetRunningMode(); return success; } void OpenAPIShutdown() { ResetAllSignals(); } #ifdef __cplusplus extern "C" { #endif #include // for DBL_EPSILON #include // for fabs() #include "config.h" #include "model.h" Status cleanup(ModelInstance*) { SDV_LOG_INFO("Shutting down..."); OpenAPIShutdown(); return OK; } bool setStartValues(ModelInstance* comp) { std::string path(comp->resourceLocation); std::string resourcePath = path.substr(8); std::replace(resourcePath.begin(), resourcePath.end(), '\\', '/'); if (!OpenAPILoad(resourcePath)) { std::cout << "Error: OpenAPILoad() failed." << std::endl; comp->terminateSimulation = true; return false; } // TODO: move this to initialize()? comp->nextEventTime = 0; comp->nextEventTimeDefined = true; return true; } Status calculateValues(ModelInstance* comp) { UNUSED(comp); // nothing to do return OK; } Status getFloat64(ModelInstance* comp, [[maybe_unused]] ValueReference vr, [[maybe_unused]] double values[], [[maybe_unused]] size_t nValues, [[maybe_unused]] size_t* index) { ASSERT_NVALUES(1); %getFloat64% return OK; } Status getInt32(ModelInstance* comp, [[maybe_unused]] ValueReference vr, [[maybe_unused]] int32_t values[], [[maybe_unused]] size_t nValues, [[maybe_unused]] size_t* index) { ASSERT_NVALUES(1); %getInt32% return OK; } Status setFloat64(ModelInstance* comp, [[maybe_unused]] ValueReference vr, [[maybe_unused]] const double values[], [[maybe_unused]] size_t nValues, [[maybe_unused]] size_t* index) { ASSERT_NVALUES(1); %setFloat64% return OK; } Status setInt32(ModelInstance* comp, [[maybe_unused]] ValueReference vr, [[maybe_unused]] const int32_t values[], [[maybe_unused]] size_t nValues, [[maybe_unused]] size_t* index) { ASSERT_NVALUES(1); %setInt32% return OK; } void eventUpdate(ModelInstance* comp) { %event_update% double epsilon = (1.0 + fabs(comp->time)) * DBL_EPSILON; if (comp->nextEventTimeDefined && comp->time + epsilon >= comp->nextEventTime) { comp->nextEventTime += FIXED_SOLVER_STEP; } comp->valuesOfContinuousStatesChanged = false; comp->nominalsOfContinuousStatesChanged = false; comp->terminateSimulation = false; comp->nextEventTimeDefined = true; } #ifdef __cplusplus } // end of extern "C" #endif )code"; /** *@brief Signal name mapping template. Signal names contain the can names of the signal which must match the names of the vehicle devices. * File 'signal_identifier.h'. Code chunks are inserted at the keywords surrounded by %%. */ const char szMappingSignalIdentifierTemplate[] = R"code(/** @file %signalidentifierfile_path% @date %date% This file is the signal identifier header. This file was generated by the DBC utility from: %dbc_sources% %dbc_version% */ #ifndef %safeguardsignalidentifier% #define %safeguardsignalidentifier% namespace %object_prefix% { // Data Dispatch Service signal names to dbc variable names C-type RX/TX vss name space %signals% } // %object_prefix% #endif // %safeguardsignalidentifier% )code"; /** *@brief Data dispatch service config template. File 'data_dispatch_config_file.toml'. Code chunks are inserted at the keywords surrounded by %%. */ const char szDataDispatchServiceTomlFile[] = R"code([Configuration] Version = 100 [[Component]] Path = "data_dispatch_service.sdv" Class = "DataDispatchService" )code"; /** *@brief Simulation task timer service config template. File 'simulation_task_timer_config_file.toml'. Code chunks are inserted at the keywords surrounded by %%. */ const char szSimulationTaskTimerServiceTomlFile[] = R"code([Configuration] Version = 100 [[Component]] Path = "simulation_task_timer.sdv" Class = "SimulationTaskTimerService" )code"; /** *@brief Task timer service config template. File 'task_timer_config_file.toml'. Code chunks are inserted at the keywords surrounded by %%. */ const char szTaskTimerhServiceTomlFile[] = R"code([Configuration] Version = 100 [[Component]] Path = "task_timer.sdv" Class = "TaskTimerService" )code";