@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.
- package/CHANGELOG.md +20 -0
- package/README.md +6 -6
- 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/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,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
|
+
|