@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.
- package/CHANGELOG.md +98 -0
- package/README.md +10 -7
- package/cordis.patch.yml +1 -1
- package/entries/netlogic.js +266 -0
- package/entries/optix.js +13 -0
- package/package.json +3 -2
- package/vendor/ftview/SKILL.md +6 -0
- package/vendor/ftview/lib/model.d.ts +8 -3
- package/vendor/ftview/lib/model.js +63 -16
- package/vendor/l5x/lib/engine/analysis.d.ts +9 -1
- package/vendor/l5x/lib/engine/analysis.js +28 -3
- package/vendor/l5x/lib/engine/change-set.js +5 -4
- package/vendor/l5x/lib/engine/cip-request.d.ts +1 -0
- package/vendor/l5x/lib/engine/cip-request.js +71 -26
- package/vendor/l5x/lib/engine/direct-polling.js +12 -6
- package/vendor/l5x/lib/engine/review.js +12 -1
- package/vendor/l5x/lib/engine/studio-sdk.js +24 -6
- package/vendor/mwi/code/mw_code.py +4 -1
- package/vendor/mwi/legacy/native-variables.js +9 -1
- package/vendor/mwi/lib/read/service.d.ts +7 -1
- package/vendor/mwi/lib/read/service.js +13 -1
- package/vendor/mwi/lib/session/service.js +7 -1
- 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,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
|
+
```
|