From 6a43fd59ece8d0184ff2ef8f890987bf968966d2 Mon Sep 17 00:00:00 2001 From: Rule Timothy Date: Mon, 20 Jan 2025 12:35:46 +0100 Subject: [PATCH] Importer CLI improvements. Importer operation is simplified. Co-authored-by: Rogoz Mateusz (VM/EMT3) Signed-off-by: Timothy Rule (VM/EMT3) --- dse/examples/gateway/CMakeLists.txt | 2 + dse/importer/importer.c | 143 ++++++++++++------ dse/importer/importer.h | 12 +- dse/importer/xml.c | 126 ++++++++++++--- .../testscript/e2e/example_fmu_counter.txtar | 6 +- tests/testscript/e2e/example_fmu_linear.txtar | 6 +- tests/testscript/e2e/fmigateway_fmi2.txtar | 6 +- tests/testscript/e2e/fmigateway_fmi3.txtar | 6 +- 8 files changed, 221 insertions(+), 86 deletions(-) diff --git a/dse/examples/gateway/CMakeLists.txt b/dse/examples/gateway/CMakeLists.txt index 4c3e5e4..a91006e 100644 --- a/dse/examples/gateway/CMakeLists.txt +++ b/dse/examples/gateway/CMakeLists.txt @@ -139,6 +139,7 @@ set_target_properties(gatewayfmi2fmu PROPERTIES OUTPUT_NAME fmi2gateway + PREFIX "" ) install( TARGETS @@ -201,6 +202,7 @@ set_target_properties(gatewayfmi3fmu PROPERTIES OUTPUT_NAME fmi3gateway + PREFIX "" ) install( TARGETS diff --git a/dse/importer/importer.c b/dse/importer/importer.c index ec84416..d62468b 100644 --- a/dse/importer/importer.c +++ b/dse/importer/importer.c @@ -52,19 +52,29 @@ typedef int32_t (*fmi3DoStep)(); typedef void (*fmi3FreeInstance)(); -#define UNUSED(x) ((void)x) -#define ARRAY_SIZE(x) (sizeof(x) / sizeof(x[0])) +#define UNUSED(x) ((void)x) +#define ARRAY_SIZE(x) (sizeof(x) / sizeof(x[0])) +#define MODEL_XML_FILE "modelDescription.xml" -#define OPT_LIST "hV:F:S:N:" +#define OPT_LIST "hs:X:P:" static struct option long_options[] = { { "help", no_argument, NULL, 'h' }, - { "version", required_argument, NULL, 'V' }, - { "fmu", required_argument, NULL, 'F' }, - { "step_size", required_argument, NULL, 'S' }, - { "steps", required_argument, NULL, 'N' }, + { "step_size", optional_argument, NULL, 's' }, + { "steps", optional_argument, NULL, 'X' }, + { "platform", optional_argument, NULL, 'P' }, }; +static inline void print_usage() +{ + printf("usage: fmuImporter [options] []\n\n"); + printf(" [] (defaults to working directory)\n"); + printf(" [-h, --help]\n"); + printf(" [-s, --step_size]\n"); + printf(" [-X, --steps]\n"); + printf(" [-P, --platform] (defaults to linux-amd64)\n"); +} + static void _log(const char* format, ...) { printf("Importer: "); @@ -78,7 +88,7 @@ static void _log(const char* format, ...) static int _run_fmu2_cosim( - modelDescription* desc, void* handle, double step_size, double steps) + modelDescription* desc, void* handle, double step_size, unsigned int steps) { /* Setup the FMU * ============= */ @@ -90,7 +100,8 @@ static int _run_fmu2_cosim( fmi2Instantiate instantiate = dlsym(handle, "fmi2Instantiate"); dlerror_str = dlerror(); if (dlerror_str || instantiate == NULL) { - _log("ERROR: could not load fmi2Instantiate() from FMU: %s", dlerror_str); + _log("ERROR: could not load fmi2Instantiate() from FMU: %s", + dlerror_str); return EINVAL; } if (instantiate == NULL) { @@ -130,7 +141,7 @@ static int _run_fmu2_cosim( fmi2DoStep do_step = dlsym(handle, "fmi2DoStep"); if (do_step == NULL) return EINVAL; - for (int i = 0; i < steps; i++) { + for (unsigned int i = 0; i < steps; i++) { /* Loopback the binary data. */ for (size_t i = 0; i < desc->binary.tx_count; i++) { if (desc->binary.val_tx_binary[i]) { @@ -195,7 +206,7 @@ static int _run_fmu2_cosim( static int _run_fmu3_cosim( - modelDescription* desc, void* handle, double step_size, double steps) + modelDescription* desc, void* handle, double step_size, unsigned int steps) { /* Setup the FMU * ============= */ @@ -239,7 +250,7 @@ static int _run_fmu3_cosim( fmi3DoStep do_step = dlsym(handle, "fmi3DoStep"); if (do_step == NULL) return EINVAL; - for (int i = 0; i < steps; i++) { + for (unsigned int i = 0; i < steps; i++) { /* Loopback the binary data. */ for (size_t i = 0; i < desc->binary.tx_count; i++) { if (desc->binary.val_tx_binary[i]) { @@ -305,54 +316,89 @@ static int _run_fmu3_cosim( } -int main(int argc, char** argv) +static inline void _parse_arguments(int argc, char** argv, double* step_size, + unsigned int* steps, const char** fmu_path, const char** platform) { extern int optind, optopt; extern char* optarg; int c; + /* Parse the named options. */ + optind = 1; + while ((c = getopt_long(argc, argv, OPT_LIST, long_options, NULL)) != -1) { + switch (c) { + case 'h': { + print_usage(); + exit(0); + } + default: + break; + } + } - uint8_t version = 2; - const char* fmu_lib_path = NULL; - double step_size = 0.0005; - double steps = 10; - + optind = 1; while ((c = getopt_long(argc, argv, OPT_LIST, long_options, NULL)) != -1) { switch (c) { case 'h': break; - case 'V': - version = atoi(optarg); + case 's': + *step_size = atof(optarg); break; - case 'F': - fmu_lib_path = optarg; + case 'X': + *steps = atoi(optarg); break; - case 'S': - step_size = atof(optarg); - break; - case 'N': - steps = atof(optarg); + case 'P': + *platform = optarg; break; default: exit(1); } } - static char _cwd[PATH_MAX]; - getcwd(_cwd, PATH_MAX); - _log("Cwd: %s", _cwd); + // get the fmu Path + if ((optind + 1) <= argc) { + *fmu_path = argv[optind]; + } +} + +int main(int argc, char** argv) +{ + double step_size = 0.0005; + unsigned int steps = 10; + const char* fmu_path = NULL; + const char* platform = "linux-amd64"; + static char _cwd[PATH_MAX]; + - modelDescription* desc = - parse_model_desc((char*)"modelDescription.xml", version); + /* Parse arguments + * =============== */ + _parse_arguments(argc, argv, &step_size, &steps, &fmu_path, &platform); + getcwd(_cwd, PATH_MAX); + if (fmu_path == NULL) { + fmu_path = _cwd; + } + errno = 0; + if (chdir(fmu_path)) { + _log("ERROR: Could not change to FMU path: %s", fmu_path); + return errno || EINVAL; + } + getcwd(_cwd, PATH_MAX); + _log("FMU Dir: %s", _cwd); + _log("Step Size: %f", step_size); + _log("Steps: %u", steps); + _log("Platform: %s", platform); + _log("Loading FMU Definition: %s", MODEL_XML_FILE); + modelDescription* desc = parse_model_desc(MODEL_XML_FILE, platform); if (desc == NULL) { - _log("Could not parse the modelDescription.xml correctly!"); + _log("ERROR: Could not parse the model correctly!"); return EINVAL; } + _log("FMU Version: %d", atoi(desc->version)); /* Load the FMU * ============ */ - _log("Loading FMU: %s", fmu_lib_path); + _log("Loading FMU: %s", desc->fmu_lib_path); dlerror(); - void* handle = dlopen(fmu_lib_path, RTLD_NOW | RTLD_GLOBAL); + void* handle = dlopen(desc->fmu_lib_path, RTLD_NOW | RTLD_GLOBAL); if (handle == NULL) { _log("ERROR: dlopen call failed: %s", dlerror()); _log("Model library not loaded!"); @@ -362,7 +408,7 @@ int main(int argc, char** argv) /* Run a CoSimulation * ================== */ int rc = 0; - switch (version) { + switch (atoi(desc->version)) { case 2: rc = _run_fmu2_cosim(desc, handle, step_size, steps); break; @@ -370,13 +416,14 @@ int main(int argc, char** argv) rc = _run_fmu3_cosim(desc, handle, step_size, steps); break; default: - _log("Unsupported FMI version (%d)!", version); + _log("Unsupported FMI version (%d)!", desc->version); return EINVAL; } _log("Simulation return value: %d", rc); /* Release allocated resources * =========================== */ + free(desc->version); for (size_t i = 0; i < desc->binary.tx_count; i++) { if (desc->binary.val_tx_binary[i]) { free(desc->binary.val_tx_binary[i]); @@ -389,17 +436,17 @@ int main(int argc, char** argv) desc->binary.val_rx_binary[i] = NULL; } } - if (desc->binary.vr_tx_binary) free(desc->binary.vr_tx_binary); - if (desc->binary.val_tx_binary) free(desc->binary.val_tx_binary); - if (desc->binary.val_size_tx_binary) free(desc->binary.val_size_tx_binary); - if (desc->binary.vr_rx_binary) free(desc->binary.vr_rx_binary); - if (desc->binary.val_rx_binary) free(desc->binary.val_rx_binary); - if (desc->binary.val_size_rx_binary) free(desc->binary.val_size_rx_binary); - if (desc->real.vr_tx_real) free(desc->real.vr_tx_real); - if (desc->real.val_tx_real) free(desc->real.val_tx_real); - if (desc->real.vr_rx_real) free(desc->real.vr_rx_real); - if (desc->real.val_rx_real) free(desc->real.val_rx_real); - if (desc) free(desc); + free(desc->binary.vr_tx_binary); + free(desc->binary.val_tx_binary); + free(desc->binary.val_size_tx_binary); + free(desc->binary.vr_rx_binary); + free(desc->binary.val_rx_binary); + free(desc->binary.val_size_rx_binary); + free(desc->real.vr_tx_real); + free(desc->real.val_tx_real); + free(desc->real.vr_rx_real); + free(desc->real.val_rx_real); + free(desc); dlclose(handle); diff --git a/dse/importer/importer.h b/dse/importer/importer.h index e8a8280..0d6df22 100644 --- a/dse/importer/importer.h +++ b/dse/importer/importer.h @@ -38,9 +38,14 @@ center footer Dynamic Simulation Environment typedef struct modelDescription { - char* modelName; - char* fmiVersion; + char* name; + char* version; char* guid; + + /* Calculated properties. */ + char* fmu_lib_path; + + /* Storage. */ struct { unsigned int* vr_rx_real; unsigned int* vr_tx_real; @@ -63,7 +68,8 @@ typedef struct modelDescription { /* xml.c */ -DLL_PRIVATE modelDescription* parse_model_desc(char* docname, uint8_t version); +DLL_PRIVATE modelDescription* parse_model_desc( + const char* docname, const char* platform); #endif // DSE_IMPORTER_IMPORTER_H_ diff --git a/dse/importer/xml.c b/dse/importer/xml.c index a1b1d86..e68f79b 100644 --- a/dse/importer/xml.c +++ b/dse/importer/xml.c @@ -36,8 +36,7 @@ xmlChar* NULL : The annotation was not found. */ -__attribute__((unused)) -static xmlChar* parse_tool_anno( +__attribute__((unused)) static xmlChar* parse_tool_anno( xmlNode* node, const char* tool, const char* name) { xmlChar* result = NULL; @@ -113,9 +112,9 @@ static inline void _alloc_var(HashMap map, void** vr_ptr, void** val_ptr, } -static inline void _parse_fmi2_scalar( - xmlNodePtr child, xmlChar* vr, xmlChar* causality, xmlChar* start, - HashMap* vr_rx_real, HashMap* vr_tx_real) +static inline void _parse_fmi2_scalar(xmlNodePtr child, xmlChar* vr, + xmlChar* causality, xmlChar* start, HashMap* vr_rx_real, + HashMap* vr_tx_real) { if (xmlStrcmp(child->name, (xmlChar*)"Real")) return; @@ -128,9 +127,9 @@ static inline void _parse_fmi2_scalar( } -static inline void _parse_fmi2_string( - xmlNodePtr child, xmlChar* vr, xmlChar* causality, xmlChar* start, - HashMap* vr_rx_binary, HashMap* vr_tx_binary) +static inline void _parse_fmi2_string(xmlNodePtr child, xmlChar* vr, + xmlChar* causality, xmlChar* start, HashMap* vr_rx_binary, + HashMap* vr_tx_binary) { if (xmlStrcmp(child->name, (xmlChar*)"String")) return; @@ -187,14 +186,13 @@ void _parse_fmi2_model_desc(HashMap* vr_rx_real, HashMap* vr_tx_real, } -static inline void _parse_fmi3_scalar( - xmlNodePtr child, xmlChar* vr, xmlChar* causality, HashMap* vr_rx_real, - HashMap* vr_tx_real) +static inline void _parse_fmi3_scalar(xmlNodePtr child, xmlChar* vr, + xmlChar* causality, HashMap* vr_rx_real, HashMap* vr_tx_real) { if (xmlStrcmp(child->name, (xmlChar*)"Float64")) return; xmlChar* start = xmlGetProp(child, (xmlChar*)"start"); - double _start = 0.0; + double _start = 0.0; if (start) _start = atof((char*)start); if (xmlStrcmp(causality, (xmlChar*)"input") == 0) { @@ -208,15 +206,13 @@ static inline void _parse_fmi3_scalar( } -static inline void _parse_fmi3_binary( - xmlNodePtr child, xmlChar* vr, xmlChar* causality, HashMap* vr_rx_binary, - HashMap* vr_tx_binary) +static inline void _parse_fmi3_binary(xmlNodePtr child, xmlChar* vr, + xmlChar* causality, HashMap* vr_rx_binary, HashMap* vr_tx_binary) { if (xmlStrcmp(child->name, (xmlChar*)"Binary")) return; xmlChar* start = NULL; - for (xmlNodePtr _child = child->children; _child; - _child = _child->next) { + for (xmlNodePtr _child = child->children; _child; _child = _child->next) { if (_child->type != XML_ELEMENT_NODE) continue; if (strcmp((char*)_child->name, "Start") == 0) { start = xmlGetProp(_child, (xmlChar*)"value"); @@ -235,8 +231,9 @@ static inline void _parse_fmi3_binary( } -static inline void _parse_fmi3_model_desc(HashMap* vr_rx_real, HashMap* vr_tx_real, - HashMap* vr_rx_binary, HashMap* vr_tx_binary, xmlXPathContext* ctx) +static inline void _parse_fmi3_model_desc(HashMap* vr_rx_real, + HashMap* vr_tx_real, HashMap* vr_rx_binary, HashMap* vr_tx_binary, + xmlXPathContext* ctx) { xmlXPathObject* xml_sv_obj = xmlXPathEvalExpression( (xmlChar*)"/fmiModelDescription/ModelVariables", ctx); @@ -251,8 +248,7 @@ static inline void _parse_fmi3_model_desc(HashMap* vr_rx_real, HashMap* vr_tx_re if (vr == NULL || causality == NULL) goto next; - _parse_fmi3_scalar( - child, vr, causality, vr_rx_real, vr_tx_real); + _parse_fmi3_scalar(child, vr, causality, vr_rx_real, vr_tx_real); _parse_fmi3_binary( child, vr, causality, vr_rx_binary, vr_tx_binary); @@ -266,7 +262,87 @@ static inline void _parse_fmi3_model_desc(HashMap* vr_rx_real, HashMap* vr_tx_re } -modelDescription* parse_model_desc(char* docname, uint8_t version) +static char* _get_fmu_binary_path( + xmlXPathContext* ctx, const char* platform, int version) +{ + char* path = NULL; + const char* dir = "linux64"; + const char* extension = "so"; + + /* Find the model_identifier. */ + xmlXPathObject* obj = xmlXPathEvalExpression( + (xmlChar*)"/fmiModelDescription/CoSimulation", ctx); + xmlNode* node = obj->nodesetval->nodeTab[0]; + xmlChar* model_identifier = xmlGetProp(node, (xmlChar*)"modelIdentifier"); + + /* Determine the OS/Arch path segment. */ + char* _platform = strdup(platform); + char* os = _platform; + char* arch = strchr(_platform, '-'); + if (arch) { + *arch = '\0'; + arch++; + } + switch (version) { + case 2: + if (strcmp(os, "linux") == 0) { + if (strcmp(arch, "amd64") == 0) dir = "linux64"; + if (strcmp(arch, "x86") == 0) dir = "linux32"; + if (strcmp(arch, "i386") == 0) dir = "linux32"; + } else if (strcmp(os, "windows") == 0) { + extension = "dll"; + if (strcmp(arch, "x64") == 0) dir = "win64"; + if (strcmp(arch, "x86") == 0) dir = "win32"; + } + break; + case 3: + if (strcmp(os, "linux") == 0) { + if (strcmp(arch, "amd64") == 0) dir = "x86_64-linux"; + if (strcmp(arch, "x86") == 0) dir = "x86_32-linux"; + if (strcmp(arch, "i386") == 0) dir = "x86_32-linux"; + } else if (strcmp(os, "windows") == 0) { + extension = "dll"; + if (strcmp(arch, "x64") == 0) dir = "x86_64-windows"; + if (strcmp(arch, "x86") == 0) dir = "x86_64-windows"; + } + break; + default: + break; + } + + /* Build the binary path. */ + const char* format = "binaries/%s/%s.%s"; + size_t len = snprintf(path, 0, format, dir, model_identifier, extension); + if (len) { + path = malloc(len + 1); + snprintf(path, len + 1, format, dir, model_identifier, extension); + } + + /* Cleanup. */ + free(_platform); + xmlFree(model_identifier); + + return path; +} + + +static inline char* _get_fmu_version(xmlXPathContext* ctx) +{ + char* result = NULL; + + xmlXPathObject* obj = + xmlXPathEvalExpression((xmlChar*)"/fmiModelDescription", ctx); + xmlNodePtr node = obj->nodesetval->nodeTab[0]; + xmlChar* version = xmlGetProp(node, (xmlChar*)"fmiVersion"); + if (version) { + result = strdup((char*)version); + xmlFree(version); + } + + return result; +} + +modelDescription* parse_model_desc(const char* docname, const char* platform) { xmlDocPtr doc; @@ -290,14 +366,18 @@ modelDescription* parse_model_desc(char* docname, uint8_t version) modelDescription* desc = calloc(1, sizeof(modelDescription)); - switch (version) { + /* Parse the Model Desc based on version.*/ + desc->version = _get_fmu_version(ctx); + switch (atoi(desc->version)) { case 2: _parse_fmi2_model_desc( &vr_rx_real, &vr_tx_real, &vr_rx_binary, &vr_tx_binary, ctx); + desc->fmu_lib_path = _get_fmu_binary_path(ctx, platform, 2); break; case 3: _parse_fmi3_model_desc( &vr_rx_real, &vr_tx_real, &vr_rx_binary, &vr_tx_binary, ctx); + desc->fmu_lib_path = _get_fmu_binary_path(ctx, platform, 3); break; default: return NULL; diff --git a/tests/testscript/e2e/example_fmu_counter.txtar b/tests/testscript/e2e/example_fmu_counter.txtar index 19d77b4..d5e0f44 100644 --- a/tests/testscript/e2e/example_fmu_counter.txtar +++ b/tests/testscript/e2e/example_fmu_counter.txtar @@ -20,12 +20,12 @@ docker run --name simer -i --rm --entrypoint="" --workdir=/repo \ -e FMU_DIR=$FMU_DIR \ -e FMU_SO=$FMU_SO \ $SIMER \ - bash -c "ls -R; cd $FMU_DIR; \ - /repo/$IMPORTER -V 2 -F $FMU_SO" + bash -c "ls -R; \ + /repo/$IMPORTER $FMU_DIR" # TODO: run with valgrind. # bash -c "cd $FMU_DIR; valgrind -q --leak-check=yes --error-exitcode=808 \ -# /repo/$IMPORTER -V 2 -F $FMU_SO" +# /repo/$IMPORTER $FMU_DIR" # TODO: add case for FMI 3. diff --git a/tests/testscript/e2e/example_fmu_linear.txtar b/tests/testscript/e2e/example_fmu_linear.txtar index 535d9e4..eb9518c 100644 --- a/tests/testscript/e2e/example_fmu_linear.txtar +++ b/tests/testscript/e2e/example_fmu_linear.txtar @@ -20,12 +20,12 @@ docker run --name simer -i --rm --entrypoint="" --workdir=/repo \ -e FMU_DIR=$FMU_DIR \ -e FMU_SO=$FMU_SO \ $SIMER \ - bash -c "ls -R; cd $FMU_DIR; \ - /repo/$IMPORTER -V 2 -F $FMU_SO" + bash -c "ls -R; \ + /repo/$IMPORTER $FMU_DIR" # TODO: run with valgrind. # bash -c "cd $FMU_DIR; valgrind -q --leak-check=yes --error-exitcode=808 \ -# /repo/$IMPORTER -V 2 -F $FMU_SO" +# /repo/$IMPORTER $FMU_DIR" # TODO: add case for FMI 3. diff --git a/tests/testscript/e2e/fmigateway_fmi2.txtar b/tests/testscript/e2e/fmigateway_fmi2.txtar index 45c9317..f82f9d5 100644 --- a/tests/testscript/e2e/fmigateway_fmi2.txtar +++ b/tests/testscript/e2e/fmigateway_fmi2.txtar @@ -19,8 +19,8 @@ exec cp -R $EXAMPLE/model $SIM/model # TEST: FMI Gateway with FMI2 exec sh -e $WORK/test.sh -stdout 'Importer: Cwd: /sim/fmu' -stdout 'Importer: Loading FMU: binaries/linux64/libfmi2gateway.so' +stdout 'Importer: FMU Dir: /sim/fmu' +stdout 'Importer: Loading FMU: binaries/linux64/fmi2gateway.so' stdout 'FMU Model instantiated' stdout 'Resource location: resources' stdout 'Setting up the Simbus connection...' @@ -52,7 +52,7 @@ docker run --name gateway -i --rm -v $ENTRYWORKDIR/$SIM:/sim \ --net="host" \ --entrypoint="" $SIMER \ bash -c "cd fmu; valgrind -q --leak-check=yes --error-exitcode=808 \ - ../bin/fmuImporter -F binaries/linux64/libfmi2gateway.so" & + ../bin/fmuImporter" & docker run --name simer -i --rm -v $ENTRYWORKDIR/$SIM:/sim \ --env SIMBUS_LOGLEVEL=4 \ diff --git a/tests/testscript/e2e/fmigateway_fmi3.txtar b/tests/testscript/e2e/fmigateway_fmi3.txtar index c1f696d..adf70c6 100644 --- a/tests/testscript/e2e/fmigateway_fmi3.txtar +++ b/tests/testscript/e2e/fmigateway_fmi3.txtar @@ -19,8 +19,8 @@ exec cp -R $EXAMPLE/model $SIM/model # TEST: FMI Gateway with FMI3 exec sh -e $WORK/test.sh -stdout 'Importer: Cwd: /sim/fmu' -stdout 'Importer: Loading FMU: binaries/x86_64-linux/libfmi3gateway.so' +stdout 'Importer: FMU Dir: /sim/fmu' +stdout 'Importer: Loading FMU: binaries/x86_64-linux/fmi3gateway.so' stdout 'FMU Model instantiated' stdout 'Resource location: resources' stdout 'Setting up the Simbus connection...' @@ -47,7 +47,7 @@ docker run --name gateway -i --rm -v $ENTRYWORKDIR/$SIM:/sim \ --net="host" \ --entrypoint="" $SIMER \ bash -c "cd fmu; valgrind -q --leak-check=yes --error-exitcode=808 \ - ../bin/fmuImporter -V 3 -F binaries/x86_64-linux/libfmi3gateway.so" & + ../bin/fmuImporter" & docker run --name simer -i --rm -v $ENTRYWORKDIR/$SIM:/sim \ --env SIMBUS_LOGLEVEL=4 \