@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.
Files changed (60) hide show
  1. package/CHANGELOG.md +20 -0
  2. package/README.md +6 -6
  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/optix/SKILL.md +68 -0
  8. package/vendor/optix/netlogic/SOURCE.json +12 -0
  9. package/vendor/optix/netlogic/index.json +1987 -0
  10. package/vendor/optix/netlogic/pages/accessing-project-nodes.md +113 -0
  11. package/vendor/optix/netlogic/pages/advanced-svg.md +124 -0
  12. package/vendor/optix/netlogic/pages/alarming.md +489 -0
  13. package/vendor/optix/netlogic/pages/async-tasks.md +236 -0
  14. package/vendor/optix/netlogic/pages/audit-signature.md +91 -0
  15. package/vendor/optix/netlogic/pages/colors.md +122 -0
  16. package/vendor/optix/netlogic/pages/companion-specs.md +77 -0
  17. package/vendor/optix/netlogic/pages/creating-objects.md +370 -0
  18. package/vendor/optix/netlogic/pages/database-interaction.md +437 -0
  19. package/vendor/optix/netlogic/pages/datagrids.md +123 -0
  20. package/vendor/optix/netlogic/pages/debugging-tips.md +605 -0
  21. package/vendor/optix/netlogic/pages/designtime-tags-import.md +531 -0
  22. package/vendor/optix/netlogic/pages/dialog-boxes.md +150 -0
  23. package/vendor/optix/netlogic/pages/dispatch-events.md +200 -0
  24. package/vendor/optix/netlogic/pages/dynamic-links.md +825 -0
  25. package/vendor/optix/netlogic/pages/element-access.md +1208 -0
  26. package/vendor/optix/netlogic/pages/engineering-units.md +298 -0
  27. package/vendor/optix/netlogic/pages/events.md +340 -0
  28. package/vendor/optix/netlogic/pages/execute-command.md +202 -0
  29. package/vendor/optix/netlogic/pages/fto-studio-cli.md +99 -0
  30. package/vendor/optix/netlogic/pages/good-practices.md +50 -0
  31. package/vendor/optix/netlogic/pages/information-model.md +89 -0
  32. package/vendor/optix/netlogic/pages/log-output.md +22 -0
  33. package/vendor/optix/netlogic/pages/managing-aliases.md +22 -0
  34. package/vendor/optix/netlogic/pages/manipulate-nodes.md +72 -0
  35. package/vendor/optix/netlogic/pages/mqtt.md +240 -0
  36. package/vendor/optix/netlogic/pages/netlogic-lifecycle.md +270 -0
  37. package/vendor/optix/netlogic/pages/netlogic-overview.md +155 -0
  38. package/vendor/optix/netlogic/pages/opcua.md +325 -0
  39. package/vendor/optix/netlogic/pages/plc-tags.md +219 -0
  40. package/vendor/optix/netlogic/pages/queries.md +1297 -0
  41. package/vendor/optix/netlogic/pages/random-generation.md +90 -0
  42. package/vendor/optix/netlogic/pages/recipes.md +375 -0
  43. package/vendor/optix/netlogic/pages/recipex.md +2383 -0
  44. package/vendor/optix/netlogic/pages/regex.md +15 -0
  45. package/vendor/optix/netlogic/pages/register-observers.md +346 -0
  46. package/vendor/optix/netlogic/pages/reports.md +141 -0
  47. package/vendor/optix/netlogic/pages/resource-uri.md +308 -0
  48. package/vendor/optix/netlogic/pages/runtime-tags-import.md +280 -0
  49. package/vendor/optix/netlogic/pages/serial-port.md +357 -0
  50. package/vendor/optix/netlogic/pages/sessions.md +58 -0
  51. package/vendor/optix/netlogic/pages/system-node.md +153 -0
  52. package/vendor/optix/netlogic/pages/template-library.md +127 -0
  53. package/vendor/optix/netlogic/pages/threading.md +236 -0
  54. package/vendor/optix/netlogic/pages/translations.md +417 -0
  55. package/vendor/optix/netlogic/pages/trends.md +128 -0
  56. package/vendor/optix/netlogic/pages/ui-animations.md +115 -0
  57. package/vendor/optix/netlogic/pages/users-groups.md +416 -0
  58. package/vendor/optix/netlogic/pages/variables-formatting.md +16 -0
  59. package/vendor/optix/netlogic/pages/variables-interaction.md +557 -0
  60. package/vendor/optix/netlogic/pages/variables-structure.md +155 -0
@@ -0,0 +1,825 @@
1
+ # Dynamic Links
2
+
3
+ > [!IMPORTANT]
4
+ > Starting from FactoryTalk Optix 1.7.x, if the DynamicLink optimization is enabled at `Project Root > RuntimeOptimization > DynamicLinks`, all DynamicLinks in the project will be automatically converted to StaticLinks before the deployment and the `DynamicLink` node wil effectively be removed, therefore, any DynamicLink manipulation at runtime with any of the snippets below will not be possible. If a specific link needs to be manipulated at runtime, it should be manually excluded from the optimization by navigating to the DynamicLink popup in the IDE, then in the Advanced tab click the gear icon and set `Disabled` in the `Runtime Optimization` menu.
5
+
6
+ > [!NOTE]
7
+ > **API Version Guide:** This documentation covers multiple FactoryTalk Optix versions. When creating dynamic links to aliases, use `SetDynamicLinkToAlias()` for **version 1.8.x and later** (recommended), or the manual path construction approach for **version 1.7.x and earlier** (legacy). The modern API is simpler, more maintainable, and type-safe.
8
+
9
+ ## Table of Contents
10
+
11
+ - [Simple Dynamic Link](#simple-dynamic-link)
12
+ - [Dynamic link manipulation](#dynamic-link-manipulation)
13
+ - [Resolve a DynamicLink](#resolve-a-dynamiclink)
14
+ - [Check for a DynamicLink](#check-for-a-dynamiclink)
15
+ - [Check for broken DynamicLink](#check-for-broken-dynamiclink)
16
+ - [Change the EU Mode of a DynamicLink](#change-the-eu-mode-of-a-dynamiclink)
17
+ - [Dynamic Link to a bit indexed word](#dynamic-link-to-a-bit-indexed-word)
18
+ - [Dynamic Link to a single element of array variable](#dynamic-link-to-a-single-element-of-array-variable)
19
+ - [Create a dynamic link to a specific element of an array variable](#create-a-dynamic-link-to-a-specific-element-of-an-array-variable)
20
+ - [Find broken dynamic links in the project](#find-broken-dynamic-links-in-the-project)
21
+ - [Formatted dynamic link](#formatted-dynamic-link)
22
+ - [Dynamic link to an Alias](#dynamic-link-to-an-alias)
23
+ - [FactoryTalk Optix 1.8.x and later (Recommended)](#factorytalk-optix-18x-and-later-recommended)
24
+ - [FactoryTalk Optix up to 1.7.x (Legacy)](#factorytalk-optix-up-to-17x-legacy)
25
+ - [Advanced dynamic links](#advanced-dynamic-links)
26
+ - [Understanding Converters](#understanding-converters)
27
+ - [Creating Converters](#creating-converters)
28
+ - [Creating a String Formatter](#creating-a-string-formatter)
29
+ - [Using translation keys in Format (dictionary namespace aware)](#using-translation-keys-in-format-dictionary-namespace-aware)
30
+ - [Important Notes](#important-notes)
31
+ - [Creating an Expression Evaluator](#creating-an-expression-evaluator)
32
+ - [Supported Expression Syntax](#supported-expression-syntax)
33
+ - [Creating a Key-Value Converter (Instance)](#creating-a-key-value-converter-instance)
34
+ - [Simple Example Using Pairs Object](#simple-example-using-pairs-object)
35
+ - [Advanced Example With Dynamic Links](#advanced-example-with-dynamic-links)
36
+ - [Creating a Key-Value Converter (Type)](#creating-a-key-value-converter-type)
37
+ - [Creating a Conditional Converter](#creating-a-conditional-converter)
38
+
39
+ ## Simple Dynamic Link
40
+
41
+ By default a `DynamicLink` is made as _Read only_ unless you specify different mode
42
+
43
+ ```csharp
44
+ ...
45
+ // Get the element where the dynamic link has to be added
46
+ var myObj = Owner.Get<Motor>("Motor1");
47
+ // Reach the specific variable
48
+ var speedValue = Owner.GetObject("SpeedLabel").GetVariable("Text");
49
+ // Create the dynamic link and specify the mode (optional - read only if empty)
50
+ myObj.SpeedVariable.SetDynamicLink(speedValue, DynamicLinkMode.Read);
51
+ ...
52
+ ```
53
+
54
+ ### Dynamic link manipulation
55
+
56
+ > [!WARNING]
57
+ > Manipulating a dynamic link is a very advanced topic and should be used with caution. It is recommended to use the default dynamic link APIs unless you have a specific use case.
58
+
59
+ #### Resolve a DynamicLink
60
+
61
+ ```csharp
62
+ ...
63
+ // Get the element containing a DynamicLink
64
+ TrendPen originalPen = InformationModel.Get<TrendPen>(Owner.GetAlias("AliasPen").NodeId);
65
+ // Read the variable value
66
+ var myVariable = (String)originalPen.Refs.GetVariable(FTOptix.CoreBase.ReferenceTypes.HasDynamicLink)?.Value;
67
+ // Resolve the dynamic link target
68
+ var result = LogicObject.Context.ResolvePath(myVariable);
69
+ var resolvedNode = result.ResolvedNode;
70
+ // Set the target variable somewhere in the project
71
+ Owner.Find<ComboBox>("ComboBox1").SelectedItem = resolvedNode.Owner.NodeId;
72
+ ...
73
+ ```
74
+
75
+ ```csharp
76
+ ...
77
+ // Get the element containing a DynamicLink
78
+ var textVariable = Owner.Children.Get<IUAVariable>("Text");
79
+ // Get the DynamicLink node
80
+ var dynamicLinkNode = textVariable.Refs.GetVariable(FTOptix.CoreBase.ReferenceTypes.HasDynamicLink);
81
+ // Get the pointed node
82
+ var pointedNode = dynamicLinkNode.Refs.GetNodes(FTOptix.Core.ReferenceTypes.Resolves).First();
83
+ ...
84
+ ```
85
+
86
+ #### Check for a DynamicLink
87
+
88
+ ```csharp
89
+ // Check if the current node has a dynamic link
90
+ var dynamicLink = inputVariable.Refs.GetVariable(FTOptix.CoreBase.ReferenceTypes.HasDynamicLink);
91
+ // Retrieve the path of the dynamic link
92
+ var dynamicLinkPath = (string)dynamicLink.Value;
93
+ // Resolve the dynamic link and get to the target node
94
+ var targetVariable = LogicObject.Context.ResolvePath(inputVariable, dynamicLinkPath).ResolvedNode;
95
+ ```
96
+
97
+ #### Check for broken DynamicLink
98
+
99
+ ```csharp
100
+ [ExportMethod]
101
+ public void GetBrokenDynamicLinks()
102
+ {
103
+ var nodesWithBrokenDynamicLink = new List<string>();
104
+ var projectNodes = Project.Current.Parent.FindNodesByType<IUANode>();
105
+
106
+ foreach (var n in projectNodes)
107
+ {
108
+ // Check if the current node has a dynamic link
109
+ var dynamicLink = n.Refs.GetVariable(FTOptix.CoreBase.ReferenceTypes.HasDynamicLink);
110
+ if (dynamicLink == null) continue;
111
+ // Retrieve the path of the dynamic link
112
+ var dynamicLinkPath = (string)dynamicLink.Value;
113
+ // Resolve the dynamic link and get to the target node
114
+ var targetVariable = LogicObject.Context.ResolvePath(n, dynamicLinkPath).ResolvedNode;
115
+ if (targetVariable == null)
116
+ {
117
+ nodesWithBrokenDynamicLink.Add("Node BrowsePath: " + Log.Node(n));
118
+ }
119
+ }
120
+
121
+ foreach (var info in nodesWithBrokenDynamicLink)
122
+ {
123
+ Log.Info(info);
124
+ }
125
+ }
126
+ ```
127
+
128
+ #### Change the EU Mode of a DynamicLink
129
+
130
+ ```csharp
131
+ [ExportMethod]
132
+ public void SetEuModeOfText()
133
+ {
134
+ // Get to the label that will display the value of the variable
135
+ var myLabelText = Owner.GetVariable("Label1/Text");
136
+ // Get the dynamic link at the text property
137
+ var dynamicLink = myLabelText.Refs.GetVariable(FTOptix.CoreBase.ReferenceTypes.HasDynamicLink);
138
+ // Set the EU mode of the dynamic link to "Localize"
139
+ ((DynamicLink)dynamicLink).EUMode = DynamicLinkEUMode.SetParentLocalizedEngineeringUnit;
140
+ // Set the EU mode of the dynamic link to "Set Parent as Source" (no conversion)
141
+ //((DynamicLink)dynamicLink).EUMode = DynamicLinkEUMode.SetParentEngineeringUnitAsSource;
142
+ }
143
+ ```
144
+
145
+ #### Dynamic Link to a bit indexed word
146
+
147
+ ```csharp
148
+ [ExportMethod]
149
+ public void AddDynamicLinkToBitOfIntegerVariable()
150
+ {
151
+ // Get the variable
152
+ var myBitAlarm = Project.Current.GetVariable("Model/BitVariable");
153
+ // Create a fake variable
154
+ IUAVariable tempVariable = null;
155
+ // Add a dynamic link to the fake variable (to trigger the link mechanism)
156
+ myBitAlarm.SetDynamicLink(tempVariable, DynamicLinkMode.ReadWrite);
157
+ // Replace the dynamic link content to the real target value using HasDynamicLink reference
158
+ var myBitAlarmDl = myBitAlarm.Refs.GetVariable(FTOptix.CoreBase.ReferenceTypes.HasDynamicLink) as IUAVariable;
159
+ if (myBitAlarmDl != null)
160
+ myBitAlarmDl.Value = "../Int32Variable.1";
161
+ }
162
+ ```
163
+
164
+ #### Dynamic Link to a single element of array variable
165
+
166
+ ```csharp
167
+ private void StuffDynamicLinkToArrayElement(IUAVariable sourceVariable, IUAVariable targetDynamicLink, uint sourceVariableArrayIndex, int targetDynamicLinkArrayIndex, DynamicLinkMode dynamicLinkMode)
168
+ {
169
+ // Prepare the BrowseName for the dynamic link variable
170
+ string dynamicLinkVariableBrowseName = $"DynamicLink_{sourceVariableArrayIndex}";
171
+ // Create the dynamic link object
172
+ DynamicLink newDynamicLink = InformationModel.MakeVariable<DynamicLink>(dynamicLinkVariableBrowseName, FTOptix.Core.DataTypes.NodePath);
173
+ // Set the dynamic link values
174
+ newDynamicLink.Value = DynamicLinkPath.MakePath(sourceVariable, targetDynamicLink);
175
+ newDynamicLink.Mode = dynamicLinkMode;
176
+ // Get the parent element access variable as Struct for getting the values
177
+ Struct parentElementAccess = newDynamicLink.ParentElementAccessVariable.Value;
178
+ // Generate the new values for the parent element access variable (the first element is the index in case of an array, the second is the field index in case of struct)
179
+ var newValues = parentElementAccess.Values.ToList();
180
+ // Set the target index as array of uint (OPCUA specs)
181
+ newValues[0] = new uint[]{sourceVariableArrayIndex};
182
+ // Overwrite the struct with the new values
183
+ newDynamicLink.ParentElementAccessVariable.Value = new Struct(parentElementAccess.DataTypeId,newValues);
184
+ // Check if the target of dynamic link is an array
185
+ if (targetDynamicLinkArrayIndex >= 0)
186
+ {
187
+ //If it is set the index by adding square brackets with inside the index value at the end of the path
188
+ newDynamicLink.Value = $"{newDynamicLink.Value.Value}[{targetDynamicLinkArrayIndex}]";
189
+ }
190
+ // Add the dynamic link reference to the dynamic link (OPCUA specs)
191
+ sourceVariable.Refs.AddReference(FTOptix.CoreBase.ReferenceTypes.HasDynamicLink, newDynamicLink);
192
+ // Set the proper modelling rule (OPCUA specs)
193
+ newDynamicLink.SetModellingRuleRecursive();
194
+ }
195
+ ```
196
+
197
+ ### Create a dynamic link to a specific element of an array variable
198
+
199
+ ```csharp
200
+ /// <summary>
201
+ /// Creates a DynamicLink variable that points to a single element of a target array variable.
202
+ /// Useful when you want to map one element of an array to another array element.
203
+ /// </summary>
204
+ [ExportMethod]
205
+ public void StuffDynamicLinkToArrayElement(IUAVariable targetVariable, IUAVariable targetDynamicLink, uint arrayIndexToLink, int targetDynamicLinkArrayIndex, DynamicLinkMode dynamicLinkMode)
206
+ {
207
+ // Prepare the BrowseName for the dynamic link variable
208
+ string dynamicLinkVariableBrowseName = $"DynamicLink_{arrayIndexToLink}";
209
+ // Create the dynamic link object
210
+ DynamicLink newDynamicLink = InformationModel.MakeVariable<DynamicLink>(dynamicLinkVariableBrowseName, FTOptix.Core.DataTypes.NodePath);
211
+ // Set the dynamic link base path and mode
212
+ newDynamicLink.Value = DynamicLinkPath.MakePath(targetVariable, targetDynamicLink);
213
+ newDynamicLink.Mode = dynamicLinkMode;
214
+ newDynamicLink.ParentArrayIndexVariable.Value = arrayIndexToLink;
215
+ // If the target is an array element, append the index
216
+ if (targetDynamicLinkArrayIndex >= 0)
217
+ {
218
+ newDynamicLink.Value = $"{newDynamicLink.Value.Value}[{targetDynamicLinkArrayIndex}]";
219
+ }
220
+ // Add the dynamic link reference to the source variable
221
+ targetVariable.Refs.AddReference(FTOptix.CoreBase.ReferenceTypes.HasDynamicLink, newDynamicLink);
222
+ // Ensure modelling rules are set
223
+ newDynamicLink.SetModellingRuleRecursive();
224
+ }
225
+ ```
226
+
227
+ ### Find broken dynamic links in the project
228
+
229
+ > [!TIP]
230
+ > This feature is now available as native functionality in FactoryTalk Optix starting from version 1.6.x.
231
+
232
+ ```csharp
233
+ /// <summary>
234
+ /// Scans project nodes searching for DynamicLink variables that cannot be resolved.
235
+ /// Logs the browse path of nodes with broken dynamic links.
236
+ /// </summary>
237
+ [ExportMethod]
238
+ public void GetBrokenDynamicLinks()
239
+ {
240
+ var nodesWithBrokenDynamicLink = new List<string>();
241
+ var projectNodes = Project.Current.Parent.FindNodesByType<IUANode>();
242
+
243
+ foreach (var n in projectNodes)
244
+ {
245
+ var dynamicLink = n.Refs.GetVariable(FTOptix.CoreBase.ReferenceTypes.HasDynamicLink);
246
+ if (dynamicLink == null) continue;
247
+ var dynamicLinkPath = (string)dynamicLink.Value;
248
+ var targetVariable = LogicObject.Context.ResolvePath(n, dynamicLinkPath).ResolvedNode;
249
+ if (targetVariable == null)
250
+ {
251
+ nodesWithBrokenDynamicLink.Add("Node BrowsePath: " + Log.Node(n));
252
+ }
253
+ }
254
+
255
+ foreach (var info in nodesWithBrokenDynamicLink)
256
+ {
257
+ Log.Info(info);
258
+ }
259
+ }
260
+ ```
261
+
262
+ #### Formatted dynamic link
263
+
264
+ A formatted dynamic link is very similar to a string formatter and it is used to dynamically reference different objects in the project, such as using the same widget to control different motors using a SpinBox
265
+
266
+ ```csharp
267
+ public void StuffCreateNewDynamicLinkFormatter()
268
+ {
269
+ // Get to the text box where the dynamic link has to be created
270
+ IUAVariable targetVariable = Owner.Get("TextBox1").GetVariable("Text");
271
+ // Remove dynamic link (if any)
272
+ targetVariable.ResetDynamicLink();
273
+ // Create a new empty dynamic link object
274
+ DynamicLink newDynamicLink = InformationModel.MakeVariable<DynamicLink>("DynamicLink", FTOptix.Core.DataTypes.NodePath);
275
+ newDynamicLink.Value = "";
276
+ // Create a string formatter to be added to the dynamic link
277
+ StringFormatter newStringFormatter = InformationModel.MakeObject<StringFormatter>("DynamicLinkFormatter", FTOptix.CoreBase.ObjectTypes.StringFormatter);
278
+ // Set the proper format for the string formatter, where curly brackets are used as placeholders
279
+ newStringFormatter.Format = "/Objects/StuffTest/Model/Folder1/Variable5[{0},{1}]";
280
+ // Set the sources for the placeholders
281
+ IUAVariable source0 = InformationModel.MakeVariable("Source0", OpcUa.DataTypes.BaseDataType);
282
+ IUAVariable source1 = InformationModel.MakeVariable("Source1", OpcUa.DataTypes.BaseDataType);
283
+ source0.SetDynamicLink(Project.Current.GetVariable("Model/Folder1/Variable1"));
284
+ source1.SetDynamicLink(Project.Current.GetVariable("Model/Folder1/Variable2"));
285
+ // Add the references about the children variables to the string formatter
286
+ newStringFormatter.Refs.AddReference(FTOptix.CoreBase.ReferenceTypes.HasSource, source0);
287
+ newStringFormatter.Refs.AddReference(FTOptix.CoreBase.ReferenceTypes.HasSource, source1);
288
+ // Set the dynamic link mode (if needed)
289
+ newDynamicLink.Mode = DynamicLinkMode.ReadWrite;
290
+ // Set the proper references and modelling rules (OPCUA specs)
291
+ newDynamicLink.Refs.AddReference(FTOptix.CoreBase.ReferenceTypes.HasConverter, newStringFormatter);
292
+ newStringFormatter.SetModellingRuleRecursive();
293
+ targetVariable.Refs.AddReference(FTOptix.CoreBase.ReferenceTypes.HasDynamicLink, newDynamicLink);
294
+ newDynamicLink.SetModellingRuleRecursive();
295
+ }
296
+ ```
297
+
298
+ ### Dynamic link to an Alias
299
+
300
+ #### FactoryTalk Optix 1.8.x and later (Recommended)
301
+
302
+ > [!NOTE]
303
+ > The `SetDynamicLinkToAlias()` API is the recommended approach for FactoryTalk Optix 1.8.x and later. It provides a cleaner, more intuitive way to create dynamic links to alias nodes without manual path construction.
304
+
305
+ ##### Link to a direct child of an alias
306
+
307
+ ```csharp
308
+ [ExportMethod]
309
+ public void CreateDynamicLinkToAlias()
310
+ {
311
+ // Get the Text variable of a TextBox
312
+ var myVariable = Owner.GetVariable("TextBox1/Text");
313
+ // Link to {MotorAlias}/Speed in ReadWrite mode
314
+ myVariable.SetDynamicLinkToAlias("MotorAlias", new string[] { "Speed" }, DynamicLinkMode.ReadWrite);
315
+ }
316
+ ```
317
+
318
+ ##### Link to a nested path inside an alias
319
+
320
+ ```csharp
321
+ [ExportMethod]
322
+ public void CreateDynamicLinkToNestedAlias()
323
+ {
324
+ // Get the Text variable of a Label
325
+ var myVariable = Owner.GetVariable("Label1/Text");
326
+ // Link to {MotorAlias}/Configuration/MaxSpeed in Read mode (default)
327
+ myVariable.SetDynamicLinkToAlias("MotorAlias", new string[] { "Configuration", "MaxSpeed" });
328
+ }
329
+ ```
330
+
331
+ ##### Link to a specific OPC UA attribute of an alias node
332
+
333
+ ```csharp
334
+ [ExportMethod]
335
+ public void CreateDynamicLinkToAliasAttribute()
336
+ {
337
+ // Get the Text variable of a Label
338
+ var myVariable = Owner.GetVariable("Label1/Text");
339
+ // Link to the BrowseName attribute of {MotorAlias} itself
340
+ myVariable.SetDynamicLinkToAlias("MotorAlias", AttributeId.BrowseName, DynamicLinkMode.Read);
341
+ }
342
+ ```
343
+
344
+ ##### Link to a specific OPC UA attribute of a sub-item inside an alias
345
+
346
+ ```csharp
347
+ [ExportMethod]
348
+ public void CreateDynamicLinkToAliasSubItemAttribute()
349
+ {
350
+ // Get the Text variable of a Label
351
+ var myVariable = Owner.GetVariable("Label1/Text");
352
+ // Link to the BrowseName attribute of {MotorAlias}/Configuration/Speed
353
+ myVariable.SetDynamicLinkToAlias("MotorAlias", new string[] { "Configuration", "Speed" }, AttributeId.BrowseName, DynamicLinkMode.Read);
354
+ }
355
+ ```
356
+
357
+ #### FactoryTalk Optix up to 1.7.x (Legacy)
358
+
359
+ > [!WARNING]
360
+ > This approach is deprecated and only needed for FactoryTalk Optix versions prior to 1.8.x. Use the `SetDynamicLinkToAlias()` API above for newer versions.
361
+
362
+ ```csharp
363
+ [ExportMethod]
364
+ public void CreateDynamicLinkToAlias()
365
+ {
366
+ var myVariable = Owner.GetVariable("TextBox1/Text");
367
+ myVariable.SetDynamicLink(null, DynamicLinkMode.ReadWrite);
368
+ var myVariableDl = myVariable.Refs.GetVariable(FTOptix.CoreBase.ReferenceTypes.HasDynamicLink);
369
+ if (myVariableDl != null)
370
+ myVariableDl.Value = "{MotorAlias}/Speed";
371
+ }
372
+ ```
373
+
374
+ ## Advanced dynamic links
375
+
376
+ > [!WARNING]
377
+ > Advanced dynamic links are a very advanced topic and should be used with caution. It is recommended to use the default dynamic link APIs unless you have a specific use case.
378
+
379
+ ### Understanding Converters
380
+
381
+ Converters transform values flowing through dynamic links. Before creating converters, understand these key concepts:
382
+
383
+ **SetConverter Behavior**: When you call `variable.SetConverter(converter)`:
384
+ - Any existing converter on the variable is automatically deleted
385
+ - Any existing dynamic link on the variable is automatically deleted
386
+ - The new converter becomes the transformation layer for that variable
387
+
388
+ **Converter Modes**:
389
+ - `DynamicLinkMode.Read` (or default) - One-way conversion: source → target only
390
+ - `DynamicLinkMode.ReadWrite` - Two-way conversion: allows source ↔ target updates
391
+ - `DynamicLinkMode.OneWayToSource` - One-way in opposite direction: target → source only
392
+
393
+ **Source Variable Naming**: For `StringFormatter` and `ExpressionEvaluator`, source variables **must** follow this naming pattern:
394
+ - `Source0`, `Source1`, `Source2`, etc.
395
+ - The number must match the placeholder index in the format/expression string
396
+
397
+ ### Creating Converters
398
+
399
+ ##### Creating a String Formatter
400
+
401
+ A `StringFormatter` converter combines multiple input variables into a formatted string using placeholders like `{0}`, `{1}`, etc.
402
+
403
+ ###### Using translation keys in `Format` (dictionary namespace aware)
404
+
405
+ When your format string is a translation key (instead of plain text), set the `Format` variable value as `LocalizedText` using the localization dictionary namespace index.
406
+
407
+ ```csharp
408
+ [ExportMethod]
409
+ public void CreateLocalizedStringFormatterConverter()
410
+ {
411
+ // Target variable that will show the translated and formatted value
412
+ var displayLabel = Owner.Get<Label>("Label1").TextVariable;
413
+
414
+ // Create formatter
415
+ var stringFormatter = InformationModel.Make<StringFormatter>("LocalizedFormatter");
416
+
417
+ // Resolve translation dictionary namespace index
418
+ ushort translationNamespaceIndex = GetTranslationNamespaceIndex();
419
+
420
+ // Set Format as a translation key using LocalizedText
421
+ // Example key value in dictionary: "MainKey1" => "Speed: {1}"
422
+ stringFormatter.GetVariable("Format").Value = new LocalizedText(translationNamespaceIndex, "MainKey1");
423
+
424
+ // Placeholder {1} requires Source1 (index must match)
425
+ var source1 = InformationModel.MakeVariable("Source1", OpcUa.DataTypes.BaseDataType);
426
+ source1.SetDynamicLink(Project.Current.GetVariable("Model/InjectionValue"));
427
+ stringFormatter.Refs.AddReference(FTOptix.CoreBase.ReferenceTypes.HasSource, source1);
428
+
429
+ displayLabel.SetConverter(stringFormatter);
430
+ }
431
+
432
+ private ushort GetTranslationNamespaceIndex()
433
+ {
434
+ var dictionary = Project.Current.Get("Translations/LocalizationDictionary1");
435
+ if (dictionary != null)
436
+ return (ushort)dictionary.NodeId.NamespaceIndex;
437
+
438
+ // Fallback to current logic namespace if dictionary is not found
439
+ return (ushort)LogicObject.NodeId.NamespaceIndex;
440
+ }
441
+ ```
442
+
443
+ ```csharp
444
+ [ExportMethod]
445
+ public void CreateStringFormatterConverter()
446
+ {
447
+ // Create a target variable that will display the formatted string
448
+ var displayLabel = Owner.Get<Label>("Label1").TextVariable;
449
+
450
+ // Create a StringFormatter converter with placeholders
451
+ var stringFormatter = InformationModel.Make<StringFormatter>("StringFormatter1");
452
+ stringFormatter.Format = "Temperature: {0}°C, Humidity: {1}%";
453
+
454
+ // Create source variables that will feed the placeholders
455
+ // Sources MUST be named "Source0", "Source1", etc. to match placeholder indices
456
+ var source0 = InformationModel.MakeVariable("Source0", OpcUa.DataTypes.BaseDataType);
457
+ var source1 = InformationModel.MakeVariable("Source1", OpcUa.DataTypes.BaseDataType);
458
+
459
+ // Add the source variables to the converter
460
+ stringFormatter.Refs.AddReference(FTOptix.CoreBase.ReferenceTypes.HasSource, source0);
461
+ stringFormatter.Refs.AddReference(FTOptix.CoreBase.ReferenceTypes.HasSource, source1);
462
+
463
+ // Connect the sources to actual variables via dynamic links
464
+ var temperatureVariable = Project.Current.GetVariable("Model/Temperature");
465
+ var humidityVariable = Project.Current.GetVariable("Model/Humidity");
466
+ source0.SetDynamicLink(temperatureVariable);
467
+ source1.SetDynamicLink(humidityVariable);
468
+
469
+ // Apply the converter to the target variable
470
+ // Note: SetConverter automatically removes any previous converter or dynamic link
471
+ displayLabel.SetConverter(stringFormatter);
472
+ }
473
+ ```
474
+
475
+ ###### Important Notes
476
+
477
+ - Source variables **must** be named `Source0`, `Source1`, `Source2`, etc., matching the placeholder indices in the format string
478
+ - Placeholder index and source index must match exactly: `{0}` uses `Source0`, `{1}` uses `Source1`, and so on
479
+ - If your translation value intentionally starts from `{1}`, create `Source1` (you do not need `Source0` unless `{0}` is used)
480
+ - The `SetConverter()` method automatically removes any previous converter or dynamic link from the target variable
481
+
482
+ ##### Creating an Expression Evaluator
483
+
484
+ An `ExpressionEvaluator` converter evaluates a mathematical expression using input variables as operands. This is useful for calculations like scaling, offset, or combining multiple values.
485
+
486
+ ```csharp
487
+ [ExportMethod]
488
+ public void CreateExpressionEvaluatorConverter()
489
+ {
490
+ // Get the target variable where the calculated result will be displayed
491
+ var resultLabel = Owner.Get<Label>("Label1").TextVariable;
492
+
493
+ // Get the source variables to use in the expression
494
+ var voltageVariable = Project.Current.GetVariable("Model/Voltage");
495
+ var currentVariable = Project.Current.GetVariable("Model/Current");
496
+
497
+ // Create the expression evaluator converter
498
+ var expressionEvaluator = InformationModel.MakeObject<ExpressionEvaluator>("PowerCalculator");
499
+
500
+ // Set a mathematical expression combining placeholders
501
+ // In this example: Power (Watts) = Voltage * Current
502
+ expressionEvaluator.Expression = "{0} * {1}";
503
+
504
+ // Create source variables that will feed the expression
505
+ // Sources MUST be named "Source0", "Source1", etc., matching placeholder indices
506
+ var source0 = InformationModel.MakeVariable("Source0", OpcUa.DataTypes.BaseDataType);
507
+ var source1 = InformationModel.MakeVariable("Source1", OpcUa.DataTypes.BaseDataType);
508
+
509
+ // Add source variables to the evaluator
510
+ expressionEvaluator.Refs.AddReference(FTOptix.CoreBase.ReferenceTypes.HasSource, source0);
511
+ expressionEvaluator.Refs.AddReference(FTOptix.CoreBase.ReferenceTypes.HasSource, source1);
512
+
513
+ // Connect sources to actual variables via dynamic links
514
+ source0.SetDynamicLink(voltageVariable);
515
+ source1.SetDynamicLink(currentVariable);
516
+
517
+ // Apply the converter to the target variable
518
+ resultLabel.SetConverter(expressionEvaluator);
519
+ }
520
+ ```
521
+
522
+ ###### Supported Expression Syntax
523
+
524
+ The expression evaluator supports multiple operators and functions, please see the full list in the [official documentation](https://www.rockwellautomation.com/en-us/docs/factorytalk-optix/1-7-0/contents-ditamap/creating-projects/converters/expression-evaluator.html).
525
+
526
+ ##### Creating a Key-Value Converter (Instance)
527
+
528
+ A `ValueMapConverter` instance maps discrete values (like integers) to other values (like localized text or colors). This is useful for displaying status text or changing colors based on numeric states.
529
+
530
+ This section creates and applies a converter instance directly to a target variable (the `Text` property of a `Label`).
531
+
532
+ ###### Simple Example Using Pairs Object
533
+
534
+ ```csharp
535
+ [ExportMethod]
536
+ public void CreateSimpleKeyValueConverter()
537
+ {
538
+ // Get the target variable that will display the mapped value
539
+ var statusLabel = Owner.Get<Label>("Label1").TextVariable;
540
+
541
+ // Create the ValueMapConverter (must pass the ObjectType explicitly)
542
+ var converter =
543
+ InformationModel.MakeObject<ValueMapConverter>("StatusConverter", FTOptix.CoreBase.ObjectTypes.ValueMapConverter);
544
+
545
+ // Create the Pairs container using a QualifiedName with the converter's namespace
546
+ var pairs =
547
+ InformationModel.MakeObject(new QualifiedName(FTOptix.CoreBase.ObjectTypes.ValueMapConverter.NamespaceIndex, "Pairs"));
548
+ pairs.ModellingRule = NamingRuleType.Mandatory;
549
+ converter.Add(pairs);
550
+
551
+ // Add simple key-value pairs
552
+ AddSimplePair(pairs, 0, "Idle");
553
+ AddSimplePair(pairs, 1, "Running");
554
+ AddSimplePair(pairs, 2, "Error");
555
+ AddSimplePair(pairs, 3, "Maintenance");
556
+
557
+ // Link the converter's source to the state variable
558
+ var deviceStateVariable = Project.Current.GetVariable("Model/DeviceState");
559
+ converter.SourceVariable.SetDynamicLink(deviceStateVariable);
560
+
561
+ // Apply the converter to the target variable
562
+ statusLabel.SetConverter(converter);
563
+ }
564
+
565
+ private void AddSimplePair(IUAObject pairsContainer, int key, string value)
566
+ {
567
+ // Create a ValueMapPair object
568
+ var pair = InformationModel.MakeObject($"Pair{key}", FTOptix.CoreBase.ObjectTypes.ValueMapPair);
569
+ pair.ModellingRule = NamingRuleType.Mandatory;
570
+
571
+ // Set the key value
572
+ var keyVariable = pair.GetVariable("Key");
573
+ keyVariable.ModellingRule = NamingRuleType.Mandatory;
574
+ keyVariable.DataType = OpcUa.DataTypes.Int32;
575
+ keyVariable.Value = key;
576
+
577
+ // Set the value
578
+ var valueVariable = pair.GetVariable("Value");
579
+ valueVariable.ModellingRule = NamingRuleType.Mandatory;
580
+ valueVariable.DataType = OpcUa.DataTypes.String;
581
+ valueVariable.Value = value;
582
+
583
+ // Add the pair to the Pairs container
584
+ pairsContainer.Add(pair);
585
+ }
586
+ ```
587
+
588
+ > [!NOTE]
589
+ > This simple approach creates hard-coded key-value pairs. Use the advanced approach below only if you need dynamic links for values that change at runtime based on other variables.
590
+
591
+ ###### Advanced Example With Dynamic Links
592
+
593
+ ```csharp
594
+ /// <summary>
595
+ /// Creates a ValueMapConverter with multiple key-value pairs, including dynamic links for runtime-resolved values.
596
+ /// </summary>
597
+ [ExportMethod]
598
+ public void CreateAdvancedKeyValueConverter()
599
+ {
600
+ const string aliasName = "{PopupAlias}";
601
+
602
+ // Get the target variable that will display the mapped value
603
+ var statusLabel = Owner.Get<Label>("Label1").TextVariable;
604
+
605
+ // Create the ValueMapConverter object that will map integer keys to localized text values
606
+ var converter =
607
+ InformationModel.MakeObject<ValueMapConverter>("KeyValueConverter1", FTOptix.CoreBase.ObjectTypes.ValueMapConverter);
608
+
609
+ // Create the Pairs container to hold all key-value mappings
610
+ var pairs =
611
+ InformationModel.MakeObject(new QualifiedName(FTOptix.CoreBase.ObjectTypes.ValueMapConverter.NamespaceIndex, "Pairs"));
612
+
613
+ // Set modeling rule to ensure pairs object follows converter structure requirements
614
+ pairs.ModellingRule = NamingRuleType.Mandatory;
615
+
616
+ // Add key-value pairs to the converter:
617
+ // Pair 0: Default fallback value (hard-coded)
618
+ AddAdvancedPair(pairs, 0, null, new LocalizedText("Default value", "en-US"));
619
+ // Pair 1: Dynamic link to first localized text variable
620
+ AddAdvancedPair(pairs, 1, $"{aliasName}/localizedText1", null);
621
+ // Pair 2: Dynamic link to second localized text variable
622
+ AddAdvancedPair(pairs, 2, $"{aliasName}/localizedText2", null);
623
+ // Pair 3: Hard-coded localized text for value 3
624
+ AddAdvancedPair(pairs, 3, null, new LocalizedText("text associated to value 3 (hard-coded)", "en-US"));
625
+
626
+ // Add the configured pairs collection to the converter
627
+ converter.Add(pairs);
628
+
629
+ // Configure the converter's source: link it to an integer variable that determines which value to display
630
+ converter.SourceVariable.SetDynamicLink(null, DynamicLinkMode.Read);
631
+ var srcDl = converter.SourceVariable.Refs.GetVariable(FTOptix.CoreBase.ReferenceTypes.HasDynamicLink) as IUAVariable;
632
+ if (srcDl != null)
633
+ srcDl.Value = $"{aliasName}/integer1";
634
+
635
+ // Apply the converter to the target variable
636
+ statusLabel.SetConverter(converter);
637
+ }
638
+
639
+ /// <summary>
640
+ /// Adds a key-value pair to the ValueMapConverter's pairs collection.
641
+ /// Each pair maps an integer key to either a dynamically-linked or hard-coded localized text value.
642
+ /// </summary>
643
+ /// <param name="pairs">The pairs collection to add the new pair to</param>
644
+ /// <param name="key">The integer key that will trigger this value when matched</param>
645
+ /// <param name="dynamicLinkString">Optional dynamic link path to a variable (takes precedence if provided)</param>
646
+ /// <param name="hardCodedText">Optional hard-coded localized text value (used when no dynamic link is provided)</param>
647
+ private void AddAdvancedPair(IUAObject pairs, int key, string dynamicLinkString, LocalizedText hardCodedText)
648
+ {
649
+ // Create unique browse name for this pair
650
+ string pairBrowseName = $"Pair{key}";
651
+
652
+ // Create the ValueMapPair object
653
+ var newPair = InformationModel.MakeObject(pairBrowseName, FTOptix.CoreBase.ObjectTypes.ValueMapPair);
654
+ newPair.ModellingRule = NamingRuleType.Mandatory;
655
+
656
+ // Get the Key and Value variables from the pair
657
+ var newKey = newPair.GetVariable("Key");
658
+ var newValue = newPair.GetVariable("Value");
659
+
660
+ // Set modeling rules to ensure proper structure
661
+ newKey.ModellingRule = NamingRuleType.Mandatory;
662
+ newValue.ModellingRule = NamingRuleType.Mandatory;
663
+
664
+ // Configure the Key variable with the integer key value
665
+ newKey.DataType = OpcUa.DataTypes.Int32;
666
+ newKey.Value = key;
667
+
668
+ // Configure the Value variable as LocalizedText type
669
+ newValue.DataType = OpcUa.DataTypes.LocalizedText;
670
+
671
+ // Set the value either through dynamic link or hard-coded value
672
+ // Dynamic link allows runtime changes to the displayed text
673
+ if (!string.IsNullOrEmpty(dynamicLinkString))
674
+ {
675
+ // Create dynamic link to external variable
676
+ newValue.SetDynamicLink(null, DynamicLinkMode.ReadWrite);
677
+ var pairDl = newValue.Refs.GetVariable(FTOptix.CoreBase.ReferenceTypes.HasDynamicLink) as IUAVariable;
678
+ if (pairDl != null)
679
+ pairDl.Value = dynamicLinkString;
680
+ }
681
+ else
682
+ {
683
+ // Use hard-coded localized text value
684
+ newValue.SetValue(hardCodedText);
685
+ }
686
+
687
+ // Add the configured pair to the pairs collection
688
+ pairs.Add(newPair);
689
+ }
690
+ ```
691
+
692
+ ##### Creating a Key-Value Converter (Type)
693
+
694
+ Use this approach when you want to create a reusable converter type in the project model (for example under the Converters folder), instead of applying a runtime instance directly to a target variable.
695
+
696
+ ```csharp
697
+ /// <summary>
698
+ /// Creates a new ValueMapConverter type in the Converters folder with key-value pairs
699
+ /// that map integer keys to localized text values. ModellingRule properties are set after
700
+ /// nodes are added to the project to ensure correct serialization behavior.
701
+ /// </summary>
702
+ [ExportMethod]
703
+ public void CreateKeyValueConverterType()
704
+ {
705
+ // Get the Converters folder
706
+ var parent = Project.Current.Get("Converters");
707
+ if (parent == null)
708
+ {
709
+ Log.Error("Converters folder not found in the project.");
710
+ return;
711
+ }
712
+
713
+ // Create the converter type object
714
+ var converter = InformationModel.MakeObjectType<ValueMapConverterType>("MyKeyValueConverter");
715
+
716
+ // Create the Pairs container
717
+ var pairs =
718
+ InformationModel.MakeObject(new QualifiedName(FTOptix.CoreBase.ObjectTypes.ValueMapConverter.NamespaceIndex, "Pairs"));
719
+
720
+ // Add key-value pairs
721
+ AddPairToKeyValueConverter(pairs, 0, null, new LocalizedText("Default value", "en-US"));
722
+ AddPairToKeyValueConverter(pairs, 1, null, new LocalizedText("text associated to value 1 (hard-coded)", "en-US"));
723
+ AddPairToKeyValueConverter(pairs, 2, null, new LocalizedText("text associated to value 2 (hard-coded)", "en-US"));
724
+ AddPairToKeyValueConverter(pairs, 3, null, new LocalizedText("text associated to value 3 (hard-coded)", "en-US"));
725
+
726
+ // Add pairs to the converter and add converter to the project
727
+ converter.Add(pairs);
728
+ parent.Add(converter);
729
+
730
+ // Set modelling rules after instantiation to control serialization
731
+ pairs.ModellingRule = NamingRuleType.None;
732
+ foreach (var pair in pairs.Children)
733
+ {
734
+ pair.ModellingRule = NamingRuleType.None;
735
+ foreach (var child in pair.Children)
736
+ child.ModellingRule = NamingRuleType.None;
737
+ }
738
+ }
739
+
740
+ /// <summary>
741
+ /// Adds a key-value pair to the ValueMapConverter's pairs collection.
742
+ /// Each pair maps an integer key to either a dynamically-linked or hard-coded localized text value.
743
+ /// </summary>
744
+ /// <param name="pairs">The pairs collection to add the new pair to</param>
745
+ /// <param name="key">The integer key that will trigger this value when matched</param>
746
+ /// <param name="dynamicLinkString">Optional dynamic link path to a variable (takes precedence if provided)</param>
747
+ /// <param name="hardCodedText">Optional hard-coded localized text value (used when no dynamic link is provided)</param>
748
+ private void AddPairToKeyValueConverter(IUAObject pairs, int key, string dynamicLinkString, LocalizedText hardCodedText)
749
+ {
750
+ // Create unique browse name for this pair
751
+ string pairBrowseName = $"Pair{key}";
752
+
753
+ // Create the ValueMapPair object
754
+ var newPair = InformationModel.MakeObject(pairBrowseName, FTOptix.CoreBase.ObjectTypes.ValueMapPair);
755
+
756
+ // Get the Key and Value variables from the pair
757
+ var newKey = newPair.GetVariable("Key");
758
+ var newValue = newPair.GetVariable("Value");
759
+
760
+ // Configure the Key variable with the integer key value
761
+ newKey.DataType = OpcUa.DataTypes.Int32;
762
+ newKey.Value = key;
763
+
764
+ // Configure the Value variable as LocalizedText type
765
+ newValue.DataType = OpcUa.DataTypes.LocalizedText;
766
+
767
+ // Set the value either through dynamic link or hard-coded value
768
+ if (!string.IsNullOrEmpty(dynamicLinkString))
769
+ {
770
+ // Create dynamic link to external variable
771
+ newValue.SetDynamicLink(null, DynamicLinkMode.ReadWrite);
772
+ var pairDl = newValue.Refs.GetVariable(FTOptix.CoreBase.ReferenceTypes.HasDynamicLink) as IUAVariable;
773
+ if (pairDl != null)
774
+ pairDl.Value = dynamicLinkString;
775
+ }
776
+ else
777
+ {
778
+ // Use hard-coded localized text value
779
+ newValue.SetValue(hardCodedText);
780
+ }
781
+
782
+ // Add the configured pair to the pairs collection
783
+ pairs.Add(newPair);
784
+ }
785
+ ```
786
+
787
+ ##### Creating a Conditional Converter
788
+
789
+ A `ConditionalConverter` outputs one of two values based on a boolean condition. This is useful for switching between states, such as changing a color based on a device status.
790
+
791
+ ```csharp
792
+ [ExportMethod]
793
+ public void CreateConditionalConverter()
794
+ {
795
+ // Get the target variable where the converted value will be displayed
796
+ var statusIndicatorColor = Owner.Get<Rectangle>("Rectangle1").FillColorVariable;
797
+
798
+ // Create the conditional converter
799
+ var conditionalConverter = InformationModel.MakeObject<ConditionalConverter>("StatusColorConverter");
800
+
801
+ // Configure the true condition value (device is running - green)
802
+ conditionalConverter.TrueValueVariable.DataType = FTOptix.Core.DataTypes.Color;
803
+ conditionalConverter.TrueValueVariable.Value = System.Drawing.Color.Green.ToArgb();
804
+
805
+ // Configure the false condition value (device is stopped - red)
806
+ conditionalConverter.FalseValueVariable.DataType = FTOptix.Core.DataTypes.Color;
807
+ conditionalConverter.FalseValueVariable.Value = System.Drawing.Color.Red.ToArgb();
808
+
809
+ // Link the condition variable to a boolean source
810
+ var deviceRunningVariable = Project.Current.GetVariable("Model/DeviceIsRunning");
811
+ conditionalConverter.ConditionVariable.SetDynamicLink(deviceRunningVariable);
812
+
813
+ // Set the converter mode (ReadWrite allows both directions, Read is one-way)
814
+ conditionalConverter.Mode = DynamicLinkMode.Read;
815
+
816
+ // Apply the converter to the target variable
817
+ statusIndicatorColor.SetConverter(conditionalConverter);
818
+ }
819
+ ```
820
+
821
+ ###### Important Notes
822
+ - The `ConditionVariable` must be linked to a boolean variable
823
+ - `TrueValueVariable` and `FalseValueVariable` must have the same data type as the target variable
824
+ - Use `DynamicLinkMode.Read` for one-way conversion (source → target)
825
+ - Use `DynamicLinkMode.ReadWrite` to allow bidirectional updates