|
1 | | -import json |
2 | | -import aas_core3.types as aas_types |
3 | | -import aas_core3.jsonization as aas_jsonization |
4 | | -from basyx.object_store import ObjectStore |
5 | | -from basyx.aasx import AASXWriter, AASXReader, DictSupplementaryFileContainer |
6 | | -import pyecma376_2 # The base library for Open Packaging Specifications. We will use the OPCCoreProperties class. |
| 1 | +#!/usr/bin/env python3 |
| 2 | +""" |
| 3 | +Tutorial for exporting Asset Administration Shells with related objects and auxiliary files to AASX package files, using |
| 4 | +the :mod:`~basyx.aasx` module from the Eclipse BaSyx Python Framework. |
| 5 | +
|
| 6 | +""" |
| 7 | + |
7 | 8 | import datetime |
8 | 9 | from pathlib import Path # Used for easier handling of auxiliary file's local path |
9 | 10 |
|
10 | | -Referencetype = aas_types.ReferenceTypes("ModelReference") |
| 11 | +import pyecma376_2 # The base library for Open Packaging Specifications. We will use the OPCCoreProperties class. |
| 12 | +from aas_core3 import types as model |
| 13 | +from basyx.aasx import AASXWriter, AASXReader, DictSupplementaryFileContainer |
| 14 | +from basyx.object_store import ObjectStore |
| 15 | + |
| 16 | +# step 1: Setting up an SupplementaryFileContainer and AAS & submodel with File objects |
| 17 | +# step 2: Writing AAS objects and auxiliary files to an AASX package |
| 18 | +# step 3: Reading AAS objects and auxiliary files from an AASX package |
| 19 | + |
| 20 | + |
| 21 | +######################################################################################## |
| 22 | +# Step 1: Setting up a SupplementaryFileContainer and AAS & submodel with File objects # |
| 23 | +######################################################################################## |
11 | 24 |
|
12 | | -key_types = aas_types.KeyTypes("Submodel") |
| 25 | +# Let's first create a basic Asset Administration Shell with a simple submodel. |
| 26 | +# See `tutorial_create_simple_aas.py` for more details. |
| 27 | +submodel = model.Submodel(id='https://acplt.org/Submodel', submodel_elements=[]) |
13 | 28 |
|
14 | | -key = aas_types.Key(value="some-unique-global-identifier", type=key_types) |
| 29 | +key_types = model.KeyTypes("Submodel") |
| 30 | +Referencetype = model.ReferenceTypes("ModelReference") |
| 31 | +key = model.Key(value='https://acplt.org/Submodel', type=key_types) |
| 32 | +reference = model.Reference(type=Referencetype, keys=[key]) |
15 | 33 |
|
16 | | -reference = aas_types.Reference(type=Referencetype, keys=[key]) |
| 34 | +aas = model.AssetAdministrationShell(id='https://acplt.org/Simple_AAS', |
| 35 | + asset_information=model.AssetInformation( |
| 36 | + asset_kind=model.AssetKind.TYPE), |
| 37 | + submodels=[reference]) |
17 | 38 |
|
18 | | -submodel = aas_types.Submodel( |
19 | | - id="some-unique-global-identifier", |
20 | | - submodel_elements=[ |
21 | | - aas_types.Property( |
22 | | - id_short="some_property", |
23 | | - value_type=aas_types.DataTypeDefXSD.INT, |
24 | | - value="1984", |
25 | | - semantic_id=reference |
26 | | - ) |
27 | | - ] |
| 39 | +# Another submodel, which is not related to the AAS: |
| 40 | +unrelated_submodel = model.Submodel( |
| 41 | + id='https://acplt.org/Unrelated_Submodel' |
28 | 42 | ) |
29 | 43 |
|
| 44 | +# We add these objects to an ObjectStore for easy retrieval by id. |
| 45 | +object_store: ObjectStore = ObjectStore([unrelated_submodel, submodel, aas]) |
| 46 | + |
| 47 | + |
| 48 | +# For holding auxiliary files, which will eventually be added to an AASX package, we need a SupplementaryFileContainer. |
| 49 | +# The `DictSupplementaryFileContainer` is a simple SupplementaryFileContainer that stores the files' contents in simple |
| 50 | +# bytes objects in memory. |
30 | 51 | file_store = DictSupplementaryFileContainer() |
31 | 52 |
|
| 53 | +# Now, we add an example file from our local filesystem to the SupplementaryFileContainer. |
| 54 | +# |
| 55 | +# For this purpose, we need to specify the file's name in the SupplementaryFileContainer. This name is used to reference |
| 56 | +# the file in the container and will later be used as the filename in the AASX package file. Thus, this file must begin |
| 57 | +# with a slash and should begin with `/aasx/`. Here, we use `/aasx/suppl/MyExampleFile.pdf`. The |
| 58 | +# SupplementaryFileContainer's add_file() method will ensure uniqueness of the name by adding a suffix if an equally |
| 59 | +# named file with different contents exists. The final name is returned. |
| 60 | +# |
| 61 | +# In addition, we need to specify the MIME type of the file, which is later used in the metadata of the AASX package. |
| 62 | +# (This is actually a requirement of the underlying Open Packaging Conventions (ECMA376-2) format, which imposes the |
| 63 | +# specification of the MIME type ("content type") of every single file within the package.) |
| 64 | + |
32 | 65 | with open(Path(__file__).parent / 'data' / 'TestFile.pdf', 'rb') as f: |
33 | 66 | actual_file_name = file_store.add_file("/aasx/suppl/MyExampleFile.pdf", f, "application/pdf") |
34 | 67 |
|
35 | | -if submodel.submodel_elements is not None: |
36 | | - submodel.submodel_elements.append(aas_types.File(id_short="documentationFile", |
37 | | - content_type="application/pdf", |
38 | | - value=actual_file_name)) |
39 | 68 |
|
40 | | -aas = aas_types.AssetAdministrationShell(id="urn:x-test:aas1", |
41 | | - asset_information=aas_types.AssetInformation( |
42 | | - asset_kind=aas_types.AssetKind.TYPE), |
43 | | - submodels=[reference]) |
| 69 | +# With the actual_file_name in the SupplementaryFileContainer, we can create a reference to that file in our AAS |
| 70 | +# Submodel, in the form of a `File` object: |
44 | 71 |
|
45 | | -obj_store: ObjectStore = ObjectStore() |
46 | | -obj_store.add(aas) |
47 | | -obj_store.add(submodel) |
| 72 | +model.File(id_short="documentationFile", content_type="application/pdf", value=actual_file_name) |
48 | 73 |
|
| 74 | +file = model.File(id_short="documentationFile", content_type="application/pdf", value=actual_file_name) |
| 75 | +if submodel.submodel_elements is not None: |
| 76 | + submodel.submodel_elements.append(file) |
49 | 77 |
|
50 | | -# Serialize to a JSON-able mapping |
51 | | -jsonable = aas_jsonization.to_jsonable(submodel) |
52 | 78 |
|
| 79 | +###################################################################### |
| 80 | +# Step 2: Writing AAS objects and auxiliary files to an AASX package # |
| 81 | +###################################################################### |
53 | 82 |
|
54 | | -meta_data = pyecma376_2.OPCCoreProperties() |
55 | | -meta_data.creator = "Chair of Process Control Engineering" |
56 | | -meta_data.created = datetime.datetime.now() |
| 83 | +# After setting everything up in Step 1, writing the AAS, including the Submodel objects and the auxiliary file |
| 84 | +# to an AASX package is simple. |
57 | 85 |
|
| 86 | +# Open an AASXWriter with the destination file name and use it as a context handler, to make sure it is properly closed |
| 87 | +# after doing the modifications: |
58 | 88 | with AASXWriter("./MyAASXPackage.aasx") as writer: |
59 | | - writer.write_aas(aas_ids=["urn:x-test:aas1"], |
60 | | - object_store=obj_store, |
61 | | - file_store=file_store, |
62 | | - write_json=False) |
| 89 | + # Write the AAS and everything belonging to it to the AASX package |
| 90 | + # The `write_aas()` method will automatically fetch the AAS object with the given id |
| 91 | + # and all referenced Submodel objects from the ObjectStore. It will also scan every object for |
| 92 | + # semanticIds referencing ConceptDescription, fetch them from the ObjectStore, and scan all sbmodels for `File` |
| 93 | + # objects and fetch the referenced auxiliary files from the SupplementaryFileContainer. |
| 94 | + # In order to add more than one AAS to the package, we can simply add more Identifiers to the `aas_ids` list. |
| 95 | + # |
| 96 | + # ATTENTION: As of Version 3.0 RC01 of Details of the Asset Administration Shell, it is no longer valid to add more |
| 97 | + # than one "aas-spec" part (JSON/XML part with AAS objects) to an AASX package. Thus, `write_aas` MUST |
| 98 | + # only be called once per AASX package! |
| 99 | + writer.write_aas(aas_ids=['https://acplt.org/Simple_AAS'], |
| 100 | + object_store=object_store, |
| 101 | + file_store=file_store) |
| 102 | + |
| 103 | + # Alternatively, we can use a more low-level interface to add a JSON/XML part with any Identifiable objects (not |
| 104 | + # only an AAS and referenced objects) in the AASX package manually. `write_aas_objects()` will also take care of |
| 105 | + # adding referenced auxiliary files by scanning all submodel objects for contained `File` objects. |
| 106 | + # |
| 107 | + # ATTENTION: As of Version 3.0 RC01 of Details of the Asset Administration Shell, it is no longer valid to add more |
| 108 | + # than one "aas-spec" part (JSON/XML part with AAS objects) to an AASX package. Thus, `write_all_aas_objects` SHALL |
| 109 | + # only be used as an alternative to `write_aas` and SHALL only be called once! |
| 110 | + objects_to_be_written: ObjectStore[model.Identifiable] = ObjectStore([unrelated_submodel]) |
| 111 | + writer.write_all_aas_objects(part_name="/aasx/my_aas_part.xml", |
| 112 | + objects=objects_to_be_written, |
| 113 | + file_store=file_store) |
| 114 | + |
| 115 | + # We can also add a thumbnail image to the package (using `writer.write_thumbnail()`) or add metadata: |
| 116 | + meta_data = pyecma376_2.OPCCoreProperties() |
| 117 | + meta_data.creator = "Chair of Process Control Engineering" |
| 118 | + meta_data.created = datetime.datetime.now() |
63 | 119 | writer.write_core_properties(meta_data) |
64 | 120 |
|
| 121 | +# Closing the AASXWriter will write some required parts with relationships and MIME types to the AASX package file and |
| 122 | +# close the package file afterward. Make sure, to always call `AASXWriter.close()` or use the AASXWriter in a `with` |
| 123 | +# statement (as a context manager) as shown above. |
| 124 | + |
| 125 | + |
| 126 | +######################################################################## |
| 127 | +# Step 3: Reading AAS objects and auxiliary files from an AASX package # |
| 128 | +######################################################################## |
| 129 | + |
| 130 | +# Let's read the AASX package file, we have just written. |
| 131 | +# We'll use a fresh ObjectStore and SupplementaryFileContainer to read AAS objects and auxiliary files into. |
65 | 132 | new_object_store: ObjectStore = ObjectStore() |
66 | 133 | new_file_store = DictSupplementaryFileContainer() |
67 | 134 |
|
68 | | -with AASXReader("./MyAASXPackage.aasx") as reader: |
| 135 | +# Again, we need to use the AASXReader as a context manager (or call `.close()` in the end) to make sure the AASX |
| 136 | +# package file is properly closed when we are finished. |
| 137 | +with AASXReader("MyAASXPackage.aasx") as reader: |
69 | 138 | # Read all contained AAS objects and all referenced auxiliary files |
70 | 139 | reader.read_into(object_store=new_object_store, |
71 | 140 | file_store=new_file_store) |
72 | 141 |
|
73 | | -print(new_object_store.__len__()) |
74 | | -for item in file_store.__iter__(): |
75 | | - print(item) |
| 142 | + # We can also read the metadata |
| 143 | + new_meta_data = reader.get_core_properties() |
| 144 | + |
| 145 | + # We could also read the thumbnail image, using `reader.get_thumbnail()` |
| 146 | + |
76 | 147 |
|
77 | | -for item in new_file_store.__iter__(): |
78 | | - print(item) |
| 148 | +# Some quick checks to make sure, reading worked as expected |
| 149 | +assert 'https://acplt.org/Submodel' in new_object_store |
| 150 | +assert actual_file_name in new_file_store |
| 151 | +assert new_meta_data.creator == "Chair of Process Control Engineering" |
0 commit comments