From dbdcc86156c71b1bcd9593cd92c6c3edc1482d99 Mon Sep 17 00:00:00 2001 From: "Ritesh.K" Date: Sun, 15 Mar 2026 18:39:41 +0100 Subject: [PATCH 1/5] feat (node-fmu): New node fmu using FMI3.0 Signed-off-by: Ritesh.K --- CMakeLists.txt | 3 + cmake/FindFMI.cmake | 22 +++ etc/examples/nodes/fmu.conf | 61 ++++++ include/villas/nodes/fmu.hpp | 76 ++++++++ lib/nodes/CMakeLists.txt | 6 + lib/nodes/fmu.cpp | 348 ++++++++++++++++++++++++++++++++++ packaging/deps.sh | 15 ++ tests/integration/node-fmu.sh | 102 ++++++++++ 8 files changed, 633 insertions(+) create mode 100644 cmake/FindFMI.cmake create mode 100644 etc/examples/nodes/fmu.conf create mode 100644 include/villas/nodes/fmu.hpp create mode 100644 lib/nodes/fmu.cpp create mode 100755 tests/integration/node-fmu.sh diff --git a/CMakeLists.txt b/CMakeLists.txt index 69f9e307c..0e201fa0f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -91,6 +91,7 @@ find_package(Criterion) find_package(OpalOrchestra) find_package(LibXml2) find_package(OpalAsyncApi) +find_package(FMI) # Check for tools find_program(PROTOBUFC_COMPILER NAMES protoc-c) @@ -199,6 +200,7 @@ cmake_dependent_option(WITH_NODE_ETHERCAT "Build with ethercat node-type" cmake_dependent_option(WITH_NODE_EXAMPLE "Build with example node-type" "${WITH_DEFAULTS}" "" OFF) cmake_dependent_option(WITH_NODE_EXEC "Build with exec node-type" "${WITH_DEFAULTS}" "" OFF) cmake_dependent_option(WITH_NODE_FILE "Build with file node-type" "${WITH_DEFAULTS}" "" OFF) +cmake_dependent_option(WITH_NODE_FMU "Build with fmu node-type" "${WITH_DEFAULTS}" "FMI_FOUND" OFF) cmake_dependent_option(WITH_NODE_FPGA "Build with fpga node-type" "${WITH_DEFAULTS}" "WITH_FPGA" OFF) cmake_dependent_option(WITH_NODE_IEC60870 "Build with iec60870 node-types" "${WITH_DEFAULTS}" "LIB60870_FOUND; NOT WITHOUT_GPL" OFF) cmake_dependent_option(WITH_NODE_IEC61850 "Build with iec61850 node-types" "${WITH_DEFAULTS}" "LIBIEC61850_FOUND; NOT WITHOUT_GPL" OFF) @@ -309,6 +311,7 @@ add_feature_info(NODE_ETHERCAT WITH_NODE_ETHERCAT "Build with add_feature_info(NODE_EXAMPLE WITH_NODE_EXAMPLE "Build with example node-type") add_feature_info(NODE_EXEC WITH_NODE_EXEC "Build with exec node-type") add_feature_info(NODE_FILE WITH_NODE_FILE "Build with file node-type") +add_feature_info(NODE_FMU WITH_NODE_FMU "Build with fmu node-type") add_feature_info(NODE_FPGA WITH_NODE_FPGA "Build with fpga node-type") add_feature_info(NODE_IEC60870 WITH_NODE_IEC60870 "Build with iec60870 node-types") add_feature_info(NODE_IEC61850 WITH_NODE_IEC61850 "Build with iec61850 node-types") diff --git a/cmake/FindFMI.cmake b/cmake/FindFMI.cmake new file mode 100644 index 000000000..5463f0ca3 --- /dev/null +++ b/cmake/FindFMI.cmake @@ -0,0 +1,22 @@ + + +find_path(FMI_INCLUDE_DIR + NAMES fmilib.h +) + +find_library(FMI_LIBRARY fmilib + PATHS /usr/local/lib /usr/local/lib64 +) + +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args(FMI DEFAULT_MSG FMI_LIBRARY FMI_INCLUDE_DIR) + +mark_as_advanced(FMI_INCLUDE_DIR FMI_LIBRARY) + +if(FMI_FOUND) + add_library(FMI SHARED IMPORTED) + set_target_properties(FMI PROPERTIES + IMPORTED_LOCATION "${FMI_LIBRARY}" + INTERFACE_INCLUDE_DIRECTORIES "${FMI_INCLUDE_DIR}" + ) +endif() diff --git a/etc/examples/nodes/fmu.conf b/etc/examples/nodes/fmu.conf new file mode 100644 index 000000000..e4dab5930 --- /dev/null +++ b/etc/examples/nodes/fmu.conf @@ -0,0 +1,61 @@ +# SPDX-FileCopyrightText: 2014-2023 Institute for Automation of Complex Power Systems, RWTH Aachen University +# SPDX-License-Identifier: Apache-2.0 + +nodes = { + file_output = { + type = "file" + # format = "csv" + uri = "/workspaces/node/fmu_temp/output.dat" + out = { + + } + }, + + signal_node = { + type = "signal.v2" + rate = 100.0 + realtime = true + + limit = 100 + + in = { + signals = ( + { name = "sine1", signal = "sine", amplitude = 1, frequency = 100 }, + # { name = "in2", signal = "constant", amplitude = 1 }, + ) + } + }, + fmu_node = { + type = "fmu" + # Path to fmu file + fmu_path = "/workspaces/node/fmu_temp/asine.fmu" + fmu_unpack_path = "/workspaces/node/fmu_temp/fmu_asine" + fmu_writefirst = true + stopTime = 10.0 + startTime = 0.0 + stepSize = 0.2 + + in = { + signals = ( + {name = "In1", type = "float"} + ) + } + + out = { + signals = ( + {name = "Out1", type = "float"}, + ) + } + } +} + +paths = ( + { + in = "signal_node", + out = "fmu_node" + }, + { + in = "fmu_node", + out = "file_output" + }, +) diff --git a/include/villas/nodes/fmu.hpp b/include/villas/nodes/fmu.hpp new file mode 100644 index 000000000..069a00c83 --- /dev/null +++ b/include/villas/nodes/fmu.hpp @@ -0,0 +1,76 @@ +/* Node type: Functional Mock-up unit. + * + * Author: Jitpanu Maneeratpongsuk + * SPDX-FileCopyrightText: 2025 Institute for Automation of Complex Power Systems, RWTH Aachen University + * SPDX-License-Identifier: Apache-2.0 + */ +#pragma once + +#include +#include + +#include + +#include +#include + +namespace villas { +namespace node { + +// Forward declarations +struct Sample; + +class FmuNode : public Node { +protected: + int parse(json_t *json) override; + + int _read(struct Sample *smps[], unsigned cnt) override; + int _write(struct Sample *smps[], unsigned cnt) override; + + bool writingTurn = true; + const char *path; + const char *unpack_path; + + timespec ts; + pthread_mutex_t mutex; + pthread_cond_t cv; + + fmi3_import_t *fmu; + jm_callbacks callbacks; + fmi_import_context_t *context; + +public: + FmuNode(const uuid_t &id = {}, const std::string &name = ""); + struct fmu_signal { + unsigned int ref = 0; + fmi3_base_type_enu_t type = fmi3_base_type_enu_t::fmi3_base_type_float64; + std::string name; + }; + + std::vector signalIn; + std::vector signalOut; + + int prepare() override; + + int check() override; + + int start() override; + + int stop() override; + + // ~FmuNode(); + +private: + void parse_signal(json_t *json, bool in); + double currentTime = 0; + double stepSize = 0.1; + double stopTime = 0; + double startTime = 0; + bool stopTimeDef = false; + double nextTime = 0.0; + void get_vr(const char *var_name, fmi3_value_reference_t &ref, + fmi3_base_type_enu_t &type); +}; + +} // namespace node +} // namespace villas diff --git a/lib/nodes/CMakeLists.txt b/lib/nodes/CMakeLists.txt index 068ad9fba..39bc84131 100644 --- a/lib/nodes/CMakeLists.txt +++ b/lib/nodes/CMakeLists.txt @@ -202,6 +202,12 @@ if(WITH_NODE_WEBRTC) list(APPEND LIBRARIES LibDataChannel::LibDataChannel) endif() +# Enable FMU node-type support +if(WITH_NODE_FMU) + list(APPEND NODE_SRC fmu.cpp) + list(APPEND LIBRARIES FMI) +endif() + add_library(nodes STATIC ${NODE_SRC}) target_include_directories(nodes PUBLIC ${INCLUDE_DIRS}) target_link_libraries(nodes PUBLIC ${LIBRARIES}) diff --git a/lib/nodes/fmu.cpp b/lib/nodes/fmu.cpp new file mode 100644 index 000000000..6e3a6eae2 --- /dev/null +++ b/lib/nodes/fmu.cpp @@ -0,0 +1,348 @@ +/* Node type: Functional Mock-up unit. + * + * Author: Jitpanu Maneeratpongsuk + * SPDX-FileCopyrightText: 2025 Institute for Automation of Complex Power Systems, RWTH Aachen University + * SPDX-License-Identifier: Apache-2.0 + */ +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +using namespace villas; +using namespace villas::node; + + +FmuNode::FmuNode(const uuid_t &id, const std::string &name) + : Node(id, name), writingTurn(false), path(""), unpack_path(""), fmu(), + context() { + int ret; + ret = pthread_mutex_init(&mutex, nullptr); + if (ret) + throw RuntimeError("Failed to initialize mutex"); + ret = pthread_cond_init(&cv, nullptr); + if (ret) + throw RuntimeError("Failed to initialize conditional variable"); +} + +int FmuNode::prepare() { + callbacks.malloc = malloc; + callbacks.calloc = calloc; + callbacks.realloc = realloc; + callbacks.free = free; + callbacks.logger = nullptr; + callbacks.log_level = jm_log_level_info; + callbacks.context = nullptr; + context = fmi_import_allocate_context(&callbacks); + + struct stat sb; + if (stat(unpack_path, &sb) != 0) + std::cerr << "The unpack path is invalid" << std::endl; + + fmi_version_enu_t version = + fmi_import_get_fmi_version(context, path, unpack_path); + if (version != fmi_version_enu_t::fmi_version_3_0_enu) { + throw RuntimeError("Not FMI 3.0"); + } + + fmu = fmi3_import_parse_xml(context, unpack_path, 0); + if (!fmu) { + throw RuntimeError("Failed to parse xml file"); + } + + if (fmi3_import_create_dllfmu(fmu, fmi3_fmu_kind_cs, NULL, NULL) != + jm_status_success) { + throw RuntimeError("Failed to load FMU binary"); + } + + if (fmi3_import_instantiate_co_simulation( + fmu, "instance1", unpack_path, fmi3_false, fmi3_true, fmi3_false, + fmi3_true, NULL, 0, NULL) != jm_status_success) { + throw RuntimeError("Failed to instantiate FMU"); + } + + fmi3_import_enter_initialization_mode(fmu, fmi3_false, 0.0, startTime, + stopTimeDef, stopTime); + fmi3_import_exit_initialization_mode(fmu); + + // Fetch ref and type of input and output FMU variables + for (size_t i = 0; i < signalIn.size(); i++) { + get_vr(signalIn[i].name.data(), signalIn[i].ref, signalIn[i].type); + } + for (size_t i = 0; i < signalOut.size(); i++) { + get_vr(signalOut[i].name.data(), signalOut[i].ref, signalOut[i].type); + } + + return Node::prepare(); +} + +int FmuNode::check() { return Node::check(); } + +int FmuNode::_read(struct Sample *smps[], unsigned cnt) { + assert(cnt == 1); + + // Lock thread + pthread_mutex_lock(&mutex); + while (writingTurn || currentTime >= stopTime) { + pthread_cond_wait(&cv, &mutex); + } + double targetTime = currentTime + stepSize; + double remainingStep = stepSize; + + smps[0]->signals = getInputSignals(false); + + while (remainingStep > 0.0) { + bool eventHandlingNeeded = false; + bool terminateSimulation = false; + bool earlyReturn = false; + double lastSuccessfulTime = currentTime; + + // Perform step + fmi3_status_t status = fmi3_import_do_step( + fmu, currentTime, stepSize, fmi3_true, &eventHandlingNeeded, + &terminateSimulation, &earlyReturn, &lastSuccessfulTime); + + if (status == fmi3_status_error) + throw RuntimeError("Error during step"); + + // TODO: set global variable to bypass stepping when terminate_simulation = TRUE + if (terminateSimulation) { + logger->info("FMU called for simulation termination"); + currentTime = lastSuccessfulTime; + break; + } + + if (earlyReturn) { + logger->info("FMU returned early at {}, target time {}", lastSuccessfulTime, targetTime); + currentTime = lastSuccessfulTime; + remainingStep = targetTime - currentTime; + + // TODO: handle events when loop returns early + // TODO: implement variable step support for early returns + } else { + currentTime += remainingStep; + remainingStep = 0.0; + } + } + + smps[0]->ts.origin = time_now(); + smps[0]->flags = (int)SampleFlags::HAS_DATA | (int)SampleFlags::HAS_TS_ORIGIN; + smps[0]->length = signalOut.size(); + double val = 0; + for (size_t i = 0; i < signalOut.size(); i++) { + switch (signalOut[i].type) { + case fmi3_base_type_enu_t::fmi3_base_type_float64: + fmi3_import_get_float64(fmu, &signalOut[i].ref, 1, &val, 1); + break; + case fmi3_base_type_enu_t::fmi3_base_type_float32: { + float data = 0; + fmi3_import_get_float32(fmu, &signalOut[i].ref, 1, &data, 1); + val = data; + break; + } + case fmi3_base_type_enu_t::fmi3_base_type_int64: { + long data = 0; + fmi3_import_get_int64(fmu, &signalOut[i].ref, 1, &data, 1); + val = data; + break; + } + case fmi3_base_type_enu_t::fmi3_base_type_int32: { + int data = 0; + fmi3_import_get_int32(fmu, &signalOut[i].ref, 1, &data, 1); + val = data; + break; + } + case fmi3_base_type_enu_t::fmi3_base_type_bool: { + bool data = 0; + fmi3_import_get_boolean(fmu, &signalOut[i].ref, 1, &data, 1); + val = data; + break; + } + default: + throw RuntimeError("Unsupport data type"); + break; + } + auto sig = smps[0]->signals->getByIndex(i); + switch (sig->type) { + case villas::node::SignalType::FLOAT: + smps[0]->data[i].f = val; + break; + case villas::node::SignalType::INTEGER: + smps[0]->data[i].i = val; + break; + case villas::node::SignalType::BOOLEAN: + smps[0]->data[i].b = val; + break; + default: + break; + } + } + + currentTime += stepSize; + // Unlock thread + writingTurn = true; + pthread_cond_signal(&cv); + pthread_mutex_unlock(&mutex); + + return 1; +} + +int FmuNode::_write(struct Sample *smps[], unsigned cnt) { + assert(cnt == 1); + // Lock thread + pthread_mutex_lock(&mutex); + while (!writingTurn) { + pthread_cond_wait(&cv, &mutex); + } + ts = smps[0]->ts.origin; + for (size_t i = 0; i < signalIn.size(); i++) { + auto sig = smps[0]->signals->getByIndex(i); + double val = 0; + switch (sig->type) { + case villas::node::SignalType::FLOAT: + val = smps[0]->data[i].f; + break; + case villas::node::SignalType::INTEGER: + val = smps[0]->data[i].i; + break; + case villas::node::SignalType::BOOLEAN: + val = smps[0]->data[i].b; + break; + default: + break; + } + logger->debug("Input value {}", val); + switch (signalIn[i].type) { + case fmi3_base_type_enu_t::fmi3_base_type_float64: + fmi3_import_set_float64(fmu, &signalIn[i].ref, 1, &val, 1); + break; + case fmi3_base_type_enu_t::fmi3_base_type_float32: { + float data = val; + fmi3_import_set_float32(fmu, &signalIn[i].ref, 1, &data, 1); + break; + } + case fmi3_base_type_enu_t::fmi3_base_type_int64: { + long data = floor(val); + fmi3_import_set_int64(fmu, &signalIn[i].ref, 1, &data, 1); + break; + } + case fmi3_base_type_enu_t::fmi3_base_type_int32: { + int data = floor(val); + fmi3_import_set_int32(fmu, &signalIn[i].ref, 1, &data, 1); + break; + } + case fmi3_base_type_enu_t::fmi3_base_type_bool: { + bool data = val; + fmi3_import_set_boolean(fmu, &signalIn[i].ref, 1, &data, 1); + break; + } + default: + throw RuntimeError("Unsupport data type"); + break; + } + } + // Unlock thread + writingTurn = false; + pthread_cond_signal(&cv); + pthread_mutex_unlock(&mutex); + return 1; +} + +void FmuNode::get_vr(const char *var_name, fmi3_value_reference_t &ref, + fmi3_base_type_enu_t &type) { + fmi3_import_variable_t *var = fmi3_import_get_variable_by_name(fmu, var_name); + if (!var) { + throw SystemError("Invalid variable name '{}'", var_name); + } + type = fmi3_import_get_variable_base_type(var); + ref = fmi3_import_get_variable_vr(var); +} + +int FmuNode::parse(json_t *json) { + int ret = Node::parse(json); + if (ret) + return ret; + + json_t *json_signals_in = nullptr; + json_t *json_signals_out = nullptr; + json_error_t err; + startTime = 0; + stepSize = 0.1; + stopTime = INT_MAX; + ret = json_unpack_ex( + json, &err, 0, "{s:s, s:s, s?:f, s?:f, s?:f, s?:{s:o}, s?:{s:o}}", + "fmu_path", &path, "fmu_unpack_path", &unpack_path, "stopTime", &stopTime, + "startTime", &startTime, "stepSize", &stepSize, "in", "signals", + &json_signals_in, "out", "signals", &json_signals_out); + if (ret) + throw ConfigError(json, err, "node-config-node-fmu"); + if (stopTime == INT_MAX) { + stopTimeDef = false; + } else { + stopTimeDef = true; + } + + parse_signal(json_signals_in, true); + parse_signal(json_signals_out, false); + + return 0; +} + +void FmuNode::parse_signal(json_t *json, bool in) { + if (!json_is_array(json)) + throw ConfigError(json, "node-config-node-fmu-signals", + "Signal list of FMU node must be an array"); + + size_t i; + json_t *json_channel; + json_error_t err; + json_array_foreach (json, i, json_channel) { + fmu_signal dummy; + const char *name; + int ret = json_unpack_ex(json_channel, &err, 0, "{ s: s}", "name", &name); + if (ret) + throw ConfigError(json, err, "node-config-node-fmu-signals"); + + dummy.name = name; + if (in) + signalIn.push_back(dummy); + else + signalOut.push_back(dummy); + } +} + +int FmuNode::start() { return Node::start(); } + +int FmuNode::stop() { + fmi3_import_terminate(fmu); + fmi3_import_free_instance(fmu); + fmi3_import_destroy_dllfmu(fmu); + fmi3_import_free(fmu); + fmi_import_free_context(context); + return 0; +} + +// Register node +static char n[] = "fmu"; +static char d[] = "Interface to Functional Mockup Unit using FMI3.0"; +static NodePlugin + p; diff --git a/packaging/deps.sh b/packaging/deps.sh index 35e0f840c..0c9226439 100644 --- a/packaging/deps.sh +++ b/packaging/deps.sh @@ -587,6 +587,21 @@ if ! find /usr/{local/,}{lib,bin} -name "libOpenDSSC.so" | grep -q . && echo "${PREFIX}/openDSSC/bin/" > /etc/ld.so.conf.d/opendssc.conf fi +# Build & Install fmi-library +if ! find /usr/{local/,}{lib,lib64} -name "libfmilib_shared.so" | grep -q . && + should_build "fmi-library" "For FMI node-type"; then + git clone https://github.com/modelon-community/fmi-library.git + mkdir -p fmi-library/build + pushd fmi-library/build + cmake -DFMILIB_GENERATE_DOXYGEN_DOC=OFF \ + -DFMILIB_BUILD_TESTS=OFF \ + ${CMAKE_OPTS} .. + cmake --build . \ + --target install \ + --parallel ${PARALLEL} + popd +fi + # Build & Install ghc::filesystem if ! cmake --find-package -DNAME=ghc_filesystem -DCOMPILER_ID=GNU -DLANGUAGE=CXX -DMODE=EXIST >/dev/null 2>/dev/null && \ should_build "ghc_filesystem" "for compatability with older compilers"; then diff --git a/tests/integration/node-fmu.sh b/tests/integration/node-fmu.sh new file mode 100755 index 000000000..9f3bdb6ca --- /dev/null +++ b/tests/integration/node-fmu.sh @@ -0,0 +1,102 @@ +#!/usr/bin/env bash +# +# Integration test for node fmu. +# +# Author: Ritesh Karki +# SPDX-FileCopyrightText: 2014-2026 Institute for Automation of Complex Power Systems, RWTH Aachen University +# SPDX-License-Identifier: Apache-2.0 + +echo "Test not ready yet" +exit 99 + +set -e + +DIR=$(mktemp -d) +pushd ${DIR} + +function finish { + popd + rm -rf ${DIR} +} +trap finish EXIT + +cat > expect.dat < config.json < Date: Sun, 15 Mar 2026 22:43:09 +0100 Subject: [PATCH 2/5] fix (node-fmu): Fix pre-commit and SPDX error Signed-off-by: Ritesh.K --- cmake/FindFMI.cmake | 6 +++++- lib/nodes/fmu.cpp | 10 +++++----- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/cmake/FindFMI.cmake b/cmake/FindFMI.cmake index 5463f0ca3..0e2e3bd73 100644 --- a/cmake/FindFMI.cmake +++ b/cmake/FindFMI.cmake @@ -1,4 +1,8 @@ - +# CMakeLists.txt. +# +# Author: Ritesh Karki +# SPDX-FileCopyrightText: 2014-2026 Institute for Automation of Complex Power Systems, RWTH Aachen University +# SPDX-License-Identifier: Apache-2.0 find_path(FMI_INCLUDE_DIR NAMES fmilib.h diff --git a/lib/nodes/fmu.cpp b/lib/nodes/fmu.cpp index 6e3a6eae2..885779504 100644 --- a/lib/nodes/fmu.cpp +++ b/lib/nodes/fmu.cpp @@ -28,7 +28,6 @@ using namespace villas; using namespace villas::node; - FmuNode::FmuNode(const uuid_t &id, const std::string &name) : Node(id, name), writingTurn(false), path(""), unpack_path(""), fmu(), context() { @@ -109,14 +108,14 @@ int FmuNode::_read(struct Sample *smps[], unsigned cnt) { while (remainingStep > 0.0) { bool eventHandlingNeeded = false; - bool terminateSimulation = false; + bool terminateSimulation = false; bool earlyReturn = false; double lastSuccessfulTime = currentTime; // Perform step fmi3_status_t status = fmi3_import_do_step( - fmu, currentTime, stepSize, fmi3_true, &eventHandlingNeeded, - &terminateSimulation, &earlyReturn, &lastSuccessfulTime); + fmu, currentTime, stepSize, fmi3_true, &eventHandlingNeeded, + &terminateSimulation, &earlyReturn, &lastSuccessfulTime); if (status == fmi3_status_error) throw RuntimeError("Error during step"); @@ -129,7 +128,8 @@ int FmuNode::_read(struct Sample *smps[], unsigned cnt) { } if (earlyReturn) { - logger->info("FMU returned early at {}, target time {}", lastSuccessfulTime, targetTime); + logger->info("FMU returned early at {}, target time {}", + lastSuccessfulTime, targetTime); currentTime = lastSuccessfulTime; remainingStep = targetTime - currentTime; From 0e360f433c59a632b31fb04ff7f7e3e0721d8c70 Mon Sep 17 00:00:00 2001 From: "Ritesh.K" Date: Tue, 17 Mar 2026 14:21:18 +0100 Subject: [PATCH 3/5] feat (node-fmu): Add yaml files for node fmu for documentation Signed-off-by: Ritesh.K --- .../components/schemas/config/node_obj.yaml | 1 + .../components/schemas/config/nodes/_fmu.yaml | 7 ++ .../components/schemas/config/nodes/fmu.yaml | 71 +++++++++++++++++++ 3 files changed, 79 insertions(+) create mode 100644 doc/openapi/components/schemas/config/nodes/_fmu.yaml create mode 100644 doc/openapi/components/schemas/config/nodes/fmu.yaml diff --git a/doc/openapi/components/schemas/config/node_obj.yaml b/doc/openapi/components/schemas/config/node_obj.yaml index c3e796171..afebd1e32 100644 --- a/doc/openapi/components/schemas/config/node_obj.yaml +++ b/doc/openapi/components/schemas/config/node_obj.yaml @@ -27,6 +27,7 @@ discriminator: example: nodes/_example.yaml exec: nodes/_exec.yaml file: nodes/_file.yaml + fmu: nodes/_fmu.yaml fpga: nodes/_fpga.yaml iec60870-5-104: nodes/_iec60870-5-104.yaml iec61850-8-1: nodes/_iec61850-8-1.yaml diff --git a/doc/openapi/components/schemas/config/nodes/_fmu.yaml b/doc/openapi/components/schemas/config/nodes/_fmu.yaml new file mode 100644 index 000000000..3366cdf65 --- /dev/null +++ b/doc/openapi/components/schemas/config/nodes/_fmu.yaml @@ -0,0 +1,7 @@ +# yaml-language-server: $schema=http://json-schema.org/draft-07/schema +# SPDX-FileCopyrightText: 2014-2023 Institute for Automation of Complex Power Systems, RWTH Aachen University +# SPDX-License-Identifier: Apache-2.0 +--- +allOf: +- $ref: ../node_obj.yaml +- $ref: file.yaml diff --git a/doc/openapi/components/schemas/config/nodes/fmu.yaml b/doc/openapi/components/schemas/config/nodes/fmu.yaml new file mode 100644 index 000000000..ed0bab147 --- /dev/null +++ b/doc/openapi/components/schemas/config/nodes/fmu.yaml @@ -0,0 +1,71 @@ +# yaml-language-server: $schema=http://json-schema.org/draft-07/schema +# SPDX-FileCopyrightText: 2014-2026 Institute for Automation of Complex Power Systems, RWTH Aachen University +# SPDX-License-Identifier: Apache-2.0 +--- +allOf: +- type: object + properties: + fmu_path: + format: uri + description: | + Specifies the URI to a FMU model + + fmu_unpack_path: + format: uri + description: | + Specifies the URI to a directory which has the unpacked files of the FMU model (mainly the shared library and the modelDescription.xml for the model) + + fmuWritefirst: + type: boolean + description: | + Specifies whether the model has to be writtem with input values before running. `true` denotes that the model should use the input values. `false` would make the model use default values instead. + + startTime: + type: number + description: | + Specifies the start time for the simulation. Value has to be at least one `stepSize` lesser than `stopTime`. + + stopTime: + type: number + description: | + Specifies the stop time for the simulation. The number of steps is calculated then by the difference between `startTime` and `stopTime` divided by the `stepSize`. + + stepSize: + type: number + description: | + Specifies the stepSize for the simulation. Special care has to be taken to ensure that the value is equal to the value mantioned in modelDescription.xml. + + in: + type: object + properties: + signals: + type: array + description: | + The input signals for the model have to be mentioned here as elements of an array while specifying the name (should be the same as that found in the modelDescription file) and the datatype. + + **Example**: + + ``` + signals = ( + {name = "In1", type = "float"}, + {name = "In2", type = "int"} + ) + ``` + out: + type: object + properties: + signals: + type: array + description: | + The output signals for the model have to be mentioned here as elements of an array while specifying the name (should be the same as that found in the modelDescription file) and the datatype. + + **Example**: + + ``` + signals = ( + {name = "Out1", type = "float"} + ) + ``` + +- $ref: ../node_signals.yaml +- $ref: ../node.yaml From fcaea88d5134b785128ee78344dc544545c4ed3c Mon Sep 17 00:00:00 2001 From: "Ritesh.K" Date: Tue, 17 Mar 2026 14:42:41 +0100 Subject: [PATCH 4/5] fix (node-fmu): Fix SPDX author name for fmu node Signed-off-by: Ritesh.K --- include/villas/nodes/fmu.hpp | 2 +- lib/nodes/fmu.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/include/villas/nodes/fmu.hpp b/include/villas/nodes/fmu.hpp index 069a00c83..8b9f61fbf 100644 --- a/include/villas/nodes/fmu.hpp +++ b/include/villas/nodes/fmu.hpp @@ -1,6 +1,6 @@ /* Node type: Functional Mock-up unit. * - * Author: Jitpanu Maneeratpongsuk + * Author: Ritesh Karki * SPDX-FileCopyrightText: 2025 Institute for Automation of Complex Power Systems, RWTH Aachen University * SPDX-License-Identifier: Apache-2.0 */ diff --git a/lib/nodes/fmu.cpp b/lib/nodes/fmu.cpp index 885779504..746b0d35e 100644 --- a/lib/nodes/fmu.cpp +++ b/lib/nodes/fmu.cpp @@ -1,6 +1,6 @@ /* Node type: Functional Mock-up unit. * - * Author: Jitpanu Maneeratpongsuk + * Author: Ritesh Karki * SPDX-FileCopyrightText: 2025 Institute for Automation of Complex Power Systems, RWTH Aachen University * SPDX-License-Identifier: Apache-2.0 */ From 016e0cdd9d3a7876a1e28c95a4307f22a6c42f13 Mon Sep 17 00:00:00 2001 From: "Ritesh.K" Date: Wed, 17 Jun 2026 14:21:39 +0200 Subject: [PATCH 5/5] fix (node-fmu): Fix author in SPDX header Signed-off-by: Ritesh.K --- include/villas/nodes/fmu.hpp | 2 +- lib/nodes/fmu.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/include/villas/nodes/fmu.hpp b/include/villas/nodes/fmu.hpp index 8b9f61fbf..f311b044e 100644 --- a/include/villas/nodes/fmu.hpp +++ b/include/villas/nodes/fmu.hpp @@ -1,6 +1,6 @@ /* Node type: Functional Mock-up unit. * - * Author: Ritesh Karki + * Author: Ritesh Karki , Jitpanu Maneeratpongsuk * SPDX-FileCopyrightText: 2025 Institute for Automation of Complex Power Systems, RWTH Aachen University * SPDX-License-Identifier: Apache-2.0 */ diff --git a/lib/nodes/fmu.cpp b/lib/nodes/fmu.cpp index 746b0d35e..6a962506e 100644 --- a/lib/nodes/fmu.cpp +++ b/lib/nodes/fmu.cpp @@ -1,6 +1,6 @@ /* Node type: Functional Mock-up unit. * - * Author: Ritesh Karki + * Author: Ritesh Karki , Jitpanu Maneeratpongsuk * SPDX-FileCopyrightText: 2025 Institute for Automation of Complex Power Systems, RWTH Aachen University * SPDX-License-Identifier: Apache-2.0 */