@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,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
|