@aryacode/dsh-engineering 0.4.22 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (76) hide show
  1. package/CHANGELOG.md +98 -0
  2. package/README.md +10 -7
  3. package/cordis.patch.yml +1 -1
  4. package/entries/netlogic.js +266 -0
  5. package/entries/optix.js +13 -0
  6. package/package.json +3 -2
  7. package/vendor/ftview/SKILL.md +6 -0
  8. package/vendor/ftview/lib/model.d.ts +8 -3
  9. package/vendor/ftview/lib/model.js +63 -16
  10. package/vendor/l5x/lib/engine/analysis.d.ts +9 -1
  11. package/vendor/l5x/lib/engine/analysis.js +28 -3
  12. package/vendor/l5x/lib/engine/change-set.js +5 -4
  13. package/vendor/l5x/lib/engine/cip-request.d.ts +1 -0
  14. package/vendor/l5x/lib/engine/cip-request.js +71 -26
  15. package/vendor/l5x/lib/engine/direct-polling.js +12 -6
  16. package/vendor/l5x/lib/engine/review.js +12 -1
  17. package/vendor/l5x/lib/engine/studio-sdk.js +24 -6
  18. package/vendor/mwi/code/mw_code.py +4 -1
  19. package/vendor/mwi/legacy/native-variables.js +9 -1
  20. package/vendor/mwi/lib/read/service.d.ts +7 -1
  21. package/vendor/mwi/lib/read/service.js +13 -1
  22. package/vendor/mwi/lib/session/service.js +7 -1
  23. package/vendor/optix/SKILL.md +68 -0
  24. package/vendor/optix/netlogic/SOURCE.json +12 -0
  25. package/vendor/optix/netlogic/index.json +1987 -0
  26. package/vendor/optix/netlogic/pages/accessing-project-nodes.md +113 -0
  27. package/vendor/optix/netlogic/pages/advanced-svg.md +124 -0
  28. package/vendor/optix/netlogic/pages/alarming.md +489 -0
  29. package/vendor/optix/netlogic/pages/async-tasks.md +236 -0
  30. package/vendor/optix/netlogic/pages/audit-signature.md +91 -0
  31. package/vendor/optix/netlogic/pages/colors.md +122 -0
  32. package/vendor/optix/netlogic/pages/companion-specs.md +77 -0
  33. package/vendor/optix/netlogic/pages/creating-objects.md +370 -0
  34. package/vendor/optix/netlogic/pages/database-interaction.md +437 -0
  35. package/vendor/optix/netlogic/pages/datagrids.md +123 -0
  36. package/vendor/optix/netlogic/pages/debugging-tips.md +605 -0
  37. package/vendor/optix/netlogic/pages/designtime-tags-import.md +531 -0
  38. package/vendor/optix/netlogic/pages/dialog-boxes.md +150 -0
  39. package/vendor/optix/netlogic/pages/dispatch-events.md +200 -0
  40. package/vendor/optix/netlogic/pages/dynamic-links.md +825 -0
  41. package/vendor/optix/netlogic/pages/element-access.md +1208 -0
  42. package/vendor/optix/netlogic/pages/engineering-units.md +298 -0
  43. package/vendor/optix/netlogic/pages/events.md +340 -0
  44. package/vendor/optix/netlogic/pages/execute-command.md +202 -0
  45. package/vendor/optix/netlogic/pages/fto-studio-cli.md +99 -0
  46. package/vendor/optix/netlogic/pages/good-practices.md +50 -0
  47. package/vendor/optix/netlogic/pages/information-model.md +89 -0
  48. package/vendor/optix/netlogic/pages/log-output.md +22 -0
  49. package/vendor/optix/netlogic/pages/managing-aliases.md +22 -0
  50. package/vendor/optix/netlogic/pages/manipulate-nodes.md +72 -0
  51. package/vendor/optix/netlogic/pages/mqtt.md +240 -0
  52. package/vendor/optix/netlogic/pages/netlogic-lifecycle.md +270 -0
  53. package/vendor/optix/netlogic/pages/netlogic-overview.md +155 -0
  54. package/vendor/optix/netlogic/pages/opcua.md +325 -0
  55. package/vendor/optix/netlogic/pages/plc-tags.md +219 -0
  56. package/vendor/optix/netlogic/pages/queries.md +1297 -0
  57. package/vendor/optix/netlogic/pages/random-generation.md +90 -0
  58. package/vendor/optix/netlogic/pages/recipes.md +375 -0
  59. package/vendor/optix/netlogic/pages/recipex.md +2383 -0
  60. package/vendor/optix/netlogic/pages/regex.md +15 -0
  61. package/vendor/optix/netlogic/pages/register-observers.md +346 -0
  62. package/vendor/optix/netlogic/pages/reports.md +141 -0
  63. package/vendor/optix/netlogic/pages/resource-uri.md +308 -0
  64. package/vendor/optix/netlogic/pages/runtime-tags-import.md +280 -0
  65. package/vendor/optix/netlogic/pages/serial-port.md +357 -0
  66. package/vendor/optix/netlogic/pages/sessions.md +58 -0
  67. package/vendor/optix/netlogic/pages/system-node.md +153 -0
  68. package/vendor/optix/netlogic/pages/template-library.md +127 -0
  69. package/vendor/optix/netlogic/pages/threading.md +236 -0
  70. package/vendor/optix/netlogic/pages/translations.md +417 -0
  71. package/vendor/optix/netlogic/pages/trends.md +128 -0
  72. package/vendor/optix/netlogic/pages/ui-animations.md +115 -0
  73. package/vendor/optix/netlogic/pages/users-groups.md +416 -0
  74. package/vendor/optix/netlogic/pages/variables-formatting.md +16 -0
  75. package/vendor/optix/netlogic/pages/variables-interaction.md +557 -0
  76. 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
+ ```