@aryacode/dsh-engineering 0.5.0 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (60) hide show
  1. package/CHANGELOG.md +20 -0
  2. package/README.md +6 -6
  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/optix/SKILL.md +68 -0
  8. package/vendor/optix/netlogic/SOURCE.json +12 -0
  9. package/vendor/optix/netlogic/index.json +1987 -0
  10. package/vendor/optix/netlogic/pages/accessing-project-nodes.md +113 -0
  11. package/vendor/optix/netlogic/pages/advanced-svg.md +124 -0
  12. package/vendor/optix/netlogic/pages/alarming.md +489 -0
  13. package/vendor/optix/netlogic/pages/async-tasks.md +236 -0
  14. package/vendor/optix/netlogic/pages/audit-signature.md +91 -0
  15. package/vendor/optix/netlogic/pages/colors.md +122 -0
  16. package/vendor/optix/netlogic/pages/companion-specs.md +77 -0
  17. package/vendor/optix/netlogic/pages/creating-objects.md +370 -0
  18. package/vendor/optix/netlogic/pages/database-interaction.md +437 -0
  19. package/vendor/optix/netlogic/pages/datagrids.md +123 -0
  20. package/vendor/optix/netlogic/pages/debugging-tips.md +605 -0
  21. package/vendor/optix/netlogic/pages/designtime-tags-import.md +531 -0
  22. package/vendor/optix/netlogic/pages/dialog-boxes.md +150 -0
  23. package/vendor/optix/netlogic/pages/dispatch-events.md +200 -0
  24. package/vendor/optix/netlogic/pages/dynamic-links.md +825 -0
  25. package/vendor/optix/netlogic/pages/element-access.md +1208 -0
  26. package/vendor/optix/netlogic/pages/engineering-units.md +298 -0
  27. package/vendor/optix/netlogic/pages/events.md +340 -0
  28. package/vendor/optix/netlogic/pages/execute-command.md +202 -0
  29. package/vendor/optix/netlogic/pages/fto-studio-cli.md +99 -0
  30. package/vendor/optix/netlogic/pages/good-practices.md +50 -0
  31. package/vendor/optix/netlogic/pages/information-model.md +89 -0
  32. package/vendor/optix/netlogic/pages/log-output.md +22 -0
  33. package/vendor/optix/netlogic/pages/managing-aliases.md +22 -0
  34. package/vendor/optix/netlogic/pages/manipulate-nodes.md +72 -0
  35. package/vendor/optix/netlogic/pages/mqtt.md +240 -0
  36. package/vendor/optix/netlogic/pages/netlogic-lifecycle.md +270 -0
  37. package/vendor/optix/netlogic/pages/netlogic-overview.md +155 -0
  38. package/vendor/optix/netlogic/pages/opcua.md +325 -0
  39. package/vendor/optix/netlogic/pages/plc-tags.md +219 -0
  40. package/vendor/optix/netlogic/pages/queries.md +1297 -0
  41. package/vendor/optix/netlogic/pages/random-generation.md +90 -0
  42. package/vendor/optix/netlogic/pages/recipes.md +375 -0
  43. package/vendor/optix/netlogic/pages/recipex.md +2383 -0
  44. package/vendor/optix/netlogic/pages/regex.md +15 -0
  45. package/vendor/optix/netlogic/pages/register-observers.md +346 -0
  46. package/vendor/optix/netlogic/pages/reports.md +141 -0
  47. package/vendor/optix/netlogic/pages/resource-uri.md +308 -0
  48. package/vendor/optix/netlogic/pages/runtime-tags-import.md +280 -0
  49. package/vendor/optix/netlogic/pages/serial-port.md +357 -0
  50. package/vendor/optix/netlogic/pages/sessions.md +58 -0
  51. package/vendor/optix/netlogic/pages/system-node.md +153 -0
  52. package/vendor/optix/netlogic/pages/template-library.md +127 -0
  53. package/vendor/optix/netlogic/pages/threading.md +236 -0
  54. package/vendor/optix/netlogic/pages/translations.md +417 -0
  55. package/vendor/optix/netlogic/pages/trends.md +128 -0
  56. package/vendor/optix/netlogic/pages/ui-animations.md +115 -0
  57. package/vendor/optix/netlogic/pages/users-groups.md +416 -0
  58. package/vendor/optix/netlogic/pages/variables-formatting.md +16 -0
  59. package/vendor/optix/netlogic/pages/variables-interaction.md +557 -0
  60. package/vendor/optix/netlogic/pages/variables-structure.md +155 -0
@@ -0,0 +1,308 @@
1
+ # Resource URIs
2
+
3
+ ## Important note
4
+
5
+ Although multiple solutions are shown here to set the target path for a ResourceUri variable, please remember that the WebPresentationEngine only supports accessing elements that are in the `%PROJECTDIR%` and a relative path is used (example: `%PROJECTDIR%/imgs/logo.svg`), using an absolute path for an element (example: `C:/OptixProjects/NewHMIProject1/Project Files/imgs/logo.svg`), will never work in the WebPresentationEngine for safety reasons.
6
+
7
+ ## Check if a file is within the project directory and use relative URIs
8
+
9
+ When working with file operations, it's essential to determine whether a file is located within the project directory. If it is, you should use a project-relative URI for WebPresentationEngine compatibility. This is particularly important for components like the WebPresentationEngine that **only accept relative resource URIs**.
10
+
11
+ ```csharp
12
+ /// <summary>
13
+ /// Attempts to get the project-relative path if the file is within the project directory.
14
+ /// </summary>
15
+ /// <param name="filePath">The file path to check (can be URI or absolute path)</param>
16
+ /// <returns>The relative path within the project directory, or null if the file is not within the project</returns>
17
+ private string TryGetProjectRelativePath(string filePath)
18
+ {
19
+ try
20
+ {
21
+ // Get the absolute file path from the input (could be a file URI)
22
+ string absoluteFilePath = GetAbsolutePathFromUri(filePath);
23
+ if (string.IsNullOrEmpty(absoluteFilePath))
24
+ return null;
25
+
26
+ // Get the project directory
27
+ string projectDirectory = Project.Current.ProjectDirectory;
28
+ if (string.IsNullOrEmpty(projectDirectory))
29
+ return null;
30
+
31
+ // Normalize both paths to ensure consistent comparison
32
+ string normalizedFilePath = Path.GetFullPath(absoluteFilePath);
33
+ string normalizedProjectPath = Path.GetFullPath(projectDirectory);
34
+
35
+ // Ensure project path ends with directory separator for proper containment check
36
+ if (!normalizedProjectPath.EndsWith(Path.DirectorySeparatorChar.ToString()) &&
37
+ !normalizedProjectPath.EndsWith(Path.AltDirectorySeparatorChar.ToString()))
38
+ {
39
+ normalizedProjectPath += Path.DirectorySeparatorChar;
40
+ }
41
+
42
+ // Check if the file is within the project directory
43
+ if (normalizedFilePath.StartsWith(normalizedProjectPath, StringComparison.OrdinalIgnoreCase))
44
+ {
45
+ // Calculate the relative path from project directory
46
+ string relativePath = normalizedFilePath.Substring(normalizedProjectPath.Length);
47
+
48
+ // Convert path separators to forward slashes for consistency
49
+ relativePath = relativePath.Replace(Path.DirectorySeparatorChar, '/');
50
+
51
+ return relativePath;
52
+ }
53
+
54
+ return null;
55
+ }
56
+ catch (Exception ex)
57
+ {
58
+ Log.Error("TryGetProjectRelativePath", $"Error determining project relative path: {ex.Message}");
59
+ return null;
60
+ }
61
+ }
62
+
63
+ /// <summary>
64
+ /// Extracts the absolute file path from a URI or returns the path if it's already absolute.
65
+ /// </summary>
66
+ /// <param name="uriOrPath">URI or file path</param>
67
+ /// <returns>Absolute file path, or null if invalid</returns>
68
+ private string GetAbsolutePathFromUri(string uriOrPath)
69
+ {
70
+ if (string.IsNullOrEmpty(uriOrPath))
71
+ return null;
72
+
73
+ try
74
+ {
75
+ // Try to parse as URI first
76
+ if (Uri.TryCreate(uriOrPath, UriKind.Absolute, out Uri uri))
77
+ {
78
+ return uri.IsFile ? uri.LocalPath : uriOrPath;
79
+ }
80
+
81
+ // If not a URI, treat as file path
82
+ return Path.IsPathRooted(uriOrPath) ? uriOrPath : null;
83
+ }
84
+ catch (Exception ex)
85
+ {
86
+ Log.Error("GetAbsolutePathFromUri", $"Error parsing URI/path '{uriOrPath}': {ex.Message}");
87
+ return null;
88
+ }
89
+ }
90
+ ```
91
+
92
+ ### Usage example
93
+
94
+ ```csharp
95
+ [ExportMethod]
96
+ public void HandleFileSelection(string filePath)
97
+ {
98
+ try
99
+ {
100
+ // Try to determine if the file is within the project directory
101
+ var projectRelativePath = TryGetProjectRelativePath(filePath);
102
+ if (projectRelativePath != null)
103
+ {
104
+ // File is within project directory - use project relative URI for WebPresentationEngine compatibility
105
+ var resourceUri = ResourceUri.FromProjectRelativePath(projectRelativePath);
106
+ Log.Info("HandleFileSelection", $"Using project relative path: {projectRelativePath}");
107
+ // Use resourceUri for WebPresentationEngine-compatible components
108
+ }
109
+ else
110
+ {
111
+ // File is outside project directory - cannot be used with WebPresentationEngine
112
+ Log.Warning("HandleFileSelection", "The selected file will not work in the WebPresentationEngine as it is not in the ProjectDir");
113
+ // Fallback to absolute path if needed, but be aware of limitations
114
+ }
115
+ }
116
+ catch (Exception ex)
117
+ {
118
+ Log.Error("HandleFileSelection", $"Error processing file path '{filePath}': {ex.Message}");
119
+ }
120
+ }
121
+ ```
122
+
123
+ ## Create a ResourceUri from scratch
124
+
125
+ ```csharp
126
+ // Get the working folder from the current context
127
+ var baseUri = new Uri(System.IO.Directory.GetCurrentDirectory());
128
+ // Get the ProjectDir folder
129
+ var chartUri = new Uri(baseUri, Project.Current.ProjectDirectory);
130
+ // Browse to the NetSolution folder
131
+ var nextStep = new Uri(chartUri, "NetSolution");
132
+ // Browse to NetSolution/Charts.xlst
133
+ var finalPath = new Uri(nextStep, "Charts.xslt");
134
+ // Get the full path
135
+ string filePath = finalPath.AbsolutePath;
136
+ Log.Info("Final path: " + filePath);
137
+ Log.Info(MethodBase.GetCurrentMethod().Name, $"File name is: {fileName}");
138
+ ```
139
+
140
+ ## Reading a ResourceUri
141
+
142
+ This assumes the variable is set to ResourceUri
143
+
144
+ ```csharp
145
+ // Get a variable with datatype set to ResourceUri
146
+ var csvPathVariable = LogicObject.Get<IUAVariable>("CSVPath");
147
+ // Get the full path
148
+ string myString = new ResourceUri(csvPathVariable.Value).Uri;
149
+ ```
150
+
151
+ ```csharp
152
+ /// <summary>
153
+ /// Examples to log common ResourceUri locations: project, application and USB.
154
+ /// This helps when debugging paths used by NetLogic or UI resources.
155
+ /// </summary>
156
+ // Project files
157
+ Log.Info(ResourceUri.FromProjectRelativePath("").Uri);
158
+ // Application files
159
+ Log.Info(ResourceUri.FromApplicationRelativePath("").Uri);
160
+ // USB device 1 (may throw if not present)
161
+ try
162
+ {
163
+ Log.Info(ResourceUri.FromUSBRelativePath(1, "").Uri);
164
+ }
165
+ catch (Exception ex)
166
+ {
167
+ Log.Warning("ResourceUri", "USB1 not available: " + ex.Message);
168
+ }
169
+ ```
170
+
171
+ ```csharp
172
+ pdfPathStr = new ResourceUri(((IUAVariable)myPathNode).Value).Uri;
173
+ ```
174
+
175
+ ## Set the path of an Image to the ProjectFiles
176
+
177
+ ### Using absolute path
178
+
179
+ > [!TIP]
180
+ > This method is not recommended as it will not work if the project is moved to another folder or computer, and might have issues with the WebPresentationEngine. Use the relative path below.
181
+
182
+ ```csharp
183
+ // Note: the Path.DirectorySeparatorChar is used to make the application cross-platform capable
184
+ singlePiece.Get<Image>("PuzzleImage").Path = ResourceUri.FromProjectRelativePath("").Uri + Path.DirectorySeparatorChar + "imgs" + Path.DirectorySeparatorChar + "Puzzle" + Path.DirectorySeparatorChar + "Piece" + (i + 1).ToString() + ".png";
185
+ ```
186
+
187
+ ### Using relative path
188
+
189
+ > [!TIP]
190
+ > This method is recommended as it will produce a relative path that will work if the project is moved to another folder or computer, and is compatible with the WebPresentationEngine.
191
+
192
+ ```csharp
193
+ [ExportMethod]
194
+ public void Method2()
195
+ {
196
+ var image = Owner.Get<AdvancedSVGImage>("AdvancedSVGImage1");
197
+ // Set the path to %PROJECTDIR%/logo.svg
198
+ image.PathVariable.Value = ResourceUri.FromProjectRelativePath("logo.svg");
199
+ }
200
+ ```
201
+
202
+ ## Get a file from ProjectFiles
203
+
204
+ ```csharp
205
+ string localPath = Path.DirectorySeparatorChar + "imgs" + Path.DirectorySeparatorChar + "Puzzle" + Path.DirectorySeparatorChar + "Piece" + (i + 1).ToString() + ".png";
206
+ singlePiece.Get<Image>("PuzzleImage").Path = "ns=" + singlePiece.NodeId.NamespaceIndex.ToString() + ";%PROJECTDIR%" + localPath;
207
+ ```
208
+
209
+ ```csharp
210
+ string localPath = "imgs" + Path.DirectorySeparatorChar + "Puzzle" + Path.DirectorySeparatorChar + "Piece" + (i + 1).ToString() + ".png";
211
+ singlePiece.Get<Image>("PuzzleImage").Path = Path.Combine(ResourceUri.FromProjectRelativePath(""), localPath);
212
+ ```
213
+
214
+ ```csharp
215
+ // Start from the ProjectDir and get the Template-line-smooth.html file
216
+ var templatePath = ResourceUri.FromProjectRelativePath("Template-line-smooth.html");
217
+ // Remove the Template- prefix
218
+ templatePath = ResourceUri.FromProjectRelativePath(templatePath.Uri.Replace("Template-", ""));
219
+ // Write the new content to the file
220
+ File.WriteAllText(templatePath.Uri, text);
221
+ ```
222
+
223
+ ## USB Check (Windows)
224
+
225
+ ```csharp
226
+ /// <summary>
227
+ /// Periodically checks for removable USB drives (Windows) and logs their presence.
228
+ /// Inputs: none. This is intended for a Runtime NetLogic.
229
+ /// Notes: Uses System.IO.DriveInfo and will only run on platforms that expose drive letters (Windows).
230
+ /// </summary>
231
+ private PeriodicTask task;
232
+
233
+ public override void Start()
234
+ {
235
+ task = new PeriodicTask(CheckUSB, 1000, LogicObject);
236
+ task.Start();
237
+ }
238
+
239
+ private void CheckUSB()
240
+ {
241
+ var usbDevices = System.IO.DriveInfo.GetDrives();
242
+ foreach (var device in usbDevices)
243
+ {
244
+ if (device.DriveType == System.IO.DriveType.Removable && device.IsReady)
245
+ {
246
+ Log.Info("RuntimeNetLogic1", $"USB device found: {device.Name} - {device.VolumeLabel}");
247
+ }
248
+ }
249
+ }
250
+
251
+ public override void Stop()
252
+ {
253
+ task.Dispose();
254
+ }
255
+ ```
256
+
257
+ ## Ensure a directory exists and write a file (Windows)
258
+
259
+ ```csharp
260
+ /// <summary>
261
+ /// Create a folder if missing and write a small test file. Useful when exporting logs or CSV files.
262
+ /// Inputs: NetLogic variables `FolderPath` (string) and `FileName` (string).
263
+ /// </summary>
264
+ [ExportMethod]
265
+ public void CreateFileTest()
266
+ {
267
+ var folder = LogicObject.GetVariable("FolderPath");
268
+ var fileName = LogicObject.GetVariable("FileName");
269
+
270
+ bool exists = System.IO.Directory.Exists(folder.Value.ToString());
271
+
272
+ if (!exists)
273
+ System.IO.Directory.CreateDirectory(folder.Value.ToString());
274
+
275
+ using (var writer = new StreamWriter(System.IO.Path.Combine(folder.Value.ToString(), fileName.Value.ToString())))
276
+ {
277
+ // Write some data rows
278
+ writer.WriteLine("Test file created from NetLogic");
279
+ }
280
+
281
+ }
282
+ ```
283
+
284
+ ## Project / Application / USB resource URIs
285
+
286
+ ```csharp
287
+ /// <summary>
288
+ /// Examples showing how to log or inspect the different ResourceUri roots available from C#.
289
+ /// Use these to resolve files inside the project, application or on attached USB devices.
290
+ /// </summary>
291
+ // Project files root
292
+ Log.Info(ResourceUri.FromProjectRelativePath("").Uri);
293
+
294
+ // Application files root
295
+ Log.Info(ResourceUri.FromApplicationRelativePath("").Uri);
296
+
297
+ // Access USB slot 1 (if present)
298
+ // [!WARNING]
299
+ // Accessing USB paths at runtime can throw if the device is not present - always guard with try/catch.
300
+ try
301
+ {
302
+ Log.Info(ResourceUri.FromUSBRelativePath(1, "").Uri);
303
+ }
304
+ catch (Exception ex)
305
+ {
306
+ Log.Warning("ResourceUri", "USB not available: " + ex.Message);
307
+ }
308
+ ```
@@ -0,0 +1,280 @@
1
+ # Run time import of PLC tags
2
+
3
+ ## Introduction
4
+
5
+ Importing of PLC tags while the application is running is currently only supported for:
6
+
7
+ - TwinCAT
8
+ - RA Ethernet/IP
9
+ - Siemens S7 Profinet
10
+
11
+ > [!NOTE]
12
+ > Runtime import of extended tag attributes from the RA Ethernet/IP driver is not supported yet.
13
+
14
+ ### Warning
15
+
16
+ when importing tags at runtime, especially if filtering is used, a flat list is returned, this means that of you have a structure like:
17
+
18
+ - PLC
19
+ - Motor1 (UDT)
20
+ - Speed (Int32)
21
+ - Torque (Int32)
22
+ - Motor2 (UDT)
23
+ - Speed (Int32)
24
+ - Torque (Int32)
25
+
26
+ And you use the filtering mechanism to extract the `Speed` property of both structures, you will get:
27
+
28
+ - PLC
29
+ - Speed (Int32)
30
+ - Speed (Int32)
31
+
32
+ So, the whole structure should be imported or unique names shall be used
33
+
34
+ ### Code example
35
+
36
+ Browse and import PLC tags
37
+
38
+ ```csharp
39
+ [ExportMethod]
40
+ public void Import()
41
+ {
42
+ var station = Project.Current.Get<Station>("CommDrivers/RAEtherNet_IPDriver1/RAEtherNet_IPStation1");
43
+
44
+ Struct[] instances;
45
+ Struct[] types;
46
+ station.Browse(out instances, out types);
47
+ station.Import(instances, types);
48
+
49
+ var tag = Project.Current.Get<Tag>("CommDrivers/RAEtherNet_IPDriver1/RAEtherNet_IPStation1/Tags/Program:CustomUdtProgram/Increment_INT");
50
+ Log.Info($"Read value of {tag.BrowseName}");
51
+ Log.Info($"{tag.RemoteRead()}");
52
+ }
53
+ ```
54
+
55
+ The following code contains a method for each of the driver where runtime import is supported
56
+
57
+ ```csharp
58
+ #region StandardUsing
59
+ using System;
60
+ using System.Collections.Generic;
61
+ using System.Linq;
62
+ using System.Reflection;
63
+ using FTOptix.CommunicationDriver;
64
+ using FTOptix.HMIProject;
65
+ using UAManagedCore;
66
+ #endregion
67
+
68
+ public class RuntimeTagsImport : FTOptix.NetLogic.BaseNetLogic
69
+ {
70
+ /// <summary>
71
+ /// Import tags from TwinCAT driver
72
+ /// </summary>
73
+ [FTOptix.NetLogic.ExportMethod]
74
+ public static void ImportFromTwincat()
75
+ {
76
+ Log.Info(MethodBase.GetCurrentMethod().Name, "Starting runtime tags import");
77
+
78
+ var station = Project.Current.Get<FTOptix.TwinCAT.Station>("CommDrivers/TwinCATDriver1/TwinCATStation1");
79
+
80
+ // Call Browse method to retrieve data from PLC
81
+ // prototypes parameter for TwinCat is always empty
82
+ station.Browse(out var plcItems, out var prototypes);
83
+
84
+ Log.Info("RuntimeTagsImport.ImportFromTwincat", "Fetched " + plcItems.Length + " tags");
85
+
86
+ // Filter tag to import by tag name
87
+ var tagInstances = FilterTagsToImport(new List<string>() { "SampleTag.A", "SomeOtherTag.B" }, plcItems); //TODO
88
+
89
+ // Delete existing tags
90
+ Log.Info("RuntimeTagsImport.ImportFromTwincat", "Clearing existing tags");
91
+ station.Tags.Clear();
92
+
93
+ Log.Info("RuntimeTagsImport.ImportFromTwincat", "Importing " + tagInstances.Length + " tags");
94
+
95
+ // Filter tag to import by prototypeId
96
+ // var tagInstances = GetTagsOfType("MachineParameter", plcItems);
97
+
98
+ station.Import(tagInstances, prototypes);
99
+
100
+ // Remote read on imported tag
101
+ ReadTag("SampleTag.A", station);
102
+
103
+ // Remote write on imported structure
104
+ // WriteStructure("SomeOtherTag.B", station);
105
+
106
+ // Remote read on imported structure
107
+ // ReadStructure("SomeOtherTag.B", station);
108
+ }
109
+
110
+ /// <summary>
111
+ /// Import tags from Profinet driver
112
+ /// </summary>
113
+ [FTOptix.NetLogic.ExportMethod]
114
+ public static void ImportFromProfinet()
115
+ {
116
+ Log.Info(MethodBase.GetCurrentMethod().Name, "Starting runtime tags import");
117
+
118
+ var station = Project.Current.Get<FTOptix.S7TiaProfinet.Station>("CommDrivers/S7TIAPROFINETDriver1/S7TIAPROFINETStation1");
119
+
120
+ // Call Browse method to retrieve data from PLC
121
+ // prototypes parameter for S7TiaProfinet is always empty
122
+ station.Browse(out var plcItems, out var prototypes);
123
+
124
+ Log.Info("RuntimeTagsImport.ImportFromProfinet", "Fetched " + plcItems.Length + " PLC structures");
125
+
126
+ // Profinet tag are grouped by project, so we need to retrieve tags from root
127
+ var root = plcItems.OfType<BasePlcItem>().ToList();
128
+ // Root node is always the first element in the list
129
+ // This is called "PLC" when browsing from the TagImporter
130
+ var tags = root[0].Items;
131
+ Log.Info("RuntimeTagsImport.ImportFromProfinet", "Fetched " + tags.Length + " tags");
132
+ // Filter tag to import by tag name
133
+ var tagInstances = FilterTagsToImport(new List<string>() { "PLC/TestFix", "PLC/TestDT" }, tags);
134
+
135
+ // Delete existing tags
136
+ Log.Info("RuntimeTagsImport.ImportFromProfinet", "Clearing existing tags");
137
+ station.Tags.Clear();
138
+
139
+ Log.Info("RuntimeTagsImport.ImportFromProfinet", "Importing " + tagInstances.Length + " tags");
140
+
141
+ //Filter tag to import by prototypeId
142
+ //var tagInstances = GetTagsOfType("MachineParameter", tags);
143
+
144
+ station.Import(tagInstances, prototypes);
145
+ Log.Info("Imported " + tagInstances.Length + " tags");
146
+
147
+ // Remote read on imported tag
148
+ ReadTag("SampleTag.A", station);
149
+
150
+ // Remote write on imported structure
151
+ // WriteStructure("SomeOtherTag.B", station);
152
+
153
+ // Remote read on imported structure
154
+ // ReadStructure("SomeOtherTag.B", station);
155
+ }
156
+
157
+ /// <summary>
158
+ /// Import tags from RA Ethernet/IP driver
159
+ /// </summary>
160
+ [FTOptix.NetLogic.ExportMethod]
161
+ public static void ImportFromRAEthernetIP()
162
+ {
163
+ Log.Info("RuntimeTagsImport.ImportFromRAEthernetIP", "Starting runtime tags import");
164
+
165
+ var station = Project.Current.Get<FTOptix.RAEtherNetIP.Station>("CommDrivers/RAEtherNet_IPDriver1/RAEtherNet_IPStation1");
166
+
167
+ // Call Browse method to retrieve data from PLC
168
+ station.Browse(out var plcItems, out var prototypes);
169
+
170
+ Log.Info("RuntimeTagsImport.ImportFromRAEthernetIP", "Fetched " + plcItems.Length + " tags structures from the PLC");
171
+
172
+ // Filter tag to import by tag name
173
+ var tagInstances = FilterTagsToImport(new List<string>() { "Controller Tags/Tank1/Level", "Controller Tags/Tank2/Level" }, plcItems);
174
+
175
+ // Delete existing tags
176
+ Log.Info("RuntimeTagsImport.ImportFromRAEthernetIP", "Clearing existing tags");
177
+ station.Tags.Clear();
178
+
179
+ Log.Info("RuntimeTagsImport.ImportFromRAEthernetIP", "Importing " + tagInstances.Length + " tags");
180
+
181
+ if (tagInstances.Length == 0)
182
+ {
183
+ Log.Warning("RuntimeTagsImport.ImportFromRAEthernetIP", "No tags to import. Returning.");
184
+ return;
185
+ }
186
+
187
+ //Filter tag to import by prototypeId
188
+ // var tagInstances = GetTagsOfType("MachineParameter", plcItems);
189
+
190
+ station.Import(tagInstances, prototypes);
191
+
192
+ // Remote read on imported tag
193
+ ReadTag("Controller Tags/Tank1/Level", station);
194
+
195
+ // Remote write on imported structure
196
+ // WriteStructure("Controller Tags/Tank1/Level", station);
197
+
198
+ // Remote read on imported structure
199
+ // ReadStructure("Controller Tags/Tank1", station);
200
+ }
201
+
202
+ /// <summary>
203
+ /// Filter tags to import by tag name.
204
+ /// Expects a list of tag paths to import (like "Controller Tags/Motor1/Speed" and
205
+ /// the list of all tags that were fetch from the PLC.
206
+ /// If the list of tags to import is empty, all fetched tags will be returned.
207
+ /// Warning: a flat list is returned, so the tag paths must be unique.
208
+ /// </summary>
209
+ /// <param name="tagPathsToImport">List of tag paths to import (path with forward slashes "/")</param>
210
+ /// <param name="plcItems">List of all tags fetched from the PLC</param>
211
+ /// <returns>List of tags (Struct[]) that matches the input list</returns>
212
+ private static Struct[] FilterTagsToImport(List<string> tagPathsToImport, Struct[] plcItems)
213
+ {
214
+ var listOfFoundTags = new List<Struct>();
215
+
216
+ // If no list of tags to import was provided, return all tags
217
+ if (tagPathsToImport.Count == 0)
218
+ {
219
+ Log.Warning("RuntimeTagsImport.FilterTagsToImport", "No list of tags to import was provided. Returning all tags.");
220
+ return plcItems;
221
+ }
222
+
223
+ if (plcItems.Length == 0)
224
+ {
225
+ Log.Warning("RuntimeTagsImport.FilterTagsToImport", "No tags were fetched from the PLC. Returning empty list.");
226
+ return plcItems;
227
+ }
228
+
229
+ // Filter tags to import by tag name
230
+ foreach (string tagPath in tagPathsToImport)
231
+ {
232
+ string[] splitPath = tagPath.Split('/');
233
+ var node = plcItems;
234
+ for (int i = 0; i < splitPath.Length; i++)
235
+ {
236
+ for (int k = 0; k < node.Length; k++)
237
+ {
238
+ if (((BasePlcItem)node[k]).Name == splitPath[i])
239
+ {
240
+ if (i == splitPath.Length - 1)
241
+ {
242
+ listOfFoundTags.Add(node[k]);
243
+ break;
244
+ }
245
+ else
246
+ {
247
+ node = ((BasePlcItem)node[k]).Items;
248
+ break;
249
+ }
250
+ }
251
+ }
252
+ }
253
+ }
254
+
255
+ return listOfFoundTags.ToArray();
256
+ }
257
+
258
+ /// <summary>
259
+ /// Performs a remote read on a tag.
260
+ /// </summary>
261
+ /// <param name="tagName"></param>
262
+ /// <param name="station"></param>
263
+ private static void ReadTag(string tagName, FTOptix.CommunicationDriver.CommunicationStation station)
264
+ {
265
+ try
266
+ {
267
+ var tag = station.Get<FTOptix.CommunicationDriver.Tag>("Tags/" + tagName);
268
+ var result = tag.RemoteRead();
269
+ Log.Info($"RemoteRead succeed -> {tagName}: {result.Value}");
270
+ }
271
+ catch (Exception e)
272
+ {
273
+ Log.Error("RemoteRead failed: " + e.Message);
274
+ }
275
+ }
276
+ }
277
+
278
+
279
+ ```
280
+