aas-standard-parser 0.1.7__tar.gz → 0.1.8__tar.gz
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.
- {aas_standard_parser-0.1.7 → aas_standard_parser-0.1.8}/PKG-INFO +1 -1
- {aas_standard_parser-0.1.7 → aas_standard_parser-0.1.8}/aas_standard_parser/__init__.py +2 -2
- {aas_standard_parser-0.1.7 → aas_standard_parser-0.1.8}/aas_standard_parser/aid_parser.py +34 -58
- aas_standard_parser-0.1.8/aas_standard_parser/aimc_parser.py +272 -0
- {aas_standard_parser-0.1.7 → aas_standard_parser-0.1.8}/aas_standard_parser/collection_helpers.py +26 -23
- aas_standard_parser-0.1.8/aas_standard_parser/demo/demo_process.py +18 -0
- aas_standard_parser-0.1.8/aas_standard_parser/demo/logging_handler.py +197 -0
- {aas_standard_parser-0.1.7 → aas_standard_parser-0.1.8}/aas_standard_parser/submodel_parser.py +24 -26
- aas_standard_parser-0.1.8/aas_standard_parser/utils.py +25 -0
- {aas_standard_parser-0.1.7 → aas_standard_parser-0.1.8}/aas_standard_parser.egg-info/PKG-INFO +1 -1
- {aas_standard_parser-0.1.7 → aas_standard_parser-0.1.8}/aas_standard_parser.egg-info/SOURCES.txt +5 -1
- {aas_standard_parser-0.1.7 → aas_standard_parser-0.1.8}/pyproject.toml +1 -1
- aas_standard_parser-0.1.8/tests/test_aimc_parser.py +97 -0
- aas_standard_parser-0.1.7/aas_standard_parser/aimc_parser.py +0 -344
- {aas_standard_parser-0.1.7 → aas_standard_parser-0.1.8}/LICENSE +0 -0
- {aas_standard_parser-0.1.7 → aas_standard_parser-0.1.8}/README.md +0 -0
- {aas_standard_parser-0.1.7 → aas_standard_parser-0.1.8}/aas_standard_parser/reference_helpers.py +0 -0
- {aas_standard_parser-0.1.7 → aas_standard_parser-0.1.8}/aas_standard_parser.egg-info/dependency_links.txt +0 -0
- {aas_standard_parser-0.1.7 → aas_standard_parser-0.1.8}/aas_standard_parser.egg-info/requires.txt +0 -0
- {aas_standard_parser-0.1.7 → aas_standard_parser-0.1.8}/aas_standard_parser.egg-info/top_level.txt +0 -0
- {aas_standard_parser-0.1.7 → aas_standard_parser-0.1.8}/setup.cfg +0 -0
|
@@ -13,7 +13,7 @@ except importlib.metadata.PackageNotFoundError:
|
|
|
13
13
|
__project__ = "aas-standard-parser"
|
|
14
14
|
__package__ = "aas-standard-parser"
|
|
15
15
|
|
|
16
|
+
from aas_standard_parser import aimc_parser
|
|
16
17
|
from aas_standard_parser.aid_parser import AIDParser
|
|
17
|
-
from aas_standard_parser.aimc_parser import AIMCParser
|
|
18
18
|
|
|
19
|
-
__all__ = ["
|
|
19
|
+
__all__ = ["AIDParser", "aimc_parser"]
|
|
@@ -1,24 +1,24 @@
|
|
|
1
1
|
"""This module provides functions to parse AID Submodels and extract MQTT interface descriptions."""
|
|
2
|
+
|
|
2
3
|
import base64
|
|
3
4
|
from typing import Dict, List
|
|
4
5
|
|
|
5
6
|
from basyx.aas.model import (
|
|
6
7
|
Property,
|
|
7
8
|
SubmodelElement,
|
|
8
|
-
SubmodelElementCollection,
|
|
9
|
+
SubmodelElementCollection,
|
|
10
|
+
SubmodelElementList,
|
|
9
11
|
)
|
|
10
12
|
|
|
11
|
-
from aas_standard_parser.collection_helpers import
|
|
13
|
+
from aas_standard_parser.collection_helpers import find_all_by_semantic_id, find_by_id_short, find_by_semantic_id
|
|
12
14
|
|
|
13
15
|
|
|
14
16
|
class IProtocolBinding:
|
|
15
|
-
|
|
16
17
|
def __init__(self):
|
|
17
18
|
pass
|
|
18
19
|
|
|
19
20
|
|
|
20
21
|
class HttpProtocolBinding(IProtocolBinding):
|
|
21
|
-
|
|
22
22
|
def __init__(self, method_name: str, headers: Dict[str, str]):
|
|
23
23
|
super().__init__()
|
|
24
24
|
self.method_name = method_name
|
|
@@ -26,7 +26,6 @@ class HttpProtocolBinding(IProtocolBinding):
|
|
|
26
26
|
|
|
27
27
|
|
|
28
28
|
class PropertyDetails:
|
|
29
|
-
|
|
30
29
|
def __init__(self, href: str, keys: List[str], protocol_binding: IProtocolBinding = None):
|
|
31
30
|
self.href = href
|
|
32
31
|
self.keys = keys
|
|
@@ -34,14 +33,12 @@ class PropertyDetails:
|
|
|
34
33
|
|
|
35
34
|
|
|
36
35
|
class IAuthenticationDetails:
|
|
37
|
-
|
|
38
36
|
def __init__(self):
|
|
39
37
|
# TODO: different implementations for different AID versions
|
|
40
38
|
pass
|
|
41
39
|
|
|
42
40
|
|
|
43
41
|
class BasicAuthenticationDetails(IAuthenticationDetails):
|
|
44
|
-
|
|
45
42
|
def __init__(self, user: str, password: str):
|
|
46
43
|
self.user = user
|
|
47
44
|
self.password = password
|
|
@@ -49,13 +46,11 @@ class BasicAuthenticationDetails(IAuthenticationDetails):
|
|
|
49
46
|
|
|
50
47
|
|
|
51
48
|
class NoAuthenticationDetails(IAuthenticationDetails):
|
|
52
|
-
|
|
53
49
|
def __init__(self):
|
|
54
50
|
super().__init__()
|
|
55
51
|
|
|
56
52
|
|
|
57
|
-
class AIDParser
|
|
58
|
-
|
|
53
|
+
class AIDParser:
|
|
59
54
|
def __init__(self):
|
|
60
55
|
pass
|
|
61
56
|
|
|
@@ -68,15 +63,12 @@ class AIDParser():
|
|
|
68
63
|
if endpoint_metadata is None:
|
|
69
64
|
raise ValueError(f"'EndpointMetadata' SMC not found in the provided '{aid_interface.id_short}' SMC.")
|
|
70
65
|
|
|
71
|
-
base: Property | None = find_by_semantic_id(
|
|
72
|
-
endpoint_metadata.value, "https://www.w3.org/2019/wot/td#baseURI"
|
|
73
|
-
)
|
|
66
|
+
base: Property | None = find_by_semantic_id(endpoint_metadata.value, "https://www.w3.org/2019/wot/td#baseURI")
|
|
74
67
|
if base is None:
|
|
75
68
|
raise ValueError("'base' Property not found in 'EndpointMetadata' SMC.")
|
|
76
69
|
|
|
77
70
|
return base.value
|
|
78
71
|
|
|
79
|
-
|
|
80
72
|
def parse_properties(self, aid_interface: SubmodelElementCollection) -> Dict[str, PropertyDetails]:
|
|
81
73
|
"""Find all first-level and nested properties in a provided SMC describing one interface in the AID.
|
|
82
74
|
Map each property (either top-level or nested) to the according 'href' attribute.
|
|
@@ -107,9 +99,16 @@ class AIDParser():
|
|
|
107
99
|
print(f"WARN: No first-level 'property' SMC not found in 'properties' SMC.")
|
|
108
100
|
return {}
|
|
109
101
|
|
|
110
|
-
def traverse_property(
|
|
111
|
-
|
|
112
|
-
|
|
102
|
+
def traverse_property(
|
|
103
|
+
smc: SubmodelElementCollection,
|
|
104
|
+
parent_path: str,
|
|
105
|
+
href: str,
|
|
106
|
+
key_path: List[str | int],
|
|
107
|
+
is_items=False,
|
|
108
|
+
idx=None,
|
|
109
|
+
is_top_level=False,
|
|
110
|
+
protocol_binding: IProtocolBinding = None,
|
|
111
|
+
):
|
|
113
112
|
# determine local key only if not top-level
|
|
114
113
|
if not is_top_level:
|
|
115
114
|
if is_items and idx is not None:
|
|
@@ -117,19 +116,17 @@ class AIDParser():
|
|
|
117
116
|
local_key = idx
|
|
118
117
|
else:
|
|
119
118
|
# is a nested "properties" property -> add value of "key" attribute or idShort to list of keys
|
|
120
|
-
key_prop = find_by_semantic_id(
|
|
121
|
-
smc.value, "https://admin-shell.io/idta/AssetInterfacesDescription/1/0/key"
|
|
122
|
-
)
|
|
119
|
+
key_prop = find_by_semantic_id(smc.value, "https://admin-shell.io/idta/AssetInterfacesDescription/1/0/key")
|
|
123
120
|
local_key = key_prop.value if key_prop else smc.id_short
|
|
124
121
|
new_key_path = key_path + [local_key]
|
|
125
122
|
else:
|
|
126
123
|
# TODO: use the key of the first-level property (or its idShort otherwise)
|
|
127
124
|
# is a top-level property
|
|
128
|
-
#key_prop: Property | None = find_by_semantic_id(
|
|
125
|
+
# key_prop: Property | None = find_by_semantic_id(
|
|
129
126
|
# smc.value, "https://admin-shell.io/idta/AssetInterfacesDescription/1/0/key"
|
|
130
|
-
#)
|
|
131
|
-
#local_key = key_prop.value if key_prop else smc.id_short
|
|
132
|
-
#new_key_path = [local_key]
|
|
127
|
+
# )
|
|
128
|
+
# local_key = key_prop.value if key_prop else smc.id_short
|
|
129
|
+
# new_key_path = [local_key]
|
|
133
130
|
|
|
134
131
|
new_key_path = key_path
|
|
135
132
|
# NOTE (Tom Gneuß, 2025-10-20)
|
|
@@ -173,16 +170,12 @@ class AIDParser():
|
|
|
173
170
|
traverse_property(nested, full_path, href, new_key_path)
|
|
174
171
|
|
|
175
172
|
# process all first-level properties
|
|
176
|
-
for fl_property in fl_properties:
|
|
177
|
-
forms: SubmodelElementCollection | None = find_by_semantic_id(
|
|
178
|
-
fl_property.value, "https://www.w3.org/2019/wot/td#hasForm"
|
|
179
|
-
)
|
|
173
|
+
for fl_property in fl_properties: # type: SubmodelElementCollection
|
|
174
|
+
forms: SubmodelElementCollection | None = find_by_semantic_id(fl_property.value, "https://www.w3.org/2019/wot/td#hasForm")
|
|
180
175
|
if forms is None:
|
|
181
176
|
raise ValueError(f"'forms' SMC not found in '{fl_property.id_short}' SMC.")
|
|
182
177
|
|
|
183
|
-
href: Property | None = find_by_semantic_id(
|
|
184
|
-
forms.value, "https://www.w3.org/2019/wot/hypermedia#hasTarget"
|
|
185
|
-
)
|
|
178
|
+
href: Property | None = find_by_semantic_id(forms.value, "https://www.w3.org/2019/wot/hypermedia#hasTarget")
|
|
186
179
|
if href is None:
|
|
187
180
|
raise ValueError("'href' Property not found in 'forms' SMC.")
|
|
188
181
|
|
|
@@ -199,22 +192,14 @@ class AIDParser():
|
|
|
199
192
|
protocol_binding: IProtocolBinding = None
|
|
200
193
|
|
|
201
194
|
# ... try HTTP ("htv_methodName" must be present)
|
|
202
|
-
htv_method_name: Property | None = find_by_semantic_id(
|
|
203
|
-
forms.value, "https://www.w3.org/2011/http#methodName"
|
|
204
|
-
)
|
|
195
|
+
htv_method_name: Property | None = find_by_semantic_id(forms.value, "https://www.w3.org/2011/http#methodName")
|
|
205
196
|
if htv_method_name is not None:
|
|
206
197
|
protocol_binding: HttpProtocolBinding = HttpProtocolBinding(htv_method_name.value, {})
|
|
207
|
-
htv_headers: SubmodelElementCollection | None = find_by_semantic_id(
|
|
208
|
-
forms.value, "https://www.w3.org/2011/http#headers"
|
|
209
|
-
)
|
|
198
|
+
htv_headers: SubmodelElementCollection | None = find_by_semantic_id(forms.value, "https://www.w3.org/2011/http#headers")
|
|
210
199
|
if htv_headers is not None:
|
|
211
|
-
for header in htv_headers.value:
|
|
212
|
-
htv_field_name: Property | None = find_by_semantic_id(
|
|
213
|
-
|
|
214
|
-
)
|
|
215
|
-
htv_field_value: Property | None = find_by_semantic_id(
|
|
216
|
-
header.value, "https://www.w3.org/2011/http#fieldValue"
|
|
217
|
-
)
|
|
200
|
+
for header in htv_headers.value: # type: SubmodelElementCollection
|
|
201
|
+
htv_field_name: Property | None = find_by_semantic_id(header.value, "https://www.w3.org/2011/http#fieldName")
|
|
202
|
+
htv_field_value: Property | None = find_by_semantic_id(header.value, "https://www.w3.org/2011/http#fieldValue")
|
|
218
203
|
protocol_binding.headers[htv_field_name.value] = htv_field_value.value
|
|
219
204
|
|
|
220
205
|
# TODO: the other protocols
|
|
@@ -230,12 +215,11 @@ class AIDParser():
|
|
|
230
215
|
is_items=False,
|
|
231
216
|
idx=None,
|
|
232
217
|
is_top_level=True,
|
|
233
|
-
protocol_binding=protocol_binding
|
|
218
|
+
protocol_binding=protocol_binding,
|
|
234
219
|
)
|
|
235
220
|
|
|
236
221
|
return mapping
|
|
237
222
|
|
|
238
|
-
|
|
239
223
|
def parse_security(self, aid_interface: SubmodelElementCollection) -> IAuthenticationDetails:
|
|
240
224
|
"""Extract the authentication details (EndpointMetadata.security) from the provided interface in the AID.
|
|
241
225
|
|
|
@@ -248,9 +232,7 @@ class AIDParser():
|
|
|
248
232
|
if endpoint_metadata is None:
|
|
249
233
|
raise ValueError(f"'EndpointMetadata' SMC not found in the provided '{aid_interface.id_short}' SMC.")
|
|
250
234
|
|
|
251
|
-
security: SubmodelElementList | None = find_by_semantic_id(
|
|
252
|
-
endpoint_metadata.value, "https://www.w3.org/2019/wot/td#hasSecurityConfiguration"
|
|
253
|
-
)
|
|
235
|
+
security: SubmodelElementList | None = find_by_semantic_id(endpoint_metadata.value, "https://www.w3.org/2019/wot/td#hasSecurityConfiguration")
|
|
254
236
|
if security is None:
|
|
255
237
|
raise ValueError("'security' SML not found in 'EndpointMetadata' SMC.")
|
|
256
238
|
|
|
@@ -269,16 +251,12 @@ class AIDParser():
|
|
|
269
251
|
raise ValueError("'securityDefinitions' SMC not found in 'EndpointMetadata' SMC.")
|
|
270
252
|
|
|
271
253
|
# find the security scheme SMC with the same idShort as mentioned in the reference "sc"
|
|
272
|
-
referenced_security: SubmodelElementCollection | None = find_by_id_short(
|
|
273
|
-
security_definitions.value, sc_idshort
|
|
274
|
-
)
|
|
254
|
+
referenced_security: SubmodelElementCollection | None = find_by_id_short(security_definitions.value, sc_idshort)
|
|
275
255
|
if referenced_security is None:
|
|
276
256
|
raise ValueError(f"Referenced security scheme '{sc_idshort}' SMC not found in 'securityDefinitions' SMC")
|
|
277
257
|
|
|
278
258
|
# get the name of the security scheme
|
|
279
|
-
scheme: Property | None = find_by_semantic_id(
|
|
280
|
-
referenced_security.value, "https://www.w3.org/2019/wot/security#SecurityScheme"
|
|
281
|
-
)
|
|
259
|
+
scheme: Property | None = find_by_semantic_id(referenced_security.value, "https://www.w3.org/2019/wot/security#SecurityScheme")
|
|
282
260
|
if scheme is None:
|
|
283
261
|
raise ValueError(f"'scheme' Property not found in referenced security scheme '{sc_idshort}' SMC.")
|
|
284
262
|
|
|
@@ -289,9 +267,7 @@ class AIDParser():
|
|
|
289
267
|
auth_details = NoAuthenticationDetails()
|
|
290
268
|
|
|
291
269
|
case "basic":
|
|
292
|
-
basic_sc_name: Property | None = find_by_semantic_id(
|
|
293
|
-
referenced_security.value, "https://www.w3.org/2019/wot/security#name"
|
|
294
|
-
)
|
|
270
|
+
basic_sc_name: Property | None = find_by_semantic_id(referenced_security.value, "https://www.w3.org/2019/wot/security#name")
|
|
295
271
|
if basic_sc_name is None:
|
|
296
272
|
raise ValueError("'name' Property not found in 'basic_sc' SMC")
|
|
297
273
|
|
|
@@ -0,0 +1,272 @@
|
|
|
1
|
+
"""Parser for Mapping Configurations in AIMC Submodel."""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
import logging
|
|
5
|
+
from dataclasses import dataclass, field
|
|
6
|
+
|
|
7
|
+
import basyx.aas.adapter.json
|
|
8
|
+
from basyx.aas import model
|
|
9
|
+
|
|
10
|
+
import aas_standard_parser.collection_helpers as ch
|
|
11
|
+
|
|
12
|
+
logger = logging.getLogger(__name__)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class SourceSinkRelation:
|
|
16
|
+
"""Class representing a source-sink relation in the mapping configuration."""
|
|
17
|
+
|
|
18
|
+
aid_submodel_id: str = field(metadata={"description": "Identifier of the AID submodel used by the source reference."})
|
|
19
|
+
source: model.ExternalReference = field(metadata={"description": "Reference to the source property in the AID submodel."})
|
|
20
|
+
sink: model.ExternalReference = field(metadata={"description": "Reference to the sink property in the target submodel."})
|
|
21
|
+
property_name: str = field(metadata={"description": "Name of the mapped property."})
|
|
22
|
+
source_parent_path: list[str] = field(metadata={"description": "List of idShorts representing the parent path of the reference."})
|
|
23
|
+
|
|
24
|
+
def source_as_dict(self) -> dict:
|
|
25
|
+
"""Convert the source reference to a dictionary.
|
|
26
|
+
|
|
27
|
+
:return: The source reference as a dictionary.
|
|
28
|
+
"""
|
|
29
|
+
dict_string = json.dumps(self.source, cls=basyx.aas.adapter.json.AASToJsonEncoder)
|
|
30
|
+
dict_string = dict_string.replace("GlobalReference", "Submodel").replace("FragmentReference", "SubmodelElementCollection")
|
|
31
|
+
return json.loads(dict_string)
|
|
32
|
+
|
|
33
|
+
def sink_as_dict(self) -> dict:
|
|
34
|
+
"""Convert the sink reference to a dictionary.
|
|
35
|
+
|
|
36
|
+
:return: The sink reference as a dictionary.
|
|
37
|
+
"""
|
|
38
|
+
return json.loads(json.dumps(self.sink, cls=basyx.aas.adapter.json.AASToJsonEncoder))
|
|
39
|
+
|
|
40
|
+
def get_source_parent_property_group_name(self) -> str:
|
|
41
|
+
"""Get the name of the parent property group from the source. Ignore 'properties' entries from the path."""
|
|
42
|
+
if len(self.source_parent_path) == 0:
|
|
43
|
+
return ""
|
|
44
|
+
|
|
45
|
+
return next((n for n in reversed(self.source_parent_path) if n != "properties"), "")
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
class MappingConfiguration:
|
|
49
|
+
"""Class representing a mapping configuration."""
|
|
50
|
+
|
|
51
|
+
interface_reference: model.ReferenceElement = field(metadata={"description": "Reference to the interface in the AID submodel."})
|
|
52
|
+
aid_submodel_id: str = field(metadata={"description": "Identifier of the AID submodel used by the interface reference."})
|
|
53
|
+
source_sink_relations: list[SourceSinkRelation] = field(metadata={"description": "List of source-sink relations in the mapping configuration."})
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
class MappingConfigurations:
|
|
57
|
+
"""Class representing mapping configurations from AIMC submodel."""
|
|
58
|
+
|
|
59
|
+
configurations: list[MappingConfiguration] = field(metadata={"description": "List of mapping configurations."})
|
|
60
|
+
aid_submodel_ids: list[str] = field(metadata={"description": "List of AID submodel IDs used in the mapping configurations."})
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def get_mapping_configuration_root_element(aimc_submodel: model.Submodel) -> model.SubmodelElementCollection | None:
|
|
64
|
+
"""Get the mapping configuration root submodel element collection from the AIMC submodel.
|
|
65
|
+
|
|
66
|
+
:param aimc_submodel: The AIMC submodel to extract the mapping configuration root from.
|
|
67
|
+
:return: The mapping configuration root submodel element collection or None if not found.
|
|
68
|
+
"""
|
|
69
|
+
# check if AIMC submodel is None
|
|
70
|
+
if aimc_submodel is None:
|
|
71
|
+
logger.error("AIMC submodel is None.")
|
|
72
|
+
return None
|
|
73
|
+
|
|
74
|
+
# get 'MappingConfigurations' element list by its semantic ID
|
|
75
|
+
mc_element = ch.find_by_in_semantic_id(aimc_submodel.submodel_element, "idta/AssetInterfacesMappingConfiguration/1/0/MappingConfigurations")
|
|
76
|
+
if mc_element is None:
|
|
77
|
+
logger.error("'MappingConfigurations' element list not found in AIMC submodel.")
|
|
78
|
+
|
|
79
|
+
return mc_element
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def get_mapping_configuration_elements(aimc_submodel: model.Submodel) -> list[model.SubmodelElementCollection] | None:
|
|
83
|
+
"""Get all mapping configurations from the AIMC submodel.
|
|
84
|
+
|
|
85
|
+
:param aimc_submodel: The AIMC submodel to extract mapping configurations from.
|
|
86
|
+
:return: A dictionary containing all mapping configurations.
|
|
87
|
+
"""
|
|
88
|
+
# check if AIMC submodel is None
|
|
89
|
+
if aimc_submodel is None:
|
|
90
|
+
logger.error("AIMC submodel is None.")
|
|
91
|
+
return None
|
|
92
|
+
|
|
93
|
+
# get mapping configuration root element
|
|
94
|
+
root_element = get_mapping_configuration_root_element(aimc_submodel)
|
|
95
|
+
if root_element is None:
|
|
96
|
+
return None
|
|
97
|
+
|
|
98
|
+
# find all 'MappingConfiguration' elements by their semantic ID
|
|
99
|
+
mapping_configurations = ch.find_all_by_in_semantic_id(root_element.value, "idta/AssetInterfacesMappingConfiguration/1/0/MappingConfiguration")
|
|
100
|
+
|
|
101
|
+
logger.debug(f"Found {len(mapping_configurations)} mapping configuration elements in AIMC submodel.")
|
|
102
|
+
|
|
103
|
+
return mapping_configurations
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def parse_mapping_configurations(aimc_submodel: model.Submodel) -> MappingConfigurations:
|
|
107
|
+
"""Parse all mapping configurations in the AIMC submodel.
|
|
108
|
+
|
|
109
|
+
:param aimc_submodel: The AIMC submodel to parse mapping configurations from.
|
|
110
|
+
:return: A list of parsed mapping configurations.
|
|
111
|
+
"""
|
|
112
|
+
logger.info("Parse mapping configurations from AIMC submodel.")
|
|
113
|
+
|
|
114
|
+
mapping_configurations: list[MappingConfiguration] = []
|
|
115
|
+
|
|
116
|
+
# get all mapping configuration elements
|
|
117
|
+
mapping_configurations_elements = get_mapping_configuration_elements(aimc_submodel)
|
|
118
|
+
if mapping_configurations_elements is None:
|
|
119
|
+
logger.error("No mapping configuration elements found in AIMC submodel.")
|
|
120
|
+
return mapping_configurations_elements
|
|
121
|
+
|
|
122
|
+
# parse each mapping configuration element
|
|
123
|
+
for mc_element in mapping_configurations_elements:
|
|
124
|
+
mc = parse_mapping_configuration_element(mc_element)
|
|
125
|
+
if mc is not None:
|
|
126
|
+
mapping_configurations.append(mc)
|
|
127
|
+
|
|
128
|
+
logger.debug(f"Parsed {len(mapping_configurations)} mapping configurations.")
|
|
129
|
+
|
|
130
|
+
mcs = MappingConfigurations()
|
|
131
|
+
mcs.configurations = mapping_configurations
|
|
132
|
+
# add all unique AID submodel IDs from all mapping configurations
|
|
133
|
+
mcs.aid_submodel_ids = list({mc.aid_submodel_id for mc in mapping_configurations})
|
|
134
|
+
|
|
135
|
+
logger.debug(f"Found {len(mcs.aid_submodel_ids)} unique AID submodel IDs in mapping configurations.")
|
|
136
|
+
logger.debug(f"Found {len(mcs.configurations)} mapping configurations in AIMC submodel.")
|
|
137
|
+
|
|
138
|
+
return mcs
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
def parse_mapping_configuration_element(
|
|
142
|
+
mapping_configuration_element: model.SubmodelElementCollection,
|
|
143
|
+
) -> MappingConfiguration | None:
|
|
144
|
+
"""Parse a mapping configuration element.
|
|
145
|
+
|
|
146
|
+
:param mapping_configuration_element: The mapping configuration element to parse.
|
|
147
|
+
:return: The parsed mapping configuration or None if parsing failed.
|
|
148
|
+
"""
|
|
149
|
+
if mapping_configuration_element is None:
|
|
150
|
+
logger.error("Mapping configuration element is None.")
|
|
151
|
+
return None
|
|
152
|
+
|
|
153
|
+
logger.debug(f"Parse mapping configuration '{mapping_configuration_element}'")
|
|
154
|
+
|
|
155
|
+
# get interface reference element
|
|
156
|
+
interface_reference = _get_interface_reference_element(mapping_configuration_element)
|
|
157
|
+
if interface_reference is None:
|
|
158
|
+
return None
|
|
159
|
+
|
|
160
|
+
source_sink_relations = _generate_source_sink_relations(mapping_configuration_element)
|
|
161
|
+
|
|
162
|
+
if len(source_sink_relations) == 0:
|
|
163
|
+
logger.error(f"No source-sink relations found in mapping configuration '{mapping_configuration_element.id_short}'.")
|
|
164
|
+
return None
|
|
165
|
+
|
|
166
|
+
# check if all relations have the same AID submodel
|
|
167
|
+
aid_submodel_ids = list({source_sink_relation.aid_submodel_id for source_sink_relation in source_sink_relations})
|
|
168
|
+
|
|
169
|
+
if len(aid_submodel_ids) != 1:
|
|
170
|
+
logger.error(
|
|
171
|
+
f"Multiple AID submodel IDs found in mapping configuration '{mapping_configuration_element.id_short}': {aid_submodel_ids}. Expected exactly one AID submodel ID."
|
|
172
|
+
)
|
|
173
|
+
return None
|
|
174
|
+
|
|
175
|
+
mc = MappingConfiguration()
|
|
176
|
+
mc.interface_reference = interface_reference
|
|
177
|
+
mc.source_sink_relations = source_sink_relations
|
|
178
|
+
# add all unique AID submodel IDs from source-sink relations
|
|
179
|
+
mc.aid_submodel_id = aid_submodel_ids[0]
|
|
180
|
+
return mc
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
def _get_interface_reference_element(
|
|
184
|
+
mapping_configuration_element: model.SubmodelElementCollection,
|
|
185
|
+
) -> model.ReferenceElement | None:
|
|
186
|
+
"""Get the interface reference ID from the mapping configuration element.
|
|
187
|
+
|
|
188
|
+
:param mapping_configuration_element: The mapping configuration element to extract the interface reference ID from.
|
|
189
|
+
:return: The interface reference ID or None if not found.
|
|
190
|
+
"""
|
|
191
|
+
logger.debug(f"Get 'InterfaceReference' from mapping configuration '{mapping_configuration_element}'.")
|
|
192
|
+
|
|
193
|
+
interface_ref: model.ReferenceElement = ch.find_by_in_semantic_id(
|
|
194
|
+
mapping_configuration_element, "idta/AssetInterfacesMappingConfiguration/1/0/InterfaceReference"
|
|
195
|
+
)
|
|
196
|
+
|
|
197
|
+
if interface_ref is None or not isinstance(interface_ref, model.ReferenceElement):
|
|
198
|
+
logger.error(f"'InterfaceReference' not found in mapping configuration '{mapping_configuration_element.id_short}'.")
|
|
199
|
+
return None
|
|
200
|
+
|
|
201
|
+
if interface_ref.value is None or len(interface_ref.value.key) == 0:
|
|
202
|
+
logger.error(f"'InterfaceReference' has no value in mapping configuration '{mapping_configuration_element.id_short}'.")
|
|
203
|
+
return None
|
|
204
|
+
|
|
205
|
+
return interface_ref
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
def _generate_source_sink_relations(mapping_configuration_element: model.SubmodelElementCollection) -> list[SourceSinkRelation]:
|
|
209
|
+
source_sink_relations: list[SourceSinkRelation] = []
|
|
210
|
+
|
|
211
|
+
logger.debug(f"Get 'MappingSourceSinkRelations' from mapping configuration '{mapping_configuration_element}'.")
|
|
212
|
+
|
|
213
|
+
relations_list: model.SubmodelElementList = ch.find_by_in_semantic_id(
|
|
214
|
+
mapping_configuration_element, "/idta/AssetInterfacesMappingConfiguration/1/0/MappingSourceSinkRelations"
|
|
215
|
+
)
|
|
216
|
+
|
|
217
|
+
if relations_list is None or not isinstance(relations_list, model.SubmodelElementList):
|
|
218
|
+
logger.error(f"'MappingSourceSinkRelations' not found in mapping configuration '{mapping_configuration_element.id_short}'.")
|
|
219
|
+
return source_sink_relations
|
|
220
|
+
|
|
221
|
+
for source_sink_relation in relations_list.value:
|
|
222
|
+
logger.debug(f"Parse source-sink relation '{source_sink_relation}'.")
|
|
223
|
+
|
|
224
|
+
if not isinstance(source_sink_relation, model.RelationshipElement):
|
|
225
|
+
logger.warning(f"'{source_sink_relation}' is not a RelationshipElement")
|
|
226
|
+
continue
|
|
227
|
+
|
|
228
|
+
if source_sink_relation.first is None or len(source_sink_relation.first.key) == 0:
|
|
229
|
+
logger.warning(f"'first' reference is missing in RelationshipElement '{source_sink_relation.id_short}'")
|
|
230
|
+
continue
|
|
231
|
+
|
|
232
|
+
if source_sink_relation.second is None or len(source_sink_relation.second.key) == 0:
|
|
233
|
+
logger.warning(f"'second' reference is missing in RelationshipElement '{source_sink_relation.id_short}'")
|
|
234
|
+
continue
|
|
235
|
+
|
|
236
|
+
source_ref = source_sink_relation.first
|
|
237
|
+
|
|
238
|
+
global_ref = next((key for key in source_ref.key if key.type == model.KeyTypes.GLOBAL_REFERENCE), None)
|
|
239
|
+
|
|
240
|
+
if global_ref is None:
|
|
241
|
+
logger.warning(f"No GLOBAL_REFERENCE key found in 'first' reference of RelationshipElement '{source_sink_relation.id_short}'")
|
|
242
|
+
continue
|
|
243
|
+
|
|
244
|
+
last_fragment_ref = next(
|
|
245
|
+
(key for key in reversed(source_ref.key) if key.type == model.KeyTypes.FRAGMENT_REFERENCE),
|
|
246
|
+
None,
|
|
247
|
+
)
|
|
248
|
+
|
|
249
|
+
if last_fragment_ref is None:
|
|
250
|
+
logger.warning(f"No FRAGMENT_REFERENCE key found in 'first' reference of RelationshipElement '{source_sink_relation.id_short}'")
|
|
251
|
+
continue
|
|
252
|
+
|
|
253
|
+
relation = SourceSinkRelation()
|
|
254
|
+
relation.source = source_sink_relation.first
|
|
255
|
+
relation.sink = source_sink_relation.second
|
|
256
|
+
relation.aid_submodel_id = global_ref.value
|
|
257
|
+
relation.property_name = last_fragment_ref.value
|
|
258
|
+
relation.source_parent_path = _get_reference_parent_path(source_ref)
|
|
259
|
+
|
|
260
|
+
source_sink_relations.append(relation)
|
|
261
|
+
|
|
262
|
+
return source_sink_relations
|
|
263
|
+
|
|
264
|
+
|
|
265
|
+
def _get_reference_parent_path(reference: model.ExternalReference) -> list[str]:
|
|
266
|
+
"""Get the parent path of a reference as a list of idShorts.
|
|
267
|
+
|
|
268
|
+
:param reference: The reference to extract the parent path from.
|
|
269
|
+
:return: A list of idShorts representing the parent path.
|
|
270
|
+
"""
|
|
271
|
+
# Exclude the last key which is the actual element
|
|
272
|
+
return [key.value for key in reference.key[:-1] if key.type == model.KeyTypes.FRAGMENT_REFERENCE]
|
{aas_standard_parser-0.1.7 → aas_standard_parser-0.1.8}/aas_standard_parser/collection_helpers.py
RENAMED
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
from basyx.aas.model import NamespaceSet, SubmodelElement, ExternalReference, Reference, Key, KeyTypes
|
|
2
1
|
from typing import List
|
|
3
2
|
|
|
3
|
+
from basyx.aas.model import ExternalReference, Key, KeyTypes, NamespaceSet, Reference, SubmodelElement
|
|
4
|
+
|
|
4
5
|
|
|
5
6
|
def find_all_by_semantic_id(parent: NamespaceSet[SubmodelElement], semantic_id_value: str) -> List[SubmodelElement]:
|
|
6
7
|
"""Find all SubmodelElements having a specific Semantic ID.
|
|
@@ -9,15 +10,8 @@ def find_all_by_semantic_id(parent: NamespaceSet[SubmodelElement], semantic_id_v
|
|
|
9
10
|
:param semantic_id_value: The semantic ID value to search for.
|
|
10
11
|
:return: The found SubmodelElement(s) or an empty list if not found.
|
|
11
12
|
"""
|
|
12
|
-
reference: Reference = ExternalReference(
|
|
13
|
-
|
|
14
|
-
type_=KeyTypes.GLOBAL_REFERENCE,
|
|
15
|
-
value=semantic_id_value
|
|
16
|
-
),)
|
|
17
|
-
)
|
|
18
|
-
found_elements: list[SubmodelElement] = [
|
|
19
|
-
element for element in parent if element.semantic_id.__eq__(reference)
|
|
20
|
-
]
|
|
13
|
+
reference: Reference = ExternalReference((Key(type_=KeyTypes.GLOBAL_REFERENCE, value=semantic_id_value),))
|
|
14
|
+
found_elements: list[SubmodelElement] = [element for element in parent if element.semantic_id.__eq__(reference)]
|
|
21
15
|
return found_elements
|
|
22
16
|
|
|
23
17
|
|
|
@@ -29,14 +23,8 @@ def find_by_semantic_id(parent: NamespaceSet[SubmodelElement], semantic_id_value
|
|
|
29
23
|
:return: The first found SubmodelElement, or None if not found.
|
|
30
24
|
@rtype: object
|
|
31
25
|
"""
|
|
32
|
-
|
|
33
26
|
# create a Reference that acts like the to-be-matched semanticId
|
|
34
|
-
reference: Reference = ExternalReference(
|
|
35
|
-
(Key(
|
|
36
|
-
type_=KeyTypes.GLOBAL_REFERENCE,
|
|
37
|
-
value=semantic_id_value
|
|
38
|
-
),)
|
|
39
|
-
)
|
|
27
|
+
reference: Reference = ExternalReference((Key(type_=KeyTypes.GLOBAL_REFERENCE, value=semantic_id_value),))
|
|
40
28
|
|
|
41
29
|
# check if the constructed Reference appears as semanticId of the child elements
|
|
42
30
|
for element in parent:
|
|
@@ -45,6 +33,26 @@ def find_by_semantic_id(parent: NamespaceSet[SubmodelElement], semantic_id_value
|
|
|
45
33
|
return None
|
|
46
34
|
|
|
47
35
|
|
|
36
|
+
def find_by_in_semantic_id(parent: NamespaceSet[SubmodelElement], semantic_id_part: str) -> SubmodelElement | None:
|
|
37
|
+
"""Find a SubmodelElement by checking if its semantic ID contains the given value.
|
|
38
|
+
|
|
39
|
+
:param parent: The NamespaceSet to search within.
|
|
40
|
+
:param semantic_id_value: The semantic ID value to search for.
|
|
41
|
+
:return: The first found SubmodelElement, or None if not found.
|
|
42
|
+
"""
|
|
43
|
+
return next((el for el in parent if any(semantic_id_part in key.value for key in el.semantic_id.key)), None)
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def find_all_by_in_semantic_id(parent: NamespaceSet[SubmodelElement], semantic_id_part: str) -> SubmodelElement | None:
|
|
47
|
+
"""Find a SubmodelElement by checking if its semantic ID contains the given value.
|
|
48
|
+
|
|
49
|
+
:param parent: The NamespaceSet to search within.
|
|
50
|
+
:param semantic_id_value: The semantic ID value to search for.
|
|
51
|
+
:return: The first found SubmodelElement, or None if not found.
|
|
52
|
+
"""
|
|
53
|
+
return [el for el in parent if any(semantic_id_part in key.value for key in el.semantic_id.key)]
|
|
54
|
+
|
|
55
|
+
|
|
48
56
|
def find_by_id_short(parent: NamespaceSet[SubmodelElement], id_short_value: str) -> SubmodelElement | None:
|
|
49
57
|
"""Find a SubmodelElement by its idShort.
|
|
50
58
|
|
|
@@ -79,10 +87,5 @@ def contains_supplemental_semantic_id(element: SubmodelElement, semantic_id_valu
|
|
|
79
87
|
:param semantic_id_value: The supplemental semantic ID value to search for.
|
|
80
88
|
:return: True if the element contains the supplemental semantic ID, False otherwise.
|
|
81
89
|
"""
|
|
82
|
-
reference: Reference = ExternalReference(
|
|
83
|
-
(Key(
|
|
84
|
-
type_=KeyTypes.GLOBAL_REFERENCE,
|
|
85
|
-
value=semantic_id_value
|
|
86
|
-
),)
|
|
87
|
-
)
|
|
90
|
+
reference: Reference = ExternalReference((Key(type_=KeyTypes.GLOBAL_REFERENCE, value=semantic_id_value),))
|
|
88
91
|
return element.supplemental_semantic_id.__contains__(reference)
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
|
|
3
|
+
import aas_standard_parser.aimc_parser as aimc_parser
|
|
4
|
+
from aas_standard_parser.utils import create_submodel_from_file
|
|
5
|
+
|
|
6
|
+
logger = logging.getLogger(__name__)
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def start() -> None:
|
|
10
|
+
logger.info("Demo process started.")
|
|
11
|
+
|
|
12
|
+
aimc_submodel = create_submodel_from_file("tests/test_data/aimc_submodel.json")
|
|
13
|
+
|
|
14
|
+
tmp = aimc_parser.parse_mapping_configurations(aimc_submodel)
|
|
15
|
+
|
|
16
|
+
tmp2 = tmp.configurations[0].source_sink_relations[0].get_source_parent_property_group_name()
|
|
17
|
+
|
|
18
|
+
logger.info("Demo process finished.")
|