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.Arrays;
12 import java.util.HashSet;
13 import java.util.List;
15 import java.util.UUID;
17 import org.eclipse.core.runtime.FileLocator;
18 import org.eclipse.core.runtime.Path;
19 import org.eclipse.core.runtime.Platform;
20 import org.osgi.framework.Bundle;
21 import org.simantics.fmil.core.ExecEnvironment.OSType;
22 import org.simantics.utils.FileUtils;
23 import org.slf4j.Logger;
24 import org.slf4j.LoggerFactory;
26 import gnu.trove.list.array.TIntArrayList;
27 import gnu.trove.map.hash.TObjectIntHashMap;
32 private static final Logger LOGGER = LoggerFactory.getLogger(FMIL.class);
37 private static int OK = 0;
38 private static int ERROR = 1;
39 private static String UNSATISFIED_LINK = "Method not found. DLL might not be loaded properly.";
40 public static final String TEMP_FMU_DIRECTORY_NAME = "fmil";
41 public static String TEMP_FMU_COMMON_DIRECTORY;
42 public static String LOCK_FILE_NAME = "fmil.lock";
44 public static Object syncObject = new Object();
47 * Static: load native libraries required for the FMU simulation to work.
51 File[] libraries = new File[2];
53 ExecEnvironment env = ExecEnvironment.calculate();
56 URL sharedFMILIBUrl = null;
57 URL simulatorFMIUrl = null;
58 Bundle b = Platform.getBundle("org.simantics.fmil.core");
60 if (env.os == OSType.WINDOWS) {
61 sharedFMILIBUrl = FileLocator.find(b, new Path("libraries/fmilib_shared.dll"), null);
62 simulatorFMIUrl = FileLocator.find(b, new Path("libraries/FMUSimulator.dll"), null);
63 } else if(env.os == OSType.LINUX) {
64 sharedFMILIBUrl = FileLocator.find(b, new Path("libraries/libfmilib_shared.so"), null);
65 simulatorFMIUrl = FileLocator.find(b, new Path("libraries/libFMUSimulator.so"), null);
68 libraries[0] = new File(FileLocator.toFileURL(sharedFMILIBUrl).getPath());
69 libraries[1] = new File(FileLocator.toFileURL(simulatorFMIUrl).getPath());
70 } catch (Exception e) {
71 LOGGER.error("Failed to resolve native FMU simulation library for execution environment {}.{}", env.os, env.arch, e);
74 for(File library : libraries) {
76 System.err.println("FMU library not loaded. FMU simulation not working.");
78 } else if(!library.isFile()) {
79 System.err.println(library.getAbsolutePath() + " not found");
82 System.load(library.getAbsolutePath());
83 } catch (Throwable t) {
84 System.err.println(t.getMessage());
91 * Static: initialize fmu temp folder from current working directory
94 TEMP_FMU_COMMON_DIRECTORY = Paths.get(".").toAbsolutePath().normalize().toString();
97 public static void setTempFMUCommonDir(File dir) {
98 TEMP_FMU_COMMON_DIRECTORY = dir.getAbsolutePath();
101 private String fmuDir;
104 public String TEMP_FOLDER_1;
105 public String TEMP_FOLDER_2;
106 public String TEMP_FMU_DIRECTORY;
107 private String dirName;
109 private String[] variableNames;
110 private String[] variableDescriptions;
111 private String[] variableDeclaredTypes;
112 private int[] variableReferences;
113 private int[] variableTypes;
114 private int[] variableCausalities;
115 private int[] variableVariabilities;
117 private String[] declaredTypes;
118 private String[] declaredTypeDescriptions;
119 private String[] declaredTypeQuantities;
120 private String[] declaredTypeUnits;
122 private TObjectIntHashMap<String> variableMap = new TObjectIntHashMap<String>();
124 private Set<String> subscriptionSet = new HashSet<String>();
125 private TIntArrayList subscription = new TIntArrayList();
126 private ArrayList<String> subscribedNames = new ArrayList<String>();
128 public List<String> getSubscribedNames() {
129 return subscribedNames;
132 public boolean subscribe(String name) throws FMILException {
133 synchronized(syncObject) {
135 int vr = variableMap.get(name);
136 if(vr == 0) return false;
137 if(!subscriptionSet.add(name)) return false;
138 subscribedNames.add(name);
139 subscription.add(vr);
140 subscribe(new int[] { vr });
146 // Create a directory for this control
147 File tempDir = new File(TEMP_FMU_COMMON_DIRECTORY, UUID.randomUUID().toString());
149 TEMP_FMU_DIRECTORY = tempDir.getAbsolutePath();
151 // Create two directories inside the temp directory for this control
152 dirName = UUID.randomUUID().toString();
153 File fmuDir = new File(TEMP_FMU_DIRECTORY, dirName);
156 TEMP_FOLDER_1 = fmuDir.toString();
157 TEMP_FOLDER_2 = fmuDir.toString() + "_2";
159 // Lock fmu directory in temp directory
163 public int getModelIDNew() {
167 public String getModelID() {
171 public String getFmuDir() {
176 * Load fmu from a given file path. Releases the (possible) previously
179 * @param path absolute file path for fmu file
180 * @throws FMILException
182 private int fmuN = 0;
183 private boolean fmuLoaded = false;
184 public void loadFMUFile(String path) throws FMILException {
186 synchronized(syncObject) {
189 fmuDir = TEMP_FOLDER_1;
192 fmuDir = TEMP_FOLDER_2;
196 File tempDir = new File(fmuDir);
197 if(tempDir.isDirectory()) {
199 FileUtils.deleteAll(tempDir);
200 } catch (IOException e) {
201 throw new FMILException("Could not create temp folder for fmu");
210 String tmpPath = tempDir.getAbsolutePath();
211 if(!tmpPath.endsWith("\\") && !tmpPath.endsWith("/"))
212 tmpPath = tmpPath + "/";
213 id = loadFMUFile_(path, tmpPath);
216 getAllVariableReferences();
218 for(int i=0;i<variableNames.length;i++) {
219 variableMap.put(variableNames[i], variableReferences[i]);
223 } catch (UnsatisfiedLinkError err) {
224 throw new FMILException(UNSATISFIED_LINK, err);
225 } catch (Exception e) {
226 LOGGER.error(e.getMessage());
227 throw new FMILException(e.getMessage());
232 private native int loadFMUFile_(String path, String toDir);
235 * Set a step length for simulation
237 * @param step Step length for simulation
238 * @throws FMILException
240 public void setStepLength(double step) throws FMILException {
241 synchronized(syncObject) {
244 int ret = setStepLength_(getModelIDNew(), step);
246 LOGGER.warn("Function return value != OK, an exception should have been thrown from native code!");
248 } catch (UnsatisfiedLinkError err) {
249 throw new FMILException(UNSATISFIED_LINK);
250 } catch (Exception e) {
251 throw new FMILException(e.getMessage());
256 private native int setStepLength_(int id, double step);
259 * Instantiates a simulation.
261 * Make sure that an FMU is loaded first.
262 * @throws FMILException
264 public void instantiateSimulation() throws FMILException {
265 synchronized(syncObject) {
269 int ret = instantiateSimulation_(getModelIDNew());
271 LOGGER.warn("Function return value != OK, an exception should have been thrown from native code!");
273 } catch (FMILException e) {
275 } catch (UnsatisfiedLinkError err) {
276 throw new FMILException(UNSATISFIED_LINK);
277 } catch (Exception e) {
278 throw new FMILException(e.getMessage());
283 private native int instantiateSimulation_(int id) throws FMILException;
287 * Initializes a simulation.
289 * Make sure that simulation is instantiated first!
290 * @throws FMILException
292 public void initializeSimulation() throws FMILException {
293 synchronized(syncObject) {
297 int ret = initializeSimulation_(getModelIDNew());
299 LOGGER.warn("Function return value != OK, an exception should have been thrown from native code!");
301 } catch (FMILException e) {
303 } catch (UnsatisfiedLinkError err) {
304 throw new FMILException(UNSATISFIED_LINK);
305 } catch (Exception e) {
306 throw new FMILException(e.getMessage());
311 private native int initializeSimulation_(int id) throws FMILException;
314 * Subscribe a set of variables from a loaded simulation.
316 * Make sure that an FMU is loaded first.
317 * @param variables Array of variables
318 * @throws FMILException
320 public void subscribe(int[] variables) throws FMILException {
321 synchronized(syncObject) {
325 int ret = subscribe_(getModelIDNew(), variables);
327 LOGGER.warn("Function return value != OK, an exception should have been thrown from native code!");
329 } catch (UnsatisfiedLinkError err) {
330 throw new FMILException(UNSATISFIED_LINK);
331 } catch (Exception e) {
332 throw new FMILException(e.getMessage());
337 private native int subscribe_(int id, int[] variables);
340 * Set a new (Real, double) value for a variable. If the variable is a
341 * parameter, the change is effective immediately.
343 * @param name Variable
344 * @param value New (Real, double) value
345 * @throws FMILException
347 public void setRealValue(String name, double value) throws FMILException {
349 synchronized(syncObject) {
353 int ret = setRealValue_(getModelIDNew(), variableMap.get(name), value);
355 LOGGER.warn("Function return value != OK, an exception should have been thrown from native code!");
357 } catch (FMILException e) {
359 } catch (UnsatisfiedLinkError err) {
360 throw new FMILException(UNSATISFIED_LINK);
361 } catch (Exception e) {
362 throw new FMILException(e.getMessage());
369 public void setRealValue(int variableReference, double value) throws FMILException {
371 synchronized(syncObject) {
375 int ret = setRealValue_(getModelIDNew(), variableReference, value);
377 LOGGER.warn("Function return value != OK, an exception should have been thrown from native code!");
379 } catch (FMILException e) {
381 } catch (UnsatisfiedLinkError err) {
382 throw new FMILException(UNSATISFIED_LINK);
383 } catch (Exception e) {
384 throw new FMILException(e.getMessage());
391 private native int setRealValue_(int id, int variableReference, double value) throws FMILException;
394 // * Set a new (integer) value for a variable. If the variable is a
395 // * parameter, the change is effective immediately.
397 // * @param name Variable
398 // * @param value New (integer) value
399 // * @throws FMILException
401 // public void setIntegerValue(String name, int value) throws FMILException {
402 // synchronized(syncObject) {
406 // int ret = setIntegerValue_(getModelID(), name, value);
408 // throw new FMILException(getLastErrorMessage());
410 // } catch (UnsatisfiedLinkError err) {
411 // throw new FMILException(UNSATISFIED_LINK);
412 // } catch (Exception e) {
413 // throw new FMILException(e.getMessage());
417 // private native int setIntegerValue_(String id, String name, int value);
420 // * Set a new (boolean) value for a variable. If the variable is a
421 // * parameter, the change is effective immediately.
423 // * @param name Variable
424 // * @param value New (boolean) value
425 // * @throws FMILException
427 // public void setBooleanValue(String name, boolean value) throws FMILException {
428 // synchronized(syncObject) {
432 // int ret = setBooleanValue_(getModelID(), name, value);
434 // throw new FMILException(getLastErrorMessage());
436 // } catch (UnsatisfiedLinkError err) {
437 // throw new FMILException(UNSATISFIED_LINK);
438 // } catch (Exception e) {
439 // throw new FMILException(e.getMessage());
443 // private native int setBooleanValue_(String id, String name, boolean value);
445 // public void setTime(double time) throws FMILException {
446 // synchronized(syncObject) {
450 // int ret = setTime_(getModelID(), time);
452 // throw new FMILException(getLastErrorMessage());
454 // } catch (UnsatisfiedLinkError err) {
455 // throw new FMILException(UNSATISFIED_LINK);
456 // } catch (Exception e) {
457 // throw new FMILException(e.getMessage());
461 // private native int setTime_(String id, double time);
464 * Simulate one step forward. The step length can be set with
467 * @throws FMILException
469 public void simulateStep() throws FMILException {
470 synchronized(syncObject) {
474 int ret = simulateStep_(getModelIDNew());
476 LOGGER.warn("Function return value != OK, an exception should have been thrown from native code!");
478 } catch (FMILException e) {
480 } catch (UnsatisfiedLinkError err) {
481 throw new FMILException(UNSATISFIED_LINK);
482 } catch (Exception e) {
483 throw new FMILException(e.getMessage());
487 private native int simulateStep_(int id) throws FMILException;
490 * Get an array containing the current values for subscribed variables. The
491 * values are in the same order as in the subscription.
493 * @param results An array the size of subscribed results
496 public double[] getSubscribedResults() throws FMILException {
497 synchronized(syncObject) {
501 double[] results = new double[subscription.size()];
502 Arrays.fill(results, Double.NaN);
504 return getSubscribedResults_(getModelIDNew(), results);
505 } catch (UnsatisfiedLinkError err) {
506 throw new FMILException(UNSATISFIED_LINK);
507 } catch (Exception e) {
508 throw new FMILException(e.getMessage());
513 private native double[] getSubscribedResults_(int id, double[] results);
517 * Unload FMU and the dll:s that it requires.
519 * To be called after all FMU simulations are ended.
520 * If the fmu is loaded again / changed, call to loadFMUFile is sufficient. loadFMUFile
521 * releases the previous fmu.dll
523 * @throws FMILException
525 public void unloadFMU() throws FMILException {
526 synchronized(syncObject) {
530 unlockFMUDirectory();
532 int ret = unloadFMU_(getModelIDNew());
534 LOGGER.warn("Function return value != OK, an exception should have been thrown from native code!");
537 removeFMUDirectoryContents();
539 } catch (FMILException e) {
541 } catch (UnsatisfiedLinkError err) {
542 throw new FMILException(UNSATISFIED_LINK);
543 } catch (Exception e) {
544 throw new FMILException(e.getMessage());
548 private native int unloadFMU_(int id) throws FMILException;
551 // * Checks if fmu has been initialized
552 // * @return current simulation time
554 // public boolean isInitialized() throws FMILException {
555 // synchronized(syncObject) {
557 // return isInitialized_(getModelID());
558 // } catch (UnsatisfiedLinkError err) {
559 // throw new FMILException(UNSATISFIED_LINK);
560 // } catch (Exception e) {
561 // throw new FMILException(e.getMessage());
566 // private native boolean isInitialized_(String id);
569 * Get the current simulation time
570 * @return current simulation time
572 public double getTime() throws FMILException {
573 synchronized(syncObject) {
577 return getTime_(getModelIDNew());
579 } catch (UnsatisfiedLinkError err) {
580 throw new FMILException(UNSATISFIED_LINK);
581 } catch (Exception e) {
582 throw new FMILException(e.getMessage());
587 private native double getTime_(int id);
590 * Get all variables in a loaded model
591 * @return all variables in a loaded model
593 public String[] getAllVariables() throws FMILException {
594 synchronized(syncObject) {
598 if(variableNames == null) {
599 variableNames = getAllVariables_(getModelIDNew());
601 return variableNames;
603 } catch (UnsatisfiedLinkError err) {
604 throw new FMILException(UNSATISFIED_LINK);
605 } catch (Exception e) {
606 throw new FMILException(e.getMessage());
611 private native String[] getAllVariables_(int id);
613 public String[] getAllVariableDescriptions() throws FMILException {
614 synchronized(syncObject) {
618 if(variableDescriptions == null) {
619 variableDescriptions = getAllVariableDescriptions_(getModelIDNew());
621 return variableDescriptions;
623 } catch (UnsatisfiedLinkError err) {
624 throw new FMILException(UNSATISFIED_LINK);
625 } catch (Exception e) {
626 throw new FMILException(e.getMessage());
631 private native String[] getAllVariableDescriptions_(int id);
633 public String[] getAllVariableDeclaredTypes() throws FMILException {
634 synchronized(syncObject) {
638 if(variableDeclaredTypes == null) {
639 variableDeclaredTypes = getAllVariableDeclaredTypes_(getModelIDNew());
641 return variableDeclaredTypes;
643 } catch (UnsatisfiedLinkError err) {
644 throw new FMILException(UNSATISFIED_LINK);
645 } catch (Exception e) {
646 throw new FMILException(e.getMessage());
651 private native String[] getAllVariableDeclaredTypes_(int id);
653 public int[] getAllVariableReferences() throws FMILException {
654 synchronized(syncObject) {
658 if(variableReferences == null) {
659 variableReferences = getAllVariableReferences_(getModelIDNew(), new int[variableNames.length]);
661 return variableReferences;
663 } catch (UnsatisfiedLinkError err) {
664 throw new FMILException(UNSATISFIED_LINK);
665 } catch (Exception e) {
666 throw new FMILException(e.getMessage());
671 private native int[] getAllVariableReferences_(int id, int[] array);
673 public int[] getAllVariableTypes() throws FMILException {
674 synchronized(syncObject) {
678 if(variableTypes == null) {
679 variableTypes = getAllVariableTypes_(getModelIDNew(), new int[variableNames.length]);
681 return variableTypes;
683 } catch (UnsatisfiedLinkError err) {
684 throw new FMILException(UNSATISFIED_LINK);
685 } catch (Exception e) {
686 throw new FMILException(e.getMessage());
691 private native int[] getAllVariableTypes_(int id, int[] array);
693 public int[] getAllVariableCausalities() throws FMILException {
694 synchronized(syncObject) {
698 if(variableCausalities == null) {
699 variableCausalities = getAllVariableCausalities_(getModelIDNew(), new int[variableNames.length]);
701 return variableCausalities;
703 } catch (UnsatisfiedLinkError err) {
704 throw new FMILException(UNSATISFIED_LINK);
705 } catch (Exception e) {
706 throw new FMILException(e.getMessage());
711 private native int[] getAllVariableCausalities_(int id, int[] array);
713 public int[] getAllVariableVariabilities() throws FMILException {
714 synchronized(syncObject) {
718 if(variableVariabilities == null) {
719 variableVariabilities = getAllVariableVariabilities_(getModelIDNew(), new int[variableNames.length]);
721 return variableVariabilities;
723 } catch (UnsatisfiedLinkError err) {
724 throw new FMILException(UNSATISFIED_LINK);
725 } catch (Exception e) {
726 throw new FMILException(e.getMessage());
731 private native int[] getAllVariableVariabilities_(int id, int[] array);
734 * Get all variables in a loaded model
735 * @return all variables in a loaded model
737 public String[] getAllDeclaredTypes() throws FMILException {
738 synchronized(syncObject) {
742 if(declaredTypes == null) {
743 declaredTypes = getAllDeclaredTypes_(getModelIDNew());
745 return declaredTypes;
747 } catch (UnsatisfiedLinkError err) {
748 throw new FMILException(UNSATISFIED_LINK);
749 } catch (Exception e) {
750 throw new FMILException(e.getMessage());
755 private native String[] getAllDeclaredTypes_(int id);
757 public String[] getAllDeclaredTypeDescriptions() throws FMILException {
758 synchronized(syncObject) {
762 if(declaredTypeDescriptions == null) {
763 declaredTypeDescriptions = getAllDeclaredTypeDescriptions_(getModelIDNew());
765 return declaredTypeDescriptions;
767 } catch (UnsatisfiedLinkError err) {
768 throw new FMILException(UNSATISFIED_LINK);
769 } catch (Exception e) {
770 throw new FMILException(e.getMessage());
775 private native String[] getAllDeclaredTypeDescriptions_(int id);
777 public String[] getAllDeclaredTypeQuantities() throws FMILException {
778 synchronized(syncObject) {
782 if(declaredTypeQuantities == null) {
783 declaredTypeQuantities = getAllDeclaredTypeQuantities_(getModelIDNew());
785 return declaredTypeQuantities;
787 } catch (UnsatisfiedLinkError err) {
788 throw new FMILException(UNSATISFIED_LINK);
789 } catch (Exception e) {
790 throw new FMILException(e.getMessage());
795 private native String[] getAllDeclaredTypeQuantities_(int id);
797 public String[] getAllDeclaredTypeUnits() throws FMILException {
798 synchronized(syncObject) {
802 if(declaredTypeUnits == null) {
803 declaredTypeUnits = getAllDeclaredTypeUnits_(getModelIDNew());
805 return declaredTypeUnits;
807 } catch (UnsatisfiedLinkError err) {
808 throw new FMILException(UNSATISFIED_LINK);
809 } catch (Exception e) {
810 throw new FMILException(e.getMessage());
815 private native String[] getAllDeclaredTypeUnits_(int id);
820 // * Get all variables from model that match the filter (and time variable)
822 // * @param regexp Regular expression filter
823 // * @return An array of variable names that match regexp filter (and time-variable)
824 // * @throws FMILException
826 // public String[] filterVariables(String regexp) throws FMILException {
827 // synchronized(syncObject) {
830 // return filterVariables_(getModelID(), regexp + "|time");
832 // } catch (UnsatisfiedLinkError err) {
833 // throw new FMILException(UNSATISFIED_LINK);
834 // } catch (Exception e) {
835 // throw new FMILException(e.getMessage());
840 // private native String[] filterVariables_(String id, String regexp);
843 // * Get the last error message
844 // * @return Last error message
846 // public String getLastErrorMessage() throws FMILException {
847 // synchronized(syncObject) {
852 // //return getLastErrorMessage_(getModelID());
854 // } catch (UnsatisfiedLinkError err) {
855 // throw new FMILException(UNSATISFIED_LINK);
856 // } catch (Exception e) {
857 // throw new FMILException(e.getMessage());
862 // private native String getLastErrorMessage_(String id);
865 * Get a real (double) value for variable
866 * @param name Name of the variable
868 * @throws FMILException
870 public double getRealValue(String name) throws FMILException {
871 synchronized(syncObject) {
874 // TODO: printtaa id ja name, jotta saadaan virheessä kiinni
875 double result = getRealValue_(getModelIDNew(), variableMap.get(name));
876 System.err.println("getRealValue " + name + " = " + result);
878 } catch (UnsatisfiedLinkError err) {
879 throw new FMILException(UNSATISFIED_LINK);
880 } catch (Exception e) {
881 throw new FMILException(e.getMessage());
886 private native double getRealValue_(int id, int variableReference);
889 // * Get a string value for variable
890 // * @param name Name of the variable
892 // * @throws FMILException
894 // public String getStringValue(String name) throws FMILException {
895 // synchronized(syncObject) {
898 // return getStringValue_(getModelID(), name);
899 // } catch (UnsatisfiedLinkError err) {
900 // throw new FMILException(UNSATISFIED_LINK);
901 // } catch (Exception e) {
902 // throw new FMILException(e.getMessage());
907 // private native String getStringValue_(String id, String name);
910 // * Get an integer value for variable
911 // * @param name Name of the variable
913 // * @throws FMILException
915 // public int getIntegerValue(String name) throws FMILException {
916 // synchronized(syncObject) {
919 // return getIntegerValue_(getModelID(), name);
920 // } catch (UnsatisfiedLinkError err) {
921 // throw new FMILException(UNSATISFIED_LINK);
922 // } catch (Exception e) {
923 // throw new FMILException(e.getMessage());
928 // private native int getIntegerValue_(String id, String name);
931 // * Get a real (double) value for variable
932 // * @param name Name of the variable
934 // * @throws FMILException
936 // public boolean getBooleanValue(String name) throws FMILException {
937 // synchronized(syncObject) {
940 // return getBooleanValue_(getModelID(), name);
941 // } catch (UnsatisfiedLinkError err) {
942 // throw new FMILException(UNSATISFIED_LINK);
943 // } catch (Exception e) {
944 // throw new FMILException(e.getMessage());
949 // private native boolean getBooleanValue_(String id, String name);
951 private FileChannel channel;
952 private FileLock lock;
954 @SuppressWarnings("resource")
955 private boolean lockFMUDirectory() {
958 // Get a file channel for the lock file
959 File lockFile = new File(TEMP_FMU_DIRECTORY, LOCK_FILE_NAME);
960 if(!lockFile.isFile())
961 lockFile.createNewFile();
963 channel = new RandomAccessFile(lockFile, "rw").getChannel();
965 // Use the file channel to create a lock on the file.
966 // This method blocks until it can retrieve the lock.
967 lock = channel.lock();
969 // // Try acquiring the lock without blocking. This method returns
970 // // null or throws an exception if the file is already locked.
972 // lock = channel.tryLock();
973 // } catch (OverlappingFileLockException e) {
974 // // File is already locked in this thread or virtual machine
976 } catch (IOException e) {
983 private boolean unlockFMUDirectory() {
992 } catch (IOException e) {
998 private boolean removeFMUDirectoryContents() {
1001 File tempDir = new File(TEMP_FMU_DIRECTORY);
1002 FileUtils.deleteAll(tempDir);
1004 } catch (IOException e) {
1011 protected void finalize() throws Throwable {
1014 } catch (Throwable t) {
1015 LOGGER.error("Could not unload native FMU!", t);