@aryacode/dsh-engineering 0.5.0 → 0.7.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 +52 -0
- package/README.md +9 -8
- package/cordis.patch.yml +1 -1
- package/entries/netlogic.js +266 -0
- package/entries/optix.js +13 -0
- package/package.json +10 -10
- package/vendor/l5x/SKILL.md +27 -7
- package/vendor/l5x/lib/engine/change-set.d.ts +9 -1
- package/vendor/l5x/lib/engine/change-set.js +32 -7
- package/vendor/l5x/lib/engine/documentation.d.ts +58 -0
- package/vendor/l5x/lib/engine/documentation.js +237 -0
- package/vendor/l5x/lib/engine/instructions.d.ts +50 -0
- package/vendor/l5x/lib/engine/instructions.js +107 -0
- package/vendor/l5x/lib/engine/ladder.d.ts +74 -0
- package/vendor/l5x/lib/engine/ladder.js +209 -0
- package/vendor/l5x/lib/index.d.ts +1 -0
- package/vendor/l5x/lib/index.js +3 -1
- package/vendor/l5x/lib/model/routines.js +6 -1
- package/vendor/l5x/lib/session.d.ts +20 -0
- package/vendor/l5x/lib/session.js +36 -0
- package/vendor/l5x/lib/tools/document.d.ts +18 -0
- package/vendor/l5x/lib/tools/document.js +119 -0
- package/vendor/l5x/lib/tools/drafts.js +109 -3
- 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,1208 @@
|
|
|
1
|
+
# ElementAccess
|
|
2
|
+
|
|
3
|
+
## Overview
|
|
4
|
+
|
|
5
|
+
**ElementAccess** is a core FactoryTalk Optix mechanism that enables precise navigation and access to values within complex data structures. It allows you to navigate through nested structures, access array elements, and target specific fields within structured data types across the entire platform.
|
|
6
|
+
|
|
7
|
+
## Understanding ElementAccess Structure
|
|
8
|
+
|
|
9
|
+
### What is ElementAccess?
|
|
10
|
+
|
|
11
|
+
ElementAccess is a compound navigation path that combines two navigation strategies:
|
|
12
|
+
|
|
13
|
+
1. **ArrayIndex** - Direct array subscripts applied at the root level
|
|
14
|
+
2. **FieldIndexes** - Hierarchical field navigation through nested structures
|
|
15
|
+
|
|
16
|
+
Together, they provide a complete path to navigate from a starting node to any value within nested structures and arrays.
|
|
17
|
+
|
|
18
|
+
```csharp
|
|
19
|
+
public class ElementAccessStruct
|
|
20
|
+
{
|
|
21
|
+
public uint[] ArrayIndex { get; set; } // Root-level array indices
|
|
22
|
+
public FieldIndexStruct[] FieldIndexes { get; set; } // Field navigation path
|
|
23
|
+
}
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
### ArrayIndex
|
|
27
|
+
|
|
28
|
+
The **ArrayIndex** property is an array of unsigned integers that specifies array subscripts to apply at the root level before diving into the structure.
|
|
29
|
+
|
|
30
|
+
**Examples:**
|
|
31
|
+
|
|
32
|
+
- `ArrayIndex = new uint[] { }` - No array indexing (access root directly)
|
|
33
|
+
- `ArrayIndex = new uint[] { 0 }` - Access first element of array
|
|
34
|
+
- `ArrayIndex = new uint[] { 0, 1 }` - Multi-dimensional array access (rarely used in recipes)
|
|
35
|
+
|
|
36
|
+
```csharp
|
|
37
|
+
// Access first element of an array of structures
|
|
38
|
+
var elementAccess = new ElementAccessStruct
|
|
39
|
+
{
|
|
40
|
+
ArrayIndex = new uint[] { 0 },
|
|
41
|
+
FieldIndexes = new FieldIndexStruct[] { }
|
|
42
|
+
};
|
|
43
|
+
// Equivalent to: targetArray[0]
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
### FieldIndexes
|
|
47
|
+
|
|
48
|
+
The **FieldIndexes** property is an array of `FieldIndexStruct` instances that define the hierarchical path through nested structures.
|
|
49
|
+
|
|
50
|
+
```csharp
|
|
51
|
+
public class FieldIndexStruct
|
|
52
|
+
{
|
|
53
|
+
public uint FieldPos { get; set; } // Position of field in parent structure
|
|
54
|
+
public uint[] ArrayIndex { get; set; } // Array indices within this field
|
|
55
|
+
}
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
**FieldPos** - Field Position within Structure
|
|
59
|
+
|
|
60
|
+
Each structure type has its fields in a defined order. `FieldPos` specifies which field to access:
|
|
61
|
+
- `FieldPos = 0` - Access first field
|
|
62
|
+
- `FieldPos = 1` - Access second field
|
|
63
|
+
- `FieldPos = 2` - Access third field
|
|
64
|
+
- And so on...
|
|
65
|
+
|
|
66
|
+
**ArrayIndex within FieldIndexStruct** - Array Access at This Level
|
|
67
|
+
|
|
68
|
+
If the field itself is an array or multi-dimensional structure, specify array indices:
|
|
69
|
+
- `ArrayIndex = new uint[] { }` - No array indexing at this level
|
|
70
|
+
- `ArrayIndex = new uint[] { 2 }` - Access third element of field array
|
|
71
|
+
- `ArrayIndex = new uint[] { 1, 3 }` - Multi-dimensional access
|
|
72
|
+
|
|
73
|
+
### Visual Examples
|
|
74
|
+
|
|
75
|
+
#### Example 1: Simple Nested Structure
|
|
76
|
+
|
|
77
|
+
```csharp
|
|
78
|
+
// Model structure:
|
|
79
|
+
Settings (structure)
|
|
80
|
+
├── Motor (instance of Motor structure, field 0 of Settings)
|
|
81
|
+
│ ├── Speed (field 0 of Motor)
|
|
82
|
+
│ └── Torque (field 1 of Motor)
|
|
83
|
+
└── Conveyor (instance of Conveyor structure, field 1 of Settings)
|
|
84
|
+
└── Enable (field 0 of Conveyor)
|
|
85
|
+
|
|
86
|
+
// To access Settings.Motor.Torque:
|
|
87
|
+
var elementAccess = new ElementAccessStruct
|
|
88
|
+
{
|
|
89
|
+
ArrayIndex = new uint[] { },
|
|
90
|
+
FieldIndexes = new FieldIndexStruct[]
|
|
91
|
+
{
|
|
92
|
+
new FieldIndexStruct { FieldPos = 0, ArrayIndex = new uint[] { } }, // Navigate to Motor (field 0)
|
|
93
|
+
new FieldIndexStruct { FieldPos = 1, ArrayIndex = new uint[] { } } // Navigate to Torque (field 1 of Motor)
|
|
94
|
+
}
|
|
95
|
+
};
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
#### Example 2: Array with Nested Structures
|
|
99
|
+
|
|
100
|
+
```csharp
|
|
101
|
+
// Model structure:
|
|
102
|
+
Motors (array of Motor structures)
|
|
103
|
+
├── [0] Motor (instance of Motor structure, element 0 of the array)
|
|
104
|
+
│ ├── Speed (field 0 of Motor)
|
|
105
|
+
│ └── Torque (field 1 of Motor)
|
|
106
|
+
├── [1] Motor (instance of Motor structure, element 1 of the array)
|
|
107
|
+
│ ├── Speed (field 0 of Motor)
|
|
108
|
+
│ └── Torque (field 1 of Motor)
|
|
109
|
+
└── ...
|
|
110
|
+
|
|
111
|
+
// To access Motors[1].Speed:
|
|
112
|
+
var elementAccess = new ElementAccessStruct
|
|
113
|
+
{
|
|
114
|
+
ArrayIndex = new uint[] { 1 }, // Access second motor in array
|
|
115
|
+
FieldIndexes = new FieldIndexStruct[]
|
|
116
|
+
{
|
|
117
|
+
new FieldIndexStruct { FieldPos = 0, ArrayIndex = new uint[] { } } // Navigate to Speed (field 0)
|
|
118
|
+
}
|
|
119
|
+
};
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
#### Example 3: Array Within Structure
|
|
123
|
+
|
|
124
|
+
```csharp
|
|
125
|
+
// Model structure:
|
|
126
|
+
Settings (structure)
|
|
127
|
+
├── Profiles (array of Profile structures, field 0 of Settings)
|
|
128
|
+
│ ├── [0] Profile (instance of Profile structure, element 0 of the array)
|
|
129
|
+
│ │ ├── Name (field 0 of Profile)
|
|
130
|
+
│ │ └── Values (array of scalars, field 1 of Profile)
|
|
131
|
+
│ │ ├── [0] = 10
|
|
132
|
+
│ │ ├── [1] = 20
|
|
133
|
+
│ │ └── [2] = 30
|
|
134
|
+
│ └── ...
|
|
135
|
+
|
|
136
|
+
// To access Settings.Profiles[2].Values[1]:
|
|
137
|
+
var elementAccess = new ElementAccessStruct
|
|
138
|
+
{
|
|
139
|
+
ArrayIndex = new uint[] { }, // No root-level array
|
|
140
|
+
FieldIndexes = new FieldIndexStruct[]
|
|
141
|
+
{
|
|
142
|
+
new FieldIndexStruct { FieldPos = 0, ArrayIndex = new uint[] { } }, // Navigate to Profiles (field 0)
|
|
143
|
+
new FieldIndexStruct { FieldPos = 1, ArrayIndex = new uint[] { 2, 1 } } // Profiles[2].Values[1] (field 1)
|
|
144
|
+
}
|
|
145
|
+
};
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
#### Example 4: Deeply Nested Structure
|
|
149
|
+
|
|
150
|
+
```csharp
|
|
151
|
+
// Model structure:
|
|
152
|
+
System (structure)
|
|
153
|
+
├── Configuration (instance of Configuration structure, field 0 of System)
|
|
154
|
+
│ └── Industrial (instance of Industrial structure, field 0 of Configuration)
|
|
155
|
+
│ └── Machine (instance of Machine structure, field 0 of Industrial)
|
|
156
|
+
│ ├── Motor1 (instance of Motor structure, field 0 of Machine)
|
|
157
|
+
│ │ └── Speed (field 0 of Motor1)
|
|
158
|
+
│ └── Motor2 (instance of Motor structure, field 1 of Machine)
|
|
159
|
+
│ └── Speed (field 0 of Motor2)
|
|
160
|
+
|
|
161
|
+
// To access System.Configuration.Industrial.Machine.Motor1.Speed:
|
|
162
|
+
var elementAccess = new ElementAccessStruct
|
|
163
|
+
{
|
|
164
|
+
ArrayIndex = new uint[] { },
|
|
165
|
+
FieldIndexes = new FieldIndexStruct[]
|
|
166
|
+
{
|
|
167
|
+
new FieldIndexStruct { FieldPos = 0, ArrayIndex = new uint[] { } }, // Configuration
|
|
168
|
+
new FieldIndexStruct { FieldPos = 0, ArrayIndex = new uint[] { } }, // Industrial
|
|
169
|
+
new FieldIndexStruct { FieldPos = 0, ArrayIndex = new uint[] { } }, // Machine
|
|
170
|
+
new FieldIndexStruct { FieldPos = 0, ArrayIndex = new uint[] { } }, // Motor1
|
|
171
|
+
new FieldIndexStruct { FieldPos = 0, ArrayIndex = new uint[] { } } // Speed
|
|
172
|
+
}
|
|
173
|
+
};
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
## ElementAccess Usage
|
|
177
|
+
|
|
178
|
+
### Structured Variables
|
|
179
|
+
|
|
180
|
+
The **Core** module provides the foundation for all ElementAccess operations through variable access methods. The public API enables reading and writing values at specific ElementAccess locations within complex data structures.
|
|
181
|
+
|
|
182
|
+
**Typical Usage Pattern:**
|
|
183
|
+
|
|
184
|
+
```csharp
|
|
185
|
+
// Access variable
|
|
186
|
+
var variable = LogicObject.Owner.GetVariable("MyVariable");
|
|
187
|
+
|
|
188
|
+
// Create ElementAccess for first element of array
|
|
189
|
+
var elementAccess = new ElementAccess(new uint[] { 0 });
|
|
190
|
+
|
|
191
|
+
// Read value at ElementAccess location
|
|
192
|
+
var currentValue = variable.GetValue(elementAccess);
|
|
193
|
+
|
|
194
|
+
// Write value at ElementAccess location
|
|
195
|
+
variable.SetValue(newValue, elementAccess);
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
### DynamicLink
|
|
199
|
+
|
|
200
|
+
The **DynamicLink** system uses ElementAccess for parent element navigation:
|
|
201
|
+
|
|
202
|
+
```csharp
|
|
203
|
+
public class DynamicLink
|
|
204
|
+
{
|
|
205
|
+
// ElementAccess to navigate from child to parent element
|
|
206
|
+
public Core.ElementAccess ParentElementAccess { get; set; }
|
|
207
|
+
|
|
208
|
+
// Variable storing parent ElementAccess as OPC UA value
|
|
209
|
+
public IUAVariable ParentElementAccessVariable { get; set; }
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
public class DynamicLinkType
|
|
213
|
+
{
|
|
214
|
+
// Type definition includes parent ElementAccess
|
|
215
|
+
public Core.ElementAccess ParentElementAccess { get; set; }
|
|
216
|
+
}
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
**Use Cases:**
|
|
220
|
+
|
|
221
|
+
- Resolving parent elements in hierarchical structured variables
|
|
222
|
+
- Dynamic binding to parent fields based on computed ElementAccess
|
|
223
|
+
- Cross-module variable linking
|
|
224
|
+
|
|
225
|
+
### RecipesX
|
|
226
|
+
|
|
227
|
+
The **RecipesX** module (primary focus of this guide) uses ElementAccess for:
|
|
228
|
+
|
|
229
|
+
```csharp
|
|
230
|
+
// RecipeSchema methods with ElementAccess
|
|
231
|
+
public GetRecipeDataItemValueResult GetRecipeDataItemValue(
|
|
232
|
+
RecipeId recipeId,
|
|
233
|
+
string[] itemRelativeBrowsePath,
|
|
234
|
+
string[] dataItemRelativeBrowsePath,
|
|
235
|
+
ElementAccessStruct elementAccess) // <-- ElementAccess parameter
|
|
236
|
+
|
|
237
|
+
public SetRecipeDataItemValueResultCode SetRecipeDataItemValue(
|
|
238
|
+
RecipeId recipeId,
|
|
239
|
+
string[] itemRelativeBrowsePath,
|
|
240
|
+
string[] dataItemRelativeBrowsePath,
|
|
241
|
+
ElementAccessStruct elementAccess, // <-- ElementAccess parameter
|
|
242
|
+
object value)
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
**Use Cases:**
|
|
246
|
+
|
|
247
|
+
- Accessing array elements in recipe data items
|
|
248
|
+
- Navigating nested structures in recipe schemas
|
|
249
|
+
- Transferring specific fields between recipes
|
|
250
|
+
|
|
251
|
+
|
|
252
|
+
#### RecipesX Methods Using ElementAccess
|
|
253
|
+
|
|
254
|
+
##### GetRecipeDataItemValue
|
|
255
|
+
|
|
256
|
+
Retrieve a specific value from a recipe using ElementAccess.
|
|
257
|
+
|
|
258
|
+
```csharp
|
|
259
|
+
public GetRecipeDataItemValueResult GetRecipeDataItemValue(
|
|
260
|
+
RecipeId recipeId,
|
|
261
|
+
string[] itemRelativeBrowsePath,
|
|
262
|
+
string[] dataItemRelativeBrowsePath,
|
|
263
|
+
ElementAccessStruct elementAccess)
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
**Parameters:**
|
|
267
|
+
|
|
268
|
+
- `elementAccess` - Navigation path to the specific value
|
|
269
|
+
|
|
270
|
+
**Example:**
|
|
271
|
+
|
|
272
|
+
```csharp
|
|
273
|
+
// Model structure:
|
|
274
|
+
//
|
|
275
|
+
// Motors (array of Motor structures, recipe item)
|
|
276
|
+
// ├── [0] Motor
|
|
277
|
+
// │ ├── Speed (field 0 of Motor, float) <-- target
|
|
278
|
+
// │ └── Torque (field 1 of Motor, float)
|
|
279
|
+
// ├── [1] Motor
|
|
280
|
+
// │ ├── Speed (field 0 of Motor, float)
|
|
281
|
+
// │ └── Torque (field 1 of Motor, float)
|
|
282
|
+
// └── ...
|
|
283
|
+
//
|
|
284
|
+
// Goal: read Motors[0].Speed
|
|
285
|
+
|
|
286
|
+
var schema = (RecipeSchema)LogicObject.Owner.GetObject("RecipeSchema1");
|
|
287
|
+
var recipeId = new RecipeId { Name = "Config1", Version = "1.0" };
|
|
288
|
+
|
|
289
|
+
var elementAccess = new ElementAccessStruct
|
|
290
|
+
{
|
|
291
|
+
ArrayIndex = new uint[] { 0 }, // Motors[0]
|
|
292
|
+
FieldIndexes = new FieldIndexStruct[]
|
|
293
|
+
{
|
|
294
|
+
new FieldIndexStruct { FieldPos = 0, ArrayIndex = new uint[] { } } // Motor -> Speed
|
|
295
|
+
}
|
|
296
|
+
};
|
|
297
|
+
|
|
298
|
+
var result = schema.GetRecipeDataItemValue(
|
|
299
|
+
recipeId,
|
|
300
|
+
new string[] { "Motors" },
|
|
301
|
+
new string[] { "Speed" },
|
|
302
|
+
elementAccess);
|
|
303
|
+
|
|
304
|
+
if (result.ResultCode == GetRecipeDataItemValueResultCode.Success)
|
|
305
|
+
{
|
|
306
|
+
Log.Info($"Motor 0 speed: {result.DataItemValue}");
|
|
307
|
+
}
|
|
308
|
+
```
|
|
309
|
+
|
|
310
|
+
#### SetRecipeDataItemValue
|
|
311
|
+
|
|
312
|
+
Set a specific value in a recipe using ElementAccess.
|
|
313
|
+
|
|
314
|
+
```csharp
|
|
315
|
+
public SetRecipeDataItemValueResultCode SetRecipeDataItemValue(
|
|
316
|
+
RecipeId recipeId,
|
|
317
|
+
string[] itemRelativeBrowsePath,
|
|
318
|
+
string[] dataItemRelativeBrowsePath,
|
|
319
|
+
ElementAccessStruct elementAccess,
|
|
320
|
+
object value)
|
|
321
|
+
```
|
|
322
|
+
|
|
323
|
+
**Parameters:**
|
|
324
|
+
|
|
325
|
+
- `elementAccess` - Navigation path to the target location
|
|
326
|
+
|
|
327
|
+
**Example:**
|
|
328
|
+
|
|
329
|
+
```csharp
|
|
330
|
+
// Model structure:
|
|
331
|
+
//
|
|
332
|
+
// Motors (array of Motor structures, recipe item)
|
|
333
|
+
// ├── [0] Motor
|
|
334
|
+
// │ ├── Speed (field 0 of Motor, float)
|
|
335
|
+
// │ └── Torque (field 1 of Motor, float)
|
|
336
|
+
// ├── [1] Motor
|
|
337
|
+
// │ ├── Speed (field 0 of Motor, float) <-- target
|
|
338
|
+
// │ └── Torque (field 1 of Motor, float)
|
|
339
|
+
// └── ...
|
|
340
|
+
//
|
|
341
|
+
// Goal: set Motors[1].Speed to 3500
|
|
342
|
+
|
|
343
|
+
var elementAccess = new ElementAccessStruct
|
|
344
|
+
{
|
|
345
|
+
ArrayIndex = new uint[] { 1 }, // Motors[1]
|
|
346
|
+
FieldIndexes = new FieldIndexStruct[]
|
|
347
|
+
{
|
|
348
|
+
new FieldIndexStruct { FieldPos = 0, ArrayIndex = new uint[] { } } // Motor -> Speed
|
|
349
|
+
}
|
|
350
|
+
};
|
|
351
|
+
|
|
352
|
+
var result = schema.SetRecipeDataItemValue(
|
|
353
|
+
recipeId,
|
|
354
|
+
new string[] { "Motors" },
|
|
355
|
+
new string[] { "Speed" },
|
|
356
|
+
elementAccess,
|
|
357
|
+
3500);
|
|
358
|
+
|
|
359
|
+
if (result == SetRecipeDataItemValueResultCode.Success)
|
|
360
|
+
{
|
|
361
|
+
Log.Info("Motor 1 speed updated");
|
|
362
|
+
}
|
|
363
|
+
```
|
|
364
|
+
|
|
365
|
+
## Practical Usage Patterns
|
|
366
|
+
|
|
367
|
+
### Pattern 1: Access Simple Scalar Values
|
|
368
|
+
|
|
369
|
+
When the data item is a simple scalar (not an array, not nested inside a structure), both `ArrayIndex` and `FieldIndexes` are empty. The `itemRelativeBrowsePath` and `dataItemRelativeBrowsePath` parameters alone identify the value.
|
|
370
|
+
|
|
371
|
+
```csharp
|
|
372
|
+
// Model structure:
|
|
373
|
+
//
|
|
374
|
+
// Settings (structure, recipe item)
|
|
375
|
+
// └── Temperature (scalar float, data item) <-- target
|
|
376
|
+
//
|
|
377
|
+
// Goal: read Settings.Temperature
|
|
378
|
+
|
|
379
|
+
[ExportMethod]
|
|
380
|
+
public void ReadScalarValue(string recipeName)
|
|
381
|
+
{
|
|
382
|
+
var schema = (RecipeSchema)LogicObject.Owner.GetObject("RecipeSchema1");
|
|
383
|
+
var recipeId = new RecipeId { Name = recipeName, Version = "1.0" };
|
|
384
|
+
|
|
385
|
+
// Empty ElementAccess: no array indexing, no field navigation needed
|
|
386
|
+
var elementAccess = new ElementAccessStruct
|
|
387
|
+
{
|
|
388
|
+
ArrayIndex = new uint[] { },
|
|
389
|
+
FieldIndexes = new FieldIndexStruct[] { }
|
|
390
|
+
};
|
|
391
|
+
|
|
392
|
+
var result = schema.GetRecipeDataItemValue(
|
|
393
|
+
recipeId,
|
|
394
|
+
new string[] { "Settings" },
|
|
395
|
+
new string[] { "Temperature" },
|
|
396
|
+
elementAccess);
|
|
397
|
+
}
|
|
398
|
+
```
|
|
399
|
+
|
|
400
|
+
### Pattern 2: Access Array Elements
|
|
401
|
+
|
|
402
|
+
When the data item is an array of scalars, use `ArrayIndex` to select a specific element. No `FieldIndexes` are needed because there is no structure to navigate into.
|
|
403
|
+
|
|
404
|
+
```csharp
|
|
405
|
+
// Model structure:
|
|
406
|
+
//
|
|
407
|
+
// SensorArray (structure, recipe item)
|
|
408
|
+
// └── Reading (array of float scalars, data item)
|
|
409
|
+
// ├── [0] = 23.5
|
|
410
|
+
// ├── [1] = 24.1
|
|
411
|
+
// ├── [2] = 22.8
|
|
412
|
+
// └── ...
|
|
413
|
+
//
|
|
414
|
+
// Goal: read SensorArray.Reading[elementIndex]
|
|
415
|
+
|
|
416
|
+
[ExportMethod]
|
|
417
|
+
public void ReadArrayElement(string recipeName, uint elementIndex)
|
|
418
|
+
{
|
|
419
|
+
var schema = (RecipeSchema)LogicObject.Owner.GetObject("RecipeSchema1");
|
|
420
|
+
var recipeId = new RecipeId { Name = recipeName, Version = "1.0" };
|
|
421
|
+
|
|
422
|
+
// ArrayIndex selects the element; no FieldIndexes needed for a scalar array
|
|
423
|
+
var elementAccess = new ElementAccessStruct
|
|
424
|
+
{
|
|
425
|
+
ArrayIndex = new uint[] { elementIndex }, // Reading[elementIndex]
|
|
426
|
+
FieldIndexes = new FieldIndexStruct[] { }
|
|
427
|
+
};
|
|
428
|
+
|
|
429
|
+
var result = schema.GetRecipeDataItemValue(
|
|
430
|
+
recipeId,
|
|
431
|
+
new string[] { "SensorArray" },
|
|
432
|
+
new string[] { "Reading" },
|
|
433
|
+
elementAccess);
|
|
434
|
+
|
|
435
|
+
if (result.ResultCode == GetRecipeDataItemValueResultCode.Success)
|
|
436
|
+
{
|
|
437
|
+
Log.Info($"Sensor {elementIndex} reading: {result.DataItemValue}");
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
```
|
|
441
|
+
|
|
442
|
+
### Pattern 3: Access Nested Structure Fields
|
|
443
|
+
|
|
444
|
+
When a data item is a structure containing other structures, use `FieldIndexes` to navigate level by level to the target field. Each entry in `FieldIndexes` descends one level.
|
|
445
|
+
|
|
446
|
+
```csharp
|
|
447
|
+
// Model structure:
|
|
448
|
+
//
|
|
449
|
+
// System (recipe item)
|
|
450
|
+
// └── Configuration (data item, instance of Configuration structure)
|
|
451
|
+
// ├── MotorSettings (field 0 of Configuration, instance of MotorSettings structure)
|
|
452
|
+
// │ ├── MinSpeed (field 0 of MotorSettings, float)
|
|
453
|
+
// │ └── MaxSpeed (field 1 of MotorSettings, float) <-- target
|
|
454
|
+
// └── ConveyorSettings (field 1 of Configuration, instance of ConveyorSettings structure)
|
|
455
|
+
// └── ...
|
|
456
|
+
//
|
|
457
|
+
// Goal: read System.Configuration -> MotorSettings.MaxSpeed
|
|
458
|
+
|
|
459
|
+
[ExportMethod]
|
|
460
|
+
public void ReadNestedProperty(string recipeName)
|
|
461
|
+
{
|
|
462
|
+
var schema = (RecipeSchema)LogicObject.Owner.GetObject("RecipeSchema1");
|
|
463
|
+
var recipeId = new RecipeId { Name = recipeName, Version = "1.0" };
|
|
464
|
+
|
|
465
|
+
var elementAccess = new ElementAccessStruct
|
|
466
|
+
{
|
|
467
|
+
ArrayIndex = new uint[] { },
|
|
468
|
+
FieldIndexes = new FieldIndexStruct[]
|
|
469
|
+
{
|
|
470
|
+
new FieldIndexStruct
|
|
471
|
+
{
|
|
472
|
+
FieldPos = 0, // Configuration -> MotorSettings
|
|
473
|
+
ArrayIndex = new uint[] { }
|
|
474
|
+
},
|
|
475
|
+
new FieldIndexStruct
|
|
476
|
+
{
|
|
477
|
+
FieldPos = 1, // MotorSettings -> MaxSpeed
|
|
478
|
+
ArrayIndex = new uint[] { }
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
};
|
|
482
|
+
|
|
483
|
+
var result = schema.GetRecipeDataItemValue(
|
|
484
|
+
recipeId,
|
|
485
|
+
new string[] { "System", "Configuration" },
|
|
486
|
+
new string[] { },
|
|
487
|
+
elementAccess);
|
|
488
|
+
}
|
|
489
|
+
```
|
|
490
|
+
|
|
491
|
+
### Pattern 4: Access Array of Structures with Nested Fields
|
|
492
|
+
|
|
493
|
+
When the root data item is an array of structures and the target field is inside a nested structure, combine `ArrayIndex` (to pick the array element) with `FieldIndexes` (to navigate into the nested structure).
|
|
494
|
+
|
|
495
|
+
```csharp
|
|
496
|
+
// Model structure:
|
|
497
|
+
//
|
|
498
|
+
// Motors (array of Motor structures, recipe item)
|
|
499
|
+
// ├── [0] Motor
|
|
500
|
+
// │ ├── Name (field 0 of Motor, string)
|
|
501
|
+
// │ └── Config (field 1 of Motor, instance of MotorConfig structure)
|
|
502
|
+
// │ ├── MaxRPM (field 0 of MotorConfig, int) <-- target
|
|
503
|
+
// │ └── MaxTorque (field 1 of MotorConfig, float)
|
|
504
|
+
// ├── [1] Motor
|
|
505
|
+
// │ └── ...
|
|
506
|
+
// └── ...
|
|
507
|
+
//
|
|
508
|
+
// Goal: read Motors[motorIndex].Config.MaxRPM
|
|
509
|
+
|
|
510
|
+
[ExportMethod]
|
|
511
|
+
public void ReadArrayElementProperty(string recipeName, uint motorIndex)
|
|
512
|
+
{
|
|
513
|
+
var schema = (RecipeSchema)LogicObject.Owner.GetObject("RecipeSchema1");
|
|
514
|
+
var recipeId = new RecipeId { Name = recipeName, Version = "1.0" };
|
|
515
|
+
|
|
516
|
+
var elementAccess = new ElementAccessStruct
|
|
517
|
+
{
|
|
518
|
+
ArrayIndex = new uint[] { motorIndex }, // Motors[motorIndex]
|
|
519
|
+
FieldIndexes = new FieldIndexStruct[]
|
|
520
|
+
{
|
|
521
|
+
new FieldIndexStruct
|
|
522
|
+
{
|
|
523
|
+
FieldPos = 1, // Motor -> Config
|
|
524
|
+
ArrayIndex = new uint[] { }
|
|
525
|
+
},
|
|
526
|
+
new FieldIndexStruct
|
|
527
|
+
{
|
|
528
|
+
FieldPos = 0, // MotorConfig -> MaxRPM
|
|
529
|
+
ArrayIndex = new uint[] { }
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
};
|
|
533
|
+
|
|
534
|
+
var result = schema.GetRecipeDataItemValue(
|
|
535
|
+
recipeId,
|
|
536
|
+
new string[] { "Motors" },
|
|
537
|
+
new string[] { },
|
|
538
|
+
elementAccess);
|
|
539
|
+
|
|
540
|
+
if (result.ResultCode == GetRecipeDataItemValueResultCode.Success)
|
|
541
|
+
{
|
|
542
|
+
Log.Info($"Motor {motorIndex} max RPM: {result.DataItemValue}");
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
```
|
|
546
|
+
|
|
547
|
+
### Pattern 5: Access Elements Within Nested Arrays
|
|
548
|
+
|
|
549
|
+
When a structure field is itself an array (not the root variable), use the `ArrayIndex` property inside the corresponding `FieldIndexStruct` to select an element at that level.
|
|
550
|
+
|
|
551
|
+
```csharp
|
|
552
|
+
// Model structure:
|
|
553
|
+
//
|
|
554
|
+
// Configuration (structure, recipe item)
|
|
555
|
+
// └── Profiles (array of Profile structures, field 0 of Configuration)
|
|
556
|
+
// ├── [0] Profile
|
|
557
|
+
// │ ├── Name (field 0 of Profile, string)
|
|
558
|
+
// │ └── Values (array of float scalars, field 1 of Profile)
|
|
559
|
+
// │ ├── [0] = 10.0
|
|
560
|
+
// │ ├── [1] = 20.0
|
|
561
|
+
// │ └── [2] = 30.0
|
|
562
|
+
// ├── [1] Profile
|
|
563
|
+
// │ └── ...
|
|
564
|
+
// └── ...
|
|
565
|
+
//
|
|
566
|
+
// Goal: read Configuration.Profiles[profileIndex].Values[valueIndex]
|
|
567
|
+
|
|
568
|
+
[ExportMethod]
|
|
569
|
+
public void ReadMultiArrayValue(string recipeName, uint profileIndex, uint valueIndex)
|
|
570
|
+
{
|
|
571
|
+
var schema = (RecipeSchema)LogicObject.Owner.GetObject("RecipeSchema1");
|
|
572
|
+
var recipeId = new RecipeId { Name = recipeName, Version = "1.0" };
|
|
573
|
+
|
|
574
|
+
var elementAccess = new ElementAccessStruct
|
|
575
|
+
{
|
|
576
|
+
ArrayIndex = new uint[] { },
|
|
577
|
+
FieldIndexes = new FieldIndexStruct[]
|
|
578
|
+
{
|
|
579
|
+
new FieldIndexStruct
|
|
580
|
+
{
|
|
581
|
+
FieldPos = 0, // Configuration -> Profiles
|
|
582
|
+
ArrayIndex = new uint[] { profileIndex } // Profiles[profileIndex]
|
|
583
|
+
},
|
|
584
|
+
new FieldIndexStruct
|
|
585
|
+
{
|
|
586
|
+
FieldPos = 1, // Profile -> Values
|
|
587
|
+
ArrayIndex = new uint[] { valueIndex } // Values[valueIndex]
|
|
588
|
+
}
|
|
589
|
+
}
|
|
590
|
+
};
|
|
591
|
+
|
|
592
|
+
var result = schema.GetRecipeDataItemValue(
|
|
593
|
+
recipeId,
|
|
594
|
+
new string[] { "Configuration" },
|
|
595
|
+
new string[] { },
|
|
596
|
+
elementAccess);
|
|
597
|
+
}
|
|
598
|
+
```
|
|
599
|
+
|
|
600
|
+
### Pattern 6: Batch Read Multiple Array Elements
|
|
601
|
+
|
|
602
|
+
Loop through array elements to read all values from an array of structures.
|
|
603
|
+
|
|
604
|
+
```csharp
|
|
605
|
+
// Model structure:
|
|
606
|
+
//
|
|
607
|
+
// Motors (array of Motor structures, recipe item)
|
|
608
|
+
// ├── [0] Motor
|
|
609
|
+
// │ ├── Speed (field 0 of Motor, float) <-- target for each element
|
|
610
|
+
// │ └── Torque (field 1 of Motor, float)
|
|
611
|
+
// ├── [1] Motor
|
|
612
|
+
// │ └── ...
|
|
613
|
+
// └── ... (10 elements total)
|
|
614
|
+
//
|
|
615
|
+
// Goal: read Speed from every motor
|
|
616
|
+
|
|
617
|
+
[ExportMethod]
|
|
618
|
+
public void ReadAllMotorSpeeds(string recipeName)
|
|
619
|
+
{
|
|
620
|
+
var schema = (RecipeSchema)LogicObject.Owner.GetObject("RecipeSchema1");
|
|
621
|
+
var recipeId = new RecipeId { Name = recipeName, Version = "1.0" };
|
|
622
|
+
|
|
623
|
+
for (uint i = 0; i < 10; i++)
|
|
624
|
+
{
|
|
625
|
+
var elementAccess = new ElementAccessStruct
|
|
626
|
+
{
|
|
627
|
+
ArrayIndex = new uint[] { i }, // Motors[i]
|
|
628
|
+
FieldIndexes = new FieldIndexStruct[] { }
|
|
629
|
+
};
|
|
630
|
+
|
|
631
|
+
var result = schema.GetRecipeDataItemValue(
|
|
632
|
+
recipeId,
|
|
633
|
+
new string[] { "Motors" },
|
|
634
|
+
new string[] { "Speed" },
|
|
635
|
+
elementAccess);
|
|
636
|
+
|
|
637
|
+
if (result.ResultCode == GetRecipeDataItemValueResultCode.Success)
|
|
638
|
+
{
|
|
639
|
+
Log.Info($"Motor {i} speed: {result.DataItemValue}");
|
|
640
|
+
}
|
|
641
|
+
}
|
|
642
|
+
}
|
|
643
|
+
```
|
|
644
|
+
|
|
645
|
+
### Pattern 7: Batch Write to Array Elements
|
|
646
|
+
|
|
647
|
+
Loop through array elements to write the same value to multiple entries.
|
|
648
|
+
|
|
649
|
+
```csharp
|
|
650
|
+
// Model structure (same as Pattern 6):
|
|
651
|
+
//
|
|
652
|
+
// Motors (array of Motor structures, recipe item)
|
|
653
|
+
// ├── [0] Motor
|
|
654
|
+
// │ ├── Speed (field 0 of Motor, float) <-- target for each element
|
|
655
|
+
// │ └── Torque (field 1 of Motor, float)
|
|
656
|
+
// ├── [1] Motor
|
|
657
|
+
// │ └── ...
|
|
658
|
+
// └── ...
|
|
659
|
+
//
|
|
660
|
+
// Goal: set Speed to newSpeed for motors 0, 1, 2, and 3
|
|
661
|
+
|
|
662
|
+
[ExportMethod]
|
|
663
|
+
public void SetAllMotorSpeeds(string recipeName, uint newSpeed)
|
|
664
|
+
{
|
|
665
|
+
var schema = (RecipeSchema)LogicObject.Owner.GetObject("RecipeSchema1");
|
|
666
|
+
var recipeId = new RecipeId { Name = recipeName, Version = "1.0" };
|
|
667
|
+
|
|
668
|
+
uint[] motorIndices = { 0, 1, 2, 3 };
|
|
669
|
+
|
|
670
|
+
foreach (var index in motorIndices)
|
|
671
|
+
{
|
|
672
|
+
var elementAccess = new ElementAccessStruct
|
|
673
|
+
{
|
|
674
|
+
ArrayIndex = new uint[] { index }, // Motors[index]
|
|
675
|
+
FieldIndexes = new FieldIndexStruct[] { }
|
|
676
|
+
};
|
|
677
|
+
|
|
678
|
+
var result = schema.SetRecipeDataItemValue(
|
|
679
|
+
recipeId,
|
|
680
|
+
new string[] { "Motors" },
|
|
681
|
+
new string[] { "Speed" },
|
|
682
|
+
elementAccess,
|
|
683
|
+
newSpeed);
|
|
684
|
+
|
|
685
|
+
if (result == SetRecipeDataItemValueResultCode.Success)
|
|
686
|
+
{
|
|
687
|
+
Log.Info($"Motor {index} speed set to {newSpeed}");
|
|
688
|
+
}
|
|
689
|
+
else
|
|
690
|
+
{
|
|
691
|
+
Log.Error($"Failed to set motor {index} speed: {result}");
|
|
692
|
+
}
|
|
693
|
+
}
|
|
694
|
+
}
|
|
695
|
+
```
|
|
696
|
+
|
|
697
|
+
### Pattern 8: Comparing Array Element Values
|
|
698
|
+
|
|
699
|
+
Read the same field from two different array elements and compare them.
|
|
700
|
+
|
|
701
|
+
```csharp
|
|
702
|
+
// Model structure (same as Patterns 6 and 7):
|
|
703
|
+
//
|
|
704
|
+
// Motors (array of Motor structures, recipe item)
|
|
705
|
+
// ├── [0] Motor
|
|
706
|
+
// │ ├── Speed (field 0 of Motor, float) <-- compared
|
|
707
|
+
// │ └── Torque (field 1 of Motor, float)
|
|
708
|
+
// ├── [1] Motor
|
|
709
|
+
// │ ├── Speed (field 0 of Motor, float) <-- compared
|
|
710
|
+
// │ └── Torque (field 1 of Motor, float)
|
|
711
|
+
// └── ...
|
|
712
|
+
//
|
|
713
|
+
// Goal: compare Speed of Motors[motor1] with Motors[motor2]
|
|
714
|
+
|
|
715
|
+
[ExportMethod]
|
|
716
|
+
public bool CompareMotorSettings(string recipeName, uint motor1, uint motor2)
|
|
717
|
+
{
|
|
718
|
+
var schema = (RecipeSchema)LogicObject.Owner.GetObject("RecipeSchema1");
|
|
719
|
+
var recipeId = new RecipeId { Name = recipeName, Version = "1.0" };
|
|
720
|
+
|
|
721
|
+
// Read Speed from Motors[motor1]
|
|
722
|
+
var ea1 = new ElementAccessStruct
|
|
723
|
+
{
|
|
724
|
+
ArrayIndex = new uint[] { motor1 }, // Motors[motor1]
|
|
725
|
+
FieldIndexes = new FieldIndexStruct[] { }
|
|
726
|
+
};
|
|
727
|
+
|
|
728
|
+
var result1 = schema.GetRecipeDataItemValue(
|
|
729
|
+
recipeId,
|
|
730
|
+
new string[] { "Motors" },
|
|
731
|
+
new string[] { "Speed" },
|
|
732
|
+
ea1);
|
|
733
|
+
|
|
734
|
+
// Read Speed from Motors[motor2]
|
|
735
|
+
var ea2 = new ElementAccessStruct
|
|
736
|
+
{
|
|
737
|
+
ArrayIndex = new uint[] { motor2 }, // Motors[motor2]
|
|
738
|
+
FieldIndexes = new FieldIndexStruct[] { }
|
|
739
|
+
};
|
|
740
|
+
|
|
741
|
+
var result2 = schema.GetRecipeDataItemValue(
|
|
742
|
+
recipeId,
|
|
743
|
+
new string[] { "Motors" },
|
|
744
|
+
new string[] { "Speed" },
|
|
745
|
+
ea2);
|
|
746
|
+
|
|
747
|
+
if (result1.ResultCode == GetRecipeDataItemValueResultCode.Success &&
|
|
748
|
+
result2.ResultCode == GetRecipeDataItemValueResultCode.Success)
|
|
749
|
+
{
|
|
750
|
+
bool match = Equals(result1.DataItemValue, result2.DataItemValue);
|
|
751
|
+
Log.Info($"Motor {motor1} speed: {result1.DataItemValue}");
|
|
752
|
+
Log.Info($"Motor {motor2} speed: {result2.DataItemValue}");
|
|
753
|
+
Log.Info($"Match: {match}");
|
|
754
|
+
return match;
|
|
755
|
+
}
|
|
756
|
+
|
|
757
|
+
return false;
|
|
758
|
+
}
|
|
759
|
+
```
|
|
760
|
+
|
|
761
|
+
## Common Mistakes and Troubleshooting
|
|
762
|
+
|
|
763
|
+
### Mistake 1: Using FieldPos as a Global Index
|
|
764
|
+
|
|
765
|
+
`FieldPos` is always relative to the parent structure at that level, not a global position across the entire model. Each `FieldIndexStruct` in the array navigates one level deeper, and its `FieldPos` refers to the field order within that specific parent structure.
|
|
766
|
+
|
|
767
|
+
```csharp
|
|
768
|
+
// Given this model:
|
|
769
|
+
//
|
|
770
|
+
// Plant (structure)
|
|
771
|
+
// ├── Name (field 0 of Plant, string)
|
|
772
|
+
// ├── Location (field 1 of Plant, string)
|
|
773
|
+
// └── Line (field 2 of Plant, instance of Line structure)
|
|
774
|
+
// ├── Id (field 0 of Line, int)
|
|
775
|
+
// └── Speed (field 1 of Line, float)
|
|
776
|
+
//
|
|
777
|
+
// Goal: access Plant.Line.Speed
|
|
778
|
+
|
|
779
|
+
// WRONG: Treating FieldPos as a flat, global index across the whole model.
|
|
780
|
+
// FieldPos = 2 correctly reaches Line, but FieldPos = 4 does not exist
|
|
781
|
+
// inside the Line structure (Line only has fields 0 and 1).
|
|
782
|
+
var wrong = new ElementAccessStruct
|
|
783
|
+
{
|
|
784
|
+
ArrayIndex = new uint[] { },
|
|
785
|
+
FieldIndexes = new FieldIndexStruct[]
|
|
786
|
+
{
|
|
787
|
+
new FieldIndexStruct { FieldPos = 2, ArrayIndex = new uint[] { } }, // Line (correct)
|
|
788
|
+
new FieldIndexStruct { FieldPos = 4, ArrayIndex = new uint[] { } } // Nothing at index 4 in Line
|
|
789
|
+
}
|
|
790
|
+
};
|
|
791
|
+
|
|
792
|
+
// CORRECT: Each FieldPos is relative to its parent.
|
|
793
|
+
// First entry navigates within Plant -> field 2 is Line.
|
|
794
|
+
// Second entry navigates within Line -> field 1 is Speed.
|
|
795
|
+
var correct = new ElementAccessStruct
|
|
796
|
+
{
|
|
797
|
+
ArrayIndex = new uint[] { },
|
|
798
|
+
FieldIndexes = new FieldIndexStruct[]
|
|
799
|
+
{
|
|
800
|
+
new FieldIndexStruct { FieldPos = 2, ArrayIndex = new uint[] { } }, // Plant -> Line
|
|
801
|
+
new FieldIndexStruct { FieldPos = 1, ArrayIndex = new uint[] { } } // Line -> Speed
|
|
802
|
+
}
|
|
803
|
+
};
|
|
804
|
+
```
|
|
805
|
+
|
|
806
|
+
### Mistake 2: Putting Array Indexes at the Wrong Level
|
|
807
|
+
|
|
808
|
+
The root-level `ArrayIndex` property and the `ArrayIndex` inside each `FieldIndexStruct` serve different purposes. The root `ArrayIndex` selects an element when the variable itself is an array. The `ArrayIndex` inside a `FieldIndexStruct` selects an element when a specific field within the structure is an array. Mixing them up accesses the wrong data or fails entirely.
|
|
809
|
+
|
|
810
|
+
```csharp
|
|
811
|
+
// Given this model:
|
|
812
|
+
//
|
|
813
|
+
// Motors (array of Motor structures) <-- root variable is an array
|
|
814
|
+
// ├── [0] Motor
|
|
815
|
+
// │ ├── Name (field 0 of Motor, string)
|
|
816
|
+
// │ └── Speed (field 1 of Motor, float)
|
|
817
|
+
// ├── [1] Motor
|
|
818
|
+
// │ ├── Name (field 0 of Motor, string)
|
|
819
|
+
// │ └── Speed (field 1 of Motor, float)
|
|
820
|
+
// └── ...
|
|
821
|
+
//
|
|
822
|
+
// Goal: access Motors[0].Speed
|
|
823
|
+
|
|
824
|
+
// WRONG: The root variable (Motors) is the array, so the array index
|
|
825
|
+
// belongs in the root-level ArrayIndex. Placing it inside a FieldIndexStruct
|
|
826
|
+
// would attempt to index the Name field as if Name were an array.
|
|
827
|
+
var wrong = new ElementAccessStruct
|
|
828
|
+
{
|
|
829
|
+
ArrayIndex = new uint[] { },
|
|
830
|
+
FieldIndexes = new FieldIndexStruct[]
|
|
831
|
+
{
|
|
832
|
+
new FieldIndexStruct { FieldPos = 0, ArrayIndex = new uint[] { 0 } }, // Tries Name[0], not Motors[0]
|
|
833
|
+
new FieldIndexStruct { FieldPos = 1, ArrayIndex = new uint[] { } }
|
|
834
|
+
}
|
|
835
|
+
};
|
|
836
|
+
|
|
837
|
+
// CORRECT: Use root-level ArrayIndex to select Motors[0],
|
|
838
|
+
// then FieldIndexes to navigate within the selected Motor structure.
|
|
839
|
+
var correct = new ElementAccessStruct
|
|
840
|
+
{
|
|
841
|
+
ArrayIndex = new uint[] { 0 }, // Motors[0]
|
|
842
|
+
FieldIndexes = new FieldIndexStruct[]
|
|
843
|
+
{
|
|
844
|
+
new FieldIndexStruct { FieldPos = 1, ArrayIndex = new uint[] { } } // Motor -> Speed
|
|
845
|
+
}
|
|
846
|
+
};
|
|
847
|
+
```
|
|
848
|
+
|
|
849
|
+
### Mistake 3: Skipping Intermediate Structure Levels
|
|
850
|
+
|
|
851
|
+
Each level of structure nesting requires its own `FieldIndexStruct` entry. If a structure contains another structure that in turn contains the target field, every intermediate structure must appear in the path. Skipping a level lands on the wrong field or produces an error.
|
|
852
|
+
|
|
853
|
+
```csharp
|
|
854
|
+
// Given this model:
|
|
855
|
+
//
|
|
856
|
+
// Motors (array of Motor structures)
|
|
857
|
+
// ├── [0] Motor
|
|
858
|
+
// │ ├── Name (field 0 of Motor, string)
|
|
859
|
+
// │ └── Config (field 1 of Motor, instance of MotorConfig structure)
|
|
860
|
+
// │ ├── MaxSpeed (field 0 of MotorConfig, float)
|
|
861
|
+
// │ └── MaxTorque (field 1 of MotorConfig, float)
|
|
862
|
+
// └── ...
|
|
863
|
+
//
|
|
864
|
+
// Goal: access Motors[0].Config.MaxSpeed
|
|
865
|
+
|
|
866
|
+
// WRONG: Jumps directly from the Motor level to MaxSpeed, skipping Config.
|
|
867
|
+
// FieldPos = 0 inside Motor is Name, not MaxSpeed. The path never enters Config.
|
|
868
|
+
var wrong = new ElementAccessStruct
|
|
869
|
+
{
|
|
870
|
+
ArrayIndex = new uint[] { 0 }, // Motors[0]
|
|
871
|
+
FieldIndexes = new FieldIndexStruct[]
|
|
872
|
+
{
|
|
873
|
+
new FieldIndexStruct { FieldPos = 0, ArrayIndex = new uint[] { } } // Motor -> Name (not MaxSpeed!)
|
|
874
|
+
}
|
|
875
|
+
};
|
|
876
|
+
|
|
877
|
+
// CORRECT: Navigate each level explicitly.
|
|
878
|
+
// First entry enters Config (field 1 of Motor).
|
|
879
|
+
// Second entry enters MaxSpeed (field 0 of MotorConfig).
|
|
880
|
+
var correct = new ElementAccessStruct
|
|
881
|
+
{
|
|
882
|
+
ArrayIndex = new uint[] { 0 }, // Motors[0]
|
|
883
|
+
FieldIndexes = new FieldIndexStruct[]
|
|
884
|
+
{
|
|
885
|
+
new FieldIndexStruct { FieldPos = 1, ArrayIndex = new uint[] { } }, // Motor -> Config
|
|
886
|
+
new FieldIndexStruct { FieldPos = 0, ArrayIndex = new uint[] { } } // MotorConfig -> MaxSpeed
|
|
887
|
+
}
|
|
888
|
+
};
|
|
889
|
+
```
|
|
890
|
+
|
|
891
|
+
## Debugging ElementAccess
|
|
892
|
+
|
|
893
|
+
### Verify Your Structure
|
|
894
|
+
|
|
895
|
+
Display the structure to understand field positions:
|
|
896
|
+
|
|
897
|
+
```csharp
|
|
898
|
+
[ExportMethod]
|
|
899
|
+
public void PrintRecipeStructure(string recipeName, string version)
|
|
900
|
+
{
|
|
901
|
+
var schema = (RecipeSchema)LogicObject.Owner.GetObject("RecipeSchema1");
|
|
902
|
+
var recipeId = new RecipeId { Name = recipeName, Version = version };
|
|
903
|
+
|
|
904
|
+
var dataItemsResult = schema.GetDataItems(recipeId);
|
|
905
|
+
if (dataItemsResult.ResultCode != GetDataItemsResultCode.Success)
|
|
906
|
+
{
|
|
907
|
+
Log.Error("Failed to get data items");
|
|
908
|
+
return;
|
|
909
|
+
}
|
|
910
|
+
|
|
911
|
+
Log.Info($"Recipe '{recipeName}' contains {dataItemsResult.DataItems.Length} data items:");
|
|
912
|
+
|
|
913
|
+
foreach (var item in dataItemsResult.DataItems)
|
|
914
|
+
{
|
|
915
|
+
var itemPath = string.Join("/", item.ItemRelativeBrowsePath);
|
|
916
|
+
var dataPath = string.Join("/", item.DataItemRelativeBrowsePath);
|
|
917
|
+
var arrayIndices = string.Join(",", item.ElementAccess.ArrayIndex);
|
|
918
|
+
var fieldIndices = string.Join(";", item.ElementAccess.FieldIndexes.Select(f =>
|
|
919
|
+
$"Pos:{f.FieldPos}[{string.Join(",", f.ArrayIndex)}]"));
|
|
920
|
+
|
|
921
|
+
Log.Info($" Item: {itemPath}");
|
|
922
|
+
Log.Info($" DataPath: {dataPath}");
|
|
923
|
+
Log.Info($" ArrayIndex: [{arrayIndices}]");
|
|
924
|
+
Log.Info($" FieldIndexes: [{fieldIndices}]");
|
|
925
|
+
}
|
|
926
|
+
}
|
|
927
|
+
```
|
|
928
|
+
|
|
929
|
+
### Test Single Values
|
|
930
|
+
|
|
931
|
+
Write a helper to test ElementAccess values:
|
|
932
|
+
|
|
933
|
+
```csharp
|
|
934
|
+
[ExportMethod]
|
|
935
|
+
public void TestElementAccess(string recipeName, uint[] arrayIndex, uint[] fieldPositions)
|
|
936
|
+
{
|
|
937
|
+
var schema = (RecipeSchema)LogicObject.Owner.GetObject("RecipeSchema1");
|
|
938
|
+
var recipeId = new RecipeId { Name = recipeName, Version = "1.0" };
|
|
939
|
+
|
|
940
|
+
// Build ElementAccess from inputs
|
|
941
|
+
var fieldIndexes = fieldPositions.Select(pos =>
|
|
942
|
+
new FieldIndexStruct { FieldPos = pos, ArrayIndex = new uint[] { } }
|
|
943
|
+
).ToArray();
|
|
944
|
+
|
|
945
|
+
var elementAccess = new ElementAccessStruct
|
|
946
|
+
{
|
|
947
|
+
ArrayIndex = arrayIndex,
|
|
948
|
+
FieldIndexes = fieldIndexes
|
|
949
|
+
};
|
|
950
|
+
|
|
951
|
+
var result = schema.GetRecipeDataItemValue(
|
|
952
|
+
recipeId,
|
|
953
|
+
new string[] { "Data" },
|
|
954
|
+
new string[] { },
|
|
955
|
+
elementAccess);
|
|
956
|
+
|
|
957
|
+
if (result.ResultCode == GetRecipeDataItemValueResultCode.Success)
|
|
958
|
+
{
|
|
959
|
+
Log.Info($"Value found: {result.DataItemValue}");
|
|
960
|
+
}
|
|
961
|
+
else
|
|
962
|
+
{
|
|
963
|
+
Log.Error($"Failed to access: {result.ResultCode}");
|
|
964
|
+
}
|
|
965
|
+
}
|
|
966
|
+
```
|
|
967
|
+
|
|
968
|
+
## Migration Guide - From Array Index to ElementAccess
|
|
969
|
+
|
|
970
|
+
Several APIs have been deprecated in favor of ElementAccess-based versions. This section helps you migrate existing code to use the new ElementAccess API.
|
|
971
|
+
|
|
972
|
+
### DynamicLink: ParentArrayIndex → ParentElementAccess
|
|
973
|
+
|
|
974
|
+
**Deprecated Properties:**
|
|
975
|
+
|
|
976
|
+
- `ParentArrayIndex` (uint[])
|
|
977
|
+
- `ParentScalarIndex` (uint)
|
|
978
|
+
- `ParentArrayIndexVariable` (IUAVariable)
|
|
979
|
+
|
|
980
|
+
**New Properties:**
|
|
981
|
+
|
|
982
|
+
- `ParentElementAccess` (ElementAccess)
|
|
983
|
+
- `ParentElementAccessVariable` (IUAVariable)
|
|
984
|
+
|
|
985
|
+
**Migration Example:**
|
|
986
|
+
|
|
987
|
+
```csharp
|
|
988
|
+
// DEPRECATED: Old way using ParentArrayIndex
|
|
989
|
+
var dynamicLink = (DynamicLink)LogicObject.Owner.GetObject("MyDynamicLink");
|
|
990
|
+
uint[] oldArrayIndex = dynamicLink.ParentArrayIndex;
|
|
991
|
+
dynamicLink.ParentArrayIndex = new uint[] { 0, 1 };
|
|
992
|
+
|
|
993
|
+
// Or set a single value
|
|
994
|
+
dynamicLink.ParentScalarIndex = 2;
|
|
995
|
+
|
|
996
|
+
// Access the variable
|
|
997
|
+
var oldVariable = dynamicLink.ParentArrayIndexVariable;
|
|
998
|
+
|
|
999
|
+
// NEW: Use ParentElementAccess instead
|
|
1000
|
+
var dynamicLink = (DynamicLink)LogicObject.Owner.GetObject("MyDynamicLink");
|
|
1001
|
+
|
|
1002
|
+
// Read ParentElementAccess
|
|
1003
|
+
var elementAccess = dynamicLink.ParentElementAccess;
|
|
1004
|
+
var arrayIndex = elementAccess.ArrayIndex; // Get array portion
|
|
1005
|
+
|
|
1006
|
+
// Set ParentElementAccess with field navigation
|
|
1007
|
+
dynamicLink.ParentElementAccess = new ElementAccess(new uint[] { 0, 1 }, new FieldIndex[] { });
|
|
1008
|
+
|
|
1009
|
+
// Set ParentElementAccess with single array index
|
|
1010
|
+
dynamicLink.ParentElementAccess = new ElementAccess(new uint[] { 2 });
|
|
1011
|
+
|
|
1012
|
+
// Set with nested field navigation
|
|
1013
|
+
dynamicLink.ParentElementAccess = new ElementAccess(
|
|
1014
|
+
new uint[] { },
|
|
1015
|
+
new[] {
|
|
1016
|
+
new FieldIndex { FieldPos = 0, ArrayIndex = new uint[] { } },
|
|
1017
|
+
new FieldIndex { FieldPos = 1, ArrayIndex = new uint[] { 2 } }
|
|
1018
|
+
});
|
|
1019
|
+
|
|
1020
|
+
// Access the variable
|
|
1021
|
+
var newVariable = dynamicLink.ParentElementAccessVariable;
|
|
1022
|
+
```
|
|
1023
|
+
|
|
1024
|
+
**Why This Changed:**
|
|
1025
|
+
|
|
1026
|
+
- **ParentArrayIndex was limited** - Only supported array indices, not nested field navigation
|
|
1027
|
+
- **ElementAccess is comprehensive** - Supports both array indices and field navigation in a single structure
|
|
1028
|
+
- **Consistency** - ElementAccess is the standard across all FactoryTalk Optix modules
|
|
1029
|
+
|
|
1030
|
+
**Specific Migration Paths:**
|
|
1031
|
+
|
|
1032
|
+
| Old Code | New Code | Notes |
|
|
1033
|
+
|----------|----------|-------|
|
|
1034
|
+
| `link.ParentArrayIndex` | `link.ParentElementAccess.ArrayIndex` | Read array index |
|
|
1035
|
+
| `link.ParentArrayIndex = new uint[] { 0 }` | `link.ParentElementAccess = new ElementAccess(new uint[] { 0 })` | Set array index only |
|
|
1036
|
+
| `link.ParentScalarIndex = 5` | `link.ParentElementAccess = new ElementAccess(new uint[] { 5 })` | Set single array index |
|
|
1037
|
+
| `link.ParentArrayIndexVariable` | `link.ParentElementAccessVariable` | Access underlying variable |
|
|
1038
|
+
|
|
1039
|
+
### Event Handlers: uint[] Indexes to ElementAccess
|
|
1040
|
+
|
|
1041
|
+
**Deprecated Interface:**
|
|
1042
|
+
|
|
1043
|
+
```csharp
|
|
1044
|
+
// DEPRECATED: IVariableChangeObserver with uint[] indexes
|
|
1045
|
+
public interface IVariableChangeObserver : IEventObserver
|
|
1046
|
+
{
|
|
1047
|
+
[Obsolete("Use OnVariableChanged with ElementAccess parameter instead.")]
|
|
1048
|
+
void OnVariableChanged(IUAVariable variable, UAValue newValue, UAValue oldValue,
|
|
1049
|
+
uint[] indexes, // <-- Array index only
|
|
1050
|
+
ulong senderId) { }
|
|
1051
|
+
}
|
|
1052
|
+
```
|
|
1053
|
+
|
|
1054
|
+
**New Interface:**
|
|
1055
|
+
|
|
1056
|
+
```csharp
|
|
1057
|
+
// NEW: IVariableChangeObserver with ElementAccess
|
|
1058
|
+
public interface IVariableChangeObserver : IEventObserver
|
|
1059
|
+
{
|
|
1060
|
+
void OnVariableChanged(IUAVariable variable, UAValue newValue, UAValue oldValue,
|
|
1061
|
+
ElementAccess elementAccess, // <-- Full ElementAccess
|
|
1062
|
+
ulong senderId);
|
|
1063
|
+
}
|
|
1064
|
+
```
|
|
1065
|
+
|
|
1066
|
+
**Migration Example:**
|
|
1067
|
+
|
|
1068
|
+
```csharp
|
|
1069
|
+
// DEPRECATED: Old handler using uint[] indexes
|
|
1070
|
+
public class OldVariableMonitor : IVariableChangeObserver
|
|
1071
|
+
{
|
|
1072
|
+
public void OnVariableChanged(IUAVariable variable, UAValue newValue, UAValue oldValue,
|
|
1073
|
+
uint[] indexes, ulong senderId)
|
|
1074
|
+
{
|
|
1075
|
+
if (indexes.Length > 0)
|
|
1076
|
+
{
|
|
1077
|
+
Log.Info($"Array element {indexes[0]} changed");
|
|
1078
|
+
}
|
|
1079
|
+
}
|
|
1080
|
+
}
|
|
1081
|
+
|
|
1082
|
+
// NEW: Handler using ElementAccess
|
|
1083
|
+
public class NewVariableMonitor : IVariableChangeObserver
|
|
1084
|
+
{
|
|
1085
|
+
public void OnVariableChanged(IUAVariable variable, UAValue newValue, UAValue oldValue,
|
|
1086
|
+
ElementAccess elementAccess, ulong senderId)
|
|
1087
|
+
{
|
|
1088
|
+
var arrayIndex = elementAccess.ArrayIndex;
|
|
1089
|
+
var fieldIndexes = elementAccess.FieldIndexes;
|
|
1090
|
+
|
|
1091
|
+
if (arrayIndex.Length > 0)
|
|
1092
|
+
{
|
|
1093
|
+
Log.Info($"Array element {arrayIndex[0]} changed");
|
|
1094
|
+
}
|
|
1095
|
+
|
|
1096
|
+
if (fieldIndexes.Length > 0)
|
|
1097
|
+
{
|
|
1098
|
+
Log.Info($"Field at position {fieldIndexes[0].FieldPos} changed");
|
|
1099
|
+
}
|
|
1100
|
+
}
|
|
1101
|
+
}
|
|
1102
|
+
```
|
|
1103
|
+
|
|
1104
|
+
### Callback Observer: Constructor Change
|
|
1105
|
+
|
|
1106
|
+
**Deprecated Constructor:**
|
|
1107
|
+
|
|
1108
|
+
```csharp
|
|
1109
|
+
// DEPRECATED: CallbackVariableChangeObserver with uint[] action
|
|
1110
|
+
var observer = new CallbackVariableChangeObserver(
|
|
1111
|
+
(variable, newValue, oldValue, indexes, senderId) => {
|
|
1112
|
+
Log.Info($"Index: {indexes}");
|
|
1113
|
+
}
|
|
1114
|
+
);
|
|
1115
|
+
```
|
|
1116
|
+
|
|
1117
|
+
**New Constructor:**
|
|
1118
|
+
|
|
1119
|
+
```csharp
|
|
1120
|
+
// NEW: CallbackVariableChangeObserver with ElementAccess action
|
|
1121
|
+
var observer = new CallbackVariableChangeObserver(
|
|
1122
|
+
(variable, newValue, oldValue, elementAccess, senderId) => {
|
|
1123
|
+
Log.Info($"ArrayIndex: {elementAccess.ArrayIndex}");
|
|
1124
|
+
Log.Info($"FieldIndexes: {elementAccess.FieldIndexes}");
|
|
1125
|
+
}
|
|
1126
|
+
);
|
|
1127
|
+
```
|
|
1128
|
+
|
|
1129
|
+
**Migration Example:**
|
|
1130
|
+
|
|
1131
|
+
```csharp
|
|
1132
|
+
// OLD: Register callback with array indices
|
|
1133
|
+
LogicObject.Owner.Owner.GetVariable("MyVariable").Subscribe(
|
|
1134
|
+
new CallbackVariableChangeObserver(
|
|
1135
|
+
(variable, newValue, oldValue, indexes, senderId) => {
|
|
1136
|
+
var index = indexes.Length > 0 ? indexes[0] : 0;
|
|
1137
|
+
UpdateUI(index, newValue);
|
|
1138
|
+
}
|
|
1139
|
+
)
|
|
1140
|
+
);
|
|
1141
|
+
|
|
1142
|
+
// NEW: Register callback with ElementAccess
|
|
1143
|
+
LogicObject.Owner.Owner.GetVariable("MyVariable").Subscribe(
|
|
1144
|
+
new CallbackVariableChangeObserver(
|
|
1145
|
+
(variable, newValue, oldValue, elementAccess, senderId) => {
|
|
1146
|
+
var index = elementAccess.ArrayIndex.Length > 0 ? elementAccess.ArrayIndex[0] : 0;
|
|
1147
|
+
var fieldInfo = elementAccess.FieldIndexes.Length > 0 ? elementAccess.FieldIndexes[0] : null;
|
|
1148
|
+
UpdateUI(index, newValue, fieldInfo);
|
|
1149
|
+
}
|
|
1150
|
+
)
|
|
1151
|
+
);
|
|
1152
|
+
```
|
|
1153
|
+
|
|
1154
|
+
### Event Args Properties: Indexes to ElementAccess
|
|
1155
|
+
|
|
1156
|
+
**Deprecated Properties:**
|
|
1157
|
+
|
|
1158
|
+
```csharp
|
|
1159
|
+
// DEPRECATED properties in event argument classes
|
|
1160
|
+
public class VariableChangeEventArgs
|
|
1161
|
+
{
|
|
1162
|
+
[Obsolete("Use ElementAccess.ArrayIndex instead.")]
|
|
1163
|
+
public uint[] Indexes => ElementAccess.ArrayIndex;
|
|
1164
|
+
|
|
1165
|
+
public ElementAccess ElementAccess { get; }
|
|
1166
|
+
}
|
|
1167
|
+
|
|
1168
|
+
public class PathResolverResult
|
|
1169
|
+
{
|
|
1170
|
+
[Obsolete("Use ElementAccess.ArrayIndex instead.")]
|
|
1171
|
+
public uint[] Indexes => ElementAccess.ArrayIndex;
|
|
1172
|
+
|
|
1173
|
+
public ElementAccess ElementAccess { get; }
|
|
1174
|
+
}
|
|
1175
|
+
```
|
|
1176
|
+
|
|
1177
|
+
**Migration Example:**
|
|
1178
|
+
|
|
1179
|
+
```csharp
|
|
1180
|
+
// OLD: Access through Indexes property
|
|
1181
|
+
var indexes = eventArgs.Indexes;
|
|
1182
|
+
var elementIndex = indexes.Length > 0 ? indexes[0] : 0;
|
|
1183
|
+
|
|
1184
|
+
// OR in path resolver
|
|
1185
|
+
var indexes = resolverResult.Indexes;
|
|
1186
|
+
if (indexes != null)
|
|
1187
|
+
{
|
|
1188
|
+
Log.Info($"Array index: {indexes[0]}");
|
|
1189
|
+
}
|
|
1190
|
+
|
|
1191
|
+
// NEW: Access through ElementAccess
|
|
1192
|
+
var elementAccess = eventArgs.ElementAccess;
|
|
1193
|
+
var arrayIndex = elementAccess.ArrayIndex;
|
|
1194
|
+
var elementIndex = arrayIndex.Length > 0 ? arrayIndex[0] : 0;
|
|
1195
|
+
|
|
1196
|
+
// OR in path resolver
|
|
1197
|
+
var elementAccess = resolverResult.ElementAccess;
|
|
1198
|
+
if (elementAccess.ArrayIndex.Length > 0)
|
|
1199
|
+
{
|
|
1200
|
+
Log.Info($"Array index: {elementAccess.ArrayIndex[0]}");
|
|
1201
|
+
}
|
|
1202
|
+
|
|
1203
|
+
// Access field navigation too (not available in old Indexes property)
|
|
1204
|
+
if (elementAccess.FieldIndexes.Length > 0)
|
|
1205
|
+
{
|
|
1206
|
+
Log.Info($"Field position: {elementAccess.FieldIndexes[0].FieldPos}");
|
|
1207
|
+
}
|
|
1208
|
+
```
|