fmu-manipulation-toolbox 1.9.1.3__py3-none-any.whl → 1.9.2b1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- fmu_manipulation_toolbox/__version__.py +1 -1
- fmu_manipulation_toolbox/container.py +265 -78
- fmu_manipulation_toolbox/ls.py +35 -0
- fmu_manipulation_toolbox/operations.py +1 -1
- fmu_manipulation_toolbox/resources/darwin64/container.dylib +0 -0
- fmu_manipulation_toolbox/resources/linux32/client_sm.so +0 -0
- fmu_manipulation_toolbox/resources/linux64/client_sm.so +0 -0
- fmu_manipulation_toolbox/resources/linux64/container.so +0 -0
- fmu_manipulation_toolbox/resources/win32/client_sm.dll +0 -0
- fmu_manipulation_toolbox/resources/win32/server_sm.exe +0 -0
- fmu_manipulation_toolbox/resources/win64/client_sm.dll +0 -0
- fmu_manipulation_toolbox/resources/win64/container.dll +0 -0
- fmu_manipulation_toolbox/resources/win64/server_sm.exe +0 -0
- fmu_manipulation_toolbox/split.py +49 -2
- fmu_manipulation_toolbox/terminals.py +137 -0
- {fmu_manipulation_toolbox-1.9.1.3.dist-info → fmu_manipulation_toolbox-1.9.2b1.dist-info}/METADATA +1 -1
- {fmu_manipulation_toolbox-1.9.1.3.dist-info → fmu_manipulation_toolbox-1.9.2b1.dist-info}/RECORD +21 -19
- {fmu_manipulation_toolbox-1.9.1.3.dist-info → fmu_manipulation_toolbox-1.9.2b1.dist-info}/WHEEL +0 -0
- {fmu_manipulation_toolbox-1.9.1.3.dist-info → fmu_manipulation_toolbox-1.9.2b1.dist-info}/entry_points.txt +0 -0
- {fmu_manipulation_toolbox-1.9.1.3.dist-info → fmu_manipulation_toolbox-1.9.2b1.dist-info}/licenses/LICENSE.txt +0 -0
- {fmu_manipulation_toolbox-1.9.1.3.dist-info → fmu_manipulation_toolbox-1.9.2b1.dist-info}/top_level.txt +0 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
'V1.9.
|
|
1
|
+
'V1.9.2b1'
|
|
@@ -6,11 +6,14 @@ import shutil
|
|
|
6
6
|
import uuid
|
|
7
7
|
import platform
|
|
8
8
|
import zipfile
|
|
9
|
+
from collections import defaultdict
|
|
9
10
|
from datetime import datetime
|
|
10
11
|
from pathlib import Path
|
|
11
12
|
from typing import *
|
|
12
13
|
|
|
14
|
+
from .ls import LayeredStandard
|
|
13
15
|
from .operations import FMU, OperationAbstract, FMUError, FMUPort
|
|
16
|
+
from .terminals import Terminals
|
|
14
17
|
from .version import __version__ as tool_version
|
|
15
18
|
|
|
16
19
|
|
|
@@ -37,7 +40,9 @@ class EmbeddedFMUPort:
|
|
|
37
40
|
'Int64': 'integer64',
|
|
38
41
|
'UInt64': 'uinteger64',
|
|
39
42
|
'String': 'string',
|
|
40
|
-
'Boolean': 'boolean1'
|
|
43
|
+
'Boolean': 'boolean1',
|
|
44
|
+
'Binary': 'binary',
|
|
45
|
+
'Clock': 'clock'
|
|
41
46
|
}
|
|
42
47
|
}
|
|
43
48
|
|
|
@@ -60,7 +65,9 @@ class EmbeddedFMUPort:
|
|
|
60
65
|
'integer64': 'Int64' ,
|
|
61
66
|
'uinteger64': 'UInt64' ,
|
|
62
67
|
'string': 'String' ,
|
|
63
|
-
'boolean1': 'Boolean'
|
|
68
|
+
'boolean1': 'Boolean',
|
|
69
|
+
'binary': 'Binary',
|
|
70
|
+
'clock': 'Clock'
|
|
64
71
|
}
|
|
65
72
|
}
|
|
66
73
|
|
|
@@ -68,12 +75,14 @@ class EmbeddedFMUPort:
|
|
|
68
75
|
"real64", "real32",
|
|
69
76
|
"integer8", "uinteger8", "integer16", "uinteger16", "integer32", "uinteger32", "integer64", "uinteger64",
|
|
70
77
|
"boolean", "boolean1",
|
|
71
|
-
"string"
|
|
78
|
+
"string",
|
|
79
|
+
"binary", "clock"
|
|
72
80
|
)
|
|
73
81
|
|
|
74
82
|
def __init__(self, fmi_type, attrs: Union[FMUPort, Dict[str, str]], fmi_version=0):
|
|
75
83
|
self.causality = attrs.get("causality", "local")
|
|
76
|
-
self.variability = attrs.get("variability",
|
|
84
|
+
self.variability = attrs.get("variability", None)
|
|
85
|
+
self.interval_variability = attrs.get("intervalVariability", None)
|
|
77
86
|
self.name = attrs["name"]
|
|
78
87
|
self.vr = int(attrs["valueReference"])
|
|
79
88
|
self.description = attrs.get("description", None)
|
|
@@ -85,6 +94,8 @@ class EmbeddedFMUPort:
|
|
|
85
94
|
|
|
86
95
|
self.start_value = attrs.get("start", None)
|
|
87
96
|
self.initial = attrs.get("initial", None)
|
|
97
|
+
self.clock = attrs.get("clocks", None)
|
|
98
|
+
|
|
88
99
|
|
|
89
100
|
def xml(self, vr: int, name=None, causality=None, start=None, fmi_version=2) -> str:
|
|
90
101
|
if name is None:
|
|
@@ -93,13 +104,15 @@ class EmbeddedFMUPort:
|
|
|
93
104
|
causality = self.causality
|
|
94
105
|
if start is None:
|
|
95
106
|
start = self.start_value
|
|
107
|
+
if start is None and self.type_name == "binary" and self.initial == "exact":
|
|
108
|
+
start = ""
|
|
96
109
|
if self.variability is None:
|
|
97
110
|
self.variability = "continuous" if "real" in self.type_name else "discrete"
|
|
98
111
|
|
|
99
112
|
try:
|
|
100
113
|
fmi_type = self.CONTAINER_TO_FMI[fmi_version][self.type_name]
|
|
101
114
|
except KeyError:
|
|
102
|
-
logger.error(f"Cannot expose '{name}' because type '{self.type_name}' is not compatible "
|
|
115
|
+
logger.error(f"Cannot expose ({causality}) '{name}' because type '{self.type_name}' is not compatible "
|
|
103
116
|
f"with FMI-{fmi_version}.0")
|
|
104
117
|
return ""
|
|
105
118
|
|
|
@@ -124,9 +137,10 @@ class EmbeddedFMUPort:
|
|
|
124
137
|
filtered_attrs = {key: value for key, value in scalar_attrs.items() if value is not None}
|
|
125
138
|
scalar_attrs_str = " ".join([f'{key}="{value}"' for (key, value) in filtered_attrs.items()])
|
|
126
139
|
return f'<ScalarVariable {scalar_attrs_str}>{child_str}</ScalarVariable>'
|
|
127
|
-
|
|
140
|
+
|
|
141
|
+
elif fmi_version == 3:
|
|
128
142
|
if fmi_type in ('String', 'Binary'):
|
|
129
|
-
if start:
|
|
143
|
+
if start is not None:
|
|
130
144
|
child_str = f'<Start value="{start}"/>'
|
|
131
145
|
else:
|
|
132
146
|
child_str = ''
|
|
@@ -149,12 +163,16 @@ class EmbeddedFMUPort:
|
|
|
149
163
|
"variability": self.variability,
|
|
150
164
|
"initial": self.initial,
|
|
151
165
|
"description": self.description,
|
|
152
|
-
"start": start
|
|
166
|
+
"start": start,
|
|
167
|
+
"intervalVariability": self.interval_variability
|
|
153
168
|
}
|
|
154
169
|
filtered_attrs = {key: value for key, value in scalar_attrs.items() if value is not None}
|
|
155
170
|
scalar_attrs_str = " ".join([f'{key}="{value}"' for (key, value) in filtered_attrs.items()])
|
|
156
171
|
|
|
157
172
|
return f'<{fmi_type} {scalar_attrs_str}/>'
|
|
173
|
+
else:
|
|
174
|
+
logger.critical(f"Unknown version {fmi_version}. BUG?")
|
|
175
|
+
return ''
|
|
158
176
|
|
|
159
177
|
|
|
160
178
|
class EmbeddedFMU(OperationAbstract):
|
|
@@ -167,6 +185,10 @@ class EmbeddedFMU(OperationAbstract):
|
|
|
167
185
|
self.name = Path(filename).name
|
|
168
186
|
self.id = Path(filename).stem.lower()
|
|
169
187
|
|
|
188
|
+
logger.debug(f"Analysing {self.name}")
|
|
189
|
+
self.terminals = Terminals(self.fmu.tmp_directory)
|
|
190
|
+
self.ls = LayeredStandard(self.fmu.tmp_directory)
|
|
191
|
+
|
|
170
192
|
self.step_size = None
|
|
171
193
|
self.start_time = None
|
|
172
194
|
self.stop_time = None
|
|
@@ -175,6 +197,7 @@ class EmbeddedFMU(OperationAbstract):
|
|
|
175
197
|
self.fmi_version = None
|
|
176
198
|
self.ports: Dict[str, EmbeddedFMUPort] = {}
|
|
177
199
|
|
|
200
|
+
self.has_event_mode = False
|
|
178
201
|
self.capabilities: Dict[str, str] = {}
|
|
179
202
|
self.current_port = None # used during apply_operation()
|
|
180
203
|
|
|
@@ -187,12 +210,14 @@ class EmbeddedFMU(OperationAbstract):
|
|
|
187
210
|
if fmi_version == "2.0":
|
|
188
211
|
self.guid = attrs['guid']
|
|
189
212
|
self.fmi_version = 2
|
|
190
|
-
if fmi_version
|
|
213
|
+
if fmi_version.startswith("3."):
|
|
191
214
|
self.guid = attrs['instantiationToken']
|
|
192
215
|
self.fmi_version = 3
|
|
193
216
|
|
|
194
217
|
def cosimulation_attrs(self, attrs: Dict[str, str]):
|
|
195
218
|
self.model_identifier = attrs['modelIdentifier']
|
|
219
|
+
if attrs.get("hasEventMode", "false") == "true":
|
|
220
|
+
self.has_event_mode = True
|
|
196
221
|
for capability in self.capability_list:
|
|
197
222
|
self.capabilities[capability] = attrs.get(capability, "false")
|
|
198
223
|
|
|
@@ -215,7 +240,12 @@ class EmbeddedFMU(OperationAbstract):
|
|
|
215
240
|
self.ports[port.name] = port
|
|
216
241
|
|
|
217
242
|
def __repr__(self):
|
|
218
|
-
|
|
243
|
+
properties = f"{len(self.ports)} variables, ts={self.step_size}s"
|
|
244
|
+
if len(self.terminals) > 0:
|
|
245
|
+
properties += f", {len(self.terminals)} terminals"
|
|
246
|
+
if len(self.ls) > 0:
|
|
247
|
+
properties += f", {self.ls}"
|
|
248
|
+
return f"'{self.name}' ({properties})"
|
|
219
249
|
|
|
220
250
|
|
|
221
251
|
class FMUContainerError(Exception):
|
|
@@ -315,13 +345,19 @@ class Link:
|
|
|
315
345
|
self.vr_converted: Dict[str, Optional[int]] = {}
|
|
316
346
|
|
|
317
347
|
if not cport_from.port.causality == "output":
|
|
318
|
-
|
|
348
|
+
if cport_from.port.type_name == "clock":
|
|
349
|
+
# LS-BUS allows to connected to input clocks.
|
|
350
|
+
self.cport_from = None
|
|
351
|
+
self.add_target(cport_from)
|
|
352
|
+
else:
|
|
353
|
+
raise FMUContainerError(f"{cport_from} is {cport_from.port.causality} instead of OUTPUT")
|
|
319
354
|
|
|
320
355
|
def add_target(self, cport_to: ContainerPort):
|
|
321
356
|
if not cport_to.port.causality == "input":
|
|
322
357
|
raise FMUContainerError(f"{cport_to} is {cport_to.port.causality} instead of INPUT")
|
|
323
358
|
|
|
324
|
-
if cport_to.port.type_name == self.cport_from
|
|
359
|
+
if (cport_to.port.type_name == "clock" and self.cport_from is None or
|
|
360
|
+
cport_to.port.type_name == self.cport_from.port.type_name):
|
|
325
361
|
self.cport_to_list.append(cport_to)
|
|
326
362
|
elif self.get_conversion(cport_to):
|
|
327
363
|
self.cport_to_list.append(cport_to)
|
|
@@ -345,6 +381,7 @@ class ValueReferenceTable:
|
|
|
345
381
|
self.vr_table:Dict[str, int] = {}
|
|
346
382
|
self.masks: Dict[str, int] = {}
|
|
347
383
|
self.nb_local_variable:Dict[str, int] = {}
|
|
384
|
+
self.local_clock = {}
|
|
348
385
|
for i, type_name in enumerate(EmbeddedFMUPort.ALL_TYPES):
|
|
349
386
|
self.vr_table[type_name] = 0
|
|
350
387
|
self.masks[type_name] = i << 24
|
|
@@ -365,13 +402,28 @@ class ValueReferenceTable:
|
|
|
365
402
|
return vr | self.masks[type_name]
|
|
366
403
|
|
|
367
404
|
def set_link_vr(self, link: Link):
|
|
368
|
-
|
|
405
|
+
if link.cport_from is None:
|
|
406
|
+
link.vr = self.add_vr("clock", local=True)
|
|
407
|
+
else:
|
|
408
|
+
link.vr = self.add_vr(link.cport_from, local=True)
|
|
409
|
+
if link.cport_from.port.type_name == "clock":
|
|
410
|
+
self.local_clock[(link.cport_from.fmu, link.cport_from.port.vr)] = link.vr
|
|
411
|
+
|
|
412
|
+
for cport_to in link.cport_to_list:
|
|
413
|
+
if cport_to.port.type_name == "clock":
|
|
414
|
+
self.local_clock[(cport_to.fmu, cport_to.port.vr)] = link.vr
|
|
415
|
+
|
|
369
416
|
for type_name in link.vr_converted.keys():
|
|
370
417
|
link.vr_converted[type_name] = self.add_vr(type_name, local=True)
|
|
371
418
|
|
|
419
|
+
def get_local_clock(self, cport: ContainerPort) -> int:
|
|
420
|
+
return self.local_clock[(cport.fmu, int(cport.port.clock))]
|
|
421
|
+
|
|
422
|
+
|
|
372
423
|
def nb_local(self, type_name: str) -> int:
|
|
373
424
|
return self.nb_local_variable[type_name]
|
|
374
425
|
|
|
426
|
+
|
|
375
427
|
class AutoWired:
|
|
376
428
|
def __init__(self):
|
|
377
429
|
self.rule_input = []
|
|
@@ -397,6 +449,102 @@ class AutoWired:
|
|
|
397
449
|
self.rule_link.append([from_fmu, from_port, to_fmu, to_port])
|
|
398
450
|
|
|
399
451
|
|
|
452
|
+
class FMUIOList:
|
|
453
|
+
def __init__(self, vr_table: ValueReferenceTable):
|
|
454
|
+
self.vr_table = vr_table
|
|
455
|
+
self.inputs = defaultdict(lambda: defaultdict(lambda: defaultdict(list))) # [type][fmu][clock_vr][(fmu_vr, vr])
|
|
456
|
+
self.nb_clocked_inputs = defaultdict(lambda: defaultdict(lambda: 0))
|
|
457
|
+
self.outputs = defaultdict(lambda: defaultdict(lambda: defaultdict(list))) # [type][fmu][clock_vr][(fmu_vr, vr])
|
|
458
|
+
self.nb_clocked_outputs = defaultdict(lambda: defaultdict(lambda: 0))
|
|
459
|
+
self.start_values = defaultdict(lambda: defaultdict(list)) # [type][fmu][(cport, value)]
|
|
460
|
+
|
|
461
|
+
def add_input(self, cport: ContainerPort, local_vr: int):
|
|
462
|
+
if cport.port.clock is None:
|
|
463
|
+
clock = None
|
|
464
|
+
else:
|
|
465
|
+
try:
|
|
466
|
+
clock = self.vr_table.get_local_clock(cport)
|
|
467
|
+
except KeyError:
|
|
468
|
+
logger.error(f"Cannot expose clocked input: {cport}")
|
|
469
|
+
return
|
|
470
|
+
self.nb_clocked_inputs[cport.port.type_name][cport.fmu.name] += 1
|
|
471
|
+
self.inputs[cport.port.type_name][cport.fmu.name][clock].append((cport.port.vr, local_vr))
|
|
472
|
+
|
|
473
|
+
def add_output(self, cport: ContainerPort, local_vr: int):
|
|
474
|
+
if cport.port.clock is None:
|
|
475
|
+
clock = None
|
|
476
|
+
else:
|
|
477
|
+
try:
|
|
478
|
+
clock = self.vr_table.get_local_clock(cport)
|
|
479
|
+
except KeyError:
|
|
480
|
+
logger.error(f"Cannot expose clocked output: {cport}")
|
|
481
|
+
return
|
|
482
|
+
self.nb_clocked_outputs[cport.port.type_name][cport.fmu.name] += 1
|
|
483
|
+
self.outputs[cport.port.type_name][cport.fmu.name][clock].append((cport.port.vr, local_vr))
|
|
484
|
+
|
|
485
|
+
def add_start_value(self, cport: ContainerPort, value: str):
|
|
486
|
+
reset = 1 if cport.port.causality == "input" else 0
|
|
487
|
+
self.start_values[cport.port.type_name][cport.fmu.name].append((cport, reset, value))
|
|
488
|
+
|
|
489
|
+
def write_txt(self, fmu_name, txt_file):
|
|
490
|
+
for type_name in EmbeddedFMUPort.ALL_TYPES:
|
|
491
|
+
print(f"# Inputs of {fmu_name} - {type_name}: <VR> <FMU_VR>", file=txt_file)
|
|
492
|
+
print(len(self.inputs[type_name][fmu_name][None]), file=txt_file)
|
|
493
|
+
for fmu_vr, vr in self.inputs[type_name][fmu_name][None]:
|
|
494
|
+
print(f"{vr} {fmu_vr}", file=txt_file)
|
|
495
|
+
if not type_name == "clock":
|
|
496
|
+
print(f"# Clocked Inputs of {fmu_name} - {type_name}: <FMU_VR_CLOCK> <n> <VR> <FMU_VR>", file=txt_file)
|
|
497
|
+
print(f"{len(self.inputs[type_name][fmu_name])-1} {self.nb_clocked_inputs[type_name][fmu_name]}",
|
|
498
|
+
file=txt_file)
|
|
499
|
+
for clock, table in self.inputs[type_name][fmu_name].items():
|
|
500
|
+
if not clock is None:
|
|
501
|
+
s = " ".join([f"{vr} {fmu_vr}" for fmu_vr, vr in table])
|
|
502
|
+
print(f"{clock} {len(table)} {s}", file=txt_file)
|
|
503
|
+
|
|
504
|
+
for type_name in EmbeddedFMUPort.ALL_TYPES[:-2]: # No start values for binary or clock
|
|
505
|
+
print(f"# Start values of {fmu_name} - {type_name}: <FMU_VR> <RESET> <VALUE>", file=txt_file)
|
|
506
|
+
print(len(self.start_values[type_name][fmu_name]), file=txt_file)
|
|
507
|
+
for vr, reset, value in self.start_values[type_name][fmu_name]:
|
|
508
|
+
print(f"{vr} {reset} {value}", file=txt_file)
|
|
509
|
+
|
|
510
|
+
for type_name in EmbeddedFMUPort.ALL_TYPES:
|
|
511
|
+
print(f"# Outputs of {fmu_name} - {type_name}: <VR> <FMU_VR>", file=txt_file)
|
|
512
|
+
print(len(self.outputs[type_name][fmu_name][None]), file=txt_file)
|
|
513
|
+
for fmu_vr, vr in self.outputs[type_name][fmu_name][None]:
|
|
514
|
+
print(f"{vr} {fmu_vr}", file=txt_file)
|
|
515
|
+
|
|
516
|
+
if not type_name == "clock":
|
|
517
|
+
print(f"# Clocked Outputs of {fmu_name} - {type_name}: <FMU_VR_CLOCK> <n> <VR> <FMU_VR>", file=txt_file)
|
|
518
|
+
print(f"{len(self.outputs[type_name][fmu_name])-1} {self.nb_clocked_outputs[type_name][fmu_name]}",
|
|
519
|
+
file=txt_file)
|
|
520
|
+
for clock, translation in self.outputs[type_name][fmu_name].items():
|
|
521
|
+
if not clock is None:
|
|
522
|
+
s = " ".join([f"{vr} {fmu_vr}" for fmu_vr, vr in translation])
|
|
523
|
+
print(f"{clock} {len(translation)} {s}", file=txt_file)
|
|
524
|
+
|
|
525
|
+
|
|
526
|
+
class ClockList:
|
|
527
|
+
def __init__(self, involved_fmu: OrderedDict[str, EmbeddedFMU]):
|
|
528
|
+
self.clocks_per_fmu: DefaultDict[int, List[Tuple[int, int]]] = defaultdict(list)
|
|
529
|
+
self.fmu_index: Dict[str, int] = {}
|
|
530
|
+
for i, fmu_name in enumerate(involved_fmu):
|
|
531
|
+
self.fmu_index[fmu_name] = i
|
|
532
|
+
|
|
533
|
+
def append(self, cport: ContainerPort, vr: int):
|
|
534
|
+
self.clocks_per_fmu[self.fmu_index[cport.fmu.name]].append((cport.port.vr, vr))
|
|
535
|
+
|
|
536
|
+
def write_txt(self, txt_file):
|
|
537
|
+
print(f"# importer CLOCKS: <FMU_INDEX> <NB> <FMU_VR> <VR> [<FMU_VR> <VR>]", file=txt_file)
|
|
538
|
+
nb_total_clocks = 0;
|
|
539
|
+
for clocks in self.clocks_per_fmu.values():
|
|
540
|
+
nb_total_clocks += len(clocks)
|
|
541
|
+
|
|
542
|
+
print(f"{len(self.clocks_per_fmu)} {nb_total_clocks}", file=txt_file)
|
|
543
|
+
for index, clocks in self.clocks_per_fmu.items():
|
|
544
|
+
clocks_str = " ".join([f"{clock[0]} {clock[1]}" for clock in clocks])
|
|
545
|
+
print(f"{index} {len(clocks)} {clocks_str}", file=txt_file)
|
|
546
|
+
|
|
547
|
+
|
|
400
548
|
class FMUContainer:
|
|
401
549
|
HEADER_XML_2 = """<?xml version="1.0" encoding="ISO-8859-1"?>
|
|
402
550
|
<fmiModelDescription
|
|
@@ -505,7 +653,7 @@ class FMUContainer:
|
|
|
505
653
|
logger.warning(f"Try to embed FMU-{fmu.fmi_version} into container FMI-{self.fmi_version}.")
|
|
506
654
|
self.involved_fmu[fmu.name] = fmu
|
|
507
655
|
|
|
508
|
-
logger.
|
|
656
|
+
logger.info(f"Involved FMU #{len(self.involved_fmu)}: {fmu}")
|
|
509
657
|
except (FMUContainerError, FMUError) as e:
|
|
510
658
|
raise FMUContainerError(f"Cannot load '{fmu_filename}': {e}")
|
|
511
659
|
|
|
@@ -564,19 +712,45 @@ class FMUContainer:
|
|
|
564
712
|
self.mark_ruled(cport_from, 'DROP')
|
|
565
713
|
|
|
566
714
|
def add_link(self, from_fmu_filename: str, from_port_name: str, to_fmu_filename: str, to_port_name: str):
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
715
|
+
fmu_from = self.get_fmu(from_fmu_filename)
|
|
716
|
+
fmu_to = self.get_fmu(to_fmu_filename)
|
|
717
|
+
|
|
718
|
+
if from_port_name in fmu_from.terminals and to_port_name in fmu_to.terminals:
|
|
719
|
+
# TERMINAL Connection
|
|
720
|
+
terminal1 = fmu_from.terminals[from_port_name]
|
|
721
|
+
terminal2 = fmu_to.terminals[to_port_name]
|
|
722
|
+
if terminal1 == terminal2:
|
|
723
|
+
logger.debug(f"Plugging terminals: {terminal1} <-> {terminal2}")
|
|
724
|
+
for terminal1_port_name, terminal2_port_name in terminal1.connect(terminal2):
|
|
725
|
+
self.add_link_regular(fmu_from, terminal1_port_name, fmu_to, terminal2_port_name)
|
|
726
|
+
else:
|
|
727
|
+
logger.error(f"Cannot plug incompatible terminals: {terminal1} <-> {terminal2}")
|
|
728
|
+
else:
|
|
729
|
+
# REGULAR port connection
|
|
730
|
+
self.add_link_regular(fmu_from, from_port_name, fmu_to, to_port_name)
|
|
572
731
|
|
|
573
|
-
|
|
574
|
-
|
|
732
|
+
def add_link_regular(self, fmu_from: EmbeddedFMU, from_port_name: str, fmu_to: EmbeddedFMU, to_port_name: str):
|
|
733
|
+
cport_from = ContainerPort(fmu_from, from_port_name)
|
|
734
|
+
cport_to = ContainerPort(fmu_to, to_port_name)
|
|
735
|
+
|
|
736
|
+
if cport_to.port.causality == "output" and cport_from.port.causality == "input":
|
|
737
|
+
logger.debug("Invert link orientation")
|
|
738
|
+
tmp = cport_to
|
|
739
|
+
cport_to = cport_from
|
|
740
|
+
cport_from = tmp
|
|
741
|
+
|
|
742
|
+
try:
|
|
743
|
+
local = self.links[cport_from]
|
|
744
|
+
except KeyError:
|
|
745
|
+
local = Link(cport_from)
|
|
746
|
+
self.links[cport_from] = local
|
|
747
|
+
|
|
748
|
+
local.add_target(cport_to) # Causality is check in the add() function
|
|
749
|
+
|
|
750
|
+
logger.debug(f"LINK: {cport_from} -> {cport_to}")
|
|
751
|
+
self.mark_ruled(cport_from, 'LINK')
|
|
752
|
+
self.mark_ruled(cport_to, 'LINK')
|
|
575
753
|
|
|
576
|
-
logger.debug(f"LINK: {cport_from} -> {cport_to}")
|
|
577
|
-
self.mark_ruled(cport_from, 'LINK')
|
|
578
|
-
self.mark_ruled(cport_to, 'LINK')
|
|
579
|
-
self.links[cport_from] = local
|
|
580
754
|
|
|
581
755
|
def add_start_value(self, fmu_filename: str, port_name: str, value: str):
|
|
582
756
|
cport = ContainerPort(self.get_fmu(fmu_filename), port_name)
|
|
@@ -588,10 +762,13 @@ class FMUContainer:
|
|
|
588
762
|
value = int(value)
|
|
589
763
|
elif cport.port.type_name == 'Boolean':
|
|
590
764
|
value = int(bool(value))
|
|
591
|
-
|
|
765
|
+
elif cport.port.type_name == 'String':
|
|
592
766
|
value = value
|
|
767
|
+
else:
|
|
768
|
+
logger.error(f"Start value cannot be set on '{cport.port.type_name}'")
|
|
769
|
+
return
|
|
593
770
|
except ValueError:
|
|
594
|
-
raise FMUContainerError(f"Start value is not conforming to
|
|
771
|
+
raise FMUContainerError(f"Start value is not conforming to {cport.port.type_name} format.")
|
|
595
772
|
|
|
596
773
|
self.start_values[cport] = value
|
|
597
774
|
|
|
@@ -658,6 +835,14 @@ class FMUContainer:
|
|
|
658
835
|
if fmu.step_size and fmu.capabilities["canHandleVariableCommunicationStepSize"] == "false":
|
|
659
836
|
freq_set.add(int(1.0/fmu.step_size))
|
|
660
837
|
|
|
838
|
+
if not freq_set:
|
|
839
|
+
# all involved FMUs can Handle Variable Communication StepSize
|
|
840
|
+
step_size_max = 0
|
|
841
|
+
for fmu in self.involved_fmu.values():
|
|
842
|
+
if fmu.step_size > step_size_max:
|
|
843
|
+
step_size_max = fmu.step_size
|
|
844
|
+
return step_size_max
|
|
845
|
+
|
|
661
846
|
common_freq = math.gcd(*freq_set)
|
|
662
847
|
try:
|
|
663
848
|
step_size = 1.0 / float(common_freq)
|
|
@@ -787,10 +972,20 @@ class FMUContainer:
|
|
|
787
972
|
index_offset = 2 # index of output ports. Start at 2 to skip "time" port
|
|
788
973
|
|
|
789
974
|
# Local variable should be first to ensure to attribute them the lowest VR.
|
|
975
|
+
nb_clocks = 0
|
|
790
976
|
for link in self.links.values():
|
|
791
977
|
self.vr_table.set_link_vr(link)
|
|
792
|
-
|
|
793
|
-
|
|
978
|
+
if link.cport_from:
|
|
979
|
+
port_local_def = link.cport_from.port.xml(link.vr, name=link.name, causality='local',
|
|
980
|
+
fmi_version=self.fmi_version)
|
|
981
|
+
else:
|
|
982
|
+
# LS-BUS allow Clock generated by fmi-importer
|
|
983
|
+
port = EmbeddedFMUPort("Clock",
|
|
984
|
+
{"name": "", "valueReference": -1, "intervalVariability": "triggered"},
|
|
985
|
+
fmi_version=3)
|
|
986
|
+
port_local_def = port.xml(link.vr, name=f"container.clock{nb_clocks}", causality='local', fmi_version=self.fmi_version)
|
|
987
|
+
nb_clocks += 1
|
|
988
|
+
|
|
794
989
|
if port_local_def:
|
|
795
990
|
print(f" {port_local_def}", file=xml_file)
|
|
796
991
|
index_offset += 1
|
|
@@ -858,6 +1053,7 @@ class FMUContainer:
|
|
|
858
1053
|
"</fmiModelDescription>")
|
|
859
1054
|
|
|
860
1055
|
def make_fmu_txt(self, txt_file, step_size: float, mt: bool, profiling: bool, sequential: bool):
|
|
1056
|
+
print("# Version 3", file=txt_file)
|
|
861
1057
|
print("# Container flags <MT> <Profiling> <Sequential>", file=txt_file)
|
|
862
1058
|
flags = [ str(int(flag == True)) for flag in (mt, profiling, sequential)]
|
|
863
1059
|
print(" ".join(flags), file=txt_file)
|
|
@@ -868,56 +1064,61 @@ class FMUContainer:
|
|
|
868
1064
|
print(f"{len(self.involved_fmu)}", file=txt_file)
|
|
869
1065
|
fmu_rank: Dict[str, int] = {}
|
|
870
1066
|
for i, fmu in enumerate(self.involved_fmu.values()):
|
|
871
|
-
print(f"{fmu.name} {fmu.fmi_version}", file=txt_file)
|
|
1067
|
+
print(f"{fmu.name} {fmu.fmi_version} {int(fmu.has_event_mode)}", file=txt_file)
|
|
872
1068
|
print(f"{fmu.model_identifier}", file=txt_file)
|
|
873
1069
|
print(f"{fmu.guid}", file=txt_file)
|
|
874
1070
|
fmu_rank[fmu.name] = i
|
|
875
1071
|
|
|
876
1072
|
# Prepare data structure
|
|
877
|
-
inputs_per_type: Dict[str, List[ContainerInput]] =
|
|
878
|
-
outputs_per_type: Dict[str, List[ContainerPort]] =
|
|
1073
|
+
inputs_per_type: Dict[str, List[ContainerInput]] = defaultdict(list) # Container's INPUT
|
|
1074
|
+
outputs_per_type: Dict[str, List[ContainerPort]] = defaultdict(list) # Container's OUTPUT
|
|
879
1075
|
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
outputs_fmu_per_type = {}
|
|
883
|
-
local_per_type: Dict[str, List[int]] = {}
|
|
884
|
-
links_per_fmu: Dict[str, List[Link]] = {}
|
|
885
|
-
|
|
886
|
-
for type_name in EmbeddedFMUPort.ALL_TYPES:
|
|
887
|
-
inputs_per_type[type_name] = []
|
|
888
|
-
outputs_per_type[type_name] = []
|
|
889
|
-
local_per_type[type_name] = []
|
|
1076
|
+
fmu_io_list = FMUIOList(self.vr_table)
|
|
1077
|
+
clock_list = ClockList(self.involved_fmu)
|
|
890
1078
|
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
outputs_fmu_per_type[type_name] = {}
|
|
894
|
-
|
|
895
|
-
for fmu in self.involved_fmu.values():
|
|
896
|
-
inputs_fmu_per_type[type_name][fmu.name] = {}
|
|
897
|
-
start_values_fmu_per_type[type_name][fmu.name] = {}
|
|
898
|
-
outputs_fmu_per_type[type_name][fmu.name] = {}
|
|
1079
|
+
local_per_type: Dict[str, List[int]] = defaultdict(list)
|
|
1080
|
+
links_per_fmu: Dict[str, List[Link]] = defaultdict(list)
|
|
899
1081
|
|
|
900
1082
|
# Fill data structure
|
|
901
1083
|
# Inputs
|
|
902
1084
|
for input_port_name, input_port in self.inputs.items():
|
|
903
1085
|
inputs_per_type[input_port.type_name].append(input_port)
|
|
1086
|
+
|
|
1087
|
+
# Start values
|
|
904
1088
|
for input_port, value in self.start_values.items():
|
|
905
|
-
|
|
1089
|
+
fmu_io_list.add_start_value(input_port, value)
|
|
1090
|
+
|
|
906
1091
|
# Outputs
|
|
907
1092
|
for output_port_name, output_port in self.outputs.items():
|
|
908
1093
|
outputs_per_type[output_port.port.type_name].append(output_port)
|
|
1094
|
+
|
|
909
1095
|
# Links
|
|
910
1096
|
for link in self.links.values():
|
|
911
|
-
|
|
912
|
-
|
|
1097
|
+
# FMU Outputs
|
|
1098
|
+
if link.cport_from:
|
|
1099
|
+
local_per_type[link.cport_from.port.type_name].append(link.vr)
|
|
1100
|
+
fmu_io_list.add_output(link.cport_from, link.vr)
|
|
1101
|
+
else:
|
|
1102
|
+
local_per_type["clock"].append(link.vr)
|
|
1103
|
+
for cport_to in link.cport_to_list:
|
|
1104
|
+
if cport_to.fmu.ls.is_bus:
|
|
1105
|
+
logger.info(f"LS-BUS: importer scheduling for '{cport_to.fmu.name}' '{cport_to.port.name}' (clock={cport_to.port.vr}, {link.vr})")
|
|
1106
|
+
clock_list.append(cport_to, link.vr)
|
|
1107
|
+
break
|
|
1108
|
+
|
|
1109
|
+
# FMU Inputs
|
|
913
1110
|
for cport_to in link.cport_to_list:
|
|
914
|
-
if
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
1111
|
+
if link.cport_from is not None or not cport_to.fmu.ls.is_bus:
|
|
1112
|
+
# LS-BUS allows, importer to feed clock signal. In this case, cport_from is None
|
|
1113
|
+
# FMU will be fed directly by importer, no need to add inpunt link!
|
|
1114
|
+
if link.cport_from is None or cport_to.port.type_name == link.cport_from.port.type_name:
|
|
1115
|
+
local_vr = link.vr
|
|
1116
|
+
else:
|
|
1117
|
+
local_per_type[cport_to.port.type_name].append(link.vr_converted[cport_to.port.type_name])
|
|
1118
|
+
links_per_fmu[link.cport_from.fmu.name].append(link)
|
|
1119
|
+
local_vr = link.vr_converted[cport_to.port.type_name]
|
|
1120
|
+
|
|
1121
|
+
fmu_io_list.add_input(cport_to, local_vr)
|
|
921
1122
|
|
|
922
1123
|
print(f"# NB local variables:", ", ".join(EmbeddedFMUPort.ALL_TYPES), file=txt_file)
|
|
923
1124
|
nb_local = [f"{self.vr_table.nb_local(type_name)}" for type_name in EmbeddedFMUPort.ALL_TYPES]
|
|
@@ -952,24 +1153,7 @@ class FMUContainer:
|
|
|
952
1153
|
|
|
953
1154
|
# LINKS
|
|
954
1155
|
for fmu in self.involved_fmu.values():
|
|
955
|
-
|
|
956
|
-
print(f"# Inputs of {fmu.name} - {type_name}: <VR> <FMU_VR>", file=txt_file)
|
|
957
|
-
print(len(inputs_fmu_per_type[type_name][fmu.name]), file=txt_file)
|
|
958
|
-
for input_port, vr in inputs_fmu_per_type[type_name][fmu.name].items():
|
|
959
|
-
print(f"{vr} {input_port.port.vr}", file=txt_file)
|
|
960
|
-
|
|
961
|
-
for type_name in EmbeddedFMUPort.ALL_TYPES:
|
|
962
|
-
print(f"# Start values of {fmu.name} - {type_name}: <FMU_VR> <RESET> <VALUE>", file=txt_file)
|
|
963
|
-
print(len(start_values_fmu_per_type[type_name][fmu.name]), file=txt_file)
|
|
964
|
-
for input_port, value in start_values_fmu_per_type[type_name][fmu.name].items():
|
|
965
|
-
reset = 1 if input_port.port.causality == "input" else 0
|
|
966
|
-
print(f"{input_port.port.vr} {reset} {value}", file=txt_file)
|
|
967
|
-
|
|
968
|
-
for type_name in EmbeddedFMUPort.ALL_TYPES:
|
|
969
|
-
print(f"# Outputs of {fmu.name} - {type_name}: <VR> <FMU_VR>", file=txt_file)
|
|
970
|
-
print(len(outputs_fmu_per_type[type_name][fmu.name]), file=txt_file)
|
|
971
|
-
for output_port, vr in outputs_fmu_per_type[type_name][fmu.name].items():
|
|
972
|
-
print(f"{vr} {output_port.port.vr}", file=txt_file)
|
|
1156
|
+
fmu_io_list.write_txt(fmu.name, txt_file)
|
|
973
1157
|
|
|
974
1158
|
print(f"# Conversion table of {fmu.name}: <VR_FROM> <VR_TO> <CONVERSION>", file=txt_file)
|
|
975
1159
|
try:
|
|
@@ -986,6 +1170,9 @@ class FMUContainer:
|
|
|
986
1170
|
except KeyError:
|
|
987
1171
|
print("0", file=txt_file)
|
|
988
1172
|
|
|
1173
|
+
# CLOCKS
|
|
1174
|
+
clock_list.write_txt(txt_file)
|
|
1175
|
+
|
|
989
1176
|
@staticmethod
|
|
990
1177
|
def long_path(path: Union[str, Path]) -> str:
|
|
991
1178
|
# https://stackoverflow.com/questions/14075465/copy-a-file-with-a-too-long-path-to-another-directory-in-python
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
import xml.etree.ElementTree as ET
|
|
3
|
+
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from typing import *
|
|
6
|
+
|
|
7
|
+
logger = logging.getLogger("fmu_manipulation_toolbox")
|
|
8
|
+
|
|
9
|
+
class LayeredStandard:
|
|
10
|
+
def __init__(self, directory: Union[Path, str]):
|
|
11
|
+
self.is_bus = False
|
|
12
|
+
self.standards: List[str] = []
|
|
13
|
+
|
|
14
|
+
if isinstance(directory, Path):
|
|
15
|
+
self.directory = directory
|
|
16
|
+
else:
|
|
17
|
+
self.directory = Path(directory)
|
|
18
|
+
|
|
19
|
+
self.parse_lsbus()
|
|
20
|
+
|
|
21
|
+
def parse_lsbus(self):
|
|
22
|
+
filename = self.directory / "extra" / "org.fmi-standard.fmi-ls-bus" / "fmi-ls-manifest.xml"
|
|
23
|
+
if filename.exists():
|
|
24
|
+
xml = ET.parse(filename)
|
|
25
|
+
root = xml.getroot()
|
|
26
|
+
root.get("isBusSimulationFMU", "")
|
|
27
|
+
self.is_bus = root.get("isBusSimulationFMU") == "true"
|
|
28
|
+
|
|
29
|
+
self.standards.append("LS-BUS")
|
|
30
|
+
|
|
31
|
+
def __len__(self):
|
|
32
|
+
return len(self.standards)
|
|
33
|
+
|
|
34
|
+
def __repr__(self):
|
|
35
|
+
return ", ".join(self.standards)
|
|
@@ -19,7 +19,7 @@ class FMU:
|
|
|
19
19
|
FMI2_TYPES = ('Real', 'Integer', 'String', 'Boolean', 'Enumeration')
|
|
20
20
|
FMI3_TYPES = ('Float64', 'Float32',
|
|
21
21
|
'Int8', 'UInt8', 'Int16', 'UInt16', 'Int32', 'UInt32', 'Int64', 'UInt64',
|
|
22
|
-
'String', 'Boolean', 'Enumeration')
|
|
22
|
+
'String', 'Boolean', 'Enumeration', 'Clock', 'Binary')
|
|
23
23
|
|
|
24
24
|
def __init__(self, fmu_filename):
|
|
25
25
|
self.fmu_filename = fmu_filename
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -48,7 +48,7 @@ class FMUSplitter:
|
|
|
48
48
|
self.dir_set = self.get_dir_set()
|
|
49
49
|
|
|
50
50
|
if "resources/container.txt" not in self.filenames_list:
|
|
51
|
-
|
|
51
|
+
raise FMUSplitterError(f"FMU file {self.fmu_filename} is not an FMU Container.")
|
|
52
52
|
|
|
53
53
|
self.directory.mkdir(exist_ok=True)
|
|
54
54
|
logger.info(f"Preparing to split '{self.fmu_filename}' into '{self.directory}'")
|
|
@@ -123,6 +123,7 @@ class FMUSplitterDescription:
|
|
|
123
123
|
"auto_local": False,
|
|
124
124
|
"auto_link": False,
|
|
125
125
|
}
|
|
126
|
+
self.format = 0
|
|
126
127
|
self.fmu_filename_list = []
|
|
127
128
|
|
|
128
129
|
# used for modelDescription.xml parsing
|
|
@@ -137,6 +138,7 @@ class FMUSplitterDescription:
|
|
|
137
138
|
def get_line(file):
|
|
138
139
|
for line in file:
|
|
139
140
|
line = line.decode('utf-8').strip()
|
|
141
|
+
logger.debug(line)
|
|
140
142
|
if line and not line.startswith("#"):
|
|
141
143
|
return line
|
|
142
144
|
raise StopIteration
|
|
@@ -263,6 +265,17 @@ class FMUSplitterDescription:
|
|
|
263
265
|
fmu_vr = int(tokens[2])
|
|
264
266
|
self.add_port(fmi_type, fmu_id, fmu_vr, container_vr)
|
|
265
267
|
|
|
268
|
+
@staticmethod
|
|
269
|
+
def get_nb(line):
|
|
270
|
+
try:
|
|
271
|
+
(nb_str, _) = line.split(" ")
|
|
272
|
+
nb = int(nb_str)
|
|
273
|
+
except ValueError:
|
|
274
|
+
nb = int(line)
|
|
275
|
+
|
|
276
|
+
return nb
|
|
277
|
+
|
|
278
|
+
|
|
266
279
|
def parse_txt_file(self, txt_filename: str):
|
|
267
280
|
self.parse_model_description(str(Path(txt_filename).parent.parent), ".")
|
|
268
281
|
logger.debug(f"Parsing container file '{txt_filename}'")
|
|
@@ -275,6 +288,8 @@ class FMUSplitterDescription:
|
|
|
275
288
|
|
|
276
289
|
for fmi_type in self.supported_fmi_types:
|
|
277
290
|
nb_input = int(self.get_line(file))
|
|
291
|
+
logger.debug(f"INPUT of {fmu_filename} {fmi_type} : {nb_input}")
|
|
292
|
+
|
|
278
293
|
for i in range(nb_input):
|
|
279
294
|
local, vr = self.get_line(file).split(" ")
|
|
280
295
|
try:
|
|
@@ -285,10 +300,26 @@ class FMUSplitterDescription:
|
|
|
285
300
|
link.to_port.append(FMUSplitterPort(fmu_filename,
|
|
286
301
|
self.vr_to_name[fmu_filename][fmi_type][int(vr)]["name"]))
|
|
287
302
|
|
|
288
|
-
|
|
303
|
+
#clocked
|
|
304
|
+
if not fmi_type == "clock":
|
|
305
|
+
nb_input = self.get_nb(self.get_line(file))
|
|
306
|
+
logger.debug(f"INPUT of {fmu_filename} {fmi_type} : CLOCKED {nb_input}")
|
|
307
|
+
for i in range(nb_input):
|
|
308
|
+
local, _clock, vr = self.get_line(file).split(" ")
|
|
309
|
+
try:
|
|
310
|
+
link = self.links[fmi_type][local]
|
|
311
|
+
except KeyError:
|
|
312
|
+
link = FMUSplitterLink()
|
|
313
|
+
self.links[fmi_type][local] = link
|
|
314
|
+
link.to_port.append(FMUSplitterPort(fmu_filename,
|
|
315
|
+
self.vr_to_name[fmu_filename][fmi_type][int(vr)]["name"]))
|
|
316
|
+
|
|
317
|
+
for fmi_type in self.supported_fmi_types[:-2]:
|
|
289
318
|
nb_start = int(self.get_line(file))
|
|
319
|
+
logger.debug(f"nb start for {fmu_filename} {fmi_type} : {nb_start}")
|
|
290
320
|
for i in range(nb_start):
|
|
291
321
|
tokens = self.get_line(file).split(" ")
|
|
322
|
+
logger.error(tokens)
|
|
292
323
|
vr = int(tokens[0])
|
|
293
324
|
value = tokens[-1]
|
|
294
325
|
start_definition = [fmu_filename, self.vr_to_name[fmu_filename][fmi_type][vr]["name"],
|
|
@@ -301,6 +332,7 @@ class FMUSplitterDescription:
|
|
|
301
332
|
# Output per FMUs
|
|
302
333
|
for fmi_type in self.supported_fmi_types:
|
|
303
334
|
nb_output = int(self.get_line(file))
|
|
335
|
+
logger.debug(f"OUTPUT of {fmu_filename} {fmi_type} : {nb_output}")
|
|
304
336
|
|
|
305
337
|
for i in range(nb_output):
|
|
306
338
|
local, vr = self.get_line(file).split(" ")
|
|
@@ -311,6 +343,21 @@ class FMUSplitterDescription:
|
|
|
311
343
|
self.links[fmi_type][local] = link
|
|
312
344
|
link.from_port = FMUSplitterPort(fmu_filename,
|
|
313
345
|
self.vr_to_name[fmu_filename][fmi_type][int(vr)]["name"])
|
|
346
|
+
|
|
347
|
+
if not fmi_type == "clock":
|
|
348
|
+
nb_output = self.get_nb(self.get_line(file))
|
|
349
|
+
logger.debug(f"OUTPUT {fmi_type} : CLOCKED {nb_output}")
|
|
350
|
+
|
|
351
|
+
for i in range(nb_output):
|
|
352
|
+
local, _clock, vr = self.get_line(file).split(" ")
|
|
353
|
+
try:
|
|
354
|
+
link = self.links[fmi_type][local]
|
|
355
|
+
except KeyError:
|
|
356
|
+
link = FMUSplitterLink()
|
|
357
|
+
self.links[fmi_type][local] = link
|
|
358
|
+
link.from_port = FMUSplitterPort(fmu_filename,
|
|
359
|
+
self.vr_to_name[fmu_filename][fmi_type][int(vr)]["name"])
|
|
360
|
+
|
|
314
361
|
#conversion
|
|
315
362
|
nb_conversion = int(self.get_line(file))
|
|
316
363
|
for i in range(nb_conversion):
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
import xml.etree.ElementTree as ET
|
|
3
|
+
|
|
4
|
+
from collections import Counter
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from typing import *
|
|
7
|
+
|
|
8
|
+
logger = logging.getLogger("fmu_manipulation_toolbox")
|
|
9
|
+
|
|
10
|
+
class Terminal:
|
|
11
|
+
def __init__(self, name: str, kind: str, matching: str):
|
|
12
|
+
self.name = name
|
|
13
|
+
self.kind = str
|
|
14
|
+
self.matching = matching
|
|
15
|
+
self.members:Dict[str, str] = {}
|
|
16
|
+
self.sub_terminals: Dict[str, Terminal] = {}
|
|
17
|
+
|
|
18
|
+
def add_member(self, member_name, variable_name):
|
|
19
|
+
self.members[member_name] = variable_name
|
|
20
|
+
|
|
21
|
+
def __repr__(self):
|
|
22
|
+
return f"{self.name} ({len(self.members)} signals)"
|
|
23
|
+
|
|
24
|
+
def __eq__(self, other):
|
|
25
|
+
if isinstance(other, Terminal):
|
|
26
|
+
return self.kind == other.kind and self.matching == other.matching
|
|
27
|
+
else:
|
|
28
|
+
return False
|
|
29
|
+
|
|
30
|
+
def connect(self, other) -> List[Tuple[str, str]]:
|
|
31
|
+
links = []
|
|
32
|
+
|
|
33
|
+
for sub_terminal in self.sub_terminals.values():
|
|
34
|
+
other_sub_terminal = other.sub_terminals[sub_terminal.name]
|
|
35
|
+
links += sub_terminal.connect(other_sub_terminal)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
if isinstance(other, Terminal):
|
|
39
|
+
if self.matching == "plug":
|
|
40
|
+
return self.connect_plug(other)
|
|
41
|
+
elif self.matching == "bus":
|
|
42
|
+
return self.connect_bus(other)
|
|
43
|
+
elif self.matching == "sequence":
|
|
44
|
+
return self.connect_sequence(other)
|
|
45
|
+
elif self.matching == "org.fmi-ls-bus.transceiver":
|
|
46
|
+
return self.connect_transceiver(other)
|
|
47
|
+
else:
|
|
48
|
+
logger.error(f"Rule '{self.matching}' not defined to connect Terminal '{self.name}'")
|
|
49
|
+
else:
|
|
50
|
+
logger.error(f"Cannot connect Terminal '{self.name}' to '{other}'.")
|
|
51
|
+
|
|
52
|
+
return links
|
|
53
|
+
|
|
54
|
+
def connect_plug(self, other) -> List[Tuple[str, str]]:
|
|
55
|
+
links = []
|
|
56
|
+
if Counter(self.members.keys()) == Counter(other.members.keys()):
|
|
57
|
+
for member_name, member in self.members.items():
|
|
58
|
+
other_member = other.members[member_name]
|
|
59
|
+
links.append((member, other_member))
|
|
60
|
+
else:
|
|
61
|
+
logger.error(f"PLUG Terminal '{self.name}' does not exactly fit Terminal '{other.name}'")
|
|
62
|
+
|
|
63
|
+
return links
|
|
64
|
+
|
|
65
|
+
def connect_bus(self, other) -> List[Tuple[str, str]]:
|
|
66
|
+
links = []
|
|
67
|
+
for member_name, member in self.members.items():
|
|
68
|
+
if member_name in other.members:
|
|
69
|
+
other_member = other.members[member_name]
|
|
70
|
+
links.append((member, other_member))
|
|
71
|
+
return links
|
|
72
|
+
|
|
73
|
+
def connect_sequence(self, other) -> List[Tuple[str, str]]:
|
|
74
|
+
links = []
|
|
75
|
+
if len(self.members) == len(other.members):
|
|
76
|
+
for member, other_member in zip(self.members.values(), other.members.values()):
|
|
77
|
+
links.append((member, other_member))
|
|
78
|
+
else:
|
|
79
|
+
logger.error(f"SEQUENCE Terminal '{self.name}' does not exactly fit Terminal '{other.name}'")
|
|
80
|
+
return links
|
|
81
|
+
|
|
82
|
+
def connect_transceiver(self, other) -> List[Tuple[str, str]]:
|
|
83
|
+
return [(self.members["Tx_Data"], other.members["Rx_Data"]),
|
|
84
|
+
(self.members["Tx_Clock"], other.members["Rx_Clock"]),
|
|
85
|
+
(self.members["Rx_Data"], other.members["Tx_Data"]),
|
|
86
|
+
(self.members["Rx_Clock"], other.members["Tx_Clock"])]
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
class Terminals:
|
|
90
|
+
FILENAME = "terminalsAndIcons.xml"
|
|
91
|
+
def __init__(self, directory: Union[Path, str]):
|
|
92
|
+
self.terminals: OrderedDict[str, Terminal] = OrderedDict()
|
|
93
|
+
|
|
94
|
+
if isinstance(directory, str):
|
|
95
|
+
directory = Path(directory)
|
|
96
|
+
|
|
97
|
+
filename = directory / "terminalsAndIcons" / self.FILENAME
|
|
98
|
+
if filename.exists():
|
|
99
|
+
|
|
100
|
+
xml = ET.parse(filename)
|
|
101
|
+
|
|
102
|
+
try:
|
|
103
|
+
for element in xml.getroot()[0]:
|
|
104
|
+
if element.tag == "Terminal":
|
|
105
|
+
terminal = self.add_terminal(element)
|
|
106
|
+
logger.debug(f"Terminal '{terminal.name}' defined with {len(terminal.members)} signals")
|
|
107
|
+
except IndexError:
|
|
108
|
+
logger.error(f"{filename} is wrongly formated.")
|
|
109
|
+
|
|
110
|
+
def __len__(self):
|
|
111
|
+
return len(self.terminals)
|
|
112
|
+
|
|
113
|
+
def __contains__(self, item):
|
|
114
|
+
return item in self.terminals
|
|
115
|
+
|
|
116
|
+
def __getitem__(self, item):
|
|
117
|
+
return self.terminals[item]
|
|
118
|
+
|
|
119
|
+
def add_terminal(self, element) -> Terminal:
|
|
120
|
+
name = element.get("name")
|
|
121
|
+
matching = element.get("matchingRule")
|
|
122
|
+
kind = element.get("terminalKind")
|
|
123
|
+
|
|
124
|
+
terminal = Terminal(name, kind, matching)
|
|
125
|
+
self.add_member_from_terminal(terminal, element)
|
|
126
|
+
|
|
127
|
+
self.terminals[name] = terminal
|
|
128
|
+
|
|
129
|
+
return terminal
|
|
130
|
+
|
|
131
|
+
def add_member_from_terminal(self, terminal, element):
|
|
132
|
+
for child in element:
|
|
133
|
+
if child.tag == "TerminalMemberVariable":
|
|
134
|
+
terminal.add_member(child.get("memberName"), child.get("variableName"))
|
|
135
|
+
elif child.tag == "Terminal":
|
|
136
|
+
sub_terminal = self.add_terminal(child)
|
|
137
|
+
terminal.subterminals[sub_terminal.name] = sub_terminal
|
{fmu_manipulation_toolbox-1.9.1.3.dist-info → fmu_manipulation_toolbox-1.9.2b1.dist-info}/METADATA
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: fmu_manipulation_toolbox
|
|
3
|
-
Version: 1.9.
|
|
3
|
+
Version: 1.9.2b1
|
|
4
4
|
Summary: FMU Manipulation Toolbox is a python application for modifying Functional Mock-up Units (FMUs) without recompilation or bundling them into FMU Containers
|
|
5
5
|
Home-page: https://github.com/grouperenault/fmu_manipulation_toolbox/
|
|
6
6
|
Author: Nicolas.LAURENT@Renault.com
|
{fmu_manipulation_toolbox-1.9.1.3.dist-info → fmu_manipulation_toolbox-1.9.2b1.dist-info}/RECORD
RENAMED
|
@@ -1,15 +1,17 @@
|
|
|
1
1
|
fmu_manipulation_toolbox/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
2
2
|
fmu_manipulation_toolbox/__main__.py,sha256=Ixgo_5YS_khXseMujNPsVzaerkHPugLPYPV7FIi5umo,364
|
|
3
|
-
fmu_manipulation_toolbox/__version__.py,sha256=
|
|
3
|
+
fmu_manipulation_toolbox/__version__.py,sha256=eFoY0dV9P-7aIw41HjOXEMNKuPwNW0s1_trJUzchB-g,11
|
|
4
4
|
fmu_manipulation_toolbox/assembly.py,sha256=-i9iQLKiPyjdFZyxHmB1FgL3NYmRPfTWVs9lf3fC5sE,27022
|
|
5
5
|
fmu_manipulation_toolbox/checker.py,sha256=RmIYd6s4p2S9wdv-uSILfVm__QTZSUJCiq6hr_G5rxA,3119
|
|
6
|
-
fmu_manipulation_toolbox/container.py,sha256=
|
|
6
|
+
fmu_manipulation_toolbox/container.py,sha256=HMi3tuMjdrlgVkbzjp5_48OM-iqNFnqc1OsR8O0e9mE,55634
|
|
7
7
|
fmu_manipulation_toolbox/gui.py,sha256=-DyWY2MoECr3FGRYdS60Ltoayhd3O0uOCtkQ4YXE8xc,29201
|
|
8
8
|
fmu_manipulation_toolbox/gui_style.py,sha256=s6WdrnNd_lCMWhuBf5LKK8wrfLXCU7pFTLUfvqkJVno,6633
|
|
9
9
|
fmu_manipulation_toolbox/help.py,sha256=j8xmnCrwQpaW-SZ8hSqA1dlTXgaqzQWc4Yr3RH_oqck,6012
|
|
10
|
-
fmu_manipulation_toolbox/
|
|
10
|
+
fmu_manipulation_toolbox/ls.py,sha256=wmyoKrvDLXpL-PFz6cUhLLqxDMD5E9L_P4KswWpQHsk,975
|
|
11
|
+
fmu_manipulation_toolbox/operations.py,sha256=rfeZiCHuVr5-M4lC496feYl1nrRBheP_W9AsJHtMm58,21094
|
|
11
12
|
fmu_manipulation_toolbox/remoting.py,sha256=N25MDFkIcEWe9CIT1M4L9kea3j-8E7i2I1VOI6zIAdw,3876
|
|
12
|
-
fmu_manipulation_toolbox/split.py,sha256=
|
|
13
|
+
fmu_manipulation_toolbox/split.py,sha256=q6a5rZ4s7E24nf4BiebUuoneBEj9j293f2LiAaSDKnE,16345
|
|
14
|
+
fmu_manipulation_toolbox/terminals.py,sha256=mGGS4tdE6cJuz-2zvwc7drpmT0QJ7YPe8ENw2UGlEHA,5062
|
|
13
15
|
fmu_manipulation_toolbox/version.py,sha256=OhBLkZ1-nhC77kyvffPNAf6m8OZe1bYTnNf_PWs1NvM,392
|
|
14
16
|
fmu_manipulation_toolbox/cli/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
15
17
|
fmu_manipulation_toolbox/cli/fmucontainer.py,sha256=oYidLBvXyDApsY7JVGlCL5g35yc1F9Gr8tFjYY7077s,4981
|
|
@@ -33,7 +35,7 @@ fmu_manipulation_toolbox/resources/icon_fmu.png,sha256=EuygB2xcoM2WAfKKdyKG_UvTL
|
|
|
33
35
|
fmu_manipulation_toolbox/resources/license.txt,sha256=5ODuU8g8pIkK-NMWXu_rjZ6k7gM7b-N2rmg87-2Kmqw,1583
|
|
34
36
|
fmu_manipulation_toolbox/resources/mask.png,sha256=px1U4hQGL0AmZ4BQPknOVREpMpTSejbah3ntkpqAzFA,3008
|
|
35
37
|
fmu_manipulation_toolbox/resources/model.png,sha256=EAf_HnZJe8zYGZygerG1MMt2U-tMMZlifzXPj4_iORA,208788
|
|
36
|
-
fmu_manipulation_toolbox/resources/darwin64/container.dylib,sha256=
|
|
38
|
+
fmu_manipulation_toolbox/resources/darwin64/container.dylib,sha256=LAXXvAWewT4P21esMyCy8DbkbrnSDggSgnS2NUTsrwc,213632
|
|
37
39
|
fmu_manipulation_toolbox/resources/fmi-2.0/fmi2Annotation.xsd,sha256=OGfyJtaJntKypX5KDpuZ-nV1oYLZ6HV16pkpKOmYox4,2731
|
|
38
40
|
fmu_manipulation_toolbox/resources/fmi-2.0/fmi2AttributeGroups.xsd,sha256=HwyV7LBse-PQSv4z1xjmtzPU3Hjnv4mluq9YdSBNHMQ,3704
|
|
39
41
|
fmu_manipulation_toolbox/resources/fmi-2.0/fmi2ModelDescription.xsd,sha256=JM4j_9q-pc40XYHb28jfT3iV3aYM5JLqD5aRjO72K1E,18939
|
|
@@ -53,19 +55,19 @@ fmu_manipulation_toolbox/resources/fmi-3.0/fmi3Type.xsd,sha256=TaHRoUBIFtmdEwBKB
|
|
|
53
55
|
fmu_manipulation_toolbox/resources/fmi-3.0/fmi3Unit.xsd,sha256=CK_F2t5LfyQ6eSNJ8soTFMVK9DU8vD2WiMi2MQvjB0g,3746
|
|
54
56
|
fmu_manipulation_toolbox/resources/fmi-3.0/fmi3Variable.xsd,sha256=3YU-3q1-c-namz7sMe5cxnmOVOJsRSmfWR02PKv3xaU,19171
|
|
55
57
|
fmu_manipulation_toolbox/resources/fmi-3.0/fmi3VariableDependency.xsd,sha256=YQSBwXt4IsDlyegY8bX-qQHGSfE5TipTPfo2g2yqq1c,3082
|
|
56
|
-
fmu_manipulation_toolbox/resources/linux32/client_sm.so,sha256=
|
|
58
|
+
fmu_manipulation_toolbox/resources/linux32/client_sm.so,sha256=OjdzO-OEU0UW1D1YvDouikjKAMWYv3f-tkLZ_qb80g4,34756
|
|
57
59
|
fmu_manipulation_toolbox/resources/linux32/server_sm,sha256=gzKU0BTeaRkvhTMQtHHj3K8uYFyEdyGGn_mZy_jG9xo,21304
|
|
58
|
-
fmu_manipulation_toolbox/resources/linux64/client_sm.so,sha256=
|
|
59
|
-
fmu_manipulation_toolbox/resources/linux64/container.so,sha256=
|
|
60
|
+
fmu_manipulation_toolbox/resources/linux64/client_sm.so,sha256=UOcyFYhHJyLNmQoLdiBD75nzloBmk77g9i91QjdItL0,32592
|
|
61
|
+
fmu_manipulation_toolbox/resources/linux64/container.so,sha256=426lYFuGlXmUGRhwDl-VW9_pFJO1RxrWbg6Wv-Fekdc,177032
|
|
60
62
|
fmu_manipulation_toolbox/resources/linux64/server_sm,sha256=MZn6vITN2qpBHYt_RaK2VnFFp00hk8fTALBHmXPtLwc,22608
|
|
61
|
-
fmu_manipulation_toolbox/resources/win32/client_sm.dll,sha256=
|
|
62
|
-
fmu_manipulation_toolbox/resources/win32/server_sm.exe,sha256=
|
|
63
|
-
fmu_manipulation_toolbox/resources/win64/client_sm.dll,sha256=
|
|
64
|
-
fmu_manipulation_toolbox/resources/win64/container.dll,sha256=
|
|
65
|
-
fmu_manipulation_toolbox/resources/win64/server_sm.exe,sha256=
|
|
66
|
-
fmu_manipulation_toolbox-1.9.
|
|
67
|
-
fmu_manipulation_toolbox-1.9.
|
|
68
|
-
fmu_manipulation_toolbox-1.9.
|
|
69
|
-
fmu_manipulation_toolbox-1.9.
|
|
70
|
-
fmu_manipulation_toolbox-1.9.
|
|
71
|
-
fmu_manipulation_toolbox-1.9.
|
|
63
|
+
fmu_manipulation_toolbox/resources/win32/client_sm.dll,sha256=fk9hb9RJ9AqGLpw5jUHy743OGyHXlcYbYLSeJCPUQRg,17920
|
|
64
|
+
fmu_manipulation_toolbox/resources/win32/server_sm.exe,sha256=LDtvrrmmyVPGBNOx4iljMcNYJuNlSZAf-kl_6hTxKu0,15360
|
|
65
|
+
fmu_manipulation_toolbox/resources/win64/client_sm.dll,sha256=koFqnnDq-Oe_tlzfNuEGbSvXpFKz9fjPDt_xwaquQqw,21504
|
|
66
|
+
fmu_manipulation_toolbox/resources/win64/container.dll,sha256=8OKu_SQABu-eEDm7YIFbcBmnhZolXoIRpmEaObsTHk8,141824
|
|
67
|
+
fmu_manipulation_toolbox/resources/win64/server_sm.exe,sha256=Vq5CG6Ll2Pb06J9IKUpV-5VMHUaoALYizIB4PbVtgdc,18432
|
|
68
|
+
fmu_manipulation_toolbox-1.9.2b1.dist-info/licenses/LICENSE.txt,sha256=c_862mzyk6ownO3Gt6cVs0-53IXLi_-ZEQFNDVabESw,1285
|
|
69
|
+
fmu_manipulation_toolbox-1.9.2b1.dist-info/METADATA,sha256=tXtq9Rodjl3GQ6FOdB7SeGV_yv4htrFXpsict3ffsvo,1157
|
|
70
|
+
fmu_manipulation_toolbox-1.9.2b1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
71
|
+
fmu_manipulation_toolbox-1.9.2b1.dist-info/entry_points.txt,sha256=HjOZkflbI1IuSY8BpOZre20m24M4GDQGCJfPIa7NrlY,264
|
|
72
|
+
fmu_manipulation_toolbox-1.9.2b1.dist-info/top_level.txt,sha256=9D_h-5BMjSqf9z-XFkbJL_bMppR2XNYW3WNuPkXou0k,25
|
|
73
|
+
fmu_manipulation_toolbox-1.9.2b1.dist-info/RECORD,,
|
{fmu_manipulation_toolbox-1.9.1.3.dist-info → fmu_manipulation_toolbox-1.9.2b1.dist-info}/WHEEL
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|