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