@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,437 @@
1
+ # Database Interaction
2
+
3
+ There are different ways to interact with database in FactoryTalk Optix, the native approach is to leverage the `Store` APIs to perform operations. The list of supported queries is documented [here](./queries.md).
4
+
5
+ > [!NOTE]
6
+ > Database connection is only established during the Runtime startup, so queries cannot be used in a DesignTime NetLogic. If you need to test your queries during the design phase, you must use third party tools or NuGet packages that provide direct connectivity to the database without using the FactoryTalk Optix APIs.
7
+
8
+ ## Insert
9
+
10
+ First you need to access the Database node, then get the Table by its BrowseName and then prepare the object which will contain the rows to add
11
+
12
+ ```csharp
13
+ private void InsertData()
14
+ {
15
+ // Get the Database object from the current project
16
+ var myStore = Project.Current.Get<Store>("DataStores/EmbeddedDatabase");
17
+ // Get a specific table by name
18
+ var myTable = myStore.Tables.Get<Table>("TableName");
19
+ // Prepare the header for the insert query (list of columns)
20
+ string[] columns = { "Username", "Apples", "Bananas", "Timestamp" };
21
+ // Create the new object, a bidimensional array where the first element
22
+ // is the number of rows to be added, the second one is the number
23
+ // of columns to be added (same size of the columns array)
24
+ var values = new object[1, 4];
25
+ // Set some values for each column
26
+ values[0, 0] = (string)Owner.Owner.Get<Label>("LbUser").Text;
27
+ values[0, 1] = (int)Owner.Owner.Get<SpinBox>("Apples").Value;
28
+ values[0, 2] = (int)Owner.Owner.Get<SpinBox>("Bananas").Value;
29
+ values[0, 3] = DateTime.Now;
30
+ // Perform the insert query
31
+ myTable.Insert(columns, values);
32
+ // Some log for users
33
+ Log.Info("InsertData", "Inserting data for user " + values[0, 0]);
34
+ }
35
+ ```
36
+
37
+ ## Update
38
+
39
+ ```csharp
40
+ private void UpdateData()
41
+ {
42
+ // Get the Database from the current project
43
+ var myStore = Project.Current.Get<Store>("DataStores/EmbeddedDatabase");
44
+ // Create the output to get the result
45
+ Object[,] ResultSet;
46
+ String[] Header;
47
+ // Perform the query
48
+ myStore.Query("UPDATE MyTable SET ColumnName=NewValue", out Header, out ResultSet);
49
+ }
50
+ ```
51
+
52
+ ## Delete
53
+
54
+ ```csharp
55
+ private void DeleteData()
56
+ {
57
+ // Get the Database from the current project
58
+ var myStore = Project.Current.Get<Store>("DataStores/EmbeddedDatabase");
59
+ // Create the output to get the result
60
+ Object[,] ResultSet;
61
+ String[] Header;
62
+ // Perform the query
63
+ myStore.Query("DELETE FROM MyTable WHERE ColumnName=NewValue", out Header, out ResultSet);
64
+ }
65
+ ```
66
+
67
+ ## Select
68
+
69
+ ### Simple select example
70
+
71
+ ```csharp
72
+ private void SelectData()
73
+ {
74
+ // Get the Database from the current project
75
+ var myStore = Project.Current.Get<Store>("DataStores/EmbeddedDatabase");
76
+ // Create the output to get the result
77
+ Object[,] ResultSet;
78
+ String[] Header;
79
+ // Perform the query
80
+ myStore.Query("SELECT * FROM TableName", out Header, out ResultSet);
81
+ // Get the result
82
+ Log.Info("SelectData", "Result: " + ResultSet[0, 0]);
83
+ }
84
+ ```
85
+
86
+ ### Select with WHERE clause
87
+
88
+ ```csharp
89
+ [ExportMethod]
90
+ public void SelectData(NodeId minDateTimePickerNodeId, NodeId maxDateTimePickerNodeId, out double averageValue)
91
+ {
92
+ // Get the DateTimePickers values from the UI
93
+ var minDateTimePicker = InformationModel.Get<DateTimePicker>(minDateTimePickerNodeId);
94
+ var maxDateTimePicker = InformationModel.Get<DateTimePicker>(maxDateTimePickerNodeId);
95
+ // Get the Database from the current project
96
+ var myStore = Project.Current.Get<Store>("DataStores/EmbeddedDatabase1");
97
+ // DateTime values need to be formatted in ISO8601 format for the query
98
+ var minDate = minDateTimePicker.Value.ToString("yyyy-MM-ddTHH:mm:ss.fffffff");
99
+ var maxDate = maxDateTimePicker.Value.ToString("yyyy-MM-ddTHH:mm:ss.fffffff");
100
+ // Create the output to get the result
101
+ Object[,] ResultSet;
102
+ String[] Header;
103
+ var query = $"SELECT AVG(Speed) AS AverageSpeed FROM prod1_table WHERE Machine_state = \"RUN\" AND (Timestamp BETWEEN \"{minDate}\" AND \"{maxDate}\") ORDER BY AverageSpeed DESC";
104
+ // Perform the query
105
+ myStore.Query(query, out Header, out ResultSet);
106
+ // Get the result
107
+ averageValue = (double)ResultSet[0, 0];
108
+ }
109
+ ```
110
+
111
+ ### Get the list of columns name from a DataBase
112
+
113
+ #### From ODBC
114
+
115
+ ```csharp
116
+ private void ExtractColumnNames()
117
+ {
118
+ // Get the Database from the current project
119
+ var myStore = Project.Current.Get<Store>("DataStores/EmbeddedDatabase");
120
+ // Create the output to get the result
121
+ Object[,] ResultSet;
122
+ String[] Header;
123
+ // Perform the query
124
+ myStore.Query("SELECT COLUMN_NAME FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME = 'AlarmsEventLogger1'", out Header, out ResultSet);
125
+ }
126
+ ```
127
+
128
+ #### From EmbeddedDatabase
129
+
130
+ The EmbeddedDatabase uses a SQLite technology which only supports a subset of features from a normal store, we will need some additional steps
131
+
132
+ ```csharp
133
+ private void ExtractColumnNames()
134
+ {
135
+ Regex regexTable = new Regex("(?<=\\bFROM\\s)(\\w+)");
136
+ String tableName = regexTable.Match(Owner.GetVariable("Query").Value.Value.ToString(), 0, Owner.GetVariable("Query").Value.Value.ToString().Length).Value;
137
+
138
+ // Check if we are using EmbeddedDatabase or SQL
139
+ if (dataSource.GetType().FullName.ToString().Contains("SQLite"))
140
+ {
141
+ // Extract list of tables
142
+ var tablesList = myStore.Children.ToList()[0].Children.ToList();
143
+ foreach (var table in tablesList)
144
+ {
145
+ // Check if current table matches input query table
146
+ if (((QPlatform.SQLiteStore.SQLiteStoreTable)table).BrowseName == tableName)
147
+ {
148
+ var columnList = ((QPlatform.SQLiteStore.SQLiteStoreTable)table).Columns.ToList();
149
+ // Extract columns name
150
+ for (int i = 1; i < (columnList.Count - 2); i++)
151
+ {
152
+ if (i == 1)
153
+ {
154
+ dataNames = columnList[i].BrowseName.ToString();
155
+ } else
156
+ {
157
+ dataNames += ", " + columnList[i].BrowseName.ToString();
158
+ }
159
+ }
160
+ }
161
+ }
162
+ }
163
+ ```
164
+
165
+ ## Creating random Database data
166
+
167
+ ```csharp
168
+ private void PopulateTableWithRandomData(string tableName)
169
+ {
170
+ // Read the column names from the current project
171
+ var tableColumnsNames = GetTableColumnsNames(tableName);
172
+ var tableColumnsTypes = GetTableColumnsTypes(tableName);
173
+ // Add some data (using a for loop to specify entries)
174
+ for (int i = 0; i < 100; i++)
175
+ {
176
+ object[,] values = GenerateTableRowData(tableColumnsTypes, tableColumnsNames);
177
+ // Perform the insert
178
+ _store.Insert(tableName, tableColumnsNames.ToArray(), values);
179
+ }
180
+ }
181
+
182
+ private object[,] GenerateTableRowData(List<Type> tableColumnsTypes, List<string> tableColumnsNames)
183
+ {
184
+ Random random = new Random();
185
+ object[,] values = new object[1, tableColumnsTypes.Count];
186
+ // Generate a random value for each column
187
+ var rowData = tableColumnsTypes.Select(rowType => GetRandomValue(rowType, random)).ToList();
188
+ // Add the new data to the returned object
189
+ for (int i = 0; i < tableColumnsTypes.Count; i++)
190
+ {
191
+ values[0, i] = rowData[i];
192
+ }
193
+ return values;
194
+ }
195
+
196
+ private static object GetRandomValue(Type dataType, Random random)
197
+ {
198
+ if (dataType == typeof(int)) return random.Next(1, 100);
199
+ if (dataType == typeof(float)) return random.NextDouble() * 100.0;
200
+ if (dataType == typeof(string)) return Guid.NewGuid().ToString();
201
+ if (dataType == typeof(bool)) return random.Next(2);
202
+ return null;
203
+ }
204
+
205
+ ```
206
+
207
+ ## Dynamic creation of a DataGrid
208
+
209
+ ```csharp
210
+ [ExportMethod]
211
+ public void QueryOnStore(string tableName)
212
+ {
213
+ queryTask?.Dispose();
214
+ var arguments = new object[] { tableName };
215
+ queryTask = new LongRunningTask(QueryAndUpdate, arguments, LogicObject);
216
+ queryTask.Start();
217
+ }
218
+
219
+ private void QueryAndUpdate(LongRunningTask myTask, object args)
220
+ {
221
+ // Get the table name from the arguments
222
+ var argumentsArray = (object[])args;
223
+ var tableName = (string)argumentsArray[0];
224
+
225
+ if (string.IsNullOrEmpty(tableName))
226
+ return;
227
+
228
+ // Get the DataGrid and Store
229
+ var store = Project.Current.Get<Store>("DataStores/EmbeddedDatabase1");
230
+ var dataGrid = Owner.Get<DataGrid>("DataGrid");
231
+
232
+ if (store == null || dataGrid == null)
233
+ return;
234
+
235
+ // Reset the grid
236
+ dataGrid.Query = "";
237
+
238
+ // Prepare the query
239
+ var query = $"SELECT * FROM {tableName}";
240
+
241
+ // Run the query
242
+ store.Query(query, out String[] header, out Object[,] resultSet);
243
+
244
+ if (header == null || resultSet == null)
245
+ return;
246
+
247
+ // Clear existing rows
248
+ dataGrid.Columns.Clear();
249
+
250
+ DynamicLink lastDynamicLink = null;
251
+
252
+ // Create columns based on the header
253
+ foreach (var columnName in header)
254
+ {
255
+ var newDataGridColumn = InformationModel.MakeObject<DataGridColumn>(columnName);
256
+ newDataGridColumn.Title = columnName;
257
+ newDataGridColumn.DataItemTemplate = InformationModel.MakeObject<DataGridLabelItemTemplate>("DataItemTemplate");
258
+ var dynamicLink = InformationModel.MakeVariable<DynamicLink>("DynamicLink", FTOptix.Core.DataTypes.NodePath);
259
+ dynamicLink.Value = "{Item}/" + NodePath.EscapeNodePathBrowseName(columnName);
260
+ newDataGridColumn.DataItemTemplate.GetVariable("Text").Refs.AddReference(FTOptix.CoreBase.ReferenceTypes.HasDynamicLink, dynamicLink);
261
+ newDataGridColumn.OrderBy = dynamicLink.Value;
262
+ dataGrid.Columns.Add(newDataGridColumn);
263
+ lastDynamicLink = dynamicLink;
264
+ }
265
+
266
+ // Set last column as sort item - not really needed
267
+ dataGrid.SortColumnVariable.Refs.AddReference(FTOptix.CoreBase.ReferenceTypes.HasDynamicLink, lastDynamicLink);
268
+
269
+ // Add the new query to the grid
270
+ dataGrid.Query = query + " ORDER BY Timestamp DESC";
271
+ }
272
+ ```
273
+
274
+ ## CSV interaction
275
+
276
+ A simple way to import or export data from a database is to use CSV files, here are some examples of how to read and write CSV files using C# in FactoryTalk Optix.
277
+
278
+ ### Read / Write CSV files
279
+
280
+ ```csharp
281
+ /// <summary>
282
+ /// Read or write a CSV file from the file system.
283
+ /// Inputs: file path (string). Outputs: none.
284
+ /// Error modes: file not found, IO exceptions. Success: file is created/read.
285
+ /// </summary>
286
+ // READ CSV FILE
287
+ using System.IO;
288
+
289
+ // Path to the CSV file
290
+ string filePath = "path/to/file.csv";
291
+
292
+ // Read the contents of the CSV file
293
+ using (var reader = new StreamReader(filePath))
294
+ {
295
+ while (!reader.EndOfStream)
296
+ {
297
+ // Read the current line
298
+ var line = reader.ReadLine();
299
+
300
+ // Split the line into an array of values (CSV separated by commas)
301
+ var values = line.Split(',');
302
+
303
+ // TODO: process values (e.g., map to variables, insert into DB, etc.)
304
+ }
305
+ }
306
+
307
+ // WRITE CSV FILE - Solution 1: overwrite/create
308
+ using (var writer = new StreamWriter(filePath))
309
+ {
310
+ // Write the header row
311
+ writer.WriteLine("Column1,Column2,Column3");
312
+
313
+ // Write some data rows
314
+ writer.WriteLine("Value1,Value2,Value3");
315
+ writer.WriteLine("Value4,Value5,Value6");
316
+ }
317
+
318
+ // WRITE CSV FILE - Solution 2: ensure file exists then write
319
+ try
320
+ {
321
+ if (!File.Exists(filePath))
322
+ {
323
+ // Create a file to write to.
324
+ using (StreamWriter sw = File.CreateText(filePath))
325
+ {
326
+ sw.WriteLine("Hello");
327
+ sw.WriteLine("And");
328
+ sw.WriteLine("Welcome");
329
+ }
330
+ }
331
+ else
332
+ {
333
+ using (var writer = new StreamWriter(filePath))
334
+ {
335
+ writer.WriteLine("New row!");
336
+ }
337
+ }
338
+ // Example: set a UI variable or log to notify success
339
+ // result.Value = "File correctly created: " + filePath;
340
+ }
341
+ catch (Exception ex)
342
+ {
343
+ // result.Value = "Impossible to create the file: " + ex.Message;
344
+ Log.Error("CSV", "Unable to create or write CSV: " + ex.Message);
345
+ }
346
+
347
+ ```
348
+
349
+ ### Create an ODBC/Embedded DB table from a CSV schema file
350
+
351
+ ```csharp
352
+ /// <summary>
353
+ /// Reads a CSV file that describes a table schema and creates a corresponding table in the specified ODBC/Embedded store.
354
+ /// CSV format expected: each line contains: ColumnName;ColumnType
355
+ /// Supported ColumnType values: String, Bool, Real, Int
356
+ /// </summary>
357
+ [ExportMethod]
358
+ public void TableCreation()
359
+ {
360
+ var csvPath = GetCSVFilePath();
361
+ if (string.IsNullOrEmpty(csvPath) || !System.IO.File.Exists(csvPath))
362
+ {
363
+ Log.Error("TableCreation", "CSV not found. Please configure the CSV to parse in the NetLogic configuration");
364
+ return;
365
+ }
366
+
367
+ var storePointer = LogicObject.GetVariable("Store")?.Value;
368
+ var storeDB = InformationModel.Get<ODBCStore>(storePointer);
369
+ if (storeDB == null)
370
+ {
371
+ Log.Error("TableCreation", "Store is not correct or not configured");
372
+ return;
373
+ }
374
+
375
+ Table myTable = null;
376
+ using (var reader = new StreamReader(csvPath))
377
+ {
378
+ int lineIndex = 0;
379
+ while (!reader.EndOfStream)
380
+ {
381
+ var line = reader.ReadLine();
382
+ if (string.IsNullOrWhiteSpace(line))
383
+ continue;
384
+
385
+ // First non-empty line is expected to be the table name
386
+ if (lineIndex == 0)
387
+ {
388
+ var tableName = line.Trim();
389
+ if (storeDB.Find<Table>(tableName) != null)
390
+ {
391
+ Log.Error("TableCreation", "The table already exists: " + tableName);
392
+ return;
393
+ }
394
+ myTable = InformationModel.Make<Table>(tableName);
395
+ storeDB.Tables.Add(myTable);
396
+ lineIndex++;
397
+ continue;
398
+ }
399
+
400
+ // Parse column definition: name;type
401
+ var values = line.Split(';');
402
+ if (values.Length < 2)
403
+ {
404
+ Log.Warning("TableCreation", "Skipping invalid line: " + line);
405
+ continue;
406
+ }
407
+
408
+ var colName = values[0].Trim();
409
+ var colType = values[1].Trim();
410
+ var myColumn = InformationModel.Make<StoreColumn>(colName);
411
+
412
+ switch (colType)
413
+ {
414
+ case "String":
415
+ myColumn.DataType = OpcUa.DataTypes.String;
416
+ break;
417
+ case "Bool":
418
+ myColumn.DataType = OpcUa.DataTypes.Boolean;
419
+ break;
420
+ case "Real":
421
+ myColumn.DataType = OpcUa.DataTypes.Float;
422
+ break;
423
+ case "Int":
424
+ myColumn.DataType = OpcUa.DataTypes.Int16;
425
+ break;
426
+ default:
427
+ Log.Error("TableCreation", "Unsupported column type: " + colType + ". Table creation not completed.");
428
+ return;
429
+ }
430
+
431
+ myTable.Columns.Add(myColumn);
432
+ }
433
+ }
434
+
435
+ Log.Info("TableCreation", "The table " + (myTable?.BrowseName ?? "(unknown)") + " has been correctly created!");
436
+ }
437
+ ```
@@ -0,0 +1,123 @@
1
+ # Data grids
2
+
3
+ ## Getting column values from the UI Selected Item
4
+
5
+ Add a Runtime NetLogic as children of the DataGrid, add the following NetLogic and invoke the `DataGridSelectionChanged` method in the `User selection changed` event of the DataGrid itself, remember to pass the NodeId of the DataGrid as argument of the function (to make it session-independent).
6
+ The UI Selected Item is actually an object which has a children variable per each column of the DataGrid.
7
+
8
+ ```csharp
9
+ [ExportMethod]
10
+ public void DataGridSelectionChanged(NodeId dataGrid)
11
+ {
12
+ // Session variables where to store the output of the SelectedItem
13
+ var sessionClientId = Session.GetVariable("selectedClientId");
14
+ var sessionUsedCnt = Session.GetVariable("selectedUsedCnt");
15
+ var sessionLastUsedTime = Session.GetVariable("selectedLastUsedTime");
16
+
17
+ // Retrieve the DataGrid object
18
+ var dataGridItem = InformationModel.Get<DataGrid>(dataGrid);
19
+ if (dataGridItem == null)
20
+ {
21
+ Log.Error("DataGrid is null");
22
+ return;
23
+ }
24
+
25
+ // Get the UI Selected Item variable (an object)
26
+ var selectedRow = InformationModel.Get(dataGridItem.UISelectedItem);
27
+ if (selectedRow == null)
28
+ {
29
+ Log.Error("Selected row is null");
30
+ return;
31
+ }
32
+
33
+ // Get the value at the column called "ClientID" (string)
34
+ var selectedRowClientId = selectedRow.Children.OfType<IUAVariable>().FirstOrDefault(x => x.BrowseName == "ClientID");
35
+ if (selectedRowClientId == null)
36
+ {
37
+ Log.Error("ClientID is null");
38
+ return;
39
+ }
40
+
41
+ // Get the value at the column called "Used" (Bool)
42
+ var selectedRowUsedCnt = selectedRow.Children.OfType<IUAVariable>().FirstOrDefault(x => x.BrowseName == "Used");
43
+ if (selectedRowUsedCnt == null)
44
+ {
45
+ Log.Error("Used is null");
46
+ return;
47
+ }
48
+
49
+ // Get the value at the column called "LastUsedTime" (DateTime)
50
+ var selectedRowLastUsedTime = selectedRow.Children.OfType<IUAVariable>().FirstOrDefault(x => x.BrowseName == "LastUsedTime");
51
+ if (selectedRowLastUsedTime == null)
52
+ {
53
+ Log.Error("LastUsedTime is null");
54
+ return;
55
+ }
56
+
57
+ // Set the variables in the editor object
58
+ sessionClientId.Value = selectedRowClientId.Value.Value.ToString();
59
+ sessionUsedCnt.Value = Convert.ToInt32(selectedRowUsedCnt.Value.Value.ToString());
60
+ sessionLastUsedTime.Value = Convert.ToDateTime(selectedRowLastUsedTime.Value.Value.ToString());
61
+ }
62
+ ```
63
+
64
+ ## Interacting with the content of a DataGrid
65
+
66
+ ```csharp
67
+ [ExportMethod]
68
+ public void UpdateDataGridValues()
69
+ {
70
+ // Retrieve the DataGrid object
71
+ DataGrid dg = (DataGrid)Owner;
72
+ // Get the "Models" object which is the parent of all the rows of the DataGrid
73
+ IUAObject t = dg.Children.GetObject("Models");
74
+
75
+ // Get all rows as a list
76
+ List<UAVariable> rows = new List<UAVariable>();
77
+ foreach (var child in t.Children)
78
+ {
79
+ if (child is UAVariable variable)
80
+ rows.Add(variable);
81
+ }
82
+
83
+ double previousEl = 0;
84
+ double previousGas = 0;
85
+ double previousWater = 0;
86
+ double previousSteam = 0;
87
+
88
+ // Row 0: header of the grid
89
+ // Row 1: selected item
90
+ // Row 2 and following: actual content of the grid, where each row is an object with a variable per each column of the grid
91
+ for (int i = 2; i < rows.Count; i++)
92
+ {
93
+ var row = rows[i];
94
+ // Get all columns as a list
95
+ List<UAVariable> cols = new List<UAVariable>();
96
+ foreach (var child in row.Children)
97
+ {
98
+ if (child is UAVariable variable)
99
+ cols.Add(variable);
100
+ }
101
+ DateTime timestamp = (DateTime)cols[0].Value;
102
+ if (timestamp.Minute == 0 && i > 0)
103
+ {
104
+ // Edit the value of a column by name
105
+ previousEl = UpdateDGField(ref cols, 2, previousEl);
106
+ previousGas = UpdateDGField(ref cols, 3, previousGas);
107
+ previousWater = UpdateDGField(ref cols, 4, previousWater);
108
+ previousSteam = UpdateDGField(ref cols, 5, previousSteam);
109
+ }
110
+ }
111
+ }
112
+
113
+ private double UpdateDGField(ref List<UAVariable> cols, int i, double prev)
114
+ {
115
+ double current = Convert.ToDouble(cols[i].Value);
116
+ if (current >= prev)
117
+ cols[i+4].Value = current - prev;
118
+ else
119
+ cols[i+4].Value = (current + 32767) - prev;
120
+ prev = current;
121
+ return prev;
122
+ }
123
+ ```