22namespace 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 )
0 commit comments