Skip to content

Commit 96de4f9

Browse files
committed
Add CDD/EClass identifiers as DictionaryReferences, fix some AAS parsing, improve mapping to the DPP metamodel and also save all node values to file.
1 parent 4f9d96d commit 96de4f9

File tree

2 files changed

+91
-27
lines changed

2 files changed

+91
-27
lines changed

I4AASNodeManager.cs

Lines changed: 89 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
namespace AdminShell
33
{
44
using AAS2Nodeset;
5+
using Newtonsoft.Json;
56
using Opc.Ua;
67
using Opc.Ua.Export;
78
using Opc.Ua.Server;
@@ -98,6 +99,39 @@ public void SaveNodestateCollectionAsNodeSet2(string filePath, NodeStateCollecti
9899
nodeSet.Write(stream.BaseStream);
99100
}
100101
}
102+
}
103+
104+
private void SaveValues(string filePath, NodeStateCollection nodesToExport)
105+
{
106+
if (nodesToExport.Count > 0)
107+
{
108+
// first remove duplicates
109+
List<NodeState> nodes = nodesToExport.ToList();
110+
for (int i = 0; i < nodes.Count; i++)
111+
{
112+
// remove all duplicate nodes from the List, based on node ID
113+
for (int j = i + 1; j < nodes.Count; j++)
114+
{
115+
if (nodes[i].NodeId == nodes[j].NodeId)
116+
{
117+
nodes.RemoveAt(j);
118+
j--;
119+
}
120+
}
121+
}
122+
123+
// capture name-value pairs of all variables in the NodeSet
124+
Dictionary<string, string> values = new();
125+
foreach (NodeState node in nodes)
126+
{
127+
if (node is BaseDataVariableState variable)
128+
{
129+
values.Add(variable.NodeId.ToString().Replace("ns=2", "ns=1"), variable.Value?.ToString() ?? string.Empty);
130+
}
131+
}
132+
133+
System.IO.File.WriteAllText(filePath.Replace(".NodeSet2.xml", ".values.json"), JsonConvert.SerializeObject(values, Formatting.Indented));
134+
}
101135
}
102136

103137
public override void CreateAddressSpace(IDictionary<NodeId, IList<IReference>> externalReferences)
@@ -147,6 +181,7 @@ public override void CreateAddressSpace(IDictionary<NodeId, IList<IReference>> e
147181
// export nodeset XML
148182
Console.WriteLine($"Writing {nodesToExport.Count} nodes to file {c_exportFilename}!");
149183
SaveNodestateCollectionAsNodeSet2(c_exportFilename, nodesToExport);
184+
SaveValues(c_exportFilename, nodesToExport);
150185

151186
Console.WriteLine();
152187
}
@@ -160,8 +195,8 @@ public override void CreateAddressSpace(IDictionary<NodeId, IList<IReference>> e
160195
AddReverseReferences(externalReferences);
161196
base.CreateAddressSpace(externalReferences);
162197
}
163-
}
164-
198+
}
199+
165200
public void CreateObjects(AssetAdministrationShellEnvironment env)
166201
{
167202
// create DPP variables and data element array
@@ -221,12 +256,39 @@ private void CreateDataElement(NodeState parent, SubmodelElement sme)
221256

222257
FolderState collectionFolder = CreateFolder(parent, sme.IdShort);
223258

259+
if ((sme.SemanticId.Keys != null) && (sme.SemanticId.Keys.Count > 0))
260+
{
261+
CreateStringVariable(collectionFolder, "DictionaryReference", sme.SemanticId.Keys[0].Value);
262+
}
263+
224264
foreach (SubmodelElement smeChild in collection.Value)
225265
{
226266
CreateDataElement(collectionFolder, smeChild);
227267
}
228268
}
229269
}
270+
else if (sme is SubmodelElementList list)
271+
{
272+
if (list.Value != null)
273+
{
274+
if (string.IsNullOrEmpty(sme.IdShort) && (sme.SemanticId.Keys != null) && (sme.SemanticId.Keys.Count > 0))
275+
{
276+
sme.IdShort = sme.SemanticId.Keys[0].Value;
277+
}
278+
279+
FolderState listFolder = CreateFolder(parent, sme.IdShort);
280+
281+
if ((sme.SemanticId.Keys != null) && (sme.SemanticId.Keys.Count > 0))
282+
{
283+
CreateStringVariable(listFolder, "DictionaryReference", sme.SemanticId.Keys[0].Value);
284+
}
285+
286+
foreach (SubmodelElement smeChild in list.Value)
287+
{
288+
CreateDataElement(listFolder, smeChild);
289+
}
290+
}
291+
}
230292
else
231293
{
232294
// fall back to SemanticID if no IDShort is provided
@@ -235,45 +297,47 @@ private void CreateDataElement(NodeState parent, SubmodelElement sme)
235297
sme.IdShort = sme.SemanticId.Keys[0].Value;
236298
}
237299

238-
if (sme is Property)
239-
{
240-
CreateStringVariable(parent, sme.IdShort, ((Property)sme).Value);
241-
}
242-
else if (sme is Blob)
243-
{
244-
CreateStringVariable(parent, sme.IdShort, ((Blob)sme).Value);
245-
}
246-
else if (sme is File)
300+
FolderState smeFolder = CreateFolder(parent, sme.IdShort);
301+
302+
if (sme is Property property)
247303
{
248-
CreateStringVariable(parent, sme.IdShort, ((File)sme).Value);
304+
CreateStringVariable(smeFolder, "Value", property.Value);
249305
}
250-
else if (sme is ReferenceElement)
306+
else if (sme is MultiLanguageProperty multiLanguageProperty)
251307
{
252-
if (string.IsNullOrEmpty(sme.IdShort) && (((ReferenceElement)sme).Value != null) && (((ReferenceElement)sme).Value.Keys.Count > 0) && (((ReferenceElement)sme).Value.Keys[0].Value != null))
308+
foreach (var entry in multiLanguageProperty.Value)
253309
{
254-
sme.IdShort = ((ReferenceElement)sme).Value.Keys[0].Value;
255-
CreateStringVariable(parent, sme.IdShort, string.Empty);
310+
FolderState mlDataElement = CreateFolder(smeFolder, "MultiLanguageValue");
311+
312+
CreateStringVariable(mlDataElement, "Language", entry.Language);
313+
CreateStringVariable(mlDataElement, "Value", entry.Text);
256314
}
257315
}
316+
else if (sme is File file)
317+
{
318+
FolderState relatedResource = CreateFolder(smeFolder, "RelatedResource");
319+
320+
CreateStringVariable(relatedResource, "contentType", file.MimeType);
321+
CreateStringVariable(relatedResource, "url", file.Value);
322+
}
258323
else
259324
{
260-
// use the SemanticID as the value of the variable
261-
string value = string.Empty;
262-
if ((sme.SemanticId.Keys != null) && (sme.SemanticId.Keys.Count > 0))
263-
{
264-
value = sme.SemanticId.Keys[0].Value;
265-
}
325+
// for other types of SubmodelElements, there is no mapping to the DPP
326+
Console.WriteLine($"Warning: SubmodelElement of type {sme.GetType().Name} with idShort '{sme.IdShort}' has no mapping to the DPP and will be created as an empty folder in the OPC UA address space.");
327+
}
266328

267-
CreateStringVariable(parent, sme.IdShort, value);
268-
}
329+
if ((sme.SemanticId.Keys != null) && (sme.SemanticId.Keys.Count > 0))
330+
{
331+
CreateStringVariable(smeFolder, "DictionaryReference", sme.SemanticId.Keys[0].Value);
332+
}
269333
}
270334
}
271335

272336
public FolderState CreateFolder(NodeState? parent, string browseDisplayName)
273337
{
274338
if (string.IsNullOrEmpty(browseDisplayName))
275339
{
276-
throw new ArgumentNullException("Cannot create UA folder with empty browsename!");
340+
browseDisplayName = "Folder";
277341
}
278342

279343
FolderState folder = new(parent)

Models/Key.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,13 @@ public class Key
1313
{
1414
[Required]
1515
[DataMember(Name="type")]
16-
[XmlAttribute(AttributeName="type")]
16+
[XmlElement(ElementName = "type")]
1717
[MetaModelName("Key.Type")]
1818
public KeyElements Type { get; set; }
1919

2020
[Required]
21-
[XmlText]
2221
[DataMember(Name="value")]
22+
[XmlElement(ElementName = "value")]
2323
[MetaModelName("Key.Value")]
2424
public string Value { get; set; }
2525

0 commit comments

Comments
 (0)