From d23a7b4e80eb5f07f38dd22a9ea67742b484148d Mon Sep 17 00:00:00 2001 From: Lars Ivar Hatledal Date: Wed, 18 Aug 2021 10:50:19 +0200 Subject: [PATCH] Feature/proxy-fmu (#633) --- .clang-format | 2 +- .github/workflows/ci-conan.yml | 8 +- CMakeLists.txt | 10 +- README.md | 10 +- cmake/FindFMILibrary.cmake | 43 ++-- cmake/FindTHRIFT.cmake | 39 ---- cmake/project-config.cmake.in | 4 +- conanfile.py | 19 +- src/CMakeLists.txt | 54 +---- src/cosim/algorithm/fixed_step_algorithm.cpp | 3 - src/cosim/fmuproxy/fmuproxy_client.cpp | 110 --------- src/cosim/fmuproxy/fmuproxy_client.hpp | 48 ---- src/cosim/fmuproxy/fmuproxy_helper.hpp | 122 ---------- src/cosim/fmuproxy/remote_fmu.cpp | 41 ---- src/cosim/fmuproxy/remote_slave.cpp | 192 --------------- src/cosim/fmuproxy/service.thrift | 218 ------------------ src/cosim/fmuproxy/thrift_state.cpp | 30 --- src/cosim/fmuproxy/thrift_state.hpp | 50 ---- src/cosim/orchestration.cpp | 9 +- .../proxy_uri_sub_resolver.cpp} | 62 +++-- .../proxy_uri_sub_resolver.hpp} | 17 +- src/cosim/proxy/remote_fmu.cpp | 121 ++++++++++ src/cosim/{fmuproxy => proxy}/remote_fmu.hpp | 34 +-- src/cosim/proxy/remote_slave.cpp | 209 +++++++++++++++++ .../{fmuproxy => proxy}/remote_slave.hpp | 19 +- src/cosim/utility/concurrency.cpp | 4 - tests/CMakeLists.txt | 21 +- .../{fmuproxy => proxy}/SystemStructure.ssd | 4 +- tests/proxyfmu_integration_unittest.cpp | 190 +++++++++++++++ tests/proxyfmu_library_unittest.cpp | 92 ++++++++ tests/ssp_loader_fmuproxy_test.cpp | 37 --- tools/housekeeping | 4 - tools/tmp_cleanup | 3 + 33 files changed, 745 insertions(+), 1084 deletions(-) delete mode 100644 cmake/FindTHRIFT.cmake delete mode 100644 src/cosim/fmuproxy/fmuproxy_client.cpp delete mode 100644 src/cosim/fmuproxy/fmuproxy_client.hpp delete mode 100644 src/cosim/fmuproxy/fmuproxy_helper.hpp delete mode 100644 src/cosim/fmuproxy/remote_fmu.cpp delete mode 100644 src/cosim/fmuproxy/remote_slave.cpp delete mode 100644 src/cosim/fmuproxy/service.thrift delete mode 100644 src/cosim/fmuproxy/thrift_state.cpp delete mode 100644 src/cosim/fmuproxy/thrift_state.hpp rename src/cosim/{fmuproxy/fmuproxy_uri_sub_resolver.cpp => proxy/proxy_uri_sub_resolver.cpp} (50%) rename src/cosim/{fmuproxy/fmuproxy_uri_sub_resolver.hpp => proxy/proxy_uri_sub_resolver.hpp} (55%) create mode 100644 src/cosim/proxy/remote_fmu.cpp rename src/cosim/{fmuproxy => proxy}/remote_fmu.hpp (55%) create mode 100644 src/cosim/proxy/remote_slave.cpp rename src/cosim/{fmuproxy => proxy}/remote_slave.hpp (84%) rename tests/data/ssp/demo/{fmuproxy => proxy}/SystemStructure.ssd (96%) create mode 100644 tests/proxyfmu_integration_unittest.cpp create mode 100644 tests/proxyfmu_library_unittest.cpp delete mode 100644 tests/ssp_loader_fmuproxy_test.cpp diff --git a/.clang-format b/.clang-format index 22dc3d690..f45326c16 100644 --- a/.clang-format +++ b/.clang-format @@ -28,7 +28,7 @@ IncludeBlocks: Regroup IncludeCategories: - Regex: '^[<"]cosim[/.]' Priority: 20 - - Regex: '^[<"](boost|event2|fmilib|gsl|nlohmann|xercesc|zip)[/.]' + - Regex: '^[<"](boost|event2|fmilib|gsl|nlohmann|proxyfmu|xercesc|zip)[/.]' Priority: 30 - Regex: '^"' Priority: 10 diff --git a/.github/workflows/ci-conan.yml b/.github/workflows/ci-conan.yml index 7f2950861..a25667791 100644 --- a/.github/workflows/ci-conan.yml +++ b/.github/workflows/ci-conan.yml @@ -13,7 +13,7 @@ jobs: build_type: [Debug, Release] compiler_version: [7, 8, 9] compiler_libcxx: [libstdc++11] - option_fmuproxy: ['fmuproxy=True', 'fmuproxy=False'] + option_proxyfmu: ['proxyfmu=True', 'proxyfmu=False'] steps: - uses: actions/checkout@v2 @@ -46,7 +46,7 @@ jobs: SHORT_REFNAME="${REFNAME:0:40}" CHANNEL="testing-${SHORT_REFNAME//\//_}" fi - conan create -s build_type=${{ matrix.build_type }} -s compiler.version=${{ matrix.compiler_version }} -s compiler.libcxx=${{ matrix.compiler_libcxx }} -o ${{ matrix.option_fmuproxy }} -b missing . osp/${CHANNEL} + conan create -s build_type=${{ matrix.build_type }} -s compiler.version=${{ matrix.compiler_version }} -s compiler.libcxx=${{ matrix.compiler_libcxx }} -o ${{ matrix.option_proxyfmu }} -b missing . osp/${CHANNEL} conan upload --all -c -r osp '*' EOF chmod 0755 /tmp/osp-builder-docker/entrypoint.sh @@ -74,7 +74,7 @@ jobs: os: [windows-2016] build_type: [Debug, Release] compiler_version: [15] - option_fmuproxy: ['fmuproxy=True', 'fmuproxy=False'] + option_proxyfmu: ['proxyfmu=True', 'proxyfmu=False'] steps: - uses: actions/checkout@v2 @@ -95,6 +95,6 @@ jobs: SHORT_REFNAME="${REFNAME:0:40}" CHANNEL="testing-${SHORT_REFNAME//\//_}" fi - conan create -s build_type=${{ matrix.build_type }} -s compiler.version=${{ matrix.compiler_version }} -o ${{ matrix.option_fmuproxy }} -b missing . osp/${CHANNEL} + conan create -s build_type=${{ matrix.build_type }} -s compiler.version=${{ matrix.compiler_version }} -o ${{ matrix.option_proxyfmu }} -b missing . osp/${CHANNEL} - name: Conan upload run: conan upload --all -c -r osp '*' diff --git a/CMakeLists.txt b/CMakeLists.txt index 62b316a78..6b6729907 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -14,8 +14,7 @@ option(LIBCOSIM_BUILD_APIDOC "Build API documentation (requires Doxygen)" ON) option(LIBCOSIM_BUILD_PRIVATE_APIDOC "Build private API documentation (only used if LIBCOSIM_BUILD_APIDOC=ON)" OFF) option(LIBCOSIM_STANDALONE_INSTALLATION "Whether to build for a standalone installation (Linux only; sets a relative RPATH)" OFF) option(LIBCOSIM_USING_CONAN "Whether Conan is used for package management" OFF) -option(LIBCOSIM_WITH_FMUPROXY "Whether or not to build with fmuproxy integration" OFF) -option(LIBCOSIM_TEST_FMUPROXY "Whether or not to run fmuproxy tests (only if LIBCOSIM_BUILD_TESTS=ON and LIBCOSIM_WITH_FMUPROXY=ON)" ON) +option(LIBCOSIM_WITH_PROXYFMU "Whether or not to build with proxy-fmu integration" OFF) # ============================================================================== # Global internal configuration @@ -115,8 +114,8 @@ find_package(FMILibrary REQUIRED) find_package(LIBZIP REQUIRED) find_package(YAML_CPP REQUIRED) find_package(XercesC REQUIRED) -if(LIBCOSIM_WITH_FMUPROXY) - find_package(THRIFT 0.13.0 REQUIRED) +if(LIBCOSIM_WITH_PROXYFMU) + find_package(PROXYFMU CONFIG REQUIRED) endif() # ============================================================================== @@ -148,7 +147,7 @@ if(LIBCOSIM_BUILD_APIDOC) set(DOXYGEN_JAVADOC_AUTOBRIEF "YES") set(DOXYGEN_QT_AUTOBRIEF "YES") set(DOXYGEN_MULTILINE_CPP_IS_BRIEF "YES") - set(DOXYGEN_EXCLUDE_PATTERNS "*.cpp" "*.c" "*/fmuproxy/*service*") + set(DOXYGEN_EXCLUDE_PATTERNS "*.cpp" "*.c") set(doxygenInputs "${CMAKE_SOURCE_DIR}/docs" "${CMAKE_SOURCE_DIR}/include") if(LIBCOSIM_BUILD_PRIVATE_APIDOC) list(APPEND doxygenInputs @@ -209,7 +208,6 @@ install(FILES "${CMAKE_SOURCE_DIR}/cmake/FindFMILibrary.cmake" "${CMAKE_SOURCE_DIR}/cmake/FindLIBZIP.cmake" "${CMAKE_SOURCE_DIR}/cmake/FindMS_GSL.cmake" - "${CMAKE_SOURCE_DIR}/cmake/FindTHRIFT.cmake" "${CMAKE_SOURCE_DIR}/cmake/FindYAML_CPP.cmake" DESTINATION "${LIBCOSIM_CMAKE_INSTALL_DIR}" diff --git a/README.md b/README.md index 54bc27167..ec327093b 100644 --- a/README.md +++ b/README.md @@ -89,14 +89,10 @@ Then, acquire dependencies with Conan: `--settings compiler.libcxx=libstdc++11` to this command; see Step 1 for more information.) -#### FMU-Proxy -To include FMU-Proxy support, run conan install with the additional option: +#### proxyfmu +To include proxyfmu support, run conan install with the additional option: ```bash --o fmuproxy=True -``` -Note that it currently must be built in Release mode e.g. -```bash -conan install .. -s build_type=Release --build=missing -o fmuproxy=True +-o proxyfmu=True ``` Now, we can run CMake to generate the build system. (If you have not installed diff --git a/cmake/FindFMILibrary.cmake b/cmake/FindFMILibrary.cmake index e2dbe04c3..530e7572d 100644 --- a/cmake/FindFMILibrary.cmake +++ b/cmake/FindFMILibrary.cmake @@ -28,7 +28,6 @@ # FMILibrary_LIBRARY - Path to static library. # FMILibrary_SHARED_LIBRARY - Path to shared/import library. # -cmake_minimum_required (VERSION 2.8.11) # Find static library, and use its path prefix to provide a HINTS option to the # other find_*() commands. @@ -97,31 +96,35 @@ unset (_FMILibrary_hints) # Create the IMPORTED targets. if (FMILibrary_LIBRARY) - add_library ("fmilib::static" STATIC IMPORTED) - set_target_properties ("fmilib::static" PROPERTIES - IMPORTED_LINK_INTERFACE_LANGUAGES "C" - IMPORTED_LINK_INTERFACE_LIBRARIES "${CMAKE_DL_LIBS}" - IMPORTED_LOCATION "${FMILibrary_LIBRARY}" - INTERFACE_COMPILE_DEFINITIONS "FMILibrary_STATIC_LIB_ONLY" - INTERFACE_INCLUDE_DIRECTORIES "${FMILibrary_INCLUDE_DIRS}") + if (NOT TARGET fmilib::static) + add_library ("fmilib::static" STATIC IMPORTED) + set_target_properties ("fmilib::static" PROPERTIES + IMPORTED_LINK_INTERFACE_LANGUAGES "C" + IMPORTED_LINK_INTERFACE_LIBRARIES "${CMAKE_DL_LIBS}" + IMPORTED_LOCATION "${FMILibrary_LIBRARY}" + INTERFACE_COMPILE_DEFINITIONS "FMILibrary_STATIC_LIB_ONLY" + INTERFACE_INCLUDE_DIRECTORIES "${FMILibrary_INCLUDE_DIRS}") + endif() endif () if (FMILibrary_SHARED_LIBRARY) - add_library ("fmilib::shared" SHARED IMPORTED) - set_target_properties ("fmilib::shared" PROPERTIES - IMPORTED_LINK_INTERFACE_LANGUAGES "C" - INTERFACE_INCLUDE_DIRECTORIES "${FMILibrary_INCLUDE_DIRS}") - if (WIN32) + if (NOT TARGET fmilib::shared) + add_library ("fmilib::shared" SHARED IMPORTED) set_target_properties ("fmilib::shared" PROPERTIES - IMPORTED_IMPLIB "${FMILibrary_SHARED_LIBRARY}") - if (FMILibrary_DLL) + IMPORTED_LINK_INTERFACE_LANGUAGES "C" + INTERFACE_INCLUDE_DIRECTORIES "${FMILibrary_INCLUDE_DIRS}") + if (WIN32) set_target_properties ("fmilib::shared" PROPERTIES - IMPORTED_LOCATION "${FMILibrary_DLL}") + IMPORTED_IMPLIB "${FMILibrary_SHARED_LIBRARY}") + if (FMILibrary_DLL) + set_target_properties ("fmilib::shared" PROPERTIES + IMPORTED_LOCATION "${FMILibrary_DLL}") + endif () + else () # not WIN32 + set_target_properties ("fmilib::shared" PROPERTIES + IMPORTED_LOCATION "${FMILibrary_SHARED_LIBRARY}") endif () - else () # not WIN32 - set_target_properties ("fmilib::shared" PROPERTIES - IMPORTED_LOCATION "${FMILibrary_SHARED_LIBRARY}") - endif () + endif() endif () # Set the FMILibrary_LIBRARIES variable. diff --git a/cmake/FindTHRIFT.cmake b/cmake/FindTHRIFT.cmake deleted file mode 100644 index 3e6939988..000000000 --- a/cmake/FindTHRIFT.cmake +++ /dev/null @@ -1,39 +0,0 @@ -# Find Thrift -# -# Find the native Thrift headers and libraries. -# -# THRIFT_INCLUDE_DIRS - where to find thrift/thrift.h -# THRIFT_LIBRARIES - List of libraries when using Thrift. -# THRIFT_FOUND - True if Thrift found. -# - -find_path(THRIFT_INCLUDE_DIR NAMES thrift/Thrift.h) -mark_as_advanced(THRIFT_INCLUDE_DIR) - -find_library(THRIFT_LIBRARY NAMES thrift thriftd thriftmd thriftmdd) -mark_as_advanced(THRIFT_LIBRARY) - -find_program(THRIFT_EXECUTABLE NAMES thrift) -mark_as_advanced(THRIFT_EXECUTABLE) - -include(FindPackageHandleStandardArgs) -FIND_PACKAGE_HANDLE_STANDARD_ARGS(THRIFT - REQUIRED_VARS THRIFT_LIBRARY THRIFT_INCLUDE_DIR) - -if (THRIFT_FOUND) - - set(THRIFT_INCLUDE_DIRS ${THRIFT_INCLUDE_DIR}) - - if (NOT THRIFT_LIBRARIES) - set(THRIFT_LIBRARIES ${THRIFT_LIBRARY}) - endif() - - if (NOT TARGET thrift::thrift) - add_library(thrift::thrift UNKNOWN IMPORTED) - set_target_properties(thrift::thrift PROPERTIES - INTERFACE_INCLUDE_DIRECTORIES "${THRIFT_INCLUDE_DIR}") - set_property(TARGET thrift::thrift APPEND PROPERTY - IMPORTED_LOCATION "${THRIFT_LIBRARY}") - endif() - -endif() diff --git a/cmake/project-config.cmake.in b/cmake/project-config.cmake.in index 411761ecc..06228c8cd 100644 --- a/cmake/project-config.cmake.in +++ b/cmake/project-config.cmake.in @@ -13,8 +13,8 @@ find_dependency(LIBZIP REQUIRED) find_dependency(YAML_CPP REQUIRED) find_dependency(XercesC REQUIRED) -if(@LIBCOSIM_WITH_FMUPROXY@) - find_dependency(THRIFT REQUIRED) +if(@LIBCOSIM_WITH_PROXYFMU@) + find_dependency(PROXYFMU CONFIG REQUIRED) endif() list(REMOVE_AT CMAKE_MODULE_PATH -1) diff --git a/conanfile.py b/conanfile.py index d673a9641..2ef448d09 100755 --- a/conanfile.py +++ b/conanfile.py @@ -27,9 +27,9 @@ class LibcosimConan(ConanFile): "xz_utils/5.2.5" ) - options = {"fmuproxy": [True, False]} + options = {"proxyfmu": [True, False]} default_options = ( - "fmuproxy=False", + "proxyfmu=False", "boost:shared=True", "fmilibrary:shared=True", "libzip:shared=True", @@ -43,23 +43,24 @@ def is_tests_enabled(self): def set_version(self): self.version = tools.load(path.join(self.recipe_folder, "version.txt")).strip() + def requirements(self): + if self.options.proxyfmu: + self.requires("proxyfmu/0.2.2@osp/testing") + def imports(self): binDir = os.path.join("output", str(self.settings.build_type).lower(), "bin") + self.copy("proxyfmu*", dst=binDir, src="bin", keep_path=False) + self.copy("proxyfmu*", dst="tests", src="bin", keep_path=False) self.copy("*.dll", dst=binDir, keep_path=False) self.copy("*.pdb", dst=binDir, keep_path=False) - def requirements(self): - if self.options.fmuproxy: - self.requires("thrift/0.13.0") - def configure_cmake(self): cmake = CMake(self) cmake.definitions["LIBCOSIM_USING_CONAN"] = "ON" cmake.definitions["LIBCOSIM_BUILD_APIDOC"] = "OFF" cmake.definitions["LIBCOSIM_BUILD_TESTS"] = self.is_tests_enabled() - if self.options.fmuproxy: - cmake.definitions["LIBCOSIM_WITH_FMUPROXY"] = "ON" - cmake.definitions["LIBCOSIM_TEST_FMUPROXY"] = "OFF" # Temporary, to be removed again in PR #633 + if self.options.proxyfmu: + cmake.definitions["LIBCOSIM_WITH_PROXYFMU"] = "ON" cmake.configure() return cmake diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 3f8c22ebb..92424c66e 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -104,21 +104,16 @@ set(generatedSources "cosim/lib_info.cpp" ) -if(LIBCOSIM_WITH_FMUPROXY) +if(LIBCOSIM_WITH_PROXYFMU) list(APPEND privateHeaders - "cosim/fmuproxy/fmuproxy_client.hpp" - "cosim/fmuproxy/fmuproxy_helper.hpp" - "cosim/fmuproxy/fmuproxy_uri_sub_resolver.hpp" - "cosim/fmuproxy/remote_fmu.hpp" - "cosim/fmuproxy/remote_slave.hpp" - "cosim/fmuproxy/thrift_state.hpp" + "cosim/proxy/proxy_uri_sub_resolver.hpp" + "cosim/proxy/remote_fmu.hpp" + "cosim/proxy/remote_slave.hpp" ) list(APPEND sources - "cosim/fmuproxy/fmuproxy_client.cpp" - "cosim/fmuproxy/fmuproxy_uri_sub_resolver.cpp" - "cosim/fmuproxy/remote_slave.cpp" - "cosim/fmuproxy/remote_fmu.cpp" - "cosim/fmuproxy/thrift_state.cpp" + "cosim/proxy/proxy_uri_sub_resolver.cpp" + "cosim/proxy/remote_fmu.cpp" + "cosim/proxy/remote_slave.cpp" ) endif() @@ -151,30 +146,6 @@ add_custom_command( ) list(APPEND generatedFiles "${ospSystemStructureHeader}") -# Generate FMU-proxy classes from Thrift service definitions -if(LIBCOSIM_WITH_FMUPROXY) - if(NOT THRIFT_EXECUTABLE) - message(FATAL_ERROR "The thrift compiler was not found. Cannot build with FMU-Proxy support.") - endif() - set(thriftServiceDefinition "${CMAKE_CURRENT_SOURCE_DIR}/cosim/fmuproxy/service.thrift") - set(thriftGenerated - "${generatedSourcesDir}/FmuService.h" - "${generatedSourcesDir}/FmuService.cpp" - "${generatedSourcesDir}/service_types.h" - "${generatedSourcesDir}/service_types.cpp" - ) - add_custom_command( - OUTPUT ${thriftGenerated} - COMMAND "${THRIFT_EXECUTABLE}" - "--gen" "cpp:no_skeleton" - "-out" "${generatedSourcesDir}" - "${thriftServiceDefinition}" - DEPENDS "${thriftServiceDefinition}" - ) - add_library(fmuproxy-service OBJECT ${thriftGenerated}) - set_target_properties(fmuproxy-service PROPERTIES POSITION_INDEPENDENT_CODE ON) - list(APPEND generatedFiles "$") -endif() # ============================================================================== # Target definition @@ -210,13 +181,10 @@ target_link_libraries(cosim yaml-cpp ) -if(LIBCOSIM_WITH_FMUPROXY) - target_compile_definitions(cosim PRIVATE "HAS_FMUPROXY") - target_link_libraries(cosim - PUBLIC - thrift::thrift - ) -endif() +if(LIBCOSIM_WITH_PROXYFMU) + target_compile_definitions(cosim PRIVATE "HAS_PROXYFMU") + target_link_libraries(cosim PRIVATE proxyfmu::proxyfmu-client) +endif () if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU") target_link_libraries(cosim INTERFACE "atomic" "stdc++fs") diff --git a/src/cosim/algorithm/fixed_step_algorithm.cpp b/src/cosim/algorithm/fixed_step_algorithm.cpp index 7545eb7ad..36360559a 100644 --- a/src/cosim/algorithm/fixed_step_algorithm.cpp +++ b/src/cosim/algorithm/fixed_step_algorithm.cpp @@ -3,9 +3,6 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ -#if defined(WIN32) && !defined(NOMINMAX) -# define NOMINMAX -#endif #include "cosim/algorithm/fixed_step_algorithm.hpp" #include "cosim/error.hpp" diff --git a/src/cosim/fmuproxy/fmuproxy_client.cpp b/src/cosim/fmuproxy/fmuproxy_client.cpp deleted file mode 100644 index ad9ae80b9..000000000 --- a/src/cosim/fmuproxy/fmuproxy_client.cpp +++ /dev/null @@ -1,110 +0,0 @@ -/* - * This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at https://mozilla.org/MPL/2.0/. - */ -#include -#include -#include -#include -#include - -#include -#include - -#ifdef _MSC_VER -# pragma warning(push) -# pragma warning(disable : 4245 4706) -#endif - -#include -#include -#include - -#ifdef _MSC_VER -# pragma warning(pop) -#endif - -using namespace fmuproxy::thrift; -using namespace apache::thrift::transport; -using namespace apache::thrift::protocol; - -namespace fs = cosim::filesystem; - -namespace -{ - -void read_data(const std::string& fileName, std::string& data) -{ - FILE* file = fopen(fileName.c_str(), "rb"); - if (file == nullptr) return; - fseek(file, 0, SEEK_END); - const auto size = ftell(file); - fclose(file); - - file = fopen(fileName.c_str(), "rb"); - data.resize(size); -#if defined(__GNUC__) - size_t read __attribute__((unused)) = fread(data.data(), sizeof(unsigned char), size, file); -#else - fread(data.data(), sizeof(unsigned char), size, file); -#endif - fclose(file); -} - -} // namespace - -cosim::fmuproxy::fmuproxy_client::fmuproxy_client( - const std::string& host, - const unsigned int port) -{ - std::shared_ptr socket(new TSocket(host, port)); - std::shared_ptr transport(new TFramedTransport(socket)); - std::shared_ptr protocol(new TBinaryProtocol(transport)); - std::shared_ptr<::fmuproxy::thrift::FmuServiceIf> client; - - client = std::make_shared(protocol); - - try { - transport->open(); - } catch (TTransportException&) { - std::string msg = "Failed to connect to remote FMU @ " + host + ":" + std::to_string(port); - COSIM_PANIC_M(msg.c_str()); - } - state_ = std::make_shared(client, transport); -} - -std::shared_ptr -cosim::fmuproxy::fmuproxy_client::from_url(const std::string& url) -{ - BOOST_LOG_SEV(log::logger(), log::debug) << "fmu-proxy will load FMU from url '" << url << "'"; - FmuId fmuId; - state_->client_->load_from_url(fmuId, url); - return from_guid(fmuId); -} - -std::shared_ptr -cosim::fmuproxy::fmuproxy_client::from_file(const std::string& file) -{ - BOOST_LOG_SEV(log::logger(), log::debug) << "fmu-proxy will load FMU from file '" << file << "'"; - - if (!fs::exists(file)) { - std::string msg = "No such file '" + file + "'!"; - COSIM_PANIC_M(msg.c_str()); - } - - const auto name = fs::path(file).stem().string(); - - std::string data; - read_data(file, data); - - FmuId fmuId; - state_->client_->load_from_file(fmuId, name, data); - return from_guid(fmuId); -} - -std::shared_ptr -cosim::fmuproxy::fmuproxy_client::from_guid(const std::string& guid) -{ - return std::make_shared(guid, state_); -} diff --git a/src/cosim/fmuproxy/fmuproxy_client.hpp b/src/cosim/fmuproxy/fmuproxy_client.hpp deleted file mode 100644 index add5e370e..000000000 --- a/src/cosim/fmuproxy/fmuproxy_client.hpp +++ /dev/null @@ -1,48 +0,0 @@ -/** - * \file - * Defines the FMU-proxy client - * - * \copyright - * This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at https://mozilla.org/MPL/2.0/. - */ -#ifndef COSIM_FMUPROXY_FMUPROXYCLIENT_HPP -#define COSIM_FMUPROXY_FMUPROXYCLIENT_HPP - -#include "FmuService.h" - -#include - -#include - -namespace cosim -{ - -namespace fmuproxy -{ - -class fmuproxy_client -{ - -public: - fmuproxy_client( - const std::string& host, - unsigned int port); - - std::shared_ptr from_url(const std::string& url); - - std::shared_ptr from_file(const std::string& file); - - std::shared_ptr from_guid(const std::string& guid); - -private: - std::shared_ptr state_; -}; - -} // namespace fmuproxy - -} // namespace cosim - - -#endif diff --git a/src/cosim/fmuproxy/fmuproxy_helper.hpp b/src/cosim/fmuproxy/fmuproxy_helper.hpp deleted file mode 100644 index a1283a7bf..000000000 --- a/src/cosim/fmuproxy/fmuproxy_helper.hpp +++ /dev/null @@ -1,122 +0,0 @@ -/** - * \file - * - * \copyright - * This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at https://mozilla.org/MPL/2.0/. - */ -#ifndef COSIM_FMUPROXY_FMUPROXY_HELPER_HPP -#define COSIM_FMUPROXY_FMUPROXY_HELPER_HPP - -#include "service_types.h" - -#include "cosim/error.hpp" -#include "cosim/model_description.hpp" - -#include - - -namespace -{ - -inline cosim::variable_causality parse_causality(const std::string& c) -{ - if (c == "input") { - return cosim::variable_causality::input; - } else if (c == "output") { - return cosim::variable_causality::output; - } else if (c == "parameter") { - return cosim::variable_causality::parameter; - } else if (c == "calculated_parameter") { - return cosim::variable_causality::calculated_parameter; - } else if (c == "local" || c == "independent" || c == "unknown" || c == "") { - return cosim::variable_causality::local; - } else { - const auto err = "Failed to parse causality: '" + c + "'"; - COSIM_PANIC_M(err.c_str()); - } -} - -inline cosim::variable_variability parse_variability(const std::string& v) -{ - if (v == "constant") { - return cosim::variable_variability::constant; - } else if (v == "discrete") { - return cosim::variable_variability::discrete; - } else if (v == "fixed") { - return cosim::variable_variability::fixed; - } else if (v == "tunable") { - return cosim::variable_variability::tunable; - } else if (v == "continuous" || v == "unknown" || v == "") { - return cosim::variable_variability::continuous; - } else { - const auto err = "Failed to parse variability: '" + v + "'"; - COSIM_PANIC_M(err.c_str()); - } -} - -inline cosim::variable_type get_type(const fmuproxy::thrift::ScalarVariable& v) -{ - if (v.attribute.__isset.integer_attribute) { - return cosim::variable_type::integer; - } else if (v.attribute.__isset.real_attribute) { - return cosim::variable_type::real; - } else if (v.attribute.__isset.string_attribute) { - return cosim::variable_type::string; - } else if (v.attribute.__isset.boolean_attribute) { - return cosim::variable_type::boolean; - } else if (v.attribute.__isset.enumeration_attribute) { - return cosim::variable_type::enumeration; - } else { - const auto err = "Failed to get type of variable: '" + v.name + "'"; - COSIM_PANIC_M(err.c_str()); - } -} - -inline cosim::variable_description convert(const fmuproxy::thrift::ScalarVariable& v) -{ - cosim::variable_description var; - var.name = v.name; - var.reference = (cosim::value_reference)v.value_reference; - var.causality = parse_causality(v.causality); - var.variability = parse_variability(v.variability); - var.type = get_type(v); - if (v.attribute.__isset.integer_attribute) { - var.start = v.attribute.integer_attribute.start; - } else if (v.attribute.__isset.real_attribute) { - var.start = v.attribute.real_attribute.start; - } else if (v.attribute.__isset.string_attribute) { - var.start = v.attribute.string_attribute.start; - } else if (v.attribute.__isset.boolean_attribute) { - var.start = v.attribute.boolean_attribute.start; - } else if (v.attribute.__isset.enumeration_attribute) { - var.start = v.attribute.enumeration_attribute.start; - } - return var; -} - -inline std::vector convert(fmuproxy::thrift::ModelVariables& vars) -{ - std::vector modelVariables; - for (const auto& v : vars) { - modelVariables.push_back(convert(v)); - } - return modelVariables; -} - -inline std::shared_ptr convert(fmuproxy::thrift::ModelDescription& md) -{ - auto modelDescription = std::make_shared(); - modelDescription->name = md.modelName; - modelDescription->author = md.author; - modelDescription->uuid = md.guid; - modelDescription->version = md.version; - modelDescription->description = md.description; - modelDescription->variables = convert(md.model_variables); - return modelDescription; -} - -} // namespace - -#endif diff --git a/src/cosim/fmuproxy/remote_fmu.cpp b/src/cosim/fmuproxy/remote_fmu.cpp deleted file mode 100644 index 09d584787..000000000 --- a/src/cosim/fmuproxy/remote_fmu.cpp +++ /dev/null @@ -1,41 +0,0 @@ -/* - * This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at https://mozilla.org/MPL/2.0/. - */ -#include -#include -#include -#include - -using namespace fmuproxy::thrift; -using namespace apache::thrift::transport; -using namespace apache::thrift::protocol; - -cosim::fmuproxy::remote_fmu::remote_fmu( - const FmuId& fmuId, - std::shared_ptr state) - : fmuId_(fmuId) - , state_(std::move(state)) -{ - ::fmuproxy::thrift::ModelDescription md = ModelDescription(); - state_->client().get_model_description(md, fmuId); - modelDescription_ = convert(md); -} - -std::shared_ptr cosim::fmuproxy::remote_fmu::description() const noexcept -{ - return modelDescription_; -} - -std::shared_ptr cosim::fmuproxy::remote_fmu::instantiate_slave() -{ - InstanceId instanceId; - state_->client().create_instance(instanceId, fmuId_); - return std::make_shared(instanceId, state_, modelDescription_); -} - -std::shared_ptr cosim::fmuproxy::remote_fmu::instantiate(std::string_view) -{ - return cosim::make_background_thread_slave(instantiate_slave()); -} diff --git a/src/cosim/fmuproxy/remote_slave.cpp b/src/cosim/fmuproxy/remote_slave.cpp deleted file mode 100644 index 3736e8b26..000000000 --- a/src/cosim/fmuproxy/remote_slave.cpp +++ /dev/null @@ -1,192 +0,0 @@ -/* - * This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at https://mozilla.org/MPL/2.0/. - */ -#include -#include -#include - -#include - - -cosim::fmuproxy::remote_slave::remote_slave(std::string instanceId, - std::shared_ptr state, - std::shared_ptr modelDescription) - : terminated_(false) - , instanceId_(std::move(instanceId)) - , state_(std::move(state)) - , modelDescription_(std::move(modelDescription)) -{ } - -cosim::model_description cosim::fmuproxy::remote_slave::model_description() const -{ - return *modelDescription_; -} - -void cosim::fmuproxy::remote_slave::setup(cosim::time_point startTime, std::optional stopTime, - std::optional relativeTolerance) -{ - startTime_ = startTime; - - double start = to_double_time_point(startTime); - double stop = to_double_time_point(stopTime.value_or(cosim::to_time_point(0))); - double tolerance = relativeTolerance.value_or(0); - - ::fmuproxy::thrift::Status::type status; - status = state_->client().setup_experiment(instanceId_, start, stop, tolerance); - if (status != ::fmuproxy::thrift::Status::OK_STATUS) { - COSIM_PANIC(); - } - status = state_->client().enter_initialization_mode(instanceId_); - if (status != ::fmuproxy::thrift::Status::OK_STATUS) { - COSIM_PANIC(); - } -} - -void cosim::fmuproxy::remote_slave::start_simulation() -{ - auto status = state_->client().exit_initialization_mode(instanceId_); - if (status != ::fmuproxy::thrift::Status::OK_STATUS) { - COSIM_PANIC(); - } -} - -void cosim::fmuproxy::remote_slave::end_simulation() -{ - if (!terminated_) { - auto status = state_->client().terminate(instanceId_); - if (status != ::fmuproxy::thrift::Status::OK_STATUS) { - COSIM_PANIC(); - } - terminated_ = true; - } -} - -cosim::step_result cosim::fmuproxy::remote_slave::do_step(cosim::time_point, cosim::duration deltaT) -{ - - double dt = to_double_duration(deltaT, startTime_); - - ::fmuproxy::thrift::StepResult result; - state_->client().step(result, instanceId_, dt); - if (result.status != ::fmuproxy::thrift::Status::OK_STATUS) { - COSIM_PANIC(); - } - return cosim::step_result::complete; -} - -void cosim::fmuproxy::remote_slave::get_real_variables(gsl::span variables, - gsl::span values) const -{ - COSIM_INPUT_CHECK(variables.size() == values.size()); - if (variables.empty()) return; - ::fmuproxy::thrift::RealRead read; - ::fmuproxy::thrift::ValueReferences vr(variables.begin(), variables.end()); - state_->client().read_real(read, instanceId_, vr); - if (read.status != ::fmuproxy::thrift::Status::OK_STATUS) { - COSIM_PANIC(); - } - for (unsigned int i = 0; i < read.value.size(); i++) { - values[i] = read.value[i]; - } -} - -void cosim::fmuproxy::remote_slave::get_integer_variables(gsl::span variables, - gsl::span values) const -{ - COSIM_INPUT_CHECK(variables.size() == values.size()); - if (variables.empty()) return; - ::fmuproxy::thrift::IntegerRead read; - ::fmuproxy::thrift::ValueReferences vr(variables.begin(), variables.end()); - state_->client().read_integer(read, instanceId_, vr); - if (read.status != ::fmuproxy::thrift::Status::OK_STATUS) { - COSIM_PANIC(); - } - for (unsigned int i = 0; i < read.value.size(); i++) { - values[i] = read.value[i]; - } -} - -void cosim::fmuproxy::remote_slave::get_boolean_variables(gsl::span variables, - gsl::span values) const -{ - COSIM_INPUT_CHECK(variables.size() == values.size()); - if (variables.empty()) return; - ::fmuproxy::thrift::BooleanRead read; - ::fmuproxy::thrift::ValueReferences vr(variables.begin(), variables.end()); - state_->client().read_boolean(read, instanceId_, vr); - if (read.status != ::fmuproxy::thrift::Status::OK_STATUS) { - COSIM_PANIC(); - } - for (unsigned int i = 0; i < read.value.size(); i++) { - values[i] = read.value[i]; - } -} - -void cosim::fmuproxy::remote_slave::get_string_variables(gsl::span variables, - gsl::span values) const -{ - COSIM_INPUT_CHECK(variables.size() == values.size()); - if (variables.empty()) return; - ::fmuproxy::thrift::StringRead read; - ::fmuproxy::thrift::ValueReferences vr(variables.begin(), variables.end()); - state_->client().read_string(read, instanceId_, vr); - for (unsigned int i = 0; i < read.value.size(); i++) { - values[i] = read.value[i]; - } -} - -void cosim::fmuproxy::remote_slave::set_real_variables(gsl::span variables, - gsl::span values) -{ - COSIM_INPUT_CHECK(variables.size() == values.size()); - if (variables.empty()) return; - ::fmuproxy::thrift::ValueReferences vr(variables.begin(), variables.end()); - auto status = state_->client().write_real(instanceId_, vr, std::vector(values.begin(), values.end())); - if (status != ::fmuproxy::thrift::Status::OK_STATUS) { - COSIM_PANIC(); - } -} - -void cosim::fmuproxy::remote_slave::set_integer_variables(gsl::span variables, - gsl::span values) -{ - COSIM_INPUT_CHECK(variables.size() == values.size()); - if (variables.empty()) return; - ::fmuproxy::thrift::ValueReferences vr(variables.begin(), variables.end()); - auto status = state_->client().write_integer(instanceId_, vr, std::vector(values.begin(), values.end())); - if (status != ::fmuproxy::thrift::Status::OK_STATUS) { - COSIM_PANIC(); - } -} - -void cosim::fmuproxy::remote_slave::set_boolean_variables(gsl::span variables, - gsl::span values) -{ - COSIM_INPUT_CHECK(variables.size() == values.size()); - if (variables.empty()) return; - ::fmuproxy::thrift::ValueReferences vr(variables.begin(), variables.end()); - auto status = state_->client().write_boolean(instanceId_, vr, std::vector(values.begin(), values.end())); - if (status != ::fmuproxy::thrift::Status::OK_STATUS) { - COSIM_PANIC(); - } -} - -void cosim::fmuproxy::remote_slave::set_string_variables(gsl::span variables, - gsl::span values) -{ - COSIM_INPUT_CHECK(variables.size() == values.size()); - if (variables.empty()) return; - ::fmuproxy::thrift::ValueReferences vr(variables.begin(), variables.end()); - auto status = state_->client().write_string(instanceId_, vr, std::vector(values.begin(), values.end())); - if (status != ::fmuproxy::thrift::Status::OK_STATUS) { - COSIM_PANIC(); - } -} - -cosim::fmuproxy::remote_slave::~remote_slave() -{ - end_simulation(); - state_->client().freeInstance(instanceId_); -} diff --git a/src/cosim/fmuproxy/service.thrift b/src/cosim/fmuproxy/service.thrift deleted file mode 100644 index 00381ae13..000000000 --- a/src/cosim/fmuproxy/service.thrift +++ /dev/null @@ -1,218 +0,0 @@ -/* - * The MIT License - * - * Copyright 2017-2019 Norwegian University of Technology (NTNU) - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ - -namespace cpp fmuproxy.thrift -namespace java no.ntnu.ihb.fmuproxy.thrift - -typedef string FmuId -typedef string InstanceId -typedef i64 ValueReference -typedef i64 FmuState -typedef list DirectionalDerivative -typedef list ValueReferences -typedef list IntArray -typedef list RealArray -typedef list StringArray -typedef list BooleanArray - - -enum Status { - OK_STATUS = 0, - WARNING_STATUS = 1, - DISCARD_STATUS = 2, - ERROR_STATUS = 3, - FATAL_STATUS = 4, - PENDING_STATUS = 5 -} - -struct IntegerAttribute { - 1: i32 min, - 2: i32 max, - 3: i32 start, - 4: string quantity -} - -struct RealAttribute { - 1: double min, - 2: double max, - 3: double start, - 4: string quantity -} - -struct StringAttribute { - 1: string start -} - -struct BooleanAttribute { - 1: bool start -} - -struct EnumerationAttribute { - 1: i32 min, - 2: i32 max, - 3: i32 start, - 4: string quantity -} - -union ScalarVariableAttribute { - 1: IntegerAttribute integer_attribute, - 2: RealAttribute real_attribute, - 3: StringAttribute string_attribute, - 4: BooleanAttribute boolean_attribute, - 5: EnumerationAttribute enumeration_attribute -} - -struct ScalarVariable { - 1: string name, - 2: ValueReference value_reference, - 3: optional string description, - 4: optional string initial, - 5: optional string causality, - 6: optional string variability, - 7: ScalarVariableAttribute attribute -} - -typedef list ModelVariables - -struct Unknown { - 1: i32 index, - 2: list dependencies, - 3: list dependencies_kind -} - -struct ModelStructure { - 1: list outputs, - 2: list derivatives, - 3: list initial_unknowns -} - -struct DefaultExperiment { - 1: double startTime, - 2: double stopTime, - 3: double tolerance, - 4: double stepSize -} - -struct StepResult { - 1: Status status, - 2: double simulation_time -} - -struct IntegerRead { - 1: list value, - 2: Status status -} - -struct RealRead { - 1: list value, - 2: Status status -} - -struct StringRead { - 1: list value, - 2: Status status -} - -struct BooleanRead { - 1: list value, - 2: Status status -} - -struct ModelDescription { - 1: string guid, - 2: string fmi_version, - 3: string modelName, - 4: optional string license, - 5: optional string copyright, - 6: optional string author, - 7: optional string version, - 8: optional string description, - 9: optional string generation_tool, - 10: optional string generation_date_and_time, - 11: optional DefaultExperiment default_experiment, - 12: optional string variable_naming_convention, - 13: ModelVariables model_variables, - 14: ModelStructure model_structure, - - 15: string model_identifier, - 16: bool can_get_and_set_fmu_state, - 17: bool can_serialize_fmu_state, - 18: bool provides_directional_derivative, - 19: bool can_handle_variable_communication_step_size, - 20: bool can_interpolate_inputs, - 21: i32 max_output_derivative_order -} - -exception NoSuchFmuException { - 1: string message -} - -exception NoSuchInstanceException { - 1: string message -} - -exception NoSuchVariableException { - 1: string message -} - -exception UnsupportedOperationException { - 1: string message -} - -struct DirectionalDerivativeResult { - 1: DirectionalDerivative dv_unknown_ref - 2: Status status -} - -service FmuService { - - FmuId load_from_url(1: string url) - FmuId load_from_file(1: string name, 2: binary data) - - ModelDescription get_model_description(1: FmuId fmuId) throws (1: NoSuchFmuException ex) - - InstanceId create_instance(1: FmuId fmuId) throws (1: UnsupportedOperationException ex1, 2: NoSuchFmuException ex2) - - Status setup_experiment(1: InstanceId instanceId, 2: double start, 3: double stop, 4: double tolerance) throws (1: NoSuchInstanceException ex) - Status enter_initialization_mode(1: InstanceId instanceId) throws (1: NoSuchInstanceException ex) - Status exit_initialization_mode(1: InstanceId instanceId) throws (1: NoSuchInstanceException ex) - - StepResult step(1: InstanceId instanceId, 2: double stepSize) throws (1: NoSuchInstanceException ex) - Status reset(1: InstanceId instanceId) throws (1: NoSuchInstanceException ex) - Status terminate(1: InstanceId instanceId) throws (1: NoSuchInstanceException ex) - void freeInstance(1: InstanceId instanceId) throws (1: NoSuchInstanceException ex) - - IntegerRead read_integer(1: InstanceId instanceId, 2: ValueReferences vr) throws (1: NoSuchInstanceException ex1, 2: NoSuchVariableException ex2) - RealRead read_real(1: InstanceId instanceId, 2: ValueReferences vr) throws (1: NoSuchInstanceException ex1, 2: NoSuchVariableException ex2) - StringRead read_string(1: InstanceId instanceId, 2: ValueReferences vr) throws (1: NoSuchInstanceException ex1, 2: NoSuchVariableException ex2) - BooleanRead read_boolean(1: InstanceId instanceId, 2: ValueReferences vr) throws (1: NoSuchInstanceException ex1, 2: NoSuchVariableException ex2) - - Status write_integer(1: InstanceId instanceId, 2: ValueReferences vr, 3: IntArray value) throws (1: NoSuchInstanceException ex1, 2: NoSuchVariableException ex2) - Status write_real(1: InstanceId instanceId, 2: ValueReferences vr, 3: RealArray value) throws (1: NoSuchInstanceException ex1, 2: NoSuchVariableException ex2) - Status write_string(1: InstanceId instanceId, 2: ValueReferences vr, 3: StringArray value) throws (1: NoSuchInstanceException ex1, 2: NoSuchVariableException ex2) - Status write_boolean(1: InstanceId instanceId, 2: ValueReferences vr, 3: BooleanArray value) throws (1: NoSuchInstanceException ex1, 2: NoSuchVariableException ex2) - - DirectionalDerivativeResult get_directional_derivative(1: InstanceId instanceId, 2: ValueReferences vUnknownRef, 3: ValueReferences vKnownRef, 4: list dvKnownRef) throws (1: NoSuchInstanceException ex1, 2: UnsupportedOperationException ex2) - -} diff --git a/src/cosim/fmuproxy/thrift_state.cpp b/src/cosim/fmuproxy/thrift_state.cpp deleted file mode 100644 index d2065e623..000000000 --- a/src/cosim/fmuproxy/thrift_state.cpp +++ /dev/null @@ -1,30 +0,0 @@ -/* - * This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at https://mozilla.org/MPL/2.0/. - */ -#include - -#include - -cosim::fmuproxy::thrift_state::thrift_state(std::shared_ptr<::fmuproxy::thrift::FmuServiceIf> client_, - std::shared_ptr transport_) - : client_(std::move(client_)) - , transport_(std::move(transport_)) -{ } - -::fmuproxy::thrift::FmuServiceIf& cosim::fmuproxy::thrift_state::client() -{ - return *client_; -} -apache::thrift::transport::TTransport& cosim::fmuproxy::thrift_state::transport() -{ - return *transport_; -} - -cosim::fmuproxy::thrift_state::~thrift_state() -{ - if (transport_->isOpen()) { - transport_->close(); - } -} diff --git a/src/cosim/fmuproxy/thrift_state.hpp b/src/cosim/fmuproxy/thrift_state.hpp deleted file mode 100644 index 876394b20..000000000 --- a/src/cosim/fmuproxy/thrift_state.hpp +++ /dev/null @@ -1,50 +0,0 @@ -/** - * \file - * - * \copyright - * This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at https://mozilla.org/MPL/2.0/. - */ -#ifndef COSIM_FMUPROXY_SHARED_THRIFT_STATE_HPP -#define COSIM_FMUPROXY_SHARED_THRIFT_STATE_HPP - -#include "FmuService.h" - -#include - -namespace cosim -{ - -namespace fmuproxy -{ - -class remote_fmu; -class fmuproxy_client; - -class thrift_state -{ - - friend class remote_fmu; - friend class fmuproxy_client; - -public: - thrift_state(std::shared_ptr<::fmuproxy::thrift::FmuServiceIf> client_, - std::shared_ptr transport_); - - ::fmuproxy::thrift::FmuServiceIf& client(); - - apache::thrift::transport::TTransport& transport(); - - ~thrift_state(); - -private: - const std::shared_ptr<::fmuproxy::thrift::FmuServiceIf> client_; - const std::shared_ptr transport_; -}; - -} // namespace fmuproxy - -} // namespace cosim - -#endif diff --git a/src/cosim/orchestration.cpp b/src/cosim/orchestration.cpp index 304f364fd..89048cd9e 100644 --- a/src/cosim/orchestration.cpp +++ b/src/cosim/orchestration.cpp @@ -9,8 +9,8 @@ #include "cosim/fmi/fmu.hpp" #include "cosim/log/logger.hpp" -#ifdef HAS_FMUPROXY -# include +#ifdef HAS_PROXYFMU +# include "cosim/proxy/proxy_uri_sub_resolver.hpp" #endif namespace cosim @@ -147,9 +147,8 @@ std::shared_ptr default_model_uri_resolver( resolver->add_sub_resolver( std::make_shared()); } -#ifdef HAS_FMUPROXY - resolver->add_sub_resolver( - std::make_shared()); +#ifdef HAS_PROXYFMU + resolver->add_sub_resolver(std::make_shared()); #endif return resolver; } diff --git a/src/cosim/fmuproxy/fmuproxy_uri_sub_resolver.cpp b/src/cosim/proxy/proxy_uri_sub_resolver.cpp similarity index 50% rename from src/cosim/fmuproxy/fmuproxy_uri_sub_resolver.cpp rename to src/cosim/proxy/proxy_uri_sub_resolver.cpp index e298e6875..15007414d 100644 --- a/src/cosim/fmuproxy/fmuproxy_uri_sub_resolver.cpp +++ b/src/cosim/proxy/proxy_uri_sub_resolver.cpp @@ -3,33 +3,25 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ +#include "cosim/log/logger.hpp" #include -#include -#include -#include -#include +#include +#include +#include -#include - -// examples of uri to resolve -//fmu-proxy://127.0.0.1:9090?guid=2213kjhlkh4lhksdkj -//fmu-proxy://127.0.0.1:9090?url=http://example.com/my_model.fmu -//fmu-proxy://127.0.0.1:9090?file=models/my_model.fmu - -namespace -{ - -std::pair parse_authority(std::string_view auth) +std::pair parse_authority(std::string_view auth) { - const auto colon = auth.find(":"); - const auto host = std::string(auth.substr(0, colon)); - const auto port = std::stoi(std::string(auth.substr(colon + 1))); - return {host, port}; + const auto colonIdx = auth.find(':'); + if (colonIdx == std::string::npos) { + return {std::string(auth), -1}; + } else { + const auto host = std::string(auth.substr(0, colonIdx)); + const auto port = std::stoi(std::string(auth.substr(colonIdx + 1))); + return {host, port}; + } } -} // namespace - -std::shared_ptr cosim::fmuproxy::fmuproxy_uri_sub_resolver::lookup_model( +std::shared_ptr cosim::proxy::proxy_uri_sub_resolver::lookup_model( const cosim::uri& baseUri, const cosim::uri& modelUriReference) { @@ -50,26 +42,26 @@ std::shared_ptr cosim::fmuproxy::fmuproxy_uri_sub_resolver::lookup return model_uri_sub_resolver::lookup_model(baseUri, mur); } -std::shared_ptr cosim::fmuproxy::fmuproxy_uri_sub_resolver::lookup_model(const cosim::uri& modelUri) +std::shared_ptr cosim::proxy::proxy_uri_sub_resolver::lookup_model(const cosim::uri& modelUri) { assert(modelUri.scheme().has_value()); - if (*modelUri.scheme() != "fmu-proxy") return nullptr; + if (*modelUri.scheme() != "proxyfmu") return nullptr; COSIM_INPUT_CHECK(modelUri.authority()); COSIM_INPUT_CHECK(modelUri.query()); - const auto query = *modelUri.query(); const auto auth = parse_authority(*modelUri.authority()); - auto client = cosim::fmuproxy::fmuproxy_client(auth.first, auth.second); + const auto query = *modelUri.query(); + if (query.substr(0, 5) == "file=") { + const auto file = cosim::filesystem::path(std::string(query.substr(5))); + if (!cosim::filesystem::exists(file)) { + throw error(make_error_code(errc::bad_file), "No such file: " + file.string()); + } + if (auth.first == "localhost" && auth.second == -1) { + return std::make_shared(file); + } else { + return std::make_shared(file, proxyfmu::remote_info(auth.first, auth.second)); + } - if (query.substr(0, 5) == "guid=") { - const auto guid = std::string(query.substr(5)); - return client.from_guid(guid); - } else if (query.substr(0, 5) == "file=") { - const auto file = std::string(query.substr(5)); - return client.from_file(file); - } else if (query.substr(0, 4) == "url=") { - const auto url = std::string(query.substr(4)); - return client.from_url(url); } else { return nullptr; } diff --git a/src/cosim/fmuproxy/fmuproxy_uri_sub_resolver.hpp b/src/cosim/proxy/proxy_uri_sub_resolver.hpp similarity index 55% rename from src/cosim/fmuproxy/fmuproxy_uri_sub_resolver.hpp rename to src/cosim/proxy/proxy_uri_sub_resolver.hpp index a54882458..9e7385c67 100644 --- a/src/cosim/fmuproxy/fmuproxy_uri_sub_resolver.hpp +++ b/src/cosim/proxy/proxy_uri_sub_resolver.hpp @@ -6,36 +6,31 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ -#ifndef COSIM_FMUPROXY_URI_SUB_RESOLVER_HPP -#define COSIM_FMUPROXY_URI_SUB_RESOLVER_HPP +#ifndef COSIM_PROXY_FILE_URI_SUB_RESOLVER_HPP +#define COSIM_PROXY_FILE_URI_SUB_RESOLVER_HPP #include namespace cosim { -namespace fmuproxy +namespace proxy { /** * Class for resolving fmu-proxy URI schemes. * - * Supports file, url and guid (pre-loaded on server) FMUs - * - * From file: `fmu-proxy://127.0.0.1:9090?file=models/my_model.fmu` - * From url: `fmu-proxy://127.0.0.1:9090?url=http://example.com/my_model.fmu` - * From guid: `fmu-proxy://127.0.0.1:9090?guid={ads4ffsa4523fgg8}` + * From file: 'proxy-file:///localhost?models/my_model.fmu' */ -class fmuproxy_uri_sub_resolver : public model_uri_sub_resolver +class proxy_uri_sub_resolver : public model_uri_sub_resolver { public: std::shared_ptr lookup_model(const uri& baseUri, const uri& modelUriReference) override; - std::shared_ptr lookup_model(const cosim::uri& uri) override; }; -} // namespace fmuproxy +} // namespace proxy } // namespace cosim diff --git a/src/cosim/proxy/remote_fmu.cpp b/src/cosim/proxy/remote_fmu.cpp new file mode 100644 index 000000000..7702e55f7 --- /dev/null +++ b/src/cosim/proxy/remote_fmu.cpp @@ -0,0 +1,121 @@ +/* + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + */ +#include +#include +#include + +#include + +using namespace proxyfmu; +using namespace proxyfmu::client; + +namespace +{ + +cosim::variable_type get_type(const proxyfmu::fmi::scalar_variable& v) +{ + if (v.is_integer()) { + return cosim::variable_type::integer; + } else if (v.is_real()) { + return cosim::variable_type::real; + } else if (v.is_string()) { + return cosim::variable_type::string; + } else if (v.is_boolean()) { + return cosim::variable_type::boolean; + } else { + throw std::runtime_error("Unsupported variable type"); + } +} + +cosim::variable_causality get_causality(const proxyfmu::fmi::scalar_variable& v) +{ + if (v.causality == "output") { + return cosim::variable_causality::output; + } else if (v.causality == "input") { + return cosim::variable_causality::input; + } else if (v.causality == "parameter") { + return cosim::variable_causality::parameter; + } else if (v.causality == "calculated_parameter") { + return cosim::variable_causality::calculated_parameter; + } else if (v.causality == "local") { + return cosim::variable_causality::local; + } else { + return cosim::variable_causality::local; + } +} + +cosim::variable_variability get_variability(const proxyfmu::fmi::scalar_variable& v) +{ + if (v.variability == "discrete") { + return cosim::variable_variability::discrete; + } else if (v.variability == "fixed") { + return cosim::variable_variability::fixed; + } else if (v.variability == "tunable") { + return cosim::variable_variability::tunable; + } else if (v.variability == "constant") { + return cosim::variable_variability::constant; + } else if (v.variability == "continuous") { + return cosim::variable_variability::continuous; + } else { + return cosim::variable_variability::continuous; + } +} + +std::unique_ptr parse_model_description(const proxyfmu::fmi::model_description& md) +{ + auto _md = std::make_unique(); + _md->uuid = md.guid; + _md->author = md.author; + _md->name = md.modelName; + _md->description = md.description; + + for (auto& var : md.modelVariables) { + cosim::variable_description vd; + vd.name = var.name; + vd.reference = var.vr; + vd.type = get_type(var); + vd.causality = get_causality(var); + vd.variability = get_variability(var); + + if (var.is_real()) { + const auto type = std::get(var.typeAttribute); + vd.start = type.start; + } else if (var.is_integer()) { + const auto type = std::get(var.typeAttribute); + vd.start = type.start; + } else if (var.is_boolean()) { + const auto type = std::get(var.typeAttribute); + vd.start = type.start; + } else if (var.is_string()) { + const auto type = std::get(var.typeAttribute); + vd.start = type.start; + } + + _md->variables.push_back(vd); + } + + return _md; +} + +} // namespace + +cosim::proxy::remote_fmu::remote_fmu(const cosim::filesystem::path& fmuPath, const std::optional& remote) + : fmu_(std::make_unique(fmuPath, remote)) + , modelDescription_(parse_model_description(fmu_->get_model_description())) +{ +} + +std::shared_ptr cosim::proxy::remote_fmu::description() const noexcept +{ + return modelDescription_; +} + +std::shared_ptr cosim::proxy::remote_fmu::instantiate(std::string_view instanceName) +{ + auto proxySlave = fmu_->new_instance(std::string(instanceName)); + auto slave = std::make_shared(std::move(proxySlave), modelDescription_); + return cosim::make_background_thread_slave(slave); +} diff --git a/src/cosim/fmuproxy/remote_fmu.hpp b/src/cosim/proxy/remote_fmu.hpp similarity index 55% rename from src/cosim/fmuproxy/remote_fmu.hpp rename to src/cosim/proxy/remote_fmu.hpp index 964d8b874..7b6a80f02 100644 --- a/src/cosim/fmuproxy/remote_fmu.hpp +++ b/src/cosim/proxy/remote_fmu.hpp @@ -6,53 +6,41 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ -#ifndef COSIM_FMUPROXY_REMOTE_FMU_HPP -#define COSIM_FMUPROXY_REMOTE_FMU_HPP - -#ifdef _WIN32 -//must be included before -# include -#endif - -#include "FmuService.h" +#ifndef COSIM_PROXY_REMOTE_FMU_HPP +#define COSIM_PROXY_REMOTE_FMU_HPP #include -#include #include #include +#include +#include +#include + #include namespace cosim { -namespace fmuproxy +namespace proxy { -class fmuproxy_client; - class remote_fmu : public cosim::model { - friend class fmuproxy_client; - public: - remote_fmu(const ::fmuproxy::thrift::FmuId& fmuId, - std::shared_ptr state); + explicit remote_fmu(const cosim::filesystem::path& fmuPath, const std::optional& remote = std::nullopt); std::shared_ptr description() const noexcept override; - std::shared_ptr instantiate_slave(); - - std::shared_ptr instantiate(std::string_view name = "") override; + std::shared_ptr instantiate(std::string_view name) override; private: - const std::string fmuId_; - std::shared_ptr state_; + std::unique_ptr fmu_; std::shared_ptr modelDescription_; }; -} // namespace fmuproxy +} // namespace proxy } // namespace cosim diff --git a/src/cosim/proxy/remote_slave.cpp b/src/cosim/proxy/remote_slave.cpp new file mode 100644 index 000000000..11444721b --- /dev/null +++ b/src/cosim/proxy/remote_slave.cpp @@ -0,0 +1,209 @@ +/* + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + */ +#include +#include +#include + +#include + + +namespace +{ + +void bad_status_throw(const std::string& method) +{ + std::string reason("Bad status returned from remote slave during call to '" + method + "'."); + throw cosim::error(make_error_code(cosim::errc::model_error), reason); +} + +} // namespace + +cosim::proxy::remote_slave::remote_slave( + std::unique_ptr slave, + std::shared_ptr modelDescription) + : terminated_(false) + , slave_(std::move(slave)) + , modelDescription_(std::move(modelDescription)) +{ } + +cosim::model_description cosim::proxy::remote_slave::model_description() const +{ + return *modelDescription_; +} + +void cosim::proxy::remote_slave::setup(cosim::time_point startTime, std::optional stopTime, + std::optional relativeTolerance) +{ + startTime_ = startTime; + + double start = to_double_time_point(startTime); + double stop = to_double_time_point(stopTime.value_or(cosim::to_time_point(0))); + double tolerance = relativeTolerance.value_or(0); + + auto status = slave_->setup_experiment(start, stop, tolerance); + if (!status) { + bad_status_throw("setup_experiment"); + } + status = slave_->enter_initialization_mode(); + if (!status) { + bad_status_throw("enter_initialization_mode"); + } +} + +void cosim::proxy::remote_slave::start_simulation() +{ + auto status = slave_->exit_initialization_mode(); + if (!status) { + bad_status_throw("exit_initialization_mode"); + } +} + +void cosim::proxy::remote_slave::end_simulation() +{ + if (!terminated_) { + slave_->terminate(); + terminated_ = true; + } +} + +cosim::step_result cosim::proxy::remote_slave::do_step(cosim::time_point currentTime, cosim::duration deltaT) +{ + auto status = slave_->step(to_double_time_point(currentTime), to_double_duration(deltaT, startTime_)); + if (!status) { + bad_status_throw("step"); + } + return cosim::step_result::complete; +} + +void cosim::proxy::remote_slave::get_real_variables(gsl::span variables, + gsl::span values) const +{ + COSIM_INPUT_CHECK(variables.size() == values.size()); + if (variables.empty()) return; + + const auto vr = std::vector(variables.begin(), variables.end()); + auto data = std::vector(vr.size()); + auto status = slave_->get_real(vr, data); + if (!status) { + bad_status_throw("get_real"); + } + for (unsigned int i = 0; i < data.size(); i++) { + values[i] = data[i]; + } +} + +void cosim::proxy::remote_slave::get_integer_variables(gsl::span variables, + gsl::span values) const +{ + COSIM_INPUT_CHECK(variables.size() == values.size()); + if (variables.empty()) return; + + const auto vr = std::vector(variables.begin(), variables.end()); + auto data = std::vector(vr.size()); + auto status = slave_->get_integer(vr, data); + if (!status) { + bad_status_throw("get_integer"); + } + for (unsigned int i = 0; i < data.size(); i++) { + values[i] = data[i]; + } +} + +void cosim::proxy::remote_slave::get_boolean_variables(gsl::span variables, + gsl::span values) const +{ + COSIM_INPUT_CHECK(variables.size() == values.size()); + if (variables.empty()) return; + + const auto vr = std::vector(variables.begin(), variables.end()); + auto data = std::vector(vr.size()); + auto status = slave_->get_boolean(vr, data); + if (!status) { + bad_status_throw("get_boolean"); + } + for (unsigned int i = 0; i < data.size(); i++) { + values[i] = data[i]; + } +} + +void cosim::proxy::remote_slave::get_string_variables(gsl::span variables, + gsl::span values) const +{ + COSIM_INPUT_CHECK(variables.size() == values.size()); + if (variables.empty()) return; + + const auto vr = std::vector(variables.begin(), variables.end()); + auto data = std::vector(vr.size()); + auto status = slave_->get_string(vr, data); + if (!status) { + bad_status_throw("get_string"); + } + for (unsigned int i = 0; i < data.size(); i++) { + values[i] = data[i]; + } +} + +void cosim::proxy::remote_slave::set_real_variables(gsl::span variables, + gsl::span values) +{ + COSIM_INPUT_CHECK(variables.size() == values.size()); + if (variables.empty()) return; + + const auto vr = std::vector(variables.begin(), variables.end()); + const auto _values = std::vector(values.begin(), values.end()); + auto status = slave_->set_real(vr, _values); + if (!status) { + bad_status_throw("set_real"); + } +} + +void cosim::proxy::remote_slave::set_integer_variables(gsl::span variables, + gsl::span values) +{ + COSIM_INPUT_CHECK(variables.size() == values.size()); + if (variables.empty()) return; + + const auto vr = std::vector(variables.begin(), variables.end()); + const auto _values = std::vector(values.begin(), values.end()); + auto status = slave_->set_integer(vr, _values); + if (!status) { + bad_status_throw("set_integer"); + } +} + +void cosim::proxy::remote_slave::set_boolean_variables(gsl::span variables, + gsl::span values) +{ + COSIM_INPUT_CHECK(variables.size() == values.size()); + if (variables.empty()) return; + + const auto vr = std::vector(variables.begin(), variables.end()); + const auto _values = std::vector(values.begin(), values.end()); + auto status = slave_->set_boolean(vr, _values); + if (!status) { + bad_status_throw("set_boolean"); + } +} + +void cosim::proxy::remote_slave::set_string_variables(gsl::span variables, + gsl::span values) +{ + COSIM_INPUT_CHECK(variables.size() == values.size()); + if (variables.empty()) return; + + const auto vr = std::vector(variables.begin(), variables.end()); + const auto _values = std::vector(values.begin(), values.end()); + auto status = slave_->set_string(vr, _values); + if (!status) { + bad_status_throw("set_string"); + } +} + +cosim::proxy::remote_slave::~remote_slave() +{ + remote_slave::end_simulation(); + slave_->freeInstance(); +} diff --git a/src/cosim/fmuproxy/remote_slave.hpp b/src/cosim/proxy/remote_slave.hpp similarity index 84% rename from src/cosim/fmuproxy/remote_slave.hpp rename to src/cosim/proxy/remote_slave.hpp index 9d6dd82e9..c75374e17 100644 --- a/src/cosim/fmuproxy/remote_slave.hpp +++ b/src/cosim/proxy/remote_slave.hpp @@ -6,30 +6,28 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ -#ifndef COSIM_FMUPROXY_REMOTE_SLAVE_HPP -#define COSIM_FMUPROXY_REMOTE_SLAVE_HPP - -#include "FmuService.h" +#ifndef COSIM_PROXY_REMOTE_SLAVE_HPP +#define COSIM_PROXY_REMOTE_SLAVE_HPP #include -#include #include #include +#include + #include namespace cosim { -namespace fmuproxy +namespace proxy { class remote_slave : public slave { public: - remote_slave(std::string instanceId, - std::shared_ptr state, + remote_slave(std::unique_ptr slave, std::shared_ptr modelDescription); cosim::model_description model_description() const override; @@ -65,13 +63,12 @@ class remote_slave : public slave private: bool terminated_; - std::string instanceId_; cosim::time_point startTime_; - std::shared_ptr state_; + std::unique_ptr slave_; std::shared_ptr modelDescription_; }; -} // namespace fmuproxy +} // namespace proxy } // namespace cosim diff --git a/src/cosim/utility/concurrency.cpp b/src/cosim/utility/concurrency.cpp index f87c11600..0e5f3fa36 100644 --- a/src/cosim/utility/concurrency.cpp +++ b/src/cosim/utility/concurrency.cpp @@ -3,10 +3,6 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ -#if defined(_WIN32) && !defined(NOMINMAX) -# define NOMINMAX -#endif - #include "cosim/utility/concurrency.hpp" #include diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index b7790d1df..e91361715 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -15,12 +15,6 @@ set(tests "synchronized_xy_series_test" ) -if(LIBCOSIM_WITH_FMUPROXY AND LIBCOSIM_TEST_FMUPROXY) - list(APPEND tests - "ssp_loader_fmuproxy_test" - ) -endif() - set(unittests "async_slave_unittest" "function_unittest" @@ -38,6 +32,13 @@ set(unittests "utility_zip_unittest" ) +if(LIBCOSIM_WITH_PROXYFMU) + list(APPEND unittests + "proxyfmu_integration_unittest" + "proxyfmu_library_unittest" + ) +endif() + include("AddTestExecutable") foreach(test IN LISTS tests) add_test_executable( @@ -50,11 +51,17 @@ foreach(test IN LISTS tests) endforeach() foreach(test IN LISTS unittests) + + set(dependencies cosim "Boost::unit_test_framework" "Boost::timer") + if (LIBCOSIM_WITH_PROXYFMU) + list(APPEND dependencies "proxyfmu::proxyfmu-client") + endif() + add_test_executable( "cpp_${test}" FOLDER "C++ unit tests" SOURCES "${test}.cpp" - DEPENDENCIES cosim "Boost::unit_test_framework" "Boost::timer" + DEPENDENCIES ${dependencies} DATA_DIR "${CMAKE_CURRENT_SOURCE_DIR}/data" ) target_include_directories("cpp_${test}" PRIVATE "$") diff --git a/tests/data/ssp/demo/fmuproxy/SystemStructure.ssd b/tests/data/ssp/demo/proxy/SystemStructure.ssd similarity index 96% rename from tests/data/ssp/demo/fmuproxy/SystemStructure.ssd rename to tests/data/ssp/demo/proxy/SystemStructure.ssd index ca8500420..bee51c61d 100644 --- a/tests/data/ssp/demo/fmuproxy/SystemStructure.ssd +++ b/tests/data/ssp/demo/proxy/SystemStructure.ssd @@ -9,7 +9,7 @@ - + @@ -41,7 +41,7 @@ - + diff --git a/tests/proxyfmu_integration_unittest.cpp b/tests/proxyfmu_integration_unittest.cpp new file mode 100644 index 000000000..d3bc710bd --- /dev/null +++ b/tests/proxyfmu_integration_unittest.cpp @@ -0,0 +1,190 @@ +#define BOOST_TEST_MODULE proxyfmu_integration unittests + +#include +#include +#include + +#include + +#include + +using namespace cosim; + +BOOST_AUTO_TEST_CASE(test_ssp) +{ + log::setup_simple_console_logging(); + log::set_global_output_level(log::info); + + const auto testDataDir = std::getenv("TEST_DATA_DIR"); + BOOST_REQUIRE(testDataDir != nullptr); + cosim::filesystem::path sspFile = cosim::filesystem::path(testDataDir) / "ssp" / "demo" / "proxy"; + + ssp_loader loader; + const auto config = loader.load(sspFile); + auto exec = execution(config.start_time, config.algorithm); + const auto entityMaps = inject_system_structure( + exec, + config.system_structure, + config.parameter_sets.at("")); + BOOST_CHECK(entityMaps.simulators.size() == 2); + + auto result = exec.simulate_until(to_time_point(1e-3)); + BOOST_REQUIRE(result.get()); +} + +BOOST_AUTO_TEST_CASE(test_fmi1) +{ + const auto testDataDir = std::getenv("TEST_DATA_DIR"); + BOOST_TEST_REQUIRE(!!testDataDir); + + auto path = proxyfmu::filesystem::path(testDataDir) / "fmi1" / "identity.fmu"; + auto fmu = proxy::remote_fmu(path); + + const auto d = fmu.description(); + BOOST_TEST(d->name == "no.viproma.demo.identity"); + BOOST_TEST(d->uuid.size() == 36U); + BOOST_TEST(d->description == + "Has one input and one output of each type, and outputs are always set equal to inputs"); + BOOST_TEST(d->author == "Lars Tandle Kyllingstad"); + + value_reference + realIn = 0, + integerIn = 0, booleanIn = 0, stringIn = 0, + realOut = 0, integerOut = 0, booleanOut = 0, stringOut = 0; + for (const auto& v : d->variables) { + if (v.name == "realIn") { + realIn = v.reference; + } else if (v.name == "integerIn") { + integerIn = v.reference; + } else if (v.name == "booleanIn") { + booleanIn = v.reference; + } else if (v.name == "stringIn") { + stringIn = v.reference; + } else if (v.name == "realOut") { + realOut = v.reference; + } else if (v.name == "integerOut") { + integerOut = v.reference; + } else if (v.name == "booleanOut") { + booleanOut = v.reference; + } else if (v.name == "stringOut") { + stringOut = v.reference; + } + + if (v.name == "realIn") { + BOOST_TEST(v.type == variable_type::real); + BOOST_TEST(v.variability == variable_variability::discrete); + BOOST_TEST(v.causality == variable_causality::input); + double start = std::get(*v.start); + BOOST_TEST(start == 0.0); + } else if (v.name == "stringOut") { + BOOST_TEST(v.type == variable_type::string); + BOOST_TEST(v.variability == variable_variability::discrete); + BOOST_TEST(v.causality == variable_causality::output); + BOOST_TEST(!v.start.has_value()); + } else if (v.name == "booleanIn") { + BOOST_TEST(v.type == variable_type::boolean); + BOOST_TEST(v.variability == variable_variability::discrete); + BOOST_TEST(v.causality == variable_causality::input); + bool start = std::get(*v.start); + BOOST_TEST(start == false); + } + } + + const auto tStart = time_point(); + const auto tMax = to_time_point(1.0); + const auto dt = to_duration(0.1); + + auto instance = fmu.instantiate("testSlave"); + instance->setup(tStart, tMax, std::nullopt).get(); + instance->start_simulation().get(); + + double realVal = 0.0; + int integerVal = 0; + bool booleanVal = false; + std::string stringVal; + + for (auto t = tStart; t < tMax; t += dt) { + + auto vars = instance->get_variables( + gsl::make_span(&realOut, 1), + gsl::make_span(&integerOut, 1), + gsl::make_span(&booleanOut, 1), + gsl::make_span(&stringOut, 1)) + .get(); + + BOOST_TEST(vars.real[0] == realVal); + BOOST_TEST(vars.integer[0] == integerVal); + BOOST_TEST(vars.boolean[0] == booleanVal); + BOOST_TEST(vars.string[0] == stringVal); + + realVal += 1.0; + integerVal += 1; + booleanVal = !booleanVal; + stringVal += 'a'; + + instance->do_step(t, dt).get(); + + instance->set_variables( + gsl::make_span(&realIn, 1), gsl::make_span(&realVal, 1), + gsl::make_span(&integerIn, 1), gsl::make_span(&integerVal, 1), + gsl::make_span(&booleanIn, 1), gsl::make_span(&booleanVal, 1), + gsl::make_span(&stringIn, 1), gsl::make_span(&stringVal, 1)) + .get(); + } + + instance->end_simulation().get(); +} + +BOOST_AUTO_TEST_CASE(test_fmi2) +{ + const auto testDataDir = std::getenv("TEST_DATA_DIR"); + BOOST_TEST_REQUIRE(!!testDataDir); + + auto path = proxyfmu::filesystem::path(testDataDir) / "fmi2" / "WaterTank_Control.fmu"; + auto fmu = proxy::remote_fmu(path); + + const auto d = fmu.description(); + BOOST_TEST(d->name == "WaterTank.Control"); + BOOST_TEST(d->uuid == "{ad6d7bad-97d1-4fb9-ab3e-00a0d051e42c}"); + BOOST_TEST(d->description.empty()); + BOOST_TEST(d->author.empty()); + BOOST_TEST(d->version.empty()); + + auto instance = fmu.instantiate("testSlave"); + instance->setup( + cosim::to_time_point(0.0), + cosim::to_time_point(1.0), + std::nullopt) + .get(); + + bool foundValve = false; + bool foundMinlevel = false; + for (const auto& v : d->variables) { + if (v.name == "valve") { + foundValve = true; + BOOST_TEST(v.variability == variable_variability::continuous); + BOOST_TEST(v.causality == variable_causality::output); + double start = std::get(*v.start); + BOOST_TEST(start == 0.0); + const auto varID = v.reference; + double varVal = -1.0; + varVal = instance->get_variables(gsl::make_span(&varID, 1), {}, {}, {}).get().real[0]; + BOOST_TEST(varVal == 0.0); + } else if (v.name == "minlevel") { + foundMinlevel = true; + BOOST_TEST(v.variability == variable_variability::fixed); + BOOST_TEST(v.causality == variable_causality::parameter); + double start = std::get(*v.start); + BOOST_TEST(start == 1.0); + const auto varID = v.reference; + double varVal = -1.0; + varVal = instance->get_variables(gsl::make_span(&varID, 1), {}, {}, {}).get().real[0]; + BOOST_TEST(varVal == 1.0); + } + } + BOOST_TEST(foundValve); + BOOST_TEST(foundMinlevel); + + instance->start_simulation().get(); + instance->end_simulation().get(); +} diff --git a/tests/proxyfmu_library_unittest.cpp b/tests/proxyfmu_library_unittest.cpp new file mode 100644 index 000000000..76901c3e4 --- /dev/null +++ b/tests/proxyfmu_library_unittest.cpp @@ -0,0 +1,92 @@ +#define BOOST_TEST_MODULE proxyfmu_library unittests + +#include +#include + +using namespace proxyfmu; +using namespace proxyfmu::fmi; + +namespace +{ + +void test(fmu& fmu) +{ + const auto d = fmu.get_model_description(); + BOOST_TEST(d.modelName == "no.viproma.demo.identity"); + BOOST_TEST(d.description == + "Has one input and one output of each type, and outputs are always set equal to inputs"); + BOOST_TEST(d.author == "Lars Tandle Kyllingstad"); + + auto slave = fmu.new_instance("instance"); + BOOST_REQUIRE(slave->setup_experiment()); + BOOST_REQUIRE(slave->enter_initialization_mode()); + BOOST_REQUIRE(slave->exit_initialization_mode()); + + std::vector vr{0}; + + std::vector realVal{0.0}; + std::vector integerVal{0}; + std::vector booleanVal{false}; + std::vector stringVal{""}; + + std::vector realRef(1); + std::vector integerRef(1); + std::vector booleanRef(1); + std::vector stringRef(1); + + double t = 0.0; + double tEnd = 1.0; + double dt = 0.1; + + while (t <= tEnd) { + + slave->get_real(vr, realRef); + slave->get_integer(vr, integerRef); + slave->get_boolean(vr, booleanRef); + slave->get_string(vr, stringRef); + + BOOST_TEST(realVal[0] == realRef[0]); + BOOST_TEST(integerVal[0] == integerRef[0]); + BOOST_TEST(booleanVal[0] == booleanRef[0]); + BOOST_TEST(stringVal[0] == stringRef[0]); + + BOOST_REQUIRE(slave->step(t, dt)); + + realVal[0] += 1.0; + integerVal[0] += 1; + booleanVal[0] = !booleanVal[0]; + stringVal[0] += 'a'; + + slave->set_real(vr, realVal); + slave->set_integer(vr, integerVal); + slave->set_boolean(vr, booleanVal); + slave->set_string(vr, stringVal); + + t += dt; + } + + BOOST_REQUIRE(slave->terminate()); + slave->freeInstance(); +} + +} // namespace + +BOOST_AUTO_TEST_CASE(proxyfmu_fmi_test_identity) +{ + const auto testDataDir = std::getenv("TEST_DATA_DIR"); + BOOST_TEST_REQUIRE(!!testDataDir); + + auto fmuPath = proxyfmu::filesystem::path(testDataDir) / "fmi1" / "identity.fmu"; + auto fmu = loadFmu(fmuPath); + test(*fmu); +} + +BOOST_AUTO_TEST_CASE(proxyfmu_client_test_identity) +{ + const auto testDataDir = std::getenv("TEST_DATA_DIR"); + BOOST_TEST_REQUIRE(!!testDataDir); + + auto fmuPath = proxyfmu::filesystem::path(testDataDir) / "fmi1" / "identity.fmu"; + auto fmu = client::proxy_fmu(fmuPath); + test(fmu); +} \ No newline at end of file diff --git a/tests/ssp_loader_fmuproxy_test.cpp b/tests/ssp_loader_fmuproxy_test.cpp deleted file mode 100644 index 6831c67cb..000000000 --- a/tests/ssp_loader_fmuproxy_test.cpp +++ /dev/null @@ -1,37 +0,0 @@ -#include -#include -#include - -#include -#include - -#define REQUIRE(test) \ - if (!(test)) throw std::runtime_error("Requirement not satisfied: " #test) - -int main() -{ - try { - cosim::log::setup_simple_console_logging(); - cosim::log::set_global_output_level(cosim::log::info); - - const auto testDataDir = std::getenv("TEST_DATA_DIR"); - REQUIRE(testDataDir); - cosim::filesystem::path sspFile = cosim::filesystem::path(testDataDir) / "ssp" / "demo" / "fmuproxy"; - - cosim::ssp_loader loader; - const auto config = loader.load(sspFile); - auto exec = cosim::execution(config.start_time, config.algorithm); - const auto entityMaps = cosim::inject_system_structure( - exec, - config.system_structure, - config.parameter_sets.at("")); - REQUIRE(entityMaps.simulators.size() == 2); - - auto result = exec.simulate_until(cosim::to_time_point(1e-3)); - REQUIRE(result.get()); - } catch (const std::exception& e) { - std::cerr << "Error: " << e.what(); - return 1; - } - return 0; -} diff --git a/tools/housekeeping b/tools/housekeeping index f294212a0..10ffe4d44 100755 --- a/tools/housekeeping +++ b/tools/housekeeping @@ -12,9 +12,6 @@ set -o errexit -o nounset -o pipefail # Regex to select files for clang-format readonly clangFormattableFiles='\.(h|c|hpp|cpp)$' -# Regex to exclude files from clang-format -readonly excludeFromClangFormat='(include/cse/fmuproxy|src/cpp/fmuproxy)/(fmu_service|service_types)' - # Per-file corrections for f in $(git ls-files); do # Ensure that only executable files have executable permissions @@ -34,5 +31,4 @@ done echo "running clang-format" git ls-files \ | egrep "$clangFormattableFiles" \ - | egrep -v "$excludeFromClangFormat" \ | xargs -r clang-format -i -style=file diff --git a/tools/tmp_cleanup b/tools/tmp_cleanup index 9a9525940..47e4ab84a 100755 --- a/tools/tmp_cleanup +++ b/tools/tmp_cleanup @@ -14,6 +14,9 @@ rm -rf libcosim_* # FMI4j (fmu-proxy) rm -rf fmi4j_* +# proxy-fmu +rm -rf proxy_fmu_* + # Other known FMI related files # sintef vessel model FMU