]> gerrit.simantics Code Review - simantics/fmil.git/blob - org.simantics.fmil.core/native/FMUSimulator/src/sim_support.c
Switch to full JavaSE-11+ compatibility
[simantics/fmil.git] / org.simantics.fmil.core / native / FMUSimulator / src / sim_support.c
1 /* ------------------------------------------------------------------------- 
2  * sim_support.c
3  * Functions used by both FMU simulators fmusim_me and fmusim_cs
4  * to parse command-line arguments, to unzip and load an fmu, 
5  * to write CSV file, and more.
6  * Copyright 2011 QTronic GmbH. All rights reserved. 
7  * -------------------------------------------------------------------------*/ 
8
9 #include <stdio.h>
10 #include <stdlib.h>
11 #include <string.h>
12 #include <assert.h>
13 #include <time.h>
14 #include <stdarg.h>
15
16 #ifdef FMI_COSIMULATION
17 #include "fmi_cs.h"
18 #else
19 #include "fmi_me.h"
20 #endif
21
22 // linux/unix DSO loading
23 #ifndef _MSC_VER
24 #include <dlfcn.h>
25 #endif
26
27 #include "sim_support.h"
28 //#include "fmuExtract.h"
29
30 #define MAX_PATH_SIZE 4096
31
32 #ifdef _MSC_VER
33
34 #include <windows.h>
35 // fileName is an absolute path, e.g. C:\test\a.fmu
36 // or relative to the current dir, e.g. ..\test\a.fmu
37 // Does not check for existence of the file
38 static char* getFmuPath(const char* fileName){
39     char pathName[MAX_PATH_SIZE];
40     int n = GetFullPathName(fileName, MAX_PATH_SIZE, pathName, NULL);
41     return n ? strdup(pathName) : NULL;
42 }
43
44 int tmpPathRequests = 0;
45 static char* getTmpPath() {
46     char tmpPath[BUFSIZE];
47     if(! GetTempPath(BUFSIZE, tmpPath)) {
48         printf ("error: Could not find temporary disk space\n");
49         return NULL;
50     }
51         if(tmpPathRequests % 2 == 0) {
52                 strcat(tmpPath, "fmu\\");
53                 tmpPathRequests++;
54         } else {
55                 strcat(tmpPath, "fmu2\\");
56                 tmpPathRequests = 0;
57         }
58
59         makedir(tmpPath);
60
61     return strdup(tmpPath);
62 }
63 #endif
64
65 static void* getAdr(int* s, FMU *fmu, const char* functionName){
66     char name[BUFSIZE];
67     void* fp;
68     sprintf(name, "%s_%s", getModelIdentifier(fmu->modelDescription), functionName);
69 #ifdef _MSC_VER
70     fp = GetProcAddress(fmu->dllHandle, name);
71 #else
72     fp = dlsym(fmu->dllHandle, name);
73 #endif
74     if (!fp) {
75         printf ("warning: Function %s not found in dll\n", name);
76         *s = 0; // mark dll load as 'failed'        
77     }
78     return fp;
79 }
80
81 // Load the given dll and set function pointers in fmu
82 // Return 0 to indicate failure
83 static int loadDll(const char* dllPath, FMU *fmu) {
84     int s = 1;
85 #ifdef _MSC_VER
86     HANDLE h = LoadLibraryEx(dllPath, NULL, LOAD_WITH_ALTERED_SEARCH_PATH);
87     if (!h) {
88         int error = GetLastError();
89         printf("error %d: Could not load %s\n", error, dllPath);
90         return 0; // failure
91     }
92     fmu->dllHandle = h;
93 #else
94     void* h = dlopen(dllPath, RTLD_LAZY);
95     if (!h) {
96         char* error = dlerror();
97         printf("error %s: Could not load %s\n", error, dllPath);
98         return 0; // failure
99     }
100     fmu->dllHandle = h;
101 #endif
102
103 #ifdef FMI_COSIMULATION   
104     fmu->getTypesPlatform        = (fGetTypesPlatform)   getAdr(&s, fmu, "fmiGetTypesPlatform");
105     if (s==0) { 
106         s = 1; // work around bug for FMUs exported using Dymola 2012 and SimulationX 3.x
107         fmu->getTypesPlatform    = (fGetTypesPlatform)   getAdr(&s, fmu, "fmiGetModelTypesPlatform");
108         if (s==1) printf("  using fmiGetModelTypesPlatform instead\n", dllPath);
109     }
110     fmu->instantiateSlave        = (fInstantiateSlave)   getAdr(&s, fmu, "fmiInstantiateSlave");
111     fmu->initializeSlave         = (fInitializeSlave)    getAdr(&s, fmu, "fmiInitializeSlave");    
112     fmu->terminateSlave          = (fTerminateSlave)     getAdr(&s, fmu, "fmiTerminateSlave");
113     fmu->resetSlave              = (fResetSlave)         getAdr(&s, fmu, "fmiResetSlave");
114     fmu->freeSlaveInstance       = (fFreeSlaveInstance)  getAdr(&s, fmu, "fmiFreeSlaveInstance");
115     fmu->setRealInputDerivatives = (fSetRealInputDerivatives) getAdr(&s, fmu, "fmiSetRealInputDerivatives");
116     fmu->getRealOutputDerivatives = (fGetRealOutputDerivatives) getAdr(&s, fmu, "fmiGetRealOutputDerivatives");
117     fmu->cancelStep              = (fCancelStep)         getAdr(&s, fmu, "fmiCancelStep");
118     fmu->doStep                  = (fDoStep)             getAdr(&s, fmu, "fmiDoStep");
119     // SimulationX 3.4 and 3.5 do not yet export getStatus and getXStatus: do not count this as failure here
120     fmu->getStatus               = (fGetStatus)          getAdr(&x, fmu, "fmiGetStatus");
121     fmu->getRealStatus           = (fGetRealStatus)      getAdr(&x, fmu, "fmiGetRealStatus");
122     fmu->getIntegerStatus        = (fGetIntegerStatus)   getAdr(&x, fmu, "fmiGetIntegerStatus");
123     fmu->getBooleanStatus        = (fGetBooleanStatus)   getAdr(&x, fmu, "fmiGetBooleanStatus");
124     fmu->getStringStatus         = (fGetStringStatus)    getAdr(&x, fmu, "fmiGetStringStatus");    
125
126 #else // FMI for Model Exchange 1.0
127     fmu->getModelTypesPlatform   = (fGetModelTypesPlatform) getAdr(&s, fmu, "fmiGetModelTypesPlatform");
128     fmu->instantiateModel        = (fInstantiateModel)   getAdr(&s, fmu, "fmiInstantiateModel");
129     fmu->freeModelInstance       = (fFreeModelInstance)  getAdr(&s, fmu, "fmiFreeModelInstance");
130     fmu->setTime                 = (fSetTime)            getAdr(&s, fmu, "fmiSetTime");
131     fmu->setContinuousStates     = (fSetContinuousStates)getAdr(&s, fmu, "fmiSetContinuousStates");
132     fmu->completedIntegratorStep = (fCompletedIntegratorStep)getAdr(&s, fmu, "fmiCompletedIntegratorStep");
133     fmu->initialize              = (fInitialize)         getAdr(&s, fmu, "fmiInitialize");
134     fmu->getDerivatives          = (fGetDerivatives)     getAdr(&s, fmu, "fmiGetDerivatives");
135     fmu->getEventIndicators      = (fGetEventIndicators) getAdr(&s, fmu, "fmiGetEventIndicators");
136     fmu->eventUpdate             = (fEventUpdate)        getAdr(&s, fmu, "fmiEventUpdate");
137     fmu->getContinuousStates     = (fGetContinuousStates)getAdr(&s, fmu, "fmiGetContinuousStates");
138     fmu->getNominalContinuousStates = (fGetNominalContinuousStates)getAdr(&s, fmu, "fmiGetNominalContinuousStates");
139     fmu->getStateValueReferences = (fGetStateValueReferences)getAdr(&s, fmu, "fmiGetStateValueReferences");
140     fmu->terminate               = (fTerminate)          getAdr(&s, fmu, "fmiTerminate");
141 #endif 
142     fmu->getVersion              = (fGetVersion)         getAdr(&s, fmu, "fmiGetVersion");
143     fmu->setDebugLogging         = (fSetDebugLogging)    getAdr(&s, fmu, "fmiSetDebugLogging");
144     fmu->setReal                 = (fSetReal)            getAdr(&s, fmu, "fmiSetReal");
145     fmu->setInteger              = (fSetInteger)         getAdr(&s, fmu, "fmiSetInteger");
146     fmu->setBoolean              = (fSetBoolean)         getAdr(&s, fmu, "fmiSetBoolean");
147     fmu->setString               = (fSetString)          getAdr(&s, fmu, "fmiSetString");
148     fmu->getReal                 = (fGetReal)            getAdr(&s, fmu, "fmiGetReal");
149     fmu->getInteger              = (fGetInteger)         getAdr(&s, fmu, "fmiGetInteger");
150     fmu->getBoolean              = (fGetBoolean)         getAdr(&s, fmu, "fmiGetBoolean");
151     fmu->getString               = (fGetString)          getAdr(&s, fmu, "fmiGetString");
152     return s; 
153 }
154
155 /*static void printModelDescription(ModelDescription* md){
156     Element* e = (Element*)md;  
157     int i;
158     printf("%s\n", elmNames[e->type]);
159     for (i=0; i<e->n; i+=2) 
160         printf("  %s=%s\n", e->attributes[i], e->attributes[i+1]);
161 #ifdef FMI_COSIMULATION   
162     if (!md->cosimulation) {
163         printf("error: No Implementation element found in model description. This FMU is not for Co-Simulation.\n");
164         exit(EXIT_FAILURE);
165     }
166     e = md->cosimulation->capabilities;
167     printf("%s\n", elmNames[e->type]);
168     for (i=0; i<e->n; i+=2) 
169         printf("  %s=%s\n", e->attributes[i], e->attributes[i+1]);
170 #endif // FMI_COSIMULATION  
171 }
172 */
173
174 /*
175  * return: 1 for successful laod or number for error.
176  * -1. FMU path not found
177  * -2. Unzip failed
178  * -3. Loading model description failed
179  * -4. FMU dll load failed
180  */
181 /*
182 int loadFMU(FMU *fmu, const char* fmuFileName, const char* tmpPath) {
183     char* fmuPath;
184     char* xmlPath;
185     char* dllPath;
186         unsigned old_clock = clock();
187         unsigned current_clock = 0;//will be assigned later
188     
189     // get absolute path to FMU, NULL if not found
190     fmuPath = getFmuPath(fmuFileName);
191     if (!fmuPath) return -1; // path not found
192
193     // unzip the FMU to the tmpPath directory
194     if (unzip(fmuPath, tmpPath)) return -2; // unzip failed
195
196     // parse tmpPath\modelDescription.xml
197     xmlPath = calloc(sizeof(char), strlen(tmpPath) + strlen(XML_FILE) + 1);
198     sprintf(xmlPath, "%s%s", tmpPath, XML_FILE);
199     fmu->modelDescription = parse(xmlPath);
200     free(xmlPath);
201     if (!fmu->modelDescription) return -3; // loading model description failed
202
203         // printModelDescription(fmu.modelDescription);
204         // fflush(stdout);
205
206     // load the FMU dll
207     dllPath = calloc(sizeof(char), strlen(tmpPath) + strlen(DLL_DIR) 
208             + strlen( getModelIdentifier(fmu->modelDescription)) +  strlen(".dll") + 1);
209     sprintf(dllPath,"%s%s%s.dll", tmpPath, DLL_DIR, getModelIdentifier(fmu->modelDescription));
210     if (!loadDll(dllPath, fmu)) return -4; // loading dll failed
211
212     free(dllPath);
213     free(fmuPath);
214
215         return 1;
216 }
217 */
218
219 static void doubleToCommaString(char* buffer, double r){
220     char* comma;
221     sprintf(buffer, "%.16g", r);
222     comma = strchr(buffer, '.');
223     if (comma) *comma = ',';
224 }
225
226 // output time and all non-alias variables in CSV format
227 // if separator is ',', columns are separated by ',' and '.' is used for floating-point numbers.
228 // otherwise, the given separator (e.g. ';' or '\t') is to separate columns, and ',' is used 
229 // as decimal dot in floating-point numbers.
230 /*
231 void outputRow(FMU *fmu, fmiComponent c, double time, FILE* file, char separator, boolean header) {
232     int k;
233     fmiReal r;
234     fmiInteger i;
235     fmiBoolean b;
236     fmiString s;
237     fmiValueReference vr;
238     ScalarVariable** vars = fmu->modelDescription->modelVariables;
239     char buffer[32];
240     
241     // print first column
242     if (header) 
243         fprintf(file, "time"); 
244     else {
245         if (separator==',') 
246             fprintf(file, "%.16g", time);
247         else {
248             // separator is e.g. ';' or '\t'
249             doubleToCommaString(buffer, time);
250             fprintf(file, "%s", buffer);       
251         }
252     }
253     
254     // print all other columns
255     for (k=0; vars[k]; k++) {
256         ScalarVariable* sv = vars[k];
257         if (getAlias(sv)!=enu_noAlias) continue;
258         if (header) {
259             // output names only
260             if (separator==',') {
261                 // treat array element, e.g. print a[1, 2] as a[1.2]
262                 char* s = getName(sv);
263                 fprintf(file, "%c", separator);
264                 while (*s) {
265                    if (*s!=' ') fprintf(file, "%c", *s==',' ? '.' : *s);
266                    s++;
267                 }
268              }
269             else
270                 fprintf(file, "%c%s", separator, getName(sv));
271         }
272         else {
273             // output values
274             vr = getValueReference(sv);
275             switch (sv->typeSpec->type){
276                 case elm_Real:
277                     fmu->getReal(c, &vr, 1, &r);
278                     if (separator==',') 
279                         fprintf(file, ",%.16g", r);
280                     else {
281                         // separator is e.g. ';' or '\t'
282                         doubleToCommaString(buffer, r);
283                         fprintf(file, "%c%s", separator, buffer);       
284                     }
285                     break;
286                 case elm_Integer:
287                 case elm_Enumeration:
288                     fmu->getInteger(c, &vr, 1, &i);
289                     fprintf(file, "%c%d", separator, i);
290                     break;
291                 case elm_Boolean:
292                     fmu->getBoolean(c, &vr, 1, &b);
293                     fprintf(file, "%c%d", separator, b);
294                     break;
295                 case elm_String:
296                     fmu->getString(c, &vr, 1, &s);
297                     fprintf(file, "%c%s", separator, s);
298                     break;
299                 default: 
300                     fprintf(file, "%cNoValueForType=%d", separator,sv->typeSpec->type);
301             }
302         }
303     } // for
304     
305     // terminate this row
306     fprintf(file, "\n"); 
307 }
308 */
309
310 static const char* fmiStatusToString(fmiStatus status){
311     switch (status){
312         case fmiOK:      return "ok";
313         case fmiWarning: return "warning";
314         case fmiDiscard: return "discard";
315         case fmiError:   return "error";
316         case fmiFatal:   return "fatal";
317 #ifdef FMI_COSIMULATION
318         case fmiPending: return "fmiPending";
319 #endif
320         default:         return "?";
321     }
322 }
323
324 // search a fmu for the given variable
325 // return NULL if not found or vr = fmiUndefinedValueReference
326 static ScalarVariable* getSV(FMU* fmu, char type, fmiValueReference vr) {
327     int i;
328     Elm tp;
329     ScalarVariable** vars = fmu->modelDescription->modelVariables;
330     if (vr==fmiUndefinedValueReference) return NULL;
331     switch (type) {
332         case 'r': tp = elm_Real;    break;
333         case 'i': tp = elm_Integer; break;
334         case 'b': tp = elm_Boolean; break;
335         case 's': tp = elm_String;  break;                
336     }
337     for (i=0; vars[i]; i++) {
338         ScalarVariable* sv = vars[i];
339         if (vr==getValueReference(sv) && tp==sv->typeSpec->type) 
340             return sv;
341     }
342     return NULL;
343 }
344
345 // replace e.g. #r1365# by variable name and ## by # in message
346 // copies the result to buffer
347 static void replaceRefsInMessage(const char* msg, char* buffer, int nBuffer, FMU* fmu){
348     int i=0; // position in msg
349     int k=0; // position in buffer
350     int n;
351     char c = msg[i];
352     while (c!='\0' && k < nBuffer) {
353         if (c!='#') {
354             buffer[k++]=c;
355             i++;
356             c = msg[i];
357         }
358         else {
359             char* end = strchr((char*)(msg+i+1), '#');
360             if (!end) {
361                 printf("unmatched '#' in '%s'\n", msg);
362                 buffer[k++]='#';
363                 break;
364             }
365             n = end - (msg+i);
366             if (n==1) {
367                 // ## detected, output #
368                 buffer[k++]='#';
369                 i += 2;
370                 c = msg[i];
371             }
372             else {
373                 char type = msg[i+1]; // one of ribs
374                 fmiValueReference vr;
375                 int nvr = sscanf(msg+i+2, "%u", &vr);
376                 if (nvr==1) {
377                     // vr of type detected, e.g. #r12#
378                     ScalarVariable* sv = getSV(fmu, type, vr);
379                     const char* name = sv ? getName(sv) : "?";
380                     sprintf(buffer+k, "%s", name);
381                     k += strlen(name);
382                     i += (n+1);
383                     c = msg[i]; 
384                 }
385                 else {
386                     // could not parse the number
387                     printf("illegal value reference at position %d in '%s'\n", i+2, msg);
388                     buffer[k++]='#';
389                     break;
390                 }
391             }
392         }
393     } // while
394     buffer[k] = '\0';
395 }
396
397 #define MAX_MSG_SIZE 1000
398 void fmuLogger(FMU *fmu, fmiComponent c, fmiString instanceName, fmiStatus status,
399                fmiString category, fmiString message, ...) {
400     char msg[MAX_MSG_SIZE];
401 //    char* copy;
402     va_list argp;
403
404     // replace C format strings
405     va_start(argp, message);
406     vsprintf(msg, message, argp);
407     va_end(argp);
408
409     // replace e.g. ## and #r12#  
410 //    copy = strdup(msg);
411 //    replaceRefsInMessage(copy, msg, MAX_MSG_SIZE, fmu);
412 //    free(copy);
413     
414     // print the final message
415     if (!instanceName) instanceName = "?";
416     if (!category) category = "?";
417     //printf("%s %s (%s): %s\n", fmiStatusToString(status), instanceName, category, msg);
418         printf("%s\n", message);
419 }
420
421 int error(const char* message){
422     printf("%s\n", message);
423     return 0;
424 }
425
426 void parseArguments(int argc, char *argv[], char** fmuFileName, double* tEnd, double* h, int* loggingOn, char* csv_separator) {
427     // parse command line arguments
428     if (argc>1) {
429         *fmuFileName = argv[1];
430     }
431     else {
432         printf("error: no fmu file\n");
433         printHelp(argv[0]);
434         exit(EXIT_FAILURE);
435     }
436     if (argc>2) {
437         if (sscanf(argv[2],"%lf", tEnd) != 1) {
438             printf("error: The given end time (%s) is not a number\n", argv[2]);
439             exit(EXIT_FAILURE);
440         }
441     }
442     if (argc>3) {
443         if (sscanf(argv[3],"%lf", h) != 1) {
444             printf("error: The given stepsize (%s) is not a number\n", argv[3]);
445             exit(EXIT_FAILURE);
446         }
447     }
448     if (argc>4) {
449         if (sscanf(argv[4],"%d", loggingOn) != 1 || *loggingOn<0 || *loggingOn>1) {
450             printf("error: The given logging flag (%s) is not boolean\n", argv[4]);
451             exit(EXIT_FAILURE);
452         }
453     }
454     if (argc>5) {
455         if (strlen(argv[5]) != 1) {
456             printf("error: The given CSV separator char (%s) is not valid\n", argv[5]);
457             exit(EXIT_FAILURE);
458         }
459         switch (argv[5][0]) {
460             case 'c': *csv_separator = ','; break; // comma
461             case 's': *csv_separator = ';'; break; // semicolon
462             default:  *csv_separator = argv[5][0]; break; // any other char
463         }
464     }
465     if (argc>6) {
466         printf("warning: Ignoring %d additional arguments: %s ...\n", argc-6, argv[6]);
467         printHelp(argv[0]);
468     }
469 }
470
471 void printHelp(const char* fmusim) {
472     printf("command syntax: %s <model.fmu> <tEnd> <h> <loggingOn> <csv separator>\n", fmusim);
473     printf("   <model.fmu> .... path to FMU, relative to current dir or absolute, required\n");
474     printf("   <tEnd> ......... end  time of simulation, optional, defaults to 1.0 sec\n");
475     printf("   <h> ............ step size of simulation, optional, defaults to 0.1 sec\n");
476     printf("   <loggingOn> .... 1 to activate logging,   optional, defaults to 0\n");
477     printf("   <csv separator>. separator in csv file,   optional, c for ';', s for';', defaults to c\n");
478 }
479