@aryacode/dsh-engineering 0.4.22 → 0.6.0

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.
Files changed (76) hide show
  1. package/CHANGELOG.md +98 -0
  2. package/README.md +10 -7
  3. package/cordis.patch.yml +1 -1
  4. package/entries/netlogic.js +266 -0
  5. package/entries/optix.js +13 -0
  6. package/package.json +3 -2
  7. package/vendor/ftview/SKILL.md +6 -0
  8. package/vendor/ftview/lib/model.d.ts +8 -3
  9. package/vendor/ftview/lib/model.js +63 -16
  10. package/vendor/l5x/lib/engine/analysis.d.ts +9 -1
  11. package/vendor/l5x/lib/engine/analysis.js +28 -3
  12. package/vendor/l5x/lib/engine/change-set.js +5 -4
  13. package/vendor/l5x/lib/engine/cip-request.d.ts +1 -0
  14. package/vendor/l5x/lib/engine/cip-request.js +71 -26
  15. package/vendor/l5x/lib/engine/direct-polling.js +12 -6
  16. package/vendor/l5x/lib/engine/review.js +12 -1
  17. package/vendor/l5x/lib/engine/studio-sdk.js +24 -6
  18. package/vendor/mwi/code/mw_code.py +4 -1
  19. package/vendor/mwi/legacy/native-variables.js +9 -1
  20. package/vendor/mwi/lib/read/service.d.ts +7 -1
  21. package/vendor/mwi/lib/read/service.js +13 -1
  22. package/vendor/mwi/lib/session/service.js +7 -1
  23. package/vendor/optix/SKILL.md +68 -0
  24. package/vendor/optix/netlogic/SOURCE.json +12 -0
  25. package/vendor/optix/netlogic/index.json +1987 -0
  26. package/vendor/optix/netlogic/pages/accessing-project-nodes.md +113 -0
  27. package/vendor/optix/netlogic/pages/advanced-svg.md +124 -0
  28. package/vendor/optix/netlogic/pages/alarming.md +489 -0
  29. package/vendor/optix/netlogic/pages/async-tasks.md +236 -0
  30. package/vendor/optix/netlogic/pages/audit-signature.md +91 -0
  31. package/vendor/optix/netlogic/pages/colors.md +122 -0
  32. package/vendor/optix/netlogic/pages/companion-specs.md +77 -0
  33. package/vendor/optix/netlogic/pages/creating-objects.md +370 -0
  34. package/vendor/optix/netlogic/pages/database-interaction.md +437 -0
  35. package/vendor/optix/netlogic/pages/datagrids.md +123 -0
  36. package/vendor/optix/netlogic/pages/debugging-tips.md +605 -0
  37. package/vendor/optix/netlogic/pages/designtime-tags-import.md +531 -0
  38. package/vendor/optix/netlogic/pages/dialog-boxes.md +150 -0
  39. package/vendor/optix/netlogic/pages/dispatch-events.md +200 -0
  40. package/vendor/optix/netlogic/pages/dynamic-links.md +825 -0
  41. package/vendor/optix/netlogic/pages/element-access.md +1208 -0
  42. package/vendor/optix/netlogic/pages/engineering-units.md +298 -0
  43. package/vendor/optix/netlogic/pages/events.md +340 -0
  44. package/vendor/optix/netlogic/pages/execute-command.md +202 -0
  45. package/vendor/optix/netlogic/pages/fto-studio-cli.md +99 -0
  46. package/vendor/optix/netlogic/pages/good-practices.md +50 -0
  47. package/vendor/optix/netlogic/pages/information-model.md +89 -0
  48. package/vendor/optix/netlogic/pages/log-output.md +22 -0
  49. package/vendor/optix/netlogic/pages/managing-aliases.md +22 -0
  50. package/vendor/optix/netlogic/pages/manipulate-nodes.md +72 -0
  51. package/vendor/optix/netlogic/pages/mqtt.md +240 -0
  52. package/vendor/optix/netlogic/pages/netlogic-lifecycle.md +270 -0
  53. package/vendor/optix/netlogic/pages/netlogic-overview.md +155 -0
  54. package/vendor/optix/netlogic/pages/opcua.md +325 -0
  55. package/vendor/optix/netlogic/pages/plc-tags.md +219 -0
  56. package/vendor/optix/netlogic/pages/queries.md +1297 -0
  57. package/vendor/optix/netlogic/pages/random-generation.md +90 -0
  58. package/vendor/optix/netlogic/pages/recipes.md +375 -0
  59. package/vendor/optix/netlogic/pages/recipex.md +2383 -0
  60. package/vendor/optix/netlogic/pages/regex.md +15 -0
  61. package/vendor/optix/netlogic/pages/register-observers.md +346 -0
  62. package/vendor/optix/netlogic/pages/reports.md +141 -0
  63. package/vendor/optix/netlogic/pages/resource-uri.md +308 -0
  64. package/vendor/optix/netlogic/pages/runtime-tags-import.md +280 -0
  65. package/vendor/optix/netlogic/pages/serial-port.md +357 -0
  66. package/vendor/optix/netlogic/pages/sessions.md +58 -0
  67. package/vendor/optix/netlogic/pages/system-node.md +153 -0
  68. package/vendor/optix/netlogic/pages/template-library.md +127 -0
  69. package/vendor/optix/netlogic/pages/threading.md +236 -0
  70. package/vendor/optix/netlogic/pages/translations.md +417 -0
  71. package/vendor/optix/netlogic/pages/trends.md +128 -0
  72. package/vendor/optix/netlogic/pages/ui-animations.md +115 -0
  73. package/vendor/optix/netlogic/pages/users-groups.md +416 -0
  74. package/vendor/optix/netlogic/pages/variables-formatting.md +16 -0
  75. package/vendor/optix/netlogic/pages/variables-interaction.md +557 -0
  76. package/vendor/optix/netlogic/pages/variables-structure.md +155 -0
@@ -0,0 +1,557 @@
1
+ # Variables interaction
2
+
3
+ ## Create a simple Variable
4
+
5
+ ```csharp
6
+ using OpcUa = UAManagedCore.OpcUa;
7
+
8
+ [ExportMethod]
9
+ public void CreateVariable()
10
+ {
11
+ var myVar = InformationModel.MakeVariable("MyVariable", OpcUa.DataTypes.Int32);
12
+ }
13
+ ```
14
+
15
+ ## Create a complex Variable
16
+
17
+ Some DataTypes are not exposed by OPC/UA, so you need to get to the DataType number (by attaching the debugger or inspecting the decompiled sources) and then use that info to build a valid NodeId
18
+
19
+ ### TimeRange variable
20
+
21
+ TimeRange variables are structures with two DateTime inside, its type as NodeID is contained in the FTOptix:Core name space and refers as primary ancestor to Struct
22
+
23
+ #### Create a TimeRange variable
24
+
25
+ ```csharp
26
+ [ExportMethod]
27
+ public void CreateTimeRangeVariable(string browseName)
28
+ {
29
+ // Create the TimeRange object
30
+ IUAVariable newTimeRangeVariable = InformationModel.MakeVariable(browseName, new NodeId(LogicObject.Context.GetNamespaceIndex("urn:FTOptix:Core"), 277u));
31
+ // Prepare the TimeRange variable
32
+ TimeRange timeRangeValue = new TimeRange();
33
+ // Set the TimeRange value
34
+ timeRangeValue.StartTime = DateTime.Now.AddDays(-2);
35
+ timeRangeValue.EndTime = DateTime.Now.AddDays(5);
36
+ newTimeRangeVariable.Value = timeRangeValue;
37
+ // Add the value to the variable
38
+ if (Project.Current.Get("Model").GetVariable(browseName) == null)
39
+ Project.Current.Get("Model").Add(newTimeRangeVariable);
40
+ }
41
+ ```
42
+
43
+ #### Edit a TimeRange variable
44
+
45
+ ```csharp
46
+ [ExportMethod]
47
+ public void ModifyTimeRangeVariable(NodeId timeRangeVariableNodeId, DateTime startTime, DateTime stopTime)
48
+ {
49
+ // Get the TimeRange variable
50
+ IUAVariable timeRangeVariable = InformationModel.GetVariable(timeRangeVariableNodeId);
51
+ if (timeRangeVariable == null)
52
+ return;
53
+ // Read the current value
54
+ TimeRange timeRangeValue = timeRangeVariable.Value.Value as TimeRange;
55
+ // Set the new values
56
+ timeRangeValue.StartTime = startTime;
57
+ timeRangeValue.EndTime = stopTime;
58
+ // Update the variable value
59
+ timeRangeVariable.Value = timeRangeValue;
60
+ }
61
+ ```
62
+
63
+ ### Enumeration DataType
64
+
65
+ Enumerations are DataTypes that contain a variable called EnumValues that contains an array of structures that define the entries of the enumeration in this order:
66
+
67
+ - `Key` : integer value
68
+ - `DisplayValue` : LocalizedText of the displayed text for the enumeration
69
+ - `Description` : Localized text of the description of the key-DisplayValue pair
70
+
71
+ #### Create a Enumeration DataType
72
+
73
+ ```csharp
74
+ [ExportMethod]
75
+ public void MakeEnumerationAndVariable()
76
+ {
77
+ // Create the values for the enumeration
78
+ List<Tuple<int, string, string, string, string>> enumerationDataCollection = new List<Tuple<int, string, string, string, string>>()
79
+ {
80
+ {new Tuple<int, string, string, string, string>(0,"NA","en-US","Not available","en-US") },
81
+ {new Tuple<int, string, string, string, string>(1,"Automatic","en-US","Automatic mode and running","en-US") },
82
+ {new Tuple<int, string, string, string, string>(2,"Manual","en-US","Manual mode","en-US") },
83
+ {new Tuple<int, string, string, string, string>(3,"Stop","en-US","","") },
84
+ {new Tuple<int, string, string, string, string>(5,"Emergency restore","en-US","Automatic mode and operation phase after emergency stop","en-US") }
85
+ };
86
+ StuffMakeNewEnumeration(Project.Current.Get("Model"), "MachineOperationStatus", enumerationDataCollection);
87
+ // Create a variable with DataType the new Enumeration if not exist
88
+ if (Project.Current.Get("Model").Get("FeedingMachineStatus") == null)
89
+ {
90
+ IUAVariable newEnumerationVariable = InformationModel.MakeVariable("FeedingMachineStatus", Project.Current.Get("Model/MachineOperationStatus").NodeId);
91
+ Project.Current.Get("Model").Add(newEnumerationVariable);
92
+ }
93
+ }
94
+
95
+ /// <summary>
96
+ /// Generate a new OPC-UA enumeration with values filled by a Tuple including all necessary information
97
+ /// </summary>
98
+ /// <param name="newEnumerationOwner">Node where to add the new enumeration</param>
99
+ /// <param name="newEnumerationName">BrowseName of the new enumeration</param>
100
+ /// <param name="enumerationDataCollection">Tuple that is structured as: key, text value, LocaleId of text value, description, LocaleId of description</param>
101
+ private void StuffMakeNewEnumeration(IUANode newEnumerationOwner, string newEnumerationName, List<Tuple<int, string, string, string, string>> enumerationDataCollection)
102
+ {
103
+ if (newEnumerationOwner.Get<IUADataType>(newEnumerationName) != null)
104
+ {
105
+ // If the enumeration already exists do not perform any operation
106
+ Log.Warning("StuffMakeNewEnumeration", $"Enumeration with BrowseName {newEnumerationName} alread exist!");
107
+ return;
108
+ }
109
+ // Generate a NodeId for the new Enumeration
110
+ NodeId newEnumerationNodeId = NodeId.Random(newEnumerationOwner.NodeId.NamespaceIndex);
111
+ // For enumeration I need to create the reference structure and the values to be assigned
112
+ List<EnumField> newEnumerationFields = new List<EnumField>();
113
+ List<Struct> newEnumerationValues = new List<Struct>();
114
+ // With one foreach loop it populates both Lists
115
+ foreach (Tuple<int, string, string, string, string> enumerationData in enumerationDataCollection)
116
+ {
117
+ // Generate the localizedText of display Value with Item 2 (value) and Item 3 (LocaleId)
118
+ LocalizedText displayValue = new LocalizedText(enumerationData.Item2, enumerationData.Item3);
119
+ // Generate the localizedText of description with Item 4 (value) and Item 5 (LocaleId)
120
+ LocalizedText description = new LocalizedText(enumerationData.Item4, enumerationData.Item5);
121
+ // Generate the Struct containing the enumeration values in the order Key, DisplayValue and Description
122
+ List<object> newValues = new List<object>
123
+ {
124
+ enumerationData.Item1,
125
+ displayValue,
126
+ description
127
+ };
128
+ newEnumerationValues.Add(new Struct(OpcUa.DataTypes.EnumValueType, newValues.AsReadOnly()));
129
+ // Generate the EnumField called Value<key> (ex Value0) containing the values of the enumeration in the order Key, DisplayValue and Description
130
+ newEnumerationFields.Add(new EnumField($"Value{enumerationData.Item1}", enumerationData.Item1, displayValue, description));
131
+ }
132
+ // Generate a new EnumDefinition with the same nodeId and browseName of the new enumeration
133
+ EnumDefinition newEnumerationDefinition = new EnumDefinition(newEnumerationName, newEnumerationNodeId, newEnumerationFields.AsReadOnly());
134
+ // Generate the new Enumeration (is a DataType)
135
+ IUADataType newEnumeration = Project.Current.Context.NodeFactory.MakeDataType(newEnumerationNodeId, newEnumerationName, OpcUa.DataTypes.Enumeration, enumDefinition: newEnumerationDefinition);
136
+ // Generate the variable EnumValues, which contains all the values of the enumeration that you will see in the IDE
137
+ IUAVariable newEnumValuesVariable = InformationModel.MakeVariable(new QualifiedName(0, "EnumValues"), OpcUa.DataTypes.EnumValueType, OpcUa.VariableTypes.BaseDataVariableType, new uint[1] { (uint) newEnumerationValues.Count });
138
+ // Fill the variable EnumValues with the values passed in the method
139
+ newEnumValuesVariable.Value = new UAValue(newEnumerationValues.ToArray());
140
+ // Finalize by adding the variable to the enumeration and the latter to its owner
141
+ newEnumeration.Add(newEnumValuesVariable);
142
+ newEnumerationOwner.Add(newEnumeration);
143
+ }
144
+ ```
145
+
146
+ ### Read Enumeration DataType fields
147
+
148
+ ```csharp
149
+ /// <summary>
150
+ /// Read the fields of an Enumeration DataType and log Value, DisplayName and Description.
151
+ /// </summary>
152
+ var enumerationDataType = Project.Current.Get<IUADataType>("Model/Enumeration1");
153
+ if (enumerationDataType != null)
154
+ {
155
+ var fields = enumerationDataType.EnumDefinition.Fields;
156
+ foreach (EnumField field in fields)
157
+ {
158
+ Log.Info("Value: " + field.Value + ", DisplayName: " + field.DisplayName.Text + ", Description: " + field.Description.Text);
159
+ }
160
+ }
161
+ ```
162
+
163
+ ## Sync to variable change
164
+
165
+ For each variable that is created in FactoryTalk Optix, the corresponding class is automatically generated, this is actually creating two properties/classes per each variable
166
+
167
+ - The first one is a variable with the same BrowseName
168
+ - The second one is the BrowseName of the variable concatenated by `Variable`, this is typically used to sync to change in value or to make DynamicLinks
169
+ - Example:
170
+ - User creates a `MotorType` which exposes a `Speed` property
171
+ - Optix creates:
172
+ - `MotorType.Speed` -> This is used to access the value of the variable
173
+ - `MotorType.SpeedVariable` -> This is used to sync to change in value
174
+
175
+ Registering for the `VariableChange` event in C#, registers a Core-side subscription to the `VariableChange` event. Simultaneously, a native C++ observer for the `VariableChange` event is created, which acts as a bridge by calling the C# method you subscribed to the `VariableChange` event with. Even if the `NetLogic` dies, without de-registration, the native observer remains alive, so the C# method is still triggered even after the `NetLogic` stops. This means it is **mandatory** to remove the observer before disposing the NetLogic.
176
+
177
+ Let me know if you need any further assistance!
178
+
179
+ ### Subscribing to a value change
180
+
181
+ ```csharp
182
+ public override void Start()
183
+ {
184
+ // Get the base variable
185
+ IUAVariable myVar = Project.Current.GetVariable("Model/MotorType/Speed");
186
+ // Create the new event and call the method when a change is detected
187
+ myVar.VariableChange += MyVar_VariableChange;
188
+ }
189
+
190
+ private void MyVar_VariableChange(object sender, VariableChangeEventArgs e)
191
+ {
192
+ // Event listener to print old value and new value
193
+ Log.Info("Old value: " + e.OldValue.ToString());
194
+ Log.Info("New value: " + e.NewValue.ToString());
195
+ }
196
+
197
+ public override void Stop()
198
+ {
199
+ // Mandatory unsubscriber
200
+ myVar.VariableChange -= MyVar_VariableChange;
201
+ }
202
+ ```
203
+
204
+ ## Single and multi dimensional arrays
205
+
206
+ ### Create a mono-dimensional array variable
207
+
208
+ ```csharp
209
+ // Use the MakeVariable method with the array dimensions as last parameter
210
+ var newVariable = InformationModel.MakeVariable("MyArray", OpcUa.DataTypes.Int32, new uint[] { 10 });
211
+ // Adds the MyArray variable (Int32[10]) to the Model folder
212
+ Project.Current.Get("Model").Add(newVariable);
213
+ ```
214
+
215
+ ### Create a multi-dimensional array variable
216
+
217
+ ```csharp
218
+ // Use the MakeVariable method with the array dimensions as last parameter
219
+ var newMatrix = InformationModel.MakeVariable("MyMatrix", OpcUa.DataTypes.Float, new uint[] { 5, 5 });
220
+ // Adds the MyMatrix variable (Int32[5,5]) to the Model folder
221
+ Project.Current.Get("Model").Add(newMatrix);
222
+ ```
223
+
224
+ ### Access mono-dimensional arrays
225
+
226
+ ```csharp
227
+ // Get the model variable
228
+ var MyVar = Project.Current.GetVariable("Model/Variable");
229
+ // Create a C# variable to manipulate values
230
+ var tempVar = (float[])MyVar.Value.Value;
231
+ // Set the elements of the C# variable
232
+ tempVar[0] = (float)Project.Current.GetVariable("CommDrivers/EthernetIPDriver1/EthernetIPStation1/TagName").Value;
233
+ // Write the new value(s) to the model variable
234
+ MyVar.SetValue(tempVar);
235
+ ```
236
+
237
+ ### Access multi-dimensional arrays
238
+
239
+ ```csharp
240
+ // Read the model matrix
241
+ var MatrixMover = Project.Current.GetVariable("Model/PosMoverA");
242
+ // Create a C# variable
243
+ var tempVar = (float[,])MatrixMover.Value.Value;
244
+ // Set some values in the C# variable
245
+ tempVar[0, 0] = (float)Project.Current.GetVariable("CommDrivers/EthernetIPDriver1/EthernetIPStation1/Tags/Program:MainProgram/AOI_CalcPosMovA/x_Pos").Value;
246
+ tempVar[0, 1] = (float)Project.Current.GetVariable("CommDrivers/EthernetIPDriver1/EthernetIPStation1/Tags/Program:MainProgram/AOI_CalcPosMovA/y_Pos").Value;
247
+ tempVar[1, 0] = (float)Project.Current.GetVariable("CommDrivers/EthernetIPDriver1/EthernetIPStation1/Tags/Program:MainProgram/AOI_CalcPosMovA/x_Pos").Value;
248
+ tempVar[1, 1] = (float)Project.Current.GetVariable("CommDrivers/EthernetIPDriver1/EthernetIPStation1/Tags/Program:MainProgram/AOI_CalcPosMovA/y_Pos").Value;
249
+ // Update the model variable with the new value
250
+ MatrixMover.SetValue(tempVar);
251
+ ```
252
+
253
+ ### Convert an array of bytes to a string
254
+
255
+ Assuming the array of bytes comes from the PLC and we want to write the string to a Model variable
256
+
257
+ ```csharp
258
+ // Get the array content from the controller. A RemoteRead can also be added if needed.
259
+ byte[] byteArray = (byte[]) Project.Current.GetVariable("CommDrivers/RAEtherNet_IPDriver1/RAEtherNet_IPStation1/Tags/Controller Tags/chsArray").Value.Value;
260
+ // Convert the array of bytes to the output string
261
+ string appStr = System.Text.Encoding.UTF8.GetString(byteArray, 0, byteArray.Length);
262
+ ```
263
+
264
+ ### Convert a string to an array of bytes
265
+
266
+ ```csharp
267
+ // Convert the string back to a byte array
268
+ byte[] byteArray = System.Text.Encoding.UTF8.GetBytes(appStr);
269
+ // Optionally, you can add a RemoteWrite to force the new value in the controller
270
+ Project.Current.GetVariable("CommDrivers/RAEtherNet_IPDriver1/RAEtherNet_IPStation1/Tags/Controller Tags/chsArray").Value = byteArray;
271
+ ```
272
+
273
+ ### Read an array from a PLC (RemoteRead) and modify it
274
+
275
+ ```csharp
276
+ // Read a PLC array variable
277
+ var arrayPLC = Project.Current.GetVariable("CommDrivers/CODESYSDriver1/CODESYSStation1/Tags/Application/PLC_PRG/arrayFloat");
278
+ // Perform a remote read to get the full array into local memory
279
+ float[] localArray = arrayPLC.RemoteRead();
280
+ for (int i = 0; i < localArray.Length; i++)
281
+ {
282
+ // Modify values as needed
283
+ localArray[i]++;
284
+ Log.Info(localArray[i].ToString());
285
+ }
286
+ // Write the modified array back to the PLC variable
287
+ arrayPLC.Value = new UAValue(localArray);
288
+ ```
289
+
290
+ ### Work with two-dimensional arrays
291
+
292
+ ```csharp
293
+ [ExportMethod]
294
+ public void ArrayBidimensionalExample()
295
+ {
296
+ var test = Project.Current.GetVariable("Model/Variable2");
297
+ int[,] array = new int[5,10];
298
+ array[1,5] = 50;
299
+ test.Value = new UAValue(array);
300
+ }
301
+ ```
302
+
303
+ ### Use ChildrenRemoteRead to inspect an aliased object structure
304
+
305
+ ```csharp
306
+ var refLight = LogicObject.GetAlias("LightPointer");
307
+ if (refLight != null)
308
+ {
309
+ var structure = refLight.ChildrenRemoteRead();
310
+ Log.Info("IntermittentMode value in LightPointer structure: " + structure.SingleOrDefault(item => item.RelativePath == "IntermittentMode").Value);
311
+ }
312
+ ```
313
+
314
+ ## Creating Guid for NodeID to use NodeFactory.MakeDataType
315
+
316
+ ```csharp
317
+ public static Guid CreateGuidFromModelElement(string parentGUID, string itemName)
318
+ {
319
+ return CreateGuidFromText($"{parentGUID}.{itemName}");
320
+ }
321
+ public static Guid CreateGuidFromText(string text)
322
+ {
323
+ var hashBytes = Encoding.UTF8.GetBytes(text);
324
+ var hasher = MD5.Create();
325
+ var hashValue = hasher.ComputeHash(hashBytes);
326
+ var guid = new Guid(hashValue);
327
+ return guid;
328
+ }
329
+ ```
330
+
331
+ ## Check if a variable on an instance has a dynamic link, converter or the default value was overwritten
332
+
333
+ ```csharp
334
+ #region Using directives
335
+ // Required namespaces for the script
336
+ using System.Collections.Generic; // Provides collections such as List and Dictionary
337
+ using System.Linq; // Provides LINQ methods for querying collections
338
+ using FTOptix.NetLogic; // FTOptix-specific logic and framework utilities
339
+ using FTOptix.UI; // FTOptix UI elements
340
+ using UAManagedCore; // Core OPC UA components for FTOptix
341
+ #endregion
342
+
343
+ public class DesignTimeNetLogic1 : BaseNetLogic
344
+ {
345
+ /// <summary>
346
+ /// This method is used when the node is an instance of a type and no other super types are used.
347
+ /// It checks and logs specific properties of the children nodes within a panel.
348
+ /// </summary>
349
+ [ExportMethod]
350
+ public void SimpleInstance()
351
+ {
352
+ Log.Info("Method1 called");
353
+
354
+ // Retrieve the panel object by name
355
+ var myPanel = Owner.Get<Panel>("MyPanel1");
356
+
357
+ // Get all child nodes of type IUAVariable within the panel
358
+ var panelChildren = myPanel.GetNodesByType<IUAVariable>();
359
+ Log.Info("Found " + panelChildren.Count().ToString() + " children in panel");
360
+
361
+ // Iterate over each child node
362
+ foreach (var item in panelChildren)
363
+ {
364
+ // Check if the variable's value differs from its corresponding object type's value
365
+ if (myPanel.ObjectType.GetVariable(item.BrowseName).Value != myPanel.GetVariable(item.BrowseName).Value)
366
+ Log.Warning(item.BrowseName + " has a different value from the object type");
367
+
368
+ // Check if the variable has a dynamic link
369
+ if (myPanel.GetVariable(item.BrowseName).Refs.GetNode(Optix.CoreBase.ReferenceTypes.HasDynamicLink) != null)
370
+ Log.Warning(item.BrowseName + " has a dynamic link");
371
+
372
+ // Check if the variable has a converter
373
+ if (myPanel.GetVariable(item.BrowseName).Refs.GetNode(FTOptix.CoreBase.ReferenceTypes.HasConverter) != null)
374
+ Log.Warning(item.BrowseName + " has a converter");
375
+ }
376
+ }
377
+
378
+ /// <summary>
379
+ /// This method is used for instances derived from a type with multiple levels of inheritance.
380
+ /// It traverses up the type hierarchy to find and compare values for child nodes.
381
+ /// </summary>
382
+ [ExportMethod]
383
+ public void RecursiveApproach()
384
+ {
385
+ // Retrieve the panel object by name
386
+ var myPanel = Owner.Get<Panel>("MyPanel1");
387
+
388
+ // Get all child nodes of type IUAVariable within the panel
389
+ var panelChildren = myPanel.GetNodesByType<IUAVariable>();
390
+ Log.Info("Found " + panelChildren.Count().ToString() + " children in panel");
391
+
392
+ // Browse path used for locating nodes in the hierarchy
393
+ var browsePath = new List<QualifiedName>();
394
+
395
+ // Determine the node type or supertype for comparison
396
+ IUANode newMatchingNode;
397
+ if (myPanel.IsObjectTypeOrVariableType())
398
+ newMatchingNode = FindNodeInTypeOrSuperType(myPanel.GetSuperType(), browsePath);
399
+ else if (myPanel.IsObjectOrVariable())
400
+ newMatchingNode = FindNodeInTypeOrSuperType(myPanel.GetTypeNode(), browsePath);
401
+ else
402
+ newMatchingNode = null;
403
+
404
+ // Log an error if no matching node is found in the hierarchy
405
+ if (newMatchingNode == null)
406
+ {
407
+ Log.Error("Can't get to the node supertype!");
408
+ return;
409
+ }
410
+
411
+ // Compare instance values with type values for each child node
412
+ foreach (var node in panelChildren)
413
+ {
414
+ Log.Info(node.BrowseName);
415
+ var instanceValue = myPanel.GetVariable(node.BrowseName).Value;
416
+ var typeValue = newMatchingNode.GetVariable(node.BrowseName).Value;
417
+
418
+ if (instanceValue != typeValue)
419
+ Log.Warning("Value of node " + node.BrowseName + " has a different value");
420
+
421
+ if (myPanel.GetVariable(node.BrowseName).Refs.GetVariable(FTOptix.CoreBase.ReferenceTypes.HasDynamicLink) != null)
422
+ Log.Warning(node.BrowseName + " has a dynamic link");
423
+
424
+ if (myPanel.GetVariable(node.BrowseName).Refs.GetVariable(FTOptix.CoreBase.ReferenceTypes.HasConverter) != null)
425
+ Log.Warning(node.BrowseName + " has a converter");
426
+ }
427
+ }
428
+
429
+ /// <summary>
430
+ /// Finds a node within a type or its supertypes by traversing the hierarchy.
431
+ /// </summary>
432
+ private IUANode FindNodeInTypeOrSuperType(IUANode typeNode, List<QualifiedName> browsePath)
433
+ {
434
+ while (typeNode != null)
435
+ {
436
+ // Attempt to locate the node at the current type level
437
+ var childNode = FindNode(typeNode, browsePath);
438
+ if (childNode != null)
439
+ return childNode;
440
+
441
+ // Move to the supertype and continue searching
442
+ typeNode = typeNode.GetSuperType();
443
+ }
444
+
445
+ return null;
446
+ }
447
+
448
+ /// <summary>
449
+ /// Locates a node by following a browse path from a parent node.
450
+ /// </summary>
451
+ private IUANode FindNode(IUANode parentNode, List<QualifiedName> browsePath)
452
+ {
453
+ var currentNode = parentNode;
454
+
455
+ // Traverse the browse path in reverse
456
+ for (int i = browsePath.Count - 1; i >= 0; --i)
457
+ {
458
+ var childNode = currentNode.Refs.GetNode(browsePath[i]);
459
+ if (childNode == null || childNode.Owner != currentNode)
460
+ return null;
461
+
462
+ currentNode = childNode;
463
+ }
464
+
465
+ return currentNode;
466
+ }
467
+ }
468
+
469
+ public static class PrototypeAnalyzerExtensions
470
+ {
471
+ // Extensions for working with node prototypes and type hierarchies in FTOptix
472
+
473
+ public static IUANode GetTypeNode(this IUANode node)
474
+ {
475
+ if (node is IUAObject obj)
476
+ return obj.ObjectType;
477
+
478
+ if (node is IUAVariable var)
479
+ return var.VariableType;
480
+
481
+ return null;
482
+ }
483
+
484
+ public static IUANode GetSuperType(this IUANode node)
485
+ {
486
+ if (node is IUAObjectType objType)
487
+ return objType.SuperType;
488
+
489
+ if (node is IUAVariableType varType)
490
+ return varType.SuperType;
491
+
492
+ return null;
493
+ }
494
+
495
+ public static bool IsObjectTypeOrVariableType(this IUANode node) => node.NodeClass == NodeClass.ObjectType || node.NodeClass == NodeClass.VariableType;
496
+
497
+ public static bool IsObjectOrVariable(this IUANode node) => node.NodeClass == NodeClass.Object || node.NodeClass == NodeClass.Variable;
498
+
499
+ public static IUANode GetPrototype(this IUANode node)
500
+ {
501
+ if (node is IUAObject obj)
502
+ return obj.Prototype;
503
+
504
+ if (node is IUAVariable var)
505
+ return var.Prototype;
506
+
507
+ return null;
508
+ }
509
+
510
+ public static void SetPrototype(this IUANode node, IUANode prototype)
511
+ {
512
+ if (node is IUAObject obj)
513
+ obj.Prototype = (IUAObject)prototype;
514
+ else if (node is IUAVariable var)
515
+ var.Prototype = (IUAVariable)prototype;
516
+ }
517
+
518
+ public static IReadOnlyList<IUANode> GetInstances(this IUANode node) => node.InverseRefs.GetNodes(UAManagedCore.OpcUa.ReferenceTypes.HasTypeDefinition, false);
519
+ }
520
+ ```
521
+
522
+ ## Loading environment variables
523
+
524
+ Some elements can be loaded from Environment Variables, for example it is good practice to load secrets of Databases or external sources from env. This can be done using the [System.Environment.GetEnvironmentVariable](https://learn.microsoft.com/en-us/dotnet/fundamentals/runtime-libraries/system-environment-getenvironmentvariable), please note about some limitations, for example the `User` and `Machine` target are only supported on Windows platforms, when working with Linux (like Docker Containers), they should be loaded from `Process`.
525
+
526
+ ```csharp
527
+ public override void Start()
528
+ {
529
+ var myStore = (ODBCStore)Owner;
530
+
531
+ // Load DB password
532
+ string env_password = Environment.GetEnvironmentVariable("DB_PASSWORD", EnvironmentVariableTarget.User); // Windows only
533
+ if (string.IsNullOrEmpty(env_password))
534
+ env_password = Environment.GetEnvironmentVariable("DB_PASSWORD", EnvironmentVariableTarget.Machine); // Windows only
535
+ if (string.IsNullOrEmpty(env_password))
536
+ env_password = Environment.GetEnvironmentVariable("DB_PASSWORD", EnvironmentVariableTarget.Process); // Required on Linux
537
+
538
+ // Load variables to the project
539
+ if (!string.IsNullOrEmpty(env_password))
540
+ {
541
+ Log.Info("Loading database password from environment variable DB_PASSWORD");
542
+ myStore.Password = env_password;
543
+ }
544
+ }
545
+ ```
546
+
547
+ ## Remove the value from a NodeId variable
548
+
549
+ ```csharp
550
+ private void ClearNodeIdValue()
551
+ {
552
+ // Get the variable
553
+ var myVar = Project.Current.GetVariable("Model/MyNodeIdVariable");
554
+ // Clear the value
555
+ myVar.Value = NodeId.Empty;
556
+ }
557
+ ```