@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.
- package/CHANGELOG.md +98 -0
- package/README.md +10 -7
- package/cordis.patch.yml +1 -1
- package/entries/netlogic.js +266 -0
- package/entries/optix.js +13 -0
- package/package.json +3 -2
- package/vendor/ftview/SKILL.md +6 -0
- package/vendor/ftview/lib/model.d.ts +8 -3
- package/vendor/ftview/lib/model.js +63 -16
- package/vendor/l5x/lib/engine/analysis.d.ts +9 -1
- package/vendor/l5x/lib/engine/analysis.js +28 -3
- package/vendor/l5x/lib/engine/change-set.js +5 -4
- package/vendor/l5x/lib/engine/cip-request.d.ts +1 -0
- package/vendor/l5x/lib/engine/cip-request.js +71 -26
- package/vendor/l5x/lib/engine/direct-polling.js +12 -6
- package/vendor/l5x/lib/engine/review.js +12 -1
- package/vendor/l5x/lib/engine/studio-sdk.js +24 -6
- package/vendor/mwi/code/mw_code.py +4 -1
- package/vendor/mwi/legacy/native-variables.js +9 -1
- package/vendor/mwi/lib/read/service.d.ts +7 -1
- package/vendor/mwi/lib/read/service.js +13 -1
- package/vendor/mwi/lib/session/service.js +7 -1
- package/vendor/optix/SKILL.md +68 -0
- package/vendor/optix/netlogic/SOURCE.json +12 -0
- package/vendor/optix/netlogic/index.json +1987 -0
- package/vendor/optix/netlogic/pages/accessing-project-nodes.md +113 -0
- package/vendor/optix/netlogic/pages/advanced-svg.md +124 -0
- package/vendor/optix/netlogic/pages/alarming.md +489 -0
- package/vendor/optix/netlogic/pages/async-tasks.md +236 -0
- package/vendor/optix/netlogic/pages/audit-signature.md +91 -0
- package/vendor/optix/netlogic/pages/colors.md +122 -0
- package/vendor/optix/netlogic/pages/companion-specs.md +77 -0
- package/vendor/optix/netlogic/pages/creating-objects.md +370 -0
- package/vendor/optix/netlogic/pages/database-interaction.md +437 -0
- package/vendor/optix/netlogic/pages/datagrids.md +123 -0
- package/vendor/optix/netlogic/pages/debugging-tips.md +605 -0
- package/vendor/optix/netlogic/pages/designtime-tags-import.md +531 -0
- package/vendor/optix/netlogic/pages/dialog-boxes.md +150 -0
- package/vendor/optix/netlogic/pages/dispatch-events.md +200 -0
- package/vendor/optix/netlogic/pages/dynamic-links.md +825 -0
- package/vendor/optix/netlogic/pages/element-access.md +1208 -0
- package/vendor/optix/netlogic/pages/engineering-units.md +298 -0
- package/vendor/optix/netlogic/pages/events.md +340 -0
- package/vendor/optix/netlogic/pages/execute-command.md +202 -0
- package/vendor/optix/netlogic/pages/fto-studio-cli.md +99 -0
- package/vendor/optix/netlogic/pages/good-practices.md +50 -0
- package/vendor/optix/netlogic/pages/information-model.md +89 -0
- package/vendor/optix/netlogic/pages/log-output.md +22 -0
- package/vendor/optix/netlogic/pages/managing-aliases.md +22 -0
- package/vendor/optix/netlogic/pages/manipulate-nodes.md +72 -0
- package/vendor/optix/netlogic/pages/mqtt.md +240 -0
- package/vendor/optix/netlogic/pages/netlogic-lifecycle.md +270 -0
- package/vendor/optix/netlogic/pages/netlogic-overview.md +155 -0
- package/vendor/optix/netlogic/pages/opcua.md +325 -0
- package/vendor/optix/netlogic/pages/plc-tags.md +219 -0
- package/vendor/optix/netlogic/pages/queries.md +1297 -0
- package/vendor/optix/netlogic/pages/random-generation.md +90 -0
- package/vendor/optix/netlogic/pages/recipes.md +375 -0
- package/vendor/optix/netlogic/pages/recipex.md +2383 -0
- package/vendor/optix/netlogic/pages/regex.md +15 -0
- package/vendor/optix/netlogic/pages/register-observers.md +346 -0
- package/vendor/optix/netlogic/pages/reports.md +141 -0
- package/vendor/optix/netlogic/pages/resource-uri.md +308 -0
- package/vendor/optix/netlogic/pages/runtime-tags-import.md +280 -0
- package/vendor/optix/netlogic/pages/serial-port.md +357 -0
- package/vendor/optix/netlogic/pages/sessions.md +58 -0
- package/vendor/optix/netlogic/pages/system-node.md +153 -0
- package/vendor/optix/netlogic/pages/template-library.md +127 -0
- package/vendor/optix/netlogic/pages/threading.md +236 -0
- package/vendor/optix/netlogic/pages/translations.md +417 -0
- package/vendor/optix/netlogic/pages/trends.md +128 -0
- package/vendor/optix/netlogic/pages/ui-animations.md +115 -0
- package/vendor/optix/netlogic/pages/users-groups.md +416 -0
- package/vendor/optix/netlogic/pages/variables-formatting.md +16 -0
- package/vendor/optix/netlogic/pages/variables-interaction.md +557 -0
- 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
|
+
```
|