@aryacode/dsh-engineering 0.5.0 → 0.7.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 +52 -0
- package/README.md +9 -8
- package/cordis.patch.yml +1 -1
- package/entries/netlogic.js +266 -0
- package/entries/optix.js +13 -0
- package/package.json +10 -10
- package/vendor/l5x/SKILL.md +27 -7
- package/vendor/l5x/lib/engine/change-set.d.ts +9 -1
- package/vendor/l5x/lib/engine/change-set.js +32 -7
- package/vendor/l5x/lib/engine/documentation.d.ts +58 -0
- package/vendor/l5x/lib/engine/documentation.js +237 -0
- package/vendor/l5x/lib/engine/instructions.d.ts +50 -0
- package/vendor/l5x/lib/engine/instructions.js +107 -0
- package/vendor/l5x/lib/engine/ladder.d.ts +74 -0
- package/vendor/l5x/lib/engine/ladder.js +209 -0
- package/vendor/l5x/lib/index.d.ts +1 -0
- package/vendor/l5x/lib/index.js +3 -1
- package/vendor/l5x/lib/model/routines.js +6 -1
- package/vendor/l5x/lib/session.d.ts +20 -0
- package/vendor/l5x/lib/session.js +36 -0
- package/vendor/l5x/lib/tools/document.d.ts +18 -0
- package/vendor/l5x/lib/tools/document.js +119 -0
- package/vendor/l5x/lib/tools/drafts.js +109 -3
- 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,15 @@
|
|
|
1
|
+
# Regex
|
|
2
|
+
|
|
3
|
+
## Extract alphanumeric characters from a string
|
|
4
|
+
|
|
5
|
+
```csharp
|
|
6
|
+
Regex regexFileName = new Regex("[^a-zA-Z0-9 -]");
|
|
7
|
+
fileName = regexFileName.Replace(fileName, "");
|
|
8
|
+
```
|
|
9
|
+
|
|
10
|
+
## Extract the table name from a query
|
|
11
|
+
|
|
12
|
+
```csharp
|
|
13
|
+
Regex regexTable = new Regex("(?<=\\bFROM\\s)(\\w+)");
|
|
14
|
+
Var tableName = regexTable.Match(Query, 0, Query.Length)
|
|
15
|
+
```
|
|
@@ -0,0 +1,346 @@
|
|
|
1
|
+
# OPC/UA Observers
|
|
2
|
+
|
|
3
|
+
> [!WARNING]
|
|
4
|
+
> This page describes low-level features and should be used with caution as they can potentially lead to unexpected results or even break your project.
|
|
5
|
+
> These features are not officially supported and may be subject to changes in future releases.
|
|
6
|
+
|
|
7
|
+
> [!NOTE]
|
|
8
|
+
> Observers can only be registered at runtime. Attempting to register an observer at design time will not work.
|
|
9
|
+
|
|
10
|
+
The OPC/UA protocol implements some ways to monitor changes in the nodes tree, this allows the creation of observers that can monitor some project nodes and triggers some logics based on a set of events (which can be specified using a mask).
|
|
11
|
+
|
|
12
|
+
This technique allows multiple scenarios like:
|
|
13
|
+
|
|
14
|
+
- Detecting the nodes status (started, stopped, attached, detached, dispose)
|
|
15
|
+
- Detecting changes in values
|
|
16
|
+
- Detecting changes in attributes
|
|
17
|
+
- Detecting changes in references
|
|
18
|
+
- Detecting OPC/UA events
|
|
19
|
+
|
|
20
|
+
Subscribers must be added to all the elements that needs to be monitored, attaching an observer to the `Model` folder, won't enable the listener on all children nodes
|
|
21
|
+
|
|
22
|
+
## References to OPC/UA Specifications
|
|
23
|
+
|
|
24
|
+
- **OPC UA Part 4 - Services**: Describes the services provided by the OPC UA server, including event subscriptions.
|
|
25
|
+
- **OPC UA Part 9 - Alarms & Conditions**: Provides details on how events and conditions are managed in OPC UA.
|
|
26
|
+
|
|
27
|
+
For more detailed information, refer to the [OPC UA Specifications](https://opcfoundation.org/developer-tools/specifications-unified-architecture).
|
|
28
|
+
|
|
29
|
+
## Intercepting FactoryTalk Optix Runtime messages
|
|
30
|
+
|
|
31
|
+
The messages that are printed to the "Runtime Output" (or "Emulator Output") are generated as per OPC/UA specs by the Server object, which can be subscribed to trigger custom logics
|
|
32
|
+
|
|
33
|
+
```csharp
|
|
34
|
+
#region Using directives
|
|
35
|
+
using System;
|
|
36
|
+
using UAManagedCore;
|
|
37
|
+
using OpcUa = UAManagedCore.OpcUa;
|
|
38
|
+
using FTOptix.NetLogic;
|
|
39
|
+
using System.Collections.Generic;
|
|
40
|
+
using LogsHandler;
|
|
41
|
+
#endregion
|
|
42
|
+
|
|
43
|
+
public class RuntimeLoggerMonitor : BaseNetLogic
|
|
44
|
+
{
|
|
45
|
+
public override void Start()
|
|
46
|
+
{
|
|
47
|
+
// Build the observer for the output messages
|
|
48
|
+
StartObserver();
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
public override void Stop()
|
|
52
|
+
{
|
|
53
|
+
// Destroy the observer when closing the NetLogic
|
|
54
|
+
logsRegistration?.Dispose();
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
public void StartObserver()
|
|
58
|
+
{
|
|
59
|
+
// Get the server object
|
|
60
|
+
var serverObject = LogicObject.Context.GetObject(OpcUa.Objects.Server);
|
|
61
|
+
// Create a new observer
|
|
62
|
+
var logsObserver = new LogsHandler.LogsEventObserver();
|
|
63
|
+
// Register the observer to the server node
|
|
64
|
+
logsRegistration = serverObject.RegisterUAEventObserver(logsObserver, FTOptix.Core.ObjectTypes.LogEvent);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// This node must stay "alive" as long as the subscriber is needed
|
|
68
|
+
// Do not move it to a local variable or the observer won't work
|
|
69
|
+
private IEventRegistration logsRegistration;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
namespace LogsHandler
|
|
73
|
+
{
|
|
74
|
+
class LogsEventObserver : IUAEventObserver
|
|
75
|
+
{
|
|
76
|
+
public LogsEventObserver()
|
|
77
|
+
{
|
|
78
|
+
Log.Info("LogsEventObserver", "Starting observer");
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
public void OnEvent(IUAObject eventNotifier, IUAObjectType eventType, IReadOnlyList<object> args, ulong senderId)
|
|
82
|
+
{
|
|
83
|
+
Console.WriteLine(((LocalizedText) args[7]).Text); // Content of the message
|
|
84
|
+
Console.WriteLine(args[15]); // Module name
|
|
85
|
+
Console.WriteLine(args[13]); // Message level
|
|
86
|
+
// Do not use `Log.` events here or it will loop!
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
## Subscribe to methods invoke
|
|
94
|
+
|
|
95
|
+
This script can be put anywhere on the project and will listen to every OPCUA method being called (script or UI)
|
|
96
|
+
|
|
97
|
+
```csharp
|
|
98
|
+
using System.Collections.Generic;
|
|
99
|
+
using System.Text;
|
|
100
|
+
using FTOptix.NetLogic;
|
|
101
|
+
using OpcUa = UAManagedCore.OpcUa;
|
|
102
|
+
using UAManagedCore;
|
|
103
|
+
|
|
104
|
+
public class RuntimeNetLogic1 : BaseNetLogic
|
|
105
|
+
{
|
|
106
|
+
public override void Start()
|
|
107
|
+
{
|
|
108
|
+
var serverObject = LogicObject.Context.GetObject(OpcUa.Objects.Server);
|
|
109
|
+
var eventHandler = new EventsHandler.EventsHandler();
|
|
110
|
+
eventRegistration = serverObject.RegisterUAEventObserver(eventHandler, UAManagedCore.OpcUa.ObjectTypes.AuditUpdateMethodEventType);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
public override void Stop()
|
|
114
|
+
{
|
|
115
|
+
eventRegistration?.Dispose();
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
private IEventRegistration eventRegistration;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
namespace EventsHandler
|
|
122
|
+
{
|
|
123
|
+
public class EventsHandler : IUAEventObserver
|
|
124
|
+
{
|
|
125
|
+
public EventsHandler()
|
|
126
|
+
{
|
|
127
|
+
Log.Info("EventsHandler", "EventsHandler instance created");
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
public void OnEvent(IUAObject eventNotifier, IUAObjectType eventType, IReadOnlyList<object> eventData, ulong senderId)
|
|
131
|
+
{
|
|
132
|
+
StringBuilder builder = new StringBuilder();
|
|
133
|
+
builder.Append($"Event of type {eventType.BrowseName} triggered");
|
|
134
|
+
var eventArguments = eventType.EventArguments;
|
|
135
|
+
foreach (var eventField in eventArguments.GetFields())
|
|
136
|
+
{
|
|
137
|
+
var fieldValue = eventArguments.GetFieldValue(eventData, eventField);
|
|
138
|
+
builder.Append($"\t{eventField} = {fieldValue?.ToString() ?? "null"}");
|
|
139
|
+
}
|
|
140
|
+
builder.Append("\n");
|
|
141
|
+
Log.Info(builder.ToString());
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
## Track the login and logout of users and get the list of groups for the current user
|
|
148
|
+
|
|
149
|
+
This example leverages the observers to track the login and logout of users, as well as to get the list of groups for the current user.
|
|
150
|
+
|
|
151
|
+
```csharp
|
|
152
|
+
using System.Collections.Generic;
|
|
153
|
+
using System.Text;
|
|
154
|
+
using FTOptix.NetLogic;
|
|
155
|
+
using OpcUa = UAManagedCore.OpcUa;
|
|
156
|
+
using UAManagedCore;
|
|
157
|
+
|
|
158
|
+
public class RuntimeNetLogic1 : BaseNetLogic
|
|
159
|
+
{
|
|
160
|
+
public override void Start()
|
|
161
|
+
{
|
|
162
|
+
var serverObject = LogicObject.Context.GetObject(OpcUa.Objects.Server);
|
|
163
|
+
var eventHandler = new EventsHandler.EventsHandler();
|
|
164
|
+
eventRegistration = serverObject.RegisterUAEventObserver(eventHandler, FTOptix.Core.ObjectTypes.UserSessionEvent);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
public override void Stop()
|
|
168
|
+
{
|
|
169
|
+
eventRegistration?.Dispose();
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
private IEventRegistration eventRegistration;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
namespace EventsHandler
|
|
176
|
+
{
|
|
177
|
+
public class EventsHandler : IUAEventObserver
|
|
178
|
+
{
|
|
179
|
+
public EventsHandler()
|
|
180
|
+
{
|
|
181
|
+
Log.Info("EventsHandler", "EventsHandler instance created");
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
public void OnEvent(IUAObject eventNotifier, IUAObjectType eventType, IReadOnlyList<object> eventData, ulong senderId)
|
|
185
|
+
{
|
|
186
|
+
// Log the event to the console
|
|
187
|
+
StringBuilder builder = new StringBuilder();
|
|
188
|
+
builder.Append($"Event of type {eventType.BrowseName} triggered");
|
|
189
|
+
var eventArguments = eventType.EventArguments;
|
|
190
|
+
foreach (var eventField in eventArguments.GetFields())
|
|
191
|
+
{
|
|
192
|
+
var fieldValue = eventArguments.GetFieldValue(eventData, eventField);
|
|
193
|
+
builder.Append($"\t{eventField} = {fieldValue?.ToString() ?? "null"}");
|
|
194
|
+
}
|
|
195
|
+
Log.Debug(builder.ToString());
|
|
196
|
+
|
|
197
|
+
// Optional: only operate on Login event
|
|
198
|
+
if ((string)eventArguments.GetFieldValue(eventData, "SourceName") == "Login")
|
|
199
|
+
{
|
|
200
|
+
// Get the groups linked to the current user
|
|
201
|
+
var userNodeId = (NodeId)eventArguments.GetFieldValue(eventData, "UserId");
|
|
202
|
+
// Get the user object from the node ID
|
|
203
|
+
var newUser = InformationModel.Get<User>(userNodeId);
|
|
204
|
+
// Get all groups the user is part of
|
|
205
|
+
var userGroups = newUser.Refs.GetObjects(FTOptix.Core.ReferenceTypes.HasGroup, false);
|
|
206
|
+
// Create a string with the groups and log it
|
|
207
|
+
builder = new StringBuilder();
|
|
208
|
+
builder.Append($"User {newUser.BrowseName} is part of the following groups: ");
|
|
209
|
+
foreach (var group in userGroups)
|
|
210
|
+
{
|
|
211
|
+
builder.Append($"{group.BrowseName}, ");
|
|
212
|
+
}
|
|
213
|
+
// Remove the last comma and space
|
|
214
|
+
if (userGroups.Count > 0)
|
|
215
|
+
{
|
|
216
|
+
builder.Length -= 2; // Remove the last comma and space
|
|
217
|
+
}
|
|
218
|
+
else
|
|
219
|
+
{
|
|
220
|
+
builder.Append("No groups found");
|
|
221
|
+
}
|
|
222
|
+
Log.Info(builder.ToString());
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
## Subscribe to alarm events
|
|
230
|
+
|
|
231
|
+
This code subscribes to every event being performed on any alarm in the project, specific filtering can be added on the `OnEvent` to perform different operations when different actions are triggered on the alarm (like acknowledging or silencing the alarm).
|
|
232
|
+
|
|
233
|
+
```csharp
|
|
234
|
+
using System.Collections.Generic;
|
|
235
|
+
using System.Text;
|
|
236
|
+
using FTOptix.NetLogic;
|
|
237
|
+
using OpcUa = UAManagedCore.OpcUa;
|
|
238
|
+
using UAManagedCore;
|
|
239
|
+
using FTOptix.Alarm;
|
|
240
|
+
|
|
241
|
+
public class RuntimeNetLogic1 : BaseNetLogic
|
|
242
|
+
{
|
|
243
|
+
public override void Start()
|
|
244
|
+
{
|
|
245
|
+
var serverObject = LogicObject.Context.GetObject(OpcUa.Objects.Server);
|
|
246
|
+
var eventHandler = new EventsHandler.EventsHandler();
|
|
247
|
+
// Subscribe to alarm events
|
|
248
|
+
eventRegistration = serverObject.RegisterUAEventObserver(eventHandler, UAManagedCore.OpcUa.ObjectTypes.AlarmConditionType);
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
public override void Stop()
|
|
252
|
+
{
|
|
253
|
+
eventRegistration?.Dispose();
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
private IEventRegistration eventRegistration;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
namespace EventsHandler
|
|
260
|
+
{
|
|
261
|
+
public class EventsHandler : IUAEventObserver
|
|
262
|
+
{
|
|
263
|
+
public EventsHandler()
|
|
264
|
+
{
|
|
265
|
+
Log.Info("EventsHandler", "EventsHandler instance created");
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
public void OnEvent(IUAObject eventNotifier, IUAObjectType eventType, IReadOnlyList<object> eventData, ulong senderId)
|
|
269
|
+
{
|
|
270
|
+
StringBuilder builder = new StringBuilder();
|
|
271
|
+
builder.Append($"Event of type {eventType.BrowseName} triggered");
|
|
272
|
+
var eventArguments = eventType.EventArguments;
|
|
273
|
+
foreach (var eventField in eventArguments.GetFields())
|
|
274
|
+
{
|
|
275
|
+
var fieldValue = eventArguments.GetFieldValue(eventData, eventField);
|
|
276
|
+
builder.Append($"\t{eventField} = {fieldValue?.ToString() ?? "null"}");
|
|
277
|
+
}
|
|
278
|
+
builder.Append("\n");
|
|
279
|
+
Log.Info(builder.ToString());
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
```
|
|
284
|
+
|
|
285
|
+
## Monitor adding/removing users at runtime
|
|
286
|
+
|
|
287
|
+
```csharp
|
|
288
|
+
/// <summary>
|
|
289
|
+
/// Example Runtime NetLogic to observe additions and removals of users under Security/Users.
|
|
290
|
+
/// The observer registers to forward reference added/removed events and logs the change.
|
|
291
|
+
/// </summary>
|
|
292
|
+
public class UserCreationObserverLogic : BaseNetLogic
|
|
293
|
+
{
|
|
294
|
+
private IEventRegistration userCreationObserver;
|
|
295
|
+
private uint affinityId; // store affinity to avoid cross-thread issues
|
|
296
|
+
|
|
297
|
+
public override void Start()
|
|
298
|
+
{
|
|
299
|
+
// Get the affinity ID of the current NetLogic to avoid cross thread issues
|
|
300
|
+
affinityId = LogicObject.Context.AssignAffinityId();
|
|
301
|
+
// Create and register the observer
|
|
302
|
+
StartObserver();
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
public override void Stop()
|
|
306
|
+
{
|
|
307
|
+
// Dispose the observer when stopping
|
|
308
|
+
userCreationObserver?.Dispose();
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
public void StartObserver()
|
|
312
|
+
{
|
|
313
|
+
// Get the user folder server object
|
|
314
|
+
var userObject = LogicObject.Context.GetNode(Project.Current.Get("Security/Users").NodeId);
|
|
315
|
+
// Create a new observer instance
|
|
316
|
+
var logsObserver = new LogsHandler.UserCreationObserver();
|
|
317
|
+
// Register the observer to the server node
|
|
318
|
+
userCreationObserver = userObject.RegisterEventObserver(
|
|
319
|
+
logsObserver,
|
|
320
|
+
EventType.ForwardReferenceAdded | // when users are created
|
|
321
|
+
EventType.ForwardReferenceRemoved, // when users are deleted
|
|
322
|
+
affinityId);
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
namespace LogsHandler
|
|
327
|
+
{
|
|
328
|
+
class UserCreationObserver : IReferenceObserver
|
|
329
|
+
{
|
|
330
|
+
public UserCreationObserver()
|
|
331
|
+
{
|
|
332
|
+
Log.Info("LogsEventObserver", "Starting users observer");
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
public void OnReferenceAdded(IUANode sourceNode, IUANode targetNode, NodeId referenceTypeId, ulong senderId)
|
|
336
|
+
{
|
|
337
|
+
Log.Info("LogsEventObserver", $"{targetNode.BrowseName} user is added");
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
public void OnReferenceRemoved(IUANode sourceNode, IUANode targetNode, NodeId referenceTypeId, ulong senderId)
|
|
341
|
+
{
|
|
342
|
+
Log.Info("LogsEventObserver", $"{targetNode.BrowseName} user got removed");
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
```
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
# Reports
|
|
2
|
+
|
|
3
|
+
## Generate a report and trigger some action based on the result code
|
|
4
|
+
|
|
5
|
+
> [!NOTE]
|
|
6
|
+
> Report generation can only be triggered at runtime.
|
|
7
|
+
|
|
8
|
+
```csharp
|
|
9
|
+
#region Using directives
|
|
10
|
+
using System;
|
|
11
|
+
using System.IO;
|
|
12
|
+
using System.Linq;
|
|
13
|
+
using System.Reflection.Emit;
|
|
14
|
+
using FTOptix.Core;
|
|
15
|
+
using FTOptix.HMIProject;
|
|
16
|
+
using FTOptix.NetLogic;
|
|
17
|
+
using FTOptix.Report;
|
|
18
|
+
using FTOptix.UI;
|
|
19
|
+
using UAManagedCore;
|
|
20
|
+
#endregion
|
|
21
|
+
|
|
22
|
+
public partial class PdfReportLogic : BaseNetLogic
|
|
23
|
+
{
|
|
24
|
+
public override void Start()
|
|
25
|
+
{
|
|
26
|
+
// Try to assign a value to the button
|
|
27
|
+
try
|
|
28
|
+
{
|
|
29
|
+
generatePdfButton = Owner.Get<Button>("TrackedValues/Generate");
|
|
30
|
+
viewPdfButton = Owner.Get<Button>("TrackedValues/View");
|
|
31
|
+
viewPdfButton.Enabled = false;
|
|
32
|
+
}
|
|
33
|
+
catch
|
|
34
|
+
{
|
|
35
|
+
// Button does not exist
|
|
36
|
+
Log.Warning("PdfReportLogic", "Can't find PDF buttons, maybe they were renamed?");
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
public override void Stop()
|
|
41
|
+
{
|
|
42
|
+
myReport.OnGeneratePdfCompleted -= MyReport_OnGeneratePdfCompleted;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
[ExportMethod]
|
|
46
|
+
public void GenerateReport(string fileName, string pdfLocale)
|
|
47
|
+
{
|
|
48
|
+
// Hide button
|
|
49
|
+
generatePdfButton.Enabled = false;
|
|
50
|
+
|
|
51
|
+
// Check if the path is empty
|
|
52
|
+
if (string.IsNullOrEmpty(fileName))
|
|
53
|
+
{
|
|
54
|
+
Log.Error("PdfReportLogic", "Empty PDF name");
|
|
55
|
+
SetOutputMessage("ReportEmptyPath");
|
|
56
|
+
throw new ArgumentNullException();
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Check if the pdf has the extension
|
|
60
|
+
if (!fileName.EndsWith(".pdf", StringComparison.InvariantCultureIgnoreCase))
|
|
61
|
+
{
|
|
62
|
+
Log.Warning("PdfReportLogic", "PDF extension not found, adding it");
|
|
63
|
+
fileName += ".pdf";
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Make sure to use only the file name
|
|
67
|
+
fileName = fileName.Split('\\', '/').Last();
|
|
68
|
+
|
|
69
|
+
// Check if the path is an Optix path variable
|
|
70
|
+
var pdfResourceUri = ResourceUri.FromProjectRelativePath(fileName);
|
|
71
|
+
|
|
72
|
+
// Check if the locale is valid
|
|
73
|
+
if (string.IsNullOrEmpty(pdfLocale))
|
|
74
|
+
{
|
|
75
|
+
Log.Error("PdfReportLogic", "Empty locale");
|
|
76
|
+
SetOutputMessage("ReportEmptyLocale");
|
|
77
|
+
throw new ArgumentNullException();
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// Check if the locale is valid using regex
|
|
81
|
+
if (!LocaleIdRegex().IsMatch(pdfLocale))
|
|
82
|
+
{
|
|
83
|
+
Log.Error("PdfReportLogic", "Invalid locale");
|
|
84
|
+
SetOutputMessage("ReportInvalidLocale");
|
|
85
|
+
throw new ArgumentException();
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Generate the report
|
|
89
|
+
myReport = Project.Current.Get<Report>("Reports/LoggerReport");
|
|
90
|
+
Log.Debug("PdfReportLogic", $"Generating PDF report with locale: {pdfLocale}");
|
|
91
|
+
Log.Debug("PdfReportLogic", $"Saving PDF report to: {pdfResourceUri.Uri}");
|
|
92
|
+
myReport.GeneratePdf(pdfResourceUri, pdfLocale, out Guid operationId);
|
|
93
|
+
|
|
94
|
+
// Subscribe to the report generation event
|
|
95
|
+
myReport.OnGeneratePdfCompleted += MyReport_OnGeneratePdfCompleted;
|
|
96
|
+
|
|
97
|
+
Log.Debug("PdfReportLogic", $"Report generation started with operation ID: {operationId}");
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
private void MyReport_OnGeneratePdfCompleted(object sender, GeneratePdfCompletedEvent e)
|
|
101
|
+
{
|
|
102
|
+
Log.Info("PdfReportLogic", $"Report generation completed with result: {e.Result}");
|
|
103
|
+
if (e.Result == GeneratePdfCompletedResult.PdfSuccessfullyGenerated)
|
|
104
|
+
{
|
|
105
|
+
SetOutputMessage("ReportSuccess");
|
|
106
|
+
viewPdfButton.Enabled = true;
|
|
107
|
+
}
|
|
108
|
+
else
|
|
109
|
+
{
|
|
110
|
+
SetOutputMessage("ReportFailed");
|
|
111
|
+
viewPdfButton.Enabled = false;
|
|
112
|
+
Log.Error("PdfReportLogic", $"PDF generation failed, error: {e.Result}");
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
generatePdfButton.Enabled = true;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// Set output message
|
|
119
|
+
private void SetOutputMessage(string translationKey)
|
|
120
|
+
{
|
|
121
|
+
var outputLabel = Owner.Get<FTOptix.UI.Label>("TrackedValues/OutputMessage");
|
|
122
|
+
try
|
|
123
|
+
{
|
|
124
|
+
outputLabel.LocalizedText = new LocalizedText(outputLabel.NodeId.NamespaceIndex, translationKey);
|
|
125
|
+
}
|
|
126
|
+
catch
|
|
127
|
+
{
|
|
128
|
+
Log.Warning("PdfReportLogic", $"Translation key not found: {translationKey}");
|
|
129
|
+
outputLabel.Text = translationKey;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// Regex for locale ID
|
|
134
|
+
[System.Text.RegularExpressions.GeneratedRegex("^[a-z]{2}-[A-Z]{2}$")]
|
|
135
|
+
private static partial System.Text.RegularExpressions.Regex LocaleIdRegex();
|
|
136
|
+
// PDF generator button
|
|
137
|
+
private FTOptix.UI.Button generatePdfButton;
|
|
138
|
+
private FTOptix.UI.Button viewPdfButton;
|
|
139
|
+
private FTOptix.Report.Report myReport;
|
|
140
|
+
}
|
|
141
|
+
```
|