@aryacode/dsh-engineering 0.5.0 → 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 +20 -0
- package/README.md +6 -6
- 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/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,325 @@
|
|
|
1
|
+
# OPC/UA
|
|
2
|
+
|
|
3
|
+
## Node's references
|
|
4
|
+
|
|
5
|
+
In OPC/UA, some nodes may have additional references are explicit relationship (a named pointer) from one Node to another
|
|
6
|
+
|
|
7
|
+
```csharp
|
|
8
|
+
[ExportMethod]
|
|
9
|
+
public void ReadReference(NodeId element)
|
|
10
|
+
{
|
|
11
|
+
// Get to the element where we want to investigate the OPC/UA references
|
|
12
|
+
IUANode targetNode = InformationModel.Get(element);
|
|
13
|
+
// Assuming the node exists
|
|
14
|
+
if (targetNode != null)
|
|
15
|
+
{
|
|
16
|
+
// Loop per each reference and list it in the console output
|
|
17
|
+
foreach (var item in targetNode.Refs.GetReferences())
|
|
18
|
+
{
|
|
19
|
+
Log.Info("References", $"ReferenceTypeId: {item.ReferenceTypeId}, TargetNodeId: {item.TargetNodeId}, TargetNode: {item.TargetNode.BrowseName}");
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
else
|
|
23
|
+
{
|
|
24
|
+
Log.Info("References", "Cannot find such node in the current project");
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## Read arbitrary tag from an OPC/UA Server
|
|
30
|
+
|
|
31
|
+
> [!WARNING]
|
|
32
|
+
> This method involves some low-level OPC/UA operations and may not be recommended for production usage. It is recommended to use the OPC/UA Client TagImporter instead.
|
|
33
|
+
|
|
34
|
+
> [!NOTE]
|
|
35
|
+
> This method can only be used at runtime.
|
|
36
|
+
|
|
37
|
+
This method allows to read any OPC/UA variables from a remote server knowing its `NameSpaceIndex` and `ID`. The following examples reads the `Server > ServerStatus > State` (0/2259) variable from the UaAnsiCServer from the OPC Foundation
|
|
38
|
+
|
|
39
|
+
### FactoryTalk Optix 1.6.x or older
|
|
40
|
+
|
|
41
|
+
```csharp
|
|
42
|
+
// This list comes from the OPC/UA specification
|
|
43
|
+
enum OpcAttributes
|
|
44
|
+
{
|
|
45
|
+
NodeId = 1,
|
|
46
|
+
NodeClass = 2,
|
|
47
|
+
BrowseName = 3,
|
|
48
|
+
DisplayName = 4,
|
|
49
|
+
Description = 5,
|
|
50
|
+
WriteMask = 6,
|
|
51
|
+
UserWriteMask = 7,
|
|
52
|
+
IsAbstract = 8,
|
|
53
|
+
Symmetric = 9,
|
|
54
|
+
InverseName = 10,
|
|
55
|
+
ContainsNoLoops = 11,
|
|
56
|
+
EventNotifier = 12,
|
|
57
|
+
Value = 13,
|
|
58
|
+
DataType = 14,
|
|
59
|
+
ValueRank = 15,
|
|
60
|
+
ArrayDimensions = 16,
|
|
61
|
+
AccessLevel = 17,
|
|
62
|
+
UserAccessLevel = 18,
|
|
63
|
+
MinimumSamplingInterval = 19,
|
|
64
|
+
Historizing = 20,
|
|
65
|
+
Executable = 21,
|
|
66
|
+
UserExecutable = 22,
|
|
67
|
+
DataTypeDefinition = 23,
|
|
68
|
+
RolePermissions = 24,
|
|
69
|
+
UserRolePermissions = 25,
|
|
70
|
+
AccessRestrictions = 26,
|
|
71
|
+
AccessLevelEx = 27,
|
|
72
|
+
BrowsePath = 64,
|
|
73
|
+
Quality = 65,
|
|
74
|
+
StatusCode = 66,
|
|
75
|
+
SourceTimestamp = 67,
|
|
76
|
+
ServerTimestamp = 68,
|
|
77
|
+
ActualDataType = 69,
|
|
78
|
+
ActualArrayDimensions = 70
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
[ExportMethod]
|
|
82
|
+
public void ReadArbitraryValueFromRemoteOpcUaServer(int nameSpaceIndex, uint identifier)
|
|
83
|
+
{
|
|
84
|
+
// Get the OPC/UA Client from the project
|
|
85
|
+
var opc = Project.Current.Get<OPCUAClient>("OPC-UA/OPCUAClient1");
|
|
86
|
+
|
|
87
|
+
// OPC/UA Variable to read from the remote server
|
|
88
|
+
object[] opcVariable = new object[3]
|
|
89
|
+
{
|
|
90
|
+
new NodeId(nameSpaceIndex, identifier), // NodeId of the variable to read (UaExpert can be used to get these values)
|
|
91
|
+
OpcAttributes.Value, // Attribute ID to read (13 = Value), see enumeration above
|
|
92
|
+
new uint[0] // Indexes, for simple scalar tags this is an empty array
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
// Create a Struct object that holds the information of the remote variable to be passed to the OPC/UA Client
|
|
96
|
+
// Syntax: new Struct(NodeId dataTypeId, object[] values)
|
|
97
|
+
// Where:
|
|
98
|
+
// - DataTypeId is the NodeId of the DataType expected as input argument of the method (50 is the ReadValues DataType)
|
|
99
|
+
// - Values is an array containing the information of the variable to read
|
|
100
|
+
UAManagedCore.Struct asd = new Struct(new NodeId(FTOptix.OPCUAClient.ObjectTypes.OPCUAClient.NamespaceIndex, 50), opcVariable);
|
|
101
|
+
|
|
102
|
+
// The ExecuteMethod method of the OPC/UA Client is used to call the ReadValues method of the OPC/UA Server
|
|
103
|
+
// This expects an object[] as input parameters, so we will create a mono-dimensional array with the Struct object
|
|
104
|
+
object[] inputArgs = new object[1];
|
|
105
|
+
// The first element of the array is the Struct object that we created
|
|
106
|
+
inputArgs[0] = new UAManagedCore.Struct[1];
|
|
107
|
+
// The first element of the Struct array is the variable information to read
|
|
108
|
+
((UAManagedCore.Struct[])inputArgs[0])[0] = asd;
|
|
109
|
+
|
|
110
|
+
// The ExecuteMethod method returns an object[] with the output parameters of the method
|
|
111
|
+
object[] outputArgs = new object[1];
|
|
112
|
+
|
|
113
|
+
// Call the ReadValues method of the OPC/UA Server
|
|
114
|
+
opc.ExecuteMethod("ReadValues", inputArgs, out outputArgs); // OPCUA\OPCUAClient\OPCUAClient\Module.xml.in
|
|
115
|
+
|
|
116
|
+
// The outputArgs array contains the output parameters of the ReadValues method
|
|
117
|
+
var output = (UAManagedCore.Struct[])outputArgs[0];
|
|
118
|
+
Log.Info(output[0].Values[0].ToString());
|
|
119
|
+
}
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
### FactoryTalk Optix 1.7.x or newer
|
|
123
|
+
|
|
124
|
+
```csharp
|
|
125
|
+
struct ReadResult
|
|
126
|
+
{
|
|
127
|
+
public object Value;
|
|
128
|
+
public uint StatusCode;
|
|
129
|
+
|
|
130
|
+
public ReadResult(UAManagedCore.Struct s)
|
|
131
|
+
{
|
|
132
|
+
Value = s.Values[0];
|
|
133
|
+
StatusCode = (uint)s.Values[1];
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
public bool IsGood => (StatusCode & 0xC0000000) == 0;
|
|
137
|
+
|
|
138
|
+
public string StatusName
|
|
139
|
+
{
|
|
140
|
+
get
|
|
141
|
+
{
|
|
142
|
+
if (Enum.IsDefined(typeof(OpcUaStatusCode), StatusCode))
|
|
143
|
+
return ((OpcUaStatusCode)StatusCode).ToString();
|
|
144
|
+
return $"0x{StatusCode:X8}";
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
enum OpcUaStatusCode : uint
|
|
150
|
+
{
|
|
151
|
+
Good = 0x00000000,
|
|
152
|
+
BadUnexpectedError = 0x80010000,
|
|
153
|
+
BadInternalError = 0x80020000,
|
|
154
|
+
BadNodeIdInvalid = 0x80330000,
|
|
155
|
+
BadAttributeIdInvalid = 0x80350000,
|
|
156
|
+
BadNodeIdUnknown = 0x80340000,
|
|
157
|
+
BadNotReadable = 0x803A0000,
|
|
158
|
+
BadNotWritable = 0x803B0000,
|
|
159
|
+
BadTypeMismatch = 0x80740000,
|
|
160
|
+
BadInvalidArgument = 0x80AB0000,
|
|
161
|
+
BadOutOfRange = 0x803C0000,
|
|
162
|
+
BadNoCommunication = 0x80310000,
|
|
163
|
+
BadTimeout = 0x800A0000,
|
|
164
|
+
BadServerNotConnected = 0x800D0000,
|
|
165
|
+
BadSessionIdInvalid = 0x80250000,
|
|
166
|
+
BadUserAccessDenied = 0x801F0000,
|
|
167
|
+
BadSecurityModeInsufficient = 0x80E60000,
|
|
168
|
+
BadServiceUnsupported = 0x800B0000,
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// This list comes from the OPC/UA specification
|
|
172
|
+
enum OpcAttributes
|
|
173
|
+
{
|
|
174
|
+
NodeId = 1,
|
|
175
|
+
NodeClass = 2,
|
|
176
|
+
BrowseName = 3,
|
|
177
|
+
DisplayName = 4,
|
|
178
|
+
Description = 5,
|
|
179
|
+
WriteMask = 6,
|
|
180
|
+
UserWriteMask = 7,
|
|
181
|
+
IsAbstract = 8,
|
|
182
|
+
Symmetric = 9,
|
|
183
|
+
InverseName = 10,
|
|
184
|
+
ContainsNoLoops = 11,
|
|
185
|
+
EventNotifier = 12,
|
|
186
|
+
Value = 13,
|
|
187
|
+
DataType = 14,
|
|
188
|
+
ValueRank = 15,
|
|
189
|
+
ArrayDimensions = 16,
|
|
190
|
+
AccessLevel = 17,
|
|
191
|
+
UserAccessLevel = 18,
|
|
192
|
+
MinimumSamplingInterval = 19,
|
|
193
|
+
Historizing = 20,
|
|
194
|
+
Executable = 21,
|
|
195
|
+
UserExecutable = 22,
|
|
196
|
+
DataTypeDefinition = 23,
|
|
197
|
+
RolePermissions = 24,
|
|
198
|
+
UserRolePermissions = 25,
|
|
199
|
+
AccessRestrictions = 26,
|
|
200
|
+
AccessLevelEx = 27,
|
|
201
|
+
BrowsePath = 64,
|
|
202
|
+
Quality = 65,
|
|
203
|
+
StatusCode = 66,
|
|
204
|
+
SourceTimestamp = 67,
|
|
205
|
+
ServerTimestamp = 68,
|
|
206
|
+
ActualDataType = 69,
|
|
207
|
+
ActualArrayDimensions = 70
|
|
208
|
+
};
|
|
209
|
+
|
|
210
|
+
[ExportMethod]
|
|
211
|
+
public void ReadArbitraryValueFromRemoteOpcUaServer(string namespaceUri, string identifier)
|
|
212
|
+
{
|
|
213
|
+
// Change this to read a different OPC UA attribute
|
|
214
|
+
var attribute = OpcAttributes.Value;
|
|
215
|
+
|
|
216
|
+
// Get the OPC/UA Client from the project
|
|
217
|
+
var opc = Project.Current.Get<OPCUAClient>("OPC-UA/OPCUAClient1");
|
|
218
|
+
|
|
219
|
+
// Find the local namespace index by searching all registered namespaces.
|
|
220
|
+
// FTOptix builds aggregated URIs for dynamic namespaces as "{clientGuid}#{remoteNamespaceUri}",
|
|
221
|
+
// so we match a namespace URI that equals or ends with the provided remote namespace URI.
|
|
222
|
+
var context = LogicObject.Context;
|
|
223
|
+
var localNsIndex = context.GetNamespaceIndexes()
|
|
224
|
+
.Cast<int>()
|
|
225
|
+
.FirstOrDefault(i => {
|
|
226
|
+
var uri = context.GetNamespaceUri(i);
|
|
227
|
+
return uri == namespaceUri || uri.EndsWith("#" + namespaceUri);
|
|
228
|
+
}, -1);
|
|
229
|
+
|
|
230
|
+
if (localNsIndex < 0)
|
|
231
|
+
{
|
|
232
|
+
Log.Error($"Namespace URI '{namespaceUri}' not found. Have you imported some tags in the Optix project?");
|
|
233
|
+
return;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
FTOptix.Core.ElementAccess ea = new FTOptix.Core.ElementAccess();
|
|
237
|
+
UAValue eaValue = ea; // implicit conversion to UAValue
|
|
238
|
+
var emptyElementAccess = (UAManagedCore.Struct)eaValue.Value;
|
|
239
|
+
|
|
240
|
+
var nodeIdStr = $"ns={localNsIndex};s={identifier}";
|
|
241
|
+
|
|
242
|
+
object[] opcVariable = new object[3]
|
|
243
|
+
{
|
|
244
|
+
new NodeId(localNsIndex, identifier),
|
|
245
|
+
(uint)attribute,
|
|
246
|
+
emptyElementAccess // ElementAccess struct, NOT uint[]
|
|
247
|
+
};
|
|
248
|
+
|
|
249
|
+
// Create a Struct object that holds the information of the remote variable to be passed to the OPC/UA Client
|
|
250
|
+
// Syntax: new Struct(NodeId dataTypeId, object[] values)
|
|
251
|
+
// Where:
|
|
252
|
+
// - DataTypeId is the NodeId of the DataType expected as input argument of the method (50 is the ReadValues DataType)
|
|
253
|
+
// - Values is an array containing the information of the variable to read
|
|
254
|
+
UAManagedCore.Struct asd = new Struct(new NodeId(FTOptix.OPCUAClient.ObjectTypes.OPCUAClient.NamespaceIndex, 50), opcVariable);
|
|
255
|
+
|
|
256
|
+
// The ExecuteMethod method of the OPC/UA Client is used to call the ReadValues method of the OPC/UA Server
|
|
257
|
+
// This expects an object[] as input parameters, so we will create a mono-dimensional array with the Struct object
|
|
258
|
+
object[] inputArgs = new object[1];
|
|
259
|
+
// The first element of the array is the Struct object that we created
|
|
260
|
+
inputArgs[0] = new UAManagedCore.Struct[1];
|
|
261
|
+
// The first element of the Struct array is the variable information to read
|
|
262
|
+
((UAManagedCore.Struct[])inputArgs[0])[0] = asd;
|
|
263
|
+
|
|
264
|
+
// The ExecuteMethod method returns an object[] with the output parameters of the method
|
|
265
|
+
object[] outputArgs = new object[1];
|
|
266
|
+
|
|
267
|
+
// Call the ReadValues method of the OPC/UA Server
|
|
268
|
+
opc.ExecuteMethod("ReadValues", inputArgs, out outputArgs);
|
|
269
|
+
|
|
270
|
+
// The outputArgs array contains the output parameters of the ReadValues method
|
|
271
|
+
// ReadValueResult struct has 2 fields: [0] = Value, [1] = StatusCode (UInt32)
|
|
272
|
+
var output = (UAManagedCore.Struct[])outputArgs[0];
|
|
273
|
+
var result = new ReadResult(output[0]);
|
|
274
|
+
|
|
275
|
+
if (!result.IsGood)
|
|
276
|
+
{
|
|
277
|
+
Log.Error($"Reading attribute `{attribute}` for `{nodeIdStr}` failed: {result.StatusName} (0x{result.StatusCode:X8})");
|
|
278
|
+
return;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
Log.Info($"Reading attribute `{attribute}` for `{nodeIdStr}` returned: `{result.Value}`");
|
|
282
|
+
}
|
|
283
|
+
```
|
|
284
|
+
|
|
285
|
+
## Change the Status property of a Tag (changing the DataValue structure)
|
|
286
|
+
|
|
287
|
+
This may be needed in some OPC/UA companion specifications where the Status of a tag is used to detect other status that are not only good or bad
|
|
288
|
+
|
|
289
|
+
> [!NOTE]
|
|
290
|
+
> This method can only be used at runtime.
|
|
291
|
+
|
|
292
|
+
```csharp
|
|
293
|
+
[ExportMethod]
|
|
294
|
+
public void UpdateStatusCode()
|
|
295
|
+
{
|
|
296
|
+
var myVariable = Project.Current.GetVariable("Model/MyFolder/Test");
|
|
297
|
+
// The StatusCode shortcut is read-only
|
|
298
|
+
Log.Info($"Original status code: {myVariable.StatusCode}");
|
|
299
|
+
// new DataValue(UAValue value, uint statusCode, DateTime sourceTimestamp)
|
|
300
|
+
// new DataValue(UAValue value, uint statusCode, DateTime sourceTimestamp, DateTime serverTimestamp)
|
|
301
|
+
myVariable.DataValue = new DataValue(myVariable.Value, 100, DateTime.UtcNow);
|
|
302
|
+
// The StatusCode shortcut is read-only
|
|
303
|
+
Log.Info($"New status code: {myVariable.StatusCode}");
|
|
304
|
+
}
|
|
305
|
+
```
|
|
306
|
+
|
|
307
|
+
## Manually create an OPC/UA Client Tag
|
|
308
|
+
|
|
309
|
+
This script presumes that the OPC/UA Client is already configured in the project and at least a tag was imported (needed to get the NameSpaceIndex). It creates a new tag in the project that will be used to read a variable from the OPC/UA Server.
|
|
310
|
+
|
|
311
|
+
> [!NOTE]
|
|
312
|
+
> This method can only be used at designtime.
|
|
313
|
+
|
|
314
|
+
```csharp
|
|
315
|
+
[ExportMethod]
|
|
316
|
+
public void CreateArbitraryTag()
|
|
317
|
+
{
|
|
318
|
+
// Get the NameSpaceIndex of the OPC/UA Client, the best solution would be to read it from an imported tag.
|
|
319
|
+
// This must be in the `.optix` file of the project, so it must be a known index.
|
|
320
|
+
var nxIndex = LogicObject.Context.GetNamespaceIndex("0ccdea9893ed5102b4fae754ef1732f7#http://www.unifiedautomation.com/DemoServer/");
|
|
321
|
+
// Create a new variable in the project with the NodeId of the OPC/UA variable to read in the remote server
|
|
322
|
+
var variable = LogicObject.Context.NodeFactory.MakeVariable(new NodeId(nxIndex, "Demo.Static.Scalar.Double"), "Double", OpcUa.DataTypes.Double, OpcUa.VariableTypes.BaseDataVariableType);
|
|
323
|
+
// Add the variable to the Objects folder of the OPC/UA client project
|
|
324
|
+
LogicObject.Owner.GetObject("Objects").Add(variable);
|
|
325
|
+
}
|
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
# Field tags
|
|
2
|
+
|
|
3
|
+
Field tags are functionally identical to Model variables (global variables), but with some additions.
|
|
4
|
+
|
|
5
|
+
Field tags can be forced to read or write a value to the field device (a.k.a. PLC) or to force the value to be synched to the controller with a specific refresh time.
|
|
6
|
+
|
|
7
|
+
> [!NOTE]
|
|
8
|
+
> Tags are automatically synchronized with the controller when they are used in:
|
|
9
|
+
> - A page being displayed in any of the UI sessions
|
|
10
|
+
> - Used in a DataLogger or EventLogger
|
|
11
|
+
> - Used as InputVariable of an Alarm
|
|
12
|
+
> - A ValueChange event is subscribed to the tag
|
|
13
|
+
>
|
|
14
|
+
> If none of these conditions are met, the tag will not be synchronized with the controller and will not be updated, so the following techniques to read or write values to the controller are needed.
|
|
15
|
+
|
|
16
|
+
> [!NOTE]
|
|
17
|
+
> Communication drivers are only loaded at runtime, so all the methods described in this page to read/write/force values to the controller can only be used at runtime, they will not work at design time.
|
|
18
|
+
|
|
19
|
+
## Introduction
|
|
20
|
+
|
|
21
|
+
Field tags in FactoryTalk Optix provide several methods for efficiently communicating with PLCs, each with different performance characteristics and use cases. Understanding these methods helps optimize both application performance and network traffic.
|
|
22
|
+
|
|
23
|
+
### Tags access methods
|
|
24
|
+
|
|
25
|
+
1. **RemoteVariableSynchronizer**
|
|
26
|
+
|
|
27
|
+
- Optimizes data exchange by bundling requests and minimizing network traffic
|
|
28
|
+
- Efficient for continuous monitoring of multiple tags at specified intervals
|
|
29
|
+
- Expensive during initial setup but provides optimized ongoing communication
|
|
30
|
+
- After setup, exchanges tag values in the most optimized way possible
|
|
31
|
+
- Best for scenarios requiring regular polling of many dispersed tags
|
|
32
|
+
|
|
33
|
+
2. **RemoteRead/RemoteWrite**
|
|
34
|
+
|
|
35
|
+
- Provides synchronous, on-demand reading/writing of specific tags
|
|
36
|
+
- Best for atomic operations when immediate values are needed
|
|
37
|
+
- Less efficient for large numbers of tags
|
|
38
|
+
- InformationModel.RemoteRead() can bundle multiple tag reads for better performance
|
|
39
|
+
|
|
40
|
+
### RA Ethernet/IP Controller Array Reading (Optix 1.6+)
|
|
41
|
+
|
|
42
|
+
Starting from FactoryTalk Optix 1.6.x, a major optimization was introduced in the RA Ethernet/IP driver, which allows reading large arrays directly from the controller, significantly improving performance for bulk data operations. The user does not need to manually specify the read mode, as the system automatically optimizes the read operation for large arrays if possible.
|
|
43
|
+
|
|
44
|
+
This new algorithm:
|
|
45
|
+
- Is highly efficient for reading large blocks of contiguous data
|
|
46
|
+
- Can read large arrays (e.g., 10K DINT array) in ~200ms
|
|
47
|
+
- Requires data to be organized in arrays within the controller
|
|
48
|
+
|
|
49
|
+
Example:
|
|
50
|
+
|
|
51
|
+
```csharp
|
|
52
|
+
var tag = Project.Current.Get<Tag>("CommDrivers/RAEtherNet_IPDriver1/RAEtherNet_IPStation1/Tags/Controller Tags/DINT_A10K");
|
|
53
|
+
var value = tag.RemoteRead();
|
|
54
|
+
var arrayInts = (int[])value.Value;
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
### Performance Considerations
|
|
58
|
+
|
|
59
|
+
- **Network Traffic**: The `RemoteVariableSynchronizer` drastically reduces network load. For example, reading 10K DINTs with the RA Ethernet/IP driver will require 10x less network traffic when using a `RemoteVariableSynchronizer` compared to a `RemoteRead`.
|
|
60
|
+
- **Cached Values**: Using `Project.Current.GetVariable("MyTag").Value` accesses the last cached value without triggering new controller communication
|
|
61
|
+
- **Hardware Impact**: Consider your network infrastructure, PLC capabilities and host machine capabilities when designing communication patterns
|
|
62
|
+
- **Synchronization Frequency**: Always specify appropriate refresh intervals in Remote Variable Synchronizer to prevent overwhelming the controller (`TimeSpan.FromSeconds()`)
|
|
63
|
+
|
|
64
|
+
Learning to use these methods effectively can greatly enhance the performance and responsiveness of your FactoryTalk Optix applications, especially in scenarios involving large datasets or frequent updates.
|
|
65
|
+
|
|
66
|
+
For example:
|
|
67
|
+
|
|
68
|
+
- A `RemoteVariableSynchronizer` can be created in a global NetLogic to continuously monitor and synchronize multiple tags, ensuring they are always up-to-date without excessive network traffic.
|
|
69
|
+
- All the NetLogic in the project can simply access the synchronized variables using the `Project.Current.GetVariable("MyTag").Value` (either getting or setting the value) method without needing to perform individual reads, which is particularly useful for applications with many tags that need regular updates.
|
|
70
|
+
|
|
71
|
+
## Remote Variable synchronizer
|
|
72
|
+
|
|
73
|
+
The `RemoteVariableSynchronizer` allows users to keep some variables synched even if not in use by any current page, it will keep all the children elements updated with the field. This method handles an automatic optimization of the Tags in order to minimize the number of read/write from the controller (while the `RemoteRead` performs a request per each tag and it is much slower)
|
|
74
|
+
|
|
75
|
+
> [!NOTE]
|
|
76
|
+
> When the `RemoteVariableSynchronizer` is created, it will collect all the children variables and create an optimized read request to the controller, so it is important to create it only once and not every time a page is opened or a variable is needed.
|
|
77
|
+
|
|
78
|
+
> [!WARNING]
|
|
79
|
+
> The `RemoteVariableSynchronizer` might have significant impact on the communication driver, especially if used with a large number of tags. It is recommended to use it wisely and only for a small number of tags.
|
|
80
|
+
|
|
81
|
+
### Providing a TimeSpan argument
|
|
82
|
+
|
|
83
|
+
The `TimeSpan` argument of the `RemoteVariableSynchronizer` should be used to avoid overloading the field device. If the `RemoteVariableSynchronizer` is created without any arguments, it defaults to full-speed synchronization, which can be too fast for the field device to handle. This can lead to performance issues because the iPC (industrial PC) is typically faster than the controller. By specifying a `TimeSpan` argument, you define a refresh interval between subsequent reads, which helps in managing the load on the field device and ensures smoother operation.
|
|
84
|
+
|
|
85
|
+
Avoid this constructor: `variableSynchronizer = new RemoteVariableSynchronizer();`
|
|
86
|
+
Use this instead: `variableSynchronizer = new RemoteVariableSynchronizer(new TimeSpan.FromSeconds(5));`
|
|
87
|
+
|
|
88
|
+
The argument can be configure as needed, depending on the speed of the synchronization which is required.
|
|
89
|
+
|
|
90
|
+
### RemoteVariableSynchronizer example
|
|
91
|
+
|
|
92
|
+
```csharp
|
|
93
|
+
public override void Start()
|
|
94
|
+
{
|
|
95
|
+
// Get the field variable
|
|
96
|
+
motorSpeed = LogicObject.Owner.GetVariable("Speed");
|
|
97
|
+
// Create the VariableSynchronizer object and request the update every 5 seconds
|
|
98
|
+
var updateRate = TimeSpan.FromSeconds(5);
|
|
99
|
+
variableSynchronizer = new RemoteVariableSynchronizer(updateRate);
|
|
100
|
+
// Add the variables to the synchronizer
|
|
101
|
+
variableSynchronizer.Add(motorSpeed);
|
|
102
|
+
// Add the event to listen to value changes
|
|
103
|
+
motorSpeed.VariableChange += MotorSpeed_VariableChange;
|
|
104
|
+
}
|
|
105
|
+
public override void Stop()
|
|
106
|
+
{
|
|
107
|
+
// Destroy the synchronized on stop
|
|
108
|
+
variableSynchronizer?.Dispose();
|
|
109
|
+
}
|
|
110
|
+
private void MotorSpeed_VariableChange(object sender, VariableChangeEventArgs e)
|
|
111
|
+
{
|
|
112
|
+
// Event listener when value change is detected
|
|
113
|
+
if (motorSpeed.Value > 200) {
|
|
114
|
+
Log.Warning("Speed limit reached!");
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
private IUAVariable motorSpeed;
|
|
118
|
+
private RemoteVariableSynchronizer variableSynchronizer;
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
## RemoteRead
|
|
122
|
+
|
|
123
|
+
Remote read is a inline read of the field value, code is stopped to read the value, once the variable is updated, the code keeps running
|
|
124
|
+
|
|
125
|
+
> [!NOTE]
|
|
126
|
+
> The `ChildrenRemoteRead` is just as "slow" as the `RemoteRead`, it is just a recursive approach to a normal `RemoteRead`
|
|
127
|
+
|
|
128
|
+
### Read simple tags
|
|
129
|
+
|
|
130
|
+
```csharp
|
|
131
|
+
[ExportMethod]
|
|
132
|
+
public void ReadBitFromPlcInteger(out int value)
|
|
133
|
+
{
|
|
134
|
+
// Get the plc tag to read
|
|
135
|
+
var tag1 = Project.Current.GetVariable("CommDrivers/RAEtherNet_IPDriver1/RAEtherNet_IPStation1/Tags/Program:CustomUdtProgram/program_dint_1D");
|
|
136
|
+
// Read the new values
|
|
137
|
+
var variableValue = tag1.RemoteRead();
|
|
138
|
+
// Do something with the values
|
|
139
|
+
Log.Info($"Value of the plc tag is: {variableValue}");
|
|
140
|
+
}
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
### Read arrays
|
|
144
|
+
|
|
145
|
+
```csharp
|
|
146
|
+
[ExportMethod]
|
|
147
|
+
public void ReadBitFromPlcInteger(out int value)
|
|
148
|
+
{
|
|
149
|
+
// Get the plc tag to read
|
|
150
|
+
var tag1 = Project.Current.Get<FTOptix.RAEtherNetIP.Tag>("CommDrivers/RAEtherNet_IPDriver1/RAEtherNet_IPStation1/Tags/Program:CustomUdtProgram/program_dint_1D");
|
|
151
|
+
// Create the remote read object
|
|
152
|
+
var remoteVariables = new List<RemoteVariable>()
|
|
153
|
+
{
|
|
154
|
+
// Add the variables to read to the list
|
|
155
|
+
new RemoteVariable(tag1),
|
|
156
|
+
};
|
|
157
|
+
// Read the new values
|
|
158
|
+
var values = InformationModel.RemoteRead(remoteVariables).ToList();
|
|
159
|
+
// Get the field values to a C# variable
|
|
160
|
+
var plcArray = (int[])values[0].Value.Value;
|
|
161
|
+
// Do something with the values
|
|
162
|
+
Log.Info($"Value of first element of the array is {plcArray[0]}");
|
|
163
|
+
Log.Info($"Bit 5 of first element of the array is {GetBitValue(plcArray[0], 5)}");
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
private bool GetBitValue(int inputValue, int bitPosition)
|
|
167
|
+
{
|
|
168
|
+
// Read a specific bit from a word
|
|
169
|
+
var array = new BitArray(new int[] { inputValue });
|
|
170
|
+
return array[bitPosition];
|
|
171
|
+
}
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
### Adding a timeout
|
|
175
|
+
|
|
176
|
+
```csharp
|
|
177
|
+
// Get the variable to update
|
|
178
|
+
var currentSpeed = Project.Current.GetVariable("Model/Motor/Speed");
|
|
179
|
+
// Try to get the most updated value with a timeout of 100ms
|
|
180
|
+
var newValue = currentSpeed.RemoteRead(100.0);
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
## RemoteWrite
|
|
184
|
+
|
|
185
|
+
Similar to the `RemoteRead`, when a value should be sent immediately to the field, a `RemoteWrite` method can be used
|
|
186
|
+
|
|
187
|
+
### Write a simple value
|
|
188
|
+
|
|
189
|
+
```csharp
|
|
190
|
+
// Which tag to update
|
|
191
|
+
var currentSpeed = Project.Current.GetVariable("Model/Motor/Speed");
|
|
192
|
+
// Write the new value (no write timeout)
|
|
193
|
+
currentSpeed.RemoteWrite(42);
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
### Write multiple values
|
|
197
|
+
|
|
198
|
+
```csharp
|
|
199
|
+
// Which tags to update
|
|
200
|
+
var tag1 = Project.Current.Get<Tag>("CommDrivers/CodesysDriver1/CodesysStation1/Tags/Application/PLC_PRG/VAR1");
|
|
201
|
+
var tag3 = Project.Current.Get<Tag>("CommDrivers/CodesysDriver1/CodesysStation1/Tags/Application/PLC_PRG/VAR3");
|
|
202
|
+
// Create the RemoteWrite object
|
|
203
|
+
var remoteVariableValues = new List<RemoteVariableValue>()
|
|
204
|
+
{
|
|
205
|
+
new RemoteVariableValue(tag3, 0),
|
|
206
|
+
new RemoteVariableValue(tag1, 123)
|
|
207
|
+
};
|
|
208
|
+
// Perform the write operation (no write timeout)
|
|
209
|
+
InformationModel.RemoteWrite(remoteVariableValues);
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
### Adding a timeout
|
|
213
|
+
|
|
214
|
+
```csharp
|
|
215
|
+
// Get the variable to update
|
|
216
|
+
var currentSpeed = Project.Current.GetVariable("Model/Motor/Speed");
|
|
217
|
+
// Try to write the value 150 with a timeout of 100ms
|
|
218
|
+
currentSpeed.RemoteWrite(150, 100.0);
|
|
219
|
+
```
|