1 package org.simantics.fmil.core;
4 import java.io.IOException;
5 import java.io.RandomAccessFile;
7 import java.nio.channels.FileChannel;
8 import java.nio.channels.FileLock;
9 import java.nio.file.Paths;
10 import java.util.ArrayList;
11 import java.util.HashSet;
12 import java.util.List;
14 import java.util.UUID;
16 import org.eclipse.core.runtime.FileLocator;
17 import org.eclipse.core.runtime.Path;
18 import org.eclipse.core.runtime.Platform;
19 import org.osgi.framework.Bundle;
20 import org.simantics.fmil.core.ExecEnvironment.OSType;
21 import org.simantics.utils.FileUtils;
22 import org.slf4j.Logger;
23 import org.slf4j.LoggerFactory;
25 import gnu.trove.list.array.TIntArrayList;
26 import gnu.trove.map.hash.TObjectIntHashMap;
31 private static final Logger LOGGER = LoggerFactory.getLogger(FMIL.class);
36 private static int OK = 0;
37 private static int ERROR = 1;
38 private static String UNSATISFIED_LINK = "Method not found. DLL might not be loaded properly.";
39 public static final String TEMP_FMU_DIRECTORY_NAME = "fmil";
40 public static String TEMP_FMU_COMMON_DIRECTORY;
41 public static String LOCK_FILE_NAME = "fmil.lock";
43 public static Object syncObject = new Object();
46 * Static: load native libraries required for the FMU simulation to work.
50 File[] libraries = new File[2];
52 ExecEnvironment env = ExecEnvironment.calculate();
55 URL sharedFMILIBUrl = null;
56 URL simulatorFMIUrl = null;
57 Bundle b = Platform.getBundle("org.simantics.fmil.core");
59 if (env.os == OSType.WINDOWS) {
60 sharedFMILIBUrl = FileLocator.find(b, new Path("libraries/fmilib_shared.dll"), null);
61 simulatorFMIUrl = FileLocator.find(b, new Path("libraries/FMUSimulator.dll"), null);
62 } else if(env.os == OSType.LINUX) {
63 sharedFMILIBUrl = FileLocator.find(b, new Path("libraries/libfmilib_shared.so"), null);
64 simulatorFMIUrl = FileLocator.find(b, new Path("libraries/FMUSimulator.so"), null);
67 libraries[0] = new File(FileLocator.toFileURL(sharedFMILIBUrl).getPath());
68 libraries[1] = new File(FileLocator.toFileURL(simulatorFMIUrl).getPath());
69 } catch (Exception e) {
70 LOGGER.error("Failed to resolve native FMU simulation library for execution environment {}.{}", env.os, env.arch, e);
73 for(File library : libraries) {
75 System.err.println("FMU library not loaded. FMU simulation not working.");
77 } else if(!library.isFile()) {
78 System.err.println(library.getAbsolutePath() + " not found");
81 System.load(library.getAbsolutePath());
82 } catch (Throwable t) {
83 System.err.println(t.getMessage());
90 * Static: initialize fmu temp folder from current working directory
93 TEMP_FMU_COMMON_DIRECTORY = Paths.get(".").toAbsolutePath().normalize().toString();
96 public static void setTempFMUCommonDir(File dir) {
97 TEMP_FMU_COMMON_DIRECTORY = dir.getAbsolutePath();
100 private String fmuDir;
103 public String TEMP_FOLDER_1;
104 public String TEMP_FOLDER_2;
105 public String TEMP_FMU_DIRECTORY;
106 private String dirName;
108 private String[] variableNames;
109 private String[] variableDescriptions;
110 private String[] variableDeclaredTypes;
111 private int[] variableReferences;
112 private int[] variableTypes;
113 private int[] variableCausalities;
114 private int[] variableVariabilities;
116 private String[] declaredTypes;
117 private String[] declaredTypeDescriptions;
118 private String[] declaredTypeQuantities;
119 private String[] declaredTypeUnits;
121 private TObjectIntHashMap<String> variableMap = new TObjectIntHashMap<String>();
123 private Set<String> subscriptionSet = new HashSet<String>();
124 private TIntArrayList subscription = new TIntArrayList();
125 private ArrayList<String> subscribedNames = new ArrayList<String>();
127 public List<String> getSubscribedNames() {
128 return subscribedNames;
131 public boolean subscribe(String name) throws FMILException {
133 int vr = variableMap.get(name);
134 if(vr == 0) return false;
135 if(!subscriptionSet.add(name)) return false;
136 subscribedNames.add(name);
137 System.err.println("subscribed : " + name + " => " + subscribedNames.size());
138 subscription.add(vr);
139 subscribe(new int[] { vr });
144 // Create a directory for this control
145 File tempDir = new File(TEMP_FMU_COMMON_DIRECTORY, UUID.randomUUID().toString());
147 TEMP_FMU_DIRECTORY = tempDir.getAbsolutePath();
149 // Create two directories inside the temp directory for this control
150 dirName = UUID.randomUUID().toString();
151 File fmuDir = new File(TEMP_FMU_DIRECTORY, dirName);
154 TEMP_FOLDER_1 = fmuDir.toString();
155 TEMP_FOLDER_2 = fmuDir.toString() + "_2";
157 // Lock fmu directory in temp directory
161 public int getModelIDNew() {
165 public String getModelID() {
169 public String getFmuDir() {
174 * Load fmu from a given file path. Releases the (possible) previously
177 * @param path absolute file path for fmu file
178 * @throws FMILException
180 private int fmuN = 0;
181 private boolean fmuLoaded = false;
182 public void loadFMUFile(String path) throws FMILException {
184 synchronized(syncObject) {
187 fmuDir = TEMP_FOLDER_1;
190 fmuDir = TEMP_FOLDER_2;
194 File tempDir = new File(fmuDir);
195 if(tempDir.isDirectory()) {
197 FileUtils.deleteAll(tempDir);
198 } catch (IOException e) {
199 throw new FMILException("Could not create temp folder for fmu");
208 String tmpPath = tempDir.getAbsolutePath();
209 if(!tmpPath.endsWith("\\"))
210 tmpPath = tmpPath + "\\";
211 id = loadFMUFile_(path, tmpPath);
214 getAllVariableReferences();
216 for(int i=0;i<variableNames.length;i++) {
217 variableMap.put(variableNames[i], variableReferences[i]);
221 } catch (UnsatisfiedLinkError err) {
222 throw new FMILException(UNSATISFIED_LINK, err);
223 } catch (Exception e) {
224 throw new FMILException(e.getMessage());
229 private native int loadFMUFile_(String path, String toDir);
232 * Set a step length for simulation
234 * @param step Step length for simulation
235 * @throws FMILException
237 public void setStepLength(double step) throws FMILException {
238 synchronized(syncObject) {
241 int ret = setStepLength_(getModelIDNew(), step);
243 LOGGER.warn("Function return value != OK, an exception should have been thrown from native code!");
245 } catch (UnsatisfiedLinkError err) {
246 throw new FMILException(UNSATISFIED_LINK);
247 } catch (Exception e) {
248 throw new FMILException(e.getMessage());
253 private native int setStepLength_(int id, double step);
256 * Instantiates a simulation.
258 * Make sure that an FMU is loaded first.
259 * @throws FMILException
261 public void instantiateSimulation() throws FMILException {
262 synchronized(syncObject) {
266 int ret = instantiateSimulation_(getModelIDNew());
268 LOGGER.warn("Function return value != OK, an exception should have been thrown from native code!");
270 } catch (FMILException e) {
272 } catch (UnsatisfiedLinkError err) {
273 throw new FMILException(UNSATISFIED_LINK);
274 } catch (Exception e) {
275 throw new FMILException(e.getMessage());
280 private native int instantiateSimulation_(int id) throws FMILException;
284 * Initializes a simulation.
286 * Make sure that simulation is instantiated first!
287 * @throws FMILException
289 public void initializeSimulation() throws FMILException {
290 synchronized(syncObject) {
294 int ret = initializeSimulation_(getModelIDNew());
296 LOGGER.warn("Function return value != OK, an exception should have been thrown from native code!");
298 } catch (FMILException e) {
300 } catch (UnsatisfiedLinkError err) {
301 throw new FMILException(UNSATISFIED_LINK);
302 } catch (Exception e) {
303 throw new FMILException(e.getMessage());
308 private native int initializeSimulation_(int id) throws FMILException;
311 * Subscribe a set of variables from a loaded simulation.
313 * Make sure that an FMU is loaded first.
314 * @param variables Array of variables
315 * @throws FMILException
317 public void subscribe(int[] variables) throws FMILException {
318 synchronized(syncObject) {
322 int ret = subscribe_(getModelIDNew(), variables);
324 LOGGER.warn("Function return value != OK, an exception should have been thrown from native code!");
326 } catch (UnsatisfiedLinkError err) {
327 throw new FMILException(UNSATISFIED_LINK);
328 } catch (Exception e) {
329 throw new FMILException(e.getMessage());
334 private native int subscribe_(int id, int[] variables);
337 * Set a new (Real, double) value for a variable. If the variable is a
338 * parameter, the change is effective immediately.
340 * @param name Variable
341 * @param value New (Real, double) value
342 * @throws FMILException
344 public void setRealValue(String name, double value) throws FMILException {
346 synchronized(syncObject) {
350 int ret = setRealValue_(getModelIDNew(), variableMap.get(name), value);
352 LOGGER.warn("Function return value != OK, an exception should have been thrown from native code!");
354 } catch (FMILException e) {
356 } catch (UnsatisfiedLinkError err) {
357 throw new FMILException(UNSATISFIED_LINK);
358 } catch (Exception e) {
359 throw new FMILException(e.getMessage());
366 public void setRealValue(int variableReference, double value) throws FMILException {
368 synchronized(syncObject) {
372 int ret = setRealValue_(getModelIDNew(), variableReference, value);
374 LOGGER.warn("Function return value != OK, an exception should have been thrown from native code!");
376 } catch (FMILException e) {
378 } catch (UnsatisfiedLinkError err) {
379 throw new FMILException(UNSATISFIED_LINK);
380 } catch (Exception e) {
381 throw new FMILException(e.getMessage());
388 private native int setRealValue_(int id, int variableReference, double value) throws FMILException;
391 // * Set a new (integer) value for a variable. If the variable is a
392 // * parameter, the change is effective immediately.
394 // * @param name Variable
395 // * @param value New (integer) value
396 // * @throws FMILException
398 // public void setIntegerValue(String name, int value) throws FMILException {
399 // synchronized(syncObject) {
403 // int ret = setIntegerValue_(getModelID(), name, value);
405 // throw new FMILException(getLastErrorMessage());
407 // } catch (UnsatisfiedLinkError err) {
408 // throw new FMILException(UNSATISFIED_LINK);
409 // } catch (Exception e) {
410 // throw new FMILException(e.getMessage());
414 // private native int setIntegerValue_(String id, String name, int value);
417 // * Set a new (boolean) value for a variable. If the variable is a
418 // * parameter, the change is effective immediately.
420 // * @param name Variable
421 // * @param value New (boolean) value
422 // * @throws FMILException
424 // public void setBooleanValue(String name, boolean value) throws FMILException {
425 // synchronized(syncObject) {
429 // int ret = setBooleanValue_(getModelID(), name, value);
431 // throw new FMILException(getLastErrorMessage());
433 // } catch (UnsatisfiedLinkError err) {
434 // throw new FMILException(UNSATISFIED_LINK);
435 // } catch (Exception e) {
436 // throw new FMILException(e.getMessage());
440 // private native int setBooleanValue_(String id, String name, boolean value);
442 // public void setTime(double time) throws FMILException {
443 // synchronized(syncObject) {
447 // int ret = setTime_(getModelID(), time);
449 // throw new FMILException(getLastErrorMessage());
451 // } catch (UnsatisfiedLinkError err) {
452 // throw new FMILException(UNSATISFIED_LINK);
453 // } catch (Exception e) {
454 // throw new FMILException(e.getMessage());
458 // private native int setTime_(String id, double time);
461 * Simulate one step forward. The step length can be set with
464 * @throws FMILException
466 public void simulateStep() throws FMILException {
467 synchronized(syncObject) {
471 int ret = simulateStep_(getModelIDNew());
473 LOGGER.warn("Function return value != OK, an exception should have been thrown from native code!");
475 } catch (FMILException e) {
477 } catch (UnsatisfiedLinkError err) {
478 throw new FMILException(UNSATISFIED_LINK);
479 } catch (Exception e) {
480 throw new FMILException(e.getMessage());
484 private native int simulateStep_(int id) throws FMILException;
487 * Get an array containing the current values for subscribed variables. The
488 * values are in the same order as in the subscription.
490 * @param results An array the size of subscribed results
493 public double[] getSubscribedResults() throws FMILException {
494 synchronized(syncObject) {
498 double[] results = new double[subscription.size()];
499 return getSubscribedResults_(getModelIDNew(), results);
501 } catch (UnsatisfiedLinkError err) {
502 throw new FMILException(UNSATISFIED_LINK);
503 } catch (Exception e) {
504 throw new FMILException(e.getMessage());
509 private native double[] getSubscribedResults_(int id, double[] results);
513 * Unload FMU and the dll:s that it requires.
515 * To be called after all FMU simulations are ended.
516 * If the fmu is loaded again / changed, call to loadFMUFile is sufficient. loadFMUFile
517 * releases the previous fmu.dll
519 * @throws FMILException
521 public void unloadFMU() throws FMILException {
522 synchronized(syncObject) {
526 unlockFMUDirectory();
528 int ret = unloadFMU_(getModelIDNew());
530 LOGGER.warn("Function return value != OK, an exception should have been thrown from native code!");
533 removeFMUDirectoryContents();
535 } catch (FMILException e) {
537 } catch (UnsatisfiedLinkError err) {
538 throw new FMILException(UNSATISFIED_LINK);
539 } catch (Exception e) {
540 throw new FMILException(e.getMessage());
544 private native int unloadFMU_(int id) throws FMILException;
547 // * Checks if fmu has been initialized
548 // * @return current simulation time
550 // public boolean isInitialized() throws FMILException {
551 // synchronized(syncObject) {
553 // return isInitialized_(getModelID());
554 // } catch (UnsatisfiedLinkError err) {
555 // throw new FMILException(UNSATISFIED_LINK);
556 // } catch (Exception e) {
557 // throw new FMILException(e.getMessage());
562 // private native boolean isInitialized_(String id);
565 * Get the current simulation time
566 * @return current simulation time
568 public double getTime() throws FMILException {
569 synchronized(syncObject) {
573 return getTime_(getModelIDNew());
575 } catch (UnsatisfiedLinkError err) {
576 throw new FMILException(UNSATISFIED_LINK);
577 } catch (Exception e) {
578 throw new FMILException(e.getMessage());
583 private native double getTime_(int id);
586 * Get all variables in a loaded model
587 * @return all variables in a loaded model
589 public String[] getAllVariables() throws FMILException {
590 synchronized(syncObject) {
594 if(variableNames == null) {
595 variableNames = getAllVariables_(getModelIDNew());
597 return variableNames;
599 } catch (UnsatisfiedLinkError err) {
600 throw new FMILException(UNSATISFIED_LINK);
601 } catch (Exception e) {
602 throw new FMILException(e.getMessage());
607 private native String[] getAllVariables_(int id);
609 public String[] getAllVariableDescriptions() throws FMILException {
610 synchronized(syncObject) {
614 if(variableDescriptions == null) {
615 variableDescriptions = getAllVariableDescriptions_(getModelIDNew());
617 return variableDescriptions;
619 } catch (UnsatisfiedLinkError err) {
620 throw new FMILException(UNSATISFIED_LINK);
621 } catch (Exception e) {
622 throw new FMILException(e.getMessage());
627 private native String[] getAllVariableDescriptions_(int id);
629 public String[] getAllVariableDeclaredTypes() throws FMILException {
630 synchronized(syncObject) {
634 if(variableDeclaredTypes == null) {
635 variableDeclaredTypes = getAllVariableDeclaredTypes_(getModelIDNew());
637 return variableDeclaredTypes;
639 } catch (UnsatisfiedLinkError err) {
640 throw new FMILException(UNSATISFIED_LINK);
641 } catch (Exception e) {
642 throw new FMILException(e.getMessage());
647 private native String[] getAllVariableDeclaredTypes_(int id);
649 public int[] getAllVariableReferences() throws FMILException {
650 synchronized(syncObject) {
654 if(variableReferences == null) {
655 variableReferences = getAllVariableReferences_(getModelIDNew(), new int[variableNames.length]);
657 return variableReferences;
659 } catch (UnsatisfiedLinkError err) {
660 throw new FMILException(UNSATISFIED_LINK);
661 } catch (Exception e) {
662 throw new FMILException(e.getMessage());
667 private native int[] getAllVariableReferences_(int id, int[] array);
669 public int[] getAllVariableTypes() throws FMILException {
670 synchronized(syncObject) {
674 if(variableTypes == null) {
675 variableTypes = getAllVariableTypes_(getModelIDNew(), new int[variableNames.length]);
677 return variableTypes;
679 } catch (UnsatisfiedLinkError err) {
680 throw new FMILException(UNSATISFIED_LINK);
681 } catch (Exception e) {
682 throw new FMILException(e.getMessage());
687 private native int[] getAllVariableTypes_(int id, int[] array);
689 public int[] getAllVariableCausalities() throws FMILException {
690 synchronized(syncObject) {
694 if(variableCausalities == null) {
695 variableCausalities = getAllVariableCausalities_(getModelIDNew(), new int[variableNames.length]);
697 return variableCausalities;
699 } catch (UnsatisfiedLinkError err) {
700 throw new FMILException(UNSATISFIED_LINK);
701 } catch (Exception e) {
702 throw new FMILException(e.getMessage());
707 private native int[] getAllVariableCausalities_(int id, int[] array);
709 public int[] getAllVariableVariabilities() throws FMILException {
710 synchronized(syncObject) {
714 if(variableVariabilities == null) {
715 variableVariabilities = getAllVariableVariabilities_(getModelIDNew(), new int[variableNames.length]);
717 return variableVariabilities;
719 } catch (UnsatisfiedLinkError err) {
720 throw new FMILException(UNSATISFIED_LINK);
721 } catch (Exception e) {
722 throw new FMILException(e.getMessage());
727 private native int[] getAllVariableVariabilities_(int id, int[] array);
730 * Get all variables in a loaded model
731 * @return all variables in a loaded model
733 public String[] getAllDeclaredTypes() throws FMILException {
734 synchronized(syncObject) {
738 if(declaredTypes == null) {
739 declaredTypes = getAllDeclaredTypes_(getModelIDNew());
741 return declaredTypes;
743 } catch (UnsatisfiedLinkError err) {
744 throw new FMILException(UNSATISFIED_LINK);
745 } catch (Exception e) {
746 throw new FMILException(e.getMessage());
751 private native String[] getAllDeclaredTypes_(int id);
753 public String[] getAllDeclaredTypeDescriptions() throws FMILException {
754 synchronized(syncObject) {
758 if(declaredTypeDescriptions == null) {
759 declaredTypeDescriptions = getAllDeclaredTypeDescriptions_(getModelIDNew());
761 return declaredTypeDescriptions;
763 } catch (UnsatisfiedLinkError err) {
764 throw new FMILException(UNSATISFIED_LINK);
765 } catch (Exception e) {
766 throw new FMILException(e.getMessage());
771 private native String[] getAllDeclaredTypeDescriptions_(int id);
773 public String[] getAllDeclaredTypeQuantities() throws FMILException {
774 synchronized(syncObject) {
778 if(declaredTypeQuantities == null) {
779 declaredTypeQuantities = getAllDeclaredTypeQuantities_(getModelIDNew());
781 return declaredTypeQuantities;
783 } catch (UnsatisfiedLinkError err) {
784 throw new FMILException(UNSATISFIED_LINK);
785 } catch (Exception e) {
786 throw new FMILException(e.getMessage());
791 private native String[] getAllDeclaredTypeQuantities_(int id);
793 public String[] getAllDeclaredTypeUnits() throws FMILException {
794 synchronized(syncObject) {
798 if(declaredTypeUnits == null) {
799 declaredTypeUnits = getAllDeclaredTypeUnits_(getModelIDNew());
801 return declaredTypeUnits;
803 } catch (UnsatisfiedLinkError err) {
804 throw new FMILException(UNSATISFIED_LINK);
805 } catch (Exception e) {
806 throw new FMILException(e.getMessage());
811 private native String[] getAllDeclaredTypeUnits_(int id);
816 // * Get all variables from model that match the filter (and time variable)
818 // * @param regexp Regular expression filter
819 // * @return An array of variable names that match regexp filter (and time-variable)
820 // * @throws FMILException
822 // public String[] filterVariables(String regexp) throws FMILException {
823 // synchronized(syncObject) {
826 // return filterVariables_(getModelID(), regexp + "|time");
828 // } catch (UnsatisfiedLinkError err) {
829 // throw new FMILException(UNSATISFIED_LINK);
830 // } catch (Exception e) {
831 // throw new FMILException(e.getMessage());
836 // private native String[] filterVariables_(String id, String regexp);
839 // * Get the last error message
840 // * @return Last error message
842 // public String getLastErrorMessage() throws FMILException {
843 // synchronized(syncObject) {
848 // //return getLastErrorMessage_(getModelID());
850 // } catch (UnsatisfiedLinkError err) {
851 // throw new FMILException(UNSATISFIED_LINK);
852 // } catch (Exception e) {
853 // throw new FMILException(e.getMessage());
858 // private native String getLastErrorMessage_(String id);
861 * Get a real (double) value for variable
862 * @param name Name of the variable
864 * @throws FMILException
866 public double getRealValue(String name) throws FMILException {
867 synchronized(syncObject) {
870 // TODO: printtaa id ja name, jotta saadaan virheessä kiinni
871 double result = getRealValue_(getModelIDNew(), variableMap.get(name));
872 System.err.println("getRealValue " + name + " = " + result);
874 } catch (UnsatisfiedLinkError err) {
875 throw new FMILException(UNSATISFIED_LINK);
876 } catch (Exception e) {
877 throw new FMILException(e.getMessage());
882 private native double getRealValue_(int id, int variableReference);
885 // * Get a string value for variable
886 // * @param name Name of the variable
888 // * @throws FMILException
890 // public String getStringValue(String name) throws FMILException {
891 // synchronized(syncObject) {
894 // return getStringValue_(getModelID(), name);
895 // } catch (UnsatisfiedLinkError err) {
896 // throw new FMILException(UNSATISFIED_LINK);
897 // } catch (Exception e) {
898 // throw new FMILException(e.getMessage());
903 // private native String getStringValue_(String id, String name);
906 // * Get an integer value for variable
907 // * @param name Name of the variable
909 // * @throws FMILException
911 // public int getIntegerValue(String name) throws FMILException {
912 // synchronized(syncObject) {
915 // return getIntegerValue_(getModelID(), name);
916 // } catch (UnsatisfiedLinkError err) {
917 // throw new FMILException(UNSATISFIED_LINK);
918 // } catch (Exception e) {
919 // throw new FMILException(e.getMessage());
924 // private native int getIntegerValue_(String id, String name);
927 // * Get a real (double) value for variable
928 // * @param name Name of the variable
930 // * @throws FMILException
932 // public boolean getBooleanValue(String name) throws FMILException {
933 // synchronized(syncObject) {
936 // return getBooleanValue_(getModelID(), name);
937 // } catch (UnsatisfiedLinkError err) {
938 // throw new FMILException(UNSATISFIED_LINK);
939 // } catch (Exception e) {
940 // throw new FMILException(e.getMessage());
945 // private native boolean getBooleanValue_(String id, String name);
947 private FileChannel channel;
948 private FileLock lock;
950 @SuppressWarnings("resource")
951 private boolean lockFMUDirectory() {
954 // Get a file channel for the lock file
955 File lockFile = new File(TEMP_FMU_DIRECTORY, LOCK_FILE_NAME);
956 if(!lockFile.isFile())
957 lockFile.createNewFile();
959 channel = new RandomAccessFile(lockFile, "rw").getChannel();
961 // Use the file channel to create a lock on the file.
962 // This method blocks until it can retrieve the lock.
963 lock = channel.lock();
965 // // Try acquiring the lock without blocking. This method returns
966 // // null or throws an exception if the file is already locked.
968 // lock = channel.tryLock();
969 // } catch (OverlappingFileLockException e) {
970 // // File is already locked in this thread or virtual machine
972 } catch (IOException e) {
979 private boolean unlockFMUDirectory() {
988 } catch (IOException e) {
994 private boolean removeFMUDirectoryContents() {
997 File tempDir = new File(TEMP_FMU_DIRECTORY);
998 FileUtils.deleteAll(tempDir);
1000 } catch (IOException e) {
1007 protected void finalize() throws Throwable {
1010 } catch (Throwable t) {
1011 LOGGER.error("Could not unload native FMU!", t);